Marcio Cunha

Distributed Rate Limiting with Token Bucket in Serverless Architectures

Learn how to control request flow in serverless applications using the Token Bucket algorithm and distributed databases to prevent abuse.

Marcio Cunha•5 min
Also available in:EspañolPortuguês
Summary
  • Serverless architectures scale infinitely, but they leave APIs exposed to uncontrolled traffic spikes and denial-of-service attacks.
  • The Token Bucket algorithm works like a container that stores tokens released at a constant rate to authorize incoming client access.
  • Stateless systems require high-speed central data stores like Redis to synchronize counters across multiple isolated instances.
  • Ensuring atomic consistency in read and write operations prevents concurrent accesses from bypassing business logic limits.
  • The primary trade-off involves accepting minimal network latencies in exchange for robust backend service protection.

The Challenge of Controlling Access in Elastic Clouds

When building modern cloud applications using functions that execute on demand, we eliminate the need to manage physical servers or dedicated virtual machines. This flexibility allows the system to scale instantly to serve millions of users and shrink to zero when there is no traffic. In practice, this means we only pay for the exact milliseconds the code is running. However, this same elasticity opens dangerous avenues for abuse, such as automated robots scraping data or malicious denial-of-service attacks that can silently inflate financial costs.

Protecting these entry points requires mechanisms known as rate limiting, which act like nightclub bouncers limiting the number of entries per minute. In traditional environments with fixed servers, maintaining this control is relatively simple because access counts can reside in the local memory of the machine. In serverless architectures, code functions spin up, process a request, and die within fractions of a second, spreading instances across dozens of servers worldwide. Without a central coordination point, each function would decide independently who enters, completely breaking the global limit rule.

How the Token Bucket Algorithm Works in Practice

To solve the flow control dilemma without locking the system, engineers use intelligent mathematical algorithms, with Token Bucket being one of the industry's most popular and efficient choices. Imagine a physical bucket at an event reception that receives plastic tokens at a constant and predictable rate, say, ten tokens per second. When a participant arrives, they must take a token from the bucket to gain entry. If the bucket is full, extra tokens simply overflow and are lost, establishing a maximum capacity ceiling.

Applying this analogy to software, each user or IP address has a virtual bucket stored in a high-performance database. Every incoming HTTP request consumes one token from this digital bucket. If tokens are available, the request is processed normally and the token is subtracted. If the bucket is empty, the system immediately rejects access by returning a standard HTTP error code indicating excessive attempts. The great triumph of this model is allowing legitimate traffic bursts, since a user who remained idle accumulates tokens and can perform several actions in rapid succession.

Overcoming the Distributed Challenge with Central Storage

The Achilles' heel of any solution running in elastic clouds is data synchronization between thousands of isolated instances processing simultaneous requests. If we use local in-memory variables within each serverless function, a clever user could fire a hundred requests and hit a hundred different instances, completely bypassing the established traffic limit. In practice, this requires decoupling the control state from the execution logic, moving the token count to an external, lightning-fast distributed database.

The most common technological choice for this role is Redis, an in-memory storage system known for blazing-fast read and write speeds. When a serverless function is triggered, it performs a quick query to Redis to check the client's current token balance. To prevent concurrent requests from altering the same balance incorrectly, we use atomic operations or scripts executed directly on the cache server. This ensures that two requests arriving at the exact same millisecond cannot consume the same residual token, maintaining the mathematical integrity of the control rule.

Implementing Logic in Functional Code

Below we present a practical example using JavaScript in a serverless function that interacts with Redis to apply the Token Bucket logic. The code calculates how many tokens should be refilled based on the time elapsed since the user's last request before allowing or denying access.

const redis = require('redis');
const client = redis.createClient();

async function checkRateLimit(userId) {
  const key = `rate:bucket:${userId}`;
  const now = Date.now();
  const capacity = 10;
  const refillRate = 1; // tokens per second
  
  let bucket = await client.hGetAll(key);
  let tokens = bucket.tokens ? parseFloat(bucket.tokens) : capacity;
  let lastUpdate = bucket.lastUpdate ? parseInt(bucket.lastUpdate) : now;
  
  const elapsed = (now - lastUpdate) / 1000;
  tokens = Math.min(capacity, tokens + elapsed * refillRate);
  
  if (tokens < 1) {
    return { allowed: false, remaining: 0 };
  }
  
  tokens -= 1;
  await client.hSet(key, { tokens, lastUpdate: now });
  return { allowed: true, remaining: Math.floor(tokens) };
}

This code snippet demonstrates conceptual simplicity combined with the technical rigor required for high-volume environments. By storing state in a structured way in Redis, we ensure that any new serverless function instance triggered next will have immediate and precise access to that specific user's recent history.

Operational Considerations and Architecture Trade-Offs

Adopting a distributed access control strategy brings undeniable security advantages, but it also introduces operational costs and complexities that must be evaluated carefully. The first relevant trade-off is the addition of network latency. While serverless functions typically execute close to the user at geographically distributed edges, the centralized database may reside in a specific region, adding dozens of precious milliseconds to the total API response time.

Another critical point of attention concerns resilience and fault tolerance. If the centralized cache database experiences temporary downtime, the entire microservice chain could become inaccessible if the application lacks an appropriate fallback mechanism. In high-criticality scenarios, engineers frequently choose to allow traffic temporarily during Redis failures, prioritizing business availability over the strict blocking of abuses during brief windows of technical instability. To complement this implementation in production environments, see our related technical analyses on Distributed Rate Limiting in Serverless with Token Bucket and Redis Cluster and Microservices Isolation and Rate Limiting with Distributed Token Buckets.

Final Considerations

Implementing distributed rate limiting using the Token Bucket algorithm in serverless environments requires careful alignment between scalability, data consistency, and response speed. While elastic infrastructure brings unprecedented synchronization challenges, using high-performance in-memory storage solves the problem elegantly and securely. Mastering these concepts ensures your applications remain protected, resilient, and financially predictable even under extreme peaks of global access.