FullStack Course LogoFullStack Course
Module: System Design
System Design·275·13 MIN READ

275: Multi-Tenancy: Shared Tables, Schemas, Databases, Isolation, Noisy Neighbors, and Tenant Routing

TOPICS COVERED: Multi-Tenancy: Shared Tables, Schemas, Databases, Isolation, Noisy Neighbors, and Tenant Routing

Learning outcomes

By the end of this lesson, you can:

  • explain and apply the shared table model in a realistic implementation;
  • explain and apply schema-per-tenant in a realistic implementation;
  • explain and apply database-per-tenant in a realistic implementation;
  • explain and apply hybrid tiers in a realistic implementation;
  • explain and apply noisy-neighbor controls in a realistic implementation.

Prerequisites and retrieval

This lesson assumes the earlier 01–06 foundation and the preceding lessons in this module. Before you read, retrieve one concrete example from an earlier project where the same concern appeared. Perhaps several customers shared a database, a customer needed a separate deployment, or one workload consumed more resources than the others. The point is not to memorize labels. It is to make a defensible decision for a large-scale distributed service, where requirements, traffic, failure modes, cost, and operational constraints all need to be explicit.

Terminology

  • Shared table model: Tenant ID is part of every tenant-owned key and query, and is commonly included in indexes and unique constraints. This is usually the most cost-efficient model, but a missing tenant predicate or an authorization mistake can expose another tenant's data.
  • Schema-per-tenant: Each tenant has a logical schema boundary. This can improve logical isolation and support customization, but it multiplies migrations, connection and search-path management, and database objects.
  • Database-per-tenant: Each tenant receives a dedicated database. This improves isolation, backup and restore control, regional placement, and enterprise customization, at the cost of higher provisioning and operational overhead.
  • Hybrid tiers: Small tenants share infrastructure while large or regulatory tenants receive dedicated shards or databases. This can align isolation and cost with the tenant's needs, but it requires reliable placement and migration mechanisms.
  • Noisy neighbor: Per-tenant quotas, concurrency limits, partition keys, cache isolation, and workload scheduling stop one tenant from exhausting shared CPU, database, or queue capacity.
  • Tenant lifecycle: Provision, migrate, suspend, export, delete, restore, and relocate tenant data deliberately. Tenant isolation is an operational lifecycle, not merely a WHERE clause added to a query.

Mental model

Treat Multi-Tenancy: Shared Tables, Schemas, Databases, Isolation, Noisy Neighbors, and Tenant Routing as a design problem with observable inputs, outputs, invariants, and failure modes. The architecture is balancing two competing pressures: shared infrastructure reduces cost and operational complexity, while stronger isolation can improve compliance, customization, and blast-radius control. A good implementation makes its assumptions visible, narrows uncertainty at system boundaries, and leaves evidence such as tests, types, constraints, metrics, or diagrams that explains why the design is safe.

A useful interview and production sequence is:

text
requirement -> constraints -> model -> implementation -> failure analysis -> verification

For example, do not jump from “we have many customers” directly to a library call or a database topology. First state what must remain true: a request must resolve to one tenant, tenant data must not cross that boundary, and one tenant's workload must not consume unbounded shared capacity. Then choose the mechanism that enforces those invariants and decide how you will observe failures.

Deep dive

1. Shared table model

In a shared-table design, tenant ID is part of every tenant-owned key or query. It is commonly included in indexes and in unique constraints, so uniqueness is scoped correctly. For example, a username that may repeat across customers should be constrained by (tenant_id, username), not by username alone.

The model is cost-efficient because tenants can share tables, connections, indexes, and database infrastructure. The same sharing is also its main risk: one omitted tenant predicate, incorrectly scoped repository method, or authorization bug can return another tenant's rows. Application scoping and database-enforced row-level security (RLS) can reinforce one another, but neither should be treated as a substitute for testing the boundary.

Decision rule: Use the shared table model deliberately when its cost and operational simplicity fit the requirements and the tenant invariant is easy to prove. If it merely reduces typing while hiding an assumption about authorization, uniqueness, or query scope, prefer a more explicit design or add an enforcement mechanism at the database boundary.

2. Schema-per-tenant

With schema-per-tenant, each tenant's tables live in a separate logical schema within a database. That boundary can make accidental cross-tenant queries less likely and can provide more room for tenant-specific structure. It does not remove operational work. Migrations must reach every schema, connections must use the correct schema or search path, and the number of database objects can grow rapidly.

This model is most useful when logical separation and some customization matter, but a separate database for every tenant would be too expensive or operationally heavy. A failed migration for one schema, a stale connection search path, or a newly provisioned tenant that missed a migration can create failures that do not appear in the common path.

Decision rule: Use schema-per-tenant deliberately when it makes the isolation or customization contract easier to prove and the migration and object-count costs are acceptable. If the schema boundary only hides routing complexity, make the selected schema explicit and verify it at the connection and repository boundaries.

3. Database-per-tenant

