Con MongoDB Vector Search, puede implementar la arquitectura multiusuario para que una única instancia de una aplicación dé servicio a varios usuarios. Esta página describe recomendaciones de diseño específicas para MongoDB Vector Search. Estas recomendaciones difieren de las recomendaciones de arquitectura multiusuario para Atlas.
Recomendaciones
Consulta las siguientes recomendaciones al diseñar una arquitectura multiinquilino para MongoDB Vector Search.
Importante
Esta orientación asume que puedes ubicar inquilinos dentro de un solo VPC. De lo contrario, se deben mantener proyectos separados para cada tenant, lo cual no recomendamos para MongoDB Vector Search.
Una colección para todos los inquilinos
Recomendamos almacenar todos los datos del inquilino en una única colección, así como en una única base de datos y clúster. Puede distinguir entre tenants incluyendo un campo de tenant_id dentro de cada documento. Este campo puede ser cualquier identificador único para el tenant, como un UUID o un nombre de tenant. Se puede usar este campo como un prefiltro en los índices y queries de MongoDB Vector Search.
Este enfoque centralizado ofrece los siguientes beneficios:
Fácil de modelar y escalar.
Simplifica las operaciones de mantenimiento.
Enrutamiento eficiente de query mediante prefiltrado por
tenant_id.Nota
Este filtro ofrece garantías de que las query devuelvan datos solo para los inquilinos que coincidan con este filtro.
Una colección por inquilino u una base de datos por inquilino
No recomendamos almacenar cada inquilino en una colección o base de datos diferente por las siguientes razones:
Impacto en el rendimiento: Este enfoque puede producir cargas de flujo de cambios variables según el número de colecciones, lo que podría afectar negativamente el rendimiento y las capacidades de supervisión.
No hay aislamiento adicional: Las garantías de aislamiento de datos en Atlas se aplican a nivel de base de datos. El uso de colecciones separadas dentro de la misma base de datos no proporciona ningún beneficio adicional de aislamiento de datos. El uso de bases de datos separadas introduce una complejidad operativa sin ventajas significativas en materia de seguridad para la mayoría de los casos de uso.
En su lugar, utiliza una sola colección para todos los inquilinos. Para ver un ejemplo de cómo migrar de un modelo de colección por inquilino a un modelo de colección única, consulta Migrar de un modelo de colección por inquilino.
Considerations
Considera las siguientes estrategias para mitigar posibles problemas de rendimiento con el enfoque recomendado.
Discrepancias en el tamaño del inquilino
Si tiene muchos tenants con relativamente pocos documentos cada uno, o si experimenta problemas de rendimiento debido a una distribución desigual de los datos (algunos tenants grandes y muchos pequeños), considere las siguientes estrategias.
Utilice índices planos para muchos arrendatarios pequeños
Si tiene muchos inquilinos (hasta 1 millones) y cada inquilino tiene relativamente pocos documentos (menos de 10,000 documentos cada uno), utilice un índice flat en lugar del índice predeterminado de Mundos Pequeños Navegables Jerárquicos. Para crear un índice plano, establezca el campo indexingMethod en flat en la definición de su índice.
Cuando cada tenant tiene un pequeño número de documentos, las query filtradas a un tenant específico ya se buscan exhaustivamente. En estos casos, el grafo Hierarchical Navigable Small Worlds no proporciona ningún beneficio, pero añade gastos en general de memoria y mantenimiento. Los índice planos eliminan este gasto en general innecesario.
Los índices planos ofrecen los siguientes beneficios para cargas de trabajo con múltiples inquilinos:
Optimizado para filtros selectivos: para consultas muy selectivas en las que cada inquilino tiene un número reducido de documentos, el escaneo exhaustivo ya es la opción más rápida. Los índices planos lo permiten directamente, mejorando tanto la latencia como la exhaustividad.
Desempeño predecible: La latencia de las query se mantiene en un rango estrecho, independientemente de qué inquilino sea el objetivo, eliminando los efectos de vecinos ruidosos entre los inquilinos.
Eficiencia de recursos: Los índices planos eliminan la sobrecarga de memoria y mantenimiento asociada con la creación de Mundos Pequeños Navegables Jerárquicos grafos.
Por ejemplo, las siguientes definiciones de índice crean un índice plano con un campo de filtro tenant_id. Seleccione el tipo de autoEmbed si desea que la búsqueda vectorial de MongoDB genere automáticamente incrustaciones mediante los modelos de Voyage AI, o el tipo de vector si ya tiene incrustaciones:
Nota
Los índices planos son compatibles con la cuantificación escalar y binaria, ya sea que utilice la incrustación automatizada con el tipo autoEmbed o incrustaciones existentes con el tipo vector. Incluya siempre el campo tenant_id como prefiltro en sus query cuando utilice índices planos.
Usar índices HNSW en vistas para inquilinos más grandes
Para inquilinos más grandes que tengan más de 10,000 documentos cada uno, utilice índices de mundos pequeños navegables jerárquicos en las vistas de MongoDB para separar los inquilinos grandes de los inquilinos más pequeños:
Grandes inquilinos (Top 1%):
Crea una vista para cada inquilino grande.
Cree un Mundos Pequeños Navegables Jerárquicos índice para cada vista.
Mantén un registro de grandes inquilinos que verifiques en tiempo de query para dirigir las queries en consecuencia.
Pequeños inquilinos (inquilinos restantes):
Crear una vista única para todos los pequeños inquilinos.
Construye un índice plano único para esta vista.
Utiliza el campo
tenant_idcomo pre-filtro para enrutar las consultas en consecuencia.
Ejemplo
El siguiente ejemplo demuestra cómo crear vistas para inquilinos grandes y pequeños utilizando mongosh:
Mantén un registro de tus grandes inquilinos y sus respectivos valores de tenant_id, y luego crea una vista para cada uno de estos inquilinos:
db.createView( "<viewName>", "<collectionName>", [ { "$match": { "tenant_id": "<largeTenantId>" } } ] )
Crear una vista para los pequeños inquilinos, excluyendo a los grandes inquilinos:
db.createView( "<viewName>", "<collectionName>", [ { "$match": { "tenant_id": { "$nin": [ "<largeTenantId1>", "<largeTenantId2>", ... ] } } } ] )
Después de crear las vistas, crea los índices para cada vista. Verifica lo siguiente:
Al especificar el nombre de la colección para el índice, utiliza el nombre de la vista en lugar del nombre original de la colección.
Para vistas de inquilinos grandes, cree un Pequeños mundos navegables jerárquicos índice (el por defecto).
Para la vista de inquilino pequeño, cree un índice con el método de indexación plana e incluya el campo
tenant_idcomo prefiltro.
Consulta la página Crear índices para obtener instrucciones sobre cómo crear índices.
Muchos usuarios grandes
Si tiene muchos inquilinos, cada uno con una gran cantidad de documentos, considere la posibilidad de utilizar un sistema basado en particiones, distribuyendo los datos entre fragmentos.
Puedes usar el campo tenant_id como llave de partición para distribuir los datos a través de rangos específicos basados en el ID de arrendatario. Para más información, consulta Particionado clasificado por rango.
Migración desde un modelo de colección por inquilino
Para migrar de un modelo de colección por inquilino a un modelo de colección única, procesa cada colección de inquilinos e inserta los documentos en una nueva colección.
Por ejemplo, el siguiente script utiliza el controlador de Node.js para migrar sus datos de un modelo de colección por inquilino a un modelo de colección única. El script también incluye un campo tenant_id para cada documento, basado en el nombre de la colección de origen.
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);