Multi-tenancy patterns
There are three ways to hold many customers in one product — everyone in the same tables, one schema each, or one database each — and the choice trades isolation against operational cost. None of them is a security control on its own; enforcement is. Connect uses shared tables with a workspace key, and enforces the boundary three separate times.
Three patterns on one axis#
- Shared schema
- One set of tables for everyone, with a customer key on every row. Cheapest to run and to migrate; entirely dependent on the key being applied to every query.
- Schema per customer
- One database, many schemas, identical tables in each. A stronger blast radius, and every schema change now happens as many times as you have customers.
- Database per customer
- Full separation, per-customer restore, and per-customer placement. The strongest boundary and the heaviest operation — connections, upgrades, monitoring and cost all multiply.
- Hybrid
- Most customers pooled, a few separated because a contract or a regulation demands it. Two operational models to maintain rather than one.
The axis is the same in all four: how much do you spend, per customer, to make a mistake impossible rather than merely unlikely?
What each pattern actually costs#
| Concern | Shared schema | Schema each | Database each |
|---|---|---|---|
| Cost per additional customer | Near zero | Small but real | Substantial |
| A schema change | Once | Once per customer | Once per customer, across instances |
| Blast radius of a query bug | Everyone, unless enforced below the application | One schema, if the search path is right | One customer |
| Restoring one customer to yesterday | Hard — the rows are interleaved | Feasible | Straightforward |
| Placing one customer's data in a region | Not possible without splitting | Not possible on one instance | Straightforward |
| Noisy neighbour | Shared resources | Shared resources | Isolated |
| Answering a question across all customers | One query | A union | A pipeline |
Two rows in that table decide most real choices, and neither is about security. Per-customer restore and per-customer placement are the requirements that force separation; everything else can usually be bought more cheaply with enforcement.
The pattern is not the control#
A separate schema per customer with an application that sets the wrong search path is less safe than shared tables with the boundary enforced in the database. Separation makes a leak require a mistake in the *right* place; it does not make one impossible, and it tends to encourage the belief that the hard part is done.
What actually decides safety is whether the boundary is enforced somewhere a forgetful query cannot get past, and whether it is enforced more than once by mechanisms that fail differently. See row-level security for the layer that does the work in a shared schema.
Does Connect use multi-tenancy, and which pattern?#
Shared schema, keyed on workspaces, with the boundary enforced three times. Nearly every table carries a workspace reference and is filtered automatically; a short, enumerated list of control-plane tables — platform identity, sessions, billing, and enquiries addressed to the operator itself — is the deliberate exception.
The three enforcement points are an allowlist in the middleware, the ORM kernel that filters every query, and row-level security in the database. The customer-facing API is a separate facade: a customer session reaches /api/customer/... paths, and a request the rewriting layer does not recognise becomes a blocked path answered 403 rather than a path that happens to work.
The operator's own workspace is a workspace like any other, which is a useful property rather than a detail: there is one feature set over one implementation, and the difference between the operator and a customer is commercial — a customer's use is bounded by plan allowances. Parity is not asserted, it is enumerated: a test reads the route table out of the running application and compares every operator capability against the customer surface, with a list of known gaps that may shrink and must never grow.
Questions#
Is one database per customer safer?
It has a smaller blast radius and it is not automatically safer. Safety comes from enforcement that a forgetful query cannot bypass. A shared schema with forced row-level security and an independent query-building layer above it closes the common failure; separate databases mostly relocate it.
Can one workspace ever see another's data?
Three independent mechanisms would have to fail together: the path allowlist, the query kernel and the database policies. They fail differently by design, which is the entire reason there are three rather than one.
Does the operator's workspace have extra features?
No. It is a workspace like the others, over the same implementation. Three things are correctly operator-only because they are about running the platform rather than using it: verifying customer payments, the operator's own pricing, and enquiries addressed to the operator.