π 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)