AIエージェントの場合: ドキュメントインデックスはhttps://www.mongodb.com/ja-jp/docs/llms.txt で利用可能です。任意のURLパスに .md を追加することで、すべてのページのマークダウン バージョンが利用できます。
Make the MongoDB docs better! We value your opinion. Share your feedback for a chance to win $100.
MongoDB Branding Shape
Click here >
Docs Menu

MongoDB ベクトル検索のマルチテナント アーキテクチャをビルドする

MongoDB ベクトル検索でマルチテナンシーを実装すると、アプリケーションの 1 つのインスタンスが複数のテナントにサービスを提供できます。このページでは、 MongoDB ベクトル検索に特に適用される設計の推奨事項について説明します。これらの推奨事項は、Atlas のマルチテナンシーに関する推奨事項とは異なります。

MongoDB ベクトル検索のマルチテナント アーキテクチャを設計する際には、次の推奨事項を参照してください。

重要

このガイダンスでは、単一のVPC内にテナントを配置できることを前提としています。それ以外の場合は、テナントごとに個別のプロジェクトを維持する必要があります。これはMongoDB ベクトル検索には推奨されません。

すべてのテナント データを 単一のコレクションと、単一のデータベースと クラスター に保存することをお勧めします。各ドキュメント内に tenant_idフィールドを含めることで、テナントを区別できます。このフィールドは、UUID やテナント名など、テナントの一意の識別子にすることができます。このフィールドは、 MongoDB ベクトル検索インデックスとクエリのプレフィルターとして使用できます。

この集中型アプローチには、以下の利点があります。

  • モデル化とスケーリングが容易

  • メンテナンス操作を簡素化

  • tenant_id によるプレフィルターでクエリルーティングを効率化

    注意

    このフィルターに一致しないテナントにはサービスを提供しないことが保証されます。

次の理由で、各テナントを個別のコレクションまたはデータベースに保存することは推奨されません

  • パフォーマンスへの影響: このアプローチでは、コレクションの数に応じて変更ストリームの負荷が異なる可能性があり、パフォーマンスとモニタリング機能に悪影響を影響可能性があります。

  • 追加の分離なし: Atlas のデータ分離保証はデータベースレベルで適用されます。同じデータベース内で個別のコレクションを使用しても、追加のデータ分離の利点は得られません。別個のデータベースを使用すると運用が複雑になり、ほとんどのユースケースで意味のあるセキュリティ上の利点はありません。

代わりに、すべてのテナントに対して 1 つのコレクションを使用します。コレクション単位のテナント モデルから単一コレクションモデルに移行する方法の例については、コレクション単位のテナント モデルからの移行 を参照してください。

推奨されるアプローチで潜在的なパフォーマンスの問題を軽減するには、次の戦略を検討してください。

それぞれのベクトル数が比較的少ないテナントが多数ある場合、またはデータの分散が不均等であるためパフォーマンスの問題が発生している場合(一部の大規模なテナントと多数の小規模テナント)は、次の戦略を検討してください。

多数のテナント(最大 100 万)と各テナントのベクトル数が比較的少ない場合(それぞれのベクトル数が 10、000 より少ない場合は、デフォルトのHierarchical Navigable Small Worldsインデックスではなく、flatインデックスを使用します。平面インデックスを作成するには、インデックス定義で フィールドを に設定します。indexingMethodflat

各テナントのベクトル数が少ない場合、特定のテナントにフィルタリングされたクエリはすでに網羅的に検索されます。このような場合、 Hierarchical Navigable Small Worldsグラフにはメリットはありませんが、メモリとメンテナンスのオーバーヘッドが増加します。平面インデックスを使用すると、この不要なオーバーヘッドが排除されます。

フラット インデックスは、マルチテナント ワークロードに対して次の利点を提供します。

  • 選択性フィルターに最適化: 各テナントのベクトル数が少ない、選択性の高いクエリでは、網羅的なスキャンがすでに最速のパスになっています。平面インデックスはこれを直接サポートするため、レイテンシと再現率の両方が向上します。

  • 予測可能なパフォーマンス: どのテナントが対象になっているかに関係なく、クエリのレイテンシが緊密な範囲内に収まるため、テナント間の近隣地域影響を排除できます。

  • リソース効率: 平面インデックスにより、Hierarchical Navigable Small Worlds グラフの構築に関連するメモリとメンテナンスのオーバーヘッドが排除されます。

次のインデックス定義は、tenant_id フィルターフィールドを持つ平面インデックスを作成します。

{
"fields": [
{
"type": "vector",
"path": "<fieldToIndex>",
"numDimensions": <numberOfDimensions>,
"similarity": "euclidean | cosine | dotProduct",
"indexingMethod": "flat"
},
{
"type": "filter",
"path": "tenant_id"
}
]
}

注意

平面インデックスは、スカラーおよびバイナリ量子化と互換性があります。平面インデックスを使用する場合は、常に tenant_idフィールドをプレフィルターとしてクエリに含めてください。

それぞれのベクトル数が 10、000 よりも大きい大規模なテナントの場合は、Hierarchical Navigable Small Worlds インデックスを MongoDBビュー で使用して、大規模なテナントと小規模テナントを区別します。

  • 大規模テナント(上位 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>", ... ]
}
}
}
]
)

ビューを作成した後、各ビューのインデックスを作成してください。次の点を確認してください。

インデックスの作成について詳しくは、「インデックスの作成」ページを参照してください。

多数のテナントがあり、それぞれに多数のベクトルがある場合は、シャード全体にデータを分散するパーティションベースのシステムの使用を検討してください。

tenant_id フィールドをシャードキーとして使用すると、テナント ID に基づいて特定の範囲にデータを分散させることができます。詳しくは、「範囲シャーディング」を参照してください。

テナントごとのコレクション モデルから単一コレクション モデルに移行するには、各テナントのコレクションを処理し、ドキュメントを新しいコレクションに挿入します。

データ移行用のサンプル スクリプト

たとえば、次のスクリプトではNode.js ドライバーを使用して、テナントごとのコレクション モデルから単一コレクション モデルにデータを移行します。スクリプトには、ソース コレクションの名前に基づいて、各ドキュメントの tenant_id フィールドも含まれています。

migrate-コレクション.js
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);