Created
March 28, 2024 01:07
-
-
Save atomaras/925a13f07c24df7f15dcc4fb7bc89c81 to your computer and use it in GitHub Desktop.
Redis Lua Script that implements the Sliding Window Rate Limiting algo with support for returning remaining_tokens
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local curr_key = KEYS[1] -- Get the key from the KEYS array. | |
local current_time = redis.call('TIME') -- Get the current server time. | |
local current_seconds = tonumber(current_time[1]) -- Extract the seconds part and convert it to a number. | |
local num_windows = tonumber(ARGV[1]) -- Get the number of windows/rules from the first argument. | |
local request_counts = {} -- Store the request counts for each window. | |
-- Iterate through each window/rule to check the rate limits and get initial request counts. | |
for i = 2, num_windows * 2, 2 do | |
local window_seconds = tonumber(ARGV[i]) -- Get the window duration in seconds. | |
local max_requests = tonumber(ARGV[i + 1]) -- Get the maximum number of requests allowed in this window. | |
local trim_time_seconds = current_seconds - window_seconds -- Calculate the time before which requests should be removed. | |
-- Remove requests that are outside the current window. | |
redis.call('ZREMRANGEBYSCORE', curr_key, '-inf', trim_time_seconds) | |
-- Get the current number of requests in the window. | |
local request_count = redis.call('ZCARD', curr_key) | |
request_counts[i / 2] = request_count -- Store the request count for later use. | |
-- Check if the current number of requests exceeds the maximum allowed. | |
if request_count >= max_requests then | |
return {1} -- Return 1 if the rate limit is exceeded. | |
end | |
end | |
-- If the rate limit is not exceeded, add the current request to each window and update the counts. | |
local min_remaining_tokens = nil -- Initialize the minimum remaining tokens. | |
for i = 2, num_windows * 2, 2 do | |
local window_seconds = tonumber(ARGV[i]) -- Get the window duration in seconds, moved up here. | |
local max_requests = tonumber(ARGV[i + 1]) | |
-- Add the current request timestamp to the sorted set and set the expiration. | |
redis.call('ZADD', curr_key, current_seconds, current_seconds .. current_time[2]) | |
redis.call('EXPIRE', curr_key, window_seconds) | |
-- Increment the stored request count and calculate remaining tokens. | |
request_counts[i/2] = request_counts[i/2] + 1 | |
local remaining_tokens = max_requests - request_counts[i/2] | |
-- Determine the minimum remaining tokens across all windows. | |
if min_remaining_tokens == nil or remaining_tokens < min_remaining_tokens then | |
min_remaining_tokens = remaining_tokens | |
end | |
end | |
-- Return 0 along with the minimum number of remaining tokens. | |
return {0, min_remaining_tokens} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Enhanced version of this https://developer.redis.com/develop/dotnet/aspnetcore/rate-limiting/middleware#build-the-middleware but only supports 1 key (multiple rules per key/endpoint).
Returns the remaining tokens so your web api can return the X-Rate-Limit-Remaining header to its clients.
Usage: