@@ -4,24 +4,67 @@ import { ICacheAdapter } from '../@types/adapters'
44
55const logger = createLogger ( 'sliding-window-rate-limiter' )
66
7+ const SLIDING_WINDOW_RATE_LIMITER_LUA_SCRIPT = `
8+ local key = KEYS[1]
9+ local timestamp = tonumber(ARGV[1])
10+ local period = tonumber(ARGV[2])
11+ local step = tonumber(ARGV[3])
12+ local max_rate = tonumber(ARGV[4])
13+
14+ local windowStart = timestamp - period
15+
16+ redis.call('ZREMRANGEBYSCORE', key, 0, windowStart)
17+
18+ local entries = redis.call('ZRANGE', key, 0, -1)
19+ local hits = 0
20+ for i=1, #entries do
21+ local step_str = string.match(entries[i], "^[^:]+:([^:]+)")
22+ if step_str then
23+ local entry_step = tonumber(step_str)
24+ if entry_step then
25+ hits = hits + entry_step
26+ end
27+ end
28+ end
29+
30+ if hits + step > max_rate then
31+ return 1
32+ end
33+
34+ local base_member = timestamp .. ':' .. step
35+ local member = base_member
36+ local counter = 0
37+ while redis.call('ZSCORE', key, member) do
38+ counter = counter + 1
39+ member = base_member .. ':' .. counter
40+ end
41+
42+ redis.call('ZADD', key, timestamp, member)
43+ redis.call('PEXPIRE', key, period)
44+
45+ return 0
46+ `
47+
748export class SlidingWindowRateLimiter implements IRateLimiter {
8- public constructor ( private readonly cache : ICacheAdapter ) { }
49+ public constructor (
50+ private readonly cache : ICacheAdapter ,
51+ ) { }
952
1053 public async hit ( key : string , step : number , options : IRateLimiterOptions ) : Promise < boolean > {
1154 const timestamp = Date . now ( )
12- const { period } = options
55+ const { period, rate } = options
1356
14- const [ , , entries ] = await Promise . all ( [
15- this . cache . removeRangeByScoreFromSortedSet ( key , 0 , timestamp - period ) ,
16- this . cache . addToSortedSet ( key , { [ ` ${ timestamp } : ${ step } ` ] : timestamp . toString ( ) } ) ,
17- this . cache . getRangeFromSortedSet ( key , 0 , - 1 ) ,
18- this . cache . setKeyExpiry ( key , period ) ,
57+ const result = await this . cache . eval ( SLIDING_WINDOW_RATE_LIMITER_LUA_SCRIPT , [ key ] , [
58+ timestamp . toString ( ) ,
59+ period . toString ( ) ,
60+ step . toString ( ) ,
61+ rate . toString ( ) ,
1962 ] )
2063
21- const hits = entries . reduce ( ( acc , timestampAndStep ) => acc + Number ( timestampAndStep . split ( ':' ) [ 1 ] ) , 0 )
64+ const isRateLimited = result === 1 || result === '1'
2265
23- logger ( 'hit count on %s bucket: %d ' , key , hits )
66+ logger ( 'hit on %s bucket: is rate limited? %s ' , key , isRateLimited )
2467
25- return hits > options . rate
68+ return isRateLimited
2669 }
2770}
0 commit comments