mcpField
P
pgvector-semantic-search

pgvector-semantic-search

github.com/timescaleデータ・データベース1.9k947 回インストール6 日前に更新
リポジトリ

pgvector-semantic-search について

PostgreSQL と pgvector でセマンティック検索を構築する開発者向けの実践ガイドです。ベクトルの保存、インデックス、クエリ調整に加え、量子化やフィルター検索も扱います。

主な機能

  1. 01halfvec とコサイン距離の設定
  2. 02HNSW インデックスの作成と調整
  3. 03バイナリ量子化と再ランキング
  4. 04フィルター付きベクトル検索への対応
  5. 05一括ロードとインデックス保守

活用シーン

  1. 01テキスト埋め込みのセマンティック検索構築
  2. 02大規模ベクトルデータの検索調整
  3. 03フィルター検索の再現率向上
  4. 04ベクトルインデックス性能の調査

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/timescale/pg-aiguide --skill pgvector-semantic-search

インストール後、Claude Code で/pgvector-semantic-searchを入力して読み込まれたか確認します。

詳細

セマンティック検索のための pgvector

セマンティック検索では、完全一致するキーワードではなく、意味に基づいてコンテンツを検索します。埋め込みモデルはテキストを高次元ベクトルに変換し、意味が似ているものは近い位置に対応します。pgvector はこれらのベクトルを PostgreSQL に保存し、近似最近傍(ANN)インデックスを使って最も近い一致をすばやく検索します。データベースを離れることなく、数百万行までスケールできます。テキストとその埋め込みを一緒に保存し、検索テキストをベクトルに変換して、距離が最小の行を返すようクエリします。

このガイドでは、pgvector のセットアップとチューニングを解説します。検索品質に大きく影響する埋め込みモデルの選択やテキストのチャンク化は対象外です。すべての機能(halfvec、binary_quantize、反復スキャン)を使うには pgvector 0.8.0+ が必要です。

推奨設定(デフォルト)

特別な理由がない限り、この設定を使用してください。

  • 埋め込み列のデータ型:halfvec(N)。N は埋め込みの次元数です(すべての箇所で一致させる必要があります)。例では 1536 を使用しています。ご使用の次元数 N に置き換えてください。
  • 距離:コサイン(<=>)
  • インデックス:HNSW(m = 16、ef_construction = 64)。halfvec_cosine_ops を使用し、<=> でクエリします。
  • クエリ時の再現率:SET hnsw.ef_search = 100(公開されているベンチマークを参考にした適切な初期値です。再現率を高めるには、レイテンシーの増加を許容して値を上げてください)
  • クエリパターン:ORDER BY embedding <=> $1::halfvec(N) LIMIT k

この設定は、ほとんどのテキスト埋め込みワークロードで、速度と再現率のバランスに優れています。

基本ルール

  • 各データベースで拡張機能を有効にします:CREATE EXTENSION IF NOT EXISTS vector;
  • デフォルトでは HNSW インデックスを使用します。速度と再現率のバランスに優れ、空のテーブルにも作成でき、トレーニング工程も不要です。書き込みの多いワークロードやメモリーに制約のあるワークロードでのみ、IVFFlat を検討してください。
  • デフォルトでは halfvec を使用します。halfvec 形式で保存し、インデックスを作成すると、再現率をほとんど損なわずにストレージとインデックスのサイズを 50% 削減できます。
  • 初期データを一括ロードした後にインデックスを作成すると、構築パフォーマンスが最も高くなります。
  • 本番環境ではインデックスを同時作成します:CREATE INDEX CONCURRENTLY ...
  • デフォルトではコサイン距離(<=>)を使用します。正規化されていない埋め込みにはコサイン距離を使用してください。単位正規化された埋め込みでは、コサイン距離と内積の順位は同じになります。デフォルトではコサイン距離を使用してください。
  • クエリ演算子をインデックスの演算子クラスに合わせます:halfvec_cosine_ops で作成したインデックスには、クエリで <=> が必要です。halfvec_l2_ops には <-> が必要です。演算子が一致しないと、インデックスは使用されません。
  • クエリベクトルは必ず明示的にキャストします($1::halfvec(N))。これにより、準備済みステートメントでの暗黙的なキャスト失敗を防げます。
  • データとクエリには必ず同じ埋め込みモデルを使用します。ベクトルを生成するモデルが同一でなければ、類似検索は機能しません。

