Marcio Cunha

Rate Limiting Distribuído com Redis e Scripts Lua

Proteja suas APIs contra sobrecarga utilizando a atomicidade dos scripts Lua no Redis. Entenda como implementar um controle de tráfego resiliente em arquiteturas distribuídas.

Marcio Cunha•3 min
Também disponível em:EnglishEspañol
Resumo
  • A atomicidade do Redis garante que a contagem de requisições seja precisa mesmo em ambientes com múltiplos servidores.
  • Scripts Lua eliminam condições de corrida ao executar toda a lógica de verificação e incremento em uma única operação indivisível.
  • O uso da estratégia Token Bucket permite suavizar picos de tráfego mantendo uma taxa média constante de processamento.
  • A centralização do controle de limite em memória reduz drasticamente a latência em comparação a verificações persistentes em bancos de dados tradicionais.
  • A resiliência do sistema depende do tratamento correto de erros de conexão e de estratégias de fail-open ou fail-close adequadas para cada negócio.

O desafio da sobrecarga em sistemas distribuídos

Manter uma API saudável exige controle rigoroso sobre a quantidade de requisições que cada cliente pode realizar. Quando um sistema é distribuído entre diversos servidores, o desafio cresce: como garantir que um limite global seja respeitado sem criar gargalos de sincronização? O Rate Limiting (ou limitação de taxa) atua como um porteiro inteligente, descartando excessos antes que eles sobrecarreguem o banco de dados ou os serviços de backend.

A atomicidade como requisito de integridade

O maior inimigo do controle de tráfego é a condição de corrida, que ocorre quando duas requisições simultâneas tentam ler e atualizar um contador ao mesmo tempo. Se o processo não for atômico (ou seja, se não acontecer em um único passo indissolúvel), o contador pode ser corrompido, permitindo que usuários mal-intencionados ou bots burlem as restrições. O Redis, por ser single-threaded em seu núcleo, oferece a base ideal para essa atomicidade.

Scripts Lua: o motor de execução

Ao utilizar scripts Lua no Redis, enviamos toda a lógica de decisão para dentro do próprio servidor de cache. Isso significa que o servidor de aplicação não precisa fazer várias viagens de ida e volta (round-trips) para verificar o estado e atualizar o contador. O script é executado como uma operação bloqueante única dentro do Redis, eliminando qualquer interferência externa.

Implementação do algoritmo Token Bucket

O algoritmo Token Bucket funciona como um balde que é preenchido com tokens a uma taxa constante, onde cada requisição consome um token. Se o balde estiver vazio, a requisição é negada. No Redis, armazenamos o timestamp da última atualização e a quantidade atual de tokens, calculando o preenchimento proporcional ao tempo decorrido desde a última ação do usuário.

local key = KEYS[1] local limit = tonumber(ARGV[1]) local window = tonumber(ARGV[2]) local now = tonumber(ARGV[3]) local bucket = redis.call('hmget', key, 'tokens', 'last_refill') local tokens = tonumber(bucket[1]) or limit local last_refill = tonumber(bucket[2]) or now local elapsed = math.max(0, now - last_refill) local refill = math.floor(elapsed * (limit / window)) tokens = math.min(limit, tokens + refill) if tokens > 0 then redis.call('hmset', key, 'tokens', tokens - 1, 'last_refill', now) return 1 else return 0 end

Considerações de arquitetura e resiliência

Um sistema robusto não deve falhar completamente se o Redis estiver indisponível. Implementar um padrão de 'fail-open' garante que, em caso de erro no cache, as requisições sejam processadas normalmente, evitando uma interrupção total do serviço. Monitorar o uso de memória do Redis é igualmente crítico, visto que chaves de controle com tempo de expiração muito longo podem levar ao esgotamento de RAM. Para complementar essa implementação em produção, vale conferir também nossas análises técnicas sobre Rate Limiting Distribuído em Serverless: Redis Cluster e Scripts Lua e Rate Limiting Distribuído com Redis e Lua Scripts: Controle de Taxa Atômico.

Conclusão

A combinação de Redis com Lua transforma uma tarefa complexa de sincronização distribuída em uma operação de alta performance. Ao mover a lógica de autorização para a camada de cache, ganhamos latência mínima e precisão matemática. A escolha entre estratégias de limite depende da sensibilidade do seu sistema a picos versus uma média constante, mas a estrutura aqui apresentada serve como alicerce para escalar com segurança.