In a database-per-tenant design, every tenant has a dedicated database. That separation improves isolation and gives the team more control over backup and restore, regional placement, capacity, and enterprise-specific configuration. It can also limit the blast radius of a database incident.

The trade-off is operational scale. Provisioning, credentials, connection pools, migrations, monitoring, backups, restores, and decommissioning all have to work across many databases. A directory or control-plane record is still needed to locate the tenant, and that directory becomes a critical dependency. Dedicated placement is not automatically a complete security guarantee: the application must still authenticate and authorize requests correctly.

Decision rule: Use database-per-tenant deliberately when isolation, regulatory requirements, regional placement, or enterprise customization justify the provisioning and operational cost. If the requirement is only “avoid forgetting a tenant filter,” first consider a design whose invariants can be enforced and tested more directly.

4. Hybrid tiers

Hybrid tiers let the platform match placement to tenant needs. Small tenants may share tables or schemas on pooled infrastructure, while large or regulated tenants receive dedicated shards or databases. This avoids paying the maximum isolation cost for every customer while still providing a path for stronger guarantees.

A directory service maps each tenant to its current placement. Requests use that mapping to route to the correct storage boundary, and migration changes the mapping only as part of a controlled process. The migration itself must account for copying data, catching up writes, validating counts or checksums, switching traffic, and having a recovery plan. A stale cache of the directory can be as dangerous as a bad query because it can send a request to the wrong location.

Decision rule: Use hybrid tiers deliberately when tenant sizes, compliance requirements, or workload characteristics differ enough to justify multiple placement models. Make tier changes and routing observable; otherwise the flexibility becomes a source of invisible operational risk.

5. Noisy neighbor

Even when data isolation is correct, resource isolation can still fail. A tenant that sends a burst of expensive queries, fills a shared queue, or consumes most worker concurrency can degrade service for everyone else. This is the noisy-neighbor problem.

Controls include per-tenant quotas and rate limits, concurrency limits, tenant-aware partition keys, isolated cache capacity, and workload scheduling. These controls should be bounded and measurable. A limit that exists only in a configuration file but has no rejection metric, queue-depth signal, or alert is difficult to operate. The right limit also depends on the resource: a request quota does not necessarily protect database connections, memory, queue storage, or worker time.

Decision rule: Treat noisy-neighbor protection as deliberate capacity and fairness design. Choose controls based on the shared resource and the failure mode you need to prevent, then define what a limited tenant and an unaffected tenant observe when the limit is reached.

6. Tenant lifecycle

Tenant isolation must survive the entire lifecycle, not just a successful read. Provisioning must create the required placement and permissions. Migration must preserve data and routing. Suspension must prevent the intended operations without making recovery ambiguous. Export, deletion, restore, and relocation need explicit authorization, auditability, and failure handling.

Decision rule: Use tenant lifecycle controls deliberately when provisioning, migration, suspension, export, deletion, restoration, or relocation can change the isolation boundary. Model those operations as state transitions with observable progress and recovery behavior rather than treating them as one-off administrative scripts.

Worked example

Consider a large-scale distributed service whose requirements, traffic, failure modes, cost, and operational constraints must be made explicit. Start by writing the requirement in one sentence, list the input and output contracts, and identify which concept above owns each failure mode. For example, tenant resolution and authorization belong at the request boundary, tenant placement belongs to the directory and routing layer, query scoping belongs to the repository or database policy, and resource fairness belongs to the scheduler or quota layer.

The important move is separation: parsing and validation belong at the boundary; domain rules belong in the domain or service layer; persistence rules belong in the database or repository; presentation rules belong in the client. Mixing these concerns can make a happy-path demo look shorter, but it makes cross-tenant failures and edge cases much harder to reason about.

text
Client
  |
DNS -> CDN / Edge
  |
Load Balancer -> API instances -> Cache
                         |          |
                         +------> Primary datastore
                         |
                         +------> Queue / Stream -> Workers

Walk the example through at least four cases: the normal path; an empty or missing value; a duplicate, retry, or concurrent path where relevant; and a dependency failure. For each case, state which layer detects the problem, whether the operation can be retried safely, and what the caller observes. In a multi-tenant system, also ask whether the tenant context is present and unchanged at every hop. This is the level of explanation expected in a senior code review or technical interview: not just which component is involved, but which invariant it owns and what evidence verifies it.

Production perspective

Production correctness is broader than “the code works on my machine.” Ask how the design behaves during deploys, retries, partial failure, stale clients, concurrent requests, malformed data, schema changes, and high cardinality. Multi-tenancy adds questions about tenant onboarding, placement changes, directory staleness, per-tenant backups, deletion guarantees, and the blast radius of an overloaded or unavailable dependency. Prefer explicit contracts, bounded resource usage, structured errors, and measurable behavior. Optimize only after you can identify the bottleneck or risk with evidence.

