# Multi-tenant SaaS database isolation: separate DB, schema, or tenant_id?

> Database-per-tenant does not scale operationally at 10k; keep tenant_id on every table and bury isolation in Postgres Row-Level Security instead.

- Asked: 2026-05-12
- Answered: 2026-05-15
- Asked by: Berk
- Tags: mimari, veritabani, olcekleme
- Source: https://www.muhammetsafak.com.tr/en/just-ask/multi-tenant-saas-database-isolation-for-10k-tenants/
- Language: en-US
- Author: Muhammet Şafak

---
**Question:** I'm building a new SaaS and need to isolate customer (tenant) data. There are three options: a separate DB per customer (database-per-tenant), separate schemas in the same DB (schema-per-tenant), or a `tenant_id` column in a single DB (shared).

Considering security, backup ease, cost and schema migration, how do I choose the most optimal architecture that scales to 10,000 active customers?


Short answer: at 10,000 tenants, the pragmatic answer is a **shared** DB with `tenant_id` everywhere + isolation buried in **Postgres Row-Level Security**.

Let me first say why I rule out the other two, because at this count the decision comes down to operations:

1. **Database-per-tenant doesn't scale at 10k.** A separate DB per tenant means 10,000 migrations, 10,000 connection pools, 10,000 backups. It gives perfect isolation but you'll be crushed under the operational load. This only makes sense for a small number of large customers.
2. **Schema-per-tenant is a middle ground but also strains.** Separate schemas in one DB bridge isolation and management; but at 10k schemas the catalog bloats and migrations still run N times. Fine up to a few hundred tenants, not ten thousand.
3. **Shared DB + `tenant_id` everywhere.** One schema, `tenant_id` on every table. Backups and migrations are simplest here — one DB, one migration. Cost is lowest here too: 10,000 tenants share one instance's resources, so you never pay the per-tenant baseline of a separate database. What you trade away is security, and you buy that back with RLS.
4. **Bury isolation in the DB with RLS.** With Postgres **Row-Level Security**, every query is automatically scoped to `tenant_id`. Let isolation live in the database itself, not in the assumption that "the developer hopefully scoped every query". This is your real defense against leaks.
5. **Add an ORM scope + indexing/partitioning.** Put a tenant scope at the ORM layer (defense-in-depth), and index/partition hot tables by `tenant_id`. And if a "whale" tenant grows, keep the path open from day one to **promote** it to its own DB.

**Bottom line:** I'd build a shared DB + `tenant_id` everywhere + Postgres RLS. Backups and migrations are simplest in the shared model; security is the trade-off you pay, but you buy it back with RLS. Make the ORM scope your second line of defense, partition hot tables by `tenant_id`, and leave the door open to move a growing tenant to its own DB. I explain why the data architecture is built this way on sade.dev.

## Related Reading

- [Is PostgreSQL Enough for Everything?](https://sade.dev/en/journal/is-postgresql-enough-for-everything) — sade.dev
- [Time-series data: TimescaleDB or InfluxDB?](https://www.muhammetsafak.com.tr/en/just-ask/time-series-data-timescaledb-vs-naive-solutions/) — Just Ask
- [When should I apply CQRS, and how do I sync the read and write models?](https://www.muhammetsafak.com.tr/en/just-ask/when-to-apply-cqrs-and-syncing-read-and-write-models/) — Just Ask
- [Should I choose NoSQL or PostgreSQL for flexible product attributes?](https://www.muhammetsafak.com.tr/en/just-ask/nosql-vs-relational-for-flexible-product-attributes/) — Just Ask
