# Multi-tenant SaaS architecture: how to keep each customer's data apart

> Shared schema, schema per tenant or database per tenant. How each isolation model works, where it leaks, and why tenancy has to reach auth, caches, jobs and backups as well as the database.

Ilya Ismatov · CTO · 7 September 2026 · Platforms

Bitmason: https://bitmason.dev/en/blog/multi-tenant-saas-architecture/

A SaaS product serves many customers from one system. Each customer, a tenant, expects to see only its own data, to get steady performance whatever the others are doing, and to have its data restored or deleted without affecting anyone else. Multi-tenant architecture is the set of decisions that makes those promises true.

Most of those decisions are cheap at the start and expensive later. This post covers the main isolation models, the database features that back them up, the noisy neighbour problem, the parts of the system people forget, and how to change course once the product is live.

## What multi-tenancy means in practice

A tenant is the unit that owns data and pays: usually a company, sometimes a team or a workspace. One tenant has many users, and one user may belong to several tenants, as with a consultant who works in several client workspaces.

Isolation has three sides:

- **Data isolation.** No request, report, export or search ever returns another tenant's records.
- **Performance isolation.** One tenant's heavy use does not slow everyone else down.
- **Operational isolation.** You can back up, restore, migrate, move or delete one tenant on its own.

The isolation models below trade these three against cost and operational effort.

## Three ways to separate tenant data

### Shared schema with a tenant id

Every tenant's rows live in the same tables, and each row carries a `tenant_id` column. Every query filters by it.

This is the cheapest model to run and the easiest to scale to many small tenants. One migration updates everyone, and reporting across tenants is simple. The risk is also simple: one missing filter leaks data. Isolation depends on every query in the codebase being right, so it must be enforced by structure, not by care.

### Schema per tenant

Each tenant gets its own schema (a namespace of tables) inside one database. Queries run against the tenant's schema, so a missing filter cannot reach another tenant's tables.

Isolation is stronger, and restoring or exporting one tenant is easier. The costs arrive with numbers. Migrations must run once per schema, and a migration that fails halfway leaves tenants on different versions. Thousands of schemas strain connection pools and database catalogues. It suits products with tens or hundreds of sizeable tenants better than thousands of small ones.

### Database per tenant

Each tenant gets its own database, sometimes its own server. This gives the strongest isolation on all three sides: separate performance, separate backups, the option of placing a tenant's data in a specific region, and a clean answer to a customer's security review.

It is also the most expensive to run. Every database needs provisioning, monitoring, backups and migrations, so this model only works with thorough automation. It fits a small number of large or regulated customers, and many products offer it as a premium tier on top of a shared pool.

## Row-level security as a safety net

In the shared schema model, the database itself can enforce the tenant filter. PostgreSQL's [row-level security](https://www.postgresql.org/docs/current/ddl-rowsecurity.html) lets you attach a policy to a table so that a session only sees rows matching its current tenant, typically read from a session setting that the application sets at the start of each request.

This turns a forgotten `WHERE` clause from a data leak into an empty result. A few rules make it hold:

- The application connects as a role that cannot bypass the policies. Table owners and superusers skip them by default.
- The tenant setting is set per transaction and cleared afterwards, so a pooled connection never carries one tenant's context into the next request.
- Policies cover every tenant-owned table, and a test checks that no such table lacks one.

Row-level security is a second line, not the only one. The data access layer should still scope every query by tenant, so both have to fail before anything leaks.

## Noisy neighbours

In any shared model, tenants compete for the same CPU, memory, connections and disk. A single tenant running a large import or an expensive report can slow the product for everyone.

The defences are layered:

- **Rate limits and quotas per tenant** on API calls, background jobs and storage, tied to their plan.
- **Fair queues** for background work, so one tenant's backlog of jobs cannot starve the rest.
- **Query budgets:** timeouts, pagination limits and indexes that start with the tenant id.
- **Per-tenant metrics,** so you can see who is using what, rather than only that the system is slow.
- **A way out:** the ability to move a heavy tenant to its own database or pool without changing code.

## Tenancy beyond the database

Isolation is often designed for the database and forgotten everywhere else. Each of these needs the tenant in it:

- **Authentication and authorisation.** The tenant should come from the verified session or token, never from a request parameter the client can change. Roles are held per tenant, since the same person can be an admin in one workspace and a viewer in another.
- **Caching.** Every cache key includes the tenant id. A cached page or query result shared across tenants is one of the most common leaks in practice.
- **Background jobs.** Each job carries its tenant and sets the tenant context before it touches data, exactly as a web request would.
- **Files and search.** Object storage paths and search indexes are partitioned by tenant, and signed download links are checked against the requesting tenant.
- **Logs and analytics.** Tenant ids in logs make support and incident work possible; personal data in logs needs the same care as in the database.
- **Backups and deletion.** You should be able to restore one tenant to a point in time and delete one tenant completely when its contract ends. In a shared schema this takes deliberate tooling, which is worth building before the first customer asks.

## Choosing a model and moving later

A reasonable default for a new B2B SaaS product is a shared schema with a tenant id on every table, enforced by the data access layer and by row-level security, with tenant-aware caching and jobs from day one. It is cheap, it scales to many tenants, and it leaves the door open.

Keep the door open on purpose:

- Resolve each tenant's database connection through a tenant registry, even while every tenant points to the same place.
- Use identifiers that are unique across all tenants, so a tenant's rows can be copied elsewhere without clashes.
- Keep tenant-owned data clearly separate from global data such as plans and reference tables.

With those in place, moving a large or regulated customer to its own database becomes a data migration and a registry change, not a rewrite. Moving the other way, from many databases to one, is much harder, which is another reason to start shared.

Pick database per tenant from the start only when the market demands it: contracts that require physical separation, data residency per customer, or a handful of very large customers whose workloads would dominate a shared pool.

## The short version

- Tenancy is decided before the first table, and it reaches every layer: auth, caches, jobs, files, logs and backups.
- Shared schema is the usual default; schema per tenant and database per tenant buy stronger isolation at a higher running cost.
- Enforce isolation twice, in code and in the database, and test that it holds.
- Plan for noisy neighbours with quotas, fair queues and per-tenant metrics.
- Route connections through a tenant registry so any tenant can move later without a rewrite.

## Questions about this article

### Is a shared schema with a tenant id secure enough for business customers?

For most B2B products, yes, provided isolation is enforced in more than one place, for example in the data access layer and with row-level security in the database, and tested. Some regulated customers will still ask for a dedicated database.

### Can we offer a dedicated database to one large customer only?

Yes. Many products run most tenants in a shared pool and a few in their own databases. It works best when the code resolves each tenant's connection from a registry rather than assuming one database.

### When should tenancy be designed in?

Before the first table is created. Adding a tenant id to a live product means touching every query, every cache key and every job, which is far harder than starting with it.

### Does multi-tenancy rule out per-customer customisation?

No, but customisation should live in configuration and feature flags held per tenant, not in separate code branches. Branches per customer turn one product into many.

