MongoDB ベクトル検索でマルチテナンシーを実装すると、アプリケーションの 1 つのインスタンスが複数のテナントにサービスを提供できます。このページでは、 MongoDB ベクトル検索に特に適用される設計の推奨事項について説明します。これらの推奨事項は、Atlas のマルチテナンシーに関する推奨事項とは異なります。
推奨事項
MongoDB ベクトル検索のマルチテナント アーキテクチャを設計する際には、次の推奨事項を参照してください。
重要
このガイダンスでは、単一のVPC内にテナントを配置できることを前提としています。それ以外の場合は、テナントごとに個別のプロジェクトを維持する必要があります。これはMongoDB ベクトル検索には推奨されません。
すべてのテナントに対して 1 つのコレクション
すべてのテナントデータを単一のコレクション、データベース、およびクラスターに保存することをお勧めします。各ドキュメント内に tenant_id フィールドを含めることで、テナントを区別できます。このフィールドは、UUID やテナント名など、テナントの一意の識別子になります。このフィールドは、MongoDB ベクトル検索インデックスやクエリのプレフィルターとして使用できます。
この集中型アプローチには、以下の利点があります。
モデル化と増やすことが容易。
メンテナンス操作を簡素化
tenant_idによるプレフィルターでクエリルーティングを効率化注意
このフィルターは、このフィルターに一致するテナントのデータのみをクエリが返すことを保証します。
テナントごとに 1 つのコレクションまたはテナントごとに 1 つのデータベース
次の理由で、各テナントを個別のコレクションまたはデータベースに保存することは推奨されません。
パフォーマンスへの影響: このアプローチでは、コレクションの数に応じて変更ストリームの負荷が異なる可能性があり、パフォーマンスとモニタリング機能に悪影響を影響可能性があります。
追加の分離なし: Atlas のデータ分離保証はデータベースレベルで適用されます。同じデータベース内で個別のコレクションを使用しても、追加のデータ分離の利点は得られません。別個のデータベースを使用すると運用が複雑になり、ほとんどのユースケースで意味のあるセキュリティ上の利点はありません。
代わりに、すべてのテナントに対して1つのコレクションを使用してください。テナントごとのコレクション モデルから単一コレクション モデルへの移行方法の例については、「テナントごとのコレクション モデルからの移行」をご覧ください。
Considerations
次の戦略を考慮し、推奨アプローチを使用してパフォーマンスに関する潜在的な問題を軽減してください。
テナントのサイズ不一致
それぞれのドキュメント数が比較的少ないテナントが多い場合、またはデータの分散が不均衡であるためにパフォーマンスの問題が発生している場合(一部の大規模テナントと多数の小規模テナント)は、次の戦略を検討してください。
多数の小規模テナントにフラット インデックスを使用
多数のテナント(最大1 00 万)と各テナントのドキュメント数が比較的少ない場合(それぞれのドキュメント数が10 000、flat ドキュメントより少ない場合は、デフォルトのHierarchical Navigable Small Worldsインデックスではなく、 インデックスを使用します。平面インデックスを作成するには、インデックス定義でindexingMethod フィールドをflat に設定します。
各テナントのドキュメント数が少ない場合、特定のテナントにフィルタリングされたクエリは、すでに網羅的に検索されています。このような場合、 Hierarchical Navigable Small Worldsグラフにはメリットはありませんが、メモリとメンテナンスのオーバーヘッドが増加します。平面インデックスを使用すると、この不要なオーバーヘッドが排除されます。
フラット インデックスは、マルチテナント ワークロードに対して次の利点を提供します。
選択的フィルターに最適化: 各テナントのドキュメント数が少ない選択性の高いクエリでは、網羅的なスキャンがすでに最速のパスです。平面インデックスはこれを直接サポートするため、レイテンシと再現率の両方が向上します。
予測可能なパフォーマンス: どのテナントが対象になっているかに関係なく、クエリのレイテンシが緊密な範囲内に収まるため、テナント間の近隣地域影響を排除できます。
リソース効率: 平面インデックスにより、Hierarchical Navigable Small Worlds グラフの構築に関連するメモリとメンテナンスのオーバーヘッドが排除されます。
例えば、次のインデックス定義は、tenant_id フィルターフィールドを使用してフラットインデックスを作成します。MongoDB ベクトル検索で Voyage AI モデルを使用して埋め込みを自動的に生成する場合は autoEmbed タイプを選択します。または、既に埋め込みがある場合は vector タイプを選択します。
注意
平面インデックスは、autoEmbed タイプで自動埋め込みを使用するか、vector タイプで既存の埋め込みを使用するかにかかわらず、スカラーおよびバイナリ量子化と互換性があります。平面インデックスを使用する場合は、常に tenant_id フィールドをプレフィルターとしてクエリに含めてください。
大規模なテナントのビューでの HNSW インデックスの使用
10それぞれのドキュメント数が,000 を超える大規模なテナントの場合は、 MongoDBビューの Hierarchical Navigable Small Worlds インデックスを使用して、大規模なテナントと小規模テナントを区別します。
大規模テナント(上位 1%):
大規模なテナントごとにビューを作成してください。
各ビューに対してHierarchical Navigable Small Worldsインデックスを作成します。
大規模テナントの記録を保持し、クエリ時に確認してそれに応じてクエリをルーティングします。
小規模テナント(残りのテナント):
すべての小規模テナント用にビューを 1 つ作成します。
このビューの単一の平面インデックスを構築します。
tenant_idフィールドをプレフィルターとして使用し、それに応じてクエリをルーティングします。
例
次の例は、 を使用して大規模なテナントと小規模テナントのビューを作成する方法を示しています。mongosh
大規模テナントとそれに対応する tenant_id の値を記録し、これらのテナントごとにビューを作成します。
db.createView( "<viewName>", "<collectionName>", [ { "$match": { "tenant_id": "<largeTenantId>" } } ] )
大規模テナントを除外して、小規模テナント用のビューを作成します。
db.createView( "<viewName>", "<collectionName>", [ { "$match": { "tenant_id": { "$nin": [ "<largeTenantId1>", "<largeTenantId2>", ... ] } } } ] )
ビューを作成した後、各ビューのインデックスを作成してください。次の点を確認してください。
インデックスのコレクション名を指定する際は、元のコレクション名ではなく、ビュー名を使用してください。
大規模なテナント ビューの場合は、Hierarchical Navigable Small Worldsインデックス (デフォルト) を作成します。
小規模テナント ビューの場合は、平面インデックス作成方法でインデックスを作成し、
tenant_idフィールドをプレフィルターとして含めます。
インデックスの作成について詳しくは、「インデックスの作成」ページを参照してください。
多数の大規模テナント
それぞれが多数のドキュメントを持つテナントが多数ある場合は、シャード間でデータを分散するパーティションベースのシステムを使用することを検討してください。
tenant_id フィールドをシャードキーとして使用すると、テナント ID に基づいて特定の範囲にデータを分散させることができます。詳しくは、「範囲シャーディング」を参照してください。
テナントごとのコレクション モデルからの移行
テナントごとのコレクション モデルから単一コレクション モデルに移行するには、各テナントのコレクションを処理し、ドキュメントを新しいコレクションに挿入します。
例、次のスクリプトではNode.jsドライバーを使用して、コレクションごとのテナント モデルから単一のコレクションモデルにデータを移行します。スクリプトには、ソースtenant_id コレクションの名前に基づいて、各ドキュメントの フィールドも含まれています。
import { MongoClient } from 'mongodb'; const uri = "<connectionString>"; const sourceDbName = "<sourceDatabaseName>"; const targetDbName = "<targetDatabaseName>"; const targetCollectionName = "<targetCollectionName>"; async function migrateCollections() { const client = new MongoClient(uri); try { await client.connect(); const sourceDb = client.db(sourceDbName); const targetDb = client.db(targetDbName); const targetCollection = targetDb.collection(targetCollectionName); const collections = await sourceDb.listCollections().toArray(); console.log(`Found ${collections.length} collections.`); const BATCH_SIZE = 1000; // Define a suitable batch size based on your requirements let totalProcessed = 0; for (const collectionInfo of collections) { const collection = sourceDb.collection(collectionInfo.name); let documentsProcessed = 0; let batch = []; const tenantId = collectionInfo.name; // Uses the collection name as the tenant_id const cursor = collection.find({}); for await (const doc of cursor) { doc.tenant_id = tenantId; // Adds a tenant_id field to each document batch.push(doc); if (batch.length >= BATCH_SIZE) { await targetCollection.insertMany(batch); totalProcessed += batch.length; documentsProcessed += batch.length; console.log(`Processed ${documentsProcessed} documents from ${collectionInfo.name}. Total processed: ${totalProcessed}`); batch = []; } } if (batch.length > 0) { await targetCollection.insertMany(batch); totalProcessed += batch.length; documentsProcessed += batch.length; console.log(`Processed ${documentsProcessed} documents from ${collectionInfo.name}. Total processed: ${totalProcessed}`); } } console.log(`Migration completed. Total documents processed: ${totalProcessed}`); } catch (err) { console.error('An error occurred:', err); } finally { await client.close(); } } await migrateCollections().catch(console.error);