AI

ベクター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']])

機能比較表

比較項目pgvectorChromaLanceDB
既存PostgreSQL統合
セットアップ簡単さ✅ 最速
マルチモーダル
サーバーレス/S3
GitHub Stars14k+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 Chromavectorstore = 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

関連外部リソース

他の記事も読む

Let's Build Together

OSS導入、自社だけで悩まない。

ツール選定から構築・運用・AI活用まで、オープンソースラボ運営元のClasslessが伴走します。初回のご相談は無料です。