01Three models, honestly compared
There are effectively three options, and the trade-off is the same axis every time: isolation versus operational cost.
Shared schema puts every tenant in the same tables with a tenant identifier column. One migration, one connection pool, cheap to run, trivial to query across tenants for analytics. The risk is that a single missing filter in a single query leaks one customer's data to another.
Schema-per-tenant gives each tenant its own set of tables in one database. Isolation is much stronger and per-tenant backup and restore becomes straightforward. The cost is that every migration now runs N times, and connection and metadata overhead grows with tenant count.
Database-per-tenant is the strongest isolation and the highest operational burden. It is the right answer when a customer contractually requires their data in a specific region or on separate infrastructure, and the wrong answer when you chose it because it felt safer.
The question is not "which is most secure" but "which failure am I more likely to survive" — a leaked query, or an operational load I cannot sustain.
03The mistakes that force a migration
Almost every painful tenancy migration I have seen traces back to one of a few early decisions, none of which looked significant at the time.
Using a globally sequential identifier that customers can see. Once tenant A can infer from record 4,812 that you have roughly five thousand records in total, you have leaked business information, and switching to opaque identifiers later means rewriting every URL and integration.
Putting tenant configuration in application code or environment variables. It works for the first three customers and blocks self-service onboarding for the fourth. Tenant settings belong in a table from day one.
Treating the first customer as the system. If provisioning a new tenant requires you to run a script, you do not have a multi-tenant product; you have a single-tenant product you have installed twice.
If adding a customer requires an engineer, tenancy is not solved yet — however the data is partitioned.
04Getting the boring parts right early
The parts of multi-tenancy that hurt in year two are rarely the data model. They are onboarding, billing boundaries and the internal tooling you need to support customers.
Self-service provisioning should create the tenant, seed its defaults, invite the first user and be idempotent when someone double-clicks. Billing should map to the tenant, not the user, or the first customer who wants a second seat will break your assumptions. And you need an internal admin view that lets you look at a tenant's state without connecting to production directly — build it in the first month, because you will otherwise build it during an incident.
Found this useful?
I write these from live project work. Follow along or get in touch if you want this kind of thinking applied to your product.