If you are building software that serves multiple customers from one system, one of the first real decisions is how to keep their data apart. Get it right and the system scales quietly. Get it wrong and you are re-platforming under load, usually right when a big customer arrives.
There are three common models. None of them is "best" — each trades cost against isolation and operational effort.
1. Shared schema, tenant ID on every row
All tenants live in the same tables, and every query is scoped by a tenant column. It is the cheapest to run and the easiest to build first.
- Good for: many small-to-mid tenants, where per-tenant cost has to stay low.
- The risk: isolation is only as strong as your discipline. One query that forgets the tenant scope is a cross-tenant data leak. This belongs in a shared layer that is impossible to bypass, not in each developer's memory.
2. Schema-per-tenant in a shared database
Each tenant gets its own set of tables inside one database. Stronger separation, and you can migrate or restore a single tenant. The cost is operational: migrations now run across many schemas, and there is a practical ceiling on how many schemas one database is happy to hold.
- Good for: hundreds of tenants who expect their data to be clearly separated, and occasionally to be exported or deleted on their own.
- The risk: schema drift. Every schema must stay in lockstep, which means your migration tooling has to be genuinely reliable, not a set of manual steps.
3. Database-per-tenant
The strongest isolation: each tenant is a separate database, sometimes separate infrastructure. It is what regulated or large enterprise customers often ask for by name.
- Good for: a smaller number of high-value tenants with strict compliance or residency requirements.
- The risk: cost and operations scale with the tenant count. This is a deliberate choice for specific customers, not a default.
You do not have to pick just one
A pattern that works well in practice: shared schema for the long tail of smaller customers, and the option to promote a large or regulated customer to their own database. The application talks to a tenant through one resolver, so where a tenant physically lives is a configuration detail, not a rewrite.
Design the tenant boundary once, in a layer the rest of the code cannot skip. Then where a tenant lives becomes a decision you can change per customer.
What to decide up front
Three questions save the most pain later: How isolated does each tenant's data actually need to be? How many tenants are you realistically planning for? And can you migrate or delete a single tenant without touching the others? Answer those honestly and the right model tends to pick itself.