Skip to content

Redis Rate Limiting — Complete Distributed Implementation Guide

DodaTech Updated 2026-06-28 2 min read

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

  1. Why is atomicity important in Redis rate limiting?
  2. How does Redis sorted sets enable sliding window?
  3. What happens if Redis is down?
  4. How do you handle Redis failover?
  5. 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