Redis Rate Limiting
Rate limiting caps how many requests a user, a device, or an address can send in a set time. It protects login pages from password guessing, keeps free API plans fair, and shields servers from sudden floods. Redis counts requests quickly and shares the count across all servers.
The Turnstile Analogy
A subway turnstile lets one person pass at a time and locks when the pass limit ends. A rate limiter does the same for requests. Each caller receives an allowance, and the limiter blocks extra requests until the allowance resets.
Request ──► Rate Limiter ──► count below limit? ──► YES → pass to the app
│
└─────────────► NO → reply "429 Too Many Requests"
Method 1: Fixed Window Counter
The fixed window method counts requests inside a block of time, such as one minute.
127.0.0.1:6379> INCR rate:user:1001:minute (integer) 1 127.0.0.1:6379> EXPIRE rate:user:1001:minute 60 NX (integer) 1
INCR creates the counter on the first request and raises it on every later request. The NX option, available in Redis 7 and later, sets the 60-second timer only when the key has no timer yet. The app compares the count against the limit.
Limit: 5 requests per minute Request 1 → count 1 → allow Request 2 → count 2 → allow ... Request 5 → count 5 → allow Request 6 → count 6 → block (429) After 60 seconds the key vanishes and the count starts again.
Weak Spot
A user can send 5 requests at 00:59 and 5 more at 01:00. That means 10 requests in two seconds while every window stays within its limit. Bursts across a window boundary slip through.
Method 2: Sliding Window with a Sorted Set
The sliding window method looks back over the last 60 seconds from the current moment. Each request becomes a Sorted Set member scored by its time.
Steps for each request at time NOW (in milliseconds):
1. ZREMRANGEBYSCORE rate:user:1001 0 (NOW - 60000) → drop old entries
2. ZCARD rate:user:1001 → count recent entries
3. If the count is below the limit:
ZADD rate:user:1001 NOW "unique-request-id" → record this request
EXPIRE rate:user:1001 60 → tidy up idle keys
Otherwise block the request.
Timeline (limit 3 per 60 s): 0s ● allow (1 in window) 20s ● allow (2 in window) 40s ● allow (3 in window) 50s ✖ block (3 in window, limit reached) 65s ● allow (the 0s entry dropped out of the window)
Run the four steps inside a Lua script or a MULTI/EXEC block so two requests never read the same count together.
Method 3: Token Bucket
A token bucket holds tokens that refill at a steady speed. Each request spends one token. A full bucket allows a short burst, and an empty bucket blocks until tokens refill. Store the token count and the last refill time in a Hash, and update both inside a Lua script.
Choosing a Method
Method Accuracy Memory Complexity Best For ──────────────── ───────── ──────── ─────────── ───────────────────── Fixed window Medium Tiny Very low Simple limits Sliding window High Higher Medium Strict API limits Token bucket High Small Medium Allowing short bursts
Telling Clients About Limits
Good APIs return headers so clients can slow down on their own.
HTTP/1.1 429 Too Many Requests Retry-After: 30 X-RateLimit-Limit: 5 X-RateLimit-Remaining: 0
Use the TTL command to compute Retry-After from the counter key.
Practical Example – Login Protection
Rule: Allow 5 login attempts per 15 minutes per IP address. Key: rate:login:203.0.113.8 Each attempt: INCR the key, set EXPIRE 900 NX Count above 5: Reject the attempt and show a wait message. Success: Optionally DEL the key. Result: Password guessing slows to a crawl.
Key Points
- INCR with EXPIRE builds a simple fixed window limiter.
- A Sorted Set builds an accurate sliding window.
- Lua scripts keep multi-step checks atomic.
- Name keys by user, address, or API key plus the time window.
- Return a 429 status with a retry hint.
