SD System Design In Depth
0%
lesson system-designtenancyisolation

Multi-tenant design

Module 5.1: Production Reliability, SLOs & Resilience Updated Sep 18, 2026
📄 Cheat Sheet
Building Multi-Tenant SaaS Architectures • Tod Golding & Bill Tarr • GOTO 2024
GOTO Conferences Building Multi-Tenant SaaS Architectures • Tod Golding & Bill Tarr • GOTO 2024 Watch on YouTube ↗

Tod Golding, AWS SaaS Factory lead and author of the O'Reilly book on the subject, explains silo, bridge and pool models, tenant context and noisy-neighbour controls.

0:00 / --:--

Multi-Tenancy Isolation Models

Multi-tenant SaaS architectures balance tenant data isolation against infrastructure cost:

  • Silo Model (Database-per-Tenant): Each tenant gets a dedicated database instance. Provides maximum isolation and security compliance; highest operational and infrastructure cost.
  • Bridge Model (Schema-per-Tenant): Tenants share a database cluster but use separate PostgreSQL schemas.
  • Pool Model (Shared Database & Tables): All tenants share the same tables, with every row containing a tenant_id column. Most cost-effective; requires strict Row-Level Security (RLS) to prevent cross-tenant data leaks.

The Noisy Neighbor Problem

A single large tenant executing heavy batch queries can starve CPU and IOPS, degrading performance for all other tenants. Multi-tenant systems enforce per-tenant rate limits, resource quotas, and tenant-aware database connection pools.

Isolation models compared

ModelIsolationCost per tenantOperations at 10,000 tenantsTypical fit
Silo (database or account per tenant)Strongest; separate blast radius and encryption keysHigh10,000 databases to migrate, monitor and back upEnterprise, regulated or data-residency customers
Bridge (schema per tenant)Medium; shared engine and resourcesMediumSchema migrations times 10,000; catalogue bloat in PostgreSQLMid-size B2B with a moderate number of tenants
Pool (shared tables with tenant_id)Logical only; needs RLS and careful keysLowestOne schema; shard by tenant_idSelf-serve or long-tail customers
Cell-based (pool within cells, many cells)Blast radius limited to one cellLow to mediumTens of cells, each a full stackLarge SaaS such as Slack or Salesforce, and AWS services

Worked example: tiered tenancy for a B2B analytics product

  • 9,800 free and Pro tenants average 2 GB each, about 20 TB in total. They are pooled across 8 Postgres shards (for example with Citus distributing on tenant_id), roughly 2.5 TB per shard.
  • 200 enterprise tenants average 300 GB each. Anyone above 1 TB, or with a residency requirement, gets a silo database in the region they need.
  • Per-tenant limits in the pool: 50 queries per second, a 10 s statement timeout, and at most 5 concurrent heavy queries, enforced at the API gateway and by a tenant-aware connection pool.
  • Moving a growing tenant from pool to silo uses the same expand, copy, dual-write, cut-over process as any database migration, keyed on tenant_id. Build this tooling early; you will need it.

Enforcing isolation in the pool

In PostgreSQL, enable RLS with a policy such as USING (tenant_id = current_setting('app.tenant_id')::uuid), and set app.tenant_id at the start of every transaction from the authenticated token. Keep in mind:

  • Table owners and superusers bypass RLS unless you use FORCE ROW LEVEL SECURITY. Run the application as a role without those privileges.
  • With transaction-mode connection pooling (PgBouncer), use SET LOCAL inside the transaction, so a session setting cannot leak to the next tenant who gets that connection.
  • Put tenant_id first in composite indexes and primary keys, so queries for one tenant stay on a narrow index range and the table can be sharded by tenant later.

Noisy neighbour controls

flowchart LR T["Tenant request"] --> G["Gateway: per-tenant rate and concurrency limit"] G --> Q["Per-tenant fair queue for background jobs"] Q --> DB["Pooled shard with statement timeout"] DB --> M["Per-tenant usage metrics"] M -->|"sustained heavy use"| MV["Move tenant to its own shard or silo"]

Tag every metric and log line with tenant_id, keeping it in logs and exemplars where cardinality is too high for metric labels. Without per-tenant usage data you cannot find the noisy neighbour, charge fairly, or decide who to move.

Check yourself Question 1 of 3
Catalogue bloat model recall
From: Isolation models compared

Which tenancy model runs into repeating every schema migration per tenant and PostgreSQL catalogue bloat as you approach 10,000 tenants?

Key Takeaways

Choose the right isolation model: Silo (dedicated DBs), Bridge (dedicated schemas), or Pool (shared tables with tenant_id).
Use PostgreSQL Row-Level Security (RLS) to enforce tenant isolation at the database engine level.
Prevent noisy neighbors with per-tenant connection pooling and CPU/memory resource quotas.
Was this lesson helpful? Spot an issue or have a suggestion? Let us know on GitHub.
Report an issue