型のルール

  • 埋め込みは halfvec(N) として保存します
  • クエリベクトルは halfvec(N) にキャストします
  • 二値量子化されたベクトルは、生成列に bit(N) として保存します
  • 明示的なキャストなしに vector/halfvec/bit を混在させないでください
  • ORDER BY 内でテーブル列に対して binary_quantize() を呼び出さないでください。代わりに保存してください
  • 次元数を一致させる必要があります:halfvec(1536) 列には、::halfvec(1536) にキャストしたクエリベクトルが必要です。

標準パターン

-- Store and index as halfvec
CREATE TABLE items (
  id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  contents TEXT NOT NULL,
  embedding halfvec(1536) NOT NULL  -- NOT NULL requires embeddings generated before insert, not async
);
CREATE INDEX ON items USING hnsw (embedding halfvec_cosine_ops);

-- Query: returns 10 closest items. $1 is the embedding of your search text.
SELECT id, contents FROM items ORDER BY embedding <=> $1::halfvec(1536) LIMIT 10;

L2 距離や内積など、その他の距離演算子については、pgvector README を参照してください。

HNSW インデックス

推奨されるインデックスの種類です。速度と再現率のバランスに優れた、多層のナビゲーショングラフを作成します。空のテーブルにも作成でき(トレーニング工程は不要です)。

CREATE INDEX ON items USING hnsw (embedding halfvec_cosine_ops);

-- With tuning parameters
CREATE INDEX ON items USING hnsw (embedding halfvec_cosine_ops) WITH (m = 16, ef_construction = 64);
HNSW パラメーター
パラメーターデフォルト説明
m16レイヤーあたりの最大接続数です。値を上げると再現率が向上しますが、メモリー使用量も増加します
ef_construction64構築時の候補リストです。値を上げるとグラフの品質が向上しますが、構築に時間がかかります
hnsw.ef_search40クエリ時の候補リストです。値を上げると再現率が向上しますが、クエリに時間がかかります。LIMIT 以上にする必要があります。

ef_search の調整(大まかな目安。実際の結果はデータセットによって異なります):

ef_searchおおよその再現率相対速度
40低め(一部のベンチマークで約95%)1x(基準)
100高め約2倍遅い
200非常に高い約4倍遅い
400ほぼ完全約8倍遅い
-- Set search parameter for session
SET hnsw.ef_search = 100;

-- Set for single query
BEGIN;
SET LOCAL hnsw.ef_search = 100;
SELECT id, contents FROM items ORDER BY embedding <=> $1::halfvec(1536) LIMIT 10;
COMMIT;

IVFFlat インデックス(一般的には非推奨)

デフォルトでは HNSW を使用してください。IVFFlat は、最高の再現率よりも HNSW の運用コストを重視する場合にのみ使用してください。

次の場合は IVFFlat を選択してください:

  • 書き込みが多い、またはデータが常に変化し、インデックスを頻繁に再構築してもよい場合
  • インデックスを頻繁に再構築しており、予測しやすい構築時間とメモリー使用量を求める場合
  • メモリーに余裕がなく、HNSW グラフの大部分をメモリー上に保持できない場合
  • データがパーティション化または階層化されており、このインデックスをアクセス頻度の低いパーティションに置く場合

次の要件がある場合は IVFFlat を避けてください:

  • 低レイテンシーで最高の再現率が必要
  • 調整を最小限にしたい
  • 「設定したら後は放置」できるインデックスが必要

注意:

  • IVFFlat では、インデックス作成前にデータが存在している必要があります。
  • 再現率は lists と ivfflat.probes に依存します。probes を上げると再現率が向上しますが、クエリに時間がかかります。

初期設定:

CREATE INDEX ON items
USING ivfflat (embedding halfvec_cosine_ops)
WITH (lists = 1000);

SET ivfflat.probes = 10;

量子化戦略

  • 量子化はメモリーに関する判断であり、再現率に関する判断ではありません。
  • ストレージとインデックス作成には、デフォルトで halfvec を使用してください。
  • HNSW インデックスのフットプリントは、1536 次元の halfvec(m=16)あたり約 4~6 KB と見積もってください(概算)。3072 次元では約 2 倍になり、m=32 では HNSW のリンク/グラフのオーバーヘッドがおよそ 2 倍になります。
  • CPU がほとんどアイドル状態なのに p95/p99 レイテンシーが上昇する場合、HNSW インデックスがメモリー上に常駐しなくなっている可能性が高いです。
  • halfvec が収まらない場合は、二値量子化と再ランキングを使用してください。
1536 次元ベクトルのガイドライン

m=16、1536 次元の場合の halfvec の容量の目安(RAM の大部分をインデックスのキャッシュに利用できると仮定):

RAMhalfvec ベクトルのおおよその最大数
16 GB約200~300万ベクトル
32 GB約400~600万ベクトル
64 GB約800~1200万ベクトル
128 GB約1600~2500万ベクトル

