---
term: 'Multi-Tenant Database Architecture'
seoTitle: 'Multi-Tenant Database Architecture: The 3 Patterns Compared'
headline: 'What is Multi-Tenant Database Architecture?'
slug: multi-tenant-database-architecture
category: database
shortDefinition: 'Multi-tenant database architecture is a design for storing many customers in one data tier, isolated by row, schema, or separate database.'
relatedTerms:
  - tenant-isolation
  - multi-tenant-cloud-hosting
  - row-level-security
  - data-layer-vs-application-layer-security
  - database-schema
contrastsWith:
  - multi-tenant-cloud-hosting
aboutTerms:
  - 'Shared Schema (Pool)'
  - 'Schema-per-Tenant (Bridge)'
  - 'Database-per-Tenant (Silo)'
faq:
  - question: 'What are the three multi-tenant database patterns?'
    answer: 'Shared schema — one set of tables, every row carrying a tenant identifier; schema-per-tenant — one database, a separate namespace of tables per customer; and database-per-tenant — full physical separation. Cloud architecture guides name the same three pool, bridge, and silo. Real systems increasingly mix them, tiering tenants across patterns by size and compliance needs.'
  - question: 'Shared database or database-per-tenant — which should I choose?'
    answer: 'The consensus default is shared schema unless something forces isolation: regulatory requirements, contractual data separation, heavy per-tenant customization, or single tenants big enough to need their own resources. Shared schema maximizes density and minimizes operations; per-tenant databases maximize isolation and multiply everything else — migrations, backups, connections, cost.'
  - question: 'How many tenants can each pattern handle?'
    answer: 'The practical ceilings from production experience: database-per-tenant runs comfortably to tens or low hundreds of tenants before operations dominate; schema-per-tenant reaches the hundreds to around a thousand before metadata and migration orchestration strain; shared schema scales to thousands or millions of tenants, and sharding the shared schema by tenant extends it effectively without limit.'
  - question: 'How do you prevent one tenant seeing another tenant''s data?'
    answer: 'Defense in depth, never a WHERE clause alone. Application-level tenant scoping (middleware or ORM filters) is the first layer; database-enforced row-level security is the second — policies that filter every query by the current tenant regardless of what the application forgot. In shared-schema designs, a single missed filter is a cross-tenant breach, which is why the database itself should enforce the boundary.'
  - question: 'How do schema migrations work across the patterns?'
    answer: 'In shared schema, one migration updates every tenant at once — simple, with a blast radius to match. In schema- or database-per-tenant, the same migration must run once per tenant: hundreds of executions needing orchestration, version tracking, and drift detection, since a failed run leaves one tenant on an older schema. Migration tooling is the hidden tax of isolation.'
  - question: 'Can you restore a single tenant''s data?'
    answer: 'In database-per-tenant, trivially — restore that database to any point in time, touching nobody else. In shared schema it is genuinely hard: the backup contains everyone, so you restore to a side instance and selectively copy the tenant''s rows back. Per-tenant restore is one of the strongest practical arguments enterprises make for isolation tiers.'
  - question: 'How should you index a shared-schema multi-tenant database?'
    answer: 'Put the tenant identifier on every table — even where it looks redundant — and lead your composite indexes with it, so every query pattern becomes tenant-first. Making the tenant column the leading part of the primary key also pre-arranges the data for sharding by tenant later, which is the standard growth path.'
  - question: 'What is sharding by tenant?'
    answer: 'Splitting a shared-schema design across multiple databases, with all of one tenant''s rows living on exactly one shard and a catalog mapping tenants to shards. It preserves shared-schema density while capping any single database''s size, and enables moving hot tenants to quieter shards. The price is the catalog, rebalancing tooling, and the loss of trivial cross-tenant queries.'
codeLanguages: [javascript, dart, swift, kotlin]
externalAuthorities:
  - name: 'PostgreSQL Row Security Policies'
    url: 'https://www.postgresql.org/docs/current/ddl-rowsecurity.html'
  - name: 'Multitenancy (Wikipedia)'
    url: 'https://en.wikipedia.org/wiki/Multitenancy'
  - name: 'Citus — distributed PostgreSQL (open source)'
    url: 'https://github.com/citusdata/citus'
  - name: 'Row-Level Security and Multi-Tenancy on MongoDB (Back4app Engineering)'
    url: 'https://www.back4app.com/multi-tenant-mongodb-row-level-security'
cta:
  title: 'Tenant isolation at the data layer'
  text: 'Back4app enforces per-tenant boundaries where they belong: ACLs on every object, roles per tenant, class-level permissions on every schema — checked by the platform on every request, on both the APIs and the dashboard. No WHERE clause to forget.'
  linkText: 'Start Free'
  linkUrl: 'https://www.back4app.com/signup'
