Skip to content
awesome-applied-ai
← Design problems

14Retrieval

Multi-tenant isolation in a shared index

500 enterprise tenants share one vector index. Legal now requires provable isolation. The largest tenant has 40 million documents, the smallest has 200. Design it.

The constraint. The tenant size distribution decides the architecture, and it is a power law. Neither "one index per tenant" nor "one index for all" works — the first pays fixed per-index overhead 500 times for tenants with 200 documents, the second gives the 40-million-document tenant no isolation and lets it starve everyone else.

Tiered by size.

TierTenantsPattern
HeadTop ~20 by volumeDedicated collection or namespace, own resource limits
TailThe remaining ~480Shared collection, mandatory partition key, pre-traversal filter
RegulatedWhoever contract requiresDedicated cluster, own region

"Provable" is the operative word. An application-layer convention is not provable. "We always pass tenant_id" fails the moment one code path does not, and that path will exist. Provable means the filter cannot be omitted:

  1. 01Per-tenant credentials scoped at the database to a namespace, so an unfiltered query is rejected by the engine rather than by your code.
  2. 02Or a proxy that injects the predicate and refuses any query arriving without one — fail closed, never fail open.
  3. 03Or row-level security enforced by the database, if the scale permits Postgres.

Then test it adversarially. A test that issues a deliberately unfiltered query and asserts a rejection is the artifact legal actually wants.

Two things that get missed. Noisy neighbours: one tenant's bulk re-index starves the shared collection, so ingestion needs per-tenant rate limits, not just query limits. Embedding leakage: embeddings are invertible enough that inversion attacks recover meaningful source text. A shared index holding tenant A's vectors is holding tenant A's data whatever the filters say — which matters for residency and deletion obligations, not just access control.

Stack. Qdrant multitenancy with a payload-indexed partition key plus tenant-scoped API keys. Turbopuffer where per-namespace cost is near zero, which is exactly the 480-tenant tail. Postgres row-level security with pgvector if the whole thing fits under about 50 million vectors, in which case the database enforces isolation for you and the argument is over.

Where answers fail. Answering with a filter. The question is who enforces the filter, and how you demonstrate that no path bypasses it.