Script Valley
System Design: APIs, Caching & Scalability
Rate Limiting and ThrottlingLesson 4.4

Distributed rate limiting with Redis across multiple servers

distributed rate limiting, Redis atomic operations, MULTI/EXEC, Lua scripts in Redis, race conditions in distributed systems, consistency trade-offs

Distributed rate limiting with Redis across multiple servers

Distributed rate limiting architecture

The Problem with Local Rate Limiting

If you store rate limit counters in each server's memory and you have 10 servers behind a load balancer, each server allows 100 req/min — your actual limit becomes 1,000 req/min per client. Local rate limiting is useless at horizontal scale.

Redis as Shared State

Store all counters in Redis. Every server reads and writes to the same counter. Use atomic operations to prevent race conditions:

-- Lua script: atomic check-and-increment
local key   = KEYS[1]
local limit = tonumber(ARGV[1])
local ttl   = tonumber(ARGV[2])

local count = redis.call('INCR', key)
if count == 1 then
  redis.call('EXPIRE', key, ttl)
end
if count > limit then
  return 0
end
return 1
// Call from Node.js
const allowed = await redis.eval(luaScript, 1, key, 100, 60);

Lua for Atomicity

Redis executes Lua scripts atomically — no other command runs between steps. This eliminates the TOCTOU (time-of-check to time-of-use) race condition that exists if you use separate INCR and EXPIRE calls. Always use Lua or MULTI/EXEC pipelines for multi-step rate limit operations when multiple instances share a counter.

Up next

API gateway rate limiting vs application-level rate limiting

Sign in to track progress