Multi-tenant SaaS: design it now or retrofit it later
Three data isolation models for multi-tenant SaaS — database per tenant, shared database with tenant_id, schema per tenant. What a retrofit actually costs and when you do not need multi-tenancy at all.
Short answer: design for multi-tenancy when a second customer is plausible within about a year. Retrofitting a working single-customer system is not “add a tenant_id column” — it is a review of every database query, every background job and every export. But if a second customer is not on the horizon, it is premature complexity that you pay for in speed.
Here is what the decision is made of.
What a tenant means in practice
A tenant is an isolated data boundary for one customer inside a shared system. Users of different tenants work in the same application, cannot see each other’s data and, ideally, do not know the neighbours exist.
That sounds simple until you get to the details. Settings, branding, pricing plans, reference data, role permissions, integrations with external systems, background jobs, reports, exports — every one of them has to know which tenant it belongs to. A place you miss is a data leak between customers, which is the worst class of bug in SaaS.
Three isolation models
A database per customer. Maximum isolation: physically separate databases, cross-tenant leakage practically ruled out. Easy to export one customer’s data, easy to restore just them from a backup. The price is operations: migrations must run against every database, monitoring multiplies, and connections to a hundred databases start hitting limits. Reasonable at dozens of customers with strict isolation requirements, not at thousands.
A schema per customer in a shared database. The compromise: one database, each customer with their own schema holding the same set of tables. Isolation is weaker than physical separation but noticeably better than shared tables. Migrations run across all schemas, slower than one but far simpler than a hundred databases. Works well at dozens and hundreds of customers.
Shared tables with a tenant_id column. All data in the same tables, separated by a column value. Cheapest to operate and scales best. But safety now rests on discipline: any query missing the tenant filter becomes a leak. Database-level protection is mandatory here — in PostgreSQL that means row-level security, not just filters in application code.
Isolation and running cost move in opposite directions. The choice is a trade-off, not a search for the best option.
Isolation and running cost move in opposite directions. The choice is a trade-off, not a search for the best option.
What actually breaks in a retrofit
Experience says “add tenant_id” is the smallest part of the work. It is everything else that breaks.
Background jobs. Queue workers, cron jobs and mailers were written on the assumption that they process everything. Once tenants exist, every job has to know whose context it runs in. A job you miss either fails or, worse, processes somebody else’s data.
Caching. Cache keys without a tenant are a direct path to customer A seeing customer B’s data. That bug does not reproduce reliably and surfaces at the worst possible moment.
Files. Uploaded documents, exports and reports sit in storage under paths that also need separating. And permissions have to be checked on delivery rather than relying on an unguessable URL.
Unique constraints. A user’s email used to be globally unique — now it is unique within a tenant. Product codes, contract numbers, SKUs, the same. Every one of those constraints has to be rebuilt, and that is a migration with risk attached.
Integrations and webhooks. An external system sends an event and you have to work out whose it is. What used to be obvious now needs a routing layer.
Taken together, retrofitting a working system usually costs more than designing for it from the start. But not by an order of magnitude — and that matters, because it turns the question into an economic one rather than a dogmatic one.
When you do not need multi-tenancy
An honest list of reasons to skip it:
- there is one customer and no second one planned — an internal company system, for example;
- each customer needs substantially different logic rather than different settings: that is not one product, it is several similar ones;
- data requirements are such that the customer will not accept shared infrastructure at all — on-premise deployment, say;
- the project is still testing a hypothesis and the odds of the product surviving in its current shape are low.
In that last case the sensible compromise is not to build full multi-tenancy, but not to sabotage yourself either: avoid global uniqueness from day one, do not hardcode settings, and keep each customer’s data separable. That costs almost nothing at the start and makes the retrofit considerably cheaper if it comes.
What to put in if you decide to do it now
The minimum that pays for itself:
A tenant identifier in the request context, set once at the entry point rather than passed by hand into every function. Protection at the database level, not only in code. Tests that deliberately check that one tenant’s data is invisible from another — rarely written, and precisely the ones that catch the dangerous mistakes. And a migration mechanism that runs across all boundaries predictably.
Separately: the ability to export and delete one specific customer’s data. You will need it both when a customer leaves and under data protection law.
Time and cost
Rough numbers. Designing multi-tenancy in from scratch adds roughly 15–25% to an MVP — architecture decisions, isolation and the tests for it. Retrofitting a mid-sized working system takes one and a half to three months, depending on how many background jobs and integrations there are.
The difference is not catastrophic, and that is the main practical conclusion: if the second customer is in doubt, deferring the decision is legitimate. If they are in the plan for the coming year, doing it now is cheaper.
In short
Multi-tenancy is not a checkbox on a technology list, it is a decision about data isolation with a knowable price. The three models differ in where the boundary between customers runs: separate databases, separate schemas, or a column in a shared table. The stronger the isolation, the more expensive the operations.
The main mistake is believing the retrofit is just adding a field. What breaks is background jobs, caching, files, unique constraints and integrations, and those are what determine the real cost.
If you are building SaaS, our services page covers the billing, subscriptions and white-label work that makes up the rest of the product.
Facing a similar problem?
Tell us what you are building. We will walk through the architecture and give you a budget range in one call.