multi-tenancy · git:20260720.1dc980e · 2026-07-20 · sha256 68d46cf62bd6b537

multi-tenancy git:20260720.1dc980eA

Immutable. This exact content is served forever at /api/v1/blob/68d46cf62bd6b537.

---
name: multi-tenancy
description: Isolate tenants in data, code, and capacity, choosing the right isolation model per tier. Use when designing SaaS data models or containing noisy-neighbor and cross-tenant risks.
---

# Multi-tenancy

Two failure classes define the design: a tenant seeing another's data
(catastrophic, reputational) and a tenant consuming another's capacity
(gradual, contractual). Address both explicitly.

## Method

1. **Choose the isolation model per tier, not ideologically.** Shared
   tables with tenant_id: cheapest, scales to millions of tenants.
   Schema-per-tenant: middling isolation, migration fan-out pain beyond
   hundreds. Database-per-tenant: strongest isolation and per-tenant
   restore, real cost; sell it as the enterprise tier. Mixing models
   (shared for small, dedicated for large) is normal.
2. **Enforce tenancy below the application when possible.** Postgres
   row-level security with `tenant_id = current_setting(...)`, set per
   request from the authenticated context, turns a forgotten WHERE
   clause from a breach into a bug. Without RLS: repository-layer
   scoping that raw queries cannot bypass, and no ORM escape hatches in
   handlers.
3. **Carry tenant context, never accept it.** Tenant comes from the
   auth token/session, flows through request context to queries, jobs,
   and logs. Any endpoint taking tenant_id as a parameter from the
   client is an IDOR generator (see authz-design).
4. **Scope everything tenant-shaped.** Object storage prefixes,
   cache keys, search indexes, queue messages (tenant in payload,
   re-asserted at consumption), rate limits, encryption keys where
   contracts demand it. Cross-tenant leaks love the secondary stores
   everyone forgets.
5. **Contain noisy neighbors with per-tenant budgets.** Rate limits and
   concurrency caps per tenant at the edge; fair-share scheduling in job
   queues (round-robin across tenants, not FIFO); statement timeouts so
   one tenant's pathological query cannot hold the pool. Track cost per
   tenant; the top one is always a surprise.
6. **Test isolation adversarially.** A standing test suite that
   authenticates as tenant A and attempts every endpoint against tenant
   B's IDs; run it in CI. One passing cross-tenant read is a sev-1, not
   a ticket.

## Boundaries

- Per-tenant schema or database migration fan-out needs orchestration
  (batching, canary tenants, stragglers report); do not adopt those
  models without building it.
- Tenant deletion and export are legal obligations (GDPR); shared-table
  models must prove complete deletion across all stores, which is a
  feature you build, not a script you improvise.
- In-process caches shared across requests are where tenant bleed hides;
  key them by tenant or scope them per request.