Token Bucket Rate Limiter
The Rate Limiter algorithm allowing controlled bursts without giving up on a steady mastered throughput: the detail turning policy into a readable number.
Problem
The basic Rate Limiter limits per fixed window ("100 requests per minute"). Problem: at the window edge, one can fire 100 requests at 12:00:59 and 100 more at 12:01:00, that is 200 in one second — saturation. Another problem: enforcing "never more than 2 per second" rejects a legitimate batch of 50 INVOIC in a second that averages perfectly over the day. An algorithm distinguishing sustained rate and burst, without temporal edge effect, is needed.
Forces
- Legitimate peaks exist. Month-end, morning synchronisation, partner batches.
- Sustained throughput must be bounded. Over 24h we do not want to exceed X million requests.
- The computation must be simple. The Rate Limiter runs before any processing, must be O(1).
- Partner SLA must be computable. "You can burst to 500, sustained rate 100/s" is contractual.
Solution
Implement a token bucket of maximum capacity N (allowed burst size), refilled at rate R tokens/sec (sustained rate). Each request
consumes 1 token (or k tokens depending on cost).
If the bucket is empty, the request is rejected immediately (HTTP
429, Retry-After). The calculation on a request at
time t: tokens = min(N, tokens_prev + (t - t_prev) × R),
then tokens -= 1 if allowed. O(1) algorithm, only two
floats stored per counter. Implementations: Bucket4j (Java),
Cloudflare Edge Workers, Kong, NGINX limit_req_zone,
AWS API Gateway.
Automatic refill
R = 100 tokens/sec
│
▼
┌──────────────┐
│ Bucket N=500│ max capacity (burst)
│ ●●●●●●●●●● │ current tokens
│ ●●●●●●●●●● │
└──────────────┘
│ consume 1 per request
▼
request to partner
│
if bucket empty:
┌──────────────────────────┐
│ HTTP 429 Too Many Req │
│ Retry-After: N │
└──────────────────────────┘
EDI implementation
Concrete case: an EDI hub REST API receives INVOIC from a Walmart
partner. The contract says: 100 INVOIC/sec sustained, burst 500.
On Kong (API Gateway), plugin rate-limiting-advanced in sliding window
mode or a Lua token-bucket module: limit=500, rate=100/s, identifier=consumer.partner_id.
Walmart sends a 480-message batch at 14:32:00: all pass (500-token
bucket, 480 consumed). At 14:32:01, 20 + 100 refilled = 120
remain, etc. If Walmart pushes 600 at 14:32:00, the last 100 get HTTP 429 Retry-After: 1. On the Kafka producer: quota.producer.byte.rate per client.id,
token bucket equivalent on bytes. On PEPPOL: OpenPEPPOL standard
imposes a maximum rate per Access Point — typically token-bucket
implemented.
Practical parameters
- R (refill rate): the desired sustained per-second average. Must match downstream capacity.
- N (bucket size): how much burst is tolerated. Often N = 5×R to 10×R.
- Identifier: per
partnerId, perapiKey, per IP. Pick the contractual key. - Storage: Redis cluster for multi-instance
sharing (with Lua
SCRIPTfor atomicity), local memory if single-node. - Response on reject: HTTP 429,
Retry-After: N(RFC 6585), headerX-RateLimit-Remaining.
Anti-patterns
- Token bucket without persistence. Service restart → bucket reset → free burst.
- Multi-instance token bucket without coordination. Each instance has its bucket: the partner can exceed k×R by hitting k instances. Centralise via Redis.
- Burst N too large. If N=10000 for R=100, the pattern has no effect — the max burst always saturates.
- Partner-independent R. A single global rate limit for everyone: a chatty partner consumes everyone's quota.
Related patterns
- Rate Limiter — the parent pattern.
- Back-pressure — the rate limiter is a downstream contractual back-pressure.
- Bulkhead — paired: one token bucket per compartment.
- Circuit Breaker — complementary: if downstream fails, do not consume tokens.
Sources
- RFC 2697 — A Single Rate Three Color Marker (Heinanen J., Guerin R., IETF 1999). Canonical token-bucket spec. rfc-editor.org/rfc/rfc2697
- RFC 2698 — A Two Rate Three Color Marker (1999). Variant with two rates (CIR/PIR). rfc-editor.org/rfc/rfc2698
- RFC 6585 §4 — HTTP 429 Too Many Requests (Nottingham M., IETF 2012). rfc-editor.org/rfc/rfc6585
- Stripe — Scaling your API with rate limiters. Production rate-limiter experience report. stripe.com/blog/rate-limiters
- Bucket4j — Java reference token-bucket implementation. bucket4j.com
- Cloudflare — How we built rate limiting. blog.cloudflare.com