Rate Limiting & Throttling
Distributed Systems & Scalability
Chapter 7 · Rate Limiting & Throttling
Chapter 1 measured what happens when load exceeds what a system can handle. Rate limiting is the deliberate decision to reject some requests before that happens, protecting whatever sits downstream. Three algorithms answer "how do I count requests fairly" in genuinely different ways — and one classic implementation has a real, verified bug most people don't expect.
Token Bucket: Allows a Burst, Then Throttles
10 (refilling at 2/sec): 11 requests fired instantly all return the exact expected pattern — the first 10 all succeed, and the 11th is correctly rejected, since the bucket started full and every request drains one token. Waiting 0.6s (refilling roughly 1.2 tokens) makes the next request correctly succeed again.
Leaky Bucket: Smooths a Burst Into a Steady Output Rate
leak() fifteen times released every accepted request in the exact order it was originally submitted — ['req-0', 'req-1', ..., 'req-14'], confirmed matching the submission order exactly. Downstream code calling leak() only ever sees one request at a time, however bursty the input was.
leak() at a time. Choose token bucket when occasional bursts are genuinely fine; choose leaky bucket when downstream specifically needs a smooth, predictable rate — for instance, exactly the kind of steady processing rate a queue consumer (Chapter 6) or a database write path (Chapter 4) benefits from.
Fixed Window vs. Sliding Window: A Real Boundary Bug
A fixed window counts requests within calendar-aligned buckets (e.g., minute 0–60, 60–120) and resets the count at each boundary. What happens to traffic that straddles that boundary?
t=55 and t=59.5 (the tail end of one window) correctly allows all 10. Sending 10 more requests between t=60.5 and t=65 — barely 5.5 to 10.5 seconds later, but now in the "next" window — also allows all 10. Total: 20 requests genuinely allowed within a single 10-second span, against a limit meant to cap traffic at 10 per full 60 seconds.
0 of 10 allowed. Total allowed across the identical 10-second span: 10, matching the intended limit exactly.
Where This Connects
| This chapter's finding | What it connects to |
|---|---|
| A verified, doubled-rate fixed-window bug | Chapter 1's own O(n) bottleneck finding — both are bugs invisible in a simple test, only surfacing under a specific, real traffic shape |
| Leaky bucket's own steady, one-at-a-time release rate | Chapter 6's own message queue — leak() is structurally the same operation as a queue consumer processing one message at a time |
| Rate limiting protecting a downstream system before it's overwhelmed | Chapter 9's own Fault Tolerance & Resilience Patterns — rate limiting is a preventative resilience pattern, applied before a failure rather than reacting to one |
Hands-On Exercises
Using this chapter's own TokenBucket, verify what happens with a capacity of 5 and a much higher refill rate (10/sec) — send 5 immediate requests, then wait 0.3s and send 5 more. Report how many of the second batch succeed and explain why using this chapter's own refill formula.
Using this chapter's own LeakyBucket, submit 10 requests, leak out 3, then submit 8 more before leaking the rest. Verify the bucket correctly rejects any requests that would exceed its own capacity at the moment they're submitted, and verify the final leak order is still fully FIFO across both submission batches.
Using this chapter's own fixed-window and sliding-window results, construct a traffic pattern that does not straddle a window boundary (e.g., all 20 requests sent between t=10 and t=20, well inside one window). Verify both limiters now agree on how many requests are allowed, and explain why the boundary bug specifically requires boundary-straddling traffic to appear at all.
Chapter 7 Quick Reference
- Token bucket: allows a burst up to capacity, then throttles — verified: exactly 10 of 11 immediate requests succeeded against a 10-token bucket
- Leaky bucket: queues a burst, releases it downstream at a steady rate — verified: 15 of 20 burst requests accepted, released one at a time in exact FIFO order
- Fixed window, verified buggy: let 20 requests through in a 10-second span against a 10-per-60-second limit, purely from boundary timing
- Sliding window, verified correct: capped the identical traffic at exactly 10 in the same 10-second span
- Next chapter: API Gateways & Service Discovery — where rate limiting and routing typically get enforced in a real microservices system