Multi-Tenancy
Multi-tenancy is a software architecture where a single instance of an application serves multiple customers (tenants), with each tenant's data isolated from others. It's fundamental to SaaS (Software as a Service) applications.
Tenancy Models
Single-Tenant
One instance per customer:
Customer A → App Instance A → Database A
Customer B → App Instance B → Database B
Multi-Tenant
One instance serves all customers:
Customer A ─┐
Customer B ─┼→ Shared App Instance → Shared Database (with isolation)
Customer C ─┘
Data Isolation Strategies
1. Separate Databases
Each tenant has their own database:
| Pros | Cons |
|---|---|
| Strongest isolation | Higher infrastructure cost |
| Easy backup/restore per tenant | More complex deployment |
| Tenant-specific customisation | Connection pool management |
2. Separate Schemas
One database, separate schemas per tenant:
-- PostgreSQL
CREATE SCHEMA tenant_a;
CREATE SCHEMA tenant_b;
SELECT * FROM tenant_a.users;
3. Shared Tables with Tenant ID
All tenants in same tables, filtered by ID:
CREATE TABLE users (
id INT PRIMARY KEY,
tenant_id INT NOT NULL,
name VARCHAR(255),
-- ...
);
-- All queries filtered
SELECT * FROM users WHERE tenant_id = 123;
Implementation Patterns
Middleware Tenant Resolution
const tenantMiddleware = async (req, res, next) => {
// From subdomain: tenant.example.com
const subdomain = req.hostname.split('.')[0];
// Or from header
const tenantId = req.headers['x-tenant-id'];
req.tenant = await getTenant(subdomain || tenantId);
next();
};
Query Scoping
// Automatically scope all queries
const getUsersForTenant = (tenantId) => {
return db.query('SELECT * FROM users WHERE tenant_id = $1', [tenantId]);
};
What We Like
- Cost efficiency: Shared infrastructure reduces costs
- Simpler operations: One deployment, one codebase
- Faster onboarding: New tenants without new infrastructure
- Feature rollouts: Easier to update all tenants
What We Don't Like
- Complexity: Isolation logic pervades the codebase
- Noisy neighbors: One tenant can impact others
- Compliance challenges: Some industries require true isolation
- Schema changes: Must consider all tenants
Security Considerations
- Never trust client-provided tenant ID: Resolve from authenticated session
- Enforce at database level: Row-level security (RLS) when possible
- Audit access patterns: Log cross-tenant access attempts
- Test isolation: Verify queries can't leak data
Best Practices
- Choose isolation level carefully: Based on compliance and scale needs
- Implement RLS: PostgreSQL Row-Level Security provides defense in depth
- Centralize tenant resolution: One place, not scattered throughout code
- Plan for migration: Tenants may need stronger isolation later
- Monitor per-tenant: Identify resource-heavy tenants