Designing a Multi-Tenant SaaS System
Multi-tenancy — serving many customer organizations from shared software and infrastructure — is what makes SaaS economical. It also concentrates risk: one missing filter leaks one customer's data to another, one heavy customer slows everyone down, and one bad deploy affects every tenant at once. Tenancy decisions touch almost every part of the system: the data model, authorization, identity, billing, deployment, backups, observability, and pricing.
This page works through the design of one realistic system end to end: Trackly, a project-tracking SaaS for teams, serving about 5,000 customer organizations, from small startups on a free plan to enterprises that require SSO, audit logs, and data residency in the EU. Each section makes a decision and explains the tradeoff, the way you would in a design review or a system design interview.
TL;DR
- Estimate first: Trackly needs roughly 1,000 requests per second at peak and around 100 GB of relational data — a pooled design fits comfortably.
- Use a pooled model by default with tenant IDs on every row, row-level security, and tenant-scoped keys; offer silo deployments only where contracts require it.
- Resolve tenant context once per request from the authenticated session, and propagate it to the database, logs, and jobs.
- Model organizations, memberships, and roles; support SAML/OIDC SSO and SCIM per tenant for enterprise plans.
- Separate plans and entitlements from code paths; meter usage for billing; enforce per-tenant quotas.
- Deploy in cells per region for blast-radius control and data residency; design per-tenant export, restore, and deletion from day one.
Quick Example
The core of the data model: tenant-scoped keys, foreign keys that cannot cross tenants, and row-level security as a second line of defense (PostgreSQL):
Back-of-envelope estimates that drive the design:
Requirements
Core Concepts
The core design decisions, in the order a design review would take them.
Tenancy Model
The estimates show a pooled design is far within the capacity of one well-tuned PostgreSQL primary with replicas, so pooling wins on cost and simplicity. The model keeps the door open: because every key includes tenant_id and tenants map to cells, a tenant can move to a dedicated cell later. See Multi-Tenant Application Design.
Tenant Context and Isolation
Tenant context comes from the authenticated session, never from a request parameter the client controls. Middleware resolves the tenant once, checks membership, and sets it for the database transaction, logs, traces, caches (keys prefixed by tenant), search indexes (tenant filter on every query), and background jobs (tenant ID in every message). Row-level security catches application bugs that forget a filter. Test isolation explicitly with cross-tenant access tests. See Row-Level Security and Tenant Isolation and Noisy-Neighbor Testing.
Identity and Authorization
Users can belong to several organizations, so the model is users ↔ memberships ↔ organizations, with roles (owner, admin, member, guest) per membership and project-level permissions where needed. Enterprise tenants configure SAML or OIDC SSO with verified domains, enforce SSO-only login, and provision users with SCIM. Every authorization check includes the tenant. See Authorization Models: RBAC, ABAC, and ReBAC and SSO.
Plans, Entitlements, and Billing
Code checks entitlements ("can this tenant use audit logs?", "how many automation runs remain?"), not plan names, so plans can change without code changes. A billing provider handles subscriptions, invoices, and taxes; Trackly records usage events (automation runs, storage) idempotently and reports them for metered billing. Downgrades and failed payments follow defined grace periods rather than immediate lockouts.
Noisy Neighbors and Fairness
Per-tenant rate limits and quotas at the API gateway, fair queuing for background jobs (no tenant can fill the queue), limits on expensive operations (exports, bulk imports), and statement timeouts protect shared resources. The largest tenants get monitoring of their share of load, and can be moved to their own cell if they outgrow the pool. See Quotas, Fairness, and Backpressure.
Cells, Regions, and Residency
Trackly runs in cells: complete, independent stacks (application, database, queues) each serving a set of tenants. Cells limit the blast radius of failures and deploys, and give a natural unit for scaling and for data residency — EU tenants live in EU cells. A thin global layer holds the tenant directory (which cell hosts each tenant), routes requests, and handles login and billing. Deploys roll out cell by cell. See Multi-Region Application Architecture.
Data Lifecycle
- Backups are per cell; per-tenant restore replays a tenant's rows from a backup into a staging database and copies them back, a procedure rehearsed regularly.
- Export produces a complete, documented archive for a tenant on request.
- Deletion removes a tenant's rows, files, search documents, and caches, records proof, and lets backups age out within the retention period.
- Audit logs record administrative and security-relevant actions per tenant.
Observability and Cost
Tag logs, metrics, and traces with tenant ID (careful with metric cardinality — use tenant tiers or top-N for metrics, full IDs in logs and traces). Track cost to serve per tenant from request volume, storage, and job usage, which feeds pricing and plan limits. See Product Economics and Pricing.
Best Practices
Make Tenant ID Part of Every Key
Composite keys and tenant-scoped foreign keys make cross-tenant references structurally impossible.
Derive Tenant Context From Authentication
Never trust a tenant identifier sent by the client without checking membership.
Defend in Depth
Combine application filters, row-level security, tenant-prefixed caches, and automated cross-tenant tests.
Design the Tenant Lifecycle Up Front
Onboarding, plan changes, export, restore, deletion, and moving tenants between cells are features, not afterthoughts.
Common Mistakes
Tenant ID From the Request
Unscoped Background Jobs and Caches
No Per-Tenant Limits
FAQ
Should a SaaS product use a shared database or a database per tenant?
Most start with a shared (pooled) database with tenant IDs on every row, row-level security, and tenant-scoped keys, because it is cheapest and simplest. Separate databases or stacks suit very large tenants or contractual isolation requirements.
How do I prevent data leaks between tenants?
Resolve tenant context from authentication, include tenant IDs in keys and every query, enforce row-level security in the database, scope caches and search indexes by tenant, and run automated cross-tenant access tests.
What is a cell-based architecture?
A design in which the system is divided into independent, complete copies of the stack, each serving a subset of tenants, which limits the blast radius of failures and deploys and provides a unit for scaling and data residency.
How do entitlements differ from plans?
Plans are commercial packages; entitlements are the specific features and limits a tenant has. Checking entitlements in code lets you change plans, run promotions, and grant exceptions without code changes.
How do I handle a tenant that outgrows the shared infrastructure?
Monitor per-tenant load, protect others with quotas, and design keys and routing so the tenant can be moved to a dedicated cell or database without changing application logic.
Related Topics
- Architecture — Designing software systems
- Multi-Tenant Application Design — Tenancy patterns in application code
- Row-Level Security — Database-enforced isolation
- Tenant Isolation and Noisy-Neighbor Testing — Proving isolation holds
- Back-of-the-Envelope Estimation — Sizing systems before designing them
- System Design — Designing large-scale systems