Limited Offer

30% OFF Lifetime Access ($139) with code SYSTEM30

TOPIC #231Intermediate 9 min read

Design a Distributed Rate Limiter

๐Ÿ’ก
Core Architecture Summary

Protect APIs from overload: Token Bucket in Redis with Lua scripts, Sliding Window Counters, race condition handling, and Gateway edge integration.

Key Glossary Concepts in this TopicAll Glossary Terms

Distributed Rate Limiter with Atomic Redis Lua Engine ๐Ÿชฃ

Centralized Redis Cluster executing atomic Token Bucket Lua scripts with local in-memory fallback and HTTP 429 throttling.

Distributed Rate Limiter with Atomic Redis Lua Engine ๐Ÿชฃ
100%
Rendering visual architecture flowchart...

01.1. Functional & Non-Functional Requirements

A Distributed Rate Limiter throttles excessive client requests to prevent cascading system outages, mitigate DDoS/brute-force attacks, and enforce tiered SaaS subscription billing quotas.

Functional Requirements

  1. Multi-Dimension Throttling: Limit requests based on IP address, authenticated user_id, API Key (tenant_id), or specific endpoints (e.g., login vs search).
  2. Clear Feedback Headers: When rate limits are reached, reject requests with HTTP 429 Too Many Requests and include standard headers:
    • X-RateLimit-Limit: Maximum requests allowed per window.
    • X-RateLimit-Remaining: Number of remaining tokens in current window.
    • X-RateLimit-Reset: Unix epoch timestamp when limit refills.
    • Retry-After: Seconds until the client can retry.
  3. Configurable Rules Engine: Support dynamic updates to rate-limiting rules without restarting services.

Non-Functional Requirements

  • Sub-Millisecond Overhead: Rate-check latency must remain < 2 ms (p99) so it does not degrade overall API response time.
  • Distributed Accuracy & Concurrency Safety: Zero race conditions across thousands of parallel web server nodes.
  • High Fault Tolerance (Fail-Open Policy): If the rate-limiting Redis cluster fails or experiences a network partition, the system must degrade gracefully (fail-open or local in-memory estimate) rather than taking down all user traffic.

02.2. Algorithmic Deep Dive: Comparing Rate Limiting Algorithms

Choosing the appropriate rate-limiting algorithm depends on burst tolerance, memory constraints, and computational complexity:

AlgorithmMechanismProsCons
Token Bucket (Industry Favorite)Tokens refill at fixed rate r up to capacity C. Each request consumes 1 token.Allows short bursts up to C, memory efficient (2 values per user).Refill math requires precise timestamps.
Leaky Bucket (FIFO)Requests enter a queue and leak out at a constant fixed rate.Completely smooths out traffic spikes.Bursts are queued or dropped, delaying time-critical requests.
Fixed Window CounterDivides time into fixed 1m windows with an integer counter.Minimal memory (1 counter), simple implementation.Traffic spikes at window boundaries allow 2ร— burst (200\% quota).
Sliding Window LogStores timestamp of every request in a sorted set; removes entries older than window.100\% mathematically accurate.High memory overhead (O(N) timestamps stored in RAM).
Sliding Window CounterWeighted sum of current and previous fixed window counters.Low memory (2 counters), eliminates window boundary spike issue.Assumes uniform traffic distribution in prior window (~0.05% error).

03.3. Solving Concurrency Race Conditions with Atomic Redis Lua Scripts

In a distributed environment with 100 API gateway instances, a naive GET -> calculate -> SET pattern causes severe race conditions:

code
Server A: GET tokens (reads 1)
Server B: GET tokens (reads 1)
Server A: tokens = 1 - 1 = 0 -> SET tokens 0 (Request Allowed)
Server B: tokens = 1 - 1 = 0 -> SET tokens 0 (Request Allowed - Race Condition!)

The Winning Solution: Atomic Redis Lua Script

Redis executes Lua scripts in a single-threaded atomic transaction, guaranteeing no other command interleaves during execution.

lua
-- KEYS[1]: Rate limit key (e.g. "rate:user_981:api")
-- ARGV[1]: Bucket capacity (e.g. 100)
-- ARGV[2]: Refill rate per second (e.g. 10)
-- ARGV[3]: Current timestamp (Unix epoch seconds with ms)
-- ARGV[4]: Requested tokens (e.g. 1)

