Marcio Cunha

API crashing? How to implement Rate Limiting with Redis

Learn how to protect your API against traffic spikes and denial of service attacks by implementing the Token Bucket algorithm in Redis with practical code.

Marcio Cunha•4 min
Also available in:EspañolPortuguês
Summary
  • The Token Bucket algorithm manages request quotas by continuously refilling tokens into a virtual bucket.
  • Redis provides high speed and atomicity to validate distributed access limits in milliseconds.
  • Lua scripts executed on the Redis server prevent race conditions in environments with multiple concurrent nodes.
  • HTTP 429 responses with informative headers guide legitimate clients on when to try again.
  • Additional resilience strategies, such as using parallel architectural patterns, help shield systems under load.

The invisible danger of uncontrolled traffic in APIs

When a web application grows, one of the first operational problems to appear is performance degradation caused by excessive requests. Whether it is a malicious client trying to take down the system with a denial of service attack, or a poorly written partner script consuming resources in a loop, the result is usually the same: the server crashes, the database exhausts connections, and legitimate users are left unattended.

In practice, this means that leaving critical endpoints completely open is an invitation to catastrophic infrastructure failures. To shield the ecosystem, engineers resort to the concept of rate limiting, which consists of imposing strict limits on the speed and volume of calls that a given user, IP, or API key can make within a time interval.

Why Token Bucket is the ideal algorithm

There are several ways to control data flow, such as simple counting in fixed time windows. However, this simplistic model suffers from the boundary effect, where a user can exhaust the limit in the last second of a minute and repeat the dose right in the first second of the following minute, generating a double spike that overloads the server just as much.

The most elegant and efficient solution to this dilemma is the Token Bucket algorithm. Imagine a bucket that stores tokens up to a maximum limit and refills them at a constant rate per second. Each consumed request takes a token from the bucket; if the bucket is empty, the new request is summarily rejected, ensuring flexibility to absorb small legitimate spikes without suffocating the system.

Choosing Redis for high performance

Implementing this type of control directly in the application's RAM memory might seem tempting, but it fails miserably in modern architectures based on multiple cloud servers. If your API runs on three different containers behind a load balancer, keeping request control isolated on each machine means that the real limit would triple.

The natural answer to this synchronization challenge is to use an extremely fast in-memory database like Redis. Because it centralizes the storage of access data, all instances of your API query and update the same token bucket in real-time, keeping the business rule rigorously coherent regardless of where the request lands.

Implementing Token Bucket with Lua and Redis

To ensure that two simultaneous requests do not read and write to the same bucket at the same time, generating inconsistencies, the best practice in Redis is to use scripts in the Lua language. The Redis server executes these scripts completely atomically, meaning no other operation interferes in the middle of the reading and updating state process.

The code below demonstrates a robust script that calculates the passage of time, refills tokens proportionally, and deducts the unit of the new request in a single fast transaction:

local key = KEYS[1]local max_tokens = tonumber(ARGV[1])local refill_rate = tonumber(ARGV[2])local now = tonumber(ARGV[3])local requested = tonumber(ARGV[4])local bucket = redis.call('HMGET', key, 'tokens', 'last_updated')local tokens = tonumber(bucket[1])local last_updated = tonumber(bucket[2])if not tokens then    tokens = max_tokenstoken_last_updated = nowelse    local delta = math.max(0, now - last_updated)    tokens = math.min(max_tokens, tokens + (delta * refill_rate))endlocal allowed = 0if tokens >= requested then    tokens = tokens - requested    allowed = 1endredis.call('HMSET', key, 'tokens', tokens, 'last_updated', now)redis.call('EXPIRE', key, math.ceil(max_tokens / refill_rate))return {allowed, tokens}

This script eliminates concurrency bottlenecks and ensures that counting is mathematically accurate even under thousands of simultaneous accesses per second.

Integrating verification into your API

With the script running on the Redis server, the job of the microservice or API Gateway boils down to sending the correct parameters and interpreting the obtained response. If the return indicates that the request was not allowed, the application must immediately stop the flow and return an appropriate HTTP status code, traditionally 429 Too Many Requests.

In addition to returning the error code, it is an excellent operational courtesy to include informative headers in the response, such as 'Retry-After', informing exactly how many seconds the client should wait before attempting a new connection. This transparent posture prevents infinite retry loops by misconfigured consumer applications. To complement this implementation in production environments, see our related technical analyses on AI generating insecure code? How to use AST scanning and Heavy bundle in React? How to apply code splitting.

Final Considerations

Protecting modern applications against sudden overloads is no longer a differentiator and has become a basic software engineering requirement. As we saw, combining the dynamic model of Token Bucket with the atomic speed of Redis solves the scalability and distribution problem elegantly.

Adopting these practices mitigates unwanted attacks, improves overall infrastructure stability, and ensures that systems under high load, as we detailed in our article on high load systems and the Sidecar pattern, continue delivering fast and reliable responses to legitimate users.