GEMINI LABEN
FLASH — Gemini 3.5 Flashが一般提供(GA)に。エージェントやコーディングで持続的なフロンティア性能をうたいますTIER — 3.1 Pro・3.1 Flash-Liteといった新ティアがアプリ・クラウド・業務ツールへ順次展開されていますPIXEL — 6月Pixel DropでGeminiの楽曲生成、AIによる動画・音楽作成、画面録画リアクションが追加されましたOMNI — 生成のGemini Omni、推論の3 Deep Think、調査のDeep Researchが並行して強化されていますLIVE — Gemini Liveのライブやりとりが、Android・検索・YouTube・各Googleアプリへ広がっていますULTRA — Google AI Ultraは最上位アクセス・Deep Research・Veo 3動画・100万トークン文脈を提供しますFLASH — Gemini 3.5 Flashが一般提供(GA)に。エージェントやコーディングで持続的なフロンティア性能をうたいますTIER — 3.1 Pro・3.1 Flash-Liteといった新ティアがアプリ・クラウド・業務ツールへ順次展開されていますPIXEL — 6月Pixel DropでGeminiの楽曲生成、AIによる動画・音楽作成、画面録画リアクションが追加されましたOMNI — 生成のGemini Omni、推論の3 Deep Think、調査のDeep Researchが並行して強化されていますLIVE — Gemini Liveのライブやりとりが、Android・検索・YouTube・各Googleアプリへ広がっていますULTRA — Google AI Ultraは最上位アクセス・Deep Research・Veo 3動画・100万トークン文脈を提供します
記事一覧/API / SDK
API / SDK/2026-06-19上級

pgvector のセマンティック検索が半年で「鈍く」なるとき — Gemini エンベディングの再現率を守る運用メモ

Gemini Embedding と PostgreSQL pgvector で組んだ検索が、運用のうちに静かに精度を落とす理由を整理します。モデル固定・距離演算子の一致・HNSW の再インデックス・フィルタ付き検索の再現率低下まで、本番で踏んだ箇所を実装つきでまとめました。

gemini-api245pgvector4semantic-search2embeddings11postgresql2hnswproduction90

プレミアム記事

リリース直後はよく当たっていた検索が、半年ほど経つと「なんとなく鈍い」と感じる瞬間があります。エラーは出ていません。レイテンシも変わっていません。ただ、以前なら一番上に来ていた記事が3番目に落ち、ユーザーからの「探しても出てこない」という問い合わせが少しずつ増えていく。私自身、個人開発で複数のサイト横断検索を pgvector で回していて、この「静かな劣化」に何度かつかまりました。

やっかいなのは、これがコードのバグとして現れないことです。SELECT は通り、結果も返ってくる。崩れているのは結果の順位であって、可用性ではありません。ここでは Gemini Embedding と PostgreSQL pgvector で組んだセマンティック検索が運用のうちに再現率を落とす典型的な経路と、本番で実際に手を入れた対処を、順を追ってまとめます。

まず「鈍さ」を数値にする — 再現率を測れないと直せない

劣化の議論を始める前に、再現率(Recall@k)を測る仕組みがないと、すべてが体感の言い争いになります。最初にやるべきは、正解が分かっている評価セットを少量でいいので固定することです。

評価用には総当たり(インデックスを使わない厳密検索)を「真の近傍」とみなし、HNSW など近似インデックス経由の結果がそれをどれだけ取りこぼすかを測ります。pgvector では検索時に enable_indexscan を切ると総当たりに落とせます。

# recall_probe.py — HNSW の Recall@k を総当たりと突き合わせて測る
import psycopg2
 
DB = {"host": "localhost", "database": "semantic_search",
      "user": "postgres", "password": "your_password"}
 
def topk_ids(cur, qvec, k, exact: bool):
    # exact=True のときだけインデックスを無効化して総当たりにする
    cur.execute("SET LOCAL enable_indexscan = %s", ("off" if exact else "on",))
    cur.execute(
        """
        SELECT id
        FROM documents
        ORDER BY embedding <=> %s::vector
        LIMIT %s
        """,
        (str(qvec), k),
    )
    return [r[0] for r in cur.fetchall()]
 
def measure_recall(query_vectors, k=10):
    conn = psycopg2.connect(**DB)
    hits, total = 0, 0
    for qv in query_vectors:
        with conn.cursor() as cur:
            truth = set(topk_ids(cur, qv, k, exact=True))
            approx = set(topk_ids(cur, qv, k, exact=False))
        hits += len(truth & approx)
        total += k
    conn.close()
    return hits / total  # Recall@k
 
# 例: 200 件の代表クエリで Recall@10 を継続的に記録する
# print(round(measure_recall(sample_query_vecs, k=10), 4))  # 0.991 など

