FIX: sliding window end time in rate limiter (#11691)

If the sliding window size is N seconds, then a moment at the Nth second
should be considered as the moment outside of the sliding window.

Otherwise, if the sliding window is already full, at the Nth second,
a new call wouldn't be allowed, but a time to wait before the next call
would be equal to zero, which is confusing.

In other words, the end of the time range shouldn't be included in the
sliding window.

Let's say we start at the second 0, and the sliding window size is 10
seconds. In the current version of rate limiter, this sliding window will
be considered as a time range [0, 10] (including the end of the range),
which actually is 11 seconds in length.

After this fix, the time range will be considered as [0, 10)
(excluding the end of the range), which is exactly 10 seconds in length.
This commit is contained in:
Andrew Prigorshnev
2021-01-12 13:26:43 -05:00
committed by GitHub
parent ec0212e56b
commit e25dd41aee
2 changed files with 17 additions and 8 deletions
+4 -4
View File
@@ -66,7 +66,7 @@ class RateLimiter
if ((tonumber(redis.call("LLEN", key)) < max) or
(now - tonumber(redis.call("LRANGE", key, -1, -1)[1])) > secs) then
(now - tonumber(redis.call("LRANGE", key, -1, -1)[1])) >= secs) then
redis.call("LPUSH", key, now)
redis.call("LTRIM", key, 0, max - 1)
redis.call("EXPIRE", key, secs * 2)
@@ -91,7 +91,7 @@ class RateLimiter
local return_val = 0
if ((tonumber(redis.call("LLEN", key)) < max) or
(now - tonumber(redis.call("LRANGE", key, -1, -1)[1])) > secs) then
(now - tonumber(redis.call("LRANGE", key, -1, -1)[1])) >= secs) then
return_val = 1
else
return_val = 0
@@ -185,8 +185,8 @@ class RateLimiter
def is_under_limit?
# number of events in buffer less than max allowed? OR
(redis.llen(prefixed_key) < @max) ||
# age bigger than silding window size?
(age_of_oldest > @secs)
# age bigger or equal than sliding window size?
(age_of_oldest >= @secs)
end
def rate_unlimited?