Ramp appelle des dizaines d'API tierces (Finicity, Teller, Zendesk, Salesforce) qui imposent chacune leurs propres quotas, exécute des tâches Celery périodiques sur l'ensemble de sa base d'utilisateurs, et souhaitait limiter le débit du trafic entrant par utilisateur authentifié plutôt que par IP. Ces trois besoins pointaient vers la même pièce manquante : un limiteur de débit précis sur chaque worker et chaque serveur, et pas seulement au sein d'un seul.
L'approche existante de Ramp reposait sur la limitation de débit intégrée de Celery, par tâche. Cette limite s'appliquait par worker, et non globalement ; pour appliquer une véritable limite à l'échelle du système, un ingénieur devait donc connaître le nombre de workers de la file et son comportement d'autoscaling, puis diviser manuellement la limite souhaitée entre les workers — un chiffre qui devenait obsolète dès que le nombre de workers changeait. Dans certaines configurations de prefetch de Celery, les tâches soumises à limitation de débit pouvaient même empêcher un worker de prendre de nouvelles tâches, bloquant des files entières.
Ramp a évalué les algorithmes à fenêtre fixe, à fenêtre glissante et à seau percé avant de retenir le Generic Cell Rate Algorithm (GCRA), une variante du seau percé qui ne stocke qu'un seul horodatage calculé par clé au lieu d'un journal complet des requêtes. Ils l'ont implémenté sur Redis, en ajoutant un verrou distribué pour combler une faille de condition de course présente dans les implémentations de référence, et en utilisant l'heure serveur de Redis plutôt que l'horloge de chaque serveur applicatif afin d'éviter les dérives entre machines.
Le résultat fut un cadre unique de limitation de débit que Ramp pouvait appliquer à ses trois cas d'usage — appels aux API tierces, régulation globale des tâches Celery et limites d'API entrantes par utilisateur — sans qu'un ingénieur ait besoin de raisonner en nombre de workers.
Un limiteur de débit GCRA global adossé à Redis
Stocker une seule valeur TAT par clé coûte bien moins cher que le journal complet des requêtes d'une fenêtre glissante, si bien que le limiteur reste rapide même au volume de requêtes de Ramp.
Lire l'heure courante directement depuis Redis, plutôt que depuis le serveur applicatif qui traite la requête, élimine la dérive d'horloge comme source d'application incohérente.
Un verrou Redis autour de la vérification et de l'écriture comble la condition de course dans laquelle deux requêtes concurrentes pourraient toutes deux lire une valeur TAT obsolète et être toutes deux approuvées.
Construire un cadre général plutôt qu'un limiteur ponctuel a permis au même code de servir les quotas d'API tierces, la régulation des tâches Celery et les limites d'API par utilisateur sans logique dupliquée.
Une limite de débit appliquée par processus ne fait qu'approximer une limite globale ; c'est en centralisant le compteur dans un stockage partagé et verrouillable que le chiffre signifie réellement ce qu'il dit.
Suites
Ramp a déployé le limiteur GCRA sur Redis pour les trois cas d'usage décrits dans l'article — quotas globaux d'API tierces, régulation des tâches Celery et limitation des API entrantes par utilisateur — avec des tests unitaires et une vérification manuelle, et a indiqué prévoir d'étendre le même cadre à de nouveaux algorithmes de limitation de débit à mesure que d'autres cas d'usage se présenteraient.
VÉRIFIEZ LES SOURCES
Les sources
- Rate limiting with Redis Ramp Engineering (Builders Blog) · builders.ramp.com ·