API travando? Como aplicar rate limiting com Redis
Descubra como proteger sua API contra picos de tráfego e ataques de negação de serviço implementando o algoritmo Token Bucket no Redis com código prático.
Resumo
- O algoritmo Token Bucket gerencia cotas de requisições repondo fichas em um balde virtual de forma contínua.
- O Redis garante alta velocidade e atomicidade para validar limites de acesso distribuídos em milissegundos.
- Scripts em Lua executados no servidor Redis evitam condições de corrida em ambientes com múltiplos nós concorrentes.
- Respostas HTTP 429 com cabeçalhos informativos orientam clientes legítimos sobre quando tentar novamente.
- Estratégias adicionais de resiliência, como o uso de padrões de arquitetura paralelos, ajudam a blindar sistemas sob carga.
O perigo invisível do tráfego descontrolado em APIs
Quando uma aplicação web cresce, um dos primeiros problemas operacionais que aparecem é a degradação de performance causada por excesso de requisições. Seja por um cliente mal-intencionado tentando derrubar o sistema com um ataque de negação de serviço, ou por um script mal escrito de um parceiro consumindo recursos em looping, o resultado costuma ser o mesmo: o servidor trava, o banco de dados esgota as conexões e usuários legítimos ficam sem atendimento.
Na prática, isso significa que deixar endpoints críticos totalmente abertos é um convite para falhas catastróficas de infraestrutura. Para blindar o ecossistema, os engenheiros recorrem ao conceito de rate limiting, que consiste em impor limites rígidos de velocidade e volume de chamadas que um determinado usuário, IP ou chave de API pode fazer em um intervalo de tempo.
Por que o Token Bucket é o algoritmo ideal
Existem várias formas de controlar o fluxo de dados, como a contagem simples em janelas fixas de tempo. No entanto, esse modelo simplista sofre com o efeito de borda, onde um usuário pode estourar o limite no último segundo de um minuto e repetir a dose logo no primeiro segundo do minuto seguinte, gerando um pico duplo que sobrecarrega o servidor da mesma forma.
A solução mais elegante e eficiente para esse dilema é o algoritmo Token Bucket, ou balde de fichas. Imagine um balde que armazena fichas até um limite máximo e que se enche novamente a uma taxa constante por segundo. Cada requisição consumida retira uma ficha do balde; se o balde estiver vazio, a nova requisição é sumariamente recusada, garantindo flexibilidade para absorver pequenos picos legítimos sem sufocar o sistema.
A escolha do Redis para alta performance
Implementar esse tipo de controle diretamente na memória RAM da aplicação pode parecer tentador, mas falha miseravelmente em arquiteturas modernas baseadas em múltiplos servidores em nuvem. Se a sua API roda em três contêineres diferentes atrás de um balanceador de carga, manter o controle de requisições isolado em cada máquina significa que o limite real triplicaria.
A resposta natural para esse desafio de sincronização é utilizar um banco de dados em memória extremamente rápido, como o Redis. Como ele centraliza o armazenamento dos dados de acesso, todas as instâncias da sua API consultam e atualizam o mesmo balde de fichas em tempo real, mantendo a regra de negócio rigorosamente coerente independentemente de onde a requisição pousar.
Implementando o Token Bucket com Lua e Redis
Para garantir que duas requisições simultâneas não leiam e escrevam o mesmo balde ao mesmo tempo gerando inconsistências, a melhor prática no Redis é utilizar scripts na linguagem Lua. O servidor Redis executa esses scripts de forma totalmente atômica, ou seja, nenhuma outra operação interfere no meio do processo de leitura e atualização do estado.
O código abaixo demonstra um script robusto que calcula a passagem do tempo, repõe as fichas proporcionalmente e desconta a unidade da nova requisição em uma única transação rápida:
local key = KEYS[1]local max_tokens = tonumber(ARGV[1])local refill_rate = tonumber(ARGV[2])local now = tonumber(ARGV[3])local requested = tonumber(ARGV[4])local bucket = redis.call('HMGET', key, 'tokens', 'last_updated')local tokens = tonumber(bucket[1])local last_updated = tonumber(bucket[2])if not tokens then tokens = max_tokenstoken_last_updated = nowelse local delta = math.max(0, now - last_updated) tokens = math.min(max_tokens, tokens + (delta * refill_rate))endlocal allowed = 0if tokens >= requested then tokens = tokens - requested allowed = 1endredis.call('HMSET', key, 'tokens', tokens, 'last_updated', now)redis.call('EXPIRE', key, math.ceil(max_tokens / refill_rate))return {allowed, tokens}Esse script elimina gargalos de concorrência e assegura que a contagem seja matematicamente precisa mesmo sob milhares de acessos simultâneos por segundo.
Integrando a verificação na sua API
Com o script rodando no servidor Redis, o trabalho do microsserviço ou API Gateway resume-se a enviar os parâmetros corretos e interpretar a resposta obtida. Caso o retorno indique que a requisição não foi permitida, a aplicação deve interromper o fluxo imediatamente e devolver um código de status HTTP adequado, tradicionalmente o 429 Too Many Requests.
Além de retornar o código de erro, é uma excelente gentileza operacional incluir cabeçalhos informativos na resposta, como o 'Retry-After', informando exatamente quantos segundos o cliente deve aguardar antes de tentar uma nova conexão. Essa postura transparente evita loops infinitos de tentativas por parte de aplicações consumidoras mal configuradas. Para complementar essa implementação em produção, vale conferir também nossas análises técnicas sobre IA gerando código inseguro? Como usar AST para varredura e Site lento no React? Como aplicar code splitting eficiente.
Considerações Finais
Proteger aplicações modernas contra sobrecargas repentinas deixou de ser um diferencial e virou um requisito básico de engenharia de software. Como vimos, combinar o modelo dinâmico do Token Bucket com a velocidade atômica do Redis resolve o problema de escalabilidade e distribuição de forma elegante.
Adotar essas práticas mitiga ataques indesejados, melhora a estabilidade geral da infraestrutura e garante que sistemas sob carga alta, como detalhamos em nosso artigo sobre sistemas sob carga alta e o uso do padrão Sidecar, continuem entregando respostas rápidas e confiáveis para os usuários legítimos.