Ramp llama a docenas de API de terceros (Finicity, Teller, Zendesk, Salesforce) que imponen cada una sus propias cuotas, ejecuta tareas periódicas de Celery para toda su base de usuarios y quería limitar la tasa del tráfico entrante por usuario autenticado en lugar de por IP. Las tres necesidades apuntaban a la misma pieza faltante: un limitador de tasa que fuera preciso en todos los trabajadores y servidores, no solo dentro de uno.
El enfoque existente de Ramp se basaba en el limitador de tasa integrado de Celery por tarea. Ese límite se aplicaba por trabajador, no de forma global, por lo que imponer una tasa verdaderamente global requería que un ingeniero conociera el número de trabajadores de la cola y su comportamiento de autoescalado y dividiera manualmente el límite deseado entre los trabajadores -- un número que quedaba obsoleto en el momento en que cambiaba el número de trabajadores. Bajo ciertas configuraciones de prefetch de Celery, las tareas con límite de tasa podían incluso impedir que un trabajador tomara nuevo trabajo, bloqueando colas enteras.
Ramp evaluó los algoritmos de ventana fija, ventana deslizante y cubo con fugas antes de decidirse por el Algoritmo Genérico de Tasa de Célula (GCRA), una variante del cubo con fugas que solo necesita almacenar una única marca de tiempo calculada por clave en lugar de un registro completo de solicitudes. Lo implementaron sobre Redis, añadiendo un bloqueo distribuido para cerrar una brecha de condición de carrera en las implementaciones de referencia y usando la hora del servidor de Redis en lugar del reloj de cada servidor de aplicaciones para evitar la deriva entre máquinas.
El resultado fue un único marco de limitación de tasa que Ramp podía apuntar a los tres casos de uso -- llamadas a API de terceros, limitación global de tareas de Celery y límites de API entrantes por usuario -- sin que un ingeniero necesitara razonar sobre el número de trabajadores en absoluto.
Un limitador de tasa GCRA global respaldado por Redis
Almacenar un único valor TAT por clave es mucho más barato que el registro completo de solicitudes de una ventana deslizante, por lo que el limitador se mantiene rápido incluso con el volumen de solicitudes de Ramp.
Leer la hora actual del propio Redis, en lugar de desde el servidor de aplicaciones que atienda la solicitud, elimina la deriva de reloj como fuente de aplicación inconsistente.
Un bloqueo de Redis alrededor de la comprobación y el establecimiento cierra la condición de carrera en la que dos solicitudes concurrentes podían leer ambas un TAT obsoleto y ambas ser aprobadas.
Construir un marco general en lugar de un limitador puntual significó que el mismo código pudiera servir para cuotas de terceros, limitación de tareas de Celery y límites de API por usuario sin lógica duplicada.
Un límite de tasa aplicado solo por proceso aproxima únicamente un límite global; centralizar el contador en un almacén compartido y bloqueable es lo que hace que el número signifique realmente lo que dice.
Ramp desplegó el limitador GCRA sobre Redis en los tres casos de uso descritos en la publicación -- cuotas globales de API de terceros, limitación de tareas de Celery y limitación de API entrante por usuario -- con pruebas unitarias y verificación manual, y dijo que planeaba extender el mismo marco a nuevos algoritmos de limitación de tasa a medida que surgieran más casos de uso.
CONSULTA LAS PRUEBAS
Las fuentes
- Rate limiting with Redis Ramp Engineering (Builders Blog) · builders.ramp.com ·