ベクトルDB本番1年で学んだ、Embedding戦略の地味で重い落とし穴
Pinecone本番運用で検索精度が急落。次元数・更新戦略・コスト最適化の失敗談と解決方法を、実例データとともに解説します。
ベクトルDBで本番が死んだ日
うちのチームがPineconeを本番に入れて3ヶ月で、検索精度が急落して大騒ぎになったんですよ。当時は「Embeddingが出力されてりゃいいだろ」くらいの認識でして。問題が出た時点で初めて気づいたことがいっぱいあった。
正直、業界全体でベクトルDB関連の知見は「とりあえずPinecone」「次元数は1536」みたいな杜撰な導入が多い気がしてて。でも本番で1年運用してると、その適当さが地味だけどしんどい課題になってくるんですよ。今回は、うちが痛い目を見て気づいたことを全部吐き出します。
次元数「1536が正解」は罠だった
最初、OpenAIのtext-embedding-3-largeで出力される1536次元がデフォルトみたいになってた。でも実装してみると、これがコストにめちゃ響くんですよ。
うちの場合、月10万埋め込みのドキュメントがあって、Pineconeの従量課金を計算すると:
- 次元数1536:月額 ¥25,000〜30,000
- 次元数768:月額 ¥12,000〜15,000
- 次元数256:月額 ¥5,000〜7,000
で、実際に768に落とした時に検索精度ってどうなるのか検証してみたんですよ。テストセット500クエリで比較した結果がこれです:
xychart-beta
title "次元数別の検索精度(Recall@10)"
x-axis [256, 512, 768, 1024, 1536]
y-axis "Recall@10 (%)" 65 --> 95
line [68, 78, 85, 89, 91]
正直、768と1536の差って3%程度なんですよ。ビジネス的には誤差レベル。それでコストが倍違うなら、768でいいじゃんって判断が自然な流れになった。
ただしここで重要なのが、Embeddingモデルの選択によって適切な次元数が変わるという点なんです。OpenAI、Cohere、Anyscaleでそれぞれ推奨が違う:
| モデル | 標準次元 | 推奨削減先 | 精度低下 |
|---|---|---|---|
| text-embedding-3-large | 1536 | 768 | 3-5% |
| text-embedding-3-small | 512 | 256 | 2-3% |
| Cohere V3 | 1024 | 512 | 4-7% |
| Jina AI | 768 | 384 | 5-8% |
うちの失敗は「とにかく高次元」の思い込みでした。自社データセットの特性を考えると、むしろ512の方が本来は良かった。だから今は検索クエリと参考資料の組み合わせで次元数を分けてます。
Embedding更新戦略で本番が沈黙した
更に痛い目を見たのが、Embedding更新のタイミング問題です。
ドキュメント更新されたとき、古いEmbeddingが残ってて検索結果がおかしくなる。でもベクトルDBって「このドキュメント更新されました」みたいなイベント駆動の自動更新がないんですよ。都度、ドキュメントを再Embed→ベクトルDB更新のパイプラインを自分たちで作る必要がある。
うちが最初にやってたのは「夜間バッチで全ドキュメント再Embed」という素人くさい実装で、これが月6時間のダウンタイム引き起こしてた。本番検索が止まるわけですよ。
今は以下の3段階構成に変えました:
flowchart TD
A[ドキュメント更新イベント] --> B{更新タイプ判定}
B -->|メタデータのみ| C[即座に部分更新]
B -->|本文内容| D[Embedding生成キュー]
D --> E[非同期Embed実行]
E --> F[新Embeddingを試験環境へ]
F --> G{精度検証パス?}
G -->|YES| H[本番投入]
G -->|NO| I[ロールバック]
C --> H
イベント駆動にしたことで、更新遅延は3秒→30分程度に改善。完全な即座更新は難しいですが、「古いEmbeddingが無限に残る」という地獄は消えました。
Pinecone vs Weaviate vs Milvus、1年運用して見えたこと
うちのチームでは実際に3つのベクトルDBを使い分けてます。最初は「どれか1個選べ」という圧力があったんですが、正直なところ使い分けが正解だった。
| 観点 | Pinecone | Weaviate | Milvus |
|---|---|---|---|
| セットアップ時間 | 15分(マネージド) | 1日(自運用) | 3日(K8s) |
| 月額コスト(10GB) | ¥25,000 | ¥0(自サーバー) | ¥0(自サーバー) |
| 検索レイテンシ | 50-100ms | 100-200ms | 30-80ms |
| メタデータフィルタリング | ◎ | ◎ | △ |
| スケーラビリティ | 無制限 | 数TB | 数百GB/ノード |
| 運用の楽さ | ★★★★★ | ★★★☆☆ | ★★☆☆☆ |
Pineconeは正直、オプション有り有りだと高い。でも運用負荷がほぼゼロというのは、スタートアップには無視できないメリット。一方、Milvusは自分たちでK8sで運用できる規模なら圧倒的に安い。
うちの現在の使い分けはこんな感じです:
- Pinecone:顧客向けRAGのメイン検索。検索品質とSLAが重要だから、マネージドサービスの安定性を優先
- Weaviate:社内ナレッジグラフ。グラフ構造が必要で、スケール要件が低いため自運用でも十分
- Milvus:大規模ログ分析用の検索。自社K8sで運用してコスト最優先
Milvusは正直、ドキュメント少ないし日本語情報も限定的で、ハマると地獄ですよ。でも「月5万以上のベクトルDB代払える?」という現実的な質問には敵わない。
Embedding再生成とバージョニングの苦しみ
これも地味だけど痛い話なんですが、Embeddingモデルのバージョン更新で本番が揺れます。
例えば、text-embedding-3-largeの使用をやめて、新しいモデル(OpenAIのv3.5とか)に移行するとしましょう。すると既存の1000万ドキュメントのEmbeddingを全部作り直す必要が出てくる。これがマジで地獄。
うちがやってる対策は、Embedding生成時にモデルバージョン情報をメタデータに含めることです。日付までいれることで、後から追跡可能にしてます。全ドキュメント一気に再Embedするのはコスト的に月10万以上かかるので、段階的にやります。検索クエリ側も新旧両方のEmbeddingで実行して、精度確認してから完全移行。このプロセスは2-3ヶ月かかることもあります。
RAG導入の現実的な構成
ベクトルDB単体では何もできないんで、実際のRAG構成を示します:
graph TB
subgraph "Ingestion Pipeline"
A[Document Sources]
B[Chunking<br/>RecursiveCharacterSplitter]
C[Embedding<br/>OpenAI v3-large]
D[Metadata Extract]
end
subgraph "Vector DB Cluster"
E[Pinecone Index]
F[Weaviate Nodes]
end
subgraph "Query Pipeline"
G[User Query]
H[Query Embedding]
I[Hybrid Search]
J[Re-ranking<br/>Cohere Rerank]
end
subgraph "LLM Generation"
K[Context Assembly]
L[LLM Prompt]
M[Response]
end
A --> B --> C --> D --> E
A --> B --> C --> D --> F
G --> H --> I
E --> I
F --> I
I --> J
J --> K --> L --> M
ハイブリッド検索が重要です。Embedding検索だけだと、スペリング違いとか稀有な用語に弱い。BM25みたいなキーワード検索と組み合わせてから、Re-rankで上位を再順序付けするのが実際には効きます。
チャンキング戦略が9割決める
これは実務では超重要な話なんですが、Embeddingの品質は実はモデルよりチャンキング戦略に左右されます。
同じドキュメントでも、固定サイズ500文字で切ったら文の途中で分断されて文脈が失われる。セマンティック境界で切れば、段落単位やテーブル分離、コード分離がうまくいく。うちはLangChainのRecursiveCharacterSplitterから始まって、今はセマンティックチャンクを試験中です。Embedding前に複数のチャンク戦略を並列実行して、どれが検索精度高いか測ってます。
現在の成功例を挙げると:
- 技術ドキュメント:見出しベース + 最大1000文字で、コンテキストが保持される
- コードベース:関数単位 + 周辺コメント付きで、意図が分かりやすい
- 規約類:節ベース + 段落ごとで、検索しやすい
同じモデルでも、チャンキングを適切にするだけで精度5-15%改善した経験は複数あります。
コスト最適化の現実
Embedding代とベクトルDB代がどこまで膨らむのか、シミュレーションしてみた結果がこれです:
xychart-beta
title "年間コスト比較(月額)"
x-axis [10万Doc, 100万Doc, 1000万Doc]
y-axis "月額費用" 0 --> 500000
line [15000, 80000, 350000]
line [0, 5000, 30000]
上がPinecone、下がセルフホストMilvusです。100万ドキュメント超えたあたりで、セルフホストの圧倒的優位性が出てくる。ただし、運用コスト(人件費)を含めると、小〜中規模ならPinconeの方が実は安いことも多い。
うちは現在、Pinecone(検索メイン)とMilvus(バックアップ・分析用)の混合運用で、年間500万程度の費用に収まってます。
本番で気をつけてる細かいこと
Embedding遅延とタイムアウト
OpenAI APIのレート制限(tokens per minute)で引っかかると、Embedding生成が遅延します。うちは最初「バッチで1000個一気に投げればいい」と思ってたんですが、これがタイムアウトの温床。今は100個バッチで、リトライスケジュール付きで実装してます。
メタデータフィルタリングの設計
ベクトルDB検索の結果を、メタデータで絞り込む機能は必須。例えば「ユーザーAが見られるドキュメントだけ」みたいな権限管理がそう。Pineconeだと metadata filtering が標準機能ですが、実装側が複雑になりやすい。
うちはSparkを使ったオフラインで権限情報を更新して、ベクトルDBのメタデータに同期する運用にしてます。リアルタイム権限変更は対応してません(実務的には不要だから)。
大規模スケーリング時の部分更新
ドキュメント1個更新されたとき、Pineconeなら部分更新(upsert)ですぐ反映。でも大量バッチだと、キューイング→非同期処理のパイプラインが必要。Kafkaを間に挟むと強いです。
まとめ
1年ベクトルDB本番運用してわかった最大のポイントは、「Embedding生成は最初の一歩に過ぎない」ということなんですよ。その後の更新戦略・チャンキング・メタデータ管理・コスト最適化が本当の勝負なんだ。
次のアクションとしては、こんなことを試してみてください:
- 今Pineconeを使ってるなら、次元数削減で月コスト1/3にできないか検証してみる
- Embeddingモデルのバージョニング戦略を決めておく(後で泣くことになる)
- セマンティックチャンキング試して、精度改善幅を測る
- 100万ドキュメント超える計画があるなら、セルフホストMilvusの検証を今から始める
ベクトルDBの話は、まだまだ成熟途上なんですよ。2026年時点でも新しい知見がポンポン出てくる領域です。皆さんはどんな課題に直面してますか?コメント欄で聞かせてください。