Module P-5·20 min read

INCR/INCRBY as lock-free counters, fixed-window rate limiter with INCR+EXPIRE, sliding window using Sorted Sets with timestamp scores, token bucket algorithm, and the off-by-one race in fixed-window that Lua eliminates.

JJS
Written by Jatin Jain Saraf · Senior Software Engineer

P-5 — Atomic Counters, Rate Limiters, and Sliding Windows

Who this module is for: You need to limit how often a user can call your API, track how many times an event has occurred, or implement a quota system. The naive approach — read the counter, check it, increment it — has a race condition. This module covers lock-free atomic counters, the three rate limiting algorithms, and how to choose between them.


The Race Condition in Non-Atomic Counters

Before Redis, a typical rate limiter looked like this in pseudocode:

javascript

Under concurrent requests, two requests can both read count = 99, both pass the check, and both increment — resulting in count = 101, exceeding the limit. This is a classic TOCTOU (Time Of Check, Time Of Use) race.

Redis's INCR command is atomic. The read and increment happen in a single operation that cannot be interleaved with another client's commands.


Atomic Counters with INCR

text
text

If the key does not exist, INCR creates it with value 0 and increments to 1. This makes initialization implicit — no SET ... 0 required.

Use case: Event counters, page view tracking, API call totals. Safe under any level of concurrency.


Rate Limiter Pattern 1: Fixed Window

The simplest rate limiter: count requests in a fixed time window (per minute, per hour). Allow up to limit requests per window.

typescript

The key includes the window index (Math.floor(Date.now() / windowSeconds)) — a new key is automatically created for each window.

The INCR + EXPIRE race: If the process crashes between INCR and EXPIRE, the key has no TTL and persists indefinitely. Fix with SET ... NX EX:

typescript

EXPIRE key seconds NX (Redis 7.0+) sets the TTL only if the key does not already have one — safe for the first request, no-op for subsequent ones in the same window.

The boundary problem: At the boundary between windows (e.g., at exactly 12:00:00), a user can make limit requests at 11:59:59 and limit more requests at 12:00:00 — effectively 2×limit requests in 2 seconds. This is the fixed window's fundamental flaw.


Rate Limiter Pattern 2: Sliding Window (Log-Based)

Record a timestamp for every request in a Sorted Set, using the timestamp as the score. The window is the last windowSeconds seconds of timestamps.

typescript

Sign in to keep reading

The rest of this module is free — sign in with Google to unlock it and track your progress.

Sign in & Register

Discussion

0

Join the discussion

Loading comments...

© 2026 Jatin Jain Saraf (JJS). All rights reserved.