bytloop logobytloop
All posts
May 21, 20262 min readbytloop — Platform engineering

Multi-tenant architecture: the decisions you can't undo later

Some multi-tenant decisions are cheap to change in week one and genuinely painful to reverse by year two. Here's how we tell which is which before committing.

Multi-tenantSaaSArchitecture

Most architecture decisions in a SaaS product are reversible if you're wrong — a slow migration, some downtime, a rough week. A handful of multi-tenant decisions aren't like that; getting them wrong early means a painful, high-risk migration later, done while the product is already carrying real customer data. Those are worth getting right before the first tenant signs up.

Row-level isolation vs. a database per tenant

Row-level isolation — one database, every table scoped by a tenant column, enforced at the query layer — is operationally simpler: one thing to back up, migrate, and monitor. Separate databases per tenant limit the blast radius of a bug, at the cost of running and maintaining N databases instead of one. Most teams should start with row-level isolation enforced structurally, not "we'll remember to filter by tenant_id in every query" — that second version is a bug waiting for the one query someone forgets.

Decide where the tenant ID lives on day one

It belongs in the auth token, not only in the database row. Retrofitting tenant scoping into an authentication system that didn't have it from the start is one of the more painful migrations we get called in for — every endpoint, every background job, every cache key needs to be revisited, and there's no way to do it gradually without a window where isolation is only partially enforced.

Per-tenant customization is a feature request, not a schema decision

When your first big client asks for a workflow tweak specific to them, resist adding a special-case column or table just for their account. It feels harmless once. By your third and fourth client each wanting their own special case, you're maintaining a fork of your data model per customer, and every migration has to account for all of them. Build a real configuration layer instead, even a simple one, before the second request arrives.

Billing needs its own model earlier than you'd think

Usage-based, seat-based, and tiered billing each imply different data you need to be recording — API calls, active seats, feature flags per plan — from before you've even settled on final pricing. Deciding the pricing model after the data needed to support it should have already been captured means backfilling numbers you never recorded, for customers who are already being billed something.

Keep reading

More from the field notes