Multi-Tenancy Isolation Models
Choose silo/pool/bridge per the cost-versus-isolation tradeoff (tier it: pool SMB, silo regulated); resolve tenant context from a trusted source on every request and propagate tenant_id everywhere; enforce at the data layer with Postgres RLS or per-tenant keys/routing; add per-tenant quotas for noisy neighbors; and hunt the non-obvious leaks in caches, search indexes, async jobs, ids, and logs.
Multi-tenancy: share infra cheaply, isolate data absolutely
Multi-tenancy is running many customers (tenants) on one platform. The whole game is making it economically cheap to share infrastructure while guaranteeing tenant A can never see tenant B's data. The core spectrum is silo vs pool vs bridge.
Silo gives each tenant dedicated infrastructure: their own database, sometimes their own cluster or even their own cloud account. Strongest isolation, easiest compliance story ("your data is in your own database"), simplest blast radius, but expensive and operationally heavy (you now patch and migrate N databases). Pool shares everything: one database, one schema, rows from all tenants in the same tables distinguished by a tenant_id column. Cheapest and most scalable, but isolation now depends entirely on your code and query discipline, one missing WHERE tenant_id = ? leaks everyone. Bridge is the middle: shared database, separate schema (or separate table set) per tenant. More isolation than pool, cheaper than silo, but schema-per-tenant stops scaling past a few thousand tenants (migrations across 5,000 schemas hurt).
SILO dedicated DB/cluster per tenant strongest isolation, highest cost
BRIDGE shared DB, schema-per-tenant middle ground
POOL shared schema, tenant_id column cheapest, isolation is code-enforced
The senior move is tiered isolation: pool your thousands of small self-serve SMB customers for cost efficiency, and silo your regulated enterprise customers (health, finance, government, data-residency requirements) into dedicated databases or accounts. One product, two isolation postures, sold as a premium tier.
Silo, bridge, or pool? Match each statement to the tenancy model it describes.
Enforce at the data layer, resolve context early
Wherever tenants share, isolation must be enforced at the data layer, not just the app layer, because app-layer checks are one forgotten WHERE clause from a breach. Postgres Row-Level Security (RLS) is the workhorse: you set current_setting('app.tenant_id') at the start of each request's transaction, and a policy USING (tenant_id = current_setting('app.tenant_id')::uuid) makes the database itself refuse to return other tenants' rows even if application SQL forgets the filter. Combine with per-tenant encryption keys (crypto-isolation) and connection/schema routing where the tenant maps to a database.
Interview nuance: the deciding detail is where and when tenant context is resolved. It must be established on every request, before any business logic runs, from a trusted source: the JWT/session claim or a subdomain (acme.app.com), never from a request body field the client can set. Then tenant_id propagates through the entire call chain (into the DB session var, into cache keys, into async job payloads, into log fields). If tenant context is derived late or from untrusted input, everything downstream is exploitable.
The non-obvious leakage vectors
The part that separates a strong answer is where real multi-tenant breaches happen even when the primary DB path is perfect:
- Caches: a cache key of
user:profile:42with no tenant prefix serves tenant B's cached object to tenant A. Every cache key must includetenant_id. - Search indexes: Elasticsearch/OpenSearch queries need a tenant filter (or per-tenant index); a global search that forgets it returns everyone's documents.
- Background jobs / async workers: a job dequeued without its tenant context runs with ambient or wrong tenant, and RLS silently returns nothing or the wrong rows. Carry
tenant_idin the job payload and re-establish context on pickup. - Shared/sequential IDs: guessable global ids invite IDOR across tenants.
- Log and metrics aggregation: dumping raw payloads into a shared logging pipeline can expose tenant B's PII to tenant A's support view.
Finally, shared infra creates the noisy neighbor problem: one tenant's traffic spike starves everyone. Enforce per-tenant quotas and rate limits so tenants get fair-share isolation of capacity, not just data.
Recap: choose silo/pool/bridge per the cost-versus-isolation tradeoff (tier it: pool SMB, silo regulated); resolve tenant context from a trusted source on every request and propagate tenant_id everywhere; enforce at the data layer with Postgres RLS or per-tenant keys/routing; add per-tenant quotas for noisy neighbors; and hunt the non-obvious leaks in caches, search indexes, async jobs, ids, and logs.
Apply
Your turn
The task this lesson builds to.
Design tenant isolation for a B2B SaaS spanning small self-serve customers and large regulated enterprise customers on one platform.
Think about
- What is the silo vs pool vs bridge tradeoff?
- Where must tenant context be resolved and enforced?
- What are the non-obvious cross-tenant leakage vectors?
Practice
Make it stick
A second problem on the same idea, so it survives past today.
Design the multi-tenant data isolation for a Slack-scale workspace platform: millions of workspaces from 3-person startups to 250,000-seat enterprises, with enterprise customers demanding data residency (EU/US), their own encryption keys (BYOK), and a hard audit guarantee that no other workspace's data is ever co-mingled in a way they can access.
Think about
- How does sharding by workspace double as an isolation strategy?
- Why must tenant/region context be resolved before any I/O?
- How does BYOK turn the isolation guarantee into a cryptographic fact?