Guides · Aug 24, 2026

The Multi-Tenancy Decision You Can't Undo Later

Shared tables versus a database per customer is expensive to reverse. The decision you cannot undo is skipping a tenant-routing seam on day one.

For the first few dozen customers, every tenancy model looks fine. One database with a tenant_id column. One schema per customer. One database per customer. The product ships. Then an enterprise deal asks where their data lives, a noisy neighbour takes the shared cluster down, or a restore is needed for a single account, and you discover which decision you actually made.

You can migrate between models. Teams do it. It is one of the more expensive reverse-gears in SaaS, measured in months when the application assumed a single shared database in a hundred places. The decision you cannot cheaply undo is not "we started shared." It is "we never had a seam."

Three models, three operating costs

Shared schema puts every tenant in the same tables, separated by a tenant key. One connection pool, one migration, one backup. Cross-tenant analytics are a query. Isolation is a discipline: every query, every job, every export. Get it wrong and you have a data leak, not a bug ticket.

Schema-per-tenant looks like a middle path. Separate namespaces, same cluster. Migrations now run N times. Postgres catalogues bloat as tenant count grows; a few hundred schemas is a common point where planning and migrations start to hurt. And search_path is advisory. It is not a substitute for a security policy.

Database-per-tenant is physical isolation: separate credentials, backups, restores, and a blast radius of one. It is what many enterprise security questionnaires are actually asking for, and it is how you put one customer in a specific region. It is also how backup, migration, and connection-pool work scale with headcount of customers, not with data volume. Teams quote several times the operational cost of a shared schema once you are honest about that overhead.

What actually cannot be undone

Hard-wiring "there is one database" into the app. Integer primary keys that collide the moment you merge two tenants. File paths without a tenant prefix. A cache whose keys are just user id. A cron job that scans the whole table. Those choices make a later silo a data-and-code rewrite.

If, instead, one module answers "where does this tenant live?" and every store goes through it, moving one customer onto a dedicated database is a routing and migration job. Industry write-ups of this path put a prepared move in weeks; an unprepared one in a quarter or more. The difference is the seam, not the original model.

Start shared, unless you already know you cannot

If you have many small tenants, a small team, and no regulator demanding physical separation, shared schema is the default that lets you ship. Put a tenant_id on every row that belongs to a customer. Prefer UUIDs so rows can move. Enforce isolation in the database, not only in application code — Postgres row-level security tied to the request's tenant is the backstop for the query someone will forget.

Start siloed when the first buyers are few, large, and already asking about residency, dedicated hardware, or restore of their world without yours. Do not pick siloed because it sounds more serious. You can add isolation for the tenants who pay for it. You cannot cheaply subtract a missing routing layer.

The questions that pick the model

Who is the customer in year two — hundreds of similar accounts, or twenty enterprises with different contracts? Must a restore, a delete, or a region be per tenant? Are noisy neighbours a support problem you will actually have? Do you need cross-tenant benchmarks or a marketplace? How many databases can this team migrate on a Tuesday?

Write the answers down before you pick a framework. Tenancy is not a library you add after auth. It is how billing, onboarding, support, and deletion work. The first payable release should already know which model it is, and should already have the seam that lets you change your mind for one tenant without changing it for all of them.

This guide / FAQs

Questions this post answers.

The short versions, in the form people type into search and chat tools.

No. It is the strongest isolation model, and the most expensive to operate. Most products should start with a shared schema and a routing seam, then silo only the customers who need it.

Yes, if one layer already decides where a tenant lives. If tenant checks are hand-written in every query, the migration is a rewrite under contract pressure, not a routing change.

Not by itself. Postgres search_path is not a hard wall. A missed WHERE tenant_id clause and a mis-set search_path leak the same way. Use row-level security as a backstop either way.