Você pode implementar a multilocação com o MongoDB Vector Search para que uma única instância de um aplicação atenda a vários locatários. Esta página descreve recomendações de design que se aplicam especificamente à MongoDB Vector Search. Estas recomendações são diferentes das nossas recomendações de multilocação para o Atlas.
Recomendações
Consulte as recomendações a seguir ao projetar uma arquitetura multilocatária para o MongoDB Vector Search.
Importante
Esta orientação pressupõe que você pode colocar locatários em uma única VPC. Caso contrário, você deve manter projetos separados para cada locatário, o que não recomendamos para o MongoDB Vector Search.
Uma coleção para todos os locatários
Recomendamos armazenar todos os dados do locatário em uma única coleção, bem como em um único banco de dados e cluster. Você pode distinguir entre inquilinos incluindo um tenant_id campo dentro de cada documento. Este campo pode ser qualquer identificador exclusivo para o locatário, como UUID ou um nome de locatário. Você pode usar esse campo como um pré-filtro em seus índices e consultas do MongoDB Vector Search .
Essa abordagem centralizada oferece os seguintes benefícios:
Fácil de modelar e dimensionar.
Simplifica as operações de manutenção.
Roteamento eficiente de query por meio de pré-filtragem por
tenant_id.Observação
Você tem a garantia de não servir locatários que não correspondam a este filtro.
Uma coleção por locatário ou um banco de dados por locatário
Não recomendamos armazenar cada locatário em uma coleção ou banco de dados separado pelos seguintes motivos:
Impacto no desempenho: essa abordagem pode levar à variação das cargas de fluxo de alterações, dependendo do número de coleções, o que pode impactar negativamente o desempenho e os recursos de monitoramento.
Sem isolamento adicional: As garantias de isolamento de dados no Atlas se aplicam no nível do banco de dados. O uso de coleções separadas dentro do mesmo banco de dados não oferece nenhum benefício adicional de isolamento de dados. O uso de bancos de dados separados introduz complexidade operacional sem vantagens de segurança significativas para a maioria dos casos de uso.
Em vez disso, use uma coleção para todos os locatários. Para um exemplo de como migrar de um modelo de coleção por locatário para um modelo de coleção única, veja Migrando de um modelo de coleção por locatário.
Considerações
Considere as seguintes estratégias para mitigar possíveis problemas de desempenho com a abordagem recomendada.
Discrepâncias de tamanho do locatário
Se você tiver muitos locatários com relativamente poucos vetores cada, ou se tiver problemas de desempenho devido à distribuição desigual de dados (alguns locatários grandes e muitos locatários pequenos), considere as estratégias a seguir.
Use índices simples para muitos inquilinos pequenos
Se você tiver muitos locatários (até 1 milhões) e cada locatário tiver relativamente poucos vetores (menos de 10,000 vetores cada), use um índice flat em vez do padrão Hierarchical Navigable Small Worlds. Para criar um índice plano, configure o campo indexingMethod para flat em sua definição de índice.
Quando cada inquilino tem um pequeno número de vetores, os filtros de queries para um inquilino específico já foram pesquisados exaustivamente. In questi casi, o Hierarchical Navigable Small Worlds grafo não fornece nenhum benefício, mas adiciona memória e sobrecarga de manutenção. Índices planos eliminam essa sobrecarga desnecessária.
Os índices planos oferecem os seguintes benefícios para cargas de trabalho de vários inquilinos:
Otimizado para filtros seletivos: para queries altamente seletivas em que cada locatário tem um pequeno número de vetores, a verificação exaustiva já é o caminho mais rápido. Índices planos suportam isso diretamente, melhorando a latência e a recuperação.
Desempenho previsível: a latência da query permanece dentro de uma faixa estreita, independentemente de qual locatário é direcionado, eliminando os efeitos de vizinhos ruidosos entre os locatários.
Eficiência de recursos: Os índices planos eliminam a sobrecarga de memória e manutenção associada à criação de grafos hierárquicos navegáveis em pequenos mundos.
Exemplo
A seguinte definição de índice cria um índice plano com um campo de filtro tenant_id :
{ "fields": [ { "type": "vector", "path": "<fieldToIndex>", "numDimensions": <numberOfDimensions>, "similarity": "euclidean | cosine | dotProduct", "indexingMethod": "flat" }, { "type": "filter", "path": "tenant_id" } ] }
Observação
Os índices planos são compatíveis com quantização escalar e binária. Sempre inclua o campo tenant_id como pré-filtro em suas queries ao usar índices simples.
Use índices HNSW em visualizações para locatários maiores
Para locatários maiores que têm mais de 10,000 vetores cada, utilize índices Hierarchical Navigable Small Worlds em visualizações MongoDB para separar grandes locatários de locatários menores:
Locatários grandes (1% principal):
Crie uma visualização para cada grande locatário.
Crie um índice hierárquico navegável de pequenos mundos para cada visualização.
Mantenha um registro de grandes locatários que você verifica no momento da query para direcionar as queries de forma adequada.
Pequenos Locatários (Locatários Remanescentes):
Crie uma única visualização para todos os pequenos locatários.
Crie um único índice plano para essa visualização.
Utilize o campo
tenant_idcomo um pré-filtro para direcionar queries de forma adequada.
Exemplo
O exemplo a seguir demonstra como criar visualizações para grandes e pequenos locatários usando mongosh:
Mantenha um registro de seus grandes locatários e seus valores tenant_id correspondentes, e então crie uma visualização para cada um desses locatários:
db.createView( "<viewName>", "<collectionName>", [ { "$match": { "tenant_id": "<largeTenantId>" } } ] )
Crie uma visualização para os pequenos locatários, com filtro para os grandes locatários:
db.createView( "<viewName>", "<collectionName>", [ { "$match": { "tenant_id": { "$nin": [ "<largeTenantId1>", "<largeTenantId2>", ... ] } } } ] )
Após criar as visualizações, crie os índices para cada visualização. Verifique o seguinte:
Ao especificar o nome da coleção para o índice, utilize o nome da visualização em vez do nome original da coleção.
Para grandes visualizações de locatários, crie um índice Hierarchical Navigable Small Worlds (o padrão).
Para a exibição de locatário pequeno, crie um índice com o método de indexação plano e inclua o campo
tenant_idcomo pré-filtro.
Consulte a página Criar índices para obter instruções sobre como criar índices.
Muitos locatários grandes
Se você tiver muitos locatários, cada um com um grande número de vetores, considere usar um sistema baseado em partições distribuindo dados entre fragmentos.
Você pode usar o campo tenant_id como uma chave de fragmento para distribuir os dados em faixas específicas com base no ID do locatário. Para obter mais informações, consulte Fragmentação à distância.
Migrando de um modelo de coleção por locatário
Para migrar de um modelo de coleção por locatário para um modelo de coleção única, processe cada coleção de locatário e insira os documentos em uma nova coleção.
Por exemplo, o script a seguir usa o driver Node.js para migrar seus dados de um modelo de coleção por locatário para um modelo de coleção única. O script também inclui um campo tenant_id para cada documento com base no nome da coleção de origem.
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);