now - windowSize are pruned first. This provides a true rolling window with no burst-at-boundary problem, at the cost of higher Redis memory usage.
Configuration
Redis key format
The algorithm uses a single ZSET per user, accumulating individual request entries:| Component | Value |
|---|---|
| Key pattern | rlaas:rate_limit:{userId} |
| Member format | {currentTimeMillis}-{random} |
| Score | Unix timestamp in milliseconds |
| TTL | window * 2 milliseconds, extended on every request |
userId=alice:
Lua script
The fullslidingWindow.lua script executed atomically in Redis:
Algorithm steps
Prune stale entries
ZREMRANGEBYSCORE key 0 (now - window) removes all ZSET members whose score (timestamp) is older than the current rolling window. This keeps the set bounded to only the requests that count against the current limit.Count active entries
ZCARD key returns the number of entries remaining in the ZSET after pruning. This is the number of requests already made within the rolling window.Allow if under limit
If
count < limit, the request is allowed. A new member is added with ZADD using the current timestamp as the score, and the key TTL is extended with PEXPIRE. Returns {1, count, window/1000}.Response fields
| Field | Type | Description |
|---|---|---|
allowed | boolean | true if under limit, false if exceeded |
currentCount | long | Number of requests already in the window (before this one) |
ttl | long | The configured window-size in seconds (returned as window/1000) |
Unlike Fixed Window, the TTL returned for a Sliding Window denial is always equal to the configured
window-size — it does not reflect the exact time until the oldest entry expires.See also
Fixed Window algorithm reference
Compare the fixed window approach: lower memory use, simpler key structure, but susceptible to boundary bursts.