Requirements & scope
Problem Statement & Requirements
Design a multi-tenant Software-as-a-Service platform architecture that serves thousands of organizations (tenants) on shared infrastructure while providing data isolation, per-tenant customization, fair resource allocation, and tiered pricing — the foundational pattern behind Slack, Salesforce, Shopify, Datadog, Atlassian, and virtually every modern B2B SaaS product.
Functional Requirements
- Tenant onboarding — self-service signup with organization creation, admin user, initial configuration
- Tenant isolation — each tenant's data is logically (or physically) separated; no cross-tenant data leakage
- Tenant-level configuration — custom branding (logo, colors, domain), feature flags, workflow configuration per tenant
- User management — tenant admins manage their own users, roles, and permissions (RBAC within tenant)
- Tiered plans — Free, Pro, Enterprise tiers with different feature sets, limits, and pricing
- Usage metering & billing — track per-tenant usage (API calls, storage, seats) and bill accordingly
- Tenant-aware API — all API endpoints are tenant-scoped; tenant resolved from auth token, subdomain, or header
- Admin console — platform-level admin to manage tenants, view usage, handle support escalations
- Data export / portability — tenants can export their data (GDPR, vendor lock-in concerns)
- Custom domains — Enterprise tenants use their own domain (acme.myproduct.com or app.acme.com)
Non-Functional Requirements
- Noisy neighbor prevention — one tenant's traffic spike must not degrade others
- Scalability — support 100K+ tenants, from 1-user free tier to 100K-user enterprise
- Security — tenant data isolation is paramount; penetration of one tenant must not expose another
- Availability — 99.99% for all tenants; 99.999% SLA for Enterprise tier
- Cost efficiency — shared infrastructure for small tenants; dedicated resources for large ones
- Compliance — SOC 2, GDPR, HIPAA (for healthcare SaaS); data residency per region
- Zero-downtime deployments — rolling updates without interrupting any tenant
Out of Scope
- Specific SaaS product features (CRM, project management, etc.)
- Marketplace / app ecosystem (Shopify App Store model)
- Full identity provider implementation (use Auth0/Okta integration)
Scale estimations
Scale Estimations
Traffic
| Metric | Value |
|---|---|
| Total tenants | 100K |
| Free tier tenants (1-5 users) | 80K (80%) |
| Pro tier tenants (5-500 users) | 18K (18%) |
| Enterprise tier tenants (500-100K users) | 2K (2%) |
| Total end users | 50M |
| Daily active users | 10M |
| API requests / second (total) | 200K RPS |
| API requests / second (single large tenant peak) | 20K RPS |
| Webhook deliveries / day | 100M |
Storage
| Metric | Value |
|---|---|
| Avg data per Free tenant | 100 MB |
| Avg data per Pro tenant | 10 GB |
| Avg data per Enterprise tenant | 500 GB |
| Total: Free (80K × 100 MB) | ~8 TB |
| Total: Pro (18K × 10 GB) | ~180 TB |
| Total: Enterprise (2K × 500 GB) | ~1 PB |
| Grand total | ~1.2 PB |
| File/blob storage (attachments) | ~3 PB |
| Audit logs (all tenants, 1 year) | ~50 TB |
Compute
| Metric | Value |
|---|---|
| API servers | 200-500 (auto-scaling) |
| Background workers | 100-200 |
| Database shards | 50-100 (shared pool) |
| Dedicated DB instances (Enterprise) | 50-100 |
| Redis cache nodes | 30-50 |
| Tenant isolation overhead | ~10-15% of total compute (auth, routing, metering) |
Layered architecture
High-Level Architecture
High-Level ArchitectureExcalidraw diagram · editable shapes · reveal step by stepExplore
Component Breakdown
Component BreakdownExcalidraw diagram · editable shapes · reveal step by stepExplore
API & contracts
API Design
Tenant Onboarding
POST /api/v1/tenants
Content-Type: application/json
Request:
{
"organization_name": "Acme Corp",
"subdomain": "acme", // acme.myproduct.com
"admin_email": "alice@acme.com",
"plan": "pro", // free | pro | enterprise
"billing": {
"payment_method_id": "pm_card_4242"
}
}
Response (201 Created):
{
"tenant_id": "tenant-abc123",
"organization_name": "Acme Corp",
"subdomain": "acme",
"url": "https://acme.myproduct.com",
"plan": "pro",
"status": "provisioning", // provisioning → active
"admin_user": {
"user_id": "user-alice",
"email": "alice@acme.com",
"role": "admin",
"invite_link": "https://acme.myproduct.com/invite/..."
},
"provisioning_eta_seconds": 30,
"created_at": "2026-04-03T10:00:00Z"
}
// Behind the scenes (provisioning pipeline):
// 1. Create tenant record in control plane DB
// 2. Provision database schema / shard allocation
// 3. Seed default configuration (roles, settings)
// 4. Create admin user + send invite email
// 5. Configure subdomain DNS
// 6. Issue TLS certificate (if custom domain)
// 7. Status → "active"Tenant-Scoped API Call
GET /api/v1/projects
Authorization: Bearer <JWT with tenant_id=acme>
# OR
Host: acme.myproduct.com
Response (200 OK):
{
"projects": [
{
"project_id": "proj-001",
"name": "Q2 Launch",
"members": 12,
"created_at": "2026-03-15T10:00:00Z"
},
...
]
}
// This ONLY returns projects belonging to tenant "acme"
// Even if database has 100K tenants' projects,
// query is: SELECT * FROM projects WHERE tenant_id = 'acme'
// RLS policy enforces this even if code has a bugUsage & Billing
GET /api/v1/tenants/{tenant_id}/usage?period=2026-04
Response (200 OK):
{
"tenant_id": "tenant-abc123",
"period": "2026-04",
"plan": "pro",
"usage": {
"seats": {"used": 47, "limit": 100, "overage": 0},
"api_calls": {"used": 2340000, "limit": 5000000},
"storage_gb": {"used": 8.7, "limit": 50},
"projects": {"used": 23, "limit": 50}
},
"billing": {
"base_charge": 9900, // $99/month base
"seat_charge": 4700, // 47 seats × $1/seat
"overage_charges": 0,
"total": 14600, // $146.00
"currency": "usd"
}
}Platform Admin
GET /api/v1/admin/tenants?plan=enterprise&sort=storage_desc&limit=20
Authorization: Bearer <platform_admin_token>
Response (200 OK):
{
"tenants": [
{
"tenant_id": "tenant-bigcorp",
"name": "BigCorp Inc.",
"plan": "enterprise",
"users": 45000,
"storage_tb": 1.2,
"api_calls_per_day": 5000000,
"monthly_revenue": 250000,
"health": "healthy",
"shard": "dedicated-bigcorp-01"
},
...
]
}Data model
Data Model
Tenant (Control Plane)
Tenant (Control Plane)Excalidraw diagram · editable shapes · reveal step by stepExplore
Tenant-Scoped Data (Application Tables)
Tenant-Scoped Data (Application Tables)Excalidraw diagram · editable shapes · reveal step by stepExplore
Usage Metering
Usage MeteringExcalidraw diagram · editable shapes · reveal step by stepExplore
Core design decisions
Core Design Decisions
Decision 1: Data Isolation Model — The Most Critical Choice
Decision 1: Data Isolation Model — The Most Critical ChoiceExcalidraw diagram · editable shapes · reveal step by stepExplore
Decision 2: Noisy Neighbor Prevention
Decision 2: Noisy Neighbor PreventionExcalidraw diagram · editable shapes · reveal step by stepExplore
Decision 3: Tenant-Aware Caching
Decision 3: Tenant-Aware CachingExcalidraw diagram · editable shapes · reveal step by stepExplore
Request flows
Detailed Flow Diagrams
Request Flow (Tenant Resolution → Execution → Response)
Request Flow (Tenant Resolution → Execution → Response)Excalidraw diagram · editable shapes · reveal step by stepExplore
Tenant Provisioning Flow
Tenant Provisioning FlowExcalidraw diagram · editable shapes · reveal step by stepExplore
Tenant Data Migration (Upgrade to Enterprise)
Tenant Data Migration (Upgrade to Enterprise)Excalidraw diagram · editable shapes · reveal step by stepExplore
Performance & caching
Workshop note · added for the website’s common reading format
Start with the dominant access pattern of Multi-Tenant SaaS Platform. Shared database is one place to inspect capacity and tail latency. Measure before introducing a cache: define the cache key, invalidation policy, stale-data budget, and cold-start behavior.
Batch independent work where the latency budget allows it. Bound queues and concurrency, and verify that an optimization does not move the bottleneck to a dependency.
Advanced design
Feature Flags & Plan-Based Access
Feature Flags & Plan-Based AccessExcalidraw diagram · editable shapes · reveal step by stepExplore
Billing & Usage Metering
Billing & Usage MeteringExcalidraw diagram · editable shapes · reveal step by stepExplore
Security & Compliance
Security & ComplianceExcalidraw diagram · editable shapes · reveal step by stepExplore
Edge cases
Handling Edge Cases
Tenant Deletion (GDPR Right to Erasure)
Tenant requests account deletion:
1. Immediate: Mark tenant as "pending_deletion" (30-day grace)
→ Tenant can still cancel deletion within 30 days
→ All access blocked after request
2. Day 30: Hard deletion pipeline
→ Shared DB: DELETE FROM all_tables WHERE tenant_id = ?
(batch delete, 10K rows at a time to avoid lock contention)
→ Dedicated DB: DROP DATABASE tenant_acme
→ Blob storage: DELETE s3://bucket/tenants/acme/*
→ Cache: SCAN + DEL all keys matching acme:*
→ Search index: delete all documents for tenant
→ Audit logs: retained (anonymized) for compliance
→ Backups: tenant data excluded from future backups;
old backups expire naturally (30-90 day retention)
3. Confirmation: generate deletion certificate
→ Cryptographic proof that all data was purged
→ Required by GDPR Article 17Tenant Suspended (Non-Payment)
Billing failure for tenant "acme":
Day 0: Payment fails → retry
Day 3: Second attempt fails → email warning
Day 7: Third attempt fails → mark tenant as "payment_overdue"
→ Banner in UI: "Update payment method to continue"
→ All features still accessible (grace period)
Day 14: Mark as "suspended"
→ Read-only access (can view data, cannot modify)
→ API writes return 402 Payment Required
→ Background jobs paused
Day 30: Mark as "pending_deletion"
→ Data export available (30 more days)
Day 60: Hard deletion (same as GDPR flow)
At any point: payment succeeds → immediately reactivate
→ Status returns to "active"
→ Zero data lossCross-Tenant Data Leak Prevention
Defense in depth (5 layers):
Layer 1: Tenant Context Middleware
→ Every request resolves tenant_id
→ Attached to request context
→ Service layer reads from context
Layer 2: Row-Level Security (PostgreSQL)
→ Database enforces tenant_id filter
→ Even buggy application code can't cross tenants
→ Policy: USING (tenant_id = current_setting('app.tenant'))
Layer 3: Cache Key Namespace
→ All cache keys prefixed with tenant_id
→ Framework enforces: if key doesn't start with tenant_id, reject
Layer 4: API Response Validation
→ Response interceptor checks: do ALL returned objects
belong to the request's tenant_id?
→ If mismatch detected → 500 error + security alert + block response
Layer 5: Automated Testing
→ Integration tests with 2 test tenants
→ Verify: actions on Tenant A never return Tenant B's data
→ Run on every deployment (CI/CD gate)
→ Penetration testing quarterlySchema Migrations Across Tenants
Problem: 100K tenants on shared DB → ALTER TABLE affects everyone
Shared-table model (recommended migrations):
1. Additive changes only: ADD COLUMN (nullable), ADD INDEX
→ Online DDL in PostgreSQL/MySQL (no locks for most operations)
→ All tenants get the new column simultaneously
→ Application code handles both old (null) and new values
2. Breaking changes (rare):
→ Blue-green migration:
a. Create new table with new schema
b. Backfill data from old table (background, batched)
c. Dual-write: both old and new tables
d. Switch reads to new table
e. Stop writing to old table
f. Drop old table
→ Zero downtime for any tenant
Dedicated DB model (Enterprise):
→ Migration applied per-instance via rolling update
→ Can canary: migrate 5% of instances → verify → rollout rest
→ Failed migration: rollback that instance onlyTradeoffs
Tradeoffs & Design Decisions Summary
| Decision | Option A | Option B | Chosen | Why |
|---|---|---|---|---|
| Data isolation | All shared (pool) | All dedicated (silo) | Hybrid: shared for Free/Pro, dedicated for Enterprise | Cost-effective for small tenants; full isolation for large/compliance tenants |
| Tenant resolution | JWT claim only | Subdomain + JWT + custom domain | Multi-method | Subdomain for web UX; JWT for API; custom domain for Enterprise branding |
| Query isolation | Application-level WHERE | PostgreSQL RLS | RLS + application | RLS as safety net catches application bugs; defense in depth |
| Rate limiting | Global only | Per-tenant | Per-tenant + per-plan | Prevents noisy neighbor; plan-based limits drive upgrades |
| Cache isolation | Shared with prefix | Dedicated per tenant | Shared (Free/Pro) + dedicated (Enterprise) | Shared is cost-effective; Enterprise gets full isolation |
| Billing model | Flat rate only | Usage-based only | Hybrid (base + seat + usage overage) | Base provides predictable revenue; usage captures growth; overage monetizes heavy users |
| Feature gating | Plan column check | Feature flag service | Feature flag service + plan mapping | Flexible: can enable features per-tenant (beta), per-plan, or globally |
| Schema migrations | Downtime window | Online DDL + blue-green | Online DDL (additive) + blue-green (breaking) | Zero downtime; additive changes cover 95% of migrations |
| Tenant onboarding | Manual provisioning | Automated pipeline | Automated (< 60s) | Self-service is critical for SaaS growth; automation scales to 100K tenants |
| Data region | Single region | Multi-region per tenant | Configurable per tenant (Enterprise) | Free/Pro in default region; Enterprise chooses region for compliance |
Reliability & fault tolerance
Reliability & Fault Tolerance
Single Points of Failure & Mitigations
Single Points of Failure & MitigationsExcalidraw diagram · editable shapes · reveal step by stepExplore
Per-Tenant SLA
Per-Tenant SLAExcalidraw diagram · editable shapes · reveal step by stepExplore
Production architecture
Full System Architecture (Production-Grade)
Full System Architecture (Production-Grade)Excalidraw diagram · editable shapes · reveal step by stepExplore
Further exploration
Workshop note · added for the website’s common reading format
Rebuild Multi-Tenant SaaS Platform from memory, then change one assumption: ten times more traffic, a new region, or a stricter consistency requirement. Which component must change first—and which does not?
Compare Application services with the same boundary in a related design. Write down one alternative you rejected, what it would simplify, and when you would choose it instead. Follow the source link at the end of this article to explore the original document.
Interview playbook
Interview Tips
Start with the tenant isolation spectrum — "The fundamental decision is shared vs dedicated infrastructure. Draw the spectrum: shared tables (cheapest, highest density) → schema-per-tenant → database-per-tenant → VM-per-tenant (most isolated, most expensive). Most SaaS uses a hybrid: shared for small tenants, dedicated for enterprise."
Row-Level Security is the defense-in-depth hero — "Even if application code forgets
WHERE tenant_id = ?, PostgreSQL RLS enforces it at the database level. This is the safety net that prevents cross-tenant data leaks. Application-level filtering is the primary mechanism; RLS catches bugs."Noisy neighbor is the #1 operational challenge — Don't just say "rate limiting." Explain the full stack: per-tenant API rate limits, per-tenant DB connection pools, per-tenant query timeouts, per-tenant background job quotas, and circuit breakers for runaway tenants. One misconfigured tenant's automation must not degrade the platform.
Tenant context must flow through EVERY layer — "Tenant resolution at the gateway is just the start. The tenant_id must propagate to: service layer (request context), database (RLS session variable), cache (key prefix), background jobs (payload), events (metadata), logs (field), and traces (tag). If ANY layer lacks tenant context, you have an isolation gap."
Hybrid data model is the pragmatic answer — "100K tenants on dedicated databases = 100K DB instances = operational nightmare + cost explosion. Shared pool with
tenant_idcolumn for 98% of tenants, dedicated instances for the 2% enterprise tenants who need compliance/isolation. Upgrade path: migrate data from shared to dedicated when tenant upgrades."Feature flags drive the upgrade funnel — "Free user clicks 'Analytics' → '403 Upgrade to Pro.' This is the SaaS growth engine. Feature flags per plan, but also per-tenant (beta features to select tenants). The feature flag service is a revenue tool, not just a dev tool."
Billing metering is a distributed systems problem — "Track usage in Redis (real-time, for rate limiting) → flush to ClickHouse (durable, for billing). Monthly aggregation → invoice generation → Stripe charge. Overage pricing: base + (usage - included) × overage_rate. Get billing wrong = lose trust."
Schema migrations across 100K tenants — "Additive changes only (ADD COLUMN nullable) via online DDL — zero downtime, all tenants simultaneously. Breaking changes: blue-green migration (new table, backfill, dual-write, cutover). Never ALTER TABLE NOT NULL on a 100K-tenant shared table."
Tenant deletion must be thorough (GDPR) — Explain: 30-day grace period, then hard delete from ALL stores (DB, cache, search index, blob storage, backups age out). Generate deletion certificate. This is legally required and builds trust.
End with the business model connection — "Multi-tenancy isn't just an architecture pattern — it's the SaaS business model. Shared infrastructure = low cost per tenant = freemium viable. Per-tenant metering = usage-based pricing. Feature gating = upgrade funnel. Dedicated tier = enterprise contracts. The architecture enables the business."