Why you shouldn’t use Redis as a rate limiter: Part 1 of 2
A tour of the common Redis-based rate limiter implementations — and the correctness and performance traps each one hides.
We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
Rate limiting and admission checks that help reduce overload and protect expensive work.
Set admission budgets for launch spikes and promos before excess work reaches your handlers.
Fewer moving parts to run and monitor. Spend less on Redis and ops.
Add via SDK. Keep your stack. Clear limits your customers understand.
UNDERSTAND OVERLOAD · KEEP USEFUL WORK MOVING
For developers, architects, and managers: 27 narrated lessons on why services overload and how to protect them. No cloud experience required. Start with one request, or jump to any lesson.
Four chapters, one learning path. Read or listen in any order. ~41 minutes total · Times shown at 1×.
Keep enough capacity to finish work and recover.
Contain one customer's excess without punishing everyone.
Reduce work entering a shared dependency when it slows.
Choose, combine, and test the right protections.
Read the explanation or use the course map above. Audio is optional.
Queue fill and drain
The queue holds work; it does not perform it.
This finishing capacity stays the same.
Equal-cost, constant-rate illustration. One tile holds 20 waiting requests; empty tiles are free slots.
Read the numbered steps in order. The explanation below follows the narration.
A queue stores waiting work; a buffer supplies bounded space.
Bigger buffers buy time. Caching can remove work. Backpressure reduces incoming work.
Bigger buffers buy time. Caching can remove work. Backpressure reduces incoming work.
A queue holds requests waiting for their turn. The worker, not the queue, performs the work. A buffer provides waiting space. In our example, one hundred requests arrive each second and eighty finish. Twenty extra requests must wait each second.
Start with one hundred empty waiting slots. After five seconds, all one hundred slots are occupied. With two hundred slots, the same excess fills the buffer in ten seconds. The worker still finishes eighty per second. We bought five more seconds, not a faster worker. Once full, refuse work, slow its supplier, or move waiting elsewhere; that still needs limits.
Now consider a different case: the burst ends after four seconds, with eighty requests waiting. New arrivals fall to forty per second. The worker can now clear forty waiting requests each second, so those eighty waiting requests disappear in two seconds. If arrivals merely fall to eighty, the queue stops growing but never drains. This is when a bounded queue helps: the burst ends, and later capacity can catch up before the result becomes useless.
A durable queue keeps jobs in storage across a restart. It preserves waiting work, not processing capacity. That suits jobs whose results can arrive later. A cache reuses a computed answer, avoiding repeated work. Backpressure asks the supplier to slow down; it helps only if that supplier responds. For sustained overload, change the work required, the work arriving, or the capacity that can finish it.
Requests wait in the queue until the worker can take them. The tiles represent waiting space, not extra workers or faster execution.
The worker stays busy, but each second brings twenty requests it cannot yet finish. This simple model assumes equal-cost work, constant rates, and no refusal or cancellation.
Reset the illustration to an empty 100-slot buffer. The available slots, not the finishing rate, determine how long this particular excess can be stored.
100 free slots ÷ 20 extra requests per second = 5 seconds. Every slot is occupied, while the worker still finishes only eighty requests each second.
This alternative starts with 200 empty slots under the same continuing overload. After ten seconds it is full too: the larger buffer has not removed the twenty-request-per-second mismatch.
Keeping a full queue does not create another slot. Refuse new work, get its supplier to slow down, or recognize that waiting is moving elsewhere and needs limits there too.
Reset to a different case: after four seconds of twenty extra requests per second, eighty requests are waiting. The burst now ends; this is not a continuation of the full-buffer example.
Forty new requests arrive per second while the worker can finish eighty. The remaining forty completions per second can reduce the already waiting backlog.
After one second of the quieter period, the backlog falls from eighty to forty. Using the full finishing rate as the drain rate would forget the new requests still arriving.
80 waiting ÷ (80 finishing − 40 arriving per second) = 2 seconds. New requests still arrive, but the old backlog has drained in this constant-rate example.
Alternative outcome from the same eighty-request backlog: eighty arrivals and eighty completions per second leave zero spare finishing capacity. The old waiting count stays at eighty.
Return to the successful forty-arrivals-per-second case. The backlog cleared because capacity remained after serving new arrivals, while the waiting results were still useful.
Durability preserves waiting work across a restart, not processing capacity. It can suit delayed results, but storage limits, waiting age, and sufficient later capacity still matter.
A valid previously computed answer can satisfy a repeat request without recomputing it. That changes the work required; it is different from storing more work for later.
Backpressure communicates that the receiver cannot keep up. A cooperating supplier reduces the rate; otherwise merely sending a signal does not stop accumulation.
Reduce work per result, reduce the work arriving, or increase capacity at the limiting component. A bounded queue remains useful only within the waiting and recovery conditions you have actually tested.
Content revision: 07c7dd00db17
Download review copy (27 lessons)Eighty requests are waiting. Arrivals drop to 40/s while the worker finishes 80/s. When does this queue clear?
After two seconds: the worker has 40/s left for the backlog, so 80 ÷ (80 − 40) = 2s. At 80 arrivals/s, the backlog would not shrink.
Bound queue size and waiting age. Test both the expected burst and the quieter period needed to drain it; decide what happens when the queue is full.
Durable asynchronous work can be useful; a queue is not inherently a bad design.
Start here. No earlier lesson is required.
100 arrivals/s − 80 completions/s = 20 extra unfinished requests/s.
This constant-rate, equal-cost model starts with a busy worker and no refusals or cancellations. Fill time = free slots ÷ (arrivals − completions); drain time = backlog ÷ (completions − new arrivals), only when the denominator is positive. Moving waiting upstream does not remove its storage or age cost.
Original explanation inspired by Fred Hébert and operational references; no endorsement implied.
AI-generated narration: ElevenLabs Eleven v3, Daniel stock voice. Audio streams only when you start listening.
A tour of the common Redis-based rate limiter implementations — and the correctness and performance traps each one hides.
The myth of infinite serverless scale — why adding machines doesn’t fix overload, and what to do instead.