•10 min read

pgvectorを本番環境で使う: PostgreSQLでの高スケールベクトル検索とハイブリッドRRF

pgvectorを本番環境で使う: PostgreSQLでの高スケールベクトル検索とハイブリッドRRF

生成AIとRAG(Retrieval-Augmented Generation)アプリケーションの初期段階では、エンジニアリングチームはPinecone、Weaviate、Qdrant、Milvusなどの特殊なスタンドアロン型ベクトルデータベースをこぞって採用しました。

しかし、既存のリレーショナルデータベースと並行して別のベクトルデータベースを運用すると、デュアルライト同期の失敗、結果整合性の遅延、アクセス制御ポリシーの重複、そして困難な分散トランザクションのロールバックといった深刻な運用上の問題が発生します。

pgvector (v0.7+) のリリースにより、PostgreSQLは本番環境のベクトル検索における主要なプラットフォームとしての地位を確立しました。高次元の埋め込みを標準のPostgreSQLテーブル内に直接保存することで、ACIDトランザクション、使い慣れたSQLセマンティクス、堅牢なバックアップツール、そして単一のアトミッククエリでリレーショナル外部キーに対してベクトルクエリを結合する機能が維持されます。

このガイドでは、本番環境でのインデックス戦略(HNSW vs IVFFlat)を分解し、メモリパラメータを最適化し、純粋なSQLでReciprocal Rank Fusion (RRF) を使用してハイブリッド検索を実装し、本番環境のPythonクライアントを構築します。


Audio Briefing
0:00 / 0:00

なぜPostgreSQLに統合するのか?デュアルデータベースの罠

運用データとベクトル埋め込みを分離した場合に何が起こるか考えてみましょう。

[The Dual-Database Trap]
  User Action ──► Write to Primary Database (PostgreSQL)
                        │
                        ▼ (CDC Pipeline / Debezium / Celery Worker)
                  Dual-Write Lag & Potential Desync!
                        ▼
                  Write to Vector DB (Pinecone / Qdrant)

[The Unified pgvector Architecture]
  User Action ──► Write to PostgreSQL (Atomic Transaction)
                        ├── Relational Metadata (user_id, tenant_id, created_at)
                        └── High-Dimensional Vector (embedding vector(1536))
                  ✓ Zero Synchronization Lag
                  ✓ ACID Guarantees & Atomic Rollbacks
                  ✓ Unified Row-Level Security (RLS)

pgvectorでは、ベクトル埋め込みは単なるネイティブデータ型です。トランザクションが失敗したりロールバックしたりしても、埋め込みはリレーショナルレコードと100%一貫性を保ちます。


Advertisement

pgvectorと距離演算子の設定

拡張機能をインストールし、ベクトル次元サイズを定義します。次元サイズは埋め込みモデルと正確に一致する必要があります(例:OpenAI text-embedding-3-smallの場合は1,536、text-embedding-3-largeの場合は3,072)。

-- Enable the vector extension
CREATE EXTENSION IF NOT EXISTS vector;

-- Create production knowledge base table
CREATE TABLE document_chunks (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    document_id UUID NOT NULL,
    tenant_id UUID NOT NULL,
    content TEXT NOT NULL,
    metadata JSONB DEFAULT '{}'::jsonb,
    embedding vector(1536) NOT NULL,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

適切な距離メトリックの選択

pgvectorは3つの距離演算子を提供します。

  1. <=> コサイン距離: ベクトル間の角度を測定します(値は0から2の間で、0が同一)。正規化されたテキスト埋め込みの標準的なメトリックです。
  2. <-> ユークリッド距離 (L2): 直線的な空間距離を測定します。マグニチュードが重要な場合に最適です。
  3. <#> 負の内積: ドット積に-1を乗じたものと同等です。埋め込みモデルが事前に正規化されたベクトル(OpenAIやCohereなど)を生成する場合、内積はコサイン類似度と数学的に同一ですが、平方根の正規化ステップをスキップするため、20%から30%高速に計算されます!

インデックス戦略: HNSW vs IVFFlat

インデックスがない場合、PostgreSQLはすべてのベクトルに対して正確なシーケンシャルスキャン(O(n)のフラット検索)を実行します。100%正確ですが、テーブルが50,000ベクトルを超えると、フラットスキャンは本番環境では遅すぎます。

pgvectorは2つの近似最近傍(ANN)インデックスアルゴリズムを提供します。

1. IVFFlat (Inverted File Flat)

  • k-meansを使用してベクトルをクラスター(リスト)に分割します。
  • クラスタリングアルゴリズムが代表的なサンプルを持つように、インデックスを作成する前にテーブルにデータを事前投入する必要があります。
  • 構築時間は中程度でメモリ消費量も少ないですが、新しいベクトルが挿入されると検索の再現率が低下します。

2. HNSW (Hierarchical Navigable Small World) — 本番環境向けに推奨

  • 接続されたベクトルの多層グラフを構築します。
  • 空のテーブルに作成でき、新しい行が挿入されても高い再現率を継続的に維持します。
  • 高いRAM使用量と長いインデックス構築時間というコストはかかりますが、検索クエリは高速(O(\log n))で、再現率も優れています(98%以上)。
-- Production HNSW Index Configuration
CREATE INDEX idx_document_chunks_hnsw_cosine 
ON document_chunks 
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);

