The decision that's expensive to change
Tenancy is one of the few architecture choices that's brutal to reverse once you have customers. Get it right early. The question is always the same: how strongly must tenants be isolated, and how much operational complexity can you afford to get that isolation?
Option 1 — Shared schema + Row-Level Security (RLS)
All tenants share the same tables; every row carries a tenant_id, and the database enforces, via RLS policies, that a query only ever sees its own tenant's rows. This is my default for most SaaS.
- Pros: cheapest to operate, one schema to migrate, easy cross-tenant analytics, scales to many tenants.
- Cons: isolation depends on correct policies + session context; a policy bug is a cross-tenant leak; noisy-neighbor effects at the row level.
ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON invoices
USING (tenant_id = current_setting('app.tenant_id')::uuid);With RLS, set the tenant context once per request (a single SET LOCAL) and let the database enforce the rest. Never reconstruct tenant scope ad hoc in application queries — that's where leaks come from.
Option 2 — Schema per tenant
Each tenant gets its own Postgres schema (same database). Stronger logical isolation; queries target tenant_x.invoices.
- Pros: stronger isolation than shared rows; easy per-tenant export; still one database to run.
- Cons: migrations now run N times (one per schema); thousands of schemas strain tooling; cross-tenant queries get awkward.
Option 3 — Database per tenant
Each tenant gets a dedicated database (or instance). The strongest isolation — and the most operations.
- Pros: hard isolation, per-tenant backups/restore, easy data residency, no noisy neighbors.
- Cons: heaviest ops (provisioning, migrations, connection management across many DBs); expensive at scale; cross-tenant analytics is a separate pipeline.
How I choose
Start with shared schema + RLS unless a requirement forces otherwise. Move to schema- or database-per-tenant when you have hard compliance/data-residency needs, a small number of high-value enterprise tenants, or contractual isolation guarantees. Most products never need to leave RLS — and the ones that do usually only move their biggest tenants.
Key takeaways
- Tenancy is expensive to change — decide deliberately before you have customers.
- Shared schema + RLS is the right default: cheapest ops, strong-enough isolation if policies are correct.
- Escalate to schema/DB-per-tenant only for compliance, residency, or a few enterprise tenants.
- Whatever you pick, enforce isolation in the data layer — never the client.