Chapter 1 · Foundations
What multi-tenancy actually commits you to
The word describes a billing model, but what you are signing up for is a correctness obligation on every query you will ever write. Here is the difference, and the four things that get harder forever.
6 min read
Multi-tenant means many separate customers share one running system. That sentence sounds like an infrastructure choice. It is not. It is a commitment that every single query in the application is wrong unless it is scoped, held for the entire life of the codebase, by every person who ever joins the team.
That is the whole subject. Everything in the next 149 chapters is a consequence of it.
The thing that is actually different
Here is an ordinary query in a single-tenant application:
// A product catalogue. In a single-tenant app this is complete and correct.
export async function listProducts(): Promise<Product[]> {
return db.select().from(products).orderBy(products.name)
}Here is the same query in a multi-tenant application:
// The same query, now dangerously wrong. It returns every tenant's catalogue
// to whoever asked. Nothing about it looks broken, no test fails, and in
// development — where there is one tenant — it behaves perfectly.
export async function listProducts(): Promise<Product[]> {
return db.select().from(products).orderBy(products.name)
}The code is identical. The defect is the absence of something, which is the hardest kind of defect to see in a review and the only kind that a type system will not catch for you by default.
A missing
whereclause in a single-tenant app is a performance problem. The same missing clause in a multi-tenant app is a data breach. The code looks the same in both.
This is why the rest of this series spends so much time on mechanisms that make the omission impossible rather than discouraged. Chapter 21 builds the query layer where you cannot write an unscoped read, and Section 3 puts a second enforcement layer inside the database for the day the first one is bypassed.
Four things that get permanently harder
| Single-tenant | Multi-tenant | |
|---|---|---|
| A migration | One schema, one run, one rollback | One schema, N tenants' data, and a column default that is correct for 900 tenants and wrong for 100 |
| A bug report | Reproduce it | Reproduce it as that tenant, without being able to log in as them |
| A slow query | Slow for you | Slow for everyone, because one tenant has 400× the rows of the median |
| Deleting data | DELETE | Prove it is gone, in a system where the same table holds everyone else's rows |
None of these is a reason not to build multi-tenant. They are the bill, and the bill comes whether or not you planned for it.
What you get in exchange
The trade is real and it is why almost every SaaS product is built this way.
- One deploy upgrades everyone. A fix shipped once is shipped to every customer. In the single-stack-per-customer model, a security patch is a project.
- Cost per customer falls as you grow. Idle capacity is shared. A customer who uses the product twice a month costs almost nothing.
- One codebase. No per-customer forks, which is the failure mode that eventually eats agencies alive.
- Onboarding is a row, not a deployment. This is the one that decides whether self-serve signup is possible at all.
The commitment, stated precisely
It is worth writing this down somewhere a new engineer will read it, because "be careful about tenant scoping" is not an instruction anyone can follow reliably.
// The invariant. Everything in this codebase exists to keep it true.
//
// No request may read or write a row belonging to a tenant other than the
// one resolved for that request — regardless of the code path, the caller,
// or the correctness of the code.
//
// Note "regardless of the correctness of the code". That is deliberate. A rule
// that only holds while every query is written correctly is not an invariant,
// it is a hope. Section 3 is how it survives a mistake.The last line is the point of difference between a system that has had one incident and a system that has not yet had its first.
What this series builds
The carried example is Meridian: a platform where independent software vendors sell seat-based licences to their own business customers.
| Tenant | A vendor — its own catalogue, pricing, customers and payouts |
| Tenant's customer | A business buying seats from that vendor |
| The platform's revenue | A fee on every transaction |
| The constraint that shapes everything | Meridian never holds a vendor's money |
That last row is not a detail. It is the difference between building a payments platform and becoming a regulated money transmitter, and it decides the entire shape of Section 6. A platform that collects customers' payments into its own account and pays vendors later is holding other people's money, which is a licensing question in every jurisdiction that has one.
By the end you will have built something with tenants, isolation you can prove, identity that spans organisations, onboarding through identity verification, charges that settle to the tenant, seat-based subscriptions that survive a mid-cycle change, entitlements that do not drift, a reconciliation ledger, a support surface, and an answer for the tenant who wants all their data and then wants it deleted.
Key points
- Multi-tenancy sounds like an infrastructure decision and behaves like a correctness obligation on every query.
- The dangerous defect is an absence — a missing scope — which reviews and type systems miss by default, and which behaves perfectly in a one-tenant development database.
- Migrations, reproduction, performance and deletion all get permanently harder. That is the bill, not a reason to avoid the model.
- In exchange: one deploy for everyone, falling cost per customer, one codebase, and onboarding that is a row rather than a deployment.
- Write the invariant down, including "regardless of the correctness of the code" — a rule that depends on every query being right is not an invariant.
- Meridian never holds a vendor's money. That single constraint decides the payment architecture before any code is written.
Next: The three isolation models — shared table, shared database, and separate database, and what each one actually costs.