Multi-tenancy Patterns#
Problem statement (interviewer prompt)
A SaaS serves tens of thousands of customers from small free-tier to enterprise. Each tenant needs predictable performance, data isolation, and a billing trail. Compare the architectural patterns for sharing infrastructure and pick one per tier.
Three core models trade isolation for cost:
flowchart LR
subgraph Silo[Silo: stack per tenant]
T1([T1]) --> S1[App1] --> D1[(DB1)]
T2([T2]) --> S2[App2] --> D2[(DB2)]
end
subgraph Pool[Pool: shared stack]
T3([T3]) --> SA[App]
T4([T4]) --> SA
SA --> DA[(DB w/ tenant_id col)]
end
subgraph Bridge[Bridge: shared compute, isolated DB]
T5([T5]) --> SB[App]
T6([T6]) --> SB
SB --> D5[(DB5)]
SB --> D6[(DB6)]
end
classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
class T1,T2,T3,T4,T5,T6 client;
class S1,S2,SA,SB service;
class D1,D2,DA,D5,D6 datastore;
Bigger tenants get more isolation (silo); small tenants share (pool). Bridge is the common middle ground.
Multi-tenancy is the architectural axis "how much of my stack is shared between customers?" The answer drives cost, isolation, security, and operational complexity.
The three models#
Silo (one stack per tenant)#
flowchart TB
T([Tenant]) --> LB[Tenant LB] --> S[App] --> D[(DB)]
S --> C[(Cache)]
S --> Q[(Queue)]
classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
- Isolation: maximum. No noisy neighbours.
- Cost: highest. Fixed overhead per tenant.
- Compliance: easiest. Per-tenant encryption keys, audit logs, data residency.
- Use: regulated enterprise (banking, healthcare), top-tier customers.
Pool (single shared stack)#
flowchart LR
T1([T1]) --> LB
T2([T2]) --> LB
LB --> S[App<br/>tenant_id in every query]
S --> D[(Single DB<br/>row-level security)]
classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
- Isolation: weakest. Bug = cross-tenant leak risk.
- Cost: lowest. Resources amortized.
- Compliance: hardest. Row-level security, signed JWT context.
- Use: free / freemium / SMB tiers, B2C apps.
Bridge (pooled compute, siloed DB)#
flowchart LR
T1([T1]) --> S[Shared app]
T2([T2]) --> S
S -->|routes by tenant_id| D1[(DB T1)]
S -->|routes by tenant_id| D2[(DB T2)]
classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
- Isolation: data-layer isolated, compute shared.
- Cost: middle.
- Compliance: per-tenant encryption keys at rest are possible.
- Use: mid-market, most modern SaaS default.
Data isolation techniques in pool mode#
| Technique | Mechanism |
|---|---|
tenant_id column |
Every query has WHERE tenant_id = ? |
| Row-level security (PostgreSQL RLS) | DB enforces predicate; app can't bypass |
| Schema-per-tenant | Each tenant gets own schema in same DB |
| Database-per-tenant | Each tenant gets own DB instance (= bridge) |
| Hybrid (shards of pools) | Pool inside cells; see cell-based-architecture |
Noisy-neighbour mitigation#
flowchart LR
T([Heavy tenant]) -->|10k QPS| QoS[Tenant-aware rate limiter]
QoS -->|throttled to 100 QPS| App[Shared app]
classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
- Per-tenant rate limits at the edge.
- Bulkheads: per-tenant connection pools, thread pools.
- Resource quotas: max CPU, memory, queue depth per tenant.
- Cell-based isolation: shuffle-shard so noisy tenant blast radius is bounded.
Identity and tenant context#
sequenceDiagram
participant U as User
participant Auth as Identity provider
participant API as API gateway
participant App as Service
U->>Auth: login
Auth-->>U: JWT (sub, tenant_id, role)
U->>API: GET /resource (Bearer JWT)
API->>App: forward + verify JWT
App->>App: extract tenant_id, bind to DB session
Every layer must verify and propagate tenant identity. A missing tenant filter is the most common multi-tenant bug.
Migration paths#
| From | To | Reason |
|---|---|---|
| Pool → Bridge | Compliance ask | Per-tenant key, audit log |
| Bridge → Silo | Enterprise upsell | Dedicated stack pricing |
| Silo → Bridge | Cost rationalisation | Long tail of small enterprises |
| Pool → Pool of cells | Blast-radius limit | One bug doesn't down all |
How multi-tenancy composes#
flowchart TB
MT((Multi-tenancy<br/>silo / pool / bridge))
CB[Cell-based architecture<br/>physical isolation unit]
DBS[Database sharding<br/>pool-mode partitioning]
SEC[Security fundamentals<br/>per-tenant isolation]
RL[Rate limiter<br/>per-tenant throttling]
MT --> CB
MT --> DBS
MT --> SEC
MT --> RL
classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
class MT service;
class CB,DBS,SEC,RL datastore;
Glossary & fundamentals#
| Tag | Concept | What it is | Page |
|---|---|---|---|
HLD |
Cell-based architecture | physical isolation unit | cell-based-architecture |
HLD |
Database sharding | within-pool data partitioning | database-sharding |
HLD |
Security fundamentals | tenant isolation guarantees | security-fundamentals |
HLD |
Rate limiter | per-tenant throttling | rate-limiter |
Quick reference#
Three models#
| Model | Cost | Isolation | Use |
|---|---|---|---|
| Silo | High | Maximum | Regulated, enterprise |
| Pool | Low | Weak (logical only) | Free / SMB |
| Bridge | Middle | DB-isolated, compute pooled | Mid-market |
Data isolation in pool#
tenant_idcolumn on every row- PostgreSQL Row-Level Security
- Schema-per-tenant
- DB-per-tenant
- Cells of pools
Noisy-neighbour controls#
- Per-tenant rate limits (token bucket)
- Per-tenant bulkhead (connection pool)
- Resource quotas (CPU, memory, queue depth)
- Cell or shuffle-shard isolation
Identity propagation#
- JWT with
tenant_idclaim - Every layer verifies
- DB session sets
current_tenant_id - Missing tenant filter = the most common bug
Migration paths#
- Pool → Bridge: per-tenant key, audit
- Bridge → Silo: dedicated for enterprise
- Pool → Pool-of-cells: blast-radius bound
Refs#
- AWS SaaS Lens
- Salesforce multi-tenant architecture
- PostgreSQL RLS docs
FAQ#
What is multi-tenancy?#
Multi-tenancy is when a single software stack serves many isolated customers. The architecture decides how much each tenant shares: hardware, database, schema, or rows.
Silo vs pool vs bridge: which model fits?#
Silo gives one stack per tenant, max isolation and cost. Pool shares everything by tenant_id, cheapest. Bridge shares compute but keeps databases separate per tenant.
What is the noisy neighbour problem?#
A heavy tenant slows or breaks others on shared infra. Mitigations include per-tenant rate limits, resource quotas, isolated worker pools, and tiered cell architectures.
How do you isolate tenant data in a pool?#
Add tenant_id to every row, force it into every query at the ORM layer, and back it up with database row-level security so a bad query cannot leak across tenants.
Should free and enterprise tenants share infra?#
Often no. Most teams pool free and small customers for cost, then silo enterprise customers for isolation, SLA control, and compliance. Bridge sits between for mid-tier.
Related Topics#
- Cell-based Architecture: how to scale silo and bridge models to fleets
- Database Sharding: standard pool-mode data partitioning
- Security Fundamentals: the controls that keep pool tenants from leaking into each other