ベクトルデータベースは誤った抽象化である
(timescale.com)- AIアプリケーションを構築しようとするエンジニアリングチームを悩ませるメッセージ: 「埋め込みが再同期されていません」
- シンプルなベクトル検索の実装は、監視・同期・トラブルシューティングの複雑なオーケストレーションへと発展する
- ベクトルデータベースでAIシステムを構築しているエンジニアリングチームへの取材から、ベクトルデータベースの抽象化が誤っており、現在の使われ方にも欠陥があることが分かった
「RAGシステムを構築する際によくあるケース」
- Pineconeをベクトルデータベースとして使い、埋め込みを保存・検索する
- テキストデータがPineconeのメタデータにうまく収まらないため、blobやアプリケーションデータはDynamoDBで扱う
- 語彙検索のためにOpenSearchも必要だった
- その結果、3つのシステムを接続して同期するのが悪夢になる
ソースドキュメントを削除する際に必要な作業:
- boto3を実行してDynamoDBからレコードを削除
- Pineconeを更新して埋め込みが削除されたことを確認
- 語彙検索インデックスを更新するためのPOSTリクエストが必要
- すべてのソースドキュメントの更新・追加・削除ごとにこれを行わなければならない
- 構成管理は煩雑なだけでなく危険でもある
- 4か月前に削除すべきだったインデックスに対して、月額2,000ドルを払い続けていたチームもあった
- 誤ったデータや古いデータをユーザーに返してしまうリスクがある
ベクトルデータベースは誤った抽象化の上に構築されている
- ベクトルデータベースは埋め込みを派生データではなく独立したデータとして扱う
- 埋め込みを独立データとして扱うことで、不必要な複雑さが生まれる
より良い方法: 「Vectorizer」抽象化
- 埋め込みをデータベースインデックスのように扱う「ベクトライザー」抽象化を提案する
- このアプローチでは埋め込みをソースデータと自動的に同期させ、保守コストを取り除く
CREATE INDEXコマンドに相当するベクトライザーは次のようになる:
SELECT ai.create_vectorizer(
'public.blogs'::regclass,
embedding => ai.embedding_openai('text-embedding-3-small', 1536),
chunking => ai.chunking_recursive_character_text_splitter('content')
);
- このコマンドは、blogsテーブル内のすべての項目に対する埋め込みを生成し、テーブルのデータが変更されるたびに埋め込みを継続的に更新する
ベクトルデータベース(およびベクトルデータ型)の問題点
- ベクトルデータベースは、テキスト・画像・マルチモーダルデータに対する大量のベクトル埋め込みを処理するために開発された
- PostgreSQL、MySQL、MongoDB、Oracleのような汎用データベースもベクトル検索のサポートを追加している
- しかし、スタンドアロンシステムであれ既存データベースに追加されたベクトル検索機能であれ、その抽象化には致命的な欠陥がある
- 埋め込みがデータベースに挿入された時点で、埋め込み対象の非構造化データとベクトル埋め込みそのものの間の結び付きが断たれる
- この結び付きがないため、埋め込みは派生データではなく、開発者が管理しなければならない独立したデータアーティファクトとして誤って扱われる
- 埋め込みを派生データとして再定義すると、ソースデータと結び付いていない現在のベクトルデータベース抽象化の不合理さが明確になる
開発チームが管理しなければならないもの:
-
複雑なETL(抽出・ロード・変換)パイプライン
-
埋め込み用のベクトルデータベース、メタデータとアプリデータ用の別のデータベース、語彙検索インデックス
-
データ同期サービス
-
更新と同期のためのキューイングシステム
-
データドリフトを検知し、埋め込みサービスのレート制限などに対処する監視ツール
-
検索が古い結果を返したときのアラートシステム
-
すべてのシステムに対する検証チェック
-
新しい埋め込みモデルへアップグレードしたり、別のチャンク分割方法を試したりするには、カスタムコードを書き、複数のデータサービスやデータベースにまたがって変更を調整しなければならない
-
こうした作業は、ソースデータの変更に応じて適時に埋め込みが生成されることを保証する負担を開発チームに強いる
-
そうしなければ、埋め込みはしばしば古くなり、ユーザー体験を悪化させるリスクがある
より良い方法: データベースに複雑さを処理させる
- 埋め込みを派生データとして再定義すれば、ベースとなるデータの変更に応じた埋め込みの生成・更新の責任をDBMSに任せられる
- この変更により、開発者は埋め込みをソースデータと手動で同期し続ける負担から解放される
- この違いは、RAG向けに一度きりのデータ取り込みを行う単純なアプリケーションでは重要でないかもしれない
- しかし、ほとんどの実際のアプリケーションではデータは継続的に変化する
- たとえば、埋め込みベースのセマンティック検索を使うECプラットフォームや、最新の商品情報を維持する必要がある商品アシスタントRAGアプリを考えてほしい
- こうした変更を手動で追跡して埋め込みを再生成するのは、労力がかかりミスも起こりやすいだけでなく、開発者が中核となるビジネス目標に集中することを妨げる
- データベースシステムが自動で処理できるのに、なぜ開発時間を無駄にするのか?
ベクトライザー: インデックスとしてのベクトル埋め込み
- ベクトル埋め込みを、独立したテーブルやデータ型ではなく、埋め込み対象データに対する特殊なインデックスとして捉える方が、より有効な抽象化である
- ベクトル埋め込みは、従来の意味でのインデックスではない
- その代わり、埋め込みに基づいて最も関連性の高いデータ部分を検索するインデクシング機構として機能する
- この新しいインデックス類似の抽象化を、私たちは「ベクトライザー」と呼べる
ベクトライザー抽象化の主な利点:
自動同期
- データベースインデックスの主な利点の1つは、基になるデータとインデックスを自動的に同期状態に保てることだ
- カラムのデータが変われば、インデックスもそれに応じて更新される
- ベクトル埋め込みをインデクシングの一形態として扱うことで、同じ自動同期の仕組みを活用できる
- システムはベクトル埋め込みが常に最新データと同期していることを保証し、手動更新の必要をなくし、エラーリスクを減らす
データと埋め込みの関係強化
- ベクトルを独立して保存すると、元データとの関係を失いやすい
- このベクトルは最近のデータ更新から生成されたものか? それとも以前の埋め込みモデルによる古いベクトルなのか?
- こうした問いは重要であり、ここで混乱すると深刻なエラーにつながりかねない
- ベクトル埋め込みをインデックスとしてデータに直接結び付けることで、その関係は明確になり、自動的に維持される
データ管理の簡素化
- 開発者は、データ同期を手動で管理しようとするとしばしば困難に直面する
- たとえば、基のデータが削除された際に以前の埋め込みモデル由来のデータ削除を忘れると、不整合が生じる
- ベクトライザー抽象化はこうした関係管理をシステムの責任にし、開発者の認知負荷を減らし、ミスの可能性を最小化する
ベクトライザーはDBMSの中核的な約束の自然な進化
- ベクトライザーという概念は、現代のDBMS機能の自然な進化である
- 今日のDBMSは、インデックス、トリガー、マテリアライズドビューのような宣言的構造を通じて、データ変換や同期の管理にすでに長けている
- ベクトライザー抽象化は、このパラダイムにうまく適合し、重要性を増し続けるベクトル埋め込み管理のための新しいツールを提供する
- この機能をDBMSに直接組み込むことで、データベースシステムの究極的な約束の実現にさらに近づく
- つまり、複雑さを抽象化することでデータを管理し、ユーザーがアプリケーション構築、データ分析、イノベーション推進といった本来得意なことに集中できるようにする
PostgreSQL向けベクトライザー実装: pgai Vectorizer
- 開発者の負担を軽くするという約束に動機づけられ、TimescaleのAIエンジニアリングチームはPostgreSQL向けベクトライザーを実装した
- その名はpgai Vectorizerで、現在Early Accessにある
- これはPGAIプロジェクトの一部であり、PostgreSQLをAIシステムにより適したものにし、PostgreSQLに慣れた開発者がAI開発を容易に行えるようにすることを目的としている
- pgai VectorizerがPostgreSQL内のデータに対してベクトル埋め込みを自動生成・更新する様子は、デモ動画で確認できる
pgai Vectorizerの仕組み
- SQLでベクトライザーを定義・作成する
- 次のクエリはベクトライザーを作成し、対象テーブル、ベクトル化するカラム、使用する埋め込みモデル、埋め込み対象のソースデータに含める追加情報の書式を指定している
-- blogsテーブルのデータを自動的に埋め込むベクトライザーを作成
SELECT ai.create_vectorizer(
'public.blogs'::regclass
-- OpenAI text-embedding-3-smallモデルを使用
, embedding=>ai.embedding_openai('text-embedding-3-small', 1536, api_key_name=>'OPENAI_API_KEY')
-- テーブルが100k行に達したらStreamingDiskANNインデックスを自動作成
, indexing => ai.indexing_diskann(min_rows => 100000, storage_layout => 'memory_optimized'),
-- contentカラムに再帰的チャンク分割を適用
, chunking=>ai.chunking_recursive_character_text_splitter('content')
-- より良い検索のため、他カラムのメタデータを埋め込みに追加
, formatting=>ai.formatting_python_template('Blog title: $title url: $url blog chunk: $chunk')
);
-- ベクトライザーはソーステーブルが変更されると埋め込みを更新する
-- 他のユーザー操作は不要
- また、長いテキストは埋め込みモデルのトークン制限に収まる複数の小さなチャンクへ分割する必要があるため、基本的なチャンク分割関数も定義している
ソースデータ変更の追跡
- pgai Vectorizerは内部的にソーステーブルの変更(挿入・更新・削除)を検知し、ベクトル埋め込みを非同期に生成・更新する
- self-hostedとTimescale Cloudのフルマネージドの2つのデプロイ形態に対応するよう設計されている
- pgai Vectorizerのクラウドホスティング実装では、埋め込み生成にTimescale Cloudプラットフォームのクラウド機能を利用する
- pgai Vectorizerのオープンソース版では、外部ワーカーを実行して埋め込みを生成する
- pgai Vectorizerは設定情報やカタログ情報を、データベース内部の主要な内部管理データとともに保存する
実際に埋め込みを生成するのはどこか?
- 実際の埋め込み処理は、データベース外部の外部プロセスで行われる
- これによりデータベースサーバーの負荷を軽減し、ベクトライザーがアプリケーションクエリを処理するデータベースの能力に影響を与えない
- また、他のデータベース処理とは独立して埋め込み作業を容易にスケールさせられる
- このプロセスではまずデータベースを読み取り、実行すべき作業があるかを確認する
- あれば、データベースからデータを読み出し、チャンク分割とフォーマットを行い、OpenAIのような埋め込みモデル提供者を呼び出して埋め込みを生成し、その結果を再びデータベースに書き戻す
プロセスのカスタマイズ
- pgai Vectorizerは柔軟で、埋め込み生成に使うチャンク分割ルールとフォーマットルールを指定できる
- 特に、ベクトル化するソーステーブルのカラムと、ソースデータが埋め込みトークン制限内に収まり、関連データが各埋め込みに含まれるようにするためのチャンク分割・フォーマット規則を構成できる
- pgai VectorizerのEarly Accessリリースでは、OpenAI埋め込みモデルの選択、テキストをより小さなチャンクに分割する戦略、各チャンクに追加コンテキストを注入するフォーマットオプション、自動インデックス作成と性能チューニングのためのカスタムインデクシング設定をカスタマイズできる
- さらに近いうちに、ユーザーが独自のPythonコードを投入して、チャンク分割・埋め込み・フォーマットを完全にカスタマイズできるようにする計画だ
たとえば次は、HTMLソースファイルを再帰的に分割し、ソースデータからOpenAI埋め込みを作るよう構成されたベクトライザーである。コード、ドキュメント、Markdownなど、アプリケーションデータに合わせてチャンク分割とフォーマットを設定できる
-- 高度なベクトライザー設定
SELECT ai.create_vectorizer(
'public.blogs'::regclass,
destination => 'blogs_embedding_recursive',
embedding => ai.embedding_openai('text-embedding-3-small', 1536),
-- HTMLコンテンツに対して指定設定の再帰的チャンク分割を適用
chunking => ai.chunking_recursive_character_text_splitter(
'content',
chunk_size => 800,
chunk_overlap => 400,
-- HTMLを意識した区切り文字。優先度が高い順から低い順に並べる
separator => array[
E'
', -- 主要な文書セクションで分割
E'
', -- div境界で分割
E'
',
E'
', -- 段落で分割
E'
', -- 改行で分割
E'
', -- リスト項目で分割
E'. ', -- 文境界の代替
' ' -- 最後の手段: 空白で分割
]
),
formatting => ai.formatting_python_template('title: $title url: $url $chunk')
);
GN⁺の意見
- pgai Vectorizerは、埋め込み管理を大幅に簡素化できる強力で革新的なツールに見える。ベクトライザー抽象化は、開発者が埋め込みを手動管理する負担を軽減し、埋め込みがソースデータと同期されることを保証する。
- 特に、埋め込みモデルのアップグレードやチャンク分割戦略の変更のような変更を適用する際に非常に有用だと思われる。従来のベクトルデータベースでは、こうした変更により複数システムにまたがるカスタムコード作成と調整が必要になる複雑なプロセスが発生しうるが、pgai Vectorizerならベクトライザー設定を更新するだけでよい。
- また、PostgreSQLのような汎用データベースで埋め込みを管理すれば、複数の専用システムをオーケストレーションしなければならない問題を避けられる。これはアプリケーション開発を大きく簡素化しうる。
- 1つ考慮すべき点は、実際の埋め込み生成が外部のPythonプロセスで行われることだ。これはデータベース性能に影響を与えないようにする優れた設計判断だが、埋め込み生成プロセスを別途監視・管理する必要があることも意味する。
- 最終的に、pgai VectorizerはAIアプリケーション向けの埋め込み管理における大きな前進を示している。より多くのチームが導入し、フィードバックを提供するにつれて、この強力なツールはさらに進化していくと期待される。Postgresのような馴染み深いツールに埋め込み管理を統合することで、より多くの開発者が先進的なAI機能を活用できるようになるだろう。
1件のコメント
Hacker Newsの意見
データ同期のオーバーヘッドを過大評価しており、ほとんどの埋め込みベースのワークフローでは更新や削除はそれほど多くない。小さなデータセットでは一貫性の問題も認識しにくい。それでも、データ同期を心配しなくてよいのは依然として魅力的
Elasticの社員として、Elasticsearchが最近
semantic_textというデータ型を追加したことに言及。これはテキストを自動的にチャンクへ分割し、埋め込みを計算して保存する。クエリも簡素化され、I/Oが減り、クライアントコードもシンプルになるPostgreSQLツールを紹介し、ベクトル埋め込みをデータベースインデックスとして再構想している。現時点ではOpenAIのみ対応しているが、まもなくローカルおよびOSSモデルのサポートを予定している。フィードバックや反応を期待している
FAISSを単一のデータベースとして使うことへの疑問を提起。これはベクトル埋め込み向けのsqliteのようなもので、メタデータとベクトルを一緒に保存して関係を維持できる
Postgresでベクトルを使うことには前向きで、SQLクエリにベクトル検索とロジックを含める際のフィルタリング順序について疑問を呈している。pg_vectorのDXは気に入っているが、ベクトル検索後のフィルタリングは速度を低下させる可能性がある
生の埋め込みをベクトルデータベースに保存するのは、テキストの生のn-gramをデータベースに保存するようなものだと言及。ドキュメントを保存するほうが合理的
SQLiteでsqlite-vecとFTS5を使っており、とても有用だと言及
Node.jsでPostgreSQL ORMを構築し、ベクトルフィールドを含むコードを書けるようにしている。これにより、データや埋め込みコンテンツをクエリでき、モデルのどのフィールドを埋め込みとして保存するかを定義できる
Materialized Viewsは良いと言及
文字ベースのチャンクを使うAIアプリはPoC段階を超えていないと言及