Skip to main content
Cloud & AI Hub
Browse
Glossary AI Directory Playgrounds Models Prompts Explainers Strategy Matrix Benchmark Decoder
intermediate 9 mins Read

How API Gateways Rate Limit and Throttle Traffic

Deep-dive into token bucket, sliding window, and Redis-backed rate limiting algorithms used by API gateways.

Introduction

Rate limiting protects backend services from being overwhelmed by too many requests — whether from misbehaving clients, DDoS attacks, or runaway automation scripts. API gateways like Kong, AWS API Gateway, and Nginx implement rate limiting as a middleware layer, rejecting excess requests with 429 Too Many Requests before they reach application servers. The algorithm choice significantly affects user experience: some algorithms cause thundering-herd problems at epoch boundaries, while others smooth traffic more gracefully.

Step-by-Step: Rate Limiting Algorithms

flowchart TD
    A["1. Fixed Window Counter (Simple but Flawed)"]
    B["2. Sliding Window Log"]
    C["3. Token Bucket (Most Common)"]
    D["4. Sliding Window Counter (Best of Both)"]
    E["5. Client Response and Retry Handling"]
    A --> B
    B --> C
    C --> D
    D --> E

Step 1: Fixed Window Counter (Simple but Flawed)

The simplest approach: count requests per client per time window (e.g., 100 requests per 60-second window). Reset the counter at the window boundary.

Problem: A client can send 100 requests at 23:59:59 and 100 more at 00:00:01 — effectively 200 requests in 2 seconds.

# Redis implementation
INCR rate:user123:window:1704067200  # Unix epoch of window start
EXPIRE rate:user123:window:1704067200 60

Step 2: Sliding Window Log

Record the timestamp of every request. On each new request, count timestamps within the last N seconds and reject if over limit.

Problem: Stores every request timestamp — O(requests) memory per user.

Step 3: Token Bucket (Most Common)

A bucket holds up to capacity tokens. Tokens refill at a fixed rate (e.g., 10/second). Each request consumes one token. If empty, the request is rejected.

Advantage: Allows controlled bursting up to bucket capacity, then enforces the average rate.

class TokenBucket:
    def __init__(self, capacity, refill_rate):
        self.capacity = capacity
        self.tokens = capacity
        self.refill_rate = refill_rate  # tokens per second
        self.last_refill = time.time()

    def consume(self):
        now = time.time()
        elapsed = now - self.last_refill
        self.tokens = min(self.capacity, self.tokens + elapsed * self.refill_rate)
        self.last_refill = now
        if self.tokens >= 1:
            self.tokens -= 1
            return True  # allowed
        return False  # rate limited

Step 4: Sliding Window Counter (Best of Both)

Combines fixed window simplicity with sliding accuracy. Uses two windows and weights the previous window by the overlap:

rate = prev_window_count × (1 - elapsed/window) + curr_window_count
# Redis atomic Lua script for sliding window
local key_curr = KEYS[1]
local key_prev = KEYS[2]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local elapsed = now % window
local weight = (window - elapsed) / window
local count = tonumber(redis.call('GET', key_curr) or 0)
           + math.floor(tonumber(redis.call('GET', key_prev) or 0) * weight)
if count >= limit then return 0 end
redis.call('INCR', key_curr)
redis.call('EXPIRE', key_curr, window * 2)
return 1

Step 5: Client Response and Retry Handling

Rate-limited responses must include headers to guide clients:

HTTP/1.1 429 Too Many Requests
Retry-After: 30
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1704067260

Clients should implement exponential backoff with jitter:

import random, time
def retry_with_backoff(fn, max_retries=5):
    for attempt in range(max_retries):
        try:
            return fn()
        except RateLimitError:
            wait = (2 ** attempt) + random.uniform(0, 1)  # jitter prevents thundering herd
            time.sleep(wait)
    raise Exception("Max retries exceeded")

Key Takeaways

  • Fixed window is simple but creates boundary burst vulnerability; never use it for security-critical rate limits.
  • Token bucket is the industry standard — it allows short bursts while enforcing the average rate.
  • Sliding window counter provides the best accuracy-to-memory tradeoff for high-throughput APIs.
  • Use Redis atomic Lua scripts for distributed rate limiting across multiple gateway instances.
  • Always return Retry-After headers; clients using exponential backoff with jitter prevent thundering-herd retry storms.

Historical figures, architectures, and capabilities are for informational purposes only. Not technical, professional, legal, or financial advice. Sources: Research papers, developer documentation.