pgvector 実践 — PostgreSQL でベクトル検索を運用するときに知っておくこと

読了 5分

埋め込み検索の最初の選択肢は、専用ベクトル DB ではなく pgvector です。 すでに PostgreSQL を使っているサービスなら、拡張 1 つでベクトル検索が加わり、トランザクション・結合・バックアップが既存データと一体で回ります。埋め込みとは何で、どんな検索を可能にするのかは埋め込みとベクトル検索で扱ったので、この記事はその次、運用観点の実践を扱います。基準は pgvector 0.8 系(PostgreSQL 18 対応)です。

基本: 型、演算子、インデックス #

テーブル作成と検索
CREATE EXTENSION vector;

CREATE TABLE documents (
    id        bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    content   text NOT NULL,
    category  text NOT NULL,
    embedding vector(1536)          -- 埋め込みモデルの次元数と一致させる必要があります
);

-- コサイン距離で一番近い 10 件
SELECT id, content
FROM documents
ORDER BY embedding <=> '[0.01, -0.02, ...]'::vector
LIMIT 10;

距離演算子は 3 つが主力です。<=>(コサイン距離)、<->(ユークリッド)、<#>(内積の負数)。テキスト埋め込みはほとんどコサイン距離(<=>)が既定値で、モデルのドキュメントが指定する距離に従えば十分です。インデックスなしでも動きますが全件スキャンなので、数万行を超えたらインデックスが必須です。

インデックス: HNSW が既定値です #

項目HNSWIVFFlat
検索品質(再現率)高いパラメータに敏感
検索速度速い中間
ビルド時間・メモリ重い(並列ビルド対応)軽い
データ追加後の品質維持再ビルドしないと低下(中心点が固定)
HNSWインデックス作成
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops);

実務の既定値は HNSW です。 IVFFlat はビルドが軽い代わりに「インデックス作成時点のデータでクラスタの中心を固定」する構造なので、データが増え続けるサービスでは品質が少しずつ崩れます。HNSW はビルドが重い代わりに(並列ビルドで緩和)追加に強く、再現率が安定します。注意点を 1 つ。これらのインデックスは近似(ANN)検索です。100% 正確な最近傍ではなく「ほぼいつも正しい隣人」を速く返すもので、再現率と速度は hnsw.ef_search のようなパラメータで交換します。インデックスが実際に使われているかは、いつもどおり EXPLAIN で確かめます。

実践の罠: フィルタと一緒に使うとき #

pgvector の本当の価値は SQL との結合です。「category が ‘docs’ のものの中で一番近い 10 件」のように、ベクトル検索と WHERE を 1 つのクエリで書けます。ところがここに代表的な罠があります。インデックスは「近い候補 N 個」を先に取り出し、その後にフィルタが適用される構造なので、フィルタがきついと候補が濾され尽くして 10 件を埋められない取りこぼしが起きます。pgvector 0.8.0 からは反復インデックススキャン(iterative index scan)がこの問題を吸収します。結果が足りなければ、インデックスをさらに掘って候補を追加で取ってきます。

反復インデックススキャン設定
SET hnsw.iterative_scan = strict_order;  -- フィルタ型検索の安全装置

フィルタの選択度が極端に高いなら(全体の 0.1% のような)、そもそもその部分集合だけを持つ部分インデックスやテーブル分離のほうが正しい設計かもしれません。

規模が大きくなったら: halfvec と次元の節約 #

1536 次元の float ベクトルは行あたり約 6KB です。数百万行ならベクトルだけで数十 GB になり、インデックスがメモリに収まらなくなった瞬間、検索が急激に遅くなります。対応の順序はこうです。第一に、halfvec(半精度)で保存サイズを半分にします(halfvec(1536)。再現率の損失はほとんど無視でき、HNSW の次元上限も 4000 まで上がります)。第二に、埋め込みモデル側で次元を減らします(主要モデルが次元削減出力に対応しています。埋め込みモデルの選択参照)。第三に、それでもインデックスがメモリを超えるなら、そのときが下の分かれ道です。

専用ベクトル DB が必要になるとき #

判断基準はシンプルです。ベクトル検索がサービスの主ワークロードになり、その規模が PostgreSQL 1 台のメモリ・CPU では足りないときです。数千万〜数億ベクトル、毎秒数千クエリ、ミリ秒の遅延要求のような条件が重なると、専用エンジン(分散シャーディング、GPU アクセラレーション)が自分の舞台を持ちます。逆に数百万ベクトル以下で「既存データと結合してトランザクションで束ねる」要求があるなら、運用システムを 1 つ増やすコストが専用 DB の利得を相殺してお釣りが来ます。DynamoDB vs RDS で扱った「専用ストア追加のコスト」の論理が、ここでもそのまま当てはまります。

まとめ #

  • すでに PostgreSQL を使っているなら、ベクトル検索の最初の選択肢は pgvector です。トランザクション・結合・バックアップが一体で回ります。
  • インデックスの既定値は HNSW です。IVFFlat は増え続けるデータで品質が落ちます。ANN は近似であること、ef_search で再現率を交換することを覚えておいてください。
  • フィルタ結合検索の取りこぼしの罠は、0.8.0 の反復インデックススキャンで対応します。
  • 規模への対応順序は halfvec → 次元削減 → それでもだめなら専用 DB の検討です。
  • 専用ベクトル DB の席は「ベクトルが主ワークロード + 1 台の限界超過」のときです。それまではシステムを増やさないのが得です。
X