Filter concepts by levelShowing all levels.

System Design · Multi-Tenancy

Multi-Tenancy

Three ways to partition tenant data, the access paths a tenant_id filter alone does not cover, and where noisy-neighbor risk actually lives

  • Three modelsshared schema, separate schema, separate database
  • Isolation vs. coststrongest isolation is the most expensive to run
  • Every access pathAPI, query, cache keys, files, queues, jobs
  • Noisy neighborexposure changes with the tenancy model chosen

1.Tenancy models: shared schema, separate schema, separate database

A multi-tenant system has to decide, per resource, how much of the database each tenant shares with every other tenant. Shared database/shared schema puts every tenant's rows in the same tables, distinguished only by a tenant_id column. Shared database/separate schema still uses one database instance but gives each tenant its own schema (its own set of tables inside that instance). Separate database gives each tenant an entirely distinct database, sometimes on its own host. Each step trades lower operational cost for weaker isolation, so the right choice depends on what a single tenant's data leak or one tenant's heavy load is allowed to cost the others.

Key features

  • Shared schema needs a tenant_id (or tenant_id-derived) filter on every single query touching a shared table — the database itself does not know tenant boundaries exist
  • Separate schema isolates the tables but still shares the database engine, connection pool, and host resources — one tenant's runaway query can still starve others on the same instance
  • Separate database is the only model where a full instance restore, a schema migration gone wrong, or a resource-exhaustion incident is naturally contained to one tenant
  • Cost per tenant runs the opposite direction from isolation: shared schema is cheapest to run at scale (one set of tables, one connection pool), separate database is the most expensive (idle capacity reserved per tenant, migrations run N times)
  • Real systems often mix models — most tenants on a shared schema, with an enterprise tier or a regulated tenant promoted to its own schema or database as a contractual or compliance requirement

Three tenancy models compared

Three tenancy models compared
ModelIsolation strengthOperational costCost per tenant at scale
Shared database, shared schemaWeakest — a missing tenant_id filter on one query leaks all tenants' rowsLowest — one schema, one migration, one connection pool to operateLowest — tenants share fixed overhead, marginal cost per tenant is small
Shared database, separate schemaModerate — table-level isolation, but host and engine resources still sharedModerate — one migration run per schema, still one instance to patch and monitorModerate — schema count grows linearly with tenants, but instance count does not
Separate database per tenantStrongest — a compromised or overloaded tenant cannot reach another tenant's data or resourcesHighest — N instances (or N logical databases) to provision, migrate, back up, and monitorHighest — reserved capacity per tenant, migrations and upgrades run N times over
Shared schema vs. separate database — what actually separates two tenants

Shared schema (weak isolation, low cost)

  • One tenants table, one tickets table, one everything table
  • Only a WHERE tenant_id = ? clause keeps rows apart
  • One connection pool and one migration for every tenant at once

Separate database (strong isolation, high cost)

  • Each tenant's tables live in a database only that tenant's connection can reach
  • A missing WHERE clause cannot leak another tenant's rows — there is nothing to leak into
  • N databases to provision, back up, migrate, and monitor instead of one
  • Shared schema (weak isolation, low cost)
    • One tenants table, one tickets table, one everything table
    • Only a WHERE tenant_id = ? clause keeps rows apart
    • One connection pool and one migration for every tenant at once
  • Separate database (strong isolation, high cost)
    • Each tenant's tables live in a database only that tenant's connection can reach
    • A missing WHERE clause cannot leak another tenant's rows — there is nothing to leak into
    • N databases to provision, back up, migrate, and monitor instead of one

Command

text
Shared schema:   one DB, one schema, tenant_id column on every table
Separate schema: one DB, N schemas, one set of tables per schema
Separate DB:     N databases, one set of tables per database

Shared schema is cheapest but leans entirely on application code getting every tenant_id filter right; separate database is the only model where a mistake structurally cannot cross tenant boundaries. Isolation strength and operational cost move in opposite directions, and the choice can be made per tenant, not just once for the whole product.

2.Tenant isolation must be enforced at every access path

