Learning lens
Start with judgment, not a universal prescription.
This page is a learning model for understanding the capability. It does not claim exact production internals, exact costs, or an infinite-scale architecture.
Evidence
Conceptual pattern
0->1 default
Start with clear tenant boundaries, boring data ownership, and reversible choices before optimizing for platform scale.
Cost drivers
Watch for breakpoints
- -large tenants affect small tenants
- -support teams need self-serve controls
- -migration work becomes tenant-specific
Learn first
Concepts behind this architecture
Replication
Copy data across nodes to improve availability, read scale, and disaster recovery.
Sharding
Split a dataset across machines so storage and traffic can scale horizontally.
Strong Consistency
Make reads reflect the latest committed write for correctness-critical data.
Write-Through Cache
Keep cache and database updated together so reads can trust the cached value.
The Problem
SaaS applications serve multiple customers (tenants) from shared infrastructure. The challenge is balancing cost efficiency (shared resources) with isolation (one tenant’s data, load, or failures shouldn’t affect others). Multi-tenancy touches every layer — database, compute, caching, queues, and storage. You must decide how much isolation each tier of customer gets, how to route requests to the right tenant context, and how to handle tenants that outgrow the shared model.
Architectural Approaches
Shared Database, Shared Schema
All tenants share the same database and tables. A tenant_id column on every table discriminates data. Application code adds WHERE tenant_id = ? to every query. Simplest and most cost-effective approach, used by most early-stage SaaS.
Pros
- +Lowest infrastructure cost — one database for all tenants
- +Simplest to deploy and manage
- +Easy cross-tenant analytics and reporting
- +Database migrations apply to all tenants at once
Cons
- −Risk of data leaks if tenant_id filter is missed (security critical)
- −Noisy neighbor — one tenant's heavy query affects all others
- −Hard to meet data residency requirements per tenant
- −Schema customization per tenant is difficult
Shared Database, Separate Schemas
Each tenant gets their own database schema (PostgreSQL schemas, MySQL databases) within a shared database server. Provides logical isolation while sharing compute resources. Tenant routing happens at the connection/schema level.
Pros
- +Logical data isolation — harder to accidentally leak data
- +Per-tenant schema customization is possible
- +Can use database-level access controls per schema
- +Easier to export/delete a single tenant's data (GDPR)
Cons
- −Schema migrations must run per-tenant — can be slow with many tenants
- −Connection pooling is more complex (pool per schema or dynamic switching)
- −Still shares compute — noisy neighbor at the DB server level
- −Operational overhead grows with tenant count
Database per Tenant
Each tenant gets a completely separate database instance. Maximum isolation at maximum cost. Reserved for enterprise tiers, regulated industries, or tenants with data residency requirements.
Pros
- +Complete data isolation — strongest security boundary
- +Per-tenant performance tuning and scaling
- +Easy to meet data residency (place DB in specific region)
- +Tenant migration and backup/restore is straightforward
Cons
- −Highest infrastructure cost
- −Operational complexity grows linearly with tenants
- −Cross-tenant queries and analytics require federation
- −Connection management across hundreds of databases is challenging
Architecture
Key Components
Tenant Router Middleware
Extracts tenant context from the request (subdomain, custom domain, JWT claim, or API key). Sets tenant_id in the request context so all downstream code operates in the correct tenant scope. This is the most security-critical component — a bug here causes cross-tenant data leaks.
Tenant-Aware Data Layer
Wraps database queries to automatically inject tenant_id filters. In shared-schema mode, uses row-level security (Postgres RLS) or ORM scoping. In separate-schema mode, switches the connection’s search_path. In DB-per-tenant mode, selects the correct connection from a pool registry.
Tenant Configuration Store
Stores per-tenant settings: plan tier, feature flags, rate limits, custom branding, SSO configuration, and data residency requirements. Cached in Redis with tenant_id as the key prefix. Loaded on first request and refreshed periodically.
Resource Quotas & Rate Limiter
Enforces per-tenant limits on API calls, storage, compute, and bandwidth. Prevents noisy neighbor scenarios by throttling tenants that exceed their allocation. Uses token bucket or sliding window algorithms keyed by tenant_id.
Tenant Provisioning Service
Automates new tenant setup: creates database schemas/instances, seeds default data, configures DNS entries, and sets initial quotas. Runs as a background job triggered by signup or plan upgrade.
Tenant Isolation Monitor
Monitors for cross-tenant data access violations, query performance per tenant, and resource consumption anomalies. Alerts on potential noisy neighbor situations and data isolation breaches.
Data Model
Tenant Identification Strategies
Every request must be mapped to a tenant. Common strategies:
Subdomain-based: acme.yourapp.com → tenant “acme”. Parse the Host header, strip the platform domain, look up the tenant by slug. Simple and user-friendly. Requires wildcard DNS and certificates.
Path-based: yourapp.com/acme/... → tenant “acme”. Works without DNS configuration but pollutes the URL namespace. Less common for customer-facing apps.
Custom domain: app.acme.com → tenant “acme”. Lookup domain in the routing table. Best UX for customers but requires custom domain infrastructure (see Custom Domains guide).
JWT/API key: Extract tenant_id from the authentication token. Works for API-first products. The auth system must embed tenant context in the token at login/key-creation time.
Header-based: X-Tenant-ID header set by an upstream gateway. Used in microservice architectures where a gateway handles tenant resolution and downstream services trust the header.
Row-Level Security with PostgreSQL
PostgreSQL’s Row-Level Security (RLS) provides database-enforced tenant isolation in shared-schema mode.
Setup:
- Add
tenant_idcolumn to every table - Enable RLS:
ALTER TABLE orders ENABLE ROW LEVEL SECURITY; - Create policy:
CREATE POLICY tenant_isolation ON orders USING (tenant_id = current_setting('app.tenant_id')); - At request start, set the session variable:
SET app.tenant_id = 'tenant_abc';
Benefits: Even if application code forgets to filter by tenant_id, the database enforces it. This is a defense-in-depth strategy — bugs in application code cannot leak data across tenants.
Caveats:
- RLS adds a small query planning overhead (~1-5%)
- Superuser roles bypass RLS — never use superuser in the application
- Must be applied to every table — easy to miss new tables
- Index on tenant_id is essential for performance
- Connection poolers (PgBouncer) in transaction mode require SET LOCAL instead of SET
Noisy Neighbor Mitigation
The “noisy neighbor” problem occurs when one tenant’s workload degrades performance for others.
Database layer:
- Set per-tenant statement timeouts to kill long-running queries
- Use connection pooling with per-tenant limits (max 10 connections per tenant)
- Monitor per-tenant query latency and alert on outliers
- Consider read replicas for heavy-read tenants
Application layer:
- Per-tenant rate limiting on API endpoints
- Separate job queues for background tasks (or priority queues with tenant-based priority)
- Memory/CPU limits per request using timeouts and circuit breakers
Cache layer:
- Prefix all cache keys with tenant_id to prevent collisions
- Set per-tenant memory limits or use separate Redis databases
- Implement cache eviction policies that don’t let one tenant flush others’ cache
Nuclear option: Automatically migrate noisy tenants to dedicated infrastructure when they consistently exceed thresholds. This is the “tiered isolation” approach — start shared, promote to dedicated as needed.
Real-World Examples
Slack
Each workspace is a tenant. Uses shared-schema with workspace_id on every table. Enterprise Grid connects multiple workspaces under one organization with cross-workspace features.
Shopify
Millions of merchant stores on shared infrastructure. Uses a sharded database architecture with pod-based isolation. Large merchants (Shopify Plus) get dedicated resources with higher rate limits.
Salesforce
Pioneered the shared-schema multi-tenant model. All customers share the same database with org_id discrimination. Uses a metadata-driven architecture for per-tenant schema customization.
AWS (Control Plane)
AWS services use a “silo” model for large accounts and “pool” model for smaller ones. Critical control planes run in separate cells to contain blast radius. Each cell serves a subset of tenants.
Architectural Tradeoffs
Shared schema vs database-per-tenant
Pros
- +Shared: lowest cost, simplest ops, easy cross-tenant features
- +Per-tenant: strongest isolation, easiest compliance, per-tenant scaling
- +Shared: migrations are atomic across all tenants
Cons
- −Shared: data leak risk, noisy neighbor, hard data residency
- −Per-tenant: high cost, O(N) migrations, complex connection management
- −Hybrid (tiered): most flexible but most complex to build and operate
Application-level vs database-level isolation (RLS)
Pros
- +Application-level: flexible, works with any database
- +Database-level (RLS): defense-in-depth, cannot be bypassed by app bugs
- +Both together provide the strongest guarantees
Cons
- −Application-level: single bug can leak data across tenants
- −Database-level: adds query overhead, complex setup, DB-specific
- −Both: more development and testing effort
Subdomain vs custom domain tenant routing
Pros
- +Subdomain: simple DNS, free TLS with wildcard cert
- +Custom domain: best branding for customers, professional appearance
- +Both: support both simultaneously for different customer tiers
Cons
- −Subdomain: less professional, customers want their own domain
- −Custom domain: complex infrastructure (DNS, TLS, proxy)
- −Both: more routing logic and edge cases to handle
Learn Multi-Tenant Architecture
Copy a learning context prompt for an AI assistant. It keeps assumptions visible and asks for tradeoffs, cost drivers, failure modes, and what to measure before adding complexity.
Explore More Capabilities
API Versioning
Backward-compatible API evolution with URL, header, and content negotiation strategies
Audit Logging
Append-only event stores, tamper-proof records, and compliance-ready activity trails
Caching Strategies
Cache-aside, write-through, invalidation patterns, and distributed caching with Redis