Skip to content

Centralised Redis Counter

Centralised rate limiting via Redis INCR

A Redis counter per user, shared across all connection servers, enforces the rate limit correctly regardless of which server the user is on or how many times they reconnect.


The solution

Every connection server, before forwarding a message, increments a shared Redis counter for that user:

Key:   rate:<user_id>
Value: message count in current second
TTL:   1 second (auto-resets every second)

The flow on every incoming message:

Alice sends a message
→ Connection server: INCR rate:user_alice
→ If result > 10: reject message
→ If result ≤ 10: forward message to app server

On the first message of a new second, the key doesn't exist — Redis creates it with value 1 and sets TTL to 1 second. At the end of the second, the key expires automatically. The counter resets with no cleanup needed.


Why INCR is the right operation

Redis INCR is atomic. Two connection servers can INCR the same key simultaneously and Redis guarantees they each get a unique, correct result. There's no race condition, no double-counting.

Server 3: INCR rate:user_alice → returns 8
Server 7: INCR rate:user_alice → returns 9   (Alice reconnected mid-second)
Server 7: INCR rate:user_alice → returns 10
Server 7: INCR rate:user_alice → returns 11 → REJECT

All servers are looking at the same counter. Reconnecting doesn't help.


Key design

Key:    rate:<user_id>
Value:  integer (incremented per message)
TTL:    1 second
Limit:  10 messages/second for 1:1 messaging

The Redis call adds ~1ms latency per message. At 10 messages/second per user this is negligible.

Interview framing

"Rate limiting lives on the connection server. On every incoming message, we INCR a Redis key for that user with a 1-second TTL. If the count exceeds the limit, we reject the message. The atomic INCR means all 500 connection servers share one counter per user — reconnecting doesn't bypass it."