ベクターDB比較:pgvector vs Chroma vs LanceDB でRAGを構築する
オープンソースラボ編集部 ・ 2026年6月14日
ベクターDB比較:pgvector vs Chroma vs LanceDB でRAGを構築する
LLMアプリケーションのRAG(Retrieval-Augmented Generation)の根幹となる**ベクターストア(埋め込みベクトルの類似検索データベース)**の選択は重要なアーキテクチャ決定です。pgvector(PostgreSQL拡張・既存PG統合)・Chroma(Python特化・シンプル)・LanceDB(高速・マルチモーダル・Apache Arrow)の3つが2026年のOSSベクターDB主要選択肢です。
ベクターストアが必要な理由
- RAG(検索拡張生成): ユーザーの質問に対してコーパス(社内文書・FAQ・商品情報)から関連チャンクを検索→LLMに文脈として渡す
- セマンティック検索: キーワード完全一致ではなく意味の近さで文書を検索→「引っ越し費用」で「転居コスト」もヒット
- 推薦システム: 商品・コンテンツ・ユーザーの埋め込みベクトルで類似アイテムを推薦
- 重複排除: 文書間のコサイン類似度で重複・ほぼ重複コンテンツを検出
主要ツールの概要
pgvector
PostgreSQL拡張のOSSです。GitHubスター14k+。既存PostgreSQLに追加するだけで使えるベクター検索拡張で、Supabase・RDS・Cloud SQL等のマネージドPostgreSQLでも有効化でき、SQLで通常のリレーショナルデータとベクターを同一テーブルで管理できます。
-- PostgreSQL: pgvector 初期設定
CREATE EXTENSION IF NOT EXISTS vector;
-- ドキュメントチャンクとベクターを同一テーブルで管理
CREATE TABLE document_chunks (
id BIGSERIAL PRIMARY KEY,
doc_id BIGINT NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
content TEXT NOT NULL,
embedding VECTOR(1536), -- OpenAI text-embedding-3-small: 1536次元
chunk_index INT NOT NULL,
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- IVFFlat インデックス(近似最近傍探索: 高速)
CREATE INDEX idx_chunks_embedding
ON document_chunks
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
-- HNSWインデックス(より高精度の近似最近傍探索)
CREATE INDEX idx_chunks_hnsw
ON document_chunks
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- コサイン類似度検索(類似上位5件)
SELECT
id,
content,
metadata,
1 - (embedding <=> $1::vector) AS cosine_similarity
FROM document_chunks
WHERE metadata->>'doc_type' = 'faq'
ORDER BY embedding <=> $1::vector
LIMIT 5;
# Python: pgvector + OpenAI Embeddings でRAGパイプラインを構築
from openai import OpenAI
import psycopg2
from psycopg2.extras import execute_values
import numpy as np
openai_client = OpenAI()
conn = psycopg2.connect('postgresql://user:pass@localhost:5432/mydb')
conn.autocommit = False
def embed_text(text: str, model: str = 'text-embedding-3-small') -> list[float]:
'''テキストをOpenAI埋め込みベクトルに変換'''
resp = openai_client.embeddings.create(model=model, input=text)
return resp.data[0].embedding
def chunk_text(text: str, chunk_size: int = 500, overlap: int = 50) -> list[str]:
'''テキストをオーバーラップ付きチャンクに分割'''
words = text.split()
chunks = []
for i in range(0, len(words), chunk_size - overlap):
chunk = ' '.join(words[i:i + chunk_size])
if chunk:
chunks.append(chunk)
return chunks
def ingest_document(doc_id: int, content: str, metadata: dict = None):
'''ドキュメントをチャンク化してpgvectorに投入'''
chunks = chunk_text(content)
with conn.cursor() as cur:
rows = []
for idx, chunk in enumerate(chunks):
embedding = embed_text(chunk)
rows.append((doc_id, chunk, embedding, idx, metadata or {}))
execute_values(
cur,
'INSERT INTO document_chunks (doc_id, content, embedding, chunk_index, metadata) VALUES %s',
rows,
template='(%s, %s, %s::vector, %s, %s::jsonb)',
)
conn.commit()
print(f'投入完了: doc_id={doc_id}, chunks={len(chunks)}')
def search_similar(query: str, top_k: int = 5, filter_metadata: dict = None) -> list[dict]:
'''クエリに類似するチャンクをpgvectorで検索'''
query_embedding = embed_text(query)
with conn.cursor() as cur:
sql = '''
SELECT id, content, metadata,
1 - (embedding <=> %s::vector) AS similarity
FROM document_chunks
WHERE 1=1
'''
params = [query_embedding]
if filter_metadata:
for key, val in filter_metadata.items():
sql += f" AND metadata->>%s = %s"
params.extend([key, str(val)])
sql += ' ORDER BY embedding <=> %s::vector LIMIT %s'
params.extend([query_embedding, top_k])
cur.execute(sql, params)
return [{'id': r[0], 'content': r[1], 'metadata': r[2], 'similarity': float(r[3])} for r in cur.fetchall()]
def rag_query(question: str, context_chunks: list[dict]) -> str:
'''検索結果をコンテキストとしてLLMに渡してRAG回答を生成'''
context = '
'.join([f'[{i+1}] {c["content"]}' for i, c in enumerate(context_chunks)])
messages = [
{'role': 'system', 'content': '以下のコンテキストのみを参照して質問に答えてください。コンテキストに情報がない場合は「情報がありません」と答えてください。'},
{'role': 'user', 'content': f'コンテキスト:
{context}
質問: {question}'},
]
resp = openai_client.chat.completions.create(model='gpt-4o', messages=messages)
return resp.choices[0].message.content
# 使用例
ingest_document(1, '当社の返金ポリシーは購入後30日以内であれば全額返金可能です...', {'doc_type': 'faq'})
chunks = search_similar('返金できますか', top_k=3, filter_metadata={'doc_type': 'faq'})
answer = rag_query('購入後40日で返金できますか?', chunks)
print(answer)
Chroma
2022年公開、Python製のOSSです。GitHubスター17k+。最もシンプルなPython向けベクターDBで、インメモリ・永続化・クライアント/サーバーモードをサポートし、LangChain・LlamaIndexとの深い統合でRAG開発を最速で始めることができます。
# Python: Chroma でRAGベクターストアを構築
import chromadb
from chromadb.utils import embedding_functions
# 永続化クライアント(ディレクトリに保存)
client = chromadb.PersistentClient(path='./chroma_db')
# OpenAI埋め込み関数を設定
openai_ef = embedding_functions.OpenAIEmbeddingFunction(
api_key='sk-xxxxx',
model_name='text-embedding-3-small',
)
# コレクション作成
collection = client.get_or_create_collection(
name='my_documents',
embedding_function=openai_ef,
metadata={'hnsw:space': 'cosine'},
)
# ドキュメントを追加(自動でベクター化)
collection.add(
documents=['返金ポリシーは30日以内に限ります', '送料は全国一律500円です', '会員登録は無料です'],
metadatas=[{'type': 'faq'}, {'type': 'faq'}, {'type': 'info'}],
ids=['faq-1', 'faq-2', 'faq-3'],
)
# 類似検索
results = collection.query(
query_texts=['返金できますか'],
n_results=3,
where={'type': 'faq'},
)
print(results['documents'])
LanceDB
2022年公開、Rust製のOSSです。GitHubスター6k+。Apache Arrow/Lanceフォーマットベースのマルチモーダル対応ベクターDBで、テキスト・画像・音声の埋め込みを統一管理し、Python・TypeScript・Rust APIを持ちます。ゼロサーバー(ファイルベース)でクラウドストレージ(S3・GCS)を直接ストレージに使えます。
# Python: LanceDB でベクター検索(ゼロサーバー)
import lancedb
import numpy as np
from PIL import Image
import pyarrow as pa
db = lancedb.connect('./lancedb_data')
schema = pa.schema([
pa.field('id', pa.string()),
pa.field('content', pa.string()),
pa.field('vector', pa.list_(pa.float32(), 1536)),
pa.field('metadata', pa.string()),
])
table = db.create_table('documents', schema=schema, mode='overwrite')
# データ追加
def add_documents(docs: list[dict]):
data = []
for doc in docs:
from openai import OpenAI
oai = OpenAI()
vec = oai.embeddings.create(model='text-embedding-3-small', input=doc['content']).data[0].embedding
data.append({'id': doc['id'], 'content': doc['content'], 'vector': vec, 'metadata': doc.get('type', '')})
table.add(data)
# 検索
results = table.search([0.1] * 1536).limit(5).to_pandas()
print(results[['id', 'content']])
機能比較表
| 比較項目 | pgvector | Chroma | LanceDB |
|---|---|---|---|
| 既存PostgreSQL統合 | ✅ | ❌ | ❌ |
| セットアップ簡単さ | 中 | ✅ 最速 | ✅ |
| マルチモーダル | △ | △ | ✅ |
| サーバーレス/S3 | ❌ | ❌ | ✅ |
| GitHub Stars | 14k+ | 17k+ | 6k+ |
ベクターストアはLLM Toolsカテゴリ/categories/llm-toolsのLlamaIndex・LangChain・HaystackのRAGパイプラインの永続化層として使われます。pgvectorはDevOpsカテゴリ/categories/devopsのSupabase・PostgreSQLと同一インスタンスで運用することで既存DBインフラコストゼロでベクター検索を追加できます。
FAQ
Q. pgvectorのHNSWとIVFFlatのどちらのインデックスを使うべきですか?
A. 高精度が必要なら HNSW、大規模データセットでメモリを節約したいなら IVFFlatを選びます。HNSW(Hierarchical Navigable Small World): ①検索精度が高い(recall ≈ 0.99)②クエリが高速(インデックス構築後)③メモリ消費が多い(全ベクターをグラフに保持)④本番RAGには通常HNSWを推奨。IVFFlat(Inverted File Index): ①メモリ効率が良い(リスト数で制御)②listsパラメーターで精度・速度をトレードオフ③大規模データ(数百万ベクター以上)での本番運用に適している④クエリ時にSET ivfflat.probes = 10で精度向上可能。設定例: WITH (m = 16, ef_construction = 64)(HNSW)は典型的な設定で精度と構築速度のバランスが良い。
Q. ChromaをLangChainと統合してRAGチェーンを構築するには?
A. LangChainのChromaベクターストアクラスをRetrieverとして使いRetrievalQAチェーンに組み込みます。コード例: from langchain_community.vectorstores import Chroma→vectorstore = Chroma.from_documents(docs, OpenAIEmbeddings(), persist_directory='./chroma')→retriever = vectorstore.as_retriever(search_kwargs={"k": 5, "filter": {"source": "faq"}})→qa_chain = RetrievalQA.from_chain_type(ChatOpenAI(model="gpt-4o"), chain_type="stuff", retriever=retriever)→result = qa_chain.invoke({"query": "返金できますか"})。MMR(Maximal Marginal Relevance): as_retriever(search_type="mmr")で類似度が高く多様な検索結果を取得→単純な類似検索より回答品質が向上します。
Q. pgvectorのRAGでコサイン類似度スコアのしきい値をどう設定すべきですか?
A. コサイン類似度0.7〜0.8を初期しきい値として設定してデータで調整するのが推奨です。計算方法: pgvectorでのコサイン類似度 = 1 - (embedding <=> query_embedding)(pgvectorの<=>演算子はコサイン距離なので1から引く)。しきい値ガイドライン: ≥0.85(非常に類似・事実確認向け)・0.70〜0.85(関連する可能性が高い・FAQ向け)・0.50〜0.70(緩やかな関連・探索向け)・<0.5(無関係の可能性が高い→フィルタリング)。実装: WHERE 1 - (embedding <=> $1::vector) >= 0.7でしきい値フィルタリング。チューニング: 実際のQ&Aペアで精度(Precision)・再現率(Recall)を計測してしきい値を調整します。
Q. LanceDBをサーバーレスでS3に接続して大規模ベクターを管理するには?
A. LanceDBのS3 URIで接続してCloudflare R2・AWS S3をバックエンドに使うことができます。設定: db = lancedb.connect("s3://my-bucket/lancedb/")(またはs3+ddb://my-bucket/lancedb/でDynamoDB連携のメタデータ管理)。コスト: S3標準ストレージ($0.023/GB/月)+ クエリコスト→1000万ベクター(1536次元)= 約60GB×$0.023 = $1.4/月。高速化: LanceDB.create_scalar_indexでスカラーフィールドにインデックス→メタデータフィルタリングが高速化。マルチモーダル: CLIPモデルでテキストと画像を同一512次元空間に埋め込み→テキストでの画像検索・画像でのテキスト検索が1つのコレクションで可能。
まとめ
| ユースケース | 推奨ツール |
|---|---|
| PostgreSQL既存統合・SQLベース管理 | pgvector |
| Python RAD・LangChain/LlamaIndex統合 | Chroma |
| マルチモーダル・サーバーレス・S3バックエンド | LanceDB |