author: 'Back4app Engineering'
publishedDate: '2026-07-24'
translationKey: multi-tenant-database-architecture
---

**Multi-tenant database architecture is a design for storing many customers in one data tier, isolated by row, schema, or separate database.** Those three isolation levels are the entire decision space — everything else in this topic is consequences: how migrations run, what restore costs, where the noisy neighbor lives, and how far each pattern scales.

## Key takeaways

| Question | Answer |
| --- | --- |
| The three patterns | Shared schema (tenant ID per row) · schema-per-tenant · database-per-tenant |
| Cloud vocabulary | The same three: pool · bridge · silo |
| Default choice | Shared schema, unless compliance or scale forces isolation |
| Practical ceilings | Silo: ~100s of tenants · bridge: ~1,000 · pool: millions (sharded: unlimited) |
| The iron rule | Enforce isolation in the database, not in every query |

## The three patterns, in SQL

```sql
-- Pattern 1 · Shared schema ("pool"): one set of tables, tenant on every row
CREATE TABLE invoices (
  tenant_id uuid   NOT NULL,
  id        bigint GENERATED ALWAYS AS IDENTITY,
  total     numeric(10,2),
  PRIMARY KEY (tenant_id, id)      -- tenant leads: index- and shard-ready
);
ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_rows ON invoices
  USING (tenant_id = current_setting('app.tenant')::uuid);

-- Pattern 2 · Schema per tenant ("bridge"): one database, a namespace each
CREATE SCHEMA tenant_acme;          -- same tables, repeated per tenant

-- Pattern 3 · Database per tenant ("silo"): full physical separation
CREATE DATABASE tenant_acme;        -- strongest isolation; N of everything
```

On a managed backend the same guarantee is expressed without SQL: access control lives on the data itself, so isolation holds on every path — API, dashboard, or SDK:

**JavaScript:**

```javascript
// JavaScript / Node.js — Back4app JS SDK
// The same query for every tenant — ACLs scope results server-side
const query = new Parse.Query('Invoice');
const invoices = await query.find({ sessionToken: user.getSessionToken() });
// Only rows this tenant's role can read come back. No WHERE clause to forget.
```

**Flutter:**

```dart
// Flutter / Dart — Back4app Flutter SDK
// The same query for every tenant — ACLs scope results server-side
final query = QueryBuilder<ParseObject>(ParseObject('Invoice'));
final response = await query.query();
// The session's role decides which rows exist, before results leave the server
if (response.success) {
  print('${response.results?.length} invoices visible to this tenant');
}
```

**Swift:**

```swift
// iOS / Swift — Back4app Swift SDK
// The same query for every tenant — ACLs scope results server-side
let query = Invoice.query()
query.find { result in
  if case .success(let invoices) = result {
    print("\(invoices.count) invoices visible to this tenant")
  }
}
```

**Kotlin:**

```kotlin
// Android / Kotlin — Back4app Android SDK
// The same query for every tenant — ACLs scope results server-side
val query = ParseQuery.getQuery<ParseObject>("Invoice")
query.findInBackground { invoices, e ->
  if (e == null) {
    Log.d("Billing", "${invoices.size} invoices visible to this tenant")
  }
}
```

## Shared schema vs. schema-per-tenant vs. database-per-tenant

```mermaid
flowchart LR
  accTitle: The three multi-tenant database patterns
  accDescr: Shared schema keeps all tenants in one set of tables separated by a tenant ID; schema-per-tenant gives each tenant its own namespace inside one database; database-per-tenant gives each tenant a fully separate database.
  subgraph P["Shared schema · pool"]
    p1["One set of tables<br/>tenant_id on every row"]
  end
  subgraph B["Schema-per-tenant · bridge"]
    b1["One database<br/>a namespace per tenant"]
  end
  subgraph S["Database-per-tenant · silo"]
    s1["A database per tenant<br/>full separation"]
  end
  P --> B --> S
```

| Dimension | Shared schema | Schema-per-tenant | Database-per-tenant |
| --- | --- | --- | --- |
| Isolation | Logical, per row | Namespace | Physical |
| Tenant ceiling | Millions | ~Hundreds–1,000 | Tens–low hundreds |
| Cost per tenant | Lowest | Middle | Highest |
| Migrations | Run once, hit everyone | Run × N, orchestrated | Run × N, orchestrated |
| Per-tenant restore | Hard (selective copy) | Moderate | Trivial (restore one DB) |
| Noisy neighbor | Most exposed | Contained somewhat | Eliminated |
| Per-tenant customization | Hardest | Possible per schema | Easiest |
| Onboarding a tenant | Insert a row | Create a schema | Provision a database |