When the topic involves an external dependency, define a timeout and cancellation strategy. When it involves persistence, define transaction and consistency expectations. When it involves user-visible state, define loading, empty, error, stale, and success states. When it involves security, assume the client can be modified and all network input is untrusted. In particular, never let a tenant ID supplied by the client silently override the authenticated tenant context; resolve and authorize that context on the server.

Guided lab

Design tenancy for a SaaS ERP with 10k small tenants and 50 enterprise tenants. Compare shared, schema, database, and hybrid models; include routing, RLS or application scoping, quotas, backups, and tenant migration. Your comparison should explain not only where data lives, but how a request finds it, how access is enforced, how a backup is restored, and what happens when a tenant moves between tiers.

Complete the lab with this discipline:

  1. Write the requirement and two non-requirements.
  2. List input, output, and error contracts before implementation.
  3. Implement the smallest correct vertical slice.
  4. Add at least one invalid-input test and one edge-case test.
  5. Instrument or inspect the behavior instead of guessing.
  6. Refactor one hidden assumption into an explicit type, constraint, function, or configuration.
  7. Explain one alternative design and why you did not choose it.
  8. Record a short “what would break at 10× scale?” note.

For the lab, include at least one routing failure, one cross-tenant access attempt, and one noisy-neighbor scenario in your analysis. State which layer rejects each case and what metric or log would let an operator distinguish an authorization failure from a stale placement, a missing tenant record, or an exhausted quota.

Edge cases and failure modes

  • Shared table model: Test an absent tenant context, malformed tenant input, duplicate data within one tenant and across tenants, ordering and concurrency where applicable, and behavior at the smallest and largest credible tenant sizes. Verify that a missing tenant predicate cannot silently broaden the result set.
  • Schema-per-tenant: Test an absent or malformed schema identifier, duplicate data in separate schemas, migration ordering and concurrency where applicable, and behavior at the smallest and largest credible object counts. Verify connections and search paths after pooling and retries.
  • Database-per-tenant: Test an absent or malformed placement record, duplicate data across databases, ordering and concurrency where applicable, and behavior at the smallest and largest credible database counts. Include provisioning, credential, backup, restore, and directory-dependency failures.
  • Hybrid tiers: Test an absent or stale placement, malformed routing input, duplicate or partially copied data during migration, ordering and concurrency where applicable, and behavior at the smallest and largest credible tenant sizes. Verify cutover and rollback behavior.
  • Noisy neighbor: Test an absent or malformed quota, duplicate or repeated requests, ordering and concurrency where applicable, and behavior at the smallest and largest credible workloads. Verify that one tenant's limits protect shared CPU, database, cache, and queue capacity without making unrelated tenants fail.

Common mistakes and debugging

  • Solving the example instead of the requirement: a copied pattern can be syntactically correct but architecturally wrong.
  • Hiding uncertainty with assertions, broad exception handlers, permissive schemas, or “temporary” any values.
  • Testing only the happy path and therefore discovering contracts only after integration.
  • Optimizing before measuring, or selecting a scalable mechanism without a scale requirement.
  • Letting client-side behavior stand in for server-side authorization, validation, or persistence guarantees.
  • Treating a tenant ID as ordinary request data instead of carrying authenticated tenant context through routing, caching, persistence, and background work.

For debugging, reproduce the smallest failing case, inspect the actual value or execution plan, and trace the boundary where the invariant first becomes false. Check the request's authenticated tenant, the directory or placement result, the selected schema or database, the generated query, and any cache or queue key. Then fix the owning layer rather than adding a downstream patch. A downstream filter may hide the symptom while leaving the wrong data source or unsafe query in place.

Interview questions

  1. What problem does the shared table model solve, and what trade-off or failure mode would make you choose a different approach?
  2. What problem does schema-per-tenant solve, and what trade-off or failure mode would make you choose a different approach?
  3. What problem does database-per-tenant solve, and what trade-off or failure mode would make you choose a different approach?
  4. What problem do hybrid tiers solve, and what trade-off or failure mode would make you choose a different approach?
  5. What problem does noisy-neighbor protection solve, and what trade-off or failure mode would make you choose a different approach?

Checkpoint

Without notes, explain Multi-Tenancy: Shared Tables, Schemas, Databases, Isolation, Noisy Neighbors, and Tenant Routing to another developer in five minutes. Your explanation must include one invariant, one edge case, one production failure mode, and one alternative design. Then implement a small example without copying the lesson code. Be able to say where tenant context enters, how it is enforced, and how you would observe a failure.

Mastery checklist

  • I can define the core terms precisely.
  • I can choose a design from requirements instead of from habit.
  • I can implement and test the normal path and edge cases.
  • I can explain the runtime, storage, or complexity cost.
  • I can identify which layer owns validation, errors, and recovery.
  • I can compare at least two reasonable alternatives.
  • I can explain how the design changes at larger scale or stricter reliability.

References

Reader page: /system-design/lesson/275/multi-tenancy-shared-tables-schemas-databases-isolation-noisy-neighbors-and-tenant-routing