Module 07 // Case studiesLesson 31 / 3422 min read
Design a distributed rate limiter
A distributed rate limiter enforces limits like "100 requests per minute per API key" across many servers, quickly and accurately, without becoming a bottleneck or a single point of failure itself.
Think of it like this
Analogy
A nightclub with several entrances and a strict capacity. If each door keeps its own count, the club overfills. So every door radios a central counter before letting anyone in. The counter must answer instantly, never let two doors take the last spot, and if the radio goes down, the doors need a sensible rule of their own rather than locking everyone out.
The problem it solves
Lesson 16 covered rate-limiting algorithms. In production your API runs on dozens or hundreds of servers in several regions, and limits are per API key, per user, per IP and per endpoint, sometimes all at once. Every request must be checked in about a millisecond, the check itself must be correct under concurrency, and the limiter must not take the API down when it has problems.
A real example
Example
A public weather API
A weather API has 10,000 customers, each limited by plan, and requests arrive at 30 gateway servers.
The counters live in Redis, one bucket per API key. Each check runs as a single Lua script that refills the bucket, takes a token and returns allowed or denied in one atomic step, so two gateways can't both take the last token.
To save a Redis call on every request, each gateway grabs 10 tokens at a time for busy keys and spends them locally. That trades a little accuracy for much less load. If Redis becomes unreachable, the gateways fail open: they let requests through and alert the on-call engineer, because blocking every customer would be worse than a few minutes without limits.
How it works
1. Requirements
Functional: configurable rules (key, limit, window, algorithm) per customer tier and endpoint; return 429 with headers when over the limit; update rules without redeploying.
Non-functional: adds under ~1–2 ms per request; accurate within a small margin; highly available; scales to millions of requests per second.
2. Where it runs
As middleware in the API gateway or a sidecar next to each service, so every request is checked before it reaches business logic. Rules are loaded from a config store and cached locally.
3. Counting in a shared store
The usual design keeps counters in an in-memory store such as Redis, partitioned by key with consistent hashing so load spreads across nodes.
Each check must be atomic. For a token bucket, a small server-side script reads tokens and the last refill time, refills, takes a token if available, writes back and returns the result, all as one operation. A sliding window counter uses two per-window counters with expiry.
-- KEYS[1] = bucket key; ARGV = capacity, refill_per_ms, now_ms
local b = redis.call('HMGET', KEYS[1], 'tokens', 'ts')
local cap, rate, now = tonumber(ARGV[1]), tonumber(ARGV[2]), tonumber(ARGV[3])
local tokens = tonumber(b[1]) or cap
local ts = tonumber(b[2]) or now
tokens = math.min(cap, tokens + (now - ts) * rate)
local allowed = tokens >= 1
if allowed then tokens = tokens - 1 end
redis.call('HSET', KEYS[1], 'tokens', tokens, 'ts', now)
redis.call('PEXPIRE', KEYS[1], math.ceil(cap / rate))
return allowed and 1 or 0- Clients to Load balancer
- Load balancer to Gateway 1
- Load balancer to Gateway 2
- Gateway 1 to Counter store: check + count
- Gateway 2 to Counter store
- Gateway 1 to Rules config
- Gateway 1 to Backend services: allowed
- Gateway 2 to Backend services
4. Making it fast and resilient
- One round trip: do the check in a single atomic call, and pipeline when several rules apply.
- Local pre-check: once a key is over its limit, cache “blocked until T” locally so further requests are rejected without a network call.
- Batching for very hot keys: each gateway takes a small allowance of tokens at a time (say 10) and spends them locally. Slightly less precise, far fewer calls.
- Failure mode: if the store is slow or down, fail open with a conservative local limit and alert; for security-sensitive limits like sign-in attempts, fail closed.
- Replication: run the store with replicas and automatic failover; losing a few seconds of counts during failover is acceptable.
5. Multiple regions
Strictly global counts need cross-region coordination, which adds latency. Common options: enforce limits per region (simple; a client could get the limit in each region), split the global budget between regions and rebalance periodically, or sync counts asynchronously and accept a small overshoot.
6. Responding
HTTP/1.1 429 Too Many Requests
Retry-After: 7
RateLimit-Limit: 100
RateLimit-Remaining: 0
RateLimit-Reset: 7Log decisions as metrics (allowed and limited, per rule and tier) so you can tune limits and spot abuse.
Trade-offs
What you gain
- Accurate, consistent limits across any number of servers.
- Central rules make tiers and per-customer overrides easy.
- Local caching and batching keep overhead to about a millisecond or less.
What it costs you
- The counter store is critical infrastructure that must scale and stay up.
- Strict global limits across regions cost latency; looser ones allow overshoot.
- Every request pays a small extra cost.
When not to use it
- With one server, an in-process token bucket is enough.
- If approximate limits are fine, per-server local limits divided by the server count are simpler and need no shared store.
In the cloud
Rate limitingEvery major cloud sells this as a managed service, so you configure it rather than build it.
AWS2
- AWS WAF rate-based rules (opens vendor docs)
Blocks or counts requests from an IP or key once it passes a request rate you set.
- Amazon API Gateway throttling (opens vendor docs)
Token-bucket limits per API, stage or client usage plan.
Azure2
- Azure API Management rate-limit policies (opens vendor docs)
Limit calls per key or subscription over a time window, or set long-term quotas.
- Azure Web Application Firewall rate limiting (opens vendor docs)
Rate-limit rules on Front Door or Application Gateway's firewall.
Google Cloud2
- Google Cloud Armor rate limiting (opens vendor docs)
Throttle or temporarily ban clients that exceed a request rate at the load balancer.
- Apigee quota and spike arrest (opens vendor docs)
Policies that cap calls per developer or app and smooth sudden bursts.
Limits are enforced at the edge before requests reach your servers.
- CloudFront
- AWS WAF rate-based rule
- API Gateway usage plan
- Backend
Interview questions
Try answering out loud first, then open the model answer.
Design a rate limiter that allows 1,000 requests per minute per API key across 100 servers.
Model answer
Put limiter middleware in the gateway. Rules (key pattern, limit, window, algorithm) live in a config service and are cached locally. Counters live in a Redis cluster sharded by API key; each request runs an atomic Lua token-bucket or sliding-window-counter script in one round trip, returning allow or deny and remaining quota. Denied requests get 429 with Retry-After. Once a key is denied, the gateway caches the block locally until reset. If Redis is unavailable, fail open with a per-server fallback of 1,000 ÷ 100 per minute and alert. Metrics on decisions feed dashboards.
How do you avoid race conditions when two servers update the same counter?
Model answer
Make check-and-update a single atomic operation in the store: an INCR followed by a comparison, a Lua script that refills and takes a token, or a conditional write. Never read the count, decide in the app, then write it back, because two servers can both read 99 and both allow. Atomic operations serialize updates for that key.
How would you rate-limit across three regions?
Model answer
Decide how strict it must be. Usually I'd split the global limit across regions in proportion to recent traffic and enforce each share locally with low latency, periodically rebalancing shares as traffic shifts. That allows small overshoot during rebalancing but keeps each check local. For strict limits on low-volume, high-value operations, a single global store with a cross-region call is acceptable, since a few extra milliseconds don't matter there.
Quiz
Pick an answer to see whether it’s right and why.
0 of 6 answered
Key terms
- Rate limiting
- Capping how many requests a client can make in a period of time.
- Token bucket
- A rate-limiting algorithm where requests spend tokens that refill at a steady rate, allowing short bursts.
- Sliding window
- A rate-limiting algorithm that counts requests in the last N seconds measured from now, avoiding burst gaps at window edges.
- 429 Too Many Requests
- The HTTP status code a server returns when a client is over its rate limit.
- Cache
- A small, fast store that keeps copies of data that is expensive to fetch, so repeat requests are quicker.
- Consistent hashing
- A way to map keys to servers on a ring so that adding or removing a server only moves a small share of keys.
Progress is saved in this browser only.