How We Architect Multi-Tenant SaaS
Tenancy models compared — isolation, migrations, noisy neighbors, backups — and the row-level security setup we ship by default.
By Samiul Arafah Dhrubo · August 26, 2026
tenant_id and every query is scoped by it — not in application code that a future bug can bypass. Done right, you get the economics of one codebase with the isolation expectations of separate deployments.The three tenancy models, compared
Every multi-tenant system picks one of three data layouts. The choice determines your migration tooling, backup story, and blast radius when something goes wrong:
| Model | Data isolation | Migration cost | Noisy neighbor risk | Backup complexity |
|---|---|---|---|---|
| Shared schema (+ row-level security) | Strong when RLS policies enforce tenant_id on every table; weak if left to app-code WHERE clauses alone | Lowest — one schema, one migration run | Highest — all tenants share compute and indexes | Per-tenant restore requires filtering rows, not files |
| Schema-per-tenant | Stronger — tenants live in separate PostgreSQL schemas | Scales linearly with tenant count; 500 tenants means 500 migration executions | Medium — shares buffers and connections | Moderate — dump per schema |
| Database-per-tenant | Strongest — physical separation | Highest — connection pools, migrations, and deploys multiply per database | Lowest — dedicated resources per tenant | Simplest per tenant, heaviest in aggregate ops |
Our default architecture
For most B2B SaaS we build shared-schema with PostgreSQL Row-Level Security as the default. The API never trusts a tenant ID from the client body or URL — it resolves the tenant from the verified JWT, sets it on the database session, and RLS policies make cross-tenant rows literally unreadable even if a query forgets its WHERE clause:
┌──────────────┐
Web / Mobile ──▶ │ API Gateway │
Clients └──────┬───────┘
│ JWT verified;
▼ tenant_id resolved from token
┌──────────────────┐
│ Auth + Tenant │
│ Resolver │
└────────┬─────────┘
│ tenant context attached
┌─────────────┼─────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌───────────┐
│ Billing │ │ Core App│ │ Reporting │
│ Service │ │ Services│ │ Service │
└────┬────┘ └────┬────┘ └─────┬─────┘
└─────────────┼──────────────┘
▼
┌───────────────────────────────────────────┐
│ PostgreSQL — every table carries tenant_id│
│ RLS policies scope every query to tenant │
└─────────────────────┬─────────────────────┘
│ read-through / invalidation
▼
┌──────────────┐ ┌──────────────────┐
│ Redis Cache │ │ Background Jobs │
│ keys prefixed│ │ tenant-scoped │
│ tenant:{id}:│ │ queue + workers │
└──────────────┘ └──────────────────┘Two details matter more than the diagram. First, Redis cache keys are prefixed by tenant so a warm cache can never leak one tenant's dashboard into another's session. Second, background jobs carry tenant context through the queue — a nightly invoice runner that loses its tenant_id fails loudly instead of silently processing the wrong customers.
Tenant isolation is not a feature you add later — it's a data-model decision that determines every migration, backup, and query for the life of the product.
We have inherited products where tenancy was an afterthought: a single WHERE org_id = ? filter repeated by hand across hundreds of queries, with no database-level enforcement. One forgotten clause in a reporting endpoint is a full data breach. That is why our first sprint always lands the tenant resolver and RLS policies before any feature work.
Data isolation checklist
- Every table has a non-null
tenant_id; composite indexes lead with it. - RLS policies enabled on every tenant-owned table; the app role cannot bypass them.
- Tenant context resolved from the authenticated token — never from request body, query string, or subdomain alone.
- Cache keys namespaced per tenant; sessions and rate limits tracked per tenant.
- Migrations tested against a seeded multi-tenant fixture in CI before they ship.
- Automated cross-tenant access tests: user A attempts reads/writes against tenant B's IDs on every critical route.
- Backups verified with an actual per-tenant restore drill, not just a successful dump.
- Audit log records tenant_id on every mutation for incident forensics.
When we choose which model
Shared schema + RLS for most products: startups, internal tools, and any SaaS expecting dozens to tens of thousands of small tenants. Cheapest to operate, fastest to iterate on, and isolation is strong when enforced at the database.
Schema-per-tenant for mid-market B2B where a customer demands “our data lives separately” but the tenant count stays in the low hundreds. Workable, but budget for the migration fan-out from day one.
Database-per-tenant for regulated enterprise tenants, on-prem-style compliance requirements, or customers whose usage would degrade others. We usually deliver it as a dedicated deployment of the same codebase rather than a different architecture — so the product team maintains one system, not two.
Related
Building a multi-tenant product? See our SaaS development services and our development process.
Need multi-tenant architecture done right?
Book a free consultation. We'll review your tenancy model and show you exactly where the isolation risks are.
Get Free Consultation