この値を週次で記録しておくと、後述する原因のどれが効いているかを切り分けられます。私はこの Recall@10 が 97% を下回ったらアラートにする、という運用に落ち着きました。順位がずれてからではなく、ずれ始めで気づけるのが利点です。

原因1: 格納時と検索時で「ベクトルの作り方」がずれている

最も多く、そして最も見落とされるのがこれです。エンベディングは「同じモデル・同じ次元・同じ正規化・同じ用途指定」で作られたベクトル同士でないと、距離が意味を持ちません。運用が長くなると、ここが少しずつずれていきます。

典型的なずれ方は3つあります。

ずれの種類起きる経緯結果
モデルの暗黙更新コードが latest エイリアスを参照し、裏でモデルが入れ替わった新規格納分だけ別空間のベクトルになり、既存と混ざる
task_type の不一致格納は RETRIEVAL_DOCUMENT、検索クエリも同じものを使い回したクエリ側の最適化が効かず再現率が静かに低下
次元の取り違えoutput_dimensionality を後から変えた/正規化を忘れた距離スケールが変わり閾値が無意味化

対策は単純で、「ベクトル生成の設定を一箇所に固定し、モデル ID を明示的にピン留めする」ことに尽きます。latest のようなエイリアスを本番の格納・検索パスで使わないのが肝心です。

# embedding_config.py — 生成設定を1箇所に固定する
from google import genai
 
client = genai.Client(api_key="YOUR_GEMINI_API_KEY")
 
# モデルIDはエイリアスではなく固定版を明示する。次元も固定し、
# このモジュール以外からは embed を呼ばせない運用にする。
EMBED_MODEL = "gemini-embedding-001"   # ← latest/exp を本番で使わない
EMBED_DIM = 768
 
def embed(text: str, *, is_query: bool) -> list[float]:
    res = client.models.embed_content(
        model=EMBED_MODEL,
        contents=text,
        config={
            # 格納とクエリで task_type を必ず出し分ける
            "task_type": "RETRIEVAL_QUERY" if is_query else "RETRIEVAL_DOCUMENT",
            "output_dimensionality": EMBED_DIM,
        },
    )
    v = res.embeddings[0].values
    # 768次元など 3072 未満を指定した場合、Gemini 側で正規化されない
    # ことがあるため、コサイン前提なら自前で L2 正規化して揃える。
    norm = sum(x * x for x in v) ** 0.5
    return [x / norm for x in v] if norm else v

さらに、どの設定で作ったベクトルかを行に刻んでおくと、後から監査できます。embedding 列の隣に embed_modelembed_dim を持たせ、検索時に現行設定と一致しない行を検知できるようにしておくと、混在事故をその場で見つけられます。

ALTER TABLE documents ADD COLUMN embed_model TEXT;
ALTER TABLE documents ADD COLUMN embed_dim INT;
 
-- 現行設定と食い違うベクトルが紛れていないかを点検する
SELECT embed_model, embed_dim, count(*)
FROM documents
GROUP BY 1, 2
ORDER BY 3 DESC;
-- 行が2種類以上に割れていたら、それが「鈍さ」の正体である可能性が高い

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
格納時と検索時でモデル・次元・task_type がずれると再現率が落ちる仕組みと、それを設定で固定する具体策
HNSW の ef_search・削除行・再インデックスのタイミングを再現率の実測値から決める運用手順
フィルタ付き検索で HNSW の再現率が崩れる理由と、partial index・iterative scan(pgvector 0.8+)・候補拡大の3通りの対処
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Gemini Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

API / SDK2026-04-29
Gemini API の動的 Few-Shot 設計 — ベクター検索で実例を選び続ける自己改善型プロンプト
Few-Shot プロンプトを「固定の3例」で書いているうちは精度もコストも頭打ちになります。Gemini Embeddings + ベクター検索で例を動的に選ぶ自己改善型プロンプト設計を、コピペで動くコードと運用ループまで通しで解説します。
API / SDK2026-04-28
Gemini Embeddings × リランカーで本番RAGの精度を底上げする — Vertex AI Ranking と LLM-as-judge の使い分け
Embeddings だけでは取りこぼす「上位3件は当たり前に合うのに5件目以降に正解が埋もれる」問題を、Vertex AI Ranking API と Gemini を使ったリランカーで解決する本番アーキテクチャを実装コード付きで解説します。
API / SDK2026-04-14
Gemini API Embedding × ベクトルDB完全比較: Pinecone・Qdrant・pgvector・Cloud Spannerを本番で使い分けるガイド
Gemini text-embedding-004でPinecone・Qdrant・pgvector・Cloud Spanner Vectorを実測比較。コスト・レイテンシ・実装難易度を完全網羅した本番ベクトルDB選択ガイド。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →