Skip to content

Conversation

@sihyeonn
Copy link

Description

This PR introduces sliding window support to the limit-count plugin for Redis- and Redis Cluster–based policies while preserving the existing fixed window behavior by default.

  • Adds a new window_type option (fixed | sliding) to the limit-count plugin configuration, with fixed as the default for backward compatibility.
  • Enforces that window_type = "sliding" is only allowed when the policy is redis or redis-cluster to avoid unsupported combinations.
  • Extends the Redis (limit-count-redis) and Redis Cluster (limit-count-redis-cluster) implementations to enforce an exact N requests per rolling time window when window_type is set to sliding.
  • Adds test cases for the sliding window behavior in both Redis and Redis Cluster modes (limit-count-redis-sliding.t, limit-count-redis-cluster-sliding.t).
  • Updates the English and Chinese limit-count plugin documentation to describe the new window_type option, its behavior, and performance considerations when using sliding windows.

The default behavior remains unchanged for existing configurations that do not specify window_type or continue to use fixed.

Which issue(s) this PR fixes:

Fixes #

Checklist

  • I have explained the need for this PR and the problem it solves
  • I have explained the changes or the new features added to this PR
  • I have added tests corresponding to this change
  • I have updated the documentation to reflect this change
  • I have verified that this change is backward compatible (If not, please discuss on the APISIX mailing list first)
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>
@dosubot dosubot bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label Nov 18, 2025
@sihyeonn sihyeonn changed the title eat(limit-count): add sliding window support for redis-based policies feat(limit-count): add sliding window support for redis-based policies Nov 18, 2025
@dosubot dosubot bot added the enhancement New feature or request label Nov 18, 2025
end
for i = 1, cost do
redis.call('ZADD', KEYS[1], now, now .. ':' .. i)
Copy link
Contributor

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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @sihyeonn, any updates?

Copy link
Author

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:").

@Baoyuantop Baoyuantop added the wait for update wait for the author's response in this issue/PR label Nov 26, 2025
Signed-off-by: Sihyeon Jang <sihyeon.jang@navercorp.com>
Signed-off-by: Sihyeon Jang <sihyeon.jang@navercorp.com>
@Baoyuantop Baoyuantop removed the wait for update wait for the author's response in this issue/PR label Dec 9, 2025
Comment on lines +70 to +73
for i = 1, cost do
local member = req_id .. ':' .. i
redis.call('ZADD', KEYS[1], now, member)
end
Copy link
Member

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.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nic-6443

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 ZREMRANGEBYSCORE to drop all events outside of the current window and then enforce current + cost <= limit before 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.

Copy link
Member

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

@sihyeonn sihyeonn requested a review from nic-6443 December 9, 2025 07:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:XL This PR changes 500-999 lines, ignoring generated files.

3 participants