System Design Case Study

Design rate limiting so one tenant can't degrade service for others on shared infrastructure

➔? Design multi-tenant rate limiting: per-tenant fairness + global capacity protection + hot-key handling for large tenants
Concepts Involved

Problem Statement

Design rate limiting for a multi-tenant SaaS platform where one tenant's traffic spike cannot degrade service quality for others. The system must enforce per-tenant limits, protect global shared capacity, and handle "whale" tenants (10-100x average traffic) without dedicated infrastructure per tenant.

Core challenge: The noisy neighbor problem: one tenant sends 100x normal traffic, saturating shared database connections, CPU, and memory. Other tenants experience latency spikes and timeouts. You can't provision dedicated infrastructure for every tenant (cost-prohibitive), so you need fair scheduling + admission control on shared resources.
1000+
tenants
shared infrastructure
Noisy Neighbor
1 tenant saturates
shared resources
Two-Layer
per-tenant + global
both must pass
Fair Share
weighted scheduling
proportional to plan

Functional Requirements

What the system must do · multi-tenant fairness and capacity protection

Must Have (Core)

1. Per-tenant admission control · each tenant limited to their plan's rate
2. Global capacity protection · total system throughput cannot exceed shared infra limits
3. Weighted fair queueing when at capacity · remaining throughput shared proportional to plan tier
4. Whale tenant detection + isolation · tenants exceeding 20% total throughput routed to isolated pool
5. Self-service usage dashboard · tenants see their own consumption and limits
6. Two distinct 429 error codes · tenant_limit_exceeded vs system_capacity_exceeded

Out of Scope (this design)

Billing/invoicing (separate billing service)
SLA enforcement/credits (business logic layer)
Tenant provisioning (onboarding workflow)
Data isolation (separate concern · row-level security)
Per-endpoint limits within tenant (finer-grained throttling)

Non-Functional Requirements

Quality constraints shaping the multi-tenant rate limiting design

PropertyTargetWhy It Matters / Design Impact
IsolationOne tenant’s spike must NOT degrade others’ P99 latencyPer-tenant limits + whale isolation pool prevents noisy neighbor effect.
Capacity100K req/sec total across all tenantsShared infrastructure ceiling. Global limiter prevents aggregate overload.
DetectionWhale identified within 5 secondsTenant exceeding 20% total throughput triggers dynamic isolation routing.
FairnessProportional to plan tier when at capacityRemaining throughput shared by weighted fair queueing. Enterprise > Pro > Free.
AdaptiveLimits tighten when load > 80%Dynamic throttling prevents cascading failures during system stress.
Key tension: Per-Tenant Fairness vs. Global Capacity. Even if every tenant is within their individual limit, the sum of all tenants can exceed system capacity. Two layers are required: per-tenant (their fault) and global (system overloaded) · with different error codes for each.

Scale Estimation

Derive infrastructure sizing and isolation thresholds from tenant distribution

Given: 1000 tenants · System capacity 100K req/sec · Whale tenant can spike to 50K req/sec · 500 DB connections total
StepWhat to DeriveCalculationResultDesign Decision
1 Normal distribution 900 small (100 req/sec each) + 90 medium (2K each) 90K + 180K potential = exceeds capacity Global limiter required even if each tenant is within their own limit
2 DB connection per-tenant max 500 total pool / 10 (safety factor) 50 connections max per tenant Prevents single tenant from starving others of DB connections
3 Whale threshold 20% of 100K = 20K req/sec from single tenant 20K req/sec triggers isolation Auto-route whale to dedicated worker pool when threshold exceeded
4 Isolated pool capacity Reserve 30% of system capacity for whale overflow 30K req/sec isolated pool Whale gets dedicated resources, shared pool protected for other 999 tenants
5 Redis counter memory 1000 tenants × 10 windows × 64 bytes per counter ~640KB for all rate counters Trivial memory footprint. Redis handles easily with INCR + TTL.
Interview tip: Start by identifying the whale problem (step 3) · one tenant consuming 50% of capacity is the core challenge. Then show two-layer defense: per-tenant limits (their fault) + global capacity guard (system overloaded). The distinct error codes show maturity.

APIs

Rate limit enforcement interfaces and tenant monitoring

Rate Limit Middleware (All Requests)

Header: X-Tenant-Id identifies the tenant on every request
Layer 1 check: Per-tenant counter < plan limit? → pass or 429
Layer 2 check: Global counter < system capacity? → pass or 429
429 (tenant): {"error": "tenant_limit_exceeded", "retry_after": 1}
429 (system): {"error": "system_capacity_exceeded", "retry_after": 5}

Admin API: Tenant Monitoring

GET /admin/tenants/{id}/usage · Real-time request count, limit, % used
GET /admin/system/capacity · Global load %, active whale tenants
POST /admin/tenants/{id}/isolate · Manual whale isolation trigger
PUT /admin/tenants/{id}/limit · Override tenant rate limit

Data Model

Storage structures for per-tenant counters, global tracking, and isolation state

// Per-tenant rate counter (sliding window)
// Redis key: tenant:{id}:counter:{window_timestamp}
INCR tenant:acme:counter:1705312200     // Current window count
EXPIRE tenant:acme:counter:1705312200 60 // Auto-cleanup after window

// Global aggregate counter
INCR system:total:1705312200             // Total requests this window

// Whale tenant isolation flag
SET tenant:acme:isolated true EX 300     // Flag expires after 5 min
GET tenant:acme:isolated                 // Check before routing

