Why tenancy is an architecture decision, not a feature
Multi-tenancy looks deceptively simple at the start: add a tenant_id column and move on. But the model you pick on day one quietly shapes everything that follows — your data isolation guarantees, your blast radius during incidents, your migration story, and ultimately how far you can scale before a rewrite.
We treat tenancy as a first-class architectural concern. Before writing a line of code we decide where the isolation boundary lives: shared schema, schema-per-tenant, or database-per-tenant. Each is correct for a different stage and risk profile.
Choosing an isolation model
Shared schema with a tenant key is the cheapest to operate and the easiest to scale horizontally — it is where most products should begin. The trade-off is that isolation is enforced in application code, so a single missing predicate becomes a cross-tenant leak.
Database-per-tenant flips the trade-off: near-perfect isolation and trivial per-tenant backups or relocation, at the cost of connection pressure and migration fan-out. For regulated or enterprise tiers, that cost is usually worth it.
Defense in depth for isolation
We never rely on a single layer to keep tenants apart. Row-level security in the database backs up the application-level tenant scoping, so even a buggy query cannot return another tenant's rows.
Every request carries an authenticated tenant context that is set once at the edge and threaded through the stack. Background jobs re-establish that context explicitly — they are the most common place isolation bugs hide.
Scaling without surprises
As tenants grow unevenly, noisy-neighbor effects appear first. We shard hot tenants onto dedicated resources and keep the long tail on shared infrastructure, so cost tracks usage instead of the worst case.
The result is a system that stays boring under load: predictable isolation, predictable cost, and a clear path to move any tenant onto its own database the day it needs to.