Distributed rate limits with Redis
The in-memory limiter resets when the process restarts and gives every replica its own counter. Behind a load balancer that means a user with N replicas behind them gets N times the budget. The fix is to store timestamps in Redis instead.
import time
import redis
class RedisRateLimiter:
def __init__(self, client: redis.Redis, window_seconds: int = 60, max_requests: int = 10) -> None:
self.client = client
self.window = window_seconds
self.max_requests = max_requests
def check_rate_limit(self, username: str) -> bool:
key = f"rl:{username}"
now = time.time()
cutoff = now - self.window
pipe = self.client.pipeline()
pipe.zremrangebyscore(key, 0, cutoff)
pipe.zcard(key)
pipe.zadd(key, {str(now): now})
pipe.expire(key, self.window)
_, count, _, _ = pipe.execute()
if count >= self.max_requests:
self.client.zrem(key, str(now))
return False
return True
A Redis sorted set keyed by username holds the timestamps. The pipeline drops expired members, counts what is left, and adds the new entry in one round trip.
A sorted set is the right shape because Redis can drop expired members in a single ZREMRANGEBYSCORE call. The cost stays constant even as a user accumulates history. Same algorithm as the in-memory version, persistent state.
Quiz: Quiz
Loading practice…