ベクトルの保存先を正しく選択する
最近のエンジニアのミートアップに参加すれば、必ずと言っていいほど「RAGにはどのベクトルデータベースを使うべきか?」という議論を耳にするでしょう。レコメンデーションエンジンやドキュメントチャットボットを構築する際、最初のハードルは常にストレージの選択です。
Pinecone、Milvus、Qdrantといった名前がよく挙がります。これらは強力で特化したツールですが、「アーキテクチャ上のコスト」も伴います。新しいクエリ言語の学習、監視対象システムの増加、そしてリレーショナルデータとベクトルストアの同期を維持し続けるという摩擦です。
私は「退屈な技術(boring technology)」の哲学を支持しています。Postgresでできることは、Postgresでやるべきです。そこでpgvectorの出番です。これは、標準的なPostgresインスタンスをベクトルエンジンに変えるオープンソースの拡張機能です。データをネットワーク経由でサードパーティのサービスに送信する代わりに、メタデータとエンベディングを一箇所に保持できます。これによりスタックが簡素化され、単一のSQLクエリで類似性検索の結果を実際のユーザーテーブルや製品カタログと結合できるようになります。
アプローチの比較:特化型データベース vs pgvector
はっきりさせておきましょう。特化型のベクトルデータベースは、大規模な近似最近傍(ANN)検索のために構築されています。もし5億個のベクトルをインデックス化し、高負荷下で5ミリ秒未満のレイテンシが必要なら、その複雑さを受け入れる価値はあります。しかし、1,000万個未満のベクトルを扱うような, 本番環境にあるアプリの95%にとっては、pgvectorで十分すぎるほどです。
特化型のアプローチをとると、しばしば「データのサイロ化」が発生します。ベクトルはPineconeに、対応するIDはPostgresに保存するといった形です。ユーザーに製品名を表示するには、まずPineconeにIDを問い合わせ、次にPostgresで詳細を取得するという2段階の手順が必要になり、レイテンシが増大します。pgvectorなら、一つのクエリですべてが完結します。また、ACIDコンプライアンス、ポイントインタイムリカバリ、そして数十年にわたって実戦で鍛えられたバックアップツールの安心感も得られます。
トレードオフ:メリットとデメリット
pgvectorを採用する前に、運用上の実情を理解しておく必要があります。私はこれまでに複数のLLM活用プロジェクトで導入してきましたが、その評価を以下にまとめます。
メリット
- ネイティブな結合: ベクトルがリレーショナルデータと同じ行に存在します。壊れやすい同期ロジックを組む必要がありません。
- 使い慣れたSQL:
SELECT文が書ければ、8割方マスターしたも同然です。類似ベクトルの検索は、単なるORDER BY句のように記述できます。 - エコシステムの対応: Prisma、Drizzle、SQLAlchemyなどは既に対応しています。既存のオブザーバビリティ(観測性)スタックを大幅に変更する必要もありません。
- 強力なフィルタリング: ベクトル検索の前に
user_idやtimestampでフィルタリングを行う場合、多くの特化型ストアよりもPostgresの方が圧倒的に高速です。
デメリット
- 高いCPU負荷: 距離計算は計算量が多い処理です。適切なインデックスがないと、負荷の高いDBではベクトル検索によってCPU使用率が80%以上に跳ね上がることがあります。
- メモリ(RAM)要件: HNSWのような高性能インデックスはメモリ上に保持する必要があります。1536次元のベクトル100万個の場合、インデックスだけで少なくとも2〜3GBのRAMを割り当てることを覚悟してください。
- 構築時間: B-treeインデックスとは異なり、500万行のベクトルインデックスの構築には30分以上かかる場合があります。
はじめに
導入に複雑なセットアップは不要です。Dockerを使用している場合は、公式イメージを使用して、すべてのC拡張機能が正しくコンパイルされていることを確認してください。
# pgvectorコンテナを起動する
docker run --name pgvector-demo -e POSTGRES_PASSWORD=mysecretpassword -p 5432:5432 -d ankane/pgvector
AWS RDS、Google Cloud SQL、Azureなどのマネージドサービスは、現在すべて pgvector をサポートしています。設定を切り替えるだけです。macOSでのローカル開発なら、brew install pgvectorで即座に開始できます。
データ準備に関する注意点
生データは、整理されていないCSVやExcelシートの状態であることが多いです。エンベディングを生成する前に、これらをクリーンなJSON構造に変換する必要があります。私はこの作業にtoolcraft.app/ja/tools/data/csv-to-jsonを使用しています。ブラウザ上で完結するため、マシン外に出すべきではない機密性の高い顧客データを扱う際にも非常に有効です。
実装ガイド
ドキュメント検索ボットの実用的な実装例を見てみましょう。
1. 拡張機能を有効にする
データベースで一度実行するだけで、ベクトル機能が使えるようになります。
CREATE EXTENSION IF NOT EXISTS vector;
2. スキーマを定義する
次元数を指定する必要があります。OpenAIのtext-embedding-3-smallは1536次元を出力します。largeモデルを使用する場合、それは3072次元になります。
CREATE TABLE documents (
id SERIAL PRIMARY KEY,
content TEXT,
metadata JSONB,
embedding vector(1536)
);
3. データの挿入
ベクトルは本質的には浮動小数点数の配列です。標準的なSQLでは、文字列として渡します。
INSERT INTO documents (content, embedding)
VALUES ('Postgresは優れたベクトルストアです。', '[0.012, -0.023, 0.45, ...]');
4. 類似性検索
これが核となる機能です。pgvectorは特定の演算子を使用して距離を計算します分析:
<->:ユークリッド距離(L2)<=>:コサイン距離(テキスト処理における業界標準)
-- 最も関連性の高い5つのドキュメントを検索する
SELECT content, 1 - (embedding <=> '[0.015, -0.021, 0.48, ...]') AS similarity
FROM documents
ORDER BY embedding <=> '[0.015, -0.021, 0.48, ...]'
LIMIT 5;
5. パフォーマンスのチューニング
インデックスがない場合、Postgresは「シーケンシャルスキャン」を実行します。5,000行程度なら問題ありませんが、10万行を超えると動作が著しく低下します。主に2つの選択肢があります。
IVFFlat
ベクトルをリストにクラスタリングします。メモリ効率は良いですが、クラスタを正確に構築するためにデータの「トレーニング」セットが必要です。RAMの予算が限られている場合に適しています。
CREATE INDEX ON documents USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
HNSW (Hierarchical Navigable Small Worlds)
HNSWは現在最高水準(ゴールドスタンダード)とされる手法です。グラフ構造を構築することで、驚異的に高速なルックアップを可能にします。標準的なRDSインスタンスでの私のテストでは、100万行のテーブルに対してクエリ時間を20ミリ秒未満に抑えることができました。より多くのRAMを消費しますが、そのスピードには価値があります。
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops);
最後に
すでにPostgresのエコシステムに慣れ親しんでいるチームにとって、pgvectorを選択することは通常、最も賢明な判断です。アーキテクチャを簡素に保ち、デプロイパイプラインもシンプルになります。あらゆるエッジケースで特化型データベースを完全に置き換えるものではありませんが、現在構築されている大多数のAIアプリにとって、そのパフォーマンスは十分すぎるほどです。
私からのアドバイスは、「まずはpgvectorから始めること」です。あなたが直面するだろうと考えている限界は、思っているよりもずっと先にあります。構成はシンプルに保ち、新しいデータベースの管理に時間を割くのではなく、プロダクトそのものに集中しましょう。

