Implementing Sliding Window Rate Limiting
Implementing Sliding Window Rate Limiting
Sliding window algorithms provide more accurate rate limiting than fixed windows by considering a continuous time period rather than discrete intervals. This approach prevents burst attacks at window boundaries and provides smoother rate limiting behavior. Several sliding window implementations offer different trade-offs between accuracy and computational complexity.
// Node.js implementation of sliding window rate limiter using Redis
const redis = require('redis');
const client = redis.createClient();
class SlidingWindowRateLimiter {
constructor(windowSize, limit) {
this.windowSize = windowSize; // in seconds
this.limit = limit;
}
async checkLimit(clientId) {
const now = Date.now();
const windowStart = now - (this.windowSize * 1000);
const key = `rate_limit:${clientId}`;
// Remove old entries
await client.zremrangebyscore(key, '-inf', windowStart);
// Count requests in current window
const count = await client.zcard(key);
if (count < this.limit) {
// Add current request
await client.zadd(key, now, `${now}-${Math.random()}`);
await client.expire(key, this.windowSize);
return {
allowed: true,
remaining: this.limit - count - 1,
resetAt: now + (this.windowSize * 1000)
};
}
// Calculate when oldest request will expire
const oldestRequest = await client.zrange(key, 0, 0, 'WITHSCORES');
const retryAfter = oldestRequest[1] ?
Math.ceil((parseInt(oldestRequest[1]) + (this.windowSize * 1000) - now) / 1000) :
this.windowSize;
return {
allowed: false,
remaining: 0,
resetAt: now + (retryAfter * 1000),
retryAfter: retryAfter
};
}
}
// Express middleware
const rateLimiter = new SlidingWindowRateLimiter(3600, 1000); // 1000 requests per hour
async function rateLimitMiddleware(req, res, next) {
const clientId = req.headers['x-api-key'] || req.ip;
const result = await rateLimiter.checkLimit(clientId);
res.set({
'X-RateLimit-Limit': rateLimiter.limit,
'X-RateLimit-Remaining': result.remaining,
'X-RateLimit-Reset': new Date(result.resetAt).toISOString()
});
if (!result.allowed) {
res.set('Retry-After', result.retryAfter);
return res.status(429).json({
error: 'Too Many Requests',
message: 'Rate limit exceeded',
retryAfter: result.retryAfter
});
}
next();
}
Distributed rate limiting becomes essential when APIs run across multiple servers. Centralized storage in Redis or similar systems enables consistent rate limiting across all API instances. Consider eventual consistency trade-offs when designing distributed rate limiters. Implement local caching with conservative limits to handle temporary connectivity issues with the central store.