ほとんどのベクトルデータベースのプロトタイプは、インポート段階で失敗します。巧妙な検索パイプラインを構築し、数千のドキュメントで動作することを確認した後、誰かが5000万行のデータを渡します。次の2週間は、レート制限、部分的な失敗、バッチロジックの3回の書き直しに費やされることになります。
この記事は、私が初めて実際のデータセットを Weaviate にインポートしたときに欲しかったガイドです。サーバーサイドバッチ処理、エラーハンドリング、間違えると最もコストがかかるデータ型の決定、OCR パイプラインを構築せずにメディアや PDF を取り込む方法について説明します。
誰も警告してくれなかったインポートの問題
動作するプロトタイプは、大規模時に何が起こるかを教えてくれません。本番チームを悩ませる問題は、ほとんどチュートリアルでは現れません。
最も深刻な4つの問題:
- 埋め込みプロバイダーのレート制限:ほとんどのチームは実際のインポートから1時間以内に遭遇し、リトライコードを書き、さらにはリトライコードのためのリトライコードを書きます。
- HTTP 200 の嘘:成功したバッチ応答は、すべてのオブジェクトが書き込まれたことを意味しません。個々のオブジェクトは、緑色のステータスコードの背後で静かに失敗することがあります。
- リトライ時の重複作業:再実行のたびに新しい ID を生成すると、同じドキュメントを再ベクトル化し、そのコストを二重に支払うことになります。
- メディアでのメモリ爆発:100万枚の商品写真を Python リストにロードすると、バッチロジックが実行される前にスクリプトが終了します。
サーバーサイドバッチ処理
サーバーサイドバッチ処理は、Weaviate サーバーが自身の現在のワークロードに基づいて、次に送信すべきデータ量をクライアントに指示するストリーミングインポートモードです。バッチサイズや同時実行レベルを推測する代わりに、サーバーはキュー深度を測定し、永続的な接続を介して背圧を適用します。
これは重要です。なぜなら、適切なバッチサイズは定数ではないからです。プロパティの数、テキストフィールドのサイズ、リアルタイムのベクトル化の有無、ベクトル化エンジンの内部動作、クラスターの他の負荷に依存します。手動での調整は脆弱です。サーバーはすでにこれらすべての情報を持っています。
Python クライアントでのパターンは以下の通りです:
import weaviate
from weaviate.classes.init import Auth
client = weaviate.connect_to_weaviate_cloud(
cluster_url=WCD_URL,
auth_credentials=Auth.api_key(WCD_API_KEY),
)
collection = client.collections.get("Products")
with collection.batch.stream() as batch:
for row in iter_rows():
batch.add_object(properties=row)
if batch.number_errors > 10:
print("エラーが多すぎるため停止します。")
break
if collection.batch.failed_objects:
print(f"{len(collection.batch.failed_objects)} 個のオブジェクトが失敗しました。")これが全体のパターンです。batch_size も concurrent_requests もチューニングもありません。stream() コンテキストマネージャーが永続的な接続を開き、サーバーがレートを設定し、エラーは非同期にストリームされ、フローを中断しません。
エラーハンドリングとリトライ
本番インポートスクリプトで最も一般的なバグは、200 応答を成功の証拠として扱うことです。そうではありません。200 はリクエストがサーバーに到達し、受け入れられたことを意味します。すべてのオブジェクトが書き込まれたことを意味するわけではありません。ベクトル化エラー、スキーマの不一致、上流の埋め込み API からのレート制限応答はすべて、それ以外は正常なバッチ応答内のオブジェクトごとのエラーとして現れます。
Python クライアントは、各バッチで3つのものを公開します:
- batch.failed_objects:エラーが添付されたすべての失敗オブジェクト
- batch.failed_references:すべての失敗したクロスリファレンス
- batch.number_errors:コンテキストマネージャー内の実行カウント
failed_objects をキューとして扱います。ファイルに書き込み、リトライし、同じエラーで再び失敗した場合は、デッドレターの場所に移動して、残りのインポートが1つの壊れた行で停滞しないようにします。
以下は、本番で耐えられるリトライ+チェックポイントのパターンです:
import json
from weaviate.util import generate_uuid5
with collection.batch.stream() as batch:
for row in iter_rows():
batch.add_object(
properties=row,
uuid=generate_uuid5(row["source_id"]),
)
with open("failed.jsonl", "a") as f:
for obj in collection.batch.failed_objects:
f.write(json.dumps({
"properties": obj.object_.properties,
"error": obj.message,
}) + "\n")このパターンを安全に再実行できる理由は2つあります。まず、generate_uuid5 は同じ source_id に対して同じ UUID を生成するため、リトライは重複ではなく上書きになります。次に、エラーはファイルに保存されるため、根本的な問題を修正した後に再インポートできます。サイレントロスも埋め込みの二重請求もありません。
一般的な障害モードとその対処法:
| 症状 | 原因 | 修正 | |------|------|------| | HTTP 200、オブジェクト欠落 | ベクトル化エンジンのレート制限 | failed_objects を確認し、失敗したサブセットをリトライ | | クライアントのメモリ爆発 | ストリーミング前にデータセット全体をロード | ディスクや DB カーソルからストリーム、プリロードしない | | リトライ後の重複オブジェクト | 毎回新しいランダム UUID | 安定したソースキーから generate_uuid5 を使用 | | インポート後の空のベクトル | コレクションにベクトル化モジュール未設定 | 再実行前にコレクション設定を確認 |
MCP サーバーを介したインジェスト
Weaviate には組み込みの MCP サーバー(プレビュー、v1.37.1 で追加)が搭載されており、LLM や IDE アシスタント(Claude Code、Cursor、VS Code など)がモデルコンテキストプロトコルを介してインスタンスを読み書きできます。MCP_SERVER_ENABLED=true を有効にし、MCP_SERVER_WRITE_ACCESS_ENABLED=true で書き込みをオプトインすると、サーバーは会話内でオブジェクトを作成または更新する weaviate-objects-upsert ツールを公開します。REST API と同じポートで動作し、RBAC を尊重するため、追加でデプロイするものはありません。
これは、エージェントが作業中に少数のレコードを書き込む必要がある場合(エージェントメモリの永続化、小さなコレクションの同期、エディターを離れずに少数のオブジェクトを修正する場合)に適したツールです。
ただし、これはインジェストパイプラインではありません。各オブジェクトはモデルによってアセンブルされ、ツールコールとして渡されるため、コンテキストウィンドウとコールごとのレイテンシに制限され、上記のセクションの背圧、ストリーミング、リトライチェックポイントメカニズムはありません。数十オブジェクトを超える場合は、collection.batch.stream()(またはクライアントのバッチ API)を使用し、MCP サーバーはそれが構築された会話型の小規模バッチ書き込みに任せてください。
インポート前のデータ型選択
スキーマの決定は、インポートが実行された後に修正するのに10倍のコストがかかります。最初に正しく行いましょう。
インポート時に最も重要なもの:
- 適切なトークナイゼーションを伴う text。トークナイゼーションは、どの BM25 クエリがどのレコードに一致するかを決定します。英語の散文にはデフォルトで問題ありません。製品コード、URL、またはリテラル文字列が重要なものには、フィールドトークナイゼーションに切り替えます。トークナイゼーションのチュートリアルでトレードオフを説明しています。
- 外部キー用の uuid。高速フィルタリングのためにインデックスされ、挿入時に検証され、クライアントでは文字列ではなく実際の UUID としてレンダリングされます。
- int と number。カウントや ID には int を使用します。価格や比率には number を使用します。混在させると、すべてのクエリでキャストが強制されます。
- 実際にフィルタリングする関係には参照型を使用します。関連フィールドを個別にクエリする場合は、すべてを1つの大きなネストされたオブジェクトにフラット化しないでください。
完全なリストはデータ型リファレンスにあります。
blobHash:埋め込みを保存し、バイトをスキップ
メディア(画像、音声、動画、PDF)をインポートする場合、これが知っておくべきデータ型です。
通常の blob は完全な base64 ペイロードをディスクに保存します。blobHash はそうではありません。インポート時に生のバイトをベクトル化エンジンに送信してモデルが実際のメディアを見られるようにし、SHA-256 ハッシュ以外のすべてを破棄します。ベクトルインデックスは埋め込みを保持します。blob ストレージは32バイトのフィンガープリントを保持します。
{
"properties": [
{
"name": "product_image",
"dataType": ["blobHash"]
}
]
}実際の影響:10 TB の画像コーパスが、数 GB のハッシュとベクトルインデックスに縮小されます。類似性検索は blob の場合とまったく同じように動作します。Weaviate 内に元のバイトを保存する必要がないだけです。それらは本来あるべきオブジェクトストレージに保管してください。
もう1つの優れた特性があります。オブジェクトを更新するとき、新しい base64 がハッシュ化され、保存されたハッシュと比較されます。ハッシュが一致した場合、Weaviate は再ベクトル化を完全にスキップします。これだけで、誰かが誤ってインポートパイプラインを再実行した場合にコストを回収できます。
OCR パイプラインなしの PDF ベクトル化
ほとんどの読者にとって、実際の質問は「OCR パイプラインを書かずに PDF フォルダーを取り込む方法」です。最も短い答えは、Weaviate Cloud トライアルを起動し、Weaviate Embeddings を使用することです。
Weaviate Embeddings には、画像ベースのドキュメント検索向けに設計されたマルチモーダルモデルがあります。ページ画像を渡すと、ベクトルを生成します。OCR ステップは不要です。レイアウト検出も不要です。テキスト抽出も不要です。表、グラフ、スキャンされたフォーム、多言語ドキュメントはすべて同じ方法で処理されます。これはクラウドのみですが、実際のデータセットで PDF 検索を試すための最も簡単な方法であり、数十万ページまでのコレクションではアーキテクチャ上の決定を必要としません。
もう1つの既製オプションは、Google の multi2vec-google(gemini-embedding-2 で3072次元)で、Weaviate Cloud でデフォルトで有効になっています。同じワークフローに従います:ページを画像にレンダリングし、それらを埋め込みます。このモジュールは画像入力を受け取り、生の PDF ファイルではありません。そのため、ラスタライズステップは Weaviate Embeddings と同じです。
大規模にセルフホスティングし、ドキュメントのレイアウトが重要な場合は、マルチベクトル ColPali レシピを検討してください。視覚言語モデルを使用してページごとに複数のベクトルを生成し、チャンキングを完全にスキップします。より多くの可動部品がありますが、視覚的にリッチなドキュメントの検索における最先端です。
3つのパスはすべて同じ場所、モデルプロバイダーリファレンスにあります。
マルチモーダルインジェスト:テキスト、画像、音声、動画
Weaviate におけるマルチモーダルは別の製品ではありません。コレクション上のベクトル化モジュールです。コレクションが使用するモデルを宣言し、メディアプロパティ(理想的には blobHash として)を持つオブジェクトをインポートし、既存の同じクライアントを使用してモダリティ間でクエリを実行します。
プロバイダーごとのカバレッジ:
| プロバイダー | モジュール | テキスト | 画像 | 音声 | 動画 | |------------|----------|---------|------|------|------| | Weaviate Embeddings | native (WCD) | ✓ | ✓ | | | | Google | multi2vec-google | ✓ | ✓ | ✓ | ✓ | | Voyage AI | multi2vec-voyageai | ✓ | ✓ | ✓ | | | Jina AI | multi2vec-jinaai | ✓ | ✓ | | | | Cohere | multi2vec-cohere | ✓ | ✓ | | | | NVIDIA | multi2vec-nvidia | ✓ | ✓ | | | | CLIP(セルフホスト) | multi2vec-clip | ✓ | ✓ | | | | ImageBind(セルフホスト) | multi2vec-bind | ✓ | ✓ | | |
具体的なシナリオ:eコマースカタログの検索を構築しているとします。すべての製品には、名前、説明、3枚の写真、15秒のデモ動画があります。1つのクエリ「コンパクトワイヤレスイヤホン、アクティブノイズキャンセリング付き」で、関連するシグナルがテキスト、写真、動画のいずれにあるかに関係なく、適切な製品を見つけたいとします。
名前付きベクトルで multi2vec-google を使用する1つのコレクションを宣言します:名前と説明用のテキストベクトル、製品画像用の個別の blobHash ベクトル、デモ動画用の別のベクトルです。各 blobHash プロパティには独自の名前付きベクトルが必要です。Weaviate が生のバイトをハッシュに置き換えると、他のフィールドと一緒に再ベクトル化できなくなるため、スキーマがそれらを分離します。そして、1つのマルチターゲットクエリが3つのベクトルすべてを同時にランク付けします:1つのコレクション、1つのクエリ、3つのモダリティ、そしてメディアバイトは Weaviate 内で二重に保存されることはありません。
各プロバイダーの完全なセットアップの詳細は、モデルプロバイダーリファレンスにあります。
開始前のチェックリスト
- データ型を慎重に選択してください。生データを取得する必要がないメディアには blobHash を。リテラル文字列が重要なテキストにはフィールドトークナイゼーションを。
- ベクトル化エンジンはインポートスクリプトではなく、コレクションレベルで選択してください。Weaviate Cloud では Weaviate Embeddings が最も簡単なデフォルトです。
- 決定論的 UUID(安定したソースキーからの generate_uuid5)を使用して、リトライが冪等になるようにします。
- 手動でバッチサイズを調整せずに、サーバーサイドストリーミングバッチ処理を使用します。
- 永続的なエラーに対処するためにデッドレターキューを実装します。
- マルチモーダルコンテンツには blobHash を使用し、独立した名前付きベクトルを持つマルチモーダルモデルを設定します。