Complex Systems · Phase 4: High-Level Design (HLD)
Planetary Distributed Rate Limiter (Cloudflare & Stripe)
Architect a planetary rate limiting engine processing 1M+ req/s with multi-tier enforcement, Redis Lua sliding window counters, local memory fallbacks, and multi-region synchronization.
Primary specification: How We Built Rate Limiting Capable of Scaling to Millions of Domains (Cloudflare)
Lesson 25 of 33
1d streakThe Architectural Question
Centralized rate limiting clusters introduce 15ms of cross-region network latency to every single API request, violating latency SLAs.
Core Engineering Objectives
- Sliding Window Counter algorithm with millisecond precision in Redis Lua
- Multi-tier defense: Edge Anycast BGP filter + API Gateway + Service-level quotas
- Graceful degradation: local memory token bucket fallback when Redis cluster experiences partition
Step 1: Problem Statement & Operational Context
1. Request
Carries identity, route, and tenant context.
2. Gateway
Builds the policy key and checks local safeguards.
3. Limiter
Runs one atomic token or window update.
4. Policy store
Returns allow or reject plus remaining quota.
5. Service
Receives only allowed traffic.
Fail-open, fail-closed, and local fallback are explicit per-policy choices, not universal defaults.
A distributed API rate limiter protects downstream microservice fleets from traffic spikes, denial-of-service (DoS) attacks, brute-force credential stuffing, abusive web scrapers, and cascading system outages caused by misconfigured client retries. Operating across distributed edge gateways and application tiers, the limiter must evaluate incoming requests against complex per-tenant quotas with sub-2 millisecond latency overhead.
The Centralization vs. Latency Paradox
Token Bucket
An algorithm where tokens are added to a bucket at a constant refill rate up to a maximum burst capacity. Each request consumes one or more tokens; if the bucket is empty, the request is rejected.
Sliding Window Counter
A hybrid memory-efficient algorithm that estimates current request volume by weighting the previous window counter by the remaining percentage of the current sliding window, preventing boundary burst exploitation.
Fail-Open vs. Fail-Closed
A reliability decision specifying whether traffic is admitted (Fail-Open) or rejected (Fail-Closed) when the rate limiting infrastructure itself becomes unreachable or degraded.
Step 2: Requirements (Functional & Non-Functional)
The rate limiter must operate with extreme precision, protecting diverse internal microservice APIs while maintaining transparent communication contracts with clients.
| Category | Requirement Specification | Architectural Decision |
|---|---|---|
| Functional: Identity Tracking | Rate limit by IP, API Key, User ID, or tenant organization | Hierarchical key composition: rl:{tenant_id}:{api_key}:{route} |
| Functional: Multi-Window Rules | Support compound rules: 100 req/sec (burst) AND 10,000 req/hour (quota) | Dual-bucket pipeline evaluation in a single atomic Lua invocation |
| Functional: Client Contract | Return HTTP 429 Too Many Requests with standard RFC headers | Include RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset, Retry-After |
| Functional: Dynamic Tiers | Support dynamic tier updates (Free, Pro, Enterprise) without restarts | Control plane with Redis Pub/Sub / etcd configuration change broadcast |
| Non-Functional: Throughput | Sustain 50,000 requests/sec with peak scaling to 100,000 req/sec | Redis Cluster sharded by tenant key + local in-memory batch reservations |
| Non-Functional: Latency | Rate evaluation overhead must not exceed 2.0ms at p99 | Local L1 cache (Caffeine) + pipeline Lua scripts over low-latency VPC connections |
| Non-Functional: High Availability | Rate limiter outage must not take down the entire business platform | Configurable Fail-Open policy for non-critical endpoints |
| Non-Functional: Race Freedom | Zero over-admission under concurrent bursts hitting multiple gateways | Atomic script execution via Redis Lua engine |
Use it when
Adopt Fail-Open for public browsing, search, and catalog services so that a caching or rate-limiter failure never prevents customers from viewing products.
Reconsider when
Adopt Fail-Closed for authentication (login/signup), payment authorization, and SMS OTP endpoints where unbounded throughput permits catastrophic credential stuffing or financial fraud.
Step 3: Detailed Back-of-the-Envelope Capacity Estimations
Quantitative analysis evaluates memory footprints and compute saturation across 50,000 requests/sec sustained load.
| Parameter | Calculation / Formula | Result |
|---|---|---|
| Sustained Request Rate | Baseline given | 50,000 requests/sec |
| Peak Request Rate (2x) | 50,000 * 2 | 100,000 requests/sec |
| Daily Active Entities | 5,000,000 active API keys & IP addresses | 5M unique limiters |
| Sliding Window Log Footprint | 5M users * 100 timestamps * 8 bytes + ZSET overhead (~32B) | ~20 GB RAM (CPU bound on ZREMRANGE) |
| Sliding Window Counter Footprint | 5M users * 2 counter integers (16B) + Redis key overhead (~48B) | ~320 MB RAM (60x memory reduction) |
| Token Bucket Footprint | 5M users * 2 floats (tokens, last_updated) + hash (~64B) | ~320 MB RAM |
| Single Redis Node Ops Capacity | Standard Redis 7 on single thread | ~80,000 - 100,000 simple GET/SET ops/s |
| Single Redis Lua Script Capacity | Lua script execution with JSON/arithmetic parsing | ~30,000 - 45,000 script executions/s |
| Required Redis Cluster Nodes | 100k peak requests/s / 30k script ops per node = 3.33 | 6 nodes (3 primaries + 3 read replicas) |
Architectural Boundary · Do Not Confuse
Sliding Window Log (Exact Timestamp ZSET)
Sliding Window Counter (Approximation)
Sliding Window Log records each accepted request timestamp and can implement an exact rolling-window count, but memory scales with traffic in the window. Sliding Window Counter estimates the rolling count from the current and previous fixed windows using constant-size state. Its boundary error depends on how requests are distributed inside those windows; it does not have a universal error percentage.
Step 4: High-Level Architecture & Component Responsibilities
A production rate limiting topology employs a multi-tiered defense in depth to absorb traffic without single points of failure.
- 01Tier 1: Edge Scrubbing (Cloudflare / AWS CloudFront): BGP Anycast filters absorb L3/L4 volumetric DDoS attacks. Coarse IP rate limits (e.g. max 2,000 req/min per IP) drop aggressive scrapers before reaching the datacenter.
- 02Tier 2: API Gateway Fleet (Envoy / Kong): Intercepts authenticated traffic, extracts tenant ID and API key, and evaluates in-memory L1 Caffeine cache (Token Leasing).
- 03Tier 3: Distributed State Store (Redis Cluster): Sharded Redis cluster executes atomic Lua scripts to evaluate sliding window counters and token refills across all gateway nodes.
- 04Tier 4: Control Plane & Config Engine: Watches quota updates in the Postgres billing database, pushing updated rate-limit configurations to gateways and Redis via Redis Pub/Sub.
- 05Tier 5: Audit & Telemetry Pipeline: When a request is rejected with HTTP 429, the gateway produces an asynchronous metric event to Kafka/Prometheus to feed security SIEM dashboards.
The Two-Tier Local Token Leasing Optimization
Step 5: Detailed Data Models & Storage Schema
State storage requires precise key design to guarantee that multi-key operations map to identical hash slots in a distributed Redis cluster.
-- Relational Policy Configuration Schema (PostgreSQL Control Plane)
CREATE TABLE rate_limit_policies (
policy_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tier_name VARCHAR(50) NOT NULL UNIQUE, -- 'free', 'developer', 'enterprise'
route_pattern VARCHAR(255) NOT NULL, -- '/api/v1/checkout' or '*'
window_seconds INT NOT NULL DEFAULT 60, -- Rolling window size
max_requests INT NOT NULL DEFAULT 1000, -- Allowed threshold per window
burst_capacity INT NOT NULL DEFAULT 1500, -- Peak burst ceiling
fail_mode VARCHAR(10) NOT NULL DEFAULT 'OPEN', -- 'OPEN' or 'CLOSED'
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE client_api_keys (
api_key VARCHAR(64) PRIMARY KEY,
tenant_id UUID NOT NULL,
policy_id UUID NOT NULL REFERENCES rate_limit_policies(policy_id),
is_active BOOLEAN NOT NULL DEFAULT TRUE
);Redis Hash Tag Syntax {tenant_id}
In Redis Cluster, keys containing curly braces (e.g., `{tenant_123}:prev` and `{tenant_123}:curr`) are hashed exclusively on the substring inside the braces. This guarantees both keys reside on the exact same physical Redis node, allowing multi-key Lua scripts without CROSSSLOT errors.
TTL Expiration Safety
Every rate limiter key must be configured with an explicit TTL (e.g., 2x window duration). Without TTLs, inactive client IP keys accumulate indefinitely, causing silent RAM exhaustion and out-of-memory (OOM) evictions.
Step 6: Core Algorithms & Step-by-Step Request Flows
Below is the battle-tested Redis Lua implementation of the Sliding Window Counter algorithm with O(1) time and memory complexity.
-- Production Redis Lua Script: Sliding Window Counter
-- KEYS[1]: Current window key, e.g., {rl:user_123}:28400101
-- KEYS[2]: Previous window key, e.g., {rl:user_123}:28400100
-- ARGV[1]: Max requests allowed in window
-- ARGV[2]: Window size in seconds (e.g., 60)
-- ARGV[3]: Current epoch timestamp with millisecond precision
-- ARGV[4]: Cost of current request (typically 1)
local curr_key = KEYS[1]
local prev_key = KEYS[2]
local max_limit = tonumber(ARGV[1])
local window_size = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local cost = tonumber(ARGV[4])
-- Calculate offset within the current window (0.0 to 1.0)
local current_window_bucket = math.floor(now / window_size)
local offset = (now % window_size) / window_size
local weight = 1.0 - offset
-- Retrieve current and previous counts
local curr_count = tonumber(redis.call('get', curr_key) or '0')
local prev_count = tonumber(redis.call('get', prev_key) or '0')
-- Estimate total requests in rolling window
local estimated_count = math.floor(prev_count * weight + curr_count)
if estimated_count + cost > max_limit then
-- Exceeded limit: return rejection + headers metadata
local reset_time = (current_window_bucket + 1) * window_size
local retry_after = math.max(1, math.ceil(reset_time - now))
return {0, max_limit - estimated_count, retry_after, reset_time}
else
-- Allowed: increment current window counter atomically
local new_count = redis.call('incrby', curr_key, cost)
if new_count == cost then
-- Set TTL to 2x window to allow previous window calculation
redis.call('expire', curr_key, window_size * 2)
end
local remaining = math.max(0, max_limit - (estimated_count + cost))
local reset_time = (current_window_bucket + 1) * window_size
return {1, remaining, 0, reset_time}
end- 01Step 1: Request Ingress: Client sends
GET /api/v1/orderswithAuthorization: Bearer key_sec_99. - 02Step 2: Key Extraction: Gateway extracts API key, hashes it, and queries local in-memory policy cache to determine user tier (
100 req/min). - 03Step 3: Atomic Evaluation: Gateway executes the Sliding Window Lua script against the regional Redis Cluster. Network RTT is ~0.8ms.
- 04Step 4a (Traffic Admitted): If script returns
1, gateway sets response headers:X-RateLimit-Limit: 100,X-RateLimit-Remaining: 41,X-RateLimit-Reset: 1718000060, and forwards request upstream. - 05Step 4b (Rate Limit Exceeded): If script returns
0, gateway terminates the request immediately withHTTP 429 Too Many Requests. Response payload includes{"error": "rate_limit_exceeded", "retry_after": 14}and headerRetry-After: 14. Zero compute touches backend microservices.
Step 7: Deep Dives into Critical Bottlenecks
Redis Cluster Cross-Slot Evaluation Crash
KEYS[1] and KEYS[2]), Redis verifies that both keys hash to the identical slot on the same master. If an engineer forgets to wrap the shared user identifier in curly braces {user_123}, Redis throws a fatal CROSSSLOT Keys in request don't hash to the same slot error, causing 100% of rate limiting checks to crash.Local Batch Leasing Oversubscription vs. Starvation
Architectural Boundary · Do Not Confuse
Fail-Open Policy (Availability Preferred)
Fail-Closed Policy (Security Preferred)
When Redis suffers an outage, Fail-Open catches the network exception and permits requests through without rate enforcement, preventing a total platform blackout. Fail-Closed rejects all requests with HTTP 429 or 503, protecting downstream databases from being obliterated by unchecked traffic surges.
Step 8: Real-World Production War Stories
Failure Exercise: The Fail-Closed Cascade
Cloudflare: Scaling Rate Limiting to 50+ Million Domains
Step 9: Interviewer Deep-Dive Gauntlet & Active Recall
Deep-Dive Q1: How do you prevent system clock drift across gateway instances from corrupting sliding window calculations?
redis.call('TIME') inside the atomic script gives that shard a consistent time source for the decision. It is not a globally authoritative clock: failover, multiple shards, or multiple regions still require a documented tolerance and ownership model. For strict global quotas, route a key to one authority; for low-latency regional quotas, accept bounded approximation.Deep-Dive Q2: Why does the Fixed Window Counter algorithm permit up to 2x the configured limit, and how does the Sliding Window Counter prevent it?
count = current_count + previous_count * ((window_size - offset) / window_size). At 00:01:01, the weight of the previous window is 59/60 (98.3%), yielding an estimated count of 1 + 100 * 0.983 = 99.3. The 101st request is immediately rejected.Deep-Dive Q3: How do you protect the rate limiter itself from being overwhelmed by a high-volume Layer 7 DDoS attack?
Deep-Dive Q4: What is the Token Bucket algorithm's primary advantage over the Leaky Bucket algorithm for microservice APIs?
Active Recall Checkpoint
Why must Redis rate limiting logic be implemented via a Lua script rather than individual client-side GET and INCR commands?
Hint: Consider what happens when two gateway servers check the counter at the exact same millisecond.
Show answer & explanation
Executing individual commands introduces a classic check-then-act race condition. Multiple gateway servers read the same count concurrently, determine that capacity remains, and both execute INCR, allowing traffic to blow past the quota. A Redis Lua script executes atomically on the single-threaded Redis engine: no other command runs concurrently, guaranteeing race-free evaluation.
Active Recall Checkpoint
What standard HTTP status code and response headers should an API gateway return when a user exceeds their rate limit quota?
Hint: Refer to RFC 6585 and IETF rate limit conventions.
Show answer & explanation
The gateway must return HTTP 429 Too Many Requests. Response headers must include: RateLimit-Limit (quota ceiling), RateLimit-Remaining (0), RateLimit-Reset (epoch time when quota resets), and Retry-After (number of seconds to wait before retrying).
Topic sizing snapshot
The four numbers that materially affect this architecture. Use them as bounds, not as a generic calculator embedded in every lesson.
Peak QPS
1,000,000 RPS peak
5-Yr Storage
N/A (Transient counters)
Bandwidth
8 Gbps telemetry traffic
Working Set RAM
32 GB Redis cluster per region
Production Postmortem & Verification
Production Incident Postmortem
- Trigger / Setup: A global API gateway routes all rate limit checks to a single Redis cluster in US-East.
- Production Symptom: European and Asian API requests suffer an extra 150ms-250ms RTT latency overhead purely for rate limit evaluation.
- Root Cause & Fix: Deploy local rate limiting Redis clusters in each region; synchronize global quotas asynchronously with token-batch allocations.
Acceptance Criteria & Outcomes
- Design multi-tier rate limiting architecture with sub-millisecond local evaluation.
- Demonstrate atomic sliding window enforcement preventing burst boundary exploitation.
- Formulate client response headers (`RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset`).
End-of-Chapter Synthesis & Key Takeaway
Planetary Distributed Rate Limiter (Cloudflare & Stripe) is built around one fundamental rule: Establish clear responsibility and explicit trade-offs.. Before adding components, establish explicit quantitative SLAs for latency, throughput, and consistency. Choose the simplest architecture that meets stated constraints.
Architectural & Technical Interview Review
Core FAANG Interview Questions
Q01How do you justify introducing Planetary Distributed Rate Limiter (Cloudflare & Stripe) into a high-scale production architecture?Click to reveal model answer →Hide answer ↑Requirements & Design Brief
Introduce Planetary Distributed Rate Limiter (Cloudflare & Stripe) only when measured scale, data size, or failure boundaries exceed single-node capacity. State the exact bottleneck, evaluate operational overhead, and verify that the trade-offs justify the added moving parts.
Q02What is the primary trade-off and hidden operational cost of Planetary Distributed Rate Limiter (Cloudflare & Stripe)?Click to reveal model answer →Hide answer ↑Architecture Trade-offs
It improves scalability and availability, but introduces network hops, serialization overhead, and eventual consistency or coordination complexity.
Q03How do you handle catastrophic failure or dependency degradation in Planetary Distributed Rate Limiter (Cloudflare & Stripe)?Click to reveal model answer →Hide answer ↑Failure Review & Recovery
Implement connect/read timeouts, exponential backoff with jitter, and fallback defaults so downstream degradations do not cause cascading outages.
Production Pitfalls & Anti-Patterns
Critical Failure Anti-Patterns
Premature Distributed Complexity
Scattering distributed components before identifying concrete write, read, or storage limits creates accidental points of failure and operational debt.
Production Mitigation: Start with the simplest modular design that satisfies requirements, evolving it only as measured constraints dictate.
Unbounded Retries and Cascading Storms
When a dependency degrades, immediate synchronized retries multiply downstream traffic, turning a minor slowdown into a total fleet outage.
Production Mitigation: Enforce strict retry budgets, exponential backoff, and full randomized jitter on all client calls.
Neglecting Observability and Metrics
Deploying architecture without distributed tracing or p99 tail latency alerts leaves engineers blind when partial partitions occur.
Production Mitigation: Export metrics for request rate, error rate, p95/p99 duration, and saturation at every service boundary.