Skip to content
Muhammet Şafak
tr
Asked by: Berk Answered:

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


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?

Answer

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. 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

Tags: #architecture#database#scaling
Share:

Comments

Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.

More Questions

All questions

Search the site

Start typing to search posts, projects and pages.

Esc to close Powered by Pagefind