2026年10月8日に、pgvectorの現行公式READMEを再読しました。READMEはPostgres 13以上への導入例、exact検索とHNSW/IVFFlatによる近似最近傍検索、vector・halfvec・bit・sparsevecの型、フィルタ後に結果が不足し得る条件とiterative index scanを案内しています。これはPostgres拡張の機能と確認項目を示す資料であり、製品間の性能順位、実データのRecall、レイテンシ、自社SLAを保証するものではありません。
ベクトルデータベースは、文章などを数値の並びに変換した「埋め込み」を保存し、近いものを探す基盤です。ただし、距離が近いことは、情報が正しいことや、利用者に閲覧権限があることを意味しません。導入判断では検索方式だけでなく、正解文書・更新・権限・運用費まで一緒に考えます。
30秒で要点
- まず通常検索で見つからなかった質問を集め、同じ質問と正解文書でベクトル検索を比較する。
- 件数だけで専用DBの必要性は決まらない。既存DBの拡張・専用サービス・自前運用を、品質と運用条件で選ぶ。
- 埋め込み、検索、回答生成は別の工程。検索の改善を、そのまま回答の正確さや売上改善と呼ばない。
件数より先に、3つの質問を置く
第一に、何が見つからないのでしょうか。型番や伝票番号の完全一致なら通常検索が合う場合があります。「返品」と「返金」のような言い換えが問題なら、ベクトル検索を比較する理由があります。両者を組み合わせる方法もあります。
第二に、誤検索のコストはどれほどでしょうか。古い就業規則を顧客や社員に示すなら、速度より版・責任部署・適用日の確認が先です。ベクトルDBは元文書の矛盾を直してくれません。
第三に、誰が運用するのでしょうか。監視、バックアップ、削除、権限制御、障害対応の担当が決まらなければ、検索精度だけが良くても継続できません。FAQ30件や100文書という数を、導入可否の境界にはしません。
表が収まらない場合は、横にスクロールできます
| 選択肢 | 比較する条件 | 見落としやすい負担 |
|---|---|---|
| 既存DB・検索基盤の拡張 | 既存の認証・監視と統合できるか | 他の処理とメモリやCPUを共有する影響 |
| 専用マネージドDB | 必要な検索・絞り込みと可用性を満たすか | 利用料、移行方法、データ保管条件 |
| OSSの自前運用 | 設定や配置を自分で管理する必要があるか | 更新、復旧、容量計画を担う人と時間 |
PostgreSQLでもpgvector拡張によりベクトル検索ができます。「リレーショナルDBは意味検索不可・低速、専用DBは高速」という一律の順位は付けられません。pgvectorの公式説明は、通常のPostgresデータとベクトルを一緒に保存できることを示しています。
似た資料を探す工程と、答えを作る工程
埋め込みは、文章や画像の特徴を数値で表す処理です。図の矢印は処理の順番であり、精度や時間短縮の割合を示していません。
本の背表紙の単語で探す方法と、内容が似ていそうな本を探す方法の違い、と考えると入口になります。ただし「司書が意味を理解した」という比喩を、そのままモデルの理解や真偽判定の説明には使えません。
1. 文書を分け、由来を残す
見出し・段落・表などの意味が途切れない単位で文書を分割します。これをチャンキングと呼びます。一律に200〜500文字を最適値とはしません。短くすると前提が欠け、長くすると別の話題が混ざるため、実際の質問で比較します。原文ID、版、更新日、部署、権限をメタデータとして残します。
原文「返品の受付条件」 → 分割した本文 + 文書ID + 版 + 権限
本文 → 埋め込みモデル → [0.23, -0.45, 0.67, …]
この数値は説明用。実モデルの出力でも、真偽の確率でもない。
2. 距離の意味を揃える
表が収まらない場合は、横にスクロールできます
| 指標 | 何を比較するか | 注意点 |
|---|---|---|
| コサイン類似度 | ベクトルの向き | ゼロベクトルでは定義できない |
| ユークリッド距離 | 座標間の距離 | ベクトルの長さも影響する |
| 内積 | 各成分の積の合計 | 正規化の有無で解釈が変わる |
どれが最速・最高精度かではなく、埋め込みモデルの想定と保存側の指標を合わせます。以下は外部送信のない計算教材です。
from math import sqrt
def cosine_similarity(a, b):
if len(a) != len(b) or not a:
raise ValueError("同じ非ゼロ次元が必要")
norm_a = sqrt(sum(x*x for x in a))
norm_b = sqrt(sum(x*x for x in b))
if norm_a == 0 or norm_b == 0:
raise ValueError("ゼロベクトルは比較しない")
return sum(x*y for x, y in zip(a, b)) / (norm_a * norm_b)
print(f"類似度: {cosine_similarity([0.5, 0.3, 0.8], [0.4, 0.2, 0.9]):.3f}")
# 類似度: 0.985。正答確率98.5%という意味ではない。
3. 正確な近傍検索と近似検索を分ける
全対象を調べる検索と、候補を絞って速く探す近似最近傍検索(ANN)は違います。pgvectorでは既定のexact検索と、HNSW・IVFFlatによる近似検索を選べます。近似検索では速度と取りこぼしの兼ね合いがあります。索引を付けた後も、正確な検索結果との一致率を測ります。
さらに、近似索引で候補を取った後のフィルタにより、指定件数より結果が少なくなる場合があります。「top_kが3だから必ず有効な3件」とは考えません。権限はアプリ側でも検査し、漏えいを防ぐ境界として設計します。
実装教材:作成・保存・検索・絞り込み
以下は2026年10月6日に公式仕様の必要範囲と照合したPythonの構成例です。外部APIやクラウドDBへの実送信・課金・性能試験は行っていません。実行する場合は現行SDK、接続先、利用条件を確認し、秘密情報や実顧客データを教材へ入れないでください。
埋め込みを作る
from openai import OpenAI
client = OpenAI() # APIキーは環境変数から読む
EMBEDDING_MODEL = "text-embedding-3-small"
def create_embedding(text):
response = client.embeddings.create(model=EMBEDDING_MODEL, input=text)
return response.data[0].embedding
OpenAIの埋め込みガイドでは、上記モデルの既定次元数は1536です。モデルやdimensions設定を変える場合、既存文書と質問に同じ変換を使い、再作成・移行を計画します。異なるモデルのベクトルを同じ意味空間として混ぜません。
同じ次元と指標の保存先を用意する
import os
from pinecone import Pinecone, ServerlessSpec
pc = Pinecone(api_key=os.environ["PINECONE_API_KEY"])
index_name = "knowledge-demo"
if not pc.has_index(index_name):
pc.create_index(name=index_name, dimension=1536, metric="cosine",
spec=ServerlessSpec(cloud="aws", region="us-east-1"))
# 作成完了を確認した後に接続する。既存索引の次元・指標も別途確認する。
index = pc.Index(index_name)
これはPineconeの外部ベクトル用索引に対応する形です。旧APIのpinecone.initを使う説明から更新しました。作成直後の準備完了待ちや接続失敗時の処理は、実アプリで実装が必要です。
メタデータ付きで保存する
documents = [
"返品は受付条件と購入日を確認する。",
"RAGは検索した資料を生成時の文脈に加える。",
"ベクトルDBは数値ベクトルを保存・検索する。",
]
records = [{"id": f"doc_{i}", "values": create_embedding(doc),
"metadata": {"text": doc, "category": "guide",
"updated_day": 20261006, "version": "demo-v1"}}
for i, doc in enumerate(documents)]
index.upsert(vectors=records, namespace="public-demo")
保存形式は公式Upsert資料、検索の引数は公式Semantic search資料の必要範囲に対応します。
質問を同じモデルで変換して探す
query_embedding = create_embedding("RAGとは何ですか?")
results = index.query(namespace="public-demo", vector=query_embedding,
top_k=3, include_metadata=True)
for match in results.matches:
print(match.id, match.score, match.metadata["text"])
日付の範囲は数値として揃える
results = index.query(namespace="public-demo", vector=query_embedding,
top_k=3, include_metadata=True,
filter={"category": {"$eq": "guide"},
"updated_day": {"$gte": 20260101}})
Pineconeのフィルタ表で、gteの対象は数値です。ここでは日付をYYYYMMDDの整数で保存しています。時刻・タイムゾーンが必要なら別の一貫した数値表現を決めます。文字列日付に数値比較演算子を付ける旧例は使いません。
モデルの変更は評価と再索引を一緒に行う
# 比較実験用の別候補。largeが自社でも常に高精度という意味ではない。
candidate_model = "text-embedding-3-large"
# 既定3072次元。別の対応索引と、同じ評価質問・正解を用意して比較する。
キャッシュは更新・権限をキーに含める
from functools import lru_cache
@lru_cache(maxsize=100)
def cached_search(vector_tuple, namespace, data_version):
return index.query(namespace=namespace, vector=list(vector_tuple), top_k=3)
# 呼出例: cached_search(tuple(query_embedding), "public-demo", "demo-v1")
# 更新・削除・権限変更時にはキーの更新またはcache_clear()が必要。
リストはキャッシュのキーにできないためtupleを使います。これは公開教材用の例です。実システムでは認可済み範囲や検索条件もキーに含め、別利用者の結果を再利用しないよう設計します。
3つの用途で、期待と測定を分ける
次は導入効果の実証例ではなく、比較計画を作るための想定場面です。
表が収まらない場合は、横にスクロールできます
| 用途 | 手順として残すもの | 成功を判断する観測 |
|---|---|---|
| 社内ナレッジ検索 | 文書収集→版と権限整理→保存→質問検索 | 正しい版を上位に返せたか、閲覧不可文書が混ざらないか |
| 商品推薦 | 説明・特徴の表現→保存→候補提示 | 類似だけでなく在庫・価格条件を満たすか。購入率は別の比較試験 |
| コンテンツ検索 | 本文とメタデータ→保存→結果表示 | 言い換え質問でも必要記事が見つかるか、確認時間は短くなるか |
旧稿の「検索時間70〜90%削減」は、対象・分母・測定記録のない数値だったため採用しません。次の架空例なら、何を測ったかを読者が追えます。
表が収まらない場合は、横にスクロールできます
| 架空の比較:同じ20質問、上位3件 | 通常検索 | ベクトル検索 |
|---|---|---|
| 正解文書が少なくとも1件入った質問 | 12 | 15 |
| 上位3件のヒット率 | 12÷20=60% | 15÷20=75% |
差は15ポイント、基準60%に対する相対増加は25%です。これは検索ヒット率であり、回答正答率ではありません。20質問の架空計算から統計的な改善や事業効果も主張できません。速度、更新・削除反映、費用、ユーザーが正解を判断する時間を別々に測ります。
失敗を減らす最小の検証
「ナレッジを後で整理する」「小さい用途でも最大構成を買う」「原因を見ずにモデルだけ替える」という旧稿の3つの失敗像は、導入前に確認する仮説として残します。文書の誤りなら資料を直し、分割の誤りなら前後の文脈を直し、絞り込みの誤りなら条件を直す、という順序です。
まず通常検索で困った質問を集め、正解となる文書と版、失敗した理由を書きます。件数の閾値で導入を決めず、その質問で品質・応答時間・運用負荷を比較してください。RAGの設計とプロンプトと学習の選択は、その次の判断に役立ちます。
参考資料
- pgvector公式README:定義、距離、索引、絞り込みの必要範囲。
- Pinecone索引作成、メタデータ絞り込み:実装例の仕様。
- OpenAI埋め込み:APIと次元数。確認日2026年10月6日。サービスの料金・性能順位は比較していません。