Skip to content

Building Production API Rate Limiters: Token Bucket & Redis

CoreConceptAugust 1, 20263 min read

Rate Limiting is a critical defense mechanism for production APIs, protecting downstream microservices from traffic spikes, denial-of-service (DoS) attacks, and resource exhaustion. Implementing rate limiting across a distributed cluster requires storing state in a high-speed, centralized store like Redis.

This guide covers the core rate limiting algorithms—Token Bucket, Leaky Bucket, Fixed Window, and Sliding Window Log—and demonstrates how to build a production-grade, race-condition-free rate limiter in Redis using atomic Lua scripting.

Rate Limiting Algorithms: Token Bucket vs. Sliding Window

Choosing a rate limiting algorithm depends on your tolerance for burst traffic and memory overhead.

Token Bucket allows bursts of traffic up to a bucket capacity while refilling tokens at a constant rate. Sliding Window Log records exact timestamp logs per user, offering perfect accuracy at the cost of higher memory. Sliding Window Counter approximates logs by combining token counts from the current and previous window periods.

Quick reference

  • Use Lua scripts in Redis to execute token calculations atomically, avoiding race conditions under concurrent requests.
  • Include standard RFC rate limit response headers (X-RateLimit-Limit, X-RateLimit-Remaining) so client SDKs can back off gracefully.
  • Token Bucket handles bursty traffic well, while Leaky Bucket smooths out requests at a constant egress rate.
Atomic Redis Token Bucket Implementation (Lua & Node.js)
1import Redis from "ioredis";2 3const redis = new Redis("redis://localhost:6379");4 5// Lua Script guarantees atomic token refill and deduction without race conditions6const tokenBucketLua = `7  local key = KEYS[1]8  local max_capacity = tonumber(ARGV[1])9  local refill_rate = tonumber(ARGV[2])10  local now = tonumber(ARGV[3])11  local requested = tonumber(ARGV[4])12 13  local info = redis.call("HMGET", key, "tokens", "last_updated")14  local tokens = tonumber(info[1])15  local last_updated = tonumber(info[2])16 17  if not tokens then18    tokens = max_capacity19    last_updated = now20  else21    local delta = math.max(0, now - last_updated)22    tokens = math.min(max_capacity, tokens + delta * refill_rate)23    last_updated = now24  end25 26  if tokens >= requested then27    tokens = tokens - requested28    redis.call("HMSET", key, "tokens", tokens, "last_updated", last_updated)29    redis.call("EXPIRE", key, 3600)30    return {1, math.floor(tokens)}31  else32    return {0, math.floor(tokens)}33  end34`;35 36export async function isRateLimited(userId: string): Promise<{ allowed: boolean; remaining: number }> {37  const key = `rate_limit:${userId}`;38  const [allowed, remaining] = (await redis.eval(39    tokenBucketLua, 1, key, 100, 0.1, Date.now() / 1000, 140  )) as [number, number];41 42  return { allowed: allowed === 1, remaining };43}
Rate Limiter HTTP Response Headers
1// Standard RFC HTTP Rate Limit Headers returned to client2HTTP/1.1 429 Too Many Requests3Content-Type: application/json4X-RateLimit-Limit: 1005X-RateLimit-Remaining: 06X-RateLimit-Reset: 17225568007Retry-After: 608 9{10  "error": "Rate limit exceeded. Please retry after 60 seconds."11}

Remember this

Atomic Lua scripts in Redis provide sub-millisecond, race-condition-free rate limiting across distributed API clusters.

Avoiding Race Conditions & Memory Churn

In a distributed cluster with multiple API gateways, naive read-then-write code causes race conditions where concurrent requests pass limit thresholds simultaneously.

Using atomic Redis Lua scripts or Redis Sorted Sets (ZSETs) for Sliding Window logs ensures thread-safe execution across all gateway instances.

Quick reference

  • Set key TTL expiration times on Redis rate limit keys to automatically clean up inactive user sessions.
  • Distribute rate limit key hashes evenly across Redis Cluster shards.
  • Compare with Rate Limiting Algorithms Guide and NGINX Gateway Guide.

Remember this

Redis Lua scripts eliminate read-modify-write race conditions, ensuring rate limits are enforced strictly in production.

API Gateway Placement & Decision Matrix

Place rate limiters at the API Gateway layer (such as NGINX, Envoy, or Kong) before requests reach internal microservices.

For related guides, see our articles on Load Balancing L4 vs L7 and API Security Best Practices.

Quick reference

  • Enforce IP-based rate limiting for unauthenticated public endpoints and Token/User-based limiting for logged-in sessions.
  • Return HTTP 429 Too Many Requests status codes with explicit Retry-After headers.
  • Implement circuit breakers to fail open if the central Redis rate limiting cluster becomes unreachable.
  • Compare with Load Balancing Layer 4 vs Layer 7 and NGINX Gateway Configuration.

Remember this

Enforce rate limits at the API Gateway boundary to shield downstream services from overload and malicious traffic.

Key takeaway

Building rate limiters with Redis and Token Bucket algorithms protects your microservices while delivering predictable performance and smooth handling of bursty traffic.

Share:

Related Articles

One buggy client can retry a failing endpoint in a tight loop and starve everyone else. Rate limiting protects shared ca

Read

System design interviews evaluate a candidate's ability to architect scalable, resilient, and cost-effective distributed

Read

In-memory caching is an essential component of high-throughput web architectures, reducing database read load and accele

Read

Keep learning

Follow a structured path or browse all courses to go deeper.