Skip to content

Commit a447320

Browse files
CahidArdaytkimirti
andauthored
DX-1261: allow negative rate values (#143)
* feat: allow negative rate values * chore: format multi.ts * fix: allow for refunding more than the original limit and fix cache * fix: remove duplicate tonumber --------- Co-authored-by: ytkimirti <yusuftaha9@gmail.com>
1 parent 544454b commit a447320

File tree

6 files changed

+473
-160
lines changed

6 files changed

+473
-160
lines changed

src/lua-scripts/hash.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ type Algorithm = {
1212
getRemaining: ScriptInfo,
1313
}
1414

15-
type AlgorithmKind =
15+
type AlgorithmKind =
1616
| "fixedWindow"
1717
| "slidingWindow"
1818
| "tokenBucket"
@@ -36,7 +36,7 @@ export const SCRIPTS: {
3636
slidingWindow: {
3737
limit: {
3838
script: Single.slidingWindowLimitScript,
39-
hash: "e1391e429b699c780eb0480350cd5b7280fd9213"
39+
hash: "9b7842963bd73721f1a3011650c23c0010848ee3"
4040
},
4141
getRemaining: {
4242
script: Single.slidingWindowRemainingTokensScript,
@@ -46,7 +46,7 @@ export const SCRIPTS: {
4646
tokenBucket: {
4747
limit: {
4848
script: Single.tokenBucketLimitScript,
49-
hash: "5bece90aeef8189a8cfd28995b479529e270b3c6"
49+
hash: "d1f857ebbdaeca90ccd2cd4eada61d7c8e5db1ca"
5050
},
5151
getRemaining: {
5252
script: Single.tokenBucketRemainingTokensScript,
@@ -78,7 +78,7 @@ export const SCRIPTS: {
7878
slidingWindow: {
7979
limit: {
8080
script: Multi.slidingWindowLimitScript,
81-
hash: "cb4fdc2575056df7c6d422764df0de3a08d6753b"
81+
hash: "1e7ca8dcd2d600a6d0124a67a57ea225ed62921b"
8282
},
8383
getRemaining: {
8484
script: Multi.slidingWindowRemainingTokensScript,
@@ -92,4 +92,4 @@ export const SCRIPTS: {
9292
export const RESET_SCRIPT: ScriptInfo = {
9393
script: resetScript,
9494
hash: "54bd274ddc59fb3be0f42deee2f64322a10e2b50"
95-
}
95+
}

src/lua-scripts/multi.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ export const slidingWindowLimitScript = `
4545
end
4646
4747
local percentageInCurrent = ( now % window) / window
48-
if requestsInPreviousWindow * (1 - percentageInCurrent ) + requestsInCurrentWindow >= tokens then
48+
49+
-- Only check limit if not refunding (negative rate)
50+
if incrementBy > 0 and requestsInPreviousWindow * (1 - percentageInCurrent ) + requestsInCurrentWindow + incrementBy > tokens then
4951
return {currentFields, previousFields, false}
5052
end
5153

src/lua-scripts/single.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export const slidingWindowLimitScript = `
3030
local tokens = tonumber(ARGV[1]) -- tokens per window
3131
local now = ARGV[2] -- current timestamp in milliseconds
3232
local window = ARGV[3] -- interval in milliseconds
33-
local incrementBy = ARGV[4] -- increment rate per request at a given value, default is 1
33+
local incrementBy = tonumber(ARGV[4]) -- increment rate per request at a given value, default is 1
3434
3535
local requestsInCurrentWindow = redis.call("GET", currentKey)
3636
if requestsInCurrentWindow == false then
@@ -44,12 +44,14 @@ export const slidingWindowLimitScript = `
4444
local percentageInCurrent = ( now % window ) / window
4545
-- weighted requests to consider from the previous window
4646
requestsInPreviousWindow = math.floor(( 1 - percentageInCurrent ) * requestsInPreviousWindow)
47-
if requestsInPreviousWindow + requestsInCurrentWindow >= tokens then
47+
48+
-- Only check limit if not refunding (negative rate)
49+
if incrementBy > 0 and requestsInPreviousWindow + requestsInCurrentWindow >= tokens then
4850
return -1
4951
end
5052
5153
local newValue = redis.call("INCRBY", currentKey, incrementBy)
52-
if newValue == tonumber(incrementBy) then
54+
if newValue == incrementBy then
5355
-- The first time this key is set, the value will be equal to incrementBy.
5456
-- So we only need the expire command once
5557
redis.call("PEXPIRE", currentKey, window * 2 + 1000) -- Enough time to overlap with a new window + 1 second
@@ -108,15 +110,19 @@ export const tokenBucketLimitScript = `
108110
refilledAt = refilledAt + numRefills * interval
109111
end
110112
111-
if tokens == 0 then
113+
-- Only reject if tokens are 0 and we're consuming (not refunding)
114+
if tokens == 0 and incrementBy > 0 then
112115
return {-1, refilledAt + interval}
113116
end
114117
115118
local remaining = tokens - incrementBy
116119
local expireAt = math.ceil(((maxTokens - remaining) / refillRate)) * interval
117120
118121
redis.call("HSET", key, "refilledAt", refilledAt, "tokens", remaining)
119-
redis.call("PEXPIRE", key, expireAt)
122+
123+
if (expireAt > 0) then
124+
redis.call("PEXPIRE", key, expireAt)
125+
end
120126
return {remaining, refilledAt + interval}
121127
`;
122128

0 commit comments

Comments
 (0)