Redis Rate Limiting — Complete Distributed Implementation Guide
In this tutorial, you will learn about Redis Rate Limiting. We cover key concepts, practical examples, and best practices to help you master this topic.
Redis is the most popular backing store for distributed rate limiting. Its atomic operations, TTL, and sorted sets make it ideal for implementing consistent rate limits across multiple API servers.
What You'll Learn
You'll learn Redis patterns for rate limiting including INCR, EXPIRE, and sorted sets for Sliding Window.
Why It Matters
In-memory rate limiting does not work across multiple server instances. Redis provides shared state with sub-millisecond latency, enabling consistent rate limits in distributed deployments.
Real-World Use
Kong API Gateway uses Redis for cluster-wide rate limiting. When deployed with 10 gateway instances, the rate limit is enforced collectively, not per-instance. A client cannot bypass limits by rotating through instances.
Implementation
import redis
import time
redis_client = redis.Redis(host="localhost", port=6379, db=0, decode_responses=True)
def fixed_window_redis(client_id, limit, window):
key = f"ratelimit:{client_id}:{int(time.time()) // window}"
count = redis_client.incr(key)
if count == 1:
redis_client.expire(key, window)
if count > limit:
return False, 0
return True, limit - count
def sliding_window_redis(client_id, limit, window):
now = int(time.time() * 1000)
key = f"sliding:{client_id}"
cutoff = now - (window * 1000)
redis_client.zremrangebyscore(key, 0, cutoff)
current = redis_client.zcard(key)
if current >= limit:
oldest = redis_client.zrange(key, 0, 0, withscores=True)
retry_after = (oldest[0][1] + window * 1000 - now) / 1000 if oldest else window
return False, retry_after
redis_client.zadd(key, {str(now): now})
redis_client.expire(key, window)
return True, 0
allowed, remaining = fixed_window_redis("user:123", 100, 60)
print(f"Allowed: {allowed}, Remaining: {remaining}")
# Redis Lua script for atomic rate limiting
import hashlib
RATE_LIMIT_SCRIPT = """
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local cutoff = now - (window * 1000)
redis.call('ZREMRANGEBYSCORE', key, 0, cutoff)
local current = redis.call('ZCARD', key)
if current >= limit then
return {0, limit, current}
end
redis.call('ZADD', key, now, now)
redis.call('EXPIRE', key, window)
return {1, limit - current - 1, current + 1}
"""
script_hash = redis_client.script_load(RATE_LIMIT_SCRIPT)
def atomic_rate_limit(client_id, limit, window):
now = int(time.time() * 1000)
result = redis_client.evalsha(
script_hash, 1, f"ratelimit:{client_id}", limit, window, now
)
allowed, remaining, current = result
return bool(allowed), remaining
Common Mistakes
| Mistake | Fix | |---------|-----| | No Redis persistence | All limits reset on restart | Configure AOF or RDB persistence | | Not setting TTL | Memory filled with stale keys | Always set EXPIRE after INCR | | Using non-atomic operations | Race conditions with multiple servers | Use Lua scripts for atomicity | | Single Redis point of failure | Rate limiting unavailable | Deploy Redis Sentinel or Cluster | | Network latency affecting rate limits | Inconsistent timing | Use Redis near your application servers |
Practice Questions
- Why is atomicity important in Redis rate limiting?
- How does Redis sorted sets enable sliding window?
- What happens if Redis is down?
- How do you handle Redis failover?
- What is the performance of Redis INCR vs Lua scripts?
Challenge
Deploy Redis and implement both fixed window (INCR) and sliding window (sorted sets) rate limiters. Test with concurrent requests from multiple clients.
What's Next
Learn about distributed rate limiting patterns.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro