Rate Limiting, Quotas & Abuse Protection · Rate Limiting Fundamentals
GCRA: The Generic Cell Rate Algorithm
A memory-efficient, precise limiter used by many modern implementations.
GCRA (from ATM networks) implements token-bucket behavior with a single timestamp per key: the "theoretical arrival time" (TAT). A request is allowed if now ≥ TAT − burst_tolerance; then TAT = max(TAT, now) + emission_interval. It's precise, needs one value per key, and naturally yields Retry-After. Libraries: redis-cell (CL.THROTTLE), throttled (Go), and many gateway plugins.
Token bucket vs GCRA
| Criterion | Token bucket | GCRA |
|---|---|---|
| State per key | tokens + timestamp | one timestamp (TAT) |
| Precision | High | High |
| Retry-After | Computed | Falls out of the math |
| Intuition | Easy | Less intuitive |
Python (GCRA with Redis)
GCRA = '''
local tat = tonumber(redis.call('GET', KEYS[1]) or ARGV[1])
local now, interval, tolerance = tonumber(ARGV[1]), tonumber(ARGV[2]), tonumber(ARGV[3])
tat = math.max(tat, now)
if now < tat - tolerance then
return {0, tat - tolerance - now} -- denied, retry after (ms)
end
local new_tat = tat + interval
redis.call('SET', KEYS[1], new_tat, 'PX', math.ceil(new_tat - now + tolerance))
return {1, 0}
'''
def gcra_allow(redis, key: str, rate_per_sec: float, burst: int) -> tuple[bool, int]:
interval = 1000 / rate_per_sec # ms between requests
tolerance = interval * burst
now = redis.time() # single clock: Redis server time
now_ms = now[0] * 1000 + now[1] // 1000
allowed, retry_ms = redis.eval(GCRA, 1, key, now_ms, interval, tolerance)
return bool(allowed), int(retry_ms)
Common pitfalls
- Mixing clocks from many app servers.
- Forgetting key expiry (memory growth).
- Off-by-one burst semantics (document burst carefully).
Key points
- One timestamp per key
- Token-bucket semantics
- Natural Retry-After
Further reading: Brandur: Rate limiting with GCRA
Practice questions
What does GCRA store per key?
Answer
A single theoretical arrival time (TAT) representing when the next request would be on schedule.
Why use the Redis server's TIME in distributed limiters?
Answer
Application servers' clocks drift; using one clock (Redis) avoids inconsistent decisions across instances.
Open in the app to mark lessons complete, take notes and track your progress.