Rate Limiting
What is a rate limiter?
Section titled “What is a rate limiter?”A rate limiter caps how many requests a client can make in a given time window. Redis makes this easy with two atomic commands: INCR to count requests and EXPIRE to bound the window.
How INCR + EXPIRE works
Section titled “How INCR + EXPIRE works”For each incoming request, increment a counter keyed by user and time window. On the very first hit — when the counter equals 1 — set an expiry on that key. If the counter exceeds the limit, reject the request.
flowchart TD
A["Incoming request"] --> B["count = INCR rate:${userId}:window"]
B --> C{"count == 1?"}
C -->|"Yes (first hit)"| D["EXPIRE rate:${userId}:window 60"]
C -->|"No"| E{"count > 100?"}
D --> E
E -->|"Yes"| F["Reject (HTTP 429)"]
E -->|"No"| G["Allow request"] The key encodes the window so each new window gets a fresh counter automatically.
Redis CLI demo
Section titled “Redis CLI demo”127.0.0.1:6379> INCR rate:user:42:window1(integer) 1127.0.0.1:6379> EXPIRE rate:user:42:window1 60(integer) 1127.0.0.1:6379> INCR rate:user:42:window1(integer) 2127.0.0.1:6379> TTL rate:user:42:window1(integer) 58The first INCR returns 1, so we set the expiry immediately. Subsequent increments count against the same window, and TTL confirms the key will expire after the window closes.
INCR rate:user:42:window1
EXPIRE rate:user:42:window1 60
INCR rate:user:42:window1
TTL rate:user:42:window1Fixed window vs. sliding window
Section titled “Fixed window vs. sliding window”INCR + EXPIRE gives a fixed window: all requests in the same 60-second bucket share one counter. A burst at the very end of one window and the very start of the next can effectively double the allowed rate at the boundary.
For a true sliding window you would use a sorted set: store each request timestamp with ZADD, trim old entries with ZREMRANGEBYSCORE, then count live entries with ZCARD. This is more precise but slightly more expensive per request.