Why multi-tenancy decisions are irreversible

Multi-tenancy is one of the few architectural decisions that is genuinely very expensive to change later. Get it wrong and you will spend 6-9 months migrating mid-scale. This post covers the three main models with honest trade-offs.


Model 1 — Silo (separate database per tenant)

Each tenant gets their own isolated database. The app layer routes connections per tenant.

Strengths:

  • True data isolation — a breach of one tenant does not expose others
  • Per-tenant backup, restore, and compliance scope (critical for HIPAA, GDPR, financial audits)
  • You can scale individual tenants independently
  • Easier to offer "dedicated infrastructure" as a premium tier

Weaknesses:

  • Cost. Each tenant carries fixed DB overhead. At 1,000 tenants you are paying for 1,000 instances
  • Schema migrations across thousands of databases are painful — you need a migration orchestration layer
  • Cross-tenant analytics require data pipelines or a separate warehouse

When to use: Enterprise B2B with compliance requirements, fewer than 500 tenants expected, or when individual tenant data volume is large and unpredictable.


Model 2 — Pool (shared database, tenant ID column)

All tenants share a single database. Every table has a tenant_id column and every query filters by it.

SELECT * FROM orders WHERE tenant_id = :current_tenant AND ...;

Strengths:

  • Low infrastructure cost — one DB, one connection pool
  • Schema migrations run once
  • Cross-tenant aggregations and analytics are trivial
  • Fast to build initially

Weaknesses:

  • A missing WHERE clause is a catastrophic data leak. Use Postgres RLS as a safety net
  • One large tenant can degrade performance for all others
  • Meeting HIPAA BAA, SOC 2, and GDPR right-to-erasure is harder
  • At scale, a single Postgres instance becomes a bottleneck

Critical safety pattern — Postgres RLS:

ALTER TABLE orders ENABLE ROW LEVEL SECURITY;

CREATE POLICY tenant_isolation ON orders
  USING (tenant_id = current_setting('app.current_tenant_id')::uuid);

Set app.current_tenant_id at the start of every transaction. This makes isolation a database guarantee, not an application one.

When to use: Early-stage B2B, SMB-focused products, or high-volume low-margin SaaS.


Model 3 — Bridge (pooled with per-tenant schema)

One database, but each tenant gets their own Postgres schema. The app sets search_path per request.

Strengths:

  • Tenant isolation at schema level without separate DB overhead
  • Schema migrations can be run per-tenant with different schedules
  • Better isolation than pool model, cheaper than silo model

Weaknesses:

  • Postgres has a practical limit of ~1,000-2,000 schemas before performance degrades
  • Connection pooling gets complex with PgBouncer
  • ORM support is inconsistent

When to use: Mid-market B2B with 50-500 tenants, when you want better isolation than pool but cannot afford silo.


How we choose

Five questions at the start of every SaaS engagement:

  1. What is the compliance scope? (HIPAA, SOC 2, GDPR, financial) — silo or bridge
  2. How many tenants at 12 months? 24 months? — more than 2,000 means pool
  3. What is tenant size variance? Large enterprise + many small teams — hybrid
  4. Do you need per-tenant customisation? — bridge or silo
  5. What is the team's infra capability? — pool is simpler to operate

The hybrid approach (what we actually build most often)

Large, compliance-sensitive enterprise tenants get silo. The SMB long-tail gets pool. The pricing tier maps to the architecture tier. This is how every mature multi-tenant SaaS platform works at scale.

Build your pool model first. Design for silo migration. When your first enterprise deal requires it, you will be ready.