Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save atomaras/925a13f07c24df7f15dcc4fb7bc89c81 to your computer and use it in GitHub Desktop.
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
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}
@atomaras
Copy link
Author

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:
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment