Multi-Tenant SaaS: Database Architecture Decisions That Matter

Most multi-tenancy articles describe the options. This one is about when to choose each, based on what we've seen go wrong building RetroBridge, PlanItPoker, and BillBridge โ three different SaaS products with different tenant profiles.
The Three Models
- Shared database, shared schema โ all tenants in the same tables, separated by tenant_id
- Shared database, separate schemas โ one schema per tenant in the same Postgres instance
- Separate database per tenant โ full isolation at the infrastructure level
We use shared schema on most of our SaaS products. Here is when that stops being the right answer.
When Shared Schema Works
RetroBridge and PlanItPoker both run on shared schema. Data volumes per tenant are bounded. Tenant count can scale without DBA intervention. Migrations are simple โ one schema, one migration script. Backups are a single database dump. Operational cost stays low.
The rule: every query must include WHERE tenant_id = $1. You enforce this at the repository layer, not in ad-hoc queries. We write a query builder wrapper that makes it structurally impossible to forget the tenant_id filter. Row-level security in Postgres is an additional enforcement layer on sensitive tables โ belt and suspenders, not a replacement for application-level enforcement.
When Shared Schema Breaks Down
- One tenant has 10x the data of all others โ queries slow for everyone on that table
- A tenant requires regulatory isolation (GDPR, HIPAA) where logical separation is insufficient
- A tenant is on a custom tier that includes a dedicated environment contractually
- Backup and restore for a single tenant is needed regularly and urgently
When any of these apply, we shift that tenant to a separate schema or separate database. A connection registry maps tenant_id to the appropriate database or schema at the application layer โ the rest of the code doesn't change.
Index Strategy
Every table in a shared-schema SaaS system needs a compound index on (tenant_id, created_at DESC). Most analytical queries arrive as 'for this tenant, give me recent records.' Without this index, those queries full-scan across all tenants on every request. With it, they're tight range scans bounded by tenant and time.
The wrong multi-tenancy model for your product isn't a technical problem โ it's a business continuity problem. Fix it before you have a whale tenant.
/ Mohammad Hammoud












