RAGやセマンティック検索を実装するとき、最初に迷いやすいのが「どのベクトルDBを使うか」です。既存のPostgreSQLに載せるべきか、Pythonで手軽に始められるChromaか、検索専用のQdrantか。
先に結論を書くと、業務データをSQLで管理しているなら pgvector、ローカルでRAGを素早く試すなら Chroma、ベクトル検索を独立サービスとして運用するなら Qdrant が選びやすいです。
この記事では、同じような3次元ベクトルを登録して検索しながら、それぞれの違いを確認します。
まず比較するポイント
ベクトルDBを選ぶとき、検索速度だけを見ると判断を誤りやすくなります。私は次の観点で比較しました。
| 観点 | pgvector | Chroma | Qdrant |
|---|---|---|---|
| 位置付け | PostgreSQL拡張 | AIアプリ向けDB | ベクトル検索専用DB |
| 始めやすさ | PostgreSQLが必要 | 非常に簡単 | Dockerで簡単 |
| 通常データとの結合 | SQL JOINが得意 | アプリ側で扱う | Payloadで保持 |
| フィルタ | SQL | Metadata filter | Payload filter |
| 向いている用途 | 既存業務DB、厳密な整合性 | PoC、個人開発、小規模RAG | 検索基盤、独立サービス |
なお、どの製品を選んでも、検索品質を決める大きな要素はEmbeddingモデルとチャンク分割です。DBを変えただけで検索品質が大きく改善するわけではありません。
pgvector:既存のPostgreSQLを活かす
pgvectorはPostgreSQLの拡張機能です。ベクトルだけを別DBに切り出さず、ユーザー、権限、商品、文書などの既存テーブルと同じデータベースで管理できます。
公式ドキュメントでは、完全一致に近い検索に加えて、HNSWやIVFFlatによる近似最近傍検索もサポートされています。HNSWは検索性能と再現率のバランスがよい一方、インデックス作成時のメモリ消費が大きくなります。pgvector公式README
まずはPostgreSQLに拡張を有効化します。
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE documents (
id bigserial PRIMARY KEY,
content text NOT NULL,
category text NOT NULL,
embedding vector(3) NOT NULL
);
3次元のサンプルデータを登録して、コサイン距離で検索します。
INSERT INTO documents (content, category, embedding)
VALUES
('PostgreSQLで全文検索を実装する', 'database', '[0.90, 0.10, 0.00]'),
('RAGのチャンク分割を考える', 'ai', '[0.10, 0.90, 0.00]'),
('Dockerで開発環境を作る', 'infra', '[0.05, 0.10, 0.85]');
SELECT id, content, category,
embedding <=> '[0.12, 0.88, 0.00]'::vector AS distance
FROM documents
ORDER BY embedding <=> '[0.12, 0.88, 0.00]'::vector
LIMIT 2;
データ量が増えたら、検索用インデックスを追加します。
CREATE INDEX documents_embedding_hnsw_idx
ON documents
USING hnsw (embedding vector_cosine_ops);
実際に試してみて感じたpgvectorの強みは、検索結果に対してSQLの条件を自然に追加できることです。
SELECT id, content
FROM documents
WHERE category = 'ai'
ORDER BY embedding <=> '[0.12, 0.88, 0.00]'::vector
LIMIT 5;
すでにPostgreSQLを使っていて、検索対象の文書にも業務上の属性が多い場合は、まずpgvectorを検討するのが堅実です。
Chroma:RAGの試作を最短で始める
Chromaは、Embedding、文書、Metadataをコレクション単位で扱えるライブラリです。Pythonからの操作が簡単で、メモリ上だけで動かすことも、ローカルディスクに永続化することもできます。Chroma公式ドキュメント
インストールは次の1行です。
pip install chromadb
Embeddingをあらかじめ用意する場合は、次のように登録できます。
import chromadb
client = chromadb.PersistentClient(path="./.chroma")
collection = client.get_or_create_collection(name="documents")
collection.upsert(
ids=["doc-1", "doc-2", "doc-3"],
documents=[
"PostgreSQLで全文検索を実装する",
"RAGのチャンク分割を考える",
"Dockerで開発環境を作る",
],
metadatas=[
{"category": "database"},
{"category": "ai"},
{"category": "infra"},
],
embeddings=[
[0.90, 0.10, 0.00],
[0.10, 0.90, 0.00],
[0.05, 0.10, 0.85],
],
)
result = collection.query(
query_embeddings=[[0.12, 0.88, 0.00]],
n_results=2,
where={"category": "ai"},
)
print(result["documents"])
PersistentClientを使うと、プロセスを終了しても指定したディレクトリにデータが残ります。ノートブックでRAGを試したり、社内ツールのプロトタイプを作ったりする場合に便利でした。
一方で、業務データの更新、複雑なトランザクション、複数テーブルをまたぐ検索を中心に考えると、PostgreSQLほど自然には書けません。Chromaは「検索機能を持つアプリを素早く作る」ための選択肢として考えると理解しやすいです。
Qdrant:検索サービスを独立させる
Qdrantは、ベクトル検索を中心に設計されたサービスです。ベクトル、ID、JSON形式のPayloadを「Point」として保持し、コレクション単位で検索します。Qdrant公式ドキュメント
ローカルではDockerで起動できます。
docker run --rm \
-p 6333:6333 \
qdrant/qdrant
Pythonクライアントをインストールします。
pip install qdrant-client
登録と検索の最小例は次のとおりです。
from qdrant_client import QdrantClient, models
client = QdrantClient(url="http://localhost:6333")
collection_name = "documents"
if not client.collection_exists(collection_name):
client.create_collection(
collection_name=collection_name,
vectors_config=models.VectorParams(
size=3,
distance=models.Distance.COSINE,
),
)
client.upsert(
collection_name=collection_name,
points=[
models.PointStruct(
id=1,
vector=[0.90, 0.10, 0.00],
payload={"content": "PostgreSQLで全文検索を実装する", "category": "database"},
),
models.PointStruct(
id=2,
vector=[0.10, 0.90, 0.00],
payload={"content": "RAGのチャンク分割を考える", "category": "ai"},
),
models.PointStruct(
id=3,
vector=[0.05, 0.10, 0.85],
payload={"content": "Dockerで開発環境を作る", "category": "infra"},
),
],
)
hits = client.query_points(
collection_name=collection_name,
query=[0.12, 0.88, 0.00],
query_filter=models.Filter(
must=[
models.FieldCondition(
key="category",
match=models.MatchValue(value="ai"),
)
]
),
with_payload=True,
limit=2,
).points
for hit in hits:
print(hit.score, hit.payload)
Qdrantの特徴は、Payloadによるフィルタをベクトル検索と組み合わせやすいことです。言語、テナント、公開状態、商品カテゴリなどをPayloadに持たせておけば、意味の近さと業務条件を同時に扱えます。
また、複数のベクトルを名前付きで持つ構成や、密ベクトルと疎ベクトルを組み合わせる構成にも向いています。検索部分をAPIサービスとして切り出したい場合は、pgvectorよりQdrantのほうが責務を分離しやすいと感じました。
どれを選ぶべきか
要件に応じた選択の流れを図にすると次の通りです。
私なら、次のように選びます。
- 既存システムがPostgreSQL中心で、権限や在庫などの条件をSQLで厳密に扱うならpgvector
- PythonでRAGの流れを確認したい、または小さなアプリを作りたいならChroma
- ベクトル検索を独立したサービスとして運用し、Payload検索や将来の拡張も重視するならQdrant
ただし、最初から大量データを想定して複雑な構成にする必要はありません。私は最初にChromaやpgvectorで、次の項目を小さなデータセットで確認します。
- 期待する文書が上位に出るか
- MetadataやPayloadの条件を満たせるか
- 文書更新時に古いEmbeddingが残らないか
- バックアップと再インデックスの手順を説明できるか
近似検索を使う場合は、速度だけでなく再現率も確認します。pgvectorならインデックスなしの検索結果を基準にし、近似インデックス使用時の結果との差分を見ると、チューニングの方向性を判断しやすくなります。
まとめ
pgvector、Chroma、Qdrantは、単純な優劣ではなく得意な境界が異なります。
- 業務データとの整合性とSQLを重視するならpgvector
- 試作と実装速度を重視するならChroma
- 専用の検索基盤と柔軟なPayload検索を重視するならQdrant
最終的な検索品質は、DB製品だけでは決まりません。Embeddingモデル、チャンクサイズ、Metadata設計、検索後の再ランキングまで含めて小さく検証し、運用要件に合うものを選ぶのが現実的です。