Learning lens
Start with judgment, not a universal prescription.
This page is a learning model for understanding the capability. It does not claim exact production internals, exact costs, or an infinite-scale architecture.
Evidence
Conceptual pattern
0->1 default
Start with a durable queue, clear ownership, retries, and idempotency before building a larger event platform.
Cost drivers
Watch for breakpoints
- -backlog grows during normal traffic
- -manual recovery becomes frequent
- -independent consumers need replay
Learn first
Concepts behind this architecture
Rate Limiting
Control request volume so shared systems stay fair, stable, and protected.
API Gateway
A controlled front door for authentication, routing, rate limits, and API policy.
Cache-Aside
Let the application read through the cache and repopulate missing data on demand.
Bloom Filter
Quickly test whether an item is definitely absent or maybe present.
The Problem
APIs must protect against abuse, prevent resource exhaustion, and ensure fair usage across clients. Without rate limiting, a single misbehaving client can overwhelm your servers, a bot can scrape your entire database, or a retry storm from a downstream service can cascade into a full outage. Rate limiting controls how many requests a client can make within a time window. The challenge is implementing it efficiently in a distributed system where requests hit different servers, with minimal latency overhead, configurable per-client limits, and graceful handling of limit breaches.
Architectural Approaches
Token Bucket Algorithm
Each client has a “bucket” that fills with tokens at a steady rate. Each request consumes one token. If the bucket is empty, the request is rejected. The bucket has a maximum capacity, allowing short bursts up to that capacity. The most widely used algorithm for API rate limiting.
Pros
- +Allows bursts while enforcing average rate
- +Simple to implement and understand
- +Smooth rate limiting — no hard window boundaries
- +Memory efficient — one counter and timestamp per client
Cons
- −Burst size must be configured carefully — too large defeats the purpose
- −Distributed implementation requires atomic operations
- −Doesn't enforce strict per-second limits (bursts can exceed instantaneous rate)
Sliding Window Log
Store the timestamp of every request in a sorted set. To check the limit, count requests within the current window (e.g., last 60 seconds). Most accurate but most memory-intensive — stores every request timestamp.
Pros
- +Most accurate — exact count within any rolling window
- +No boundary issues between windows
- +Smooth rate limiting behavior
Cons
- −High memory usage — stores every request timestamp
- −Expensive count operation on large windows
- −Cleanup of old entries adds overhead
- −Not practical for high-volume endpoints
Sliding Window Counter
Hybrid of fixed window and sliding window. Maintain counters for the current and previous fixed windows. Estimate the sliding window count by weighting the previous window’s count by the overlap percentage. Balances accuracy and efficiency.
Pros
- +Low memory — only two counters per client per window
- +Smooth behavior — no hard boundary spikes
- +Good approximation of true sliding window
- +Easy to implement with Redis INCR and TTL
Cons
- −Approximate — not exact count within the window
- −Slight inaccuracy at window boundaries
- −Weighted calculation adds minor complexity
Architecture
Key Components
Rate Limiter Middleware
Intercepts every request before it reaches the application. Extracts the rate limit key (API key, user ID, IP address), checks the current count against the configured limit, and either allows the request or returns 429 Too Many Requests with Retry-After header.
Rate Limit Store (Redis)
Centralized counter store shared across all API gateway instances. Uses Redis for atomic increment operations (INCR) and automatic TTL-based cleanup. Redis Cluster for high availability. Lua scripts for atomic token bucket operations.
Rate Limit Configuration
Rules engine defining limits per client tier, endpoint, and HTTP method. Example: free tier = 100 req/min, pro = 1000 req/min, enterprise = custom. Rules stored in a config database and cached locally on each gateway instance.
Rate Limit Headers
Returns standard headers on every response: X-RateLimit-Limit (max allowed), X-RateLimit-Remaining (tokens left), X-RateLimit-Reset (when the window resets), Retry-After (seconds to wait on 429). Helps clients implement backoff.
Abuse Detection
Monitors for patterns beyond simple rate limits: credential stuffing (many failed logins), scraping (sequential page access), DDoS (distributed attack from many IPs). Can dynamically lower limits or block clients based on behavioral analysis.
Rate Limit Dashboard
Visualizes current usage per client, identifies clients approaching limits, shows historical usage patterns, and provides controls for adjusting limits in real time. Critical for customer support and capacity planning.
Data Model
Token Bucket with Redis Lua Script
Implementing a distributed token bucket requires atomic operations. A Redis Lua script ensures atomicity:
Algorithm:
- Calculate tokens to add since last request:
tokens_to_add = (now - last_refill) * rate - Update bucket:
tokens = min(bucket_capacity, current_tokens + tokens_to_add) - Check if enough tokens: if
tokens >= 1, consume and allow; else reject - Store updated state:
{tokens, last_refill_time}
Redis Lua script (executed atomically):
- KEYS[1] = rate limit key
- ARGV[1] = bucket capacity, ARGV[2] = refill rate, ARGV[3] = current timestamp
- Returns: {allowed: 0/1, remaining: N, retry_after: seconds}
Advantages of Lua script: The entire check-and-update runs as a single atomic operation on Redis. No race conditions between concurrent requests. No distributed locks needed.
Performance: A Redis Lua script executing a token bucket check takes ~0.1ms. With Redis pipelining, you can check thousands of rate limits per second per Redis connection.
Multi-Dimensional Rate Limiting
Real APIs need limits on multiple dimensions simultaneously:
Per-client: 1000 requests/minute per API key Per-endpoint: 100 requests/minute to POST /api/upload per API key Per-IP: 50 requests/second from any single IP Global: 10,000 requests/second total across all clients for a specific endpoint
Implementation: Check all applicable limits for each request. The request is allowed only if ALL limits pass. Use Redis pipeline to check multiple keys in a single round-trip:
- INCR api_key:{key}:global → check against 1000/min
- INCR api_key:{key}:endpoint:{path} → check against 100/min
- INCR ip:{ip}:second → check against 50/sec
- INCR global:{path}:second → check against 10000/sec
Priority clients: Some clients need higher limits. Store per-client overrides that supersede tier defaults. Enterprise clients may have “soft” limits that alert but don’t reject.
Graduated response: Instead of hard rejection at the limit, implement a graduated response:
- 80% of limit: return warning header
- 100% of limit: start throttling (add 100ms delay per request)
- 120% of limit: return 429 Too Many Requests
- 200%+ of limit: temporarily block the client
Handling Rate Limit Failures
What happens when Redis (the rate limit store) is down?
Fail-open: Allow all requests when the rate limiter is unavailable. Keeps the API functional but removes all protection. Suitable when availability is more important than protection.
Fail-closed: Reject all requests when the rate limiter is unavailable. Maximum safety but causes a complete outage. Suitable for security-critical endpoints.
Local fallback: Each API gateway instance maintains a local in-memory rate limiter as a fallback. Less accurate (per-instance limits instead of global) but provides basic protection. Configure local limits at 1/N of the global limit (N = number of instances).
Circuit breaker: If Redis latency exceeds 5ms (normally 0.1ms), open the circuit breaker and fall back to local rate limiting. Close the circuit after Redis recovers. This prevents Redis slowness from adding latency to every API request.
Retry storms: When clients get 429 responses, they should use exponential backoff with jitter. But many clients retry immediately, creating a storm when the window resets. Mitigate by:
- Adding jitter to Retry-After headers
- Gradually releasing blocked clients instead of all-at-once
- Implementing client-side rate limiting in your SDKs
Real-World Examples
Stripe
Token bucket rate limiting per API key. Returns detailed rate limit headers (X-RateLimit-Limit, X-RateLimit-Remaining). Different limits per endpoint (read vs write). Test mode has separate limits from live mode.
GitHub API
Fixed window rate limiting: 5000 requests/hour for authenticated users, 60/hour for unauthenticated. Rate limit info available via a dedicated /rate_limit endpoint. GraphQL API has a separate “point” budget per query complexity.
Cloudflare
Multi-layer rate limiting at the edge. Basic rate limiting in the free plan, advanced rules (per-path, per-header) in paid plans. Distributed across global edge network — rate limits are synchronized across PoPs within seconds.
Discord
Per-route and global rate limits. Returns rate limit info in response headers. Implements a “rate limit bucket” system where related endpoints share a bucket. Bot-specific rate limits separate from user limits.
Architectural Tradeoffs
Token bucket vs sliding window counter
Pros
- +Token bucket: natural burst handling, smooth rate limiting
- +Sliding window: more intuitive limits ("100 per minute means 100 per minute")
- +Token bucket: single atomic operation per check
Cons
- −Token bucket: burst can exceed instantaneous rate target
- −Sliding window: slightly more complex, approximate at boundaries
- −Token bucket: burst size is an extra parameter to tune
Gateway-level vs application-level rate limiting
Pros
- +Gateway: protects all services, single implementation point
- +Application: access to business context (user tier, endpoint semantics)
- +Gateway: rejects early, saves backend resources
Cons
- −Gateway: limited context for fine-grained rules
- −Application: each service must implement rate limiting
- −Both layers is ideal but adds complexity
Fail-open vs fail-closed on rate limiter failure
Pros
- +Fail-open: API stays available during Redis outage
- +Fail-closed: guaranteed protection, no abuse during outage
- +Local fallback: compromise between availability and protection
Cons
- −Fail-open: vulnerable to abuse when rate limiter is down
- −Fail-closed: rate limiter outage = API outage
- −Local fallback: less accurate (per-instance, not global)
Learn Rate Limiting & Throttling
Copy a learning context prompt for an AI assistant. It keeps assumptions visible and asks for tradeoffs, cost drivers, failure modes, and what to measure before adding complexity.
Explore More Capabilities
API Versioning
Backward-compatible API evolution with URL, header, and content negotiation strategies
Audit Logging
Append-only event stores, tamper-proof records, and compliance-ready activity trails
Caching Strategies
Cache-aside, write-through, invalidation patterns, and distributed caching with Redis