HNSWパラメータのチューニング

  • m: ノードあたりの双方向リンクの最大数(デフォルト: 16)。高い値(例:24~32)は、メモリを犠牲にして高次元埋め込みの再現率を向上させます。
  • ef_construction: インデックス構築中に評価される動的候補リストのサイズ(デフォルト: 64)。128に増やすと、グラフの接続性とクエリの精度が向上します。
  • hnsw.ef_search: クエリ時の精度とレイテンシを制御するために実行時に設定します。
-- Tune per-session or per-query search depth
SET hnsw.ef_search = 100; -- Default is 40; higher values yield higher recall

ハイブリッド検索: 密なベクトルとBM25キーワード検索の組み合わせ

密なベクトル検索は、概念的な同義語(「自動車」が「車」に一致する)を理解するのに優れています。しかし、ベクトル検索は、部品番号、エラーコード(ERR_404_NULL)、固有名詞などの正確なキーワード検索には苦戦します。

業界のゴールドスタンダードは、フルテキスト検索(tsvector)とセマンティックベクトル距離を組み合わせたReciprocal Rank Fusion (RRF) によるハイブリッド検索です。

-- Hybrid Search using Reciprocal Rank Fusion in pure PostgreSQL
WITH semantic_search AS (
    SELECT id, content, RANK() OVER (ORDER BY embedding <=> '[0.012, -0.045, ...]') AS rank
    FROM document_chunks
    WHERE tenant_id = 'a1b2c3d4-e5f6-7890-abcd-1234567890ab'
    ORDER BY embedding <=> '[0.012, -0.045, ...]'
    LIMIT 20
),
keyword_search AS (
    SELECT id, content, RANK() OVER (ORDER BY ts_rank_cd(to_tsvector('english', content), query) DESC) AS rank
    FROM document_chunks, plainto_tsquery('english', 'database connection pooling') query
    WHERE tenant_id = 'a1b2c3d4-e5f6-7890-abcd-1234567890ab'
      AND to_tsvector('english', content) @@ query
    LIMIT 20
)
SELECT 
    COALESCE(s.id, k.id) AS id,
    COALESCE(s.content, k.content) AS content,
    COALESCE(1.0 / (60 + s.rank), 0.0) + 
    COALESCE(1.0 / (60 + k.rank), 0.0) AS rrf_score
FROM semantic_search s
FULL OUTER JOIN keyword_search k ON s.id = k.id
ORDER BY rrf_score DESC
LIMIT 10;

Advertisement

SQLAlchemyとpgvectorによる本番環境Python統合

# db_vector_client.py
import os
from sqlalchemy import create_engine, select, text
from sqlalchemy.orm import declarative_base, Session
from pgvector.sqlalchemy import Vector

Base = declarative_base()

class DocumentChunk(Base):
    __tablename__ = 'document_chunks'
    
    id = Column(UUID, primary_key=True)
    tenant_id = Column(UUID, nullable=False)
    content = Column(Text, nullable=False)
    embedding = Column(Vector(1536), nullable=False)

def query_similar_chunks(tenant_id: str, query_embedding: list[float], limit: int = 5):
    engine = create_engine(os.getenv("DATABASE_URL"))
    with Session(engine) as session:
        # Cosine distance ordering using native pgvector operator
        stmt = (
            select(DocumentChunk)
            .filter(DocumentChunk.tenant_id == tenant_id)
            .order_by(DocumentChunk.embedding.cosine_distance(query_embedding))
            .limit(limit)
        )
        return session.scalars(stmt).all()

よくある質問

1,000,000個のベクトルに対してpgvectorはどのくらいのRAMを必要としますか?

1,536次元の1,000,000個のベクトルに対して、生のベクトルデータは約6GBのストレージを占有します。m=16のHNSWインデックスは約2.5GBのインデックスデータを追加します。最適な10ms未満のクエリレイテンシを実現するには、PostgreSQLサーバーが十分なshared_buffersとRAM(少なくとも16GBから32GB)を持ち、HNSWインデックス全体をメモリに保持できることを確認してください。

pgvectorは5,000万以上のベクトルにスケールできますか?

はい。1,000万から5,000万以上のベクトルでは、本番チームはPostgreSQLテーブルパーティショニング(例:tenant_idまたはcreated_at年によるパーティショニング)を使用します。個々のパーティションにローカルHNSWインデックスを作成することで、各インデックスをメモリバッファに収まる十分な小ささに保ちます。

pgvectorはPineconeと比較してレイテンシはどうですか?

HNSWインデックスがRAMに収まる場合、pgvectorは4msから12msのクエリレイテンシを提供し、専用のクラウドベクトルデータベースと同等かそれ以上の性能を発揮します。これにより、APIサーバーとサードパーティデータベースエンドポイント間のインターネットネットワーク転送遅延が解消されます。


こちらもおすすめ

Share this article:

Stay Updated

Get the latest posts delivered straight to your inbox.

Free Developer Utilities

Free In-Browser Developer Tools

Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

Explore Tools
Advertisement