A Ramp chama dezenas de APIs de terceiros (Finicity, Teller, Zendesk, Salesforce) que impõem cada uma suas próprias cotas, executa tarefas periódicas do Celery em toda a sua base de usuários e queria limitar a taxa do tráfego de entrada por usuário autenticado, em vez de por IP. Todas as três necessidades apontavam para a mesma peça ausente: um limitador de taxa que fosse preciso em todos os workers e servidores, não apenas dentro de um.
A abordagem existente da Ramp dependia do limite de taxa integrado do Celery por tarefa. Esse limite era aplicado por worker, não globalmente, de modo que impor uma taxa verdadeiramente sistêmica exigia que um engenheiro soubesse a contagem de workers da fila e o comportamento de autoscaling e dividisse manualmente o limite desejado entre os workers -- um número que ficava desatualizado no momento em que a contagem de workers mudava. Sob certas configurações de prefetch do Celery, tarefas com limite de taxa podiam até impedir um worker de pegar novo trabalho, travando filas inteiras.
A Ramp avaliou os algoritmos de janela fixa, janela deslizante e leaky bucket antes de optar pelo Generic Cell Rate Algorithm (GCRA), uma variante de leaky bucket que precisa armazenar apenas um único timestamp calculado por chave, em vez de um log completo de requisições. Eles o implementaram sobre o Redis, adicionando um lock distribuído para fechar uma lacuna de condição de corrida nas implementações de referência e usando o horário do servidor do Redis em vez do relógio de cada servidor de aplicação para evitar desvio entre máquinas.
O resultado foi um único framework de limitação de taxa que a Ramp podia apontar para todos os três casos de uso -- chamadas a APIs de terceiros, limitação global de tarefas do Celery e limites de entrada por usuário na API -- sem que um engenheiro precisasse raciocinar sobre contagens de workers.
Um limitador de taxa GCRA global apoiado pelo Redis
Armazenar um único valor de TAT por chave é muito mais barato do que o log completo de requisições de uma janela deslizante, então o limitador se mantém rápido mesmo no volume de requisições da Ramp.
Ler o horário atual do próprio Redis, em vez de do servidor de aplicação que atende a requisição, elimina o desvio de relógio como fonte de aplicação inconsistente.
Um lock do Redis em torno do check-and-set fecha a condição de corrida em que duas requisições concorrentes podiam ambas ler um TAT desatualizado e ambas serem aprovadas.
Construir um framework geral em vez de um limitador pontual significou que o mesmo código podia atender cotas de terceiros, limitação de tarefas do Celery e limites de API por usuário sem lógica duplicada.
Um limite de taxa aplicado apenas por processo é somente uma aproximação de um limite global; centralizar o contador em um armazenamento compartilhado e bloqueável é o que faz o número realmente significar o que diz.
A Ramp implantou o limitador GCRA no Redis nos três casos de uso descritos no post -- cotas globais de APIs de terceiros, limitação de tarefas do Celery e limitação de entrada por usuário na API -- com testes unitários e verificação manual, e disse que planejava estender o mesmo framework a novos algoritmos de limitação de taxa à medida que surgissem mais casos de uso.
CONFIRA AS EVIDÊNCIAS
As fontes
- Rate limiting with Redis Ramp Engineering (Builders Blog) · builders.ramp.com ·