RL
GitHub
Documentation

Fixed Counter

Fast fixed-window limits with boundary tradeoffs.

Fixed Window Counter: Simplicity With a Known Edge Case

The failure mode it solves

Sometimes the requirement is not perfect fairness. It is speed and simplicity. Fixed Window Counter is the fastest algorithm in this project and the easiest to reason about: count requests in a window, then reset. It is a good default for internal services or low-risk endpoints where boundary spikes are acceptable.

How the algorithm works in this service

Each IP + endpoint pair maps to a Redis key. The Lua script increments the counter and sets the TTL on first write. That TTL defines the window boundary.

Step-by-step flow:

  1. INCR the Redis key rate-limit:{ip}:{endpoint}.
  2. If the counter is 1, set the TTL to windowSize seconds.
  3. If count <= maxRequests, allow. Otherwise reject.

Diagram

Fixed window

The window is fixed to TTL boundaries, not relative to each client. That makes the algorithm fast, but it also creates boundary spikes.

Worked example: the boundary spike

Window size is 60 seconds with maxRequests 100. A client sends 100 requests between 00:59 and 01:00. All 100 are accepted. At 01:00 the counter resets, and the client can immediately send another 100 requests. The effective rate is 200 requests in two seconds, even though the limit is 100 per minute. This is the known tradeoff.

Where it fits versus the other algorithms

AlgorithmStrengthWeaknessBest fit
Fixed CounterFastest, smallest memory footprintBoundary spikesInternal or low-risk APIs
Token BucketBurst-friendly with average controlNot uniform spacingPublic APIs
Leaky BucketSmooths output rateAdds queueing latencyBackend protection
Sliding WindowMost accurate enforcementHighest Redis costBilling and security

Configuration in this project

yaml
rate-limit:
  fallback:
    algorithm: FIXED_COUNTER
    maxRequests: 1000
    windowSize: 60

windowSize is in seconds. maxRequests is the limit within that window. Increasing windowSize lowers per-second allowance but makes the boundary spike larger in absolute terms. If boundary spikes are unacceptable, use Sliding Window instead.

Redis state and TTL

Fixed Counter uses a single Redis string with a TTL:

text
key: rate-limit:{ip}:{endpoint}
value: request_count
ttl: windowSize

TTL defines the window boundary. Keep it aligned with the intended limits so clients can reason about reset timing from X-RateLimit-ResetOn.