FrontierAI.Engineer
AI System Design & Interviews

Multi-Tenant RAG Isolation

10 min read

Design a RAG system that serves multiple customers from shared infrastructure while guaranteeing that no tenant can access another tenant's data.

Building a RAG-powered product for multiple customers introduces a class of design problems that single-tenant systems never encounter. Every retrieval query must be strictly scoped to the requesting tenant's documents. A design failure here is not a quality bug — it is a data breach, where one customer receives information from another's knowledge base. Correctness under multi-tenancy deserves the same engineering rigor as security.

Isolation strategies

There are three primary approaches to tenant isolation in a vector store, each with different tradeoffs between operational simplicity, query performance, and isolation strength.

  • Separate collections per tenant: the strongest isolation; each tenant's vectors live in a completely separate namespace or index; easy to delete a tenant's data, but operational overhead scales with tenant count
  • Shared collection with metadata filtering: all tenants share one index; every document carries a tenant_id metadata field; every query filters on tenant_id before or alongside the vector search
  • Separate deployments: each enterprise customer gets a dedicated instance; maximum isolation and customization but highest infrastructure cost

Implementing tenant-scoped retrieval

from qdrant_client import QdrantClient
from qdrant_client.models import Filter, FieldCondition, MatchValue

client = QdrantClient(url="http://localhost:6333")

def tenant_search(
    tenant_id: str,
    query_vector: list[float],
    top_k: int = 10,
) -> list:
    """Retrieve documents scoped strictly to the given tenant."""
    tenant_filter = Filter(
        must=[
            FieldCondition(
                key="tenant_id",
                match=MatchValue(value=tenant_id),
            )
        ]
    )
    results = client.search(
        collection_name="shared_docs",
        query_vector=query_vector,
        query_filter=tenant_filter,
        limit=top_k,
    )
    # Defensive check: verify every result belongs to this tenant
    for r in results:
        if r.payload.get("tenant_id") != tenant_id:
            raise RuntimeError(f"Isolation violation: result tenant_id mismatch for tenant {tenant_id}")
    return results

Defense in depth for isolation

Relying solely on a metadata filter is a single point of failure. If a bug drops the filter argument, every tenant's documents become accessible. Add a defensive check after every query that verifies the tenant_id on each returned result matches the requesting tenant. Log and alert on any mismatch immediately — this should never happen in correct code, so any occurrence is a serious incident requiring investigation.

Ingestion isolation

Isolation must be enforced at write time too, not just read time. Every document ingested into the shared collection must have the correct tenant_id stamped at the point of ingest, before it reaches the embedding model. Use server-side validation in the ingestion pipeline to reject any document payload missing a valid tenant_id rather than relying on callers to supply it correctly.

Tenant data deletion

When a tenant cancels or requests data deletion, you must be able to remove all of their vectors reliably. With separate collections, drop the collection. With shared collections, delete by tenant_id filter and verify the count of remaining vectors with that tenant_id is zero. Store a manifest of document IDs per tenant at ingest time so you can audit completeness of deletion independently of the vector store.

warning

Prompt-level isolation is not sufficient. Instructing the model to only discuss documents belonging to the current tenant does not prevent the model from surfacing information from wrongly retrieved cross-tenant chunks. Isolation must be enforced at the retrieval layer before the model ever sees the results.

tip

Add a dedicated integration test that ingests documents for two tenants, queries as tenant A, and verifies zero results belong to tenant B — and vice versa. Run this test on every change to retrieval code. Isolation failures are too serious to rely on code review alone.