- Notifications
You must be signed in to change notification settings - Fork 2.8k
feat(limit-count): add sliding window support for redis-based policies #12756
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Signed-off-by: Sihyeon Jang <sihyeon.jang@navercorp.com>
Signed-off-by: Sihyeon Jang <sihyeon.jang@navercorp.com>
Signed-off-by: Sihyeon Jang <sihyeon.jang@navercorp.com>
Signed-off-by: Sihyeon Jang <sihyeon.jang@navercorp.com>
| end | ||
| for i = 1, cost do | ||
| redis.call('ZADD', KEYS[1], now, now .. ':' .. i) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In high-concurrency scenarios, uniqueness may not be guaranteed. I think we can utilize ngx.var.request_id
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @sihyeonn, any updates?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry to late and also thanks for the review and the suggestion about using ngx.var.request_id for uniqueness under high concurrency.
I’ve updated both Redis and Redis Cluster sliding window implementations so that the Lua scripts now always use ngx.var.request_id as part of the ZSET member value (i.e. "$request_id:").
Signed-off-by: Sihyeon Jang <sihyeon.jang@navercorp.com>
Signed-off-by: Sihyeon Jang <sihyeon.jang@navercorp.com>
| for i = 1, cost do | ||
| local member = req_id .. ':' .. i | ||
| redis.call('ZADD', KEYS[1], now, member) | ||
| end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the current implementation, each request needs to record an entry in the Redis set. This puts too much pressure on Redis and is probably not a good solution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for raising this concern!
In this sliding-window implementation, we intentionally trade some Redis work per request for exact “N requests per rolling window” semantics. However, the memory and per-key load are bounded:
- on every request we first call
ZREMRANGEBYSCOREto drop all events outside of the current window and then enforcecurrent + cost <= limitbefore inserting new members, - rejected requests do not insert new entries,
- so for each key the ZSET cardinality is always
<= limit, and Redis memory for this key is bounded by that limit.
Because of this, even though we do one ZADD per allowed request, we don’t end up with unbounded growth like in some TTL-only designs – the sliding window state per key stays O(limit). Also, window_type = "sliding" is an opt-in mode and the docs explicitly call out that it is more expensive than the default fixed window, so it’s intended for use cases that really need accurate rolling windows and can accept the extra Redis cost.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest you use the approximate sliding window mentioned in this blog https://blog.cloudflare.com/counting-things-a-lot-of-different-things/ to implement this function. You can refer to the code here: https://github.com/ElvinEfendi/lua-resty-global-throttle/blob/main/lib/resty/global_throttle/sliding_window.lua
Description
This PR introduces sliding window support to the
limit-countplugin for Redis- and Redis Cluster–based policies while preserving the existing fixed window behavior by default.window_typeoption (fixed|sliding) to thelimit-countplugin configuration, withfixedas the default for backward compatibility.window_type = "sliding"is only allowed when thepolicyisredisorredis-clusterto avoid unsupported combinations.limit-count-redis) and Redis Cluster (limit-count-redis-cluster) implementations to enforce an exact N requests per rolling time window whenwindow_typeis set tosliding.limit-count-redis-sliding.t,limit-count-redis-cluster-sliding.t).limit-countplugin documentation to describe the newwindow_typeoption, its behavior, and performance considerations when using sliding windows.The default behavior remains unchanged for existing configurations that do not specify
window_typeor continue to usefixed.Which issue(s) this PR fixes:
Fixes #
Checklist