# Database per tenant vs shared database

A shared database with a tenant key and engine-level row security is the right default for most new SaaS products. A dedicated database per tenant earns its cost for a small number of regulated, contractually isolated or unusually large customers. Mature products usually run both. None of this is permanent, provided your schema is built so one tenant can be lifted out later.

- No compliance framework names an architecture. They ask for evidence of access control, logging and encryption.
- Cost separates the two models faster than anything else, and the gap widens with every tenant you add.
- Migrations are the hidden operating cost of the silo model.
- Strict tenant scoping and composite keys cost nothing now and buy you the option to split later.

This assumes you already know what multi-tenancy is and have decided to build it. The question here is narrower: where does each customer's data physically live?

It usually surfaces the same way. A prospect sends a security questionnaire, one line asks you to describe how customer data is separated, and the instinct is to go build a database per customer before the call ends. Hold that thought. No major compliance framework requires a physical database per tenant, and the cost of acting as if one does is paid every day for years.

![Diagram comparing database per tenant, schema per tenant and shared schema multi-tenant architectures.](https://secure-files.proservers.ws/content/01m592bhg72795h0r7ex2werhy/b64a7b0235633053193a903bf0680af1de1e0861dcde83b43728bcf1e7e83658.jpg)

## What each model actually means

Three arrangements cover almost everything teams build, and the vocabulary below is the one most of them use.

**Database per tenant, the silo.** Each customer gets its own database. Tenant context is resolved before a query is ever written, at the connection string.

**Shared database and shared schema, the pool.** Every tenant shares the same tables. Rows are separated by a tenant identifier carried in the query predicate, ideally enforced by the database engine rather than by the application alone.

**Shared database with separate schemas, the bridge.** One database instance holds a namespace per tenant. Tenant context is resolved by setting the search path on the connection.

There is a fourth arrangement, and it is where most successful products end up: a hybrid. Standard accounts sit in the pool, while enterprise accounts that pay for isolation get a silo. That is a pricing decision as much as an engineering one.

## Why schema per tenant is usually the wrong compromise

The bridge model looks like a free lunch. Logical separation you can see, no per-tenant infrastructure bill, a dump and restore that targets one customer. It holds up fine at twenty tenants.

The trouble starts as the schema count climbs. Every table in every schema adds rows to the PostgreSQL system catalogs, and those catalogs get read constantly: by DDL, by autovacuum, by ORMs introspecting the database at boot. PostgreSQL sets no hard limit on how many schemas you can create. In practice, teams tend to feel the strain somewhere in the low thousands, and that is practitioner judgment rather than a documented threshold. Treat it as a reason to test your own workload before committing, not as a number to plan around.

The deeper problem is that the bridge inherits the silo's migration pain without buying the silo's isolation story. You still loop over N targets at deploy time. You still cannot hand an auditor a network boundary.

## Six trade-offs that decide the architecture

### Compliance evidence

SOC 2, HIPAA and GDPR do not specify database topology. They require evidence of adequate access control, audit logging and encryption, and they leave the implementation to you.

What changes between models is how easy that evidence is to produce. A silo gives you a boundary an auditor can see: separate credentials, separate network policy, separate backup. A pool requires you to demonstrate the controls instead, which means database-level policies, authorization tests that actually try to cross tenant lines, and a record of those tests running on every build. Both are defensible. One is more work to write up, and the other is more work to run.

### Cost at scale

Density is where the pool wins outright, and the gap is not subtle. Two hundred micro-sized managed instances carry a baseline bill in the low thousands of dollars a month before a single query runs, because you pay for idle capacity two hundred times over. The same two hundred tenants on one pooled cluster can sit in the low hundreds. Instance pricing moves, so model it against your provider's current rates rather than any figure in an article.

The ratio matters more than the absolute number. One is roughly linear in tenant count. The other is roughly linear in actual usage.

### Migrations and deployment reliability

A migration against a shared database is one DDL run. It commits or it does not.

A migration across N tenant databases is an orchestrated loop, and loops have partial failures. A connection drops at tenant 147. A lock wait times out on the one customer with a large table. Now your fleet is split across two schema versions and your application code has to tolerate both until you finish the cleanup. That tolerance is not free: it shapes how you write every migration from then on, and it is the single largest ongoing tax of the silo model.

### Noisy neighbors and performance isolation

In a pool, one tenant's unindexed query eats CPU, memory and disk I/O that every other tenant needs. Response times degrade for people who did nothing wrong, and the fix usually arrives after the complaints do.

Dedicated databases solve this, with a caveat worth saying out loud. Hard compute isolation only exists if those databases sit on independent instances. Pack fifty tenant databases onto one server to keep costs sane and you have rebuilt the noisy neighbor problem with extra steps.

### Backup and single-tenant restore

A customer deletes six months of records by accident. In a silo you restore that tenant's snapshot to a point in time and you are done.

In a pool you restore a full copy to a staging instance, extract that tenant's rows, and replay them into production while live traffic keeps writing. It works, but it is an engineering exercise every time, and it is worth building and rehearsing before you need it. The bridge model lands in between, since `pg_dump -n` can target a single schema.

### Cross-tenant analytics and billing

Aggregate questions are trivial in a pool. Active accounts this week, total events processed, storage consumed per plan: all of it is ordinary SQL, which means your billing system and your BI tooling can read straight from production replicas.

In a silo, every one of those questions needs an extraction pipeline and somewhere to land the data. Founders usually expect this bill to arrive with the first investor dashboard. It arrives earlier, the moment you price anything by usage.

VectorDatabase per tenant (silo)Shared database (pool)Schema per tenant (bridge)Data leakage riskNear zero, physical boundaryModerate, depends on enforcementLow, logical boundaryBaseline infrastructure costHigh, idle capacity per tenantLowest, maximum densityModerateSchema migrationsComplex, N-database loopsSimple, single DDL runHigh friction at scaleConnection poolingFragmented across targetsOne consolidated poolOne pool, shared databaseSingle-tenant restoreNative and simpleRequires extractionTargeted dump per schemaCross-tenant analyticsNeeds a pipelineNative SQLCross-schema joins![Illustration of a schema migration running against one shared database versus many tenant databases, with partial failures.](https://secure-files.proservers.ws/content/01m592bhgb8rejreqexjrxjmp9/3b463611ed61cd96debbebf82060e93c25b57006e3f5d9b0300f91c44656dfdd.jpg)

## Connection pooling constraints

Connections are the constraint that stops silo architectures quietly, long before storage or CPU does.

PostgreSQL uses a process per connection. Each backend carries meaningful fixed memory overhead, commonly put at roughly 5 to 10 MB before any per-query working memory. Multiply that by a handful of application workers for each of several hundred tenant databases and you exhaust server memory while your CPUs sit idle.

A pooler like PgBouncer is the standard answer, and it works far better for one database than for many. Pools are per target, so a silo fleet fragments its connections into hundreds of small pools that cannot lend capacity to each other. Tenant 12 sits at its ceiling while 199 other pools hold idle connections open. A shared database pools once, and every tenant draws from the same pile.

![Chart showing baseline infrastructure cost rising with tenant count for dedicated databases compared with a pooled shared database.](https://secure-files.proservers.ws/content/01m592bhgd2zdm3hzk40t336ep/1133a7cfe06fbf66db35afcede633eed1df1857655e178b8a2c099259d4c42f3.jpg)

## The modern default: shared schema with row-level security

Application-level scoping is necessary and not sufficient. An ORM global scope adds the tenant predicate to the queries it generates, which covers the common path and misses the interesting ones: raw SQL in a reporting endpoint, a background job running outside a request context, a join written without the tenant condition on the second table.

Row-level security moves the check into the database. You attach a policy to the table, the engine applies it to every query on that connection, and a query missing its tenant filter returns nothing rather than returning someone else's data. The AWS Database Blog walks through the pattern for multi-tenant isolation in PostgreSQL ([aws.amazon.com](https://aws.amazon.com/blogs/database/multi-tenant-data-isolation-with-postgresql-row-level-security/)).

Treat it as defense in depth. Keep the application scoping, keep sensible database roles, and let RLS catch what slips through.

Teams on PHP will run into `stancl/tenancy` and `spatie/laravel-multitenancy`, both of which can drive either multi-database or single-database tenancy. The packages are good. The decision they are configured to express is still the one described above, and single-database tenancy with row-level security remains the cheapest credible route through the early years.

## Building for migration readiness

Isolation is not a binary you choose on day one. It is a tier you sell later, and the work that makes that possible is schema design you do now for free.

AWcode runs as a studio, shipping in-house SaaS products such as Transporters.io and Dive Admin alongside client work. The operating constraint is small teams maintaining several products at once, which rules out any architecture that multiplies deployment work by tenant count before there is revenue to justify it. So we build the pool, and we build it so a tenant can be lifted out intact.

The rules are mechanical:

- Put `tenant_id` on every table, including join tables and lookup tables that feel like they belong to nobody.
- Use composite primary keys shaped as `(tenant_id, id)`, so extracting a tenant is a filtered copy rather than a key remapping exercise.
- Include the tenant column in foreign keys, so a cross-tenant reference cannot be written in the first place.
- Keep global auto-increment values out of tenant-visible identifiers, which also stops customers counting your growth.
- Run the test suite with a second tenant's data loaded and fail the build on any row that crosses the line.

Do that and promoting a customer to a dedicated database becomes a copy, a cutover and a connection string change. Skip it and the same promotion becomes a rewrite you will be quoting at the worst possible moment, halfway through an enterprise negotiation.

## When a dedicated database is genuinely the right call

The default is wrong in four situations, and they are easy to recognize.

1. Isolation is written into the contract or demanded by a regulator, and the deal does not close without it.
2. The system runs inside a customer's own network, on premise.
3. A handful of tenants carry data volumes or access patterns so far from the median that pooling them penalizes everyone else.
4. Data residency varies by customer, forcing separate regions.

The second case is routine in the industrial work AWcode does in Eastern Thailand. Factory software sits inside an isolated operational technology network by design, with the database on the plant floor rather than in a cloud region. Maintenance happens in scheduled uptime windows agreed with production, not when a deploy pipeline is ready. Nothing about that environment is negotiable for architectural elegance, and it is a useful reminder that "shared by default" is advice about cloud SaaS, not a law.

## A decision tree you can actually use

Work down the list and stop at the first match.

- Contracts or regulators require separate infrastructure per customer: database per tenant.
- The software runs inside a customer's plant or office network: database per tenant.
- Data residency differs by customer and you cannot satisfy it in one region: database per tenant for the affected customers only.
- Your model is hundreds or thousands of small accounts rather than dozens of large ones: shared database.
- Cross-tenant reporting or usage-based billing is a product feature: shared database.
- Nobody on the team runs infrastructure day to day: shared database.

If several lines apply, build the pool first and add an enterprise silo tier when a customer is willing to pay for it. That sequence is far easier than the reverse.

![Decision flowchart for choosing between a shared database and a database per tenant in a SaaS product.](https://secure-files.proservers.ws/content/01m592bhgfa79yj48fctfxx6e4/78abef6b35b232fb560bfe54832dfeb824746ae307ac687ecd9af28fd793d699.jpg)

## FAQ

**Does SOC 2 or HIPAA require a separate database per customer?**No. Both ask for evidence of adequate access control, audit logging and encryption. Neither dictates database topology. A shared database with enforced row-level policies and tested authorization satisfies them.

**Can you move one tenant out of a shared database later?**Yes, if the schema was designed for it. With `tenant_id` on every table and composite keys, extraction is a filtered copy followed by a cutover. Without those, expect key collisions and manual repair.

**How do you run migrations across hundreds of tenant databases safely?**Treat it as a deployment system, not a script. Track the schema version per database, run in batches, retry failures independently, and write application code that works against both the old and new schema for the duration of the rollout.

**Is row-level security enough on its own?**It is one layer. Pair it with least-privilege database roles, careful connection and session handling, and application-level authorization. The value of RLS is that it still holds when the other layers have a bad day.

**What breaks first in a schema-per-tenant setup?**Catalog performance and deployment time. As schema count grows, DDL, autovacuum and ORM introspection all slow down, and every migration multiplies by the number of schemas.

Architecture choices like this one set how your team works for the next several years, which is exactly why they should not be made in response to a single questionnaire. Start with the pool, enforce isolation in the engine, and keep the schema ready for extraction. If you want a second opinion on a multi-tenant design, or you are weighing cloud SaaS against software that has to live inside a plant network, [AWcode](https://awcode.com) builds both.

```json
{
  "title": "Database per Tenant vs Shared Database: How to Choose and How to Keep Your Options Open",
  "image_ideas": []
}

```

---

**How this post looks on the live site:** Rendered in a windowed news reader inside the AWcode OS desktop, alongside other posts.

---

**Canonical HTML version:** https://awcode.com/news/database-per-tenant-vs-shared-database

**About this document:** This is a plain-Markdown mirror of an AWcode.com page, served so that LLMs and agents can read the content without executing the site's retro-OS JavaScript UI. The HTML page at the canonical URL above carries the same content and is also fully indexable.

## Machine-readable

Resources for AI agents, LLMs and integrations:

- [https://awcode.com/llms.txt](https://awcode.com/llms.txt) — index of markdown mirrors
- [https://awcode.com/llms-full.txt](https://awcode.com/llms-full.txt) — every page + post concatenated
- [https://awcode.com/sitemap.xml](https://awcode.com/sitemap.xml) — full sitemap
- [https://awcode.com/robots.txt](https://awcode.com/robots.txt) — crawl + Content-Signal policy
- [https://awcode.com/ai.txt](https://awcode.com/ai.txt) — AI access policy
- [https://awcode.com/openapi.json](https://awcode.com/openapi.json) — OpenAPI 3.1 spec
- [https://awcode.com/.well-known/api-catalog](https://awcode.com/.well-known/api-catalog) — RFC 9264 / 9727 link set
- [https://awcode.com/.well-known/mcp.json](https://awcode.com/.well-known/mcp.json) — MCP discovery
- [https://awcode.com/mcp](https://awcode.com/mcp) — MCP server endpoint (POST JSON-RPC 2.0)
- [https://awcode.com/.well-known/agent-skills/index.json](https://awcode.com/.well-known/agent-skills/index.json) — Agent Skills index

### Public API — concrete examples

- [GET https://awcode.com/api/posts](https://awcode.com/api/posts) — list recent published posts
- [GET https://awcode.com/api/posts/how-startup-studios-de-risk-saas-builds-before-the-first-sprint](https://awcode.com/api/posts/how-startup-studios-de-risk-saas-builds-before-the-first-sprint) — fetch one post
- [GET https://awcode.com/api/pages/about](https://awcode.com/api/pages/about) — fetch the about page

### Markdown mirrors — concrete examples

- [https://awcode.com/index.md](https://awcode.com/index.md) — homepage
- [https://awcode.com/about.md](https://awcode.com/about.md) — about page
- [https://awcode.com/news/how-startup-studios-de-risk-saas-builds-before-the-first-sprint.md](https://awcode.com/news/how-startup-studios-de-risk-saas-builds-before-the-first-sprint.md) — one news post
