API ArchitectureAugust 30, 20267 min read

Designing High-Throughput API Rate Limiters: Token Bucket vs. Sliding Window Log in Redis

Nguyen Dai Long

Nguyen Dai Long

Backend Lead & Software Engineer

Unprotected public APIs are sitting ducks for automated scraping bots, credential stuffing attacks, and denial-of-service surges. A robust distributed rate limiter throttles excessive client traffic while allowing legitimate bursts. In this article, I analyze Token Bucket and Sliding Window algorithms implemented with Redis.

1. Token Bucket vs. Sliding Window Counter

The Token Bucket algorithm maintains a bucket with a maximum capacity of tokens, refilled at a constant rate. Requests consume tokens; if empty, the request is rejected with HTTP 429 Too Many Requests. The Sliding Window Counter algorithm computes usage across rolling time frames, smoothing out boundary burst spikes that plague Fixed Window approaches.

Key Implementation Takeaways:

  • Fixed Window counters suffer from double-quota bursts at window boundaries.
  • Token Bucket accommodates legitimate bursts while maintaining a steady-state rate.
  • Sliding Window Counter offers smooth traffic limiting with low memory footprint.

2. Atomic Sliding Window Execution with Redis Lua

Executing rate limiting via individual Redis commands introduces race conditions. We use an atomic Redis Lua script utilizing a Sorted Set (ZSET). Timestamps serve as both member and score, allowing automatic cleanup of expired requests with ZREMRANGEBYSCORE in a single round-trip.

lua
-- Redis Lua Sliding Window Rate Limiter
local key = KEYS[1]
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])

local clear_before = now - window
redis.call('ZREMRANGEBYSCORE', key, 0, clear_before)

local current_requests = redis.call('ZCARD', key)
if current_requests < limit then
    redis.call('ZADD', key, now, now)
    redis.call('EXPIRE', key, window)
    return 1 -- Allowed
else
    return 0 -- Throttled
end

Key Implementation Takeaways:

  • Execute rate check and count increment atomically via Redis Lua scripts.
  • Always transmit standard Retry-After and X-RateLimit-* headers to clients.
  • Segment rate limits by API Key, IP address, or authenticated User ID.

Summary & Final Thoughts

Distributed rate limiting protects database health, maintains quality of service for all users, and prevents infrastructure cost runaways.

Engineering Feedback0 likes

Was this technical breakdown helpful for your production workflow?

Nguyen Dai Long

Written by Nguyen Dai Long

Backend Engineer & Backend Lead with 4+ years of hands-on experience building production systems, RESTful APIs, and cloud infrastructure using Python (Django), Laravel, PostgreSQL, and Google Cloud Platform.

Technical Discussion0

Ask questions, challenge architectures, or share your own production insights.

Join the Technical Community Discussion

Sign in via GitHub or Google in 5 seconds to comment, exchange architecture insights, and build your engineering presence.

Loading discussion...
NDL Ecosystem

Explore Free Web Tools & Games

View All Tools & Apps