π What Is Fixed Window Rate Limiting?
Fixed Window Rate Limiting is a straightforward algorithm that controls request rates by dividing time into fixed intervals (windows) and allowing a maximum number of requests per window.
Example:
If an API allows 100 requests per minute:
- The counter resets at the start of each minute.
- A user making 100 requests at 00:59:59 can immediately make 100 more after 01:00:00. This can cause sudden bursts at window boundaries.
π Example Scenario
- Use Case: Login API with a limit of 10 attempts per minute.
- Behavior:
- A user can try 10 times in the current minute.
- After the window resets, the counter refreshes, allowing another 10 attempts.
β Benefits
- Simple and easy to implement.
- Minimal overhead and resource usage.
- Easy to debug and understand.
β οΈ Limitations
- Burstiness: Allows spikes at window boundaries.
- Precision: Less smooth than sliding window methods.
- Distributed Challenges: Single in-memory counter wonβt work across multiple instances without central coordination.
π» Single-Machine Implementation (Thread-Safe Java)
When to Use:
- Small-scale services.
- Non-critical endpoints.
- Internal services where distributed coordination isnβt needed.
Code for Single-Machine Fixed Window Rate Limiter:
import java.time.Duration; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; /** * Single-machine Fixed Window Rate Limiter. * Thread-safe implementation for small-scale services. */ public class FixedRateLimiter implements IRateLimiter { private final Timer timer; // Provides current time (injectable for testing) private final Map<String, FixedWindow> map; // Stores request counts per user/requestId private final Duration windowSize; // Size of fixed time window private final int capacity; // Max requests per window public FixedRateLimiter(Duration windowSize, int capacity, Timer timer) { this.timer = timer; this.map = new ConcurrentHashMap<>(); this.windowSize = windowSize; this.capacity = capacity; } /** * Checks if a request is allowed for the given requestId. * * @param requestId Unique identifier for a client/user * @return true if allowed, false if rate limit exceeded */ @Override public boolean isAllowed(String requestId) { long currentTimeMillis = timer.currentTimeMillis(); // Get or create FixedWindow for this requestId FixedWindow fixedWindow = map.computeIfAbsent( requestId, e -> new FixedWindow(new AtomicInteger(capacity), currentTimeMillis) ); // Reset window if current time exceeds previous window if (currentTimeMillis - fixedWindow.lastAccessTime > windowSize.toMillis()) { synchronized (fixedWindow) { if (currentTimeMillis - fixedWindow.lastAccessTime > windowSize.toMillis()) { fixedWindow.lastAccessTime = currentTimeMillis; fixedWindow.requestCount.set(capacity); // Reset request count } } } // Allow request if capacity remains int remaining = fixedWindow.requestCount.get(); if (remaining > 0) { fixedWindow.requestCount.decrementAndGet(); return true; } return false; // Deny if limit reached } /** * Internal class to hold request count and last access time per window. */ private static class FixedWindow { final AtomicInteger requestCount; // Tracks remaining requests volatile long lastAccessTime; // Timestamp of window start FixedWindow(AtomicInteger requestCount, long lastAccessTime) { this.requestCount = requestCount; this.lastAccessTime = lastAccessTime; } } /** * Timer abstraction for easy testing (injectable current time provider) */ public record Timer() { public long currentTimeMillis() { return System.currentTimeMillis(); } } }
π Distributed Implementation (Redis + Java)
When to Use:
- High-scale APIs across multiple instances.
- Requires central coordination to prevent users from exceeding limits globally.
Code for Redis-Based Fixed Window Rate Limiter:
import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; /** * Distributed Fixed Window Rate Limiter using Redis. * Suitable for multi-instance applications. */ public class RedisFixedRateLimiter { private final JedisPool jedisPool; // Redis connection pool private final String rateLimitKeyPrefix; // Prefix for Redis keys, e.g., "rate_limit:" private final int maxRequestsPerWindow; // Max requests allowed per window private final int windowDurationInSeconds; // Window size in seconds public RedisFixedRateLimiter(JedisPool jedisPool, String rateLimitKeyPrefix, int maxRequestsPerWindow, int windowDurationInSeconds) { this.jedisPool = jedisPool; this.rateLimitKeyPrefix = rateLimitKeyPrefix; this.maxRequestsPerWindow = maxRequestsPerWindow; this.windowDurationInSeconds = windowDurationInSeconds; } /** * Checks if a request is allowed for a given userId. * * @param userId Unique identifier for the client/user * @return true if request is allowed, false if rate limit exceeded */ public boolean isRequestAllowed(String userId) { String key = rateLimitKeyPrefix + userId; try (Jedis jedis = jedisPool.getResource()) { // Atomically increment the request count in Redis long currentCount = jedis.incr(key); // Set expiration only for the first request in the window if (currentCount == 1) { jedis.expire(key, windowDurationInSeconds); } // Allow if count does not exceed the maximum return currentCount <= maxRequestsPerWindow; } catch (Exception e) { // Fail-open: allow requests if Redis is unavailable e.printStackTrace(); return true; } } }
β‘ Fault Tolerance for Redis
- Redis Down: Implement a fail-open policy (allow requests) or local fallback counters.
- Circuit Breaker: Temporarily stop excessive traffic when Redis is unreachable.
- Graceful Degradation: Use in-memory counters with a short TTL to limit impact until Redis recovers.
β Summary
- Fixed Window is simple, efficient, and effective for many straightforward use cases.
- Main limitation: bursts at window boundaries.
- Single-machine approach: Suitable for low-traffic, non-critical services.
- Redis-based approach: Ideal for distributed high-traffic environments but requires fault-tolerance planning.
Top comments (0)