Why this matters
- StreamHub for Business lets companies white-label the platform for their employees — each company is a tenant sharing the same deployment.
- Poor isolation causes data leaks (tenant A sees tenant B's data) or noisy-neighbour problems (one tenant's load degrades everyone).
- The tenancy model you choose at launch is expensive to change later — design for the target customer segment upfront.
Multi-tenancy isolation models
- Shared database, shared schema —
tenant_idcolumn on every table; row-level security enforces boundaries. - Shared database, schema per tenant — separate PostgreSQL schema per tenant; stronger isolation, moderate overhead.
- Database per tenant — dedicated DB instance; maximum isolation, highest cost and ops burden.
- Compute isolation — shared app tier with tenant context, or dedicated pods per large tenant.
- Noisy-neighbour control — per-tenant rate limits, resource quotas, and fair scheduling.
Multi-tenant SaaS architecture
Tenancy model comparison
| Aspect | Model | Trade-off |
|---|---|---|
| Shared schema + RLS | Lowest cost, simplest ops | Risk of query bugs leaking data |
| Schema per tenant | Good isolation, single DB cluster | Schema migration × N tenants |
| DB per tenant | Strongest isolation, custom SLAs | Expensive; 1000 tenants = 1000 DBs |
| Hybrid | Small tenants shared; enterprise dedicated | Two code paths to maintain |
Shared schema + RLS
ModelLowest cost, simplest opsTrade-offRisk of query bugs leaking dataSchema per tenant
ModelGood isolation, single DB clusterTrade-offSchema migration × N tenantsDB per tenant
ModelStrongest isolation, custom SLAsTrade-offExpensive; 1000 tenants = 1000 DBsHybrid
ModelSmall tenants shared; enterprise dedicatedTrade-offTwo code paths to maintain
StreamHub uses shared schema with row-level security for SMB tenants and dedicated cells for enterprise.
Shared schema with row-level security
CREATE TABLE streams (
id UUID PRIMARY KEY,
tenant_id UUID NOT NULL,
title VARCHAR(255),
creator_id UUID,
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE INDEX idx_streams_tenant ON streams(tenant_id);
ALTER TABLE streams ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON streams
USING (tenant_id = current_setting('app.tenant_id')::UUID);
Every request sets the tenant context before querying:
def get_streams(tenant_id: str) -> list:
with db.connection() as conn:
conn.execute("SET app.tenant_id = %s", [tenant_id])
return conn.execute("SELECT * FROM streams ORDER BY created_at DESC")
Tenant context propagation
{
"tenant_id": "tnt_acme_corp",
"tenant_tier": "enterprise",
"features": ["custom_branding", "sso", "analytics_export"],
"rate_limit_rpm": 10000,
"storage_quota_gb": 5000
}
Extract tenant ID from JWT claims or subdomain (acme.streamhub.com → tnt_acme_corp). Propagate via request context to every downstream service.
# Middleware extracts tenant from JWT
@app.middleware("http")
async def tenant_middleware(request, call_next):
token = extract_token(request)
tenant_id = decode_jwt(token)["tenant_id"]
request.state.tenant_id = tenant_id
return await call_next(request)
Noisy-neighbour mitigation
Fairness controls
- Per-tenant rate limits — token bucket keyed by
tenant_id; enterprise tiers get higher limits. - Resource quotas — max streams, storage, API calls per billing period.
- Query timeouts — kill queries exceeding 5s to prevent one tenant's report from blocking the pool.
- Connection pooling — PgBouncer with per-tenant connection limits.
- Dedicated compute — route enterprise tenants to dedicated node pools.
Tenant onboarding and provisioning
POST /v1/admin/tenants
{
"name": "Acme Corp",
"tier": "enterprise",
"admin_email": "admin@acme.com",
"custom_domain": "streams.acme.com",
"sso_config": {
"provider": "okta",
"issuer": "https://acme.okta.com"
}
}
Provisioning pipeline: create tenant record → set up RLS policies → configure SSO → assign custom domain → send welcome email.
Scale estimates
| Metric | Estimate |
|---|---|
| Total tenants | 5,000 |
| Enterprise (dedicated cell) | 50 |
| SMB (shared schema) | 4,950 |
| Largest tenant users | 50,000 |
| Smallest tenant users | 10 |
| Tenant context overhead per request | < 1ms |
Total tenants
Estimate5,000Enterprise (dedicated cell)
Estimate50SMB (shared schema)
Estimate4,950Largest tenant users
Estimate50,000Smallest tenant users
Estimate10Tenant context overhead per request
Estimate< 1ms
Hybrid model: 1% of tenants (enterprise) on dedicated cells; 99% on shared infrastructure.
Quick recall
Everything you need if you only revisit this box.
- Multi-tenant SaaS shares infrastructure; tenant isolation is the core design challenge.
- Shared schema + RLS is cheapest; schema-per-tenant or DB-per-tenant for stronger isolation.
- Propagate tenant_id from JWT/subdomain through every service and database query.
- Per-tenant rate limits and quotas prevent noisy-neighbour degradation.
- Test isolation in CI — missing tenant filter = data breach.
Test yourself
Answer these before moving on — recall is what makes it stick.