CobssoftCobssoft
Note 01June 20267 min read

Multi-tenant isolation and the cost curve

Row-level, database-per-tenant, or account-per-tenant is not a security debate. It is a question of which of your costs are fixed, which are marginal, and how much a single mistake is allowed to cost.

The question arrives early on every SaaS engagement, and it usually arrives framed wrongly: shared or isolated. Posed that way it becomes an argument about principle, and whoever cares most about security wins the room. Two years later the platform has a per-tenant cost floor nobody modelled and a provisioning step that requires a human.

A more useful framing: a tenant boundary is a promise about blast radius. Pick the cheapest boundary that can actually keep the promise you are making to your customers, then automate it so completely that adding a tenant is a pipeline run.

Three boundaries, three failure modes

Row-level — one schema, a tenant column, every query filtered. Marginal cost per tenant is close to zero, which is why every self-serve product starts here. The failure mode is a single missing predicate in a single query path. That is not a hypothetical; it is the most common cross-tenant data incident in the industry, and it is invisible until someone sees another customer's record.

Database or schema per tenant — shared compute and a shared control plane, separate stores. Connection management and migrations become the hard part: a schema change now runs N times and must be safe to run partially. Blast radius drops sharply, because a bug leaks nothing unless it also picks the wrong connection string.

Account or subscription per tenant — separate cloud account, separate identity boundary, separate everything. Cross-tenant access is not prevented by code review; it is prevented by the cloud provider's control plane. The price is a fixed monthly floor per tenant and a provisioning process that is now infrastructure work rather than a database insert.

The actual tradeoff

You are not choosing between cheap and secure. You are choosing which cost is fixed and which is marginal — and buying down the cost of being wrong.

What bends the curve

Two variables decide this, and neither is ideology.

The first is your tenant shape. Many small tenants on low revenue each cannot carry a fixed monthly floor — a baseline database, a monitoring workspace, a backup policy and a log sink per tenant will eat the margin before you have a hundred customers. Few large tenants can carry it easily, and typically demand it in the contract anyway.

The second is the cost of a mistake. In clinical, financial or regulated data, one cross-tenant read is a reportable breach with a regulator attached. When that is true, the argument shifts: you are no longer comparing infrastructure line items, you are pricing an event that can end the product. Isolation that a code review cannot accidentally undo becomes cheap by comparison.

The part that actually costs money

On the dental SaaS platform we architected, we went to account-per-tenant. The interesting finding was not about isolation — it was that the expensive part of account-per-tenant is never the account. It is the human step.

If onboarding a tenant involves a person clicking through a console, isolation has quietly become a headcount problem that scales linearly with sales. So the boundary only holds if the whole tenant — accounts, roles, guardrails, pipelines, monitoring, backups — is described in code and created by the same pipeline every time, with policy fencing what that pipeline is allowed to do. Below is the shape of it: per-environment accounts, deployer roles the pipeline assumes, human access via SSO roles, everything fenced by service control policies.

Accounts & RolesAWS ORG · SCP · IAM · SSO
AWS CLOUDAWS ORGANIZATIONRoot Accountdeployer-scpdev-accountdeployer-dev-roleaws dev resourcesstg-accountdeployer-stg-roleaws stg resourcesuat-accountdeployer-uat-roleaws uat resourcesprd-accountdeployer-prd-roleaws prd resourcesHUMAN ACCESS — SSOsso-scpsso-accountgenyus-admingenyus-contributergenyus-readerWORKLOAD IDENTITYservices-scpservice roles

Which direction is the cheap direction

One asymmetry decides most of these calls, and it gets overlooked. Moving from row-level to isolated is a rewrite: every query path, every background job, every report, every migration assumes a shared store, and all of it has to be re-proved. Moving from isolated toward shared is mostly consolidation — you already know exactly where each tenant's data lives, because it lives somewhere separate.

So when the tenant shape is genuinely uncertain — and at MVP it usually is — the reversible choice is the more isolated one, but only if provisioning is automated from day one. Automated isolation is a decision you can walk back. Row-level with a hundred live tenants is not.

Where does that leave the middle? Database-per-tenant behind a shared control plane is the honest answer for most B2B products: it survives a code review mistake, it keeps one deployment surface, and it does not put a cloud account on your balance sheet for every customer trial.

Whichever boundary you pick, the test is the same. Can you provision it without a human, and can you prove to an auditor on any given Tuesday that it held?

Disagree with this?Tell us why