// Tenant configuration (PostgreSQL)
CREATE TABLE tenants (
  tenant_id       VARCHAR(64) PRIMARY KEY,
  plan            VARCHAR(20),          -- free, pro, enterprise
  limit_rps       INT,                  -- requests per second limit
  burst_capacity  INT,                  -- token bucket burst size
  isolation_threshold FLOAT DEFAULT 0.2 -- % of system capacity
);

// Real-time metrics (Redis Sorted Set)
ZADD tenant:throughput 45000 "acme"     // Score = current req/sec
ZRANGEBYSCORE tenant:throughput 20000 +inf  // Find whales

Architecture · Two-Layer Rate Limiting

Layer 1: per-tenant admission control. Layer 2: global capacity protection. Both enforce independently.

Multi-Tenant Rate Limiting · Two-Layer Noisy Neighbor Protection TENANT TRAFFIC (1000+ Tenants on Shared Infra) 900 Small Tenants 10-100 req/sec each 90 Medium Tenants 500-2000 req/sec each WHALE TENANT 50,000 req/sec (spike) System Capacity: 100K req/sec total Shared DB, CPU, Memory, Connections LAYER 1: Per-Tenant Limits Small: 200 req/sec Token bucket per tenant Medium: 5K req/sec Token bucket per tenant Each tenant counted independently · one tenant's traffic never affects another's quota If tenant exceeds own limit → 429 (their fault) | Other tenants unaffected LAYER 2: Global Capacity Guard Total throughput: 80K req/sec (80% of capacity) If breached: weighted fair queueing by tier Prevents: 100 tenants each at 80% = system overload If global limit hit → 429 (system overloaded) | shed by priority tier WHALE TENANT ISOLATION (Dynamic) Detection: tenant > 20% of total Real-time monitoring of per-tenant volume Route to dedicated worker pool Isolated DB connections + CPU + memory Shared pool protected Other 999 tenants unaffected Adaptive Throttling (Dynamic Limit Adjustment) Load < 60%: Full tenant limits Load 60-80%: Reduce limits 20% Load > 80%: Reduce 50% + shed low-priority Request flow: Tenant request → Layer 1 check (per-tenant) → Layer 2 check (global) → Whale detection → Process or 429 Two different 429 error codes: TENANT_LIMIT_EXCEEDED vs SYSTEM_CAPACITY_EXCEEDED (helps tenant understand if it's their fault or system-wide)
DecisionChoiceWhy
Layer 1Per-tenant token bucket (capacity = plan limit)Each tenant gets their contracted rate. Burst allowed up to bucket capacity.
Layer 2Global capacity limiter (total system throughput cap)Even if all tenants are within their individual limits, total cannot exceed system capacity.
FairnessWeighted fair queueing when global limit is hitIf system is at 90% capacity, remaining capacity is shared proportionally to plan tier.
Hot tenantIsolated processing lane for whale tenantsTenant consuming > 20% of capacity gets routed to dedicated worker pool. Prevents shared-pool saturation.
BackpressureAdaptive: tighten tenant limits when global load > 80%Dynamic throttling: when system is stressed, temporarily reduce all tenant limits proportionally.
VisibilityPer-tenant usage dashboards + 429 breakdown by tenantSelf-service: tenants see their own usage, can upgrade before hitting limits.
Two-layer design: Request arrives → Layer 1 checks per-tenant limit (fast, Redis INCR) → if passes, Layer 2 checks global capacity (shared semaphore/counter) → if both pass, request is processed. If either fails, return 429 with appropriate Retry-After.
Whale tenant handling: Monitor per-tenant traffic volume in real-time. When a tenant exceeds 20% of total throughput, dynamically route their requests to an isolated worker pool. This pool has its own DB connection pool, preventing the whale from starving other tenants of connections.
Anti-patterns: Only per-tenant limits · 100 tenants each at 80% of their limit can still overwhelm the system (100×80% = 8000% of capacity). Only global limits · one fast tenant consumes all shared capacity. Equal limits for all plans · enterprise customers subsidize free tier abuse.
Real-world: AWS · per-account service quotas + regional capacity. Salesforce · API call limits per org per 24hr rolling window. Datadog · per-org ingest rate limits. Snowflake · virtual warehouse isolation for noisy tenants.

Resilience & Edge Cases

FailureImpactRecovery
Whale tenant sudden spikeSaturates shared DB connections before rate limiter kicks inConnection pool per-tenant quotas. Admission control at connection checkout, not just request level.
All tenants spike simultaneously (event-driven)Global capacity hit, everyone degraded equallyPriority tiers: enterprise tenants shed last. Auto-scale with pre-warming. Queue excess with fair ordering.
New tenant onboards with massive backfillInitial data import floods the systemOnboarding rate limit (lower than steady-state). Dedicated import pipeline. Background job queue with throttling.
Rate limit counter storage failureCannot enforce per-tenant limitsLocal approximate counters as fallback. Conservative local limit = global_capacity / active_tenants.
Tenant complaint about unfair limitingTenant claims they're within their plan but getting 429sDistinguish per-tenant 429 (their fault) from global-capacity 429 (system overloaded). Different error codes/messages.

Interview Cheat Sheet

Key points for multi-tenant rate limiting

1. Two-layer design · per-tenant limit AND global capacity limit (both must pass)
2. Noisy neighbor isolation · whale tenants get dedicated worker pools when they exceed threshold
3. Weighted fair queueing · when at capacity, remaining throughput shared proportional to plan tier
4. Adaptive throttling · dynamically tighten all limits when system load > 80%
5. Connection-level quotas · rate limiting requests is not enough — also limit DB connections per tenant
6. Self-service visibility · tenants see their own usage dashboard + 429 breakdown