Multi-Tenant SaaS: Shared Database vs. Database-per-Tenant
The most consequential architecture decision in a multi-tenant SaaS product is often made in the first sprint, before the team has enough information to make it well. It gets baked into the data model, ORM configuration, and backup strategy — and by the time the trade-offs become painful, it's deeply structural.
There are three main tenancy models. We'll go through each, when they work, and when they break.
The three models
Shared database, shared schema (row-level isolation)
All tenants in the same tables. A tenant_id foreign key on every table. Application code filters by tenant on every query. This is the simplest to start with and the most common starting point for early-stage SaaS.
Shared database, separate schemas
All tenants on the same database server, but each tenant gets their own schema (or schema prefix). PostgreSQL schema search paths route queries to the correct tenant namespace. Stronger isolation than Model A without the operational overhead of Model C.
Database-per-tenant
Each tenant has a completely separate database instance. Maximum isolation. Maximum operational complexity. Typically only justified for enterprise, regulated, or high-value tenants.
The trade-off matrix
| Factor | Shared (Row) | Shared (Schema) | DB-per-Tenant |
|---|---|---|---|
| Setup complexity | Low | Medium | High |
| Operational overhead | Low | Medium | Very High |
| Tenant isolation risk | Medium (application layer) | Low | None |
| Compliance readiness | Harder | Moderate | Easiest |
| Performance at scale | Can degrade with hot tenants | Better isolation | Best isolation |
| Tenant data exports | Query all tables by tenant_id | Dump schema | Dump database |
| Schema migrations | One migration | Per-schema migration | N migrations |
| Cost | Lowest | Low–Medium | Scales linearly |
When shared schema breaks down
Model A (row-level isolation) has two failure modes that become critical at scale:
- The noisy neighbour problem — one large tenant running heavy queries degrades performance for all other tenants on the same database. At small scale this is invisible. At scale it causes SLA violations for customers who shouldn't be affected.
- Data leakage risk — every query must correctly apply the tenant filter. A single missed WHERE clause exposes data across tenant boundaries. This is a real compliance risk, not theoretical. We've seen it in codebases that looked clean on the surface.
If you're building for a regulated industry (healthcare, finance, legal) or selling to enterprise buyers, you should assume you'll need database-level isolation eventually. Design your provisioning layer for it from the start, even if you don't implement it immediately.
When database-per-tenant is overkill
Model C sounds appealing from a security standpoint but most early-stage SaaS products don't need it and can't operationally sustain it. Consider:
- Running schema migrations means running them N times — once per tenant database. With 500 tenants and a complex migration, this is a multi-hour operation that needs its own tooling.
- Monitoring, backups, and failover must be configured per database. The operational surface area scales linearly with tenant count.
- Cost scales directly with tenant count. At 1,000 tenants on managed database services, the infrastructure bill can be 50–100x a shared model.
Have questions? Our AI can answer instantly
Ask about our services, tech stack, process, or case studies — no forms, no waiting, no sales calls required.
Try the AI ProfileOur default recommendation
For most B2B SaaS products at the 0–500 tenant range: start with shared schema (Model A), but implement it with row-level security at the database layer, not just application code. PostgreSQL's RLS policies enforce tenant isolation even if application code has a bug.
Design your provisioning layer to support schema-per-tenant (Model B) without code changes. This gives you a migration path for compliance-sensitive enterprise customers without starting with the full complexity of Model C.
Specifically:
- Enable PostgreSQL row-level security on all tenant-scoped tables from day one
- Pass tenant context to the database via a session variable (
SET app.tenant_id = '...') rather than in WHERE clauses in application code - Build a tenant provisioning service that abstracts the isolation model — so you can swap from row-level to schema-level for specific tenants without rewiring application code
The migration trap
The most painful scenario we encounter is a product that started with shared schema and now has an enterprise customer requiring database-level isolation — with no provisioning abstraction in place. Retrofitting isolation models into a running multi-tenant product is genuinely hard work. The only way to avoid it is to design the abstraction layer at the start, even if you don't use it immediately.
If you're making this decision now or need help designing your multi-tenancy architecture, let's talk. This is something we've worked through on a lot of products.