keter-rate-limiting-plugin-0.2.0.0: Simple Keter rate limiting plugin.
Copyright(c) 2025 Oleksandr Zhabenko
LicenseMIT
Maintaineroleksandr.zhabenko@yahoo.com
Stabilitystable
Portabilityportable
Safe HaskellNone
LanguageHaskell2010

Keter.RateLimiter.SlidingWindow

Description

This module provides an implementation of the Sliding Window Counter algorithm for rate limiting. It is implemented using STM primitives and integrates with the Keter.RateLimiter.Cache key structure.

A sliding window allows a fixed number of requests within a moving time interval (window). It tracks individual request timestamps and filters them to only keep those within the defined time window. This allows for fine-grained request control and smooth rate-limiting behavior.

Example usage

let getTimeNow = realToFrac <$> getPOSIXTime
result <- allowRequest getTimeNow myMap "zone1" "user42" 60 100
when result (putStrLn "Request allowed")

This example checks whether "user42" from "zone1" can make a request, allowing up to 100 requests in a 60-second window.

Synopsis

Sliding Window Rate Limiting

allowRequest Source #

Arguments

:: IO Double

Action to get current time as fractional seconds since epoch

-> TVar (Map Text [Double])

STM map storing per-client timestamp lists

-> Text

Throttle name (logical grouping identifier)

-> Text

IP zone identifier for multi-tenant isolation

-> Text

User key (unique client identifier)

-> Int

Sliding window size in seconds (must be positive)

-> Int

Maximum allowed requests within the time window (must be positive)

-> IO Bool

True if request is allowed, False if rate-limited

Check whether a request is allowed under the sliding window policy.

This function implements a time-based sliding window algorithm. Each client is associated with a list of timestamps representing past allowed requests. If the number of timestamps in the current time window exceeds the limit, the request is denied.

This version is thread-safe and uses STM to avoid race conditions under concurrency. The timestamp list is updated atomically using focus.

Algorithm Steps

  1. Time Acquisition: Get current timestamp using provided time function
  2. Key Construction: Build composite cache key from throttle name, IP zone, and user key
  3. Atomic Update: Use STM Focus to atomically read, filter, and update timestamp list
  4. Window Filtering: Remove timestamps older than the sliding window
  5. Limit Check: Allow request if filtered list length is below limit
  6. State Update: Add current timestamp to list if request is allowed

Memory Management

The algorithm automatically cleans up old timestamps and removes empty entries from the map, ensuring efficient memory usage for long-running applications.

Examples

Expand
-- Basic API rate limiting: 100 requests per minute
getTime <- realToFrac <$> getPOSIXTime
allowed <- allowRequest (pure getTime) stmMapTVar "api" "zone1" "user123" 60 100
if allowed 
  then processApiRequest
  else sendRateLimitError
-- High-frequency trading API: 1000 requests per second
let getTimeIO = realToFrac <$> getPOSIXTime
result <- allowRequest getTimeIO storage "hft" "premium" "trader456" 1 1000
when result $ executeTrade
-- Multi-tier rate limiting with different windows
let (windowSecs, maxReqs) = case userTier of
      Premium  -> (60, 1000)    -- 1000 requests per minute
      Standard -> (60, 100)     -- 100 requests per minute  
      Free     -> (3600, 50)    -- 50 requests per hour

allowed <- allowRequest getTime storage "tiered" zone userId windowSecs maxReqs

Thread Safety: All operations are atomic via STM. Multiple threads can safely call this function concurrently for the same or different keys.

Performance: Time complexity is O(n) where n is the number of timestamps within the sliding window. Space complexity is O(k*n) where k is the number of active clients.