Redis fundamentalsmedium3-5 years

A sliding-window rate limiter — allow 10 requests per rolling second per user — is implemented by calling `ZREMRANGEBYSCORE` to trim old entries, then `ZCARD` to count what's left, then conditionally `ZADD`, as three separate Redis commands from application code. Under concurrent requests from the same user, more than 10 get allowed in some windows. What's wrong, and how does wrapping it in a Lua script fix it?

Each of the three commands is atomic on its own, but the sequence of three isn't — two concurrent requests from the same user can both run ZCARD and both see, say, nine entries (under the limit of 10), and both proceed to ZADD their own entry, because neither one's ZCARD saw the other's not-yet-added entry. The result is 11 allowed instead of 10. The fix is exactly the same principle as INCR replacing GET-then-SET: put all three steps — trim, count, conditionally add — inside one Lua script, which Redis runs as a single atomic unit, so no other client's commands can land in the middle of the check-then-add.

The lesson behind it →
More on Redis fundamentals