RLaaS Users Service supports two rate-limiting algorithms, each with different trade-offs in accuracy, memory use, and behavior at window boundaries. You select one via the rate-limiter.use-algo config property.
Fixed Window
Sliding Window
How it works
The fixed window algorithm divides time into discrete buckets of size windowSize seconds. Each bucket is identified by the index floor(currentTime / windowSize). All requests that arrive within the same bucket share a single Redis STRING key, which is atomically incremented with INCR. The TTL is set on the key when it is first created, so the key expires naturally at the end of the window.Redis key format: rlaas:rate_limit:{userId}:{window}Where {window} is the bucket index (e.g., for a 60-second window, requests at second 120 and second 179 share the same bucket).local window = math.floor(currentSeconds / windowSize)
local key = KEYS[1] .. ':' .. window
local current = redis.call('INCR', key)
if current == 1 then
redis.call('EXPIRE', key, windowSize)
end
Trade-offs
Advantages:
- Very low memory: one INTEGER key per user per active window
- Simple logic, easy to reason about
Disadvantage — boundary bursts: A user can make up to 2x max-requests in a short period by sending max-requests requests at the very end of window N and another max-requests at the start of window N+1. Both buckets count independently, so the burst goes undetected.Configuration
rate-limiter:
use-algo: fixed-window-algo
How it works
The sliding window algorithm stores each request as a unique entry in a Redis ZSET, scored by its millisecond timestamp. On every incoming request, entries older than now - windowSize are pruned first using ZREMRANGEBYSCORE, then the remaining count in the ZSET determines whether to allow or deny the request.This means the limit is always evaluated over the most recent windowSize milliseconds — a true rolling window with no fixed boundaries.Redis key format: rlaas:rate_limit:{userId} (a single ZSET per user)redis.call("ZREMRANGEBYSCORE", key, 0, now - window)
local count = redis.call("ZCARD", key)
if count < limit then
local member = tostring(now) .. "-" .. math.random()
redis.call("ZADD", key, now, member)
redis.call("PEXPIRE", key, window * 2)
return {1, count, window/1000}
else
redis.call("PEXPIRE", key, window * 2)
return {0, count, window/1000}
end
Trade-offs
Advantages:
- Accurate rolling window — boundary bursts are impossible
- The default and recommended choice for most deployments
Disadvantage — higher memory: Each request adds one entry to the ZSET. At high throughput, a user’s ZSET can hold many entries (up to max-requests entries at steady state). Entries are pruned on every request and the key expires after 2 * windowSize of inactivity.Configuration
rate-limiter:
use-algo: sliding-window-algo
Switching algorithms
To change the active algorithm, update the use-algo property in your configuration:
rate-limiter:
use-algo: fixed-window-algo # or sliding-window-algo
Switching the algorithm will immediately affect new requests. Existing Redis keys from the previous algorithm will expire naturally — you do not need to flush Redis manually.
Comparison
| Feature | Fixed Window | Sliding Window |
|---|
| Memory use | Low (1 key/user/window) | Higher (1 entry/request) |
| Boundary burst | Possible (2x limit) | Prevented |
| Accuracy | Approximate | Exact rolling window |
| Redis data type | STRING | ZSET |
| Config value | fixed-window-algo | sliding-window-algo |