A tenant_id filter on the main database query is not tenant isolation — it is one guard on one path. Every other place tenant data passes through needs the same scoping applied independently: the API layer must authorize the request against the right tenant, cache keys must include the tenant id (or a tenant-scoped cache lookup silently returns another tenant's cached data), file storage paths must be namespaced per tenant, and queue messages and background jobs must carry and re-check a tenant id rather than assuming whatever produced the message already validated it. Missing any one path leaks data between tenants even when every other path is correct.

Key features

  • A cache key must be scoped by tenant explicitly (e.g. tenant:acme-corp:user:42) — a cache keyed only by user:42 across all tenants collides the instant two tenants both have a user with id 42
  • File and object storage paths need a tenant-scoped prefix (e.g. /tenants/acme-corp/uploads/...) enforced by the code that constructs the path, since most object stores have no native concept of a tenant boundary to enforce it for you
  • A queue message must carry its own tenant id and the consumer must re-check it — a compromised or buggy producer, or a message replayed from a dead-letter queue, should not be able to make a consumer act on the wrong tenant's behalf by omission
  • Background jobs (scheduled reports, batch exports, cron-triggered cleanup) are especially easy to get wrong because they often run outside any request context — there is no inbound API call to authorize against, so the job itself must carry and enforce tenant scoping explicitly
  • A shared connection pool, shared cache cluster, or shared message broker used across tenants is not itself a violation — the violation is any single key, path, or message inside it that fails to encode which tenant it belongs to
One request, six hops -- every hop needs its own tenant scoping
authorizedrequestresultcachedattachmentfetchedprocessingenqueuedconsumed

API

tenant resolved from auth token

DB query

tenant_id in WHERE clause

Cache

tenant id embedded in key

File storage

tenant id embedded in path

Queue

tenant id in message payload

Background job

tenant id re-checked, not assumed

  • API — tenant resolved from auth token
    • leads to DB query (authorized request)
  • DB query — tenant_id in WHERE clause
    • leads to Cache (result cached)
  • Cache — tenant id embedded in key
    • leads to File storage (attachment fetched)
  • File storage — tenant id embedded in path
    • leads to Queue (processing enqueued)
  • Queue — tenant id in message payload
    • leads to Background job (consumed)
  • Background job — tenant id re-checked, not assumed

Command

text
Access paths a multi-tenant request can touch, and what
scopes each one:

  API request      -> tenant resolved from auth token/subdomain
  Database query    -> WHERE tenant_id = ? (or schema/DB boundary)
  Cache key         -> tenant id embedded in the key itself
  File/object path  -> tenant id embedded in the storage path
  Queue message      -> tenant id in the message payload/headers
  Background job    -> tenant id passed explicitly, re-checked
                        on every read the job performs

A tenant_id filter on the main query is one access path out of at least six: API, query, cache keys, files, queues, and background jobs. Cache keys are the most common silent miss — a key built from a user id alone collides across tenants and serves the wrong tenant's data with no error anywhere.

3.Noisy-neighbor prevention and quotas across the three tenancy models

Noisy-neighbor risk and the fixes for it (weighted fairness, per-tenant queues, resource isolation) are covered in full elsewhere — what matters here is that the tenancy model you picked already answers part of the question. Shared schema means every tenant shares the same connection pool, so noisy-neighbor risk is at its highest and per-tenant quotas are not optional. Separate schema still shares the database engine and host, so quotas remain necessary but the blast radius of one tenant is smaller. Separate database removes the shared-resource risk at the database layer entirely — the model itself is a form of resource isolation, though shared infrastructure elsewhere (a shared API gateway, a shared load balancer) can still need its own quotas.

Key features

  • Shared schema puts noisy-neighbor risk at its highest: one connection pool, one set of tables, one query planner's resources shared by every tenant, so a per-tenant cap on the shared pool is close to mandatory, not optional
  • Separate schema reduces contention on locks and cached query plans (each schema's tables are distinct) but the underlying database engine, CPU, memory, and I/O are still one shared pool across all schemas on that instance
  • Separate database removes database-layer noisy-neighbor risk by construction — there is no shared connection pool or shared table left to monopolize — but this is a side effect of the isolation model, not a substitute for capacity planning per tenant instance
  • Even with separate databases, tenants can still share infrastructure above the database — an API gateway, a load balancer, a shared authentication service — and those shared layers need their own per-tenant quotas independent of which database model was chosen
  • The general noisy-neighbor failure signature, hard quotas vs. burst limits, and the three fairness mechanisms (weighted fairness, per-tenant queues, resource isolation) are covered in depth in Quotas and Fairness — this concept is about which tenancy model changes how urgently those mechanisms are needed, not the mechanisms themselves

How each tenancy model changes noisy-neighbor exposure at the database layer

How each tenancy model changes noisy-neighbor exposure at the database layer
Tenancy modelShared resource at riskNoisy-neighbor exposurePer-tenant quota needed?
Shared schemaConnection pool, tables, query planner, locksHighest — one heavy tenant's query directly contends with every other tenant'sYes — close to mandatory from day one
Separate schemaDatabase engine, host CPU/memory/I/OModerate — table and lock contention removed, resource contention remainsYes — still needed, smaller blast radius per incident
Separate databaseNothing at the database layer; shared infra above it (gateway, load balancer)Lowest at the database layer — isolation is structural, not enforced by policyOnly above the database layer (gateway/API-level quotas), not for the DB itself
Where a per-tenant quota is load-bearing, by tenancy model

Shared schema — quota is the only defense

  • One connection pool serves every tenant's queries
  • No structural barrier stops tenant A from consuming most of it
  • A per-tenant cap on the shared pool is effectively mandatory

Separate database — isolation is structural

  • Each tenant has its own connection pool by construction
  • Tenant A exhausting its own pool cannot touch tenant B's
  • Remaining risk moves up the stack to shared gateway/LB layers
  • Shared schema — quota is the only defense
    • One connection pool serves every tenant's queries
    • No structural barrier stops tenant A from consuming most of it
    • A per-tenant cap on the shared pool is effectively mandatory
  • Separate database — isolation is structural
    • Each tenant has its own connection pool by construction
    • Tenant A exhausting its own pool cannot touch tenant B's
    • Remaining risk moves up the stack to shared gateway/LB layers

Command

text
Noisy-neighbor exposure by tenancy model (database layer):

  Shared schema     [############################] highest
  Separate schema   [###############             ] moderate
  Separate database [                             ] lowest

  (Full mechanics -- fairness algorithms, hard quotas vs
   burst limits -- covered in Quotas and Fairness, not here.)

Shared schema makes per-tenant quotas close to mandatory (one shared connection pool); separate database removes noisy-neighbor risk at the database layer but not at whatever shared infrastructure still sits in front of it (gateway, load balancer). The failure signature, hard-quota-vs-burst-limit distinction, and the three fairness mechanisms themselves are covered in Quotas and Fairness — this is about which tenancy model changes where those mechanisms are actually needed.

Remember

  • Shared schema is cheapest but leans entirely on application code getting every tenant_id filter right; separate database is the only model where a mistake structurally cannot cross tenant boundaries.
  • A tenant_id filter on the main query is one access path out of at least six — cache keys are the most common silent miss, leaking one tenant's cached data to another with no error anywhere.
  • Shared schema makes per-tenant quotas close to mandatory; separate database removes noisy-neighbor risk at the database layer but not at whatever shared gateway or load balancer still sits in front of it.
  • The tenancy model can be chosen per tenant, not just once for the whole product — a free-tier tenant and a regulated enterprise tenant have genuinely different isolation requirements.

Pro tip

  • Scope every cache key with the tenant id explicitly (tenant:acme-corp:user:42, not user:42) — user ids are often assigned per tenant and collide across tenants at scale.
  • When a noisy-neighbor incident is reported, check which layer the tenancy model already isolates before investigating — the same symptom has a different likely root cause depending on the model in place.

Next up

  • Compliance and data residency
  • Rate limiting revisited
  • Cost-aware architecture

Sources

  • AWS SaaS Tenant Isolation Strategies whitepaper — docs.aws.amazon.com/whitepapers
  • Checked 2026-08-25

Spotted a mistake on this page? Tell us.