3072 次元の埋め込みでは、これらの数値を約 2 で割ってください。
m=32 の場合も、容量を約 2 で割ってください。

この規模でインデックスがメモリーに収まらない場合は、二値量子化を使用してください。

これらは範囲の目安であり、保証値ではありません。負荷時のキャッシュ常駐率と p95/p99 レイテンシーを監視して検証してください。

二値量子化(非常に大規模なデータセット向け)

メモリーを 32 分の 1 に削減できます。許容できる再現率を得るには、再ランキングと併用してください。

-- Table with generated column for binary quantization
CREATE TABLE items (
  id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  contents TEXT NOT NULL,
  embedding halfvec(1536) NOT NULL,
  embedding_bq bit(1536) GENERATED ALWAYS AS (binary_quantize(embedding)::bit(1536)) STORED
);

CREATE INDEX ON items USING hnsw (embedding_bq bit_hamming_ops);

-- Query with re-ranking for better recall
-- ef_search must be >= inner LIMIT to retrieve enough candidates
SET hnsw.ef_search = 800;
WITH q AS (
  SELECT binary_quantize($1::halfvec(1536))::bit(1536) AS qb
)
SELECT *
FROM (
  SELECT i.id, i.contents, i.embedding
  FROM items i, q
  ORDER BY i.embedding_bq <~> q.qb -- computes binary distance using index
  LIMIT 800
) candidates
ORDER BY candidates.embedding <=> $1::halfvec(1536) -- computes halfvec distance (no index), more accurate than binary
LIMIT 10;

オーバーサンプリング比 80 倍(10 件の結果に対して 800 件の候補)は、妥当な出発点です。二値量子化では精度が失われるため、再ランキング中に真の最近傍を見つけるには、より多くの候補が必要です。再現率が不十分な場合は増やし、再ランキングのレイテンシーが高すぎる場合は減らしてください。

データセット規模ごとのパフォーマンス

規模ベクトル数設定備考
小規模<100Kデフォルトインデックスは任意ですが、テールレイテンシーの改善に役立ちます
中規模100K–5Mデフォルトp95 レイテンシーを監視してください。一般的な本番環境の規模です
大規模5M+ef_construction=100+メモリー常駐が重要です
非常に大規模10M+二値量子化+再ランキング可能であれば、まず RAM の増設またはパーティション化を行ってください

再現率の調整では、まず ef_search を調整してください。m の増加は、再現率が頭打ちになり、メモリーに余裕がある場合に限って行ってください。並行処理時にインデックスがメモリーに収まらないと、テールレイテンシーが急上昇します。二値量子化は最後の手段です。まずは RAM の増設またはパーティション化を検討してください。

フィルタリングのベストプラクティス

フィルターを使用したベクトル検索には注意が必要です。フィルターの選択性やクエリの形状によって、フィルターが早期終了(行数不足や結果の欠落)を引き起こしたり、処理量(レイテンシー)を増やしたりすることがあります。

反復スキャン(フィルターの選択性が高い場合に推奨)

デフォルトでは、WHERE 句があると HNSW が早期に検索を終了することがあり、期待よりも少ない結果しか返されない場合があります。反復スキャンを有効にすると、フィルターに一致する行が十分に見つかるまで HNSW が検索を続けます。

フィルターによって結果セットが大幅に絞り込まれる場合は、反復スキャンを有効にしてください。

-- Enable iterative scans for filtered queries
SET hnsw.iterative_scan = relaxed_order;

SELECT id, contents
FROM items
WHERE category_id = 123
ORDER BY embedding <=> $1::halfvec(1536)
LIMIT 10;

それでも結果が少ない場合は、スキャンの予算を増やします。

SET hnsw.max_scan_tuples = 50000;

トレードオフ:hnsw.max_scan_tuples を増やすと再現率は向上しますが、レイテンシが大幅に増加する可能性があります。

反復スキャンが不要な場合:

  • フィルターがテーブルの大部分に一致する場合(選択性が低い)
  • B-tree インデックスを使用して事前フィルタリングしている場合
  • 単一のパーティションまたは部分インデックスをクエリする場合
適切なフィルタリング戦略を選ぶ

選択性の高いフィルター(約 1 万行未満) フィルター列に B-tree インデックスを使用すると、Postgres が ANN の前に事前フィルタリングできます。

CREATE INDEX ON items (category_id);

カーディナリティの低いフィルター(異なる値が少ない場合) フィルター値ごとに部分 HNSW インデックスを使用します。

