Rate limiting e throttling em APIs distribuídas
1. Fundamentos de Rate Limiting e Throttling
1.1. Definições e diferenças conceituais
Rate limiting e throttling são mecanismos complementares, mas distintos. Rate limiting estabelece um teto absoluto de requisições que um cliente pode fazer em uma janela de tempo — uma vez atingido o limite, novas requisições são rejeitadas. Throttling, por outro lado, suaviza o fluxo de requisições, permitindo que o cliente continue operando, mas com velocidade reduzida após exceder um patamar.
1.2. Objetivos arquiteturais
Em sistemas distribuídos, esses mecanismos protegem contra abuso (bots, scraping), garantem qualidade de serviço (QoS) para todos os consumidores e promovem fair use — impedindo que um único cliente monopolize recursos compartilhados. A escolha entre rate limiting e throttling impacta diretamente a experiência do desenvolvedor e a resiliência do sistema.
1.3. Trade-offs no design
Implementar controle de taxa introduz latência adicional (cada requisição precisa ser contabilizada), exige armazenamento consistente entre instâncias e pode reduzir a disponibilidade percebida se configurado agressivamente. O arquiteto precisa equilibrar proteção com usabilidade.
2. Estratégias de Implementação no Backend
2.1. Algoritmos clássicos
- Token Bucket: Um balde com capacidade máxima é preenchido a uma taxa constante. Cada requisição consome um token. Permite bursts controlados.
- Leaky Bucket: Requisições entram em um buffer e são processadas a uma taxa fixa. Excesso é descartado. Suaviza picos.
- Fixed Window: Conta requisições em janelas de tempo fixas (ex.: 100 req/min). Simples, mas permite bursts no final de uma janela e início da próxima.
- Sliding Window Log: Mantém um log timestampado de requisições. Preciso, mas consome mais memória.
Exemplo de implementação com Token Bucket em pseudocódigo:
função allowRequest(clientId, tokensNeeded):
bucket = redis.get("bucket:" + clientId)
se bucket == null:
bucket = { tokens: maxTokens, lastRefill: now() }
tempoDecorrido = now() - bucket.lastRefill
tokensAdicionados = tempoDecorrido * refillRate
bucket.tokens = min(bucket.tokens + tokensAdicionados, maxTokens)
bucket.lastRefill = now()
se bucket.tokens >= tokensNeeded:
bucket.tokens -= tokensNeeded
redis.set("bucket:" + clientId, bucket)
retornar true
senão:
retornar false
2.2. Armazenamento distribuído
Redis é a escolha dominante. Para Fixed Window, usa-se INCR com expiração. Para Sliding Window, sorted sets com timestamp como score permitem consultas eficientes:
função slidingWindowCheck(clientId, windowMs, maxRequests):
chave = "rate:" + clientId
agora = timestampAtualMs()
inicioJanela = agora - windowMs
redis.zremrangebyscore(chave, 0, inicioJanela) // remove antigos
contagem = redis.zcard(chave)
se contagem < maxRequests:
redis.zadd(chave, agora, agora)
redis.expire(chave, windowMs / 1000)
retornar true
senão:
retornar false
2.3. Localização da decisão
API Gateway (Kong, AWS API Gateway, Zuul): centraliza a lógica, simplifica os microsserviços, mas adicion latência de rede e ponto único de falha. Biblioteca embarcada (Bucket4j, express-rate-limit): oferece baixa latência, mas exige coordenação entre instâncias e duplicação de código.
3. Rate Limiting em Arquiteturas Distribuídas
3.1. Sincronização entre instâncias
Para rate limiting global preciso, usam-se locks distribuídos (Redlock) ou sharding de contadores por hash do clientId. O gossip protocol pode propagar limites aproximados com baixa sobrecarga, sacrificando precisão.
3.2. Global vs. por instância
Rate limiting global garante fair use exato, mas depende de um repositório central (Redis), criando contenção e latência. Rate limiting por instância é mais rápido e tolerante a falhas, mas permite que um cliente abuse se distribuir requisições entre muitas instâncias.
3.3. Desafios de consistência
Race conditions ocorrem quando duas instâncias leem e escrevem o contador simultaneamente. Clock skew entre servidores pode invalidar janelas de tempo. Tolerância a falhas exige fallback para limites conservadores quando o Redis está indisponível.
4. Throttling Adaptativo e Controle de Fluxo
4.1. Backpressure e feedback loops
Throttling adaptativo ajusta dinamicamente as taxas com base na carga do sistema. Um feedback loop monitora latências e taxas de erro, reduzindo limites quando a carga aumenta. O padrão backpressure propaga essa informação upstream.
4.2. Prioridade por cliente
Clientes premium recebem limites mais altos e são throttled apenas após exaurir sua cota. Clientes free são throttled mais cedo. Isso pode ser implementado com múltiplos buckets de token por tier.
4.3. Integração com circuit breaker
Quando o throttling não é suficiente, o circuit breaker abre, rejeitando requisições imediatamente para proteger o sistema. Bulkhead isola recursos por cliente ou endpoint, impedindo que um cliente degradado afete os demais.
5. Políticas de Resposta e Comunicação com Clientes
5.1. Códigos de status e headers
429 Too Many Requests: cliente excedeu o rate limit.503 Service Unavailable: throttling por carga do servidor.Retry-After: tempo (em segundos) que o cliente deve esperar.
5.2. Headers de rate limit
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1623456789
Esses headers permitem que clientes inteligentes ajustem seu comportamento proativamente.
5.3. Estratégias de retry
Exponential backoff com jitter evita o "thundering herd" quando todos os clientes retentam simultaneamente:
função calcularDelay(tentativa):
base = 1000 // 1 segundo
maxDelay = 60000 // 1 minuto
delay = min(base * 2^tentativa, maxDelay)
jitter = random(0, delay * 0.5)
retornar delay + jitter
Idempotência é essencial: o servidor deve garantir que retries não criem efeitos colaterais duplicados.
6. Monitoramento, Observabilidade e Governança
6.1. Métricas essenciais
Taxa de rejeição (requests rejected / total requests), latência induzida pelo rate limiter e uso de cota por cliente. Essas métricas alimentam dashboards e alertas.
6.2. Tracing distribuído
Headers de rate limit devem ser propagados em spans de tracing (OpenTelemetry), permitindo identificar gargalos. Logs centralizados ajudam a detectar padrões de abuso.
6.3. Ajuste dinâmico
Limites estáticos são insuficientes. Sistemas maduros ajustam limites automaticamente com base em métricas em tempo real, usando reinforcement learning ou regras heurísticas.
7. Considerações de Segurança e Anti-Abuso
7.1. Identificação de clientes
API keys e JWT tokens são preferíveis a IP, pois IPs podem ser compartilhados (NAT) ou rotativos. Fingerprinting (User-Agent, headers) complementa a identificação, mas com menor precisão.
7.2. Proteção contra DDoS
Rate limiting por si só não para DDoS volumétricos. É necessário combiná-lo com WAF (Web Application Firewall), CDN com mitigação de DDoS e filtros em nível de rede.
7.3. Camadas de proteção
- Aplicação: rate limiting no gateway ou serviço.
- Rede: firewalls, IDS/IPS.
- Infraestrutura: CDN (Cloudflare, Akamai) absorve tráfego malicioso antes de chegar ao backend.
8. Padrões de Projeto e Exemplos Práticos
8.1. Rate Limiter as a Service
Um serviço dedicado (ex.: com Redis Cluster) centraliza toda a lógica de rate limiting. Microsserviços consultam esse serviço via gRPC ou HTTP. Vantagem: governança centralizada. Desvantagem: latência adicional e ponto único de falha.
8.2. Implementação híbrida
Combina cache local (Guava Cache, Caffeine) para decisões rápidas com Redis para sincronização eventual:
função hybridCheck(clientId):
localTokens = cacheLocal.get(clientId)
se localTokens > 0:
cacheLocal.decrement(clientId)
retornar true
// fallback para Redis
redisOk = redisCheck(clientId)
se redisOk:
cacheLocal.put(clientId, batchSize) // recarrega lote
retornar true
senão:
retornar false
8.3. Estudo de caso: GitHub API
A API pública do GitHub usa rate limiting baseado em token de acesso. Cada requisição retorna headers X-RateLimit-*. Para requisições autenticadas, o limite é de 5000 req/h. O algoritmo é Sliding Window, implementado com Redis. Quando o limite é excedido, retorna 403 Forbidden (não 429) por razões históricas. O Twitter (X) usa abordagem similar, com limites por endpoint e por janela de 15 minutos.
Referências
- Rate Limiting Strategies for Distributed Systems (Redis) — Visão geral dos algoritmos de rate limiting com exemplos práticos em Redis.
- AWS API Gateway Throttling — Documentação oficial sobre configuração de throttling no API Gateway da AWS.
- GitHub REST API Rate Limiting — Documentação oficial do GitHub sobre limites de taxa e headers de resposta.
- Stripe Rate Limiting Best Practices — Guia do Stripe sobre como implementar retry com exponential backoff e jitter.
- Bucket4j - Java Rate Limiting Library — Implementação do algoritmo Token Bucket para JVM, com suporte a Redis e clustering.
- Cloudflare Rate Limiting Documentation — Como configurar rate limiting no Cloudflare para proteção contra DDoS e abuso.