One Database, Many Customers: How We Keep Data Apart
There are two broad ways to build software that serves many customers. You can give every customer their own database, or you can store every customer's data in one shared database and keep it apart with careful design. Our platform, VoVo, uses the shared approach. It lets us improve every module for everyone at once, keeps costs reasonable for small organizations and makes features like one login across several accounts possible.
The trade-off is that separation becomes our job rather than the database's. Here is how we do it.
Every record carries its owner
Almost every table that holds customer data has a column naming the account it belongs to. Many also name the specific website within that account, because one account can run several sites. A blog post, a form submission, an order, a member, an uploaded image: each knows exactly whose it is.
That column is not optional decoration. It is how every other protection works. When we design a new table, adding the owner column and its index is part of the first draft, not something added later.
Automatic filters for everyday queries
Our data models use a shared building block that does two things automatically. When a record is created inside an account, it stamps the account's identifier onto it. When records are read, it adds a filter so only the current account's rows come back.
Because the filter is automatic, a developer writing an ordinary screen does not have to remember it every time, and an ordinary mistake does not leak data. That is the first line of defense.
Explicit scoping where automation stops
Automatic filters depend on knowing which account is active. In a web request that is clear. In other places it may not be:
- Background jobs running long after the request ended
- Command-line tools used by our team
- Data seeders that publish content as part of a release
- Scheduled tasks that work across many accounts
In those paths we do not trust the automatic filter to be active. Code names the account and website explicitly in every query and every write. If a script is meant to change something for one account, the condition that limits it to that account is written out in full.
Unique per account, not unique everywhere
Shared tables need careful uniqueness rules. Two different churches can both have a page called "about." So a page address must be unique within an account and website, not across the whole platform. Our database constraints are built that way, combining the owner columns with the field that must be unique. We explore that in composite unique keys.
People are the exception
User logins are shared across accounts on purpose, because one person can belong to several organizations. That means people need special handling everywhere they appear in a list or search. Our article on scoped user lists explains the pattern.
Reviews look for the leaks
Automatic protections are strong, but reviews are where we catch the unusual cases: a report that joins two tables and forgets the owner condition on the second one, a cache that stores an answer without naming the account, a download link that checks the file but not who is asking for it. Our review checklist asks about each of these specifically, and our standards document gives every contributor the same rules.
Testing the walls
We also check isolation from the outside. During browser checks we sign in as one account and confirm we cannot reach another account's records by changing an address, guessing an identifier or searching for a name we know exists elsewhere. A wall that has never been pushed on has not really been tested.
Why we accept the responsibility
A shared database asks more of us as engineers. In return, every customer benefits from every improvement immediately, and organizations of every size can afford a full platform. We think that is a good trade, as long as we treat separation as the most important feature we never advertise. It begins with tenant-first thinking.
Frequently asked questions
Is a shared database less secure than separate databases?
Not inherently. Both can be secure or insecure. A shared database requires consistent owner columns, automatic filtering and careful handling of background work, which is why we treat those as non-negotiable.
What happens when one person belongs to two accounts?
They have one login and a separate membership in each account. Each account only sees its own data, and lists of people are always built from the current account's memberships.
How do you stop scripts from changing the wrong account?
Scripts, jobs and seeders name the account and website explicitly in every query and stop with an error if the account does not match what they expect.
Comments (0)
No comments yet. Be the first to share your thoughts!