Why this decision is different from the others

Most architectural choices in an early product can be revisited. A framework can be replaced, a queue introduced, an interface redesigned. These are expensive but bounded, because they touch a part of the system.

Tenancy is not bounded. The decision about where one customer's data lives relative to another's reaches into every query, every migration, every backup, every report and every access check the product will ever contain. Changing it later means revisiting all of them at once, on live customer data, without downtime anyone will forgive.

The three common models, and what each actually costs

A shared schema keeps every tenant in the same tables with a tenant identifier on each row. It is the cheapest to operate and to migrate, and it puts the entire burden of isolation on application code — one query missing its tenant scope is a cross-customer data leak.

A schema per tenant gives each customer their own tables inside one database. Isolation is enforced further down the stack, which makes accidental leakage much harder, at the cost of migrations that must run many times and tooling that must cope with a schema count that grows with sales.

A database per tenant isolates furthest and costs most: separate backups, separate migrations, separate connections, and an operational burden that scales linearly with customers. It becomes the right answer when isolation is a contractual or regulatory requirement rather than a preference.

Ask what the answer has to survive

The choice follows the obligations rather than taste. Will a customer ask where their data is stored, and will the answer need to be a jurisdiction? Will anyone require that their data is not co-resident with another organization's? Will you need to export or delete one tenant completely, on request, without touching others?

A product that will never face those questions is usually well served by a shared schema with disciplined scoping and tests that specifically try to read across tenants. A product selling to regulated buyers should assume it will face all of them, and that discovering so after signing is the expensive path.

Whatever you choose, prove the boundary

Isolation that is not tested is an intention. The useful test is adversarial: authenticate as one tenant and deliberately attempt to read, update and enumerate another tenant's records through every route the application exposes, including the ones that take an identifier from the URL.

That suite is worth writing on the day the tenancy model is implemented, because it is the thing that catches the missing scope in the query somebody adds eight months later under time pressure.

Write the reasoning down

Whichever model is chosen, the decision should be recorded with the constraints that produced it. The next person to question it — often the same team, two years on, with different customers — needs to know whether the answer was a considered trade-off or the default that came with a tutorial.

That record is also what makes a later change tractable, because it identifies which of the original constraints have moved.

Tenancy reaches further than the database

It is tempting to treat this as a storage question, but the choice propagates. Background jobs need to know which tenant they are acting for, and a job that loses that context can write one customer's result into another's account. Caches need tenant-aware keys, or one tenant is served another's data from memory with no query involved at all.

Search indexes, file storage paths, exported reports, and outbound email templating all inherit the same requirement. Each is a place where the isolation guarantee can be broken by code that looks perfectly reasonable in isolation.

This is the practical argument for pushing isolation as far down the stack as the operational budget allows: application-level scoping has to be remembered in every one of those places, and database-level separation does not.

Onboarding is where the model becomes visible

Whatever the model, signing up a new customer has to provision whatever that tenancy requires — a row, a schema, or a database — without manual intervention. A model that needs an engineer for each new customer works until sales succeed, at which point it becomes the constraint on growth.

The provisioning path also needs a failure story. If creating a tenant partially succeeds, the product must either finish the job or clean up; leaving half-created tenants behind produces support cases that are difficult to diagnose because the account exists in some tables and not others.