@@ -21,45 +21,65 @@ module.exports = class Limiter {
2121 local max = tonumber(ARGV[3])
2222 local start = now - duration * 1000
2323
24- -- Remove expired entries
25- redis.call('zremrangebyscore ', key, 0, start )
24+ -- Check if the key exists
25+ local exists = redis.call('EXISTS ', key)
2626
27- -- Get current count
28- local count = redis.call('zcard', key)
27+ local count = 0
28+ local oldest = now
2929
30- -- Calculate remaining
31- local remaining = max - count
30+ if exists == 1 then
31+ -- Remove expired entries based on the current duration
32+ redis.call('ZREMRANGEBYSCORE', key, 0, start)
3233
33- -- Add current request
34- redis.call('zadd ', key, now, now )
34+ -- Get count
35+ count = redis.call('ZCARD ', key)
3536
36- -- Optimize: Only fetch oldest entry if we need it
37- local oldest
38- local oldest_result = redis.call('zrange', key, 0, 0)
39- oldest = #oldest_result > 0 and tonumber(oldest_result[1]) or now
37+ -- Get oldest timestamp if we have entries
38+ if count > 0 then
39+ local oldest_result = redis.call('ZRANGE', key, 0, 0)
40+ oldest = tonumber(oldest_result[1])
41+ end
42+ end
4043
41- -- Optimize: Only fetch oldestInRange if count is at or above max
42- local oldestInRange = now
43- if count >= max then
44- local oldest_in_range_result = redis.call('zrange', key, -max, -max)
45- oldestInRange = #oldest_in_range_result > 0 and tonumber(oldest_in_range_result[1]) or now
44+ -- Calculate remaining (before adding current request)
45+ local remaining = max - count
46+
47+ -- Early return if already at limit
48+ if remaining <= 0 then
49+ local resetMicro = oldest + duration * 1000
50+ return {0, math.floor(resetMicro / 1000000), max}
4651 end
4752
48- -- Calculate reset time
49- local resetMicro = (oldestInRange ~= now and oldestInRange or oldest) + duration * 1000
53+ -- Add current request with current timestamp
54+ redis.call('ZADD', key, now, now)
55+
56+ -- Calculate reset time and handle trimming if needed
57+ local resetMicro
5058
51- -- Optimize: Only trim if necessary
59+ -- Only perform trim if we're at or over max (based on count before adding)
5260 if count >= max then
53- redis.call('zremrangebyrank', key, 0, -(max + 1))
61+ -- Get the entry at position -max for reset time calculation
62+ local oldest_in_range_result = redis.call('ZRANGE', key, -max, -max)
63+ local oldestInRange = oldest
64+
65+ if #oldest_in_range_result > 0 then
66+ oldestInRange = tonumber(oldest_in_range_result[1])
67+ end
68+
69+ -- Trim the set
70+ redis.call('ZREMRANGEBYRANK', key, 0, -(max + 1))
71+
72+ -- Calculate reset time based on the entry at position -max
73+ resetMicro = oldestInRange + duration * 1000
74+ else
75+ -- We're under the limit, use the oldest entry for reset time
76+ resetMicro = oldest + duration * 1000
5477 end
5578
56- -- Set expiration
57- redis.call('pexpire', key, duration)
58-
59- -- Ensure remaining is never negative
60- if remaining < 0 then remaining = 0 end
79+ -- Set expiration using the provided duration
80+ redis.call('PEXPIRE', key, duration)
6181
62- return {remaining, resetMicro / 1000000, max}
82+ return {remaining, math.floor( resetMicro / 1000000) , max}
6383 `
6484 } )
6585 }
0 commit comments