Como implementar rate limiting em APIs: estratégias para proteger sem bloquear usuários legítimos
1. Fundamentos do Rate Limiting e seus Objetivos
1.1. O que é rate limiting e por que ele é crítico para APIs modernas
Rate limiting é uma técnica de controle de tráfego que restringe o número de requisições que um cliente pode fazer a uma API em um intervalo de tempo específico. Em um ecossistema digital onde APIs são a espinha dorsal de aplicações modernas, o rate limiting protege contra abusos intencionais (ataques DDoS, scraping) e não intencionais (bugs em clientes, loops de retry mal configurados). Sem ele, um único cliente mal comportado pode degradar a experiência de todos os outros usuários.
1.2. Equilíbrio entre segurança e experiência do usuário: evitar falsos positivos
O maior desafio do rate limiting não é proteger contra abusos óbvios, mas sim não penalizar usuários legítimos. Um limite muito restritivo pode bloquear uma integração válida durante um pico de uso sazonal. A chave está em implementar políticas que diferenciem comportamento anômalo de uso intenso legítimo.
1.3. Conceitos-chave: janela de tempo, limite de requisições, burst vs. throughput sustentado
Três conceitos fundamentais definem qualquer estratégia de rate limiting:
- Janela de tempo: o intervalo (ex: 1 minuto, 1 hora) durante o qual o limite é contado.
- Limite de requisições: o número máximo de chamadas permitidas na janela.
- Burst vs. throughput sustentado: um burst é um pico momentâneo de requisições; throughput sustentado é a média ao longo do tempo. Uma boa estratégia permite bursts controlados sem comprometer a estabilidade.
2. Algoritmos Clássicos de Rate Limiting
2.1. Token Bucket: flexibilidade para picos com consumo controlado
O algoritmo Token Bucket mantém um balde com capacidade máxima de tokens. A cada intervalo de tempo, tokens são adicionados até o limite. Cada requisição consome um token. Se o balde está vazio, a requisição é rejeitada. Isso permite bursts curtos (quando o balde está cheio) enquanto limita a taxa média.
# Exemplo conceitual de Token Bucket
CAPACIDADE_MAXIMA = 10
TOKENS_POR_SEGUNDO = 2
balde_atual = CAPACIDADE_MAXIMA
def verificar_requisicao():
global balde_atual
if balde_atual > 0:
balde_atual -= 1
return True
else:
return False
def recarregar_tokens():
global balde_atual
balde_atual = min(CAPACIDADE_MAXIMA, balde_atual + TOKENS_POR_SEGUNDO)
2.2. Leaky Bucket: suavização de tráfego e filas previsíveis
O Leaky Bucket funciona como um funil: as requisições entram em uma fila e são processadas a uma taxa constante. Se a fila enche, novas requisições são descartadas. É ideal para garantir vazão constante, mas menos flexível para bursts.
# Exemplo conceitual de Leaky Bucket
FILA_MAXIMA = 20
TAXA_PROCESSAMENTO = 5 # requisições por segundo
fila_atual = 0
def adicionar_requisicao():
global fila_atual
if fila_atual < FILA_MAXIMA:
fila_atual += 1
return True
else:
return False
def processar_fila():
global fila_atual
fila_atual = max(0, fila_atual - TAXA_PROCESSAMENTO)
2.3. Sliding Window Log vs. Sliding Window Counter: precisão versus eficiência de memória
- Sliding Window Log: armazena um timestamp para cada requisição. Ao verificar, conta quantas requisições caem dentro da janela deslizante. Preciso, mas consome muita memória para APIs com alto throughput.
- Sliding Window Counter: divide a janela em buckets menores (ex: 1 minuto dividido em 6 buckets de 10 segundos). Aproxima a contagem da janela deslizante com muito menos memória, aceitando pequena imprecisão.
3. Estratégias de Implementação no Backend
3.1. Rate limiting em memória (Redis, Memcached) com comandos atômicos (INCR, EXPIRE)
Redis é a escolha mais comum para rate limiting distribuído. O comando INCR incrementa um contador atômico, e EXPIRE define o TTL da janela.
# Exemplo com Redis (pseudo-código)
chave = "rate_limit:usuario123:minuto"
atual = redis.INCR(chave)
if atual == 1:
redis.EXPIRE(chave, 60) # expira em 60 segundos
if atual > 100:
return HTTP 429 (Too Many Requests)
else:
processar_requisicao()
3.2. Implementação em middleware de API Gateway (Kong, NGINX, Envoy)
Gateways como Kong e NGINX oferecem plugins nativos de rate limiting. No Kong, o plugin rate-limiting pode ser configurado por consumer, por rota ou globalmente, com suporte a Redis para sincronização entre nós.
# Configuração do plugin rate-limiting no Kong
plugins:
- name: rate-limiting
config:
minute: 100
hour: 5000
policy: local # ou "redis" para clusters
3.3. Rate limiting distribuído: consistência eventual e race conditions em clusters
Em clusters com múltiplos servidores, o rate limiting precisa de um armazenamento centralizado (Redis) ou de algoritmos de consenso. Race conditions podem ocorrer se duas requisições chegam simultaneamente e ambas verificam o contador antes de incrementá-lo. O uso de comandos atômicos (INCR, Lua scripts) resolve esse problema.
4. Políticas Inteligentes para Não Bloquear Usuários Legítimos
4.1. Diferenciação por endpoint: limites mais brandos para GETs, mais rígidos para POSTs/PUTs
Endpoints de leitura (GET) geralmente podem ter limites mais altos, enquanto endpoints de escrita (POST, PUT, DELETE) devem ser mais restritivos para evitar criação massiva de recursos ou alterações indevidas.
# Política de limites por método HTTP
GET /api/usuarios: 1000 req/min
POST /api/usuarios: 20 req/min
PUT /api/usuarios/:id: 10 req/min
DELETE /api/usuarios/:id: 5 req/min
4.2. Rate limiting baseado em perfil de usuário (free vs. premium, API key vs. OAuth)
Clientes com planos pagos ou autenticação mais forte (OAuth) podem receber limites maiores. A diferenciação pode ser feita pelo header Authorization ou pela chave de API.
# Limites por perfil
perfil = obter_perfil_do_usuario(token)
limites = {
"free": {"minute": 10, "hour": 100},
"premium": {"minute": 100, "hour": 1000},
"enterprise": {"minute": 1000, "hour": 10000}
}
limite_atual = limites[perfil]
4.3. Técnicas de "soft limit": warnings, degradação gradual e filas de espera
Em vez de bloquear abruptamente, implemente soft limits:
- Warnings: headers indicando que o limite está próximo.
- Degradação gradual: aumentar o tempo de resposta em vez de rejeitar.
- Filas de espera: enfileirar requisições excedentes e processá-las quando houver capacidade.
5. Comunicação Clara com o Cliente: Headers e Respostas
5.1. Headers padrão: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset
Sempre inclua headers informativos nas respostas:
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1623456789
5.2. Respostas HTTP 429 (Too Many Requests) com Retry-After e corpo explicativo
Quando o limite é excedido, retorne 429 com o header Retry-After indicando quantos segundos o cliente deve esperar.
HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/json
{
"error": "rate_limit_exceeded",
"message": "Limite de requisições excedido. Tente novamente em 30 segundos.",
"retry_after_seconds": 30
}
5.3. Estratégias de backoff: exponencial, jitter e retry com idempotência
Clientes devem implementar backoff exponencial com jitter para evitar tempestades de retry. A idempotência dos endpoints ajuda a garantir que retries não causem efeitos colaterais.
# Exemplo de backoff exponencial com jitter
tentativa = 0
while tentativa < 5:
resposta = fazer_requisicao()
if resposta.status == 429:
espera = (2 ** tentativa) + random.uniform(0, 1)
time.sleep(espera)
tentativa += 1
else:
break
6. Monitoramento, Alertas e Ajuste Fino
6.1. Métricas essenciais: taxa de rejeição, latência, distribuição de bursts
Monitore:
- Taxa de rejeição: % de requisições que retornam 429.
- Latência: aumento de latência em clientes próximos ao limite.
- Distribuição de bursts: horários e endpoints com maior concentração de requisições.
6.2. Logs de decisões de rate limiting para auditoria e debugging
Cada decisão de bloqueio deve ser registrada com timestamp, cliente, endpoint e motivo. Isso ajuda a identificar falsos positivos e padrões de abuso.
[2025-03-15 14:32:01] RATE_LIMIT_BLOCKED | cliente: api_key_abc123 | endpoint: POST /api/orders | motivo: limite_minuto_excedido (150/100)
6.3. Ajuste dinâmico de limites com base em padrões de tráfego (machine learning simples)
Sistemas avançados podem usar médias móveis ou modelos simples de ML para ajustar limites automaticamente. Por exemplo, aumentar temporariamente o limite para um cliente que sempre respeitou o rate limit durante um pico sazonal.
7. Considerações Avançadas e Armadilhas Comuns
7.1. Rate limiting em APIs GraphQL: consultas complexas e análise de profundidade
Em GraphQL, uma única requisição pode consultar múltiplos recursos. O rate limiting deve considerar a complexidade da query (profundidade, número de campos solicitados) em vez de apenas contar requisições.
# Atribuir peso a cada campo da query GraphQL
peso_query = calcular_peso(query_graphql)
if peso_query > 100:
return HTTP 429
7.2. Sincronização entre microserviços: problemas de clock skew e consistência
Em ambientes distribuídos, clocks de servidores podem divergir. Use Redis ou um serviço de clock global para evitar inconsistências. Evite depender de timestamps locais para decisões de rate limiting.
7.3. Testes de carga e simulação de cenários de abuso vs. uso legítimo intenso
Antes de colocar em produção, simule:
- Cenário de abuso: requisições em rajada de um único IP.
- Cenário legítimo intenso: múltiplos usuários fazendo requisições simultâneas durante uma campanha de marketing.
Ajuste os limites com base nos resultados para evitar falsos positivos.
Referências
- Documentação Oficial do Redis sobre Rate Limiting — Exemplos de implementação de rate limiting usando comandos atômicos do Redis.
- Guia de Rate Limiting do Kong API Gateway — Configuração do plugin rate-limiting no Kong, com suporte a Redis e políticas por consumer.
- Algoritmos de Rate Limiting: Token Bucket vs Leaky Bucket — Artigo da Cloudflare explicando os principais algoritmos e suas aplicações.
- HTTP Rate Limit Headers (IETF Draft) — Especificação dos headers padronizados para comunicação de rate limiting em APIs HTTP.
- Rate Limiting em GraphQL: Abordagens e Desafios — Artigo da Apollo sobre como implementar rate limiting considerando a complexidade de queries GraphQL.
- Estratégias de Backoff Exponencial com Jitter — Post da AWS sobre implementação de backoff para retries em APIs com rate limiting.
- Rate Limiting Distribuído com Redis e Lua Scripts — Tutorial prático sobre rate limiting distribuído usando scripts Lua no Redis para atomicidade.