Projetando um Rate Limiter Global: Guia Completo de System Design
Resumo: Um guia de arquitetura em nível de produção para construir rate limiting distribuído em APIs e microsserviços, cobrindo algoritmos, armazenamento de estado, trade-offs de consistência global, quotas por tenant, limites adaptativos, resistência a abuso e operação observável.
Publicado: Fevereiro 2026
Tempo de leitura: 80 minutos
Palavras-chave: #SystemDesign #RateLimiter #APIGateway #Quota #SistemasDistribuídos #Escalabilidade #Confiabilidade #EntrevistaTécnica
Às 09:00, seu tráfego está normal. Às 09:01, uma versão bugada do cliente dispara retries sem backoff. Às 09:02, um tenant enterprise inicia backfill massivo. Às 09:03, bots iniciam credential stuffing.
Sem rate limiting, tudo compartilha o mesmo colapso.
Com rate limiting fraco, usuários legítimos são bloqueados enquanto abuso continua passando.
Rate limiter sério não é só contador:
- protege disponibilidade,
- garante fairness,
- aplica plano comercial,
- limita custo operacional,
- reduz blast radius.
Este guia detalha um desenho de nível senior para entrevistas e produção.
Sumário
- Análise de Requisitos
- Cálculos de Envelope
- Arquitetura de Alto Nível
- Escolha de Algoritmos
- Contrato de API de Decisão
- Modelagem de Dados e Chaves
- Limiting Embutido no Gateway (Core 1)
- Serviço Distribuído de Decisão (Core 2)
- Quotas Globais e Regionais (Core 3)
- Burst e Suavização (Core 4)
- Políticas por Plano e Tenant (Core 5)
- Limites Adaptativos por Risco (Core 6)
- Idempotência e Retries (Core 7)
- Resistência a Abuso e Evasão (Core 8)
- Caching e Estratégia de Storage
- Confiabilidade e Degradação
- Observabilidade e SLOs
- Dicas de Entrevista
- Anti-Patterns
- Conclusão
- Referências
- Referência Rápida
Análise de Requisitos
Requisitos Funcionais
- Aplicar limite por chave e janela configurável.
- Suportar dimensões: user, API key, tenant, IP, rota, método.
- Retornar decisão
ALLOW/DENYcom motivo. - Expor
remaining,reseteretry_after. - Suportar burst + limite sustentado.
- Suportar quotas hierárquicas.
- Atualizar políticas sem restart global.
- Gerar logs de decisão para analytics e forense.
Requisitos Não Funcionais
| Requisito | Meta | Motivo |
|---|---|---|
| Latência de decisão | < 5ms p99 no gateway | limiter não pode virar gargalo |
| Disponibilidade | 99,99% | camada de proteção crítica |
| Throughput | milhões de checks/s | praticamente todo request passa por aqui |
| Correção | erro bounded em distribuição | fairness e confiança de billing |
| Propagação de policy | < 30s | resposta operacional rápida |
| Isolamento | tenants ruidosos contidos | proteger plataforma compartilhada |
Perguntas de Clarificação
- Consistência global estrita é obrigatória?
- Quota impacta billing hard?
- Edge-only ou também service-to-service?
- Qual taxa tolerável de falso bloqueio?
- Em falha, fail-open ou fail-closed?
Premissas:
- enforcement no API gateway,
- SaaS multi-tenant,
- deploy multi-região,
- limites hard para billing,
- limites soft para cenários de risco baixo.
Cálculos de Envelope
Escala Assumida
Requests/dia: 90 bilhões
RPS médio: ~1.041.667
RPS pico: 6.000.000
Chaves ativas/dia: 80 milhões
Policies ativas: 300 mil
Throughput de Decisão
Checks/s médio ~= 1,04M
Checks/s pico ~= 6M
Check envolve:
- parse de dimensões,
- lookup de policy,
- operação atômica de contador,
- retorno de cabeçalhos.
Estado Quente
Se cada estado de contador ativo for ~120 bytes:
80M * 120B ~= 9,6GB estado quente mínimo
Na prática, multiplica por réplicas, dimensões e janelas.
Logging
Se logar 10% das decisões (180B cada):
90B * 0,1 * 180B ~= 1,62TB/dia
Insight
O desafio central é latência baixa + consistência suficiente + comportamento previsível em falhas.
Arquitetura de Alto Nível
flowchart TB
subgraph Clientes
APP["Apps/SDKs"]
INT["Integrações Externas"]
end
subgraph Edge
CDN["CDN"]
WAF["WAF/Bot Filter"]
GATE["API Gateway"]
LOCAL["Local Limiter Cache"]
end
subgraph Control Plane
POLICY["Policy Service"]
ADMIN["Admin API"]
DIST["Policy Distribution"]
end
subgraph Data Plane
DECIDE["Decision Service"]
COUNTER["Counter Store"]
RISK["Risk Service"]
end
subgraph Analytics
BUS[("Kafka")]
OLAP[("Warehouse")]
ALERT["Alerting"]
end
APP --> CDN --> WAF --> GATE
INT --> CDN
GATE --> LOCAL
GATE --> DECIDE
POLICY --> DIST
DIST --> GATE
DIST --> DECIDE
DECIDE --> COUNTER
DECIDE --> RISK
GATE --> BUS
DECIDE --> BUS
BUS --> OLAP
BUS --> ALERT
ADMIN --> POLICY
Princípios
- Fast-path local no gateway.
- Counter store distribuído para rigor.
- Control plane separado do data plane.
- Fail mode por criticidade de endpoint.
Escolha de Algoritmos
Fixed Window
- simples e barato,
- problema de burst na borda da janela.
Sliding Log
- precisão alta,
- custo alto de memória/CPU.
Sliding Window Counter
- equilíbrio bom,
- comum em produção.
Token Bucket
- excelente para burst controlado,
- muito usado em API gateways.
Leaky Bucket
- suaviza saída,
- bom para proteger downstream.
Recomendação Prática
- token bucket para shaping por cliente,
- sliding window para quotas de plano,
- composição hierárquica de algoritmos.
| Algoritmo | Precisão | Custo | Burst |
|---|---|---|---|
| Fixed | média-baixa | baixo | fraco |
| Sliding Log | alta | alto | médio |
| Sliding Counter | alta | médio | médio |
| Token Bucket | alta | baixo-médio | forte |
| Leaky Bucket | média | médio | controlado |
Contrato de API de Decisão
POST /internal/v1/limit/check
Request:
{
"request_id": "req_901",
"subject": {
"tenant_id": "t_44",
"api_key_id": "k_77",
"user_id": "u_12",
"ip": "203.0.113.8"
},
"resource": {
"route": "/v1/payments",
"method": "POST",
"service": "payments-api"
},
"context": {
"region": "us-east-1",
"risk_score": 0.14
}
}
Response allow:
{
"decision": "ALLOW",
"applied_policy_id": "policy_payments_post_tier2",
"remaining": 127,
"reset_after_ms": 1820,
"retry_after_ms": 0,
"reason": "WITHIN_LIMIT",
"limit_headers": {
"x-ratelimit-limit": "200",
"x-ratelimit-remaining": "127",
"x-ratelimit-reset": "2"
}
}
Response deny:
{
"decision": "DENY",
"applied_policy_id": "policy_payments_post_tier2",
"remaining": 0,
"reset_after_ms": 780,
"retry_after_ms": 780,
"reason": "TOKEN_BUCKET_EMPTY"
}
Modelagem de Dados e Chaves
Padrão de Chave de Contador
rl:{policy_id}:{tenant_id}:{api_key_id}:{route_hash}:{window_bucket}
Exemplo de Policy
{
"policy_id": "policy_payments_post_tier2",
"scope": {
"tenant_tier": "PRO",
"route": "/v1/payments",
"method": "POST"
},
"algorithm": "TOKEN_BUCKET",
"config": {
"capacity": 200,
"refill_per_sec": 50
},
"mode": "HARD",
"fail_behavior": "FAIL_CLOSED",
"priority": 900,
"enabled": true,
"updated_at": "2026-02-22T22:10:00Z"
}
Quotas Hierárquicas
{
"quota_tree": [
{ "level": "TENANT", "limit": "50000/min" },
{ "level": "API_KEY", "limit": "1000/min" },
{ "level": "ROUTE", "limit": "200/min" }
]
}
Boas Práticas
- TTL para buckets.
- Policy versionada.
- Separar hot counters de histórico analítico.
Limiting Embutido no Gateway (Core 1)
Vantagens
- menos hop de rede,
- bloqueio antecipado,
- melhor custo por decisão.
Fluxo Híbrido
sequenceDiagram
participant C as Cliente
participant G as Gateway
participant L as Local Limiter
participant R as Remote Limiter
C->>G: request
G->>L: local check
alt local deny
L-->>G: deny
G-->>C: 429
else local allow
L-->>G: provisional allow
G->>R: strict/global check (if needed)
alt remote deny
R-->>G: deny
G-->>C: 429
else allow
R-->>G: allow
G-->>C: forward upstream
end
end
Trade-off
Só local = rápido, menos preciso globalmente.
Híbrido = bom equilíbrio.
Serviço Distribuído de Decisão (Core 2)
Responsabilidades
- resolver policy,
- atualizar contador atômico,
- retornar decisão,
- emitir evento de auditoria.
Atomicidade
Use script atômico (ex: Lua em Redis) para check + decrement.
Sem atomicidade, concorrência cria allow indevido.
Particionamento
Shard por hash(policy + subject).
Multi-DC
Global estrito em todo request costuma estourar latência. Para muitos cenários, bounded inconsistency com controle é melhor.
Quotas Globais e Regionais (Core 3)
Problema
Tenant global com limite diário, tráfego distribuído em várias regiões.
Opção Prática: Budget Leasing
- gerenciador global entrega "lotes de quota" para regiões,
- região consume localmente com baixa latência,
- solicita novo lote quando perto do fim.
flowchart LR
GQ["Global Quota Manager"] --> R1["Region A Lease"]
GQ --> R2["Region B Lease"]
GQ --> R3["Region C Lease"]
R1 --> C1["Local Counter"]
R2 --> C2["Local Counter"]
R3 --> C3["Local Counter"]
Trade-offs
- pequena discrepância temporal,
- grande ganho de latência/disponibilidade.
Burst e Suavização (Core 4)
Regra Composta
Combinar:
- limite de burst curto,
- limite sustentado longo.
ALLOW se burst_ok && sustained_ok
Retry-After com Jitter
Retornar retry com jitter ajuda a evitar retry storm sincronizado.
Políticas por Plano e Tenant (Core 5)
Exemplo de Plano
| Plano | Limite Global | Burst |
|---|---|---|
| Free | 60/min | 10/5s |
| Pro | 3.000/min | 200/5s |
| Enterprise | custom | custom |
Ordem de Resolução
- bloqueio emergencial,
- override de tenant,
- default de plano,
- ajuste por rota,
- fallback anônimo.
Requisito
Mudança de policy sem redeploy em massa.
Limites Adaptativos por Risco (Core 6)
Inputs
- risco comportamental,
- taxa de falha de auth,
- detecção de anomalia,
- sensibilidade da rota,
- status de incidente regional.
Exemplo
if risk_score > 0.9 -> reduzir limite 80%
if 0.7 <= risk_score <= 0.9 -> reduzir 40%
else -> limite padrão
Guardrails
- limite máximo de ajuste,
- decaimento temporal,
- whitelist controlada para integrações críticas.
Idempotência e Retries (Core 7)
Questão
Retry de request idempotente deve consumir quota novamente?
Modelos
- cobrar tentativa (mais simples, mais duro),
- cobrar chave idempotente única (mais justo em rede instável).
Recomendação
- endpoints financeiros: considerar dedupe por idempotency-key,
- endpoints de risco/abuso: cobrar tentativa.
Chave de Dedupe
(api_key, idempotency_key, route)
Resistência a Abuso e Evasão (Core 8)
Táticas de Evasão
- rotação de IP,
- troca de API keys,
- low-and-slow distribuído,
- user-agent aleatório.
Defesa em Camadas
- chave multidimensional,
- WAF antes do limiter,
- score de comportamento,
- challenge progressivo,
- sinais de honeypot.
Regra Prática
DENY se qualquer dimensão hard estourar
THROTTLE para risco moderado
ALLOW com observabilidade para tráfego normal
Caching e Estratégia de Storage
Split de Dados
- counters quentes em in-memory,
- policies em config store durável,
- logs em stream + warehouse.
Caches
- local policy cache,
- negative cache curto para blocos recentes,
- cache de decisão repetida em janelas curtíssimas.
TTLs
- bucket TTL alinhado com janela,
- deny cache curto,
- policy cache com invalidação por versão.
Confiabilidade e Degradação
Falhas
- counter store fora,
- policy stale,
- partição inter-região,
- skew de clock.
Matrix de Fail Mode
| Tipo de endpoint | Comportamento |
|---|---|
| pagamento/crítico | fail-closed ou fallback estrito |
| leitura geral | fail-open com caps emergenciais |
| interno baixo risco | fail-open com telemetria |
Plano de Degradação
- ativar limites estáticos locais,
- apertar dimensões de alto risco,
- desligar lógica adaptativa custosa,
- preservar disponibilidade core.
Observabilidade e SLOs
SLOs
| SLI | SLO |
|---|---|
| latência de decisão p99 | < 5ms |
| disponibilidade | > 99,99% |
| lag de propagação de policy p95 | < 30s |
| falso bloqueio em coorte válida | abaixo da meta |
| erro do counter store | dentro do error budget |
Golden Signals
- allow/deny por rota/tenant,
- latência de decisão,
- tempo de comando no counter store,
- skew de versão de policy,
- fail-open activation.
Trace
x-request-id
x-tenant-id
x-api-key-id
x-policy-id
x-rate-limit-decision
Dicas de Entrevista
Estrutura
- Clarifique dimensões e rigidez.
- Justifique algoritmo.
- Desenhe gateway + serviço distribuído.
- Explique quotas globais por lease.
- Trate fail mode por criticidade.
- Feche com observabilidade e abuse.
Erros Comuns
- contador global único para tudo,
- policy hardcoded,
- sem idempotência,
- sem plano de degradação,
- sem estratégia anti-evasão.
Script Curto
- fast check local,
- strict check remoto quando necessário,
- limites hierárquicos,
- lease regional para quota global,
- fail-open/closed por endpoint.
Anti-Patterns
1) Limitar só por IP
Problema: evasão fácil e falso bloqueio por NAT.
Correção: chaves multidimensionais.
2) Limites hardcoded no código
Problema: resposta lenta a incidentes.
Correção: policy service versionado.
3) Check/decrement não atômico
Problema: race condition em alta concorrência.
Correção: operação atômica.
4) Consistência global estrita para tudo
Problema: latência/availability ruins.
Correção: consistência pragmática por risco.
5) Fail-open sempre
Problema: abuso passa livre em falha.
Correção: matrix de fail behavior.
6) Sem headers de limite
Problema: cliente não sabe quando retryar.
Correção: Retry-After e headers padrão.
7) Sem monitorar skew de policy
Problema: comportamento inconsistente por região.
Correção: monitoramento e alerta de convergência.
Conclusão
Rate limiter de produção é uma camada distribuída de controle de risco e capacidade.
Arquitetura madura combina:
- decisão rápida na borda,
- estado distribuído com atomicidade,
- políticas flexíveis por plano,
- adaptação por risco,
- degradação previsível,
- observabilidade forte.
Esse é um dos temas mais recorrentes em entrevistas porque separa respostas superficiais de engenharia realmente pragmática.
Referências
- RFC 6585 - 429 Too Many Requests
- Redis Rate Limiting Patterns
- Envoy Global Rate Limiting
- NGINX limit_req
- Google SRE Book
- Designing Data-Intensive Applications
Referência Rápida
Componentes
| Componente | Responsabilidade |
|---|---|
| API Gateway | enforcement inicial |
| Local Limiter Cache | check ultrarrápido |
| Decision Service | decisão estrita distribuída |
| Counter Store | estado atômico de bucket |
| Policy Service | definição e distribuição |
| Risk Service | ajuste adaptativo |
| Analytics Pipeline | visibilidade e tuning |
Regras Críticas
- Separar control plane de data plane.
- Aplicar limites hierárquicos.
- Garantir atomicidade no contador.
- Definir fail mode por endpoint.
- Medir falso bloqueio e drift de policy.
Resumo de 10 minutos
1) Dimensões + criticidade.
2) Token bucket + sliding window.
3) Fast local + strict remoto.
4) Quota global por lease regional.
5) Observabilidade + abuse controls + degradação.
Você agora tem um blueprint completo para rate limiting distribuído em escala global.