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

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

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

References