Course contentsShow
← Node.js
Lesson 1147 of 1,98010. Real-time Communication and WebsocketsFree lesson

Implementing Rate Limiting with Redis Leaky Buckets

You will distribute rate limiting logic across multiple nodes to ensure consistency in throughput.

In this lesson, you will learn how to synchronize rate-limiting logic across a cluster of Node.js nodes using a shared Redis "Leaky Bucket" to ensure consistent throughput regardless of which server handles a request.

The Problem: Local vs. Distributed State

When you run multiple instances of a WebSocket server, keeping rate limits in local memory leads to "drift." If a user sends 10 messages, and your limit is 5 per second, they could accidentally bypass this by hitting different server nodes that don't know about each other’s counters. By moving the "leaky bucket" state to Redis, we create a single, shared source of truth.

How the Leaky Bucket Works in Redis

Imagine a bucket with a hole in the bottom. Every time a message arrives, we try to add "water" (a unit of cost). The hole represents the constant rate at which the bucket drains. If the water overflows, the request is rejected.

We can implement this atomic logic using a Lua script in Redis. This ensures that checking the bucket and updating the count happens as a single, uninterruptible operation, preventing race conditions between your Node.js processes.

const luaScript = `
  local bucket_key = KEYS[1]
  local capacity = tonumber(ARGV[1])
  local leak_rate = tonumber(ARGV[2])
  local now = tonumber(ARGV[3])
  
  local bucket = redis.call('HMGET', bucket_key, 'last_level', 'last_time')
  local level = math.max(0, (tonumber(bucket[1]) or 0) - (now - (tonumber(bucket[2]) or now)) * leak_rate)
  
  if level + 1 <= capacity then
    redis.call('HMSET', bucket_key, 'last_level', level + 1, 'last_time', now)
    return 1 -- Allowed
  else
    return 0 -- Rate limited
  end
`;

Why This Scales

By executing this script via redis.eval(), the state is persisted in Redis memory. When a WebSocket message hits Node-A, it updates the shared Redis key. When the next message arrives at Node-B, the Lua script pulls the current last_level and last_time from Redis, calculates the "leak" that occurred since the last request, and applies the logic correctly. This guarantees that your cluster enforces a global throughput limit across all connected clients.

Key Takeaway: By moving rate-limit state into an atomic Redis Lua script, you transform localized constraints into a reliable, cluster-wide policy that prevents throughput spikes from overwhelming your infrastructure.