## The details that bite

- **Indexing is tenant-first.** `tenant_id` goes on every table — even where joins make it look redundant — and leads every composite index: `(tenant_id, created_at)`, not the reverse. Every real query is scoped to a tenant; the indexes should be too.
- **Migrations multiply with isolation.** The silo's flexibility costs an orchestration layer: version tracking per tenant, retry logic, drift detection. Teams underestimate this more than any other line in the table.
- **Row-level security has operational fine print.** [Policies](https://www.postgresql.org/docs/current/ddl-rowsecurity.html) key off per-connection settings, which interact with connection pooling modes — set the tenant per transaction, and test the pooler. Also audit which roles bypass RLS; superuser paths are the classic hole.
- **Connection arithmetic.** A pool per tenant-database exhausts connections fast; shared-schema designs share one pool — another quiet advantage of the pool pattern that surfaces only at scale.
- **Restore asymmetry drives tiering.** "Can you restore just us to yesterday?" is an enterprise contract question; if the answer must be yes, that tenant belongs in a silo — which leads directly to the hybrid.

## Scaling and the hybrid end-state

The growth path for the pool pattern is **sharding by tenant**: several shared-schema databases, each holding a slice of tenants, with a catalog mapping tenant → shard (open-source [Citus](https://github.com/citusdata/citus) built this into PostgreSQL, including moving a hot tenant to its own node). Combined with tiering, this yields the architecture most mature SaaS converges on: the free tier pooled, mid-size tenants pooled across shards, and the few regulated or enormous tenants siloed — **with `tenant_id` kept in every schema everywhere**, so any tenant can move between tiers without a remodel. Tenant mobility is the property to design for on day one; it is nearly impossible to retrofit.

## Common use cases

- **B2B SaaS.** The defining case: every workspace, org, or team in your product is a tenant in one of these patterns.
- **Freemium products at scale.** Thousands of small free tenants pooled at near-zero marginal cost — the economics that make free tiers possible.
- **Regulated verticals.** Health, finance, and legal customers contractually requiring silos — served from the same codebase via the hybrid.
- **Agencies and platforms.** One application serving many client organizations, each with its own boundary.
- **Internal multi-team platforms.** Departments as tenants on shared tooling — same patterns, friendlier threat model.

## Which pattern should you choose? A decision matrix

| Choose shared schema when… | Choose schema-per-tenant when… | Choose database-per-tenant when… |
| --- | --- | --- |
| Tenants are many and small | Tenants number in the hundreds | Tenants are few and large |
| Cost per tenant must approach zero | Moderate isolation is worth some ops | Compliance demands physical separation |
| One migration should update everyone | Per-tenant schema tweaks are needed | Per-tenant restore is contractual |
| Self-serve signup, instant onboarding | Tenants onboard at human speed | Each tenant justifies provisioning |
| You'll shard when you grow | You'll cap tenant count | You'll automate migrations × N |

And the meta-answer: pick per *tier*, not per company — hybrid designs put each tenant in the cheapest pattern that satisfies its requirements, and move them as requirements change.

## Limitations and trade-offs

- **Shared schema:** the strongest need for database-enforced isolation — one missed filter is a breach, which is why RLS or data-layer ACLs are non-negotiable, not optional hardening.
- **Schema-per-tenant:** the awkward middle — migration orchestration like the silo, isolation weaker than the silo, and database metadata limits arriving surprisingly early.
- **Database-per-tenant:** everything × N — migrations, backups, monitoring, connections, cost — plus cross-tenant analytics becoming a data-engineering project.
- **All three:** tenant context invades everything (queries, caches, jobs, logs), and cross-pattern moves are expensive without day-one ID discipline: globally unique identifiers and `tenant_id` columns everywhere, even in silos.

## Multi-tenant data on Back4app

Back4app is an open-source Backend-as-a-Service (BaaS) platform that combines a managed database, auto-generated REST and GraphQL APIs, authentication, file storage, and Cloud Code serverless functions. Its answer to this article's iron rule — enforce isolation in the database — is per-object ACLs, per-tenant roles, and class-level permissions, checked by the platform on every request from every surface, as shown in the code tabs above. That is the shared-schema pattern with the WHERE-clause risk removed; the [engineering deep-dive on row-level security](https://www.back4app.com/multi-tenant-mongodb-row-level-security) walks the full pattern on a document database, and the [hosting-level view](/glossary/multi-tenant-cloud-hosting/) of the same topic covers the infrastructure tier above it.
