Back to all articles
Architecture7 min read

Four multi-tenancy decisions you cannot undo later

Tenant isolation, identity, data residency and the billing boundary are cheap to decide in week one and brutally expensive to change in year three. Here is how we choose.

Written by

Prakash Adhikari, Principal Engineer

Published

June 18, 2026

Most SaaS rewrites we get called into are not caused by bad code. They are caused by four decisions made in the first fortnight, by people who had no reason to think they were permanent.

You can refactor a service. You can swap a framework. You can rewrite a front end over a long weekend if you have to. What you cannot easily do is change how tenants are separated once you have three hundred of them, a signed enterprise contract with a data residency clause and an auditor asking questions.

These are the four we get right before writing the first migration.

1. Where the tenant boundary lives in the database

There are three workable models and one that is popular for the wrong reasons.

Shared schema with a tenant column. Every table carries a tenant_id. One database, one schema, one connection pool. Cheapest to run and easiest to migrate, because a schema change is a single statement. The risk is obvious: one missing WHERE tenant_id = ? and a customer sees another customer's data.

Schema per tenant. Each tenant gets a Postgres schema inside the same database. Isolation is stronger and per tenant backup becomes possible. The cost arrives at around a thousand tenants, when migrations have to loop and your connection pooler starts behaving strangely because each connection needs its search_path set.

Database per tenant. Full isolation, straightforward residency story, easy to hand a customer their data when they leave. It is also the most operationally expensive by a wide margin, and it makes cross tenant analytics a project rather than a query.

Application-level filtering with no database enforcement. This is the popular wrong answer. It looks like the shared schema model but relies entirely on developers remembering. It works until a junior engineer writes a reporting query on a Friday.

Our default is shared schema with tenant_id plus Postgres row level security, and we escalate individual tenants to their own database when a contract requires it. Row level security is the part most teams skip, and it is the part that turns a class of catastrophic bugs into an impossible one:

alter table invoices enable row level security;

create policy tenant_isolation on invoices
  using (tenant_id = current_setting('app.tenant_id')::uuid);

Set app.tenant_id once when the request checks out a connection, and every query in that request is scoped whether the developer remembered or not. A forgotten filter returns zero rows instead of someone else's invoices. That is the difference between a bug and an incident report to a regulator.

The cost is real. You need a connection strategy that guarantees the setting cannot leak between requests, which means either a transaction-scoped set local or a dedicated connection per request. Get that wrong and you have built a more confusing version of the problem you were solving.

2. Whether a user belongs to one tenant or many

This one looks like a product question. It is a data model question, and it decides your entire authorization layer.

If a user belongs to exactly one tenant, identity is simple. The user row carries a tenant_id, the session carries it too, and authorization is a comparison.

If a user can belong to several tenants, you need a membership table, a notion of the active tenant in the session, an invitation flow, a way to switch context, and a decision about what happens to a user's data when they leave one organization but stay in another. Every permission check becomes a question about the current context rather than the user.

Teams almost always start with the first model because it is a quarter of the work. Then a customer acquires another customer, or a consultant needs access to four client workspaces, and the migration is not a schema change. It is a rewrite of every authorization check in the system, plus a data migration where the correct answer for existing rows is genuinely ambiguous.

Our rule: if you sell to businesses, model membership from the start. The extra table costs you two days. Retrofitting it costs a quarter.

3. What the billing entity actually is

Ask a founder what they charge per and you get an immediate answer. Ask what happens when a customer wants two departments billed separately under one contract, with shared single sign-on and separate purchase orders, and the answer takes longer.

The trap is assuming the tenant, the billing account and the authentication boundary are the same object. In small deployments they are. In enterprise deals they routinely are not:

  • One contract, several workspaces, one invoice
  • One workspace, several cost centres, separate invoices
  • A parent organization that can see usage across children but cannot see their data
  • A reseller who pays, with an end customer who owns the data

Model these as three separate concepts from the beginning, even if today all three point at the same row. An organization that owns the contract, a workspace that owns the data, and a subscription that owns the money. Today organization has exactly one workspace and nobody notices. In two years, when your first serious enterprise deal depends on splitting them, it is a feature rather than a rebuild.

4. Where the data physically sits

Data residency arrives as a single line in a procurement questionnaire, usually attached to your largest deal of the year. By then it is either a configuration value or a six month project.

The decision is not whether to support residency on day one. It is whether the system can support it without a rewrite. In practice that means three things:

  • No hardcoded region anywhere in the infrastructure code. Region is a variable at the top of the Terraform, not a string in seventeen files.
  • The tenant record knows its home region, and request routing reads it rather than assuming.
  • Nothing shares state across regions except an identity directory that stores no customer content.

That last one is where teams get caught. A single global Redis for sessions, a single search cluster, a single analytics pipeline, one shared file bucket. Each seemed sensible in isolation. Together they mean that no tenant's data actually stays in its stated region, and the honest answer to the auditor is uncomfortable.

You do not need to deploy a second region until someone pays for it. You need to be able to, in weeks rather than quarters.

What this costs upfront

Roughly two weeks on a typical build. Row level security policies and the connection handling around them, a membership table and its invitation flow, a three entity split for organization, workspace and subscription, and region as a variable across the infrastructure code.

Against that, the last three multi-tenancy retrofits we were brought in for ran between four and nine months, all of them under contractual pressure, two of them with a feature freeze.

The reason these decisions feel optional early is that a system with five tenants behaves identically under all of them. The model only reveals itself under load, under audit and under a contract you have already signed. By then you are not choosing an architecture. You are paying for the one you chose without noticing.

A short checklist before your first migration

  • Is tenant isolation enforced by the database, or only by developer discipline?
  • Can a user belong to more than one tenant without a schema change?
  • Are organization, workspace and subscription separate rows, even if identical today?
  • Is the deployment region a variable rather than a literal?
  • Can you produce a single tenant's complete data export today, without writing new code?
  • Can you delete a single tenant completely, including backups, within your stated retention window?

If four or more of those are yes, you have bought yourself years of optionality for about two weeks of work. If fewer than three are, it is worth spending a sprint on it before the tenant count makes the decision for you.

SaaSPostgreSQLArchitectureMulti-tenancy

Working on something similar?

If this article is close to a problem on your desk, we are glad to talk it through. No pitch, just the conversation.

  • A senior engineer reads every brief
  • NDA signed before you share anything sensitive
  • No sales sequence, no automated follow ups