Architecture

One database per firm, and why we gave up the easy version

Shared tenancy is dramatically easier to build. A note on what it costs, what the alternative costs, and why for this particular kind of data the harder option is the correct one.

Pental8 min read

Building a multi-tenant application the normal way is a solved problem: one database, a tenant column on every table, and a filter on every query. It is cheap to run, simple to operate, and it is what almost everything you use is doing.

It also means the entire separation between two customers rests on every query, in every endpoint, forever, remembering to filter. One mistake in one place exposes everyone at once. For most products that trade is fine. For a system holding penetration testing findings, we did not think it was.

What a finding actually is

A report is an inventory of the fastest ways into a business, written by an expert, ordered by exploitability, with evidence attached. There are very few documents a company owns that are worth more to an attacker, and the platform holding them is therefore a target in its own right, independent of the clients it serves.

A shared database concentrates that. Every consultancy on the platform, every client of every consultancy, one query away from each other. The value of compromising the vendor is the sum of everything on it, which is exactly the shape of target that attracts sustained attention.

What we do instead

Each firm gets its own Postgres project, in its own account, in a region it chooses. There is no shared table holding several firms’ rows next to each other, which means there is no tenant filter that could be written wrong and no query that could return the wrong firm’s data by accident.

Each project also signs its own tokens, so a session minted against one firm’s database is cryptographically meaningless against another’s. It does not fail an authorisation check; it fails to validate at all.

What it costs us

Quite a lot, and it is worth being straight about it rather than presenting the decision as free.

  • A schema change has to be shipped to every database rather than applied once, which makes migrations a real engineering problem instead of a command.
  • There is no query that answers a question across all customers, so ordinary product analytics have to be designed rather than assumed.
  • Support is harder. We cannot look at a customer’s data to diagnose their problem, which means diagnostics have to be built so the customer can run them.
  • Onboarding has a step in it. Creating the project is guided, but it exists, and a competitor with shared tenancy does not have it.

That last one is a real cost in a sales cycle. We kept it because the alternative is a demo that is thirty seconds faster and an architecture that cannot answer the question a client’s security team is going to ask.

The part that made it worth it

The blast radius of any mistake we make is one firm, because there is nothing else in the database to reach. Not one mistake in one endpoint exposing every customer. One firm, whose data was already theirs.

And it changes what a consultancy can say to its own clients. Not that the vendor has good policies, but that the database is in their account, in a region they chose, and there is no standing route from us into it. That is a different kind of answer, and it is the one that ends the thread.


Pental is built by the people writing this

Engagement management for testing firms, on a database you own, under your brand, with the AI running on your key.

Start free trialMore posts