# Multi-Tenancy Design: [SaaS product or platform name]

> Design document for the multi-tenant architecture of a SaaS product:
> tenancy model (silo, pool, bridge) and trade-offs, tenant identification
> and context propagation, data isolation, noisy neighbour controls,
> tenant onboarding and offboarding, per-tenant configuration and feature
> flags, security and compliance including data residency, per-tenant
> observability, cost allocation, scaling and migration between tiers.
> Based on common SaaS architecture practice. Replace every `[bracketed]`
> value and every row marked *Example*; the examples follow a B2B project
> management product with about 400 business tenants.

<!--
Guidance: How to use this template
- Start with the tenant profile (section 1.3) and tenancy requirements
  (section 2). The right model depends on how many tenants you have, how
  different they are in size, and what isolation they will pay for.
- Make tenant context a first-class concept: every request, message, log
  line, metric and database row must carry or be scoped by a tenant ID.
  Retrofitting this later is the most expensive multi-tenancy mistake.
- Treat isolation as layered. Even in a pooled model, combine application
  checks with database-enforced controls such as row-level security.
- Design onboarding, offboarding and tier migration as automated,
  repeatable workflows from the start, not as runbooks for later.
-->

## Document control

<!-- Guidance: Record ownership, reviewers and approvals. Security and data protection sign-off is expected for any tenancy model change. -->

| Field | Value |
|---|---|
| Document ID | [MTD-001] |
| Product | [Name] |
| Version | [0.1] |
| Status | [Draft / In review / Approved / Superseded] |
| Author(s) | [@architect] |
| Reviewers | [@security-architect], [@data-protection-officer], [@sre-lead] |
| Last updated | YYYY-MM-DD |
| Classification | [Internal / Confidential] |
| Related | [System design], [Security architecture], [ADR on tenancy model], [Cost model] |

### Approvals

| Role | Name | Decision | Date |
|---|---|---|---|
| Chief architect | [@name] | [Approved / Changes requested] | YYYY-MM-DD |
| Security | [@name] | [Decision] | YYYY-MM-DD |
| Data protection | [@name] | [Decision] | YYYY-MM-DD |
| SRE / operations | [@name] | [Decision] | YYYY-MM-DD |

## Revision history

<!-- Guidance: Note design changes, especially to isolation or tenancy model. -->

| Version | Date | Author | Change summary |
|---|---|---|---|
| 0.1 | YYYY-MM-DD | [@name] | First draft |
| 0.2 | YYYY-MM-DD | [@name] | *Example:* Added bridge model for Enterprise tier after security review |
| 1.0 | YYYY-MM-DD | [@name] | *Example:* Approved |

## Contents

<!-- Guidance: Keep aligned with the numbered headings. -->

