こんにちは、かつコーチです。
前回はJSONB型とGINインデックスで半構造化データを扱う方法を解説しました。
今回は「データ型・JSON/pgvector」編の最終回として、pgvector拡張機能によるベクトル検索を扱います。
上級者向けの内容なので、embeddingやコサイン類似度といった概念は前提知識として説明を省略し、PostgreSQL上での実装・運用に焦点を当てます。
Pinecone・Weaviateといった専用のベクトルDBを別途構築せずに、既存のPostgreSQLインフラにベクトル検索機能を組み込みたいケースを想定して解説します。
pgvectorのセットアップと選択肢比較
拡張機能の有効化
pgvectorはPostgreSQLの拡張機能として提供されているため、まずは有効化が必要です。
CREATE EXTENSION IF NOT EXISTS vector;
Dockerで検証する場合は、公式のpgvector/pgvector:pg16イメージを使うと拡張機能のビルド作業を省略できます。
services:
db:
image: pgvector/pgvector:pg16
environment:
POSTGRES_USER: appuser
POSTGRES_PASSWORD: apppassword
POSTGRES_DB: appdb
ports:
- "5432:5432"
専用ベクトルDBとpgvector、どちらを選ぶか
| 項目 | 専用ベクトルDB(Pinecone等) | pgvector |
|---|---|---|
| インフラ構成 | 別サービスとして構築・運用が必要 | 既存のPostgreSQLに同居できる |
| リレーショナルデータとの結合 | アプリ側で別々に取得して突き合わせる | SQLのJOINでそのまま結合できる |
| 大規模スケール時の性能 | 専用設計のため優位な場合が多い | 規模によってはチューニングが必要 |
| 運用コスト | サービス利用料・別途運用工数 | 既存DBの運用に相乗り |
- 既存のリレーショナルデータとベクトル検索結果を1つのクエリで結合したい、運用インフラを増やしたくない → pgvector
- 数億件規模のベクトルを扱う、検索レイテンシを極限まで詰めたい → 専用ベクトルDB
「ECサイトの商品データに類似商品検索を追加する」程度の規模であれば、私は基本的にpgvectorを第一候補にしています。
商品テーブルとの結合が1クエリで完結する開発体験のメリットが大きいためです。
ベクトル型カラムの設計と類似検索
テーブル定義とデータ登録
CREATE TABLE articles (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
body TEXT NOT NULL,
embedding VECTOR(1536) -- OpenAIのtext-embedding-3-small相当の次元数
);
INSERT INTO articles (title, body, embedding)
VALUES (
'PostgreSQL入門',
'PostgreSQLは拡張性の高いRDBMSです。',
'[0.012, -0.034, 0.081, ...]' -- 実際はembedding APIから取得した1536次元の配列
);
VECTOR(1536)のように次元数を明示して定義します。
次元数はembeddingモデルによって決まるため、使用するモデルの仕様に合わせてカラム定義を変更してください。
距離関数の選択
pgvectorは複数の距離関数(演算子)をサポートしています。
-- <-> : ユークリッド距離
-- <#> : 負の内積(コサイン類似度と相関)
-- <=> : コサイン距離(1 - コサイン類似度)
SELECT title, embedding <=> '[0.01, -0.03, 0.08, ...]' AS distance
FROM articles
ORDER BY distance
LIMIT 5;
一般的なテキストembedding(OpenAI等)ではコサイン距離(<=>)が使われることが多く、値が小さいほど類似度が高いことを示します。
ORDER BY distance LIMIT Nという形が、いわゆるk近傍検索(k-NN)の基本パターンです。
ANNインデックスで検索を高速化する
インデックスなしでの限界
pgvectorはインデックスなしでも動作しますが、その場合は全行に対して距離計算を行う厳密検索(Exact Nearest Neighbor)になります。
数百件程度なら問題ありませんが、件数が数万〜数十万件を超えると、クエリのたびに全件の距離計算が発生し、レスポンスが著しく悪化します。
IVFFlatとHNSW、どちらを選ぶか
pgvectorには近似最近傍探索(ANN)用のインデックスとしてIVFFlatとHNSWが用意されています。
| 項目 | IVFFlat | HNSW |
|---|---|---|
| インデックス構築速度 | 速い | 遅い(データ量が多いと顕著) |
| 検索速度 | HNSWよりやや遅い | 速い |
| メモリ使用量 | 少なめ | 多め |
| データ追加時の挙動 | クラスタ再構築が推奨される場面がある | 追加データにも比較的頑健 |
| 事前準備 | ある程度のデータ件数が必要 | データが少なくても構築可能 |
- 検索速度を最優先し、メモリに余裕がある → HNSW
- インデックス構築を高速に済ませたい、メモリ制約がある → IVFFlat
- 迷ったら → pgvectorの公式ドキュメントでもHNSWが概ね推奨されており、特別な制約がなければHNSWを選んでおくのが無難
-- HNSWインデックスの作成(コサイン距離用)
CREATE INDEX idx_articles_embedding_hnsw
ON articles USING hnsw (embedding vector_cosine_ops);
vector_cosine_opsのように、使用する距離関数に対応した演算子クラスを指定する必要がある点に注意してください。
<->(ユークリッド距離)を使う場合はvector_l2_opsを指定します。
つまずきやすいポイント:次元数の不一致
embeddingモデルを変更した際の実体験
❌ Before:既存のVECTOR(1536)カラムに、次元数の異なるembeddingをそのまま挿入しようとする
-- text-embedding-3-largeなど、次元数が異なるモデルに切り替えた場合
INSERT INTO articles (title, body, embedding)
VALUES ('新規記事', '本文...', '[0.01, -0.02, ...]'); -- 3072次元のデータ
-- ERROR: expected 1536 dimensions, not 3072
私が実際にembeddingモデルをtext-embedding-3-smallからtext-embedding-3-largeへ切り替える検証を行った際、この次元数不一致エラーに遭遇しました。
VECTOR型は次元数を固定長で管理しているため、モデルを変更すると既存のカラム定義との間で不整合が起きます。
✅ After:新しいカラムを追加し、移行期間を設けてから切り替える
-- 新しい次元数用のカラムを追加
ALTER TABLE articles ADD COLUMN embedding_v2 VECTOR(3072);
-- 既存データを新しいembeddingで再計算し、embedding_v2に投入する
-- (アプリケーション側でembedding APIを呼び出してUPDATEする処理を実行)
-- 移行完了後、古いカラムとインデックスを削除する
-- ALTER TABLE articles DROP COLUMN embedding;
embeddingモデルのバージョンアップは今後も起こりうる前提で、カラムを使い回すのではなく、新カラムを追加して段階的に移行する設計にしておくと、本番運用での事故を防げます。
まとめ
この記事のポイント
- pgvectorは既存のPostgreSQLにベクトル検索機能を組み込める拡張機能で、リレーショナルデータとの結合に強みがある
<->(ユークリッド距離)、<#>(負の内積)、<=>(コサイン距離)を用途に応じて使い分ける- データ件数が増えたらHNSW(基本推奨)またはIVFFlatのANNインデックスを構築する
- embeddingモデルを変更する際は次元数の不一致に注意し、カラムを新設して段階移行する
次に読むべき記事
これで「データ型・JSON/pgvector」編は完結です。
次のカテゴリでは、CTEやウィンドウ関数といったPostgreSQLのクエリ応用テクニックを扱っていきます。
→ 次の記事:CTE(WITH句)の基本:クエリを読みやすく整理する
タグ: PostgreSQL, 上級者向け, データ型