LogixLoops
Start with a shared schema and row-level security unless a contract already requires otherwise. It is the cheapest model to run, the only one where a migration is a single deployment, and it can be selectively split later for the few tenants that genuinely need physical separation. Teams that start at database-per-tenant to be safe usually discover they bought an isolation guarantee nobody asked for and a migration process nobody can run.
Here is the whole decision in one table.
| Shared schema | Schema per tenant | Database per tenant | |
|---|---|---|---|
| Isolation | Logical | Strong logical | Physical |
| Migration | One deploy | N schemas | N databases |
| Cost per tenant | Lowest | Low | Highest |
| Noisy neighbour | Real risk | Real risk | Eliminated |
| Per-tenant restore | Hard | Moderate | Trivial |
| Practical ceiling | Millions | Low thousands | Hundreds |
Every table carries a tenant_id, and the database, not the application,
enforces the filter. Application-level filtering is one forgotten WHERE
clause away from a cross-tenant data leak, and that is the single worst
incident class a SaaS product has.
ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON invoices
USING (tenant_id = current_setting('app.tenant_id')::bigint);
Then set app.tenant_id once, at connection checkout, from the authenticated
session, never from a request parameter.
Two failure modes to design against explicitly. Row-level security is bypassed
by superusers and by the table owner, so the application role must be neither.
And every index needs tenant_id as its leading column, or a query for one
tenant scans across all of them and the largest customer sets the latency for
everyone.
Not on a hunch. There are three defensible triggers:
Design for this from the start by keeping the tenant-to-connection-string mapping in a routing layer, even while every tenant resolves to the same database. Adding that layer later means touching every data-access path in the product; having it from day one makes splitting a tenant a configuration change.
Operations, not architecture. Whichever model you pick, you will eventually need to: restore one tenant to a point in time without touching the others, export everything one tenant owns on request, delete it all on request, and run a schema migration across the fleet without a maintenance window.
Write those four runbooks before you have fifty customers. Every model can satisfy them; they just cost wildly different amounts of engineering depending on how early you asked. Teams that pick a tenancy model on isolation alone, and meet these requirements later, are the ones who end up rebuilding.
Join our engineering newsletter to get deep-dives like this delivered straight to your inbox every month.