1. [Introduction](#1-introduction)
2. [Tenancy requirements](#2-tenancy-requirements)
3. [Tenancy model](#3-tenancy-model)
4. [Tenant identification and context propagation](#4-tenant-identification-and-context-propagation)
5. [Data isolation](#5-data-isolation)
6. [Noisy neighbour controls](#6-noisy-neighbour-controls)
7. [Tenant lifecycle](#7-tenant-lifecycle)
8. [Per-tenant configuration and feature flags](#8-per-tenant-configuration-and-feature-flags)
9. [Security and compliance](#9-security-and-compliance)
10. [Observability per tenant](#10-observability-per-tenant)
11. [Cost allocation](#11-cost-allocation)
12. [Scaling](#12-scaling)
13. [Migration between tiers](#13-migration-between-tiers)
14. [Risks and open questions](#14-risks-and-open-questions)
15. [Appendices](#15-appendices)

## 1. Introduction

<!-- Guidance: State what the product is, who the tenants are and what this document decides. -->

### 1.1 Purpose

This document defines how [Product] serves many customer organisations
(tenants) from shared infrastructure while keeping their data, performance
and configuration isolated. It is the reference for engineering, security
and operations decisions that depend on tenancy.

### 1.2 Definitions

| Term | Meaning |
|---|---|
| Tenant | A customer organisation with its own users, data and configuration |
| Tier | A commercial plan that determines isolation level, quotas and features |
| Tenant context | The verified tenant identity attached to a request or message |
| Silo | Dedicated resources for one tenant |
| Pool | Resources shared by many tenants |
| Bridge | A mix: some layers pooled, others siloed |

### 1.3 Tenant profile

| Tier | Tenants (now / in 2 years) | Users per tenant | Data per tenant | Price per month | Isolation expectation |
|---|---|---|---|---|---|
| Starter | *Example:* 300 / 2,000 | 5-50 | < 5 GB | USD 300 | Logical isolation |
| Business | *Example:* 90 / 400 | 50-1,000 | 5-200 GB | USD 4,500 | Logical isolation, higher quotas |
| Enterprise | *Example:* 10 / 40 | 1,000-20,000 | 200 GB-5 TB | Contract | Dedicated data store, region choice |

## 2. Tenancy requirements

<!-- Guidance: List the requirements that drive the tenancy model. Each needs an ID and a way to verify it. -->

| ID | Requirement | Priority | Verification |
|---|---|---|---|
| MT-01 | *Example:* No request can read or modify another tenant's data, even with an application bug in a query | Must | RLS tests; penetration test with cross-tenant attempts |
| MT-02 | *Example:* A single tenant cannot raise p95 latency for others by more than 20% | Must | Load test with one tenant at 10x normal traffic |
| MT-03 | *Example:* New Starter or Business tenant is usable within 2 minutes of sign-up | Must | Onboarding pipeline timing |
| MT-04 | *Example:* Enterprise tenants choose EU or US data residency | Must | Deployment configuration audit |
| MT-05 | *Example:* Tenant data fully deleted within 30 days of contract end | Must | Offboarding job evidence |
| MT-06 | *Example:* Cost per tenant reported monthly | Should | Finance report |
| MT-07 | *Example:* Move a tenant between tiers with < 15 min write downtime | Should | Migration rehearsal |
| [MT-nn] | [Requirement] | [Must / Should / Could] | [Method] |

## 3. Tenancy model

<!-- Guidance: Compare the three models across the dimensions that matter to you, then decide per tier and per layer. Most products end up with a bridge model. -->

### 3.1 Options

| Dimension | Silo (dedicated per tenant) | Pool (shared by all) | Bridge (mixed per layer) |
|---|---|---|---|
| Isolation strength | Strongest; separate resources | Logical only; depends on code and database controls | Strong where siloed, logical elsewhere |
| Cost per tenant | Highest; idle capacity per tenant | Lowest; high utilisation | Medium |
| Noisy neighbour exposure | None between tenants | Highest; needs quotas and limits | Limited to pooled layers |
| Operations at scale | Many deployments to upgrade and monitor | One deployment | Several, grouped by tier |
| Onboarding speed | Minutes to hours (provisioning) | Seconds (a row in a table) | Depends on tier |
| Per-tenant customisation | Easy | Hard | Medium |
| Compliance and residency | Simple to prove | Needs per-tenant controls and evidence | Siloed layer covers strict tenants |

### 3.2 Decision

| Layer | Starter | Business | Enterprise |
|---|---|---|---|
| Edge and API gateway | Pool | Pool | Pool (per-region) |
| Application services | Pool | Pool | Pool (per-region) |
| Primary database | *Example:* Pool: shared cluster, row-level security | Pool: shared cluster, row-level security | Silo: dedicated database per tenant |
| Search index | Pool, tenant-filtered | Pool, tenant-filtered | Silo index |
| Object storage | Pool, tenant prefix | Pool, tenant prefix | Silo bucket with tenant-managed key |
| Background workers | Pool, fair queues | Pool, fair queues | Pool with reserved concurrency |

Rationale: [summary and ADR link]. *Example:* pooled compute keeps
Starter unit cost under USD 20 per month; siloed data for Enterprise
meets contractual isolation and residency requirements without running
a separate application stack per customer (ADR-031).

## 4. Tenant identification and context propagation

<!-- Guidance: Explain how a request is mapped to a tenant, how that mapping is verified, and how tenant context travels through services, queues and jobs. Never trust a tenant ID sent by the client without verification. -->

### 4.1 Identification

| Channel | How the tenant is identified | Verification |
|---|---|---|
| Web app | *Example:* Subdomain `acme.example.app` resolves to tenant | Must match `tenant_id` claim in the session token |
| Public API | *Example:* API key or OAuth client bound to one tenant | Key lookup in tenant registry |
| Webhooks inbound | *Example:* Per-tenant signing secret | Signature check |
| Background jobs | Tenant ID in job envelope | Set by producer from verified context only |

### 4.2 Context propagation

Tenant context is resolved once at the edge and carried in signed tokens
and message headers. The sequence shows a request through to the
database.

```mermaid
sequenceDiagram
    participant C as Client
    participant G as API gateway
    participant I as Identity provider
    participant S as Project service
    participant DB as Database
    participant Q as Event broker
    C->>G: Request with access token
    G->>I: Validate token (cached keys)
    I-->>G: Claims incl. tenant_id, tier
    G->>S: Request + verified tenant context header
    S->>DB: SET app.tenant_id, then query
    DB-->>S: Rows filtered by row-level security
    S->>Q: Publish event with tenant_id header
    S-->>C: Response
```

Rules: [list]. *Example:* services reject requests without tenant
context; the context library sets the database session variable on every
connection checkout and clears it on return; events without a
`tenant_id` header are dead-lettered; logs and traces include
`tenant_id` automatically.

## 5. Data isolation

<!-- Guidance: Choose the isolation pattern per data store and show how it is enforced in more than one layer. Include backups, caches, search indexes and analytics copies, which are often forgotten. -->

| Pattern | Description | Pros | Cons | Used for |
|---|---|---|---|---|
| Database per tenant | Separate database or cluster | Strong isolation, easy restore per tenant | Cost, many schemas to migrate | *Example:* Enterprise |
| Schema per tenant | One database, one schema per tenant | Moderate isolation | Catalogue bloat beyond a few thousand schemas | Not used |
| Shared schema with tenant column | All rows carry `tenant_id`; enforced by row-level security | Cheapest, simple operations | Isolation depends on policy correctness | *Example:* Starter, Business |

Row-level security example for the pooled database (PostgreSQL syntax):

```sql
ALTER TABLE project ENABLE ROW LEVEL SECURITY;
ALTER TABLE project FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON project
  USING (tenant_id = current_setting('app.tenant_id')::uuid)
  WITH CHECK (tenant_id = current_setting('app.tenant_id')::uuid);
```

| Store | Isolation control | Second line of defence |
|---|---|---|
| *Example:* Primary database | Row-level security on every tenant table | Automated test that every table has a policy; application role cannot bypass RLS |
| *Example:* Cache | Keys prefixed `t:{tenant_id}:` | Cache wrapper refuses keys without prefix |
| *Example:* Object storage | Prefix per tenant; Enterprise bucket per tenant | Signed URLs scoped to prefix |
| *Example:* Search | Mandatory tenant filter added by query builder | Index alias per tenant for Enterprise |
| *Example:* Backups | Pool backups encrypted; Enterprise backups per tenant | Restore runbook per tenant tested quarterly |
| *Example:* Analytics copy | `tenant_id` retained; access by role | No cross-tenant data in customer-facing reports |

## 6. Noisy neighbour controls

<!-- Guidance: Define limits per tier at every shared layer and what happens when a tenant hits them. Include fair scheduling for background work, not just API rate limits. -->

| Control | Starter | Business | Enterprise | Behaviour when exceeded |
|---|---|---|---|---|
| API rate limit (token bucket) | *Example:* 20 req/s, burst 40 | 100 req/s, burst 200 | 500 req/s, burst 1,000 | HTTP 429 with `Retry-After` |
| Concurrent background jobs | 2 | 10 | 50 reserved | Jobs queue; fair scheduler |
| Storage quota | 5 GB | 200 GB | Contract | Uploads rejected; admin notified at 80% |
| Search queries per minute | 300 | 3,000 | 20,000 | 429; cached results served |
| Export size per job | 1 GB | 20 GB | 200 GB | Split into parts |

Fair scheduling: [describe]. *Example:* background workers pull from
per-tenant queues in weighted round-robin, so a tenant importing a
million rows cannot delay notifications for others. Database statement
timeout is 5 s for pooled tenants. Heavy tenants are detected when they
use more than 5% of pooled capacity for 15 minutes and are flagged for
review or tier change.

## 7. Tenant lifecycle

<!-- Guidance: Automate onboarding and offboarding end to end. Offboarding must cover every copy of data, including backups and analytics, with evidence of deletion. -->

The state diagram shows the lifecycle of a tenant.

```mermaid
stateDiagram-v2
    [*] --> Provisioning
    Provisioning --> Active: Resources ready
    Active --> Suspended: Non-payment or abuse
    Suspended --> Active: Resolved
    Active --> Migrating: Tier change
    Migrating --> Active: Cut-over complete
    Active --> Offboarding: Contract ended
    Suspended --> Offboarding: Grace period ended
    Offboarding --> Deleted: Data purged and verified
    Deleted --> [*]
```

### 7.1 Onboarding

| Step | Action | Automated by | Target time |
|---|---|---|---|
| 1 | *Example:* Create tenant record, tier, region | Tenant service | < 1 s |
| 2 | Create identity provider organisation and admin user | Onboarding workflow | < 10 s |
| 3 | Pool: seed default data. Silo: provision database from template | Workflow; infrastructure as code for silo | Pool < 30 s; silo < 20 min |
| 4 | Apply tier quotas and feature flags | Configuration service | < 5 s |
| 5 | Send welcome email; register billing | Billing integration | < 1 min |

### 7.2 Offboarding

| Step | Action | Evidence |
|---|---|---|
| 1 | *Example:* Offer data export for 30 days | Export job ID |
| 2 | Disable logins and API keys | Audit log entry |
| 3 | Delete rows (pool) or drop database (silo) | Deletion job report |
| 4 | Delete object storage prefix or bucket; search index entries | Storage inventory diff |
| 5 | Backups expire under retention policy; Enterprise keys destroyed | Key destruction record |
| 6 | Issue deletion certificate to customer | Signed certificate |

## 8. Per-tenant configuration and feature flags

<!-- Guidance: Separate tier entitlements, tenant settings and release flags. Keep configuration versioned and auditable. -->

| Type | Example | Stored in | Changed by |
|---|---|---|---|
| Tier entitlement | *Example:* SSO, audit log export, API limits | Plan catalogue | Billing change only |
| Tenant setting | *Example:* Working days, logo, default language | Tenant settings table | Tenant admin |
| Release flag | *Example:* `new-gantt-view` on for 10% of tenants | Feature flag service | Product team |
| Operational override | *Example:* Raised rate limit during migration | Configuration service with expiry | SRE, with ticket |

Rules: flags are evaluated with tenant ID and tier as attributes; every
override has an owner and an expiry; entitlement checks run on the
server, never only in the client.

## 9. Security and compliance

<!-- Guidance: Show how isolation is tested and how residency and regulatory duties are met per tenant. -->

| Topic | Design |
|---|---|
| Cross-tenant access testing | *Example:* Automated suite tries every endpoint with a token of tenant A and IDs of tenant B; runs in CI |
| Encryption | At rest for all stores; Enterprise may supply its own key for its silo |
| Data residency | *Example:* EU and US stacks; tenant region fixed at onboarding; pooled services deployed per region; no replication across regions |
| Administrative access | Support staff access a tenant only with a ticket and time-limited grant, logged and visible to the tenant admin |
| Audit logging | Per-tenant audit log of admin and data export actions; retention per tier |
| Certifications and contracts | [e.g. SOC 2 report, ISO/IEC 27001 certificate, DPA per tenant] |
| Sub-processors | Listed per region; tenants notified of changes |

## 10. Observability per tenant

<!-- Guidance: Make it possible to answer "is this tenant healthy and what is it costing us" without guesswork. Watch label cardinality when adding tenant IDs to metrics. -->

| Signal | Tenant dimension | Notes |
|---|---|---|
| Logs | `tenant_id` field on every line | Searchable per tenant; masked personal data |
| Traces | `tenant_id` span attribute | Sampling raised for Enterprise |
| Metrics | *Example:* Tier label on all metrics; `tenant_id` label only on a small set of usage metrics | Avoids high-cardinality series |
| Usage meter | Requests, storage, job seconds per tenant per hour | Feeds cost allocation and billing |
| SLO reporting | Availability and latency per tier; per tenant for Enterprise | Monthly report to Enterprise tenants |

## 11. Cost allocation

<!-- Guidance: Allocate shared costs by a usage driver that tracks real consumption, add dedicated costs, and spread fixed platform costs. Compare with price to find unprofitable tiers or tenants. -->

The monthly cost attributed to tenant $t$ is

$$
C_t = D_t + C_{\text{pool}} \cdot \frac{u_t}{\sum_{k=1}^{N} u_k} + \frac{C_{\text{fixed}}}{N}
$$

where $D_t$ is the cost of resources dedicated to the tenant,
$C_{\text{pool}}$ is the cost of pooled resources, $u_t$ is the tenant's
usage units (for example weighted request and storage units), $N$ is the
number of tenants and $C_{\text{fixed}}$ is shared platform cost that
does not vary with usage.

*Example* for a Business tenant: $D_t = 500$, $C_{\text{pool}} = 40{,}000$,
$u_t = 1{,}200$ of $\sum u_k = 20{,}000$ units, $C_{\text{fixed}} = 12{,}000$
and $N = 400$:
$C_t = 500 + 40{,}000 \times 0.06 + 30 = 500 + 2{,}400 + 30 = 2{,}930$ USD.
At a price of USD 4,500 the gross margin is $(4{,}500 - 2{,}930) / 4{,}500 \approx 34.9\%$.

| Tier | Tenants | Average $C_t$ | Average price | Margin |
|---|---|---|---|---|
| Starter | *Example:* 300 | 45 | 300 | 85% |
| Business | 90 | 2,100 | 4,500 | 53% |
| Enterprise | 10 | 21,000 | 38,000 | 45% |
| [Tier] | [n] | [Cost] | [Price] | [%] |

## 12. Scaling

<!-- Guidance: Describe how each layer scales with more tenants and with bigger tenants. Define cell or shard boundaries and when a new one is added. -->

| Layer | Scaling approach | Trigger | Limit per unit |
|---|---|---|---|
| Application services | *Example:* Horizontal autoscaling | CPU > 60% or p95 > 300 ms | n/a |
| Pooled database | *Example:* Shards (cells) of up to 500 tenants each; tenant-to-cell map in registry | Cell at 70% CPU or 2 TB | 500 tenants or 2 TB |
| Siloed databases | One per Enterprise tenant; sized per contract | Tenant growth | Per tenant |
| Event broker | Partitions keyed by tenant ID | Consumer lag > 30 s | [n] partitions |

Cell placement: new tenants go to the least-loaded cell in their region.
A cell is a blast-radius boundary: an incident in one cell affects at
most its tenants.

## 13. Migration between tiers

<!-- Guidance: Upgrades from pool to silo (and the reverse) happen. Design them as a repeatable, tested workflow with a clear cut-over and rollback. -->

| Step | Pool to silo (upgrade to Enterprise) | Rollback |
|---|---|---|
| 1 | *Example:* Provision dedicated database in tenant's region | Delete new database |
| 2 | Copy tenant rows using logical replication filtered by `tenant_id` | Stop replication |
| 3 | Verify counts and checksums per table | n/a |
| 4 | Put tenant in read-only mode (target < 15 min) | Re-enable writes on pool |
| 5 | Final sync; switch tenant-to-database mapping in registry | Switch mapping back |
| 6 | Re-enable writes; monitor for 24 h | Switch back within 24 h if needed |
| 7 | Delete tenant rows from pool after 14 days | n/a |

Downgrades (silo to pool) follow the same steps in reverse and are
allowed only at contract renewal.

## 14. Risks and open questions

<!-- Guidance: Record risks specific to tenancy and open design questions with owners. -->

| ID | Item | Type | Mitigation or next step | Owner |
|---|---|---|---|---|
| R-01 | *Example:* A new table is added without a row-level security policy | Risk | CI check fails if any table with `tenant_id` lacks a policy | [@name] |
| R-02 | *Example:* One very large Business tenant dominates a cell | Risk | Heavy-tenant detection; move to own cell | [@name] |
| OQ-01 | *Example:* Should Enterprise tenants get dedicated compute in addition to data? | Question | Decide after first three Enterprise contracts | [@name] |
| [ID] | [Item] | [Risk / Question] | [Action] | [@name] |

## 15. Appendices

<!-- Guidance: Keep references and supporting material here. -->

### 15.1 References

| Reference | Use |
|---|---|
| [Cloud provider SaaS architecture guidance] | Silo, pool and bridge vocabulary |
| PostgreSQL documentation, row security policies | Section 5 example |
| [Internal ADR on tenancy model] | Section 3 decision |

### 15.2 Tenant registry fields

| Field | Description |
|---|---|
| `tenant_id` | UUID, immutable |
| `tier` | Starter, Business or Enterprise |
| `region` | Residency region, fixed at onboarding |
| `cell_id` | Pooled cell, or null for silo |
| `database_ref` | Connection reference for silo tenants |
| `state` | Lifecycle state from section 7 |

## Standard and licence note

Based on common SaaS architecture practice for multi-tenant systems,
including the widely used silo, pool and bridge tenancy models. Original
text written for this collection; examples, limits and figures are
illustrative. Template licence: MIT.
