ℹ️ Select 'Choose Exercise', or randomize 'Next Random Exercise' in selected language.

Choose Exercise:
Timer 00:00
WPM --
Score --
Acc --
Correct chars --

Redis Lua Sliding Window Counter

Redis Lua

Goal -- WPM

Ready
Exercise Algorithm Area
1local function isSlidingWindowAllowed(key, limit, window_seconds)
2-- Implements a sliding window counter rate limiter.
3-- Args:
4-- key: A unique identifier for the entity being rate-limited (e.g., user ID, IP address).
5-- limit: The maximum number of requests allowed within the window.
6-- window_seconds: The duration of the window in seconds.
7-- Returns:
8-- 1 if the request is allowed, 0 otherwise.
9
10if not key or not limit or not window_seconds then
11return redis.error_reply('Invalid arguments: key, limit, and window_seconds are required.')
12end
13
14local timestamps_key = 'sliding_window:' .. key
15local current_time_ms = redis.call('TIME')[1] * 1000 + redis.call('TIME')[2] -- Get current time in milliseconds
16local window_start_time_ms = current_time_ms - (window_seconds * 1000)
17
18-- Remove timestamps older than the window start
19-- ZREMRANGEBYSCORE removes elements with scores less than or equal to the given score.
20redis.call('ZREMRANGEBYSCORE', timestamps_key, 0, window_start_time_ms)
21
22-- Get the current count of timestamps within the window
23local current_count = redis.call('ZCARD', timestamps_key)
24
25if current_count >= limit then
26-- Exceeded limit
27return 0
28else
29-- Add the current timestamp and check if it's still within limit
30-- Use ZADD with NX to prevent duplicate entries if called multiple times for the same request.
31-- The score is the timestamp itself.
32local added = redis.call('ZADD', timestamps_key, current_time_ms, current_time_ms)
33
34-- After adding, re-check the count to be absolutely sure.
35-- This is a safeguard against potential race conditions if ZADD was called multiple times.
36local new_count = redis.call('ZCARD', timestamps_key)
37
38if new_count > limit then
39-- If adding pushed it over the limit, remove the last added element.
40-- This is important to keep the count accurate for subsequent checks.
41redis.call('ZREM', timestamps_key, current_time_ms)
42return 0
43else
44-- Ensure the ZSET has an expiry to clean up old keys.
45-- If the key is new, ZADD will create it. If it exists, ZADD won't update TTL.
46-- We need to set an expiry that is longer than the window to ensure it persists
47-- until all relevant timestamps are removed by ZREMRANGEBYSCORE.
48if redis.call('TTL', timestamps_key) < 0 then
49-- Set expiry to be slightly more than the window duration.
50redis.call('EXPIRE', timestamps_key, window_seconds + 5) -- Add a small buffer
51end
52return 1
53end
54end
55end
56
57-- Example usage:
58-- local user_id = 'user456'
59-- local request_limit = 5
60-- local window_duration = 30 -- 30 seconds
61-- local allowed = isSlidingWindowAllowed(user_id, request_limit, window_duration)
62-- if allowed == 1 then
63-- print('Request allowed.')
64-- else
65-- print('Request denied: Rate limit exceeded.')
66-- end
Algorithm description viewbox

Redis Lua Sliding Window Counter

Algorithm description:

This Redis Lua script implements a sliding window counter for rate limiting. Unlike fixed window counters, it provides a more accurate measure of request rates by considering requests within a continuously moving time window. It stores timestamps of requests in a Redis sorted set and removes old timestamps before checking the current count against the limit. This approach prevents the "bursty" behavior often seen at window boundaries with fixed windows.

Algorithm explanation:

The `isSlidingWindowAllowed` function uses a Redis Sorted Set (`ZSET`) to store request timestamps for a given `key`. It first calculates the `current_time_ms` and `window_start_time_ms`. Then, it removes all timestamps from the ZSET that are older than `window_start_time_ms` using `ZREMRANGEBYSCORE`. After cleaning up old entries, it gets the current number of elements in the ZSET using `ZCARD`. If this count is already at or above the `limit`, the request is denied. Otherwise, the current timestamp is added to the ZSET using `ZADD`. A crucial step is to re-check the `ZCARD` after adding the new timestamp. If the count now exceeds the limit, the newly added timestamp is removed using `ZREM` to maintain accuracy. Finally, it ensures the ZSET has an expiry set to clean up keys that are no longer in use. The time complexity is O(log N) where N is the number of elements in the sorted set, due to `ZREMRANGEBYSCORE` and `ZADD`. However, in practice, the number of elements within a window is bounded by the limit, making it effectively O(log limit). Space complexity is O(N) where N is the number of requests within the window.

Pseudocode:

FUNCTION isSlidingWindowAllowed(key, limit, window_seconds):
  timestamps_key = 'sliding_window:' + key
  current_time_ms = GET_CURRENT_TIME_IN_MS()
  window_start_time_ms = current_time_ms - (window_seconds * 1000)

  Redis.ZREMRANGEBYSCORE(timestamps_key, 0, window_start_time_ms)

  current_count = Redis.ZCARD(timestamps_key)

  IF current_count >= limit THEN
    RETURN 0
  ELSE
    added = Redis.ZADD(timestamps_key, current_time_ms, current_time_ms)
    new_count = Redis.ZCARD(timestamps_key)

    IF new_count > limit THEN
      Redis.ZREM(timestamps_key, current_time_ms)
      RETURN 0
    ELSE
      IF Redis.TTL(timestamps_key) < 0 THEN
        Redis.EXPIRE(timestamps_key, window_seconds + 5)
      END IF
      RETURN 1
    END IF
  END IF
END FUNCTION