Gapless

Multi-Tenancy

How to serve many customers or departments from one deployment without leaking data or letting one tenant degrade the others.

A tenant is a group whose data must be kept apart from other groups. For a SaaS product, a tenant is a customer company. For an internal tool, a tenant might be a subsidiary, a region, or a business unit with its own data rules.

Multi-tenancy in an AI system is harder than in a normal web app, because data does not only live in database rows. It also lives in vector indexes, caches, prompts, logs, traces, and evaluation datasets. Every one of those can leak.

Isolation models

There are three standard models. Most real systems mix them.

ModelWhat is sharedIsolation strengthCost per tenantUse it for
PooledEverything. Tenants share databases and indexes, separated by a tenant_id column or filterLogical only. A bug in a filter leaks dataLowestMany small tenants, internal departments
BridgeCompute is shared. Each tenant gets its own database schema, index, or namespaceStrong at the storage layerMediumMid-size customers, most B2B SaaS
SiloNothing. Each tenant gets its own deployment, sometimes its own cloud accountStrongest. Physical separationHighestRegulated customers, contractual requirements, data residency

A practical default: pooled compute and services, with a separate index namespace or collection per tenant (the bridge model for data). This gives strong data separation where leaks would be worst, without running a full stack per customer. Offer silo only to customers who pay for it.

Where the tenant ID must flow

The tenant ID is resolved once, at the gateway, from the user's token. From then on it travels with every call. It must never come from the request body, because the user controls the body.

LayerHow tenant isolation is applied
GatewayResolves tenant_id from the verified token and adds it to the request context
App databaseEvery table has tenant_id. Use row-level security in Postgres so a missing WHERE clause cannot leak data
Vector indexSeparate namespace or collection per tenant, or a mandatory filter on every query
Object storagePrefix per tenant, for example s3://bucket/tenant-a/..., with IAM policies scoped to the prefix
CachesTenant ID is part of every cache key. A cached answer for tenant A must never serve tenant B
Prompt cache and response cacheSame rule. Never share cached completions across tenants
Logs and tracesTagged with tenant ID, so you can export or delete one tenant's data
Eval datasetsBuilt per tenant, or from synthetic data. Never copy one customer's data into a shared test set without consent
Model fine-tunes or adaptersPer tenant, if you use them at all

The cache trap

Response caching is where AI systems most often leak across tenants. A question like "What is our refund policy?" is identical text for two companies, but the correct answer is different. If the cache key is only the question text, tenant B gets tenant A's policy.

Rule: cache keys include tenant ID, and for permission-filtered answers, a hash of the user's permission set. If you cannot compute a reliable permission hash, do not cache answers that used retrieval.

Permissions inside a tenant

Tenant isolation is the outer wall. Inside a tenant, users still see different data. The HR folder is not visible to engineering.

Copy each document's access control list (ACL) into the index at ingestion time, as metadata on every chunk. At query time, filter by the user's identities: their user ID and their group IDs.

chunk.allowed_principals = ["group:hr", "user:alice@corp.com"]
query filter: allowed_principals contains any of ["user:bob@corp.com", "group:eng", "group:all-staff"]

The hard part is keeping ACLs fresh. When someone is removed from a group or a document is unshared, the index must update quickly. See Data sources and connectors for sync strategies.

Noisy neighbors

In shared infrastructure, one tenant's heavy use slows down everyone. In AI systems the scarce resources are:

  • Model throughput. Providers limit tokens per minute per API key. One tenant's batch job can use all of it.
  • Embedding throughput during large ingestions.
  • GPU capacity if you self-host models.
  • Vector index query capacity.

Protect them with per-tenant limits at each layer:

ResourceLimit
Chat requestsRequests per minute per user and per tenant
Model tokensTokens per minute and per day per tenant, enforced at the model gateway
IngestionConcurrent jobs per tenant, separate queue for bulk work
Provider keysSeparate API keys or projects per large tenant, so their quota is their own

Per-tenant configuration

Enterprise tenants will ask for different behavior. Plan for configuration from the start rather than forking code.

Typical per-tenant settings:

  • Allowed models and providers. Some customers ban certain providers or require models hosted in their region.
  • Data retention period for chats and logs.
  • Guardrail strictness and blocked topics.
  • Enabled connectors and tools.
  • Custom system prompt additions, such as company tone or terminology.
  • SSO configuration and allowed email domains.

Store these in a tenant settings table, cache them, and load them at the start of each request. Validate them when they are saved, not when they are used.

Trade-offs

DecisionOption AOption B
Index per tenant vs shared index with filterPer tenant: strong isolation, easy deletion, but many small indexes to manageShared: efficient, but one missing filter leaks data
Row-level security vs application filtersRLS: enforced by the database, safe against bugsApp filters: simpler, but every query must remember them
Shared provider keys vs per-tenant keysShared: simple billing, but one tenant can exhaust quotaPer tenant: isolated quota and cost, more keys to manage

Checklist

  • Tenant ID comes only from the verified token, never from the request body.
  • The vector index separates tenants by namespace or by a filter that cannot be skipped.
  • Row-level security or an equivalent is enabled on the app database.
  • All cache keys include tenant ID.
  • Document ACLs are stored on chunks and applied at query time.
  • Each tenant has limits on model tokens and ingestion jobs.
  • You can export and delete all data for one tenant, including vectors, logs, and traces.

On this page