multi-tenant-architecture

For the complete MongoDB documentation index, see www.mongodb.com/docs/llms.txt

<!-- Tab options on this page. Append to the .md URL to filter: ?tabs=<id,...> select specific tabs (e.g. ?tabs=nodejs,shell) ?allTabs=true include every tab (no param) default: one tab per tabset

Available tabs: other tabs: auto-embed, vector, large-tenants, small-tenants -->

You can implement multi-tenancy with MongoDB Vector Search so that a single instance of an application serves multiple tenants. This page describes design recommendations that apply specifically to MongoDB Vector Search. These recommendations differ from our [multi-tenancy recommendations for Atlas.](https://www.mongodb.com/docs/atlas/build-multi-tenant-arch.md#std-label-build-multi-tenant-arch

Recommendations

Refer to the following recommendations when designing a multi-tenant architecture for MongoDB Vector Search.

Important:

This guidance assumes that you can colocate tenants within a single VPC (Virtual Private Cloud). Otherwise, you must maintain separate projects for each tenant, which we don't recommend for MongoDB Vector Search.

One Collection for All Tenants

We recommend storing all tenant data in a single collection, and in a single database and cluster. You can distinguish between tenants by including a tenant_id field within each document. This field can be any unique identifier for the tenant, such as a UUID or a tenant name. You can use this field as a [pre-filter](https://www.mongodb.com/docs/vector-search/query/aggregation-stages/vector-search-stage.md#std-label-vectorSearch-agg-pipeline-filter in your MongoDB Vector Search indexes and queries.

This centralized approach offers the following benefits:

  • Straightforward to model and scale.

  • Simplifies maintenance operations.

  • Efficient query routing through pre-filtering by tenant_id.

Note:

This filter guarantees that queries return data only for tenants that match this filter.

One Collection per Tenant or One Database per Tenant

We do not recommend storing each tenant in a separate collection or database for the following reasons:

  • Performance Impact: This approach can lead to varying [change stream](https://www.mongodb.com/docs/manual/changeStreams.md#std-label-changeStreams loads depending on the number of collections, which might negatively impact performance and monitoring capabilities.

  • No Additional Isolation: Data isolation guarantees in Atlas apply at the database level. Using separate collections within the same database provides no additional data isolation benefit. Using separate databases introduces operational complexity without meaningful security advantages for most use cases.

Instead, use [one collection for all tenants](https://www.mongodb.com/docs/vector-search/deployment/multi-tenant-architecture.md#std-label-avs-multi-tenancy-one-collection. For an example of how to migrate from a collection-per-tenant model to a single collection model, see [Migrating from a Collection-Per-Tenant Model.](https://www.mongodb.com/docs/vector-search/deployment/multi-tenant-architecture.md#std-label-multi-tenancy-migrate

Considerations

Consider the following strategies to mitigate potential performance issues with the [recommended approach.](https://www.mongodb.com/docs/vector-search/deployment/multi-tenant-architecture.md#std-label-avs-multi-tenancy-recommendations

Tenant Size Discrepancies

If you have many tenants with relatively few documents each, or if you experience performance issues due to unequal distribution of data (some large tenants and many small tenants), consider the following strategies.

Use Flat Indexes for Many Small Tenants

If you have many tenants (up to 1 million) and each tenant has relatively few documents (fewer than 10,000 documents each), use a flat index instead of the default Hierarchical Navigable Small Worlds index. To create a flat index, set the indexingMethod field to flat in your [index definition.](https://www.mongodb.com/docs/vector-search/indexes/vector-search-type.md#std-label-avs-index-definition

When each tenant has a small number of documents, queries filtered to a specific tenant are already searched exhaustively. In these cases, the Hierarchical Navigable Small Worlds graph provides no benefit but adds memory and maintenance overhead. Flat indexes eliminate this unnecessary overhead.

Flat indexes offer the following benefits for multi-tenant workloads:

  • Optimized for selective filters: For highly selective queries where each tenant has a small number of documents, exhaustive scan is already the fastest path. Flat indexes support this directly, improving both latency and recall.

  • Predictable performance: Query latency stays within a tight band regardless of which tenant is targeted, eliminating noisy-neighbor effects across tenants.

  • Resource efficiency: Flat indexes eliminate the memory and maintenance overhead associated with building Hierarchical Navigable Small Worlds graphs.

For example, the following index definitions create a flat index with a tenant_id filter field. Select the autoEmbed type if you want MongoDB Vector Search to automatically generate embeddings using Voyage AI models, or the vector type if you already have embeddings:

autoEmbed Type

{
  "fields": [
    {
      "type": "autoEmbed",
      "modality": "text",
      "path": "<fieldToIndex>",
      "model": "<embeddingModel>",
      "indexingMethod": "flat"
    },
    {
      "type": "filter",
      "path": "tenant_id"
    }
  ]
}

Note:

Flat indexes are compatible with [scalar and binary quantization](https://www.mongodb.com/docs/vector-search/indexes/vector-search-type.md#std-label-avs-types-vector-search-options, whether you use Automated Embedding with the autoEmbed type or use existing embeddings with the vector type. Always include the tenant_id field as a [pre-filter](https://www.mongodb.com/docs/vector-search/query/aggregation-stages/vector-search-stage.md#std-label-vectorSearch-agg-pipeline-filter in your queries when using flat indexes.

Use HNSW Indexes on Views for Larger Tenants

For larger tenants that have more than 10,000 documents each, use Hierarchical Navigable Small Worlds indexes on [MongoDB Views](https://www.mongodb.com/docs/vector-search/query/view-support.md#std-label-avs-transform-documents-collections to separate large tenants from smaller tenants:

  • Large Tenants (Top 1%):

  • Create a view for each large tenant.

  • Create an Hierarchical Navigable Small Worlds index for each view.

  • Maintain a record of large tenants that you check at query-time to route queries accordingly.

  • Small Tenants (Remaining Tenants):

  • Create a single view for all small tenants.

  • Build a single [flat index](https://www.mongodb.com/docs/vector-search/deployment/multi-tenant-architecture.md#std-label-avs-multi-tenancy-flat-indexes for this view.

  • Use the tenant_id field as a pre-filter to route queries accordingly.

Example

The following example demonstrates how to create views for large and small tenants by using [mongosh:](https://www.mongodb.com/docs/mongodb-shell.md#mongodb-binary-bin.mongosh

Large Tenants

Keep a record of your large tenants and their corresponding tenant_id values, and then create a view for each of these tenants:

db.createView(
  "<viewName>",
  "<collectionName>",
  [
    {
      "$match": {
        "tenant_id": "<largeTenantId>"
      }
    }
  ]
)

After creating the views, create the indexes for each view. Verify the following:

  • When specifying the collection name for the index, use the view name instead of the original collection name.

  • For large tenant views, create an Hierarchical Navigable Small Worlds index (the default).

  • For the small tenant view, create an index with the [flat indexing method](https://www.mongodb.com/docs/vector-search/deployment/multi-tenant-architecture.md#std-label-avs-multi-tenancy-flat-indexes and include the tenant_id field as a pre-filter.

Refer to the [Create Indexes](https://www.mongodb.com/docs/vector-search/indexes/vector-search-type.md#std-label-avs-types-vector-search page for instructions on creating indexes.

Many Large Tenants

If you have many tenants that each have a large number of documents, consider using a partition-based system by distributing data across [shards.](https://www.mongodb.com/docs/manual/core/sharded-cluster-shards.md#std-label-shards-concepts

You can use the tenant_id field as a [shard key](https://www.mongodb.com/docs/manual/core/sharding-shard-key.md#std-label-sharding-shard-key to distribute the data across specific ranges based on the tenant ID. For more information, see [Ranged Sharding.](https://www.mongodb.com/docs/manual/core/ranged-sharding.md#std-label-sharding-ranged

Migrating from a Collection-Per-Tenant Model

To migrate from a collection-per-tenant model to a single collection model, process each tenant collection and insert the documents into a new collection.

Migrate from Collection-Per-Tenant to Single Collection

Sample Script for Migrating Data

For example, the following script uses the Node.js driver to migrate your data from a collection-per-tenant model to a single collection model. The script also includes a tenant_id field for each document based on the source collection's name.

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);