CREATE INDEX ON items
USING hnsw (embedding halfvec_cosine_ops)
WHERE category_id = 11;

フィルター値が多い場合、またはデータセットが大きい場合 フィルターキーでパーティション分割し、各 ANN インデックスを小さく保ちます。

CREATE TABLE items (
  embedding halfvec(1536),
  category_id int
) PARTITION BY LIST (category_id);
主なルール
  • 一致する行が少ないフィルターには、事前フィルタリング、パーティション分割、または反復スキャンが必要です。
  • 現実的な負荷のもとで p95/p99 レイテンシと訪問タプル数を測定し、フィルター付きクエリを必ず検証してください。
代替手段:ラベルベースのフィルタリングに pgvectorscale を使用する

ラベルベースのフィルターを使用する大規模なデータセットでは、pgvectorscale の StreamingDiskANN インデックスが smallint[] 列のフィルター付きインデックスをサポートします。ベクトルと併せてラベルをインデックス化するため、HNSW の後処理フィルタリングで精度が低下することなく、効率的なフィルター付き検索が可能です。セットアップの詳細については、pgvectorscale のドキュメントを参照してください。

一括読み込み

-- COPY is fastest; binary format is faster but requires proper encoding
-- Text format: '[0.1, 0.2, ...]'
COPY items (contents, embedding) FROM STDIN;
-- Binary format (if your client supports it):
COPY items (contents, embedding) FROM STDIN WITH (FORMAT BINARY);

-- Add indexes AFTER loading
SET maintenance_work_mem = '4GB';
SET max_parallel_maintenance_workers = 7;
CREATE INDEX ON items USING hnsw (embedding halfvec_cosine_ops);

メンテナンス

  • 定期的に VACUUM を実行してください。更新や削除の後、古いエントリーは VACUUM が実行されるまで残る場合があります。
  • パフォーマンスが低下した場合は REINDEX を実行してください。高頻度の変更後にグラフを最初から再構築します。
  • 削除が頻繁に発生する書き込み負荷の高いワークロードでは、IVFFlat の使用、または hypertables を使用した時間によるパーティション分割を検討してください。

モニタリングとデバッグ

-- Check index size
SELECT pg_size_pretty(pg_relation_size('items_embedding_idx'));

-- Debug query performance
EXPLAIN (ANALYZE, BUFFERS) SELECT id, contents FROM items ORDER BY embedding <=> $1::halfvec(1536) LIMIT 10;

-- Monitor index build progress
SELECT phase, round(100.0 * blocks_done / nullif(blocks_total, 0), 1) AS "%" 
FROM pg_stat_progress_create_index;

-- Compare approximate vs exact recall
BEGIN;
SET LOCAL enable_indexscan = off;  -- Force exact search
SELECT id, contents FROM items ORDER BY embedding <=> $1::halfvec(1536) LIMIT 10;
COMMIT;

-- Force index use for debugging
BEGIN;
SET LOCAL enable_seqscan = off;
SELECT id, contents FROM items ORDER BY embedding <=> $1::halfvec(1536) LIMIT 10;
COMMIT;

よくある問題(症状 → 対処方法)

症状考えられる原因対処方法
クエリで ANN インデックスが使用されないORDER BY と LIMIT がない、演算子が一致しない、または暗黙の型キャストが発生しているインデックスの演算子クラスに一致する距離演算子で ORDER BY を使用し、クエリベクトルを明示的に型キャストします
期待よりも少ない結果しか返されない(フィルター付きクエリ)フィルターにより HNSW が早期に検索を終了する反復スキャンを有効にする、hnsw.max_scan_tuples を増やす、または事前フィルタリング(B-tree)、部分インデックス、パーティション分割を使用します
期待よりも少ない結果しか返されない(フィルターなしクエリ)ANN の再現率が低すぎるhnsw.ef_search を増やします
CPU 使用率が低いのにレイテンシが高いHNSW インデックスがメモリに常駐していないhalfvec を使用する、m/ef_construction を減らす、RAM を増やす、パーティション分割する、またはバイナリ量子化を使用します
インデックスの構築が遅い構築用メモリまたは並列処理が不十分maintenance_work_mem と max_parallel_maintenance_workers を増やし、一括読み込みの後に構築します
メモリ不足エラーが発生するインデックスが利用可能な RAM に対して大きすぎるhalfvec を使用する、インデックスのパラメーターを減らす、または再ランキングを伴うバイナリ量子化に切り替えます
結果がゼロ件、または一部の結果が欠落するNULL またはゼロのベクトルNULL の埋め込みを避け、コサイン距離ではゼロベクトルを使用しないでください

コメント