Websites

Database per tenant vs a shared database: the trade offs

A database per tenant gives each customer the strongest separation and the simplest restore, at the highest cost and with migrations to run many times. A shared database is cheapest and simplest to change, but every query must filter by tenant correctly, forever; a schema per tenant with its own login role sits between the two.

· Co-founder

4 min read · Published

Choosing between a database per tenant and a shared database is a trade between isolation and cost. A separate database for each tenant gives the strongest separation, no noisy neighbours and an easy restore of one customer, but costs the most and multiplies every migration. A shared database is cheapest and easiest to change, but depends on every query and every policy being right. A schema per tenant with its own login role is the common middle ground.

AWS’s SaaS isolation guidance calls the two ends silo and pool. The table above sets out four approaches against five trade offs.

Shared tables with a tenant column

Every table carries a tenant id, and every query filters on it. In Postgres, row level security moves that filter into the database: policies decide which rows each role sees, and if a table has row security enabled and no policy, the default is to deny every row. Supabase builds its access model on exactly this.

The appeal is cost and simplicity. One database, one migration, one backup. The risk is that the boundary between customers is a rule you wrote, repeated on every table, and one mistake is a data leak. Roles with the BYPASSRLS attribute and superusers skip the rules entirely, and table owners normally do too, so the role your application connects as matters. AWS also notes that a pooled model always carries some chance of a noisy neighbour, one tenant’s heavy use slowing the rest.

A database per tenant

Each customer gets a database or a whole project of their own. AWS’s silo model describes the benefits: no noisy neighbour concerns and simple per tenant cost tracking, with real challenges to cost and agility. Restoring one customer to yesterday is easy. Running a migration means running it once per tenant and checking each one, and databases that miss a migration drift apart.

Cost is the main limit. On Supabase, each additional project adds its full compute cost, $10 a month for a Micro instance, on top of the plan. That is fine for a few large customers and heavy for thousands of small ones.

A schema per tenant, in between

Postgres schemas are namespaces inside one database. By default, users cannot access objects in schemas they do not own unless the owner grants USAGE on the schema. Give each tenant a schema and a login role that can reach only that schema, and the database itself refuses one tenant’s role access to another’s tables, before any row policy runs. Row level security still guards the rows inside each schema, for example one customer’s orders from another customer of the same tenant.

This keeps most of the cost of a shared server while adding a boundary a forgotten filter cannot cross. A single tenant can be dumped and restored on its own. The costs are operational: migrations run once per schema, and the server’s CPU and connections are still shared.

How OneCraft uses this for its sites

OneCraft writes websites and apps with an AI agent, and when a feature needs data the agent turns on a backend for that site. On every plan it is a schema of its own in OneCraft’s pool project, with a login role that can reach no other site. Alternatively, an owner on any plan can connect their own Supabase account, and the site gets a project of its own there: the database per tenant route, paid for by the owner.

Inside either, every table must have row level security, and before a generation step can finish the agent checks each new table as an anonymous visitor, as a signed in person and, where there is one, as an admin. Migrations and a schema file are written into the site’s own repository, so each site’s structure is known and travels with its code. The row level security page explains the policies, and the headless CMS or a site with its own database page compares this with keeping content elsewhere. What it costs to run is in what an AI written website costs to run, and what gets built is on the website with its own database page.

Choosing for your own product

  1. Start shared when tenants are many, small and alike, and you can test every policy.
  2. Use a schema per tenant when you want a database level boundary and per tenant restores without paying for a server each.
  3. Use a database per tenant when customers require separation by contract, run heavy workloads, or need their own backups and region.
  4. Mix them when a few large customers pay for a dedicated database and everyone else shares.

Whatever you pick, test access as each kind of user before you ship, not after a customer finds the gap.

Four ways to separate tenants in Postgres, compared on five trade offs; drawn from the PostgreSQL and Supabase documentation and AWS's SaaS tenant isolation whitepaper
ApproachIsolationCost per tenantMigrationsNoisy neighboursBackup and restore of one tenant
Shared tables, tenant columnRules on every table; one missed filter or policy exposes other tenantsLowest: one database for allRun once for everyoneShared CPU, memory and connectionsHard: one tenant's rows must be picked out of a shared backup
Schema per tenant, one login role eachThe database refuses access to other schemas by default; rules still guard rows insideLow: one server, many schemasRun once per schemaShared serverModerate: one schema can be dumped and restored on its own
Database or project per tenantStrongest: separate databases, separate credentialsHighest: each one has its own computeRun once per database, can driftNone between tenantsSimple: restore that database alone
Hybrid: pool by default, dedicated on requestDepends on the tierMixedTwo paths to keep in stepOnly on the shared tierDepends on the tier

Questions people ask

Is a schema per tenant secure?

It adds a real boundary. In PostgreSQL, users cannot access objects in schemas they do not own unless the owner grants USAGE on the schema. Give each tenant its own login role that can reach only its schema, keep row level security on inside it, and avoid roles that bypass it.

How many schemas can one Postgres database hold?

There is no single number to aim for. Every schema adds its own tables, indexes and catalog entries, and every migration must run in each one. In practice operations set the limit: running and checking one migration across thousands of schemas takes planning, tooling and time.

What is a noisy neighbour?

A tenant whose heavy use slows everyone else on shared infrastructure, such as a big import or a busy sale hogging CPU or connections. AWS's isolation guidance notes that a pooled model always carries some chance of this, and that a dedicated model removes it.

Does row level security replace separate databases?

For many products it is enough: Postgres applies policies to every query, and with no policy at all it denies every row. It does not separate CPU, backups or credentials, and a role with BYPASSRLS ignores it. Separate databases remain the answer when a customer requires physical separation.

Which is cheapest for a small product?

Shared tables with a tenant column, by a wide margin, because one database serves everyone. A schema per tenant costs little more on the same server. A database or project per tenant multiplies compute, so on Supabase each extra project adds its own compute cost, $10 a month for a Micro.

Written by

Indunil Asanka · Co-founder

Builds the generation pipelines behind OneCraft: the slide, flyer and poster layout engines, the document grid and the render workers that turn a written brief into a finished file.

LinkedIn profile

Sources

Written and checked by the OneCraft team. Last checked .

Make your own website

Describe what you need and the generator writes and designs it, then you edit anything you like.

See what it can make

Read next