Por que rate limiter é pergunta padrão em entrevista sênior
Rate limiter testa 4 skills simultaneamente: escolha de algoritmo (fixed vs sliding vs token bucket), consistência distribuída (Redis atomic ops), operação (rollout seguro, observability), e APIs (headers HTTP corretos, semântica).
Quase toda API pública (Stripe, Twitter, GitHub, OpenAI) tem. Saber construir uma é requisito pra qualquer engenheiro de backend sênior.
Os 4 algoritmos clássicos
| Algoritmo | Memória | Precisão | Burst | Complexidade |
|---|---|---|---|---|
| Fixed window | O(1) contador | Baixa (burst doubling) | Não permite | Baixíssima |
| Sliding window log | O(N req) | Alta | Limitado | Média |
| Sliding window counter | O(1) + interpolação | Alta | Limitado | Média |
| Token bucket | O(1) tokens + last_refill | Alta | Permite (até capacity) | Baixa |
| Leaky bucket | O(N queue) | Alta (smoothing) | Não permite | Média |
Token bucket na prática — o padrão vencedor
Rate limiter distribuído com Redis atômico
Em cluster com múltiplos servidores, cada um mantendo bucket local = limite global X vezes maior que desejado. Solução: bucket em Redis compartilhado, operação atômica via Lua script.
# Lua script atômico — evita race condition entre GET + SET
TOKEN_BUCKET_LUA = """
local key = KEYS[1]
local capacity = tonumber(ARGV[1])
local refill = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local cost = tonumber(ARGV[4])
local data = redis.call('HMGET', key, 'tokens', 'last_refill')
local tokens = tonumber(data[1]) or capacity
local last_refill = tonumber(data[2]) or now
-- Refill proporcional
local elapsed = math.max(0, now - last_refill)
tokens = math.min(capacity, tokens + elapsed * refill)
if tokens >= cost then
tokens = tokens - cost
redis.call('HMSET', key, 'tokens', tokens, 'last_refill', now)
redis.call('EXPIRE', key, 3600) -- garbage collect chaves inativas
return {1, tokens}
else
redis.call('HMSET', key, 'tokens', tokens, 'last_refill', now)
redis.call('EXPIRE', key, 3600)
return {0, tokens}
end
"""
import redis, time
r = redis.Redis()
allow_script = r.register_script(TOKEN_BUCKET_LUA)
def allow(user_id: str, capacity: int, refill: float, cost: int = 1):
result = allow_script(
keys=[f"rl:{user_id}"],
args=[capacity, refill, time.time(), cost],
)
allowed, remaining = result[0] == 1, result[1]
return allowed, remainingLua script no Redis é atômico — roda no thread único do Redis, zero race condition. Custa ~1ms round-trip + ~0.1ms execução. Escala para milhões de usuários com cluster Redis.
Decisão: limites por chave (user, IP, API key, endpoint?)
📋 Como identificar quem rate-limitar?
Abuse pode vir de ângulos diferentes: mesmo user batendo rápido, mesmo IP gerando contas descartáveis, endpoint específico sobrecarregando DB. Aplicar uma camada por vez: global → por IP → por user → por endpoint.
Alt: —
Alt: —
Alt: —
Headers HTTP corretos:
HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 783
X-RateLimit-Reset: 1714068000
# Quando ultrapassa:
HTTP/1.1 429 Too Many Requests
Retry-After: 42
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1714068042
Content-Type: application/json
{
"error": "rate_limit_exceeded",
"retry_after": 42,
"docs": "https://docs.api.com/rate-limits"
}Rollout: como não fazer outage com seu próprio rate limiter
- Shadow mode primeiro — lógica conta e loga mas não bloqueia. Compara baseline real de tráfego: quantos users seriam bloqueados no limite proposto? Ajusta limite antes de enforçar.
- Canary enforce — 1% do tráfego → 10% → 100% com pausas de 1h entre. Monitor: taxa de 429, reclamações, regressões em métricas de negócio.
- Feature flag kill switch — desabilitar em 30s se comportamento inesperado (spike de 429 em tráfego normal = bug no limiter, não abuse).
- Allowlist pra integrações críticas — webhooks de parceiros, serviços internos. Nada pior que rate-limit bloquear sua própria infra.
Observability obrigatória
- Métrica por chave: taxa de 429 por (endpoint, tier de user). Se subir inesperado → ou abuse ou bug.
- Top-N blocked: quem está mais perto do limite? IPs/users mais bloqueados nas últimas 24h → candidatos a suspender ou aumentar tier.
- Latency do rate check: rate-limiter adicionou 3ms p99? Redis pode estar degradado.
- Baseline automática: alerta se taxa de 429 desvia > 2σ do normal — detecta problema cedo.
Take-aways
Rate limiter bem feito é invisível — user legítimo nunca sente, abuser é bloqueado antes de causar dano. Mal feito, é causa de incidente próprio. Token bucket via Redis Lua + shadow mode + canary é a receita conservadora que nunca te trai.