Toda API pública precisa de um rate limiter, e a maioria dos times descobre isso do mesmo jeito: um único cliente com um loop de retry, ou um único scraper, derruba o serviço para todo mundo. Um limitador põe um teto no quanto uma chave, um usuário ou um IP pode pedir, e assim um mau ator custa um 429 em vez de uma queda.
As perguntas de design são sempre as mesmas: qual algoritmo, onde fica o limitador, onde moram os contadores e o que acontece quando o armazenamento deles cai. Este artigo responde a cada uma e termina com um diagrama de arquitetura de um limitador distribuído na frente de uma API.
O que o limitador precisa fazer
- Decidir em poucos milissegundos se uma requisição é permitida, em toda requisição.
- Aplicar regras diferentes a chamadores diferentes: por chave de API, por usuário, por IP, por rota.
- Se comportar igual, não importa qual instância do servidor atenda a requisição.
- Dizer com clareza aos clientes quando estão limitados e quando tentar de novo.
- Continuar funcionando, ou falhar de forma previsível, quando o próprio armazenamento tem um problema.
O terceiro ponto é o que transforma isso em um problema de sistemas distribuídos. Um limite de 100 requisições por minuto não significa nada se cada uma de dez instâncias do gateway conta os próprios 100.
Cinco algoritmos
| Algoritmo | Como conta | Ponto forte | Ponto fraco |
|---|---|---|---|
| Token bucket | Um balde se enche a uma taxa fixa; cada requisição pega um token | Permite rajadas curtas até o tamanho do balde | Dois parâmetros para ajustar |
| Leaky bucket | As requisições saem de uma fila a uma taxa fixa | Taxa de saída uniforme | Rajadas esperam na fila e somam latência |
| Contador de janela fixa | Um contador por chave por minuto | O mais barato: um incremento | O dobro do limite pode passar perto da borda da janela |
| Log de janela deslizante | Guarda um timestamp por requisição | Exato | A memória cresce com o tráfego |
| Contador de janela deslizante | Pondera a contagem da janela anterior pela sobreposição | Quase exato, dois contadores por chave | Uma aproximação |
O problema da janela fixa é fácil de ver: um cliente manda 100 requisições às 12:00:59 e mais 100 às 12:01:00, e as duas janelas aceitam. São 200 requisições em dois segundos contra um limite de 100 por minuto.
Token bucket é o padrão usual para APIs, porque rajadas são comportamento normal de cliente. Um limite como “100 requisições por minuto, rajada de 20” vira diretamente uma taxa de recarga e um tamanho de balde. A Stripe contou como roda limitadores token bucket sobre o Redis no artigo sobre escalar uma API com rate limiters, ao lado de um limitador separado para requisições concorrentes.
Contadores de janela deslizante são o jeito barato de eliminar o problema da borda quando você prefere um limite rígido por janela.
Onde fica o limitador
Você pode aplicar limites em três lugares, e muitos sistemas usam mais de um:
- Na borda (CDN ou WAF). Limites grosseiros por IP para absorver enxurradas antes que cheguem até você.
- No API gateway. Limites por chave e por rota que precisam saber quem está chamando. É aqui que vive a maioria dos limites de produto.
- Dentro de um serviço. Limites que dependem de dados do negócio, como “duas exportações simultâneas por conta”.
Um gateway pode contar localmente (cada instância tem os próprios contadores) ou globalmente (todas as instâncias compartilham um armazenamento). Limites locais são de graça e bastam para uma proteção grosseira; divida o limite pelo número de instâncias e aceite o erro. Limites de produto pelos quais os clientes pagam precisam de um armazenamento global. O Envoy, por exemplo, suporta os dois: um filtro de rate limit local e um modo global que chama um serviço externo de rate limit apoiado no Redis.
Contadores no Redis
O Redis é a escolha comum para contadores globais: é rápido, mantém os contadores em memória e expira as chaves sozinho. A armadilha é a corrida entre ler um contador e gravá-lo de volta. Dois gateways leem 99, os dois permitem a requisição, os dois gravam 100.
Mantenha cada decisão atômica. Para uma janela fixa, INCR seguido de EXPIRE em uma chave nova basta; a documentação do INCR no Redis mostra padrões de rate limiting construídos sobre ele. Para um token bucket, rode um pequeno script Lua que lê o balde, recarrega conforme o tempo decorrido, pega um token e grava tudo de volta em um passo. O Redis executa um script sem intercalar outros comandos, então requisições concorrentes veem um balde consistente.
-- KEYS[1] = bucket key, ARGV = rate per second, capacity, now (ms), cost
local b = redis.call('HMGET', KEYS[1], 'tokens', 'ts')
local rate, cap, now, cost = tonumber(ARGV[1]), tonumber(ARGV[2]), tonumber(ARGV[3]), tonumber(ARGV[4])
local tokens = tonumber(b[1]) or cap
local ts = tonumber(b[2]) or now
tokens = math.min(cap, tokens + (now - ts) / 1000 * rate)
local allowed = tokens >= cost
if allowed then tokens = tokens - cost end
redis.call('HSET', KEYS[1], 'tokens', tokens, 'ts', now)
redis.call('PEXPIRE', KEYS[1], math.ceil(cap / rate * 1000))
return allowed and 1 or 0
Passe o horário a partir de quem chama ou use o comando TIME do Redis; misturar os relógios de muitos gateways é uma fonte comum de resultados estranhos. Se o mesmo Redis também serve de cache, observe a política de eviction: um balde despejado é um limite zerado. O artigo sobre estratégias de cache cobre as configurações de eviction.
Avisando o cliente
Responda a uma requisição limitada com 429 Too Many Requests, definido na RFC 6585, e inclua um cabeçalho Retry-After com o número de segundos de espera. Bons clientes recuam; os outros pelo menos recebem um sinal claro. Um rascunho do IETF, RateLimit header fields for HTTP, propõe cabeçalhos padrão que informam aos clientes a cota e o saldo restante antes de chegarem ao limite; no momento em que escrevemos (outubro de 2026), ainda é um rascunho, então trate os nomes de cabeçalho dele como provisórios.
Documente os limites. Um limite que os clientes descobrem ao serem bloqueados vira ticket de suporte.
Quando o Redis cai
Decida isso antes de acontecer:
- Fail open. Libere as requisições quando o limitador não conseguir decidir. A API continua de pé e você perde a proteção enquanto durar. É a escolha usual para limites de produto.
- Fail closed. Rejeite as requisições. É o certo para operações caras ou sujeitas a abuso, como tentativas de login ou envio de SMS.
- Voltar a limites locais. Cada gateway aplica um limite local grosseiro até o armazenamento voltar.
Seja qual for a escolha, crie um alerta. Um limitador que passa uma semana em fail open sem ninguém saber não é limitador.
O diagrama de arquitetura
Conteúdo do diagrama
- API clients
- Load balancer
- API gateway
- Rate limit service
- Redis: counters
- Under the limit?
- Orders API
- 429 + Retry-After
- Metrics
- key + route
- atomic Lua script
- yes
- no
- allowed vs rejected
Como ler:
- Da esquerda para a direita é o caminho da requisição: os clientes, o balanceador de carga, o API gateway e o serviço de limites.
- O serviço de limites é dono das regras e toma uma decisão por requisição. Ele guarda os contadores no Redis e os atualiza com um script Lua atômico.
- O nó de decisão manda as requisições permitidas para a Orders API. O resto recebe um 429 com Retry-After e nunca chega ao backend.
- As métricas são tracejadas porque são reportadas de forma assíncrona. Rejeições por regra são a primeira coisa a olhar quando um cliente diz que está bloqueado.
- O bloco Markdown lista as regras na ordem em que são checadas, incluindo o modo de falha.
O que colocar no diagrama
- As regras e suas chaves. “100/min” não significa nada sem “por chave de API” ou “por IP”.
- Contagem local ou global. A diferença decide se os limites valem entre instâncias.
- O modo de falha. Fail open ou fail closed deve estar no diagrama, não só no código.
- O caminho de rejeição. Mostre onde o 429 é gerado, para ninguém supor que o backend vê essas requisições.
Para o gateway em si, o diagrama de arquitetura de API gateway cobre roteamento, autenticação e os outros filtros que rodam ao lado do limitador. O exemplo de diagrama de system design mostra como um limitador se encaixa em um design maior.
Monte no Archboard
- Entre com Google ou GitHub e crie um diagrama.
- Do pack Modelo C4, adicione uma Pessoa para os clientes e, do pack Nuvem, um Balanceador de carga na nuvem e um Gateway de API.
- Adicione dois nós Contêiner do pack Modelo C4: o serviço de limites e a Orders API. Adicione Redis do pack Dados abaixo do serviço de limites.
- Adicione uma Decisão e um Início / Fim do pack Fluxograma para o ramo de permitir ou rejeitar, e Métricas do pack DevOps.
- Rotule as arestas, deixe tracejada a aresta das métricas e adicione um bloco Markdown do pack Básico com as regras.
O exemplo usa 10 nós de diagrama e fica dentro do plano Free.
Perguntas frequentes
Qual algoritmo de rate limiting devo usar? Token bucket para a maioria das APIs, porque ele permite as rajadas curtas que clientes reais produzem. Use um contador de janela deslizante se precisar de um limite rígido por janela sem o custo de um log completo de requisições.
O rate limiter deve ser um serviço separado? Não necessariamente. Um filtro no gateway com um Redis compartilhado basta para muitos sistemas. Um serviço separado ajuda quando muitos gateways e serviços compartilham as mesmas regras.
Que código de status uma requisição limitada deve receber? 429 Too Many Requests, com um cabeçalho Retry-After. Não use 503; ele diz aos clientes que o seu serviço caiu, e não que eles pediram demais.
Como limitar por usuário quando as requisições passam por muitos servidores? Guarde os contadores em um armazenamento compartilhado como o Redis, com o usuário ou a chave de API como chave, e atualize-os de forma atômica. Contadores locais por servidor só funcionam como teto aproximado.
O que acontece se o próprio rate limiter falhar? Escolha com antecedência. Fail open para limites de produto normais, fail closed para operações caras ou sujeitas a abuso, e um alerta nos dois casos.