Rate limiting é a técnica que controla quantas requisições um cliente pode fazer a um serviço em um intervalo de tempo. Aplicar limites de forma inteligente evita sobrecarga, abuso e custos inesperados, mantendo latência e disponibilidade sob controle.
Este artigo descreve as estratégias mais usadas, vantagens e desvantagens de cada algoritmo, decisões de projeto para escala e exemplos práticos de implementação, incluindo dicas de instrumentação e operação para manter o controle em produção.
Leia também: Diagnóstico passo a passo de memory leak em aplicações Node.js. Leia também: Como reduzir custos com infraestrutura serverless sem perder desempenho.
Por que implementar rate limiting
Sem rate limiting, um pico súbito de tráfego ou um cliente malicioso pode degradar a experiência de todos. Limites protegem recursos críticos: CPUs, conexões de banco de dados, filas e serviços externos. Em arquiteturas serverless, por exemplo, controles ajudam a evitar explosão de custos por execução em massa.
Além disso, rate limiting é uma camada de segurança que reduz impacto de scraping, brute force em endpoints de autenticação e ataques de negação de serviço em nível de aplicação. Quando integrado a observability, permite correlacionar limites acionados com métricas de uso e incidentes, ajudando a ajustar políticas com dados reais.
Principais algoritmos de rate limiting
Existem diversos algoritmos com trade-offs claros entre simplicidade, precisão e custo computacional. Os mais populares são: fixed window, sliding window, token bucket e leaky bucket. Escolher o algoritmo certo depende do padrão de tráfego, tolerância a rajadas (bursts) e requisitos de precisão.
Fixed window
No algoritmo fixed window, as requisições são contadas dentro de janelas de tempo fixas, por exemplo um minuto. É simples e barato: basta incrementar uma chave no armazenamento por cliente e resetar quando a janela expira.
Vantagens: implementação direta e baixo custo. Desvantagens: comportamento em borda de janela pode permitir burst maior ao final de uma janela e início da próxima, causando picos indesejados.
Sliding window
Sliding window resolve o problema do fixed window ao calcular o uso em uma janela deslizante contínua. Implementações clássicas usam timestamps ou buckets menores que compõem a janela inteira.
Vantagens: precisão melhor para limitar rajadas. Desvantagens: mais complexo e potencialmente mais custoso em termos de armazenamento e operações, especialmente com grande número de clientes.
Token bucket
Token bucket permite acumular créditos até um limite máximo e consome tokens conforme chegam requisições. Essa abordagem oferece controle refinado sobre taxa média e tolerância a rajadas, ideal quando se quer permitir picos curtos mas limitar a taxa média.
Vantagens: flexibilidade entre throughput e burst; fácil integração com limites por segundo/minuto. Desvantagens: requer lógica para gerar tokens com precisão temporal e armazenar estado por cliente.
Leaky bucket
Leaky bucket trata requisições como água em um balde com vazamento constante: as entradas podem checar se o balde transborda. O algoritmo regula a saída a uma taxa constante, evitando rajadas ao custo de potencial aumento de latência para requisições que aguardam.
Vantagens: suaviza o tráfego para taxa constante. Desvantagens: pode introduzir fila e latência, não é ideal quando picos curtos devem ser atendidos rapidamente.
Escolhendo o algoritmo certo
A escolha deve partir de três perguntas práticas: qual padrão de tráfego esperado, qual tolerância a bursts e qual custo de armazenamento/latência podemos aceitar. Para APIs públicas com muitos clientes desconhecidos, soluções conservadoras com token bucket costumam equilibrar proteção e usabilidade.
Se seu serviço lida com poucas integrações conhecidas e tráfego previsível, fixed window pode ser suficiente e econômico. Para sistemas que exigem fairness mais precisa entre clientes concorrentes, sliding window apresenta melhores garantias, embora custe mais.
Armazenamento e consistência: local vs centralizado
Rate limiting precisa de um lugar para manter contadores ou estados por cliente. Opções comuns são memória local (in-memory), Redis, bases de dados ou soluções de edge (CDNs, WAFs). Cada escolha tem implicações em consistência e escalabilidade.
Memória local é extremamente rápida e barata, mas não é consistente em cluster: cada instância terá seu próprio contador, o que pode permitir que um cliente contorne os limites ao distribuir requisições por várias instâncias. Redis é a escolha mais comum quando se precisa de consistência e desempenho: oferece operações atômicas, TTLs e scripts Lua que tornam a lógica de rate limiting eficiente e precisa.
Ao usar Redis, prefira operações atômicas ou scripts para evitar condições de corrida. Evite implementar rate limiting com consultas SQL frequentes quando o objetivo for latência baixa, a menos que a escala seja pequena e o banco tenha índices otimizados para esse padrão.
Implementação prática com Redis
Redis combina baixa latência com suporte a comandos atômicos, permitindo implementar fixed window, sliding window com sorted sets e token bucket com decrementos atômicos. Abaixo seguem padrões práticos e recomendações operacionais que funcionam em produção.
Fixed window: use INCR com EXPIRE para criar uma chave por cliente por janela. Deve-se ter cautela no reset de chaves e no manuseio de concorrência em clusters.
Sliding window: mantenha um sorted set por cliente com timestamps das requisições e remova entradas antigas antes de contar. Esse método é preciso, mas exige mais memória e I/O no Redis, portanto só recomendado quando a precisão justifica o custo.
Token bucket: armazene tokens em uma chave e um timestamp do último refill. Um script Lua pode recalcular o número de tokens disponíveis a cada requisição, reabastecendo proporcionalmente ao tempo decorrido e consumindo um token atomically.
Exemplo de script Lua para token bucket (padrão)
Um script Lua no Redis ajuda a garantir atomicidade. A lógica geral é: ler tokens e último timestamp, calcular tokens a adicionar desde o último refill, limitar ao máximo, se tokens suficientes decrementar e aceitar a requisição, caso contrário rejeitar. Essa abordagem evita condições de corrida em ambientes de alta concorrência.
Na prática, mantenha o script curto e testado, versionando-o e usando mecanismo de cache de scripts do Redis (SHA1) para reduzir custo de parsing em produção.
Rate limiting distribuído e edge
Em sistemas com tráfego global e muitas réplicas, aplicar rate limiting o mais cedo possível, idealmente no edge, reduz carga em backend. CDNs e gateways de API suportam políticas básicas de rate limiting e são úteis para bloquear tráfego indesejado antes que atinja a infra.
Para casos em que a política precisa ser consistente globalmente, combine limites no edge com um controle centralizado em Redis ou outro armazenamento consistente. A estratégia híbrida aplica limites por instância no edge para filtrar picos e mantém contador global para políticas mais rígidas.
Métricas, logs e observability
Instrumentar rate limiting é crucial para entender efetividade e impacto. Registre eventos aceitos e rejeitados com motivos e códigos de resposta, exporte contadores de limites acionados por cliente e monitore latência e taxas de erro correlacionadas. Use dashboards e alerts para detectar quando políticas estão muito permissivas ou demasiado restritivas.
Integre com o sistema de observabilidade da sua aplicação para correlacionar picos de limite com erros upstream, mudanças de comportamento de clientes e deploys recentes. Consulte guias práticos de observability para estruturar métricas, logs e traces relevantes, evitando decisões baseadas apenas em anedotas.
Para começar, adicione contadores: total de requisições por cliente, total de requisições bloqueadas, média de rejeições por minuto, e latência média ao decidir limites. Esses dados orientam ajustes finos na política.
Boas práticas de design de políticas
Ao definir políticas pense em camadas: limites por API key, por IP, por rota crítica e por usuário autenticado. Rotas sensíveis, como login, devem ter políticas mais restritivas para prevenir brute force. Endpoints públicos de leitura podem ter limites mais generosos se a infraestrutura suportar.
Implemente resposta clara ao cliente: use cabeçalhos HTTP que informem o limite, o uso atual e o tempo para reset. Códigos 429 são padrão para indicar limite excedido. Fornecer essas informações melhora a experiência do desenvolvedor que consome sua API e reduz suporte.
Considerações de UX e comunicação
Limitar sem comunicar gera frustração. Documente políticas e ofereça planos diferenciados quando aplicável: por exemplo, limites mais altos para clientes pagos. Para APIs públicas, forneça status de uso no painel do desenvolvedor e endpoints para consultar saldo de requisições em tempo real.
Quando uma requisição é rejeitada, inclua no corpo da resposta uma mensagem objetiva e instruções sobre retry-after. Para clientes que executam bibliotecas, disponibilize SDKs que tratam retries exponenciais e respeitam cabeçalhos de rate limiting.
Testes e validação
Testes são essenciais: simule ataques e picos reais usando ferramentas de carga para verificar que limites protegem sem causar regressões indesejadas. Teste cenários de cluster para garantir que a implementação distribuída mantém consistência e não permite contornos de limites.
Valide também políticas de retry e backoff: estratégias ingênuas de retry podem amplificar um evento de tráfego e prejudicar a recuperação. Ensaios com chaos testing ajudam a entender comportamento sob falhas parciais, por exemplo perda de conexão com Redis.
Operação e manutenção
Em produção monitore uso de memória do Redis, latência de comandos e taxa de chaves expiradas. Ajustes de TTL, compactação de dados e limpeza de estruturas obsoletas evitam acúmulo desnecessário. Se notar memory leak na aplicação por contadores locais, siga diagnósticos passo a passo para localizar vazamentos e corrigi-los.
Automatize deploy de scripts Lua e políticas para evitar divergência entre ambientes. Tenha planos de fallback: por exemplo, se o Redis falhar, degrade para limites mais agressivos por instância até a recuperação, evitando sobrecarga total do backend.
Para entender melhor métricas e traços relacionados, consulte materiais sobre observability que ajudam a correlacionar eventos e priorizar intervenções.
Exemplos de implementação em linguagens comuns
Em Node.js, bibliotecas como express-rate-limit oferecem um ponto de partida, mas para produção em cluster é recomendável integrar com Redis e scripts atômicos. Em Go e Java, muitas aplicações preferem bibliotecas leves que implementam token bucket em memória quando o número de clientes é pequeno.
Independente da linguagem, padronize a interface de consulta ao mecanismo de rate limiting: uma função que retorna se a requisição deve ser permitida, o uso atual e o tempo para reset facilita testes e substituição da implementação sem impactar camada de roteamento.
Casos de uso e exemplos práticos
API pública de leitura: token bucket com alta taxa média e burst moderado. Endpoints de autenticação: fixed window curta com limites rigorosos por IP e por usuário. Serviços internos entre microserviços: limites por instância para evitar chamadas em cascata durante falhas.
Para ofertas freemium, limite por chave com política gradual, e para clientes pagos, permita maiores limites ou acordos de SLA documentados. Em aplicações serverless, combine limites no gateway com métricas para evitar surpresas na fatura.
Erros comuns e como evitá-los
Implementar apenas em memória local sem considerar escala é erro comum. Outra falha é não instrumentar: sem métricas fica impossível ajustar políticas de forma segura. Evite respostas vagas ao cliente; use cabeçalhos claros e retry-after para orientar o comportamento.
Também observe comportamento de retries de bibliotecas de clientes, que podem amplificar picos. Padronize backoff exponencial e use jitter para diminuir sincronização de retries entre muitos clientes.
Tendências e evolução
Com a adoção de arquiteturas distribuídas e edge computing, políticas híbridas têm se destacado: bloqueio inicial no edge e verificação final em armazenamento central. Além disso, plataformas de API management incorporam políticas dinâmicas que ajustam limites conforme métricas em tempo real, abrindo espaço para rate limiting adaptativo.
Outra tendência é a integração mais estreita entre rate limiting e mecanismos de autorização e quota por usuário, permitindo políticas por feature e por plano comercial de forma dinâmica.
Conclusão
Rate limiting é uma peça essencial para proteger disponibilidade, controlar custos e fornecer experiência previsível. Não existe solução única: o ideal é combinar algoritmos adequados, armazenamento consistente e observability para tomar decisões orientadas por dados.
Comece simples, monitore e evolua: implemente limites básicos, adicione métricas e refine políticas conforme o comportamento real dos usuários. Se precisar de referência prática sobre memória e leaks em produção ou monitoring para ajustar limites, leia guias como o diagnóstico passo a passo de memory leak em aplicações Node.js e o guia prático de observability para métricas, logs e traces.
Quer compartilhar como você implementou rate limiting no seu projeto ou tem uma dúvida específica? Deixe um comentário ou confira outros artigos relacionados no site.









