In this lesson, you will learn how to leverage high-speed cache counters to effectively throttle incoming API requests and protect your backend services.
Why Cache for Rate Limiting?
When managing API traffic, you need a mechanism that is both lightning-fast and globally accessible. Checking your primary database on every single request to see if a user has exceeded their quota is too slow and puts unnecessary strain on your persistence layer. By using a fast, memory-based cache like Redis, you can increment counters in constant time () and decide whether to block or allow a request in microseconds.
The Counter Pattern
The logic is straightforward: assign each user a unique key based on their ID or IP address (e.g., rate_limit:user:123). When a request arrives, you increment this key in the cache and set an expiration time (TTL) if it’s the first request of a new window.
Here is how you might implement a basic fixed-window limiter:
async function isAllowed(userId) {
const key = `rate_limit:${userId}`;
// Increment the counter
const currentCount = await redis.incr(key);
// Set TTL only on the first request of the window (1 minute)
if (currentCount === 1) {
await redis.expire(key, 60);
}
// If count exceeds threshold, reject the request
return currentCount <= 100;
}
Atomic Operations
The beauty of this approach lies in the atomicity of the INCR command. In a distributed Node.js environment, multiple instances of your application might receive requests from the same user simultaneously. Because INCR is an atomic operation within the cache, you don't have to worry about race conditions where two processes read the same value, increment it, and write it back incorrectly. The cache acts as the "source of truth" for the user's current request tally.
When the TTL expires, the key is automatically deleted, effectively resetting the counter for the next window. This pattern is exceptionally efficient because it keeps your application code clean and offloads the heavy lifting of state management to the cache layer.
Key Takeaway: Use atomic cache increments combined with TTL expiration to enforce scalable, high-performance rate limits without putting stress on your primary database.