DEV Community

informat
informat

Posted on

Multi-Tenancy Is Not a Deployment Model. It Is a Data Model Decision.

The meeting lasted eleven minutes. The IT director of a manufacturing group had one question: "We have four subsidiaries. Can they all run on this, with each one only seeing its own data?"

Everyone in the room heard a configuration request. It was an architecture request, and we spent the next year learning the difference.

Three different things people call "tenant"

The word hides at least three separate concerns, and the trouble starts when a customer uses it to mean all of them at once.

There is a deployment tenant: a separate installation, a separate database, a separate upgrade. There is a data tenant: one installation, many logical partitions, one shared schema. And there is a configuration tenant: the same data boundary, but a different set of forms, workflows, and permission rules.

Most platforms pick one of these and quietly assume the other two come along for free. Our customer wanted all three, for four subsidiaries, in one deployment, sharing one application template, with the ability to change that template for one subsidiary without touching the others.

That last clause is where a low-code platform stops behaving like a normal SaaS product.

Metadata is data too

In a conventional multi-tenant application, isolation is a filter. Every query carries a tenant column, every index starts with it, and the code review question is refreshingly boring: did you remember the where clause?

A low-code platform does not have that luxury, because the thing being isolated is not only the customer's business records. It is the definition of the application itself: the fields, the views, the permission rules, the workflow versions, the automation triggers, the scripts, the dashboards. All of that is rows in tables too, and all of it is tenant-scoped.

So the question multiplies. When a user in subsidiary A saves a list view, does that saved filter belong to them, to their subsidiary, or to the template everyone shares? When subsidiary B's admin fixes a bug in a shared workflow, do the other three get the fix, or an unexpected change on a Tuesday afternoon?

There is no universally correct answer. There is only an answer the customer can predict. That is the real deliverable, and it is much harder to produce than a where clause.

Customization is where the boundary leaks

Here is the uncomfortable shape of the problem. The reason people buy a low-code platform is customization. The reason multi-tenancy is hard is customization. These are the same property.

Every customization surface is a candidate isolation hole. Custom fields: whose field is it, and what happens when a shared report assumes it exists? Custom scripts: this is the worst one, because it is user-authored code running inside the platform's own process, with the platform's own privileges. A script written by subsidiary A that quietly reads "all orders" is not a bug in the script. It is a bug in the boundary, and the person who wrote it had no idea they were crossing a line, because from where they sat there was no line to see.

We learned to think of it this way: the tenant boundary cannot be enforced by the application builder, because the application builder is a tenant. It has to be enforced by the runtime, at every point where metadata meets data, whether or not the caller is aware there is a boundary at all.

That means the enforcement point is not one place. It is the query compiler, the script sandbox, the workflow engine, the automation scheduler, the file store, the export pipeline, and now the AI agent. Every one of those has to know who is asking, and none of them can be trusted to guess.

The tenant that ruins it for everyone

Then there is the failure mode nobody puts in the sales deck: one tenant making the product bad for everyone else.

A shared runtime means shared connection pools, shared caches, shared background workers, shared rate limits. One subsidiary imports a two-million-row spreadsheet before lunch and the other three watch their list views crawl. One automation goes into a retry loop and eats the scheduler.

In a single-tenant installation, this is a performance bug. In a multi-tenant one, it is a fairness bug, and fairness is much harder to reason about than throughput. You end up building per-tenant quotas, per-tenant queues, and a notion of priority that never appeared in any requirement, because the customer never asked "can we be slow?" They asked to share a system.

Leaving is harder than joining

The part nobody plans for is exit.

When a tenant wants to leave — a contract ends, a subsidiary is sold, a regulation demands deletion — you have to answer a question the design has been avoiding for two years: what exactly belongs to them?

Their business records, obviously. But also their attachments, their audit history, their script versions, their workflow instances, the reference data someone imported in year one, and possibly rows inside a shared table they can no longer distinguish from the shared rows, because the design treated "shared" and "theirs" as the same column.

A platform that cannot delete a tenant cleanly cannot onboard one confidently. We found the honest test of a tenant boundary is not "can they see only their own data." It is "can we remove them completely, without touching anyone else, and prove it."

If you cannot run that test, you do not have a tenant boundary. You have a convention.

The agent does not know about tenants

The newest pressure came from somewhere we did not expect: the AI agent.

An agent that can query, create, and edit records is the most capable user the platform has ever had, and also the least aware of context. A human in subsidiary A has a session, a sidebar, and a mental model of where they are standing. A tool call has a scope parameter, and if that scope is missing, wrong, or inherited from the wrong place, the agent will answer a question about subsidiary B's revenue in exactly the same confident tone it uses for everything else.

We had to stop treating the tenant as a part of the request and start treating it as part of the identity. The agent does not get to say who it is acting for; the runtime tells it, and the runtime refuses anything that would cross a line — including the seemingly innocent requests, like "summarize all orders in the system," which sounds reasonable until you remember that the system has four customers inside it.

The uncomfortable conclusion

We set out to build a platform where every customer feels like an owner. Multi-tenancy is the price of that feeling, and it is paid in the one currency we were least willing to spend: the assumption that the platform is only ever running for one person.

The uncomfortable conclusion is that the feature we sell — deep, per-customer customization — is precisely the property that makes a tenant boundary unenforceable by policy. You cannot document your way out of it, because the document would have to be read by the person customizing the application, who is the one person who cannot see the line.

The only boundary that holds is the one the platform enforces on itself, permanently, in every path where a definition becomes a query or an action. The customer gets to stop thinking about it.

Which means, once again, we do not.

Top comments (0)