local key = KEYS[1]
local capacity = tonumber(ARGV[1])
local refill_rate = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local requested = tonumber(ARGV[4])

local data = redis.call("HMGET", key, "tokens", "last_updated")
local tokens = tonumber(data[1])
local last_updated = tonumber(data[2])

if tokens == nil then
    tokens = capacity
    last_updated = now
else
    local elapsed = math.max(0, now - last_updated)
    tokens = math.min(capacity, tokens + (elapsed * refill_rate))
    last_updated = now
end

if tokens >= requested then
    tokens = tokens - requested
    redis.call("HMSET", key, "tokens", tokens, "last_updated", last_updated)
    redis.call("EXPIRE", key, math.ceil(capacity / refill_rate) * 2)
    return {1, math.floor(tokens)} -- Allowed, remaining tokens
else
    redis.call("HMSET", key, "tokens", tokens, "last_updated", last_updated)
    return {0, math.floor(tokens)} -- Rejected (HTTP 429)
end

04.4. Distributed Architecture & Edge Integration

Where to Place the Rate Limiter?

  1. Edge CDN (Cloudflare / Fastly): Blocks volumetric DDoS and Layer 7 scraping before traffic enters cloud infrastructure.
  2. API Gateway (Envoy / Kong / Zuul): Enforces fine-grained user authentication limits, tenant quotas, and tier enforcement (Free vs Enterprise).
  3. Application Middleware: Enforces internal business rate limits (e.g., maximum 3 password reset attempts per hour).

Sharding & Scaling the Redis Layer

  • Consistent Hash Partitioning: Rate limit keys ("rate_limit:{user_id}") are sharded across a Redis Cluster using 16,384 hash slots.
  • Hash Tags: Use Redis hash tags (e.g., "rate_limit:{{tenant_123}}:resource") to ensure all limits for a specific tenant reside on the same Redis shard for batch script execution.

05.5. High Availability, Failover & Multi-Datacenter Synchronization

Fail-Open vs Fail-Closed Policy

  • Mission-Critical Endpoints (e.g., /checkout, /login): If Redis becomes unreachable, Fail-Open with a local in-memory rate limiter (e.g., Token Bucket in local RAM per Envoy worker) to prevent catastrophic customer-facing downtime.
  • High-Risk Financial Endpoints (e.g., /transfer_money): Fail-Closed to prevent double-spending attacks.

Multi-Region WAN Synchronization

Synchronizing Redis writes across cross-region datacenters (US-East to EU-West) introduces 100 ms WAN latency.

  • Regional Token Buckets: Allocate regional sub-quotas (e.g., US gets 60\%, EU gets 40\% of global quota).
  • Asynchronous Batch Synchronization: Edge servers track local token consumption and flush deltas to global Redis in batches every 100 ms.

โš–๏ธArchitectural Trade-offs & Production Realities

Architectural Advantages

  • Token Bucket allows smooth handling of natural network traffic bursts while enforcing long-term quotas
  • Atomic Redis Lua scripting eliminates concurrency race conditions with sub-millisecond execution
  • Decoupled Gateway tier protects all downstream microservices centrally

Trade-offs & Constraints

  • Redis becomes a critical-path dependency requiring robust clustering and failover handling
  • Multi-region synchronization requires token partitioning or eventual consistency compromises
Production Implementation in Big Tech
Stripeโ€ข Tiered API Rate Limiting Infrastructure

Stripe uses Redis-backed Token Bucket limiters embedded within Envoy edge proxies to enforce 100 req/sec live limits per API key, gracefully throttling non-critical webhook traffic during merchant traffic surges.

๐ŸŽฏ Staff+ Engineering Takeaways

  • Token Bucket is the industry standard for API rate limiting due to burst tolerance and memory efficiency.
  • Redis Lua scripts execute atomically in memory, preventing distributed race conditions.
  • Return HTTP 429 with informative `Retry-After` headers when clients exceed limits.
  • Use local memory fallbacks and Fail-Open policies to survive Redis cluster outages.

Topic Knowledge Assessment ๐Ÿง 

Step through 2 scenario questions to test your staff-level grasp.

Question 1 of 20 answered
#1

Why must the Token Bucket rate limiter logic in Redis be executed via a Redis Lua script rather than individual GET and SET commands from the application server?

Rate This Architecture Chapter4.9 / 5.0 (38 ratings)

How clear and staff-actionable was this system breakdown?