PrepZone Logo
PrepZone

Multi-Tenant SaaS

Shared infrastructure with tenant isolation — schema-per-tenant, row-level security and noisy-neighbour control.

Read these first

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_id column 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

CLIENT
Tenant A / B …
SECURITY
Cognitotenant in JWT
NETWORK
API Gateway
COMPUTE
Shared EKSsame fleet
DATABASE
RDS pooledtenant_id RLS
DATABASE
RDS siloschema per tena…
Cognito JWT tenant claim → shared EKS with row-level or siloed RDS.

Tenancy model comparison

AspectModelTrade-off
Shared schema + RLSLowest cost, simplest opsRisk of query bugs leaking data
Schema per tenantGood isolation, single DB clusterSchema migration × N tenants
DB per tenantStrongest isolation, custom SLAsExpensive; 1000 tenants = 1000 DBs
HybridSmall tenants shared; enterprise dedicatedTwo code paths to maintain
  • Shared schema + RLS

    ModelLowest cost, simplest ops
    Trade-offRisk of query bugs leaking data
  • Schema per tenant

    ModelGood isolation, single DB cluster
    Trade-offSchema migration × N tenants
  • DB per tenant

    ModelStrongest isolation, custom SLAs
    Trade-offExpensive; 1000 tenants = 1000 DBs
  • Hybrid

    ModelSmall tenants shared; enterprise dedicated
    Trade-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

Java
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:

Java
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

Java
{
  "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.

Java
# 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

Java
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

MetricEstimate
Total tenants5,000
Enterprise (dedicated cell)50
SMB (shared schema)4,950
Largest tenant users50,000
Smallest tenant users10
Tenant context overhead per request< 1ms
  • Total tenants

    Estimate5,000
  • Enterprise (dedicated cell)

    Estimate50
  • SMB (shared schema)

    Estimate4,950
  • Largest tenant users

    Estimate50,000
  • Smallest tenant users

    Estimate10
  • Tenant 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.