Limitation du débit de l’API expliquée : Algorithmes, en-têtes et gestion des erreurs 429
Les limites de débit protègent les API contre la surcharge et les abus. Découvrez comment les principaux algorithmes se comportent, à quoi ressemble une bonne réponse 429 et comment les clients doivent reculer, à l’aide d’une simulation que nous avons exécutée.
- La Limitation du débit limite le nombre de requêtes qu’un client peut effectuer sur une période donnée.
- Le token bucket permet de courtes rafales tout en appliquant un débit moyen.
- Une simple fenêtre fixe peut laisser passer le double de la limite au niveau d’une limite de fenêtre.
- Renvoyez 429 Too Many Requests avec un en-tête Retry-After et demandez aux clients de reculer avec instabilité.
Un script bogué appelle votre API en boucle serrée. Sans limites, cela peut ralentir le service pour tous les autres utilisateurs ou générer une facture cloud importante. La limitation du débit API est le garde-fou qui empêche un seul client de prendre plus que sa juste part.
Pour montrer comment les algorithmes courants se comportent, nous avons écrit de petites versions de Python et les avons alimentées en simulant des timings de requêtes. Les résultats ci-dessous proviennent de cette exécution. Ce sont des modèles d’enseignement, pas du code de production, et les vraies passerelles ont plus de fonctionnalités.
Qu’est-ce que la limitation de taux ?
La limitation du débit compte les demandes par client et rejette ou retarde les demandes au-delà d’une limite convenue, par exemple 100 demandes par minute. Une idée connexe, la limitation, consiste souvent à ralentir les requêtes plutôt qu’à les rejeter. Un quota correspond généralement à une allocation plus importante sur une période plus longue, par exemple 10 000 appels par mois avec un plan tarifaire.
Pourquoi les API en ont besoin
- Stabilité : les pics de trafic ne peuvent pas submerger les bases de données ou les services en aval.
- Équité : un gros client ne peut pas affamer les autres.
- Sécurité : les limites ralentissent les tentatives de connexion par force brute et le scraping.
- Contrôle des coûts : les opérations coûteuses, telles que les appels de modèles d’IA, respectent le budget.
La limitation de débit est un contrôle essentiel dans le Top 10 de sécurité des API OWASP, dans le cadre d’une consommation de ressources sans restriction. Notre guide Test d’API avec Postman montre comment envoyer les requêtes répétées nécessaires pour le vérifier.
Seau de jetons
Imaginez un seau pouvant contenir jusqu’à 5 jetons. Chaque requête prend un jeton. Les jetons se rechargent à raison de 1 par seconde et le bucket n’en contient jamais plus de 5. Si le bucket est vide, la demande est rejetée.
class TokenBucket:
def __init__(self, capacity, refill_per_sec):
self.capacity = capacity
self.rate = refill_per_sec
self.tokens = capacity
self.last = 0.0
def allow(self, now):
elapsed = now - self.last
self.tokens = min(self.capacity, self.tokens + elapsed * self.rate)
self.last = now
if self.tokens >= 1:
self.tokens -= 1
return True
return False
Nous avons envoyé 7 demandes d’un coup, puis plus par la suite :
burst of 7 at t=0: [True, True, True, True, True, False, False]
t=2.0: [True, True, False]
t=10: 5 allowed of 10
Les 5 premiers sont passés en rafale, puis le seau était vide. Deux secondes plus tard, 2 jetons s’étaient rechargés, donc 2 autres requêtes ont été transmises. Après une longue pause, le compartiment était à nouveau plein mais plafonné à 5. Ce comportement en rafale puis stable convient au trafic réel, c’est pourquoi les compartiments de jetons sont courants dans les passerelles API. Le leaky bucket est un proche parent qui traite les requêtes à un rythme fixe, lissant la sortie au lieu d’autoriser les rafales.
Fenêtre fixe vs fenêtre coulissante
Un compteur de fenêtre fixe est l’approche la plus simple : autorisez 10 requêtes par minute et réinitialisez le compteur lorsque la minute change. Il présente un défaut connu à la frontière. Nous avons envoyé 10 requêtes à 59,9 secondes et 10 autres à 60,0 secondes, avec une limite de 10 par fenêtre de 60 secondes :
fixed window allowed around boundary: 20 of 20
sliding log allowed around boundary: 10 of 20
La fenêtre fixe permettait de traiter les 20 requêtes en un dixième de seconde, soit le double du débit prévu, car elles tombaient dans deux fenêtres différentes. Un journal glissant mémorise l’heure de chaque requête récente et compte celles des 60 dernières secondes. Il n’en autorise donc correctement que 10.
Le compromis est la mémoire. Un journal glissant stocke un horodatage par demande. Le comptoir à fenêtre coulissante est un compromis populaire. Il estime le taux en mélangeant le nombre de fenêtres actuelles et précédentes, en utilisant peu de mémoire avec une bonne précision.
Comparaison d’algorithmes
| Algorithme | Éclats | Précision | Mémoire |
|---|---|---|---|
| Fenêtre fixe | Jusqu’à 2x aux limites | Faible | Très faible |
| Journal glissant | Non | Exact | Élevé |
| Comptoir à fenêtre coulissante | Petit | Bon | Faible |
| Seau de jetons | Autorisé jusqu’à capacité | Bon | Faible |
| Seau qui fuit | Lissé | Bon | Faible |
Par quoi limiter
- Clé API ou ID utilisateur : l’option la plus équitable pour les API authentifiées.
- Adresse IP : utile pour les points de terminaison publics, mais de nombreux utilisateurs peuvent partager une adresse IP et les attaquants peuvent les alterner.
- Endpoint : les opérations coûteuses, telles que la recherche, les exportations ou la connexion, méritent des limites plus strictes.
- Niveau de forfait : les clients gratuits et payants bénéficient souvent de limites différentes.
Les points de terminaison de connexion nécessitent une attention particulière. Limitez à la fois par compte et par IP, afin que les attaquants ne puissent pas simplement diffuser des suppositions sur plusieurs adresses IP ou plusieurs comptes.
La réponse 429
Lorsqu’un client dépasse la limite, renvoyez HTTP 429 Too Many Requests, et non 500 ou 403. Incluez des en-têtes qui indiquent au client ce qui s’est passé et quand réessayer. Notre simulation a produit cet ensemble pour un client sans aucune demande :
HTTP/1.1 429 Too Many Requests
RateLimit-Limit: 100
RateLimit-Remaining: 0
RateLimit-Reset: 30
Retry-After: 30
Retry-After est un en-tête HTTP standard. Les en-têtes RateLimit-* proviennent d’un brouillon de l’IETF et de nombreuses API utilisent leurs propres variantes X-RateLimit-*. Quoi que vous choisissiez, documentez-le. Pour en savoir plus sur les codes d’état, consultez notre aide-mémoire sur les codes d’état HTTP.
Gérer les limites en tant que client
Réessayer immédiatement après un 429 ne fait qu’empirer les choses. Si des milliers de clients le font en même temps, ils accèdent au serveur par vagues synchronisées. Le correctif est un intervalle exponentiel avec gigue : attendez plus longtemps après chaque échec et ajoutez du caractère aléatoire.
def backoff(attempt, base=0.5, cap=30, rng=random.Random(4)):
return round(min(cap, base * 2 ** attempt) * rng.random(), 2)
[backoff(a) for a in range(6)]
# [0.12, 0.1, 0.79, 0.62, 0.53, 6.43]
Il s’agit d’une « gigue totale » : une attente aléatoire entre zéro et un plafond à croissance exponentielle. Les attentes semblent irrégulières, et c’est bien là le problème. Les clients se sont dispersés au lieu de réessayer ensemble. Nous avons ensemencé le générateur aléatoire afin que la sortie puisse être reproduite.
Liste de contrôle du client : honorez Retry-After lorsqu’elle est présente, limitez le nombre de tentatives, réessayez uniquement les demandes qui peuvent être répétées en toute sécurité et surveillez l’en-tête des demandes restantes afin de pouvoir ralentir avant d’atteindre la limite.
Limitation de débit distribuée
Avec plusieurs serveurs API derrière un équilibreur de charge, chaque serveur comptant seul permet à un client de dépasser la limite en répartissant les requêtes entre eux. Les équipes conservent généralement les compteurs dans un magasin partagé tel que Redis, avec des opérations atomiques ou un court script afin que deux serveurs ne puissent pas prendre tous les deux le dernier jeton. Les passerelles telles que Kong, NGINX, Envoy et les passerelles API cloud fournissent cela comme configuration. Nous n’avons pas exécuté de configuration distribuée pour cet article.
Limites du taux de test
- Envoyez les demandes jusqu’à la limite et confirmez qu’elles réussissent.
- Envoyez-en un de plus et confirmez un 429 avec les en-têtes corrects.
- Attendez l’heure de réinitialisation et confirmez que les demandes réussissent à nouveau.
- Vérifiez les limites par clé : l’utilisation d’un client ne doit pas en bloquer un autre.
- Testez la limite entre les fenêtres pour détecter le problème de double rafale.
- Utilisez un outil de chargement, comme indiqué dans tests de charge vs tests de stress, pour vérifier le comportement en cas de forte concurrence.
Dans les tests unitaires, injectez l’heure actuelle, comme le fait notre simulation avec now, afin que les tests n’aient jamais à dormir.
Concevoir des limites avec lesquelles les utilisateurs peuvent vivre
Le choix des chiffres compte autant que le choix de l’algorithme. Partez du trafic réel : examinez le nombre de requêtes que vos clients légitimes les plus importants effectuent par minute et fixez des limites confortablement au-dessus de ce chiffre. Une limite qui bloque une utilisation normale crée des tickets d’assistance, pas de sécurité.
- Publiez les limites dans la documentation de votre API, par point de terminaison et par plan.
- Avertir avant de bloquer. Les en-têtes affichant les requêtes restantes permettent aux clients bien élevés de ralentir d’eux-mêmes.
- Proposez des alternatives aux interrogations intensives, telles que des webhooks, des points de terminaison groupés ou des pages plus volumineuses.
- Surveillez les taux 429. Une augmentation soudaine peut signifier une attaque, une version client buggée ou des limites désormais trop strictes.
- Accordez délibérément des exceptions, par exemple des limites élevées pour un partenaire, enregistrées dans la configuration plutôt que codées en dur.
Décidez également de ce qui arrive aux demandes qui arrivent juste au-delà de la limite. Certaines API mettent brièvement en file d’attente un petit nombre de requêtes supplémentaires au lieu de les rejeter, ce qui atténue les courts pics. D’autres donnent la priorité aux points de terminaison essentiels, tels que les paiements, par rapport aux points moins critiques comme l’analyse, afin que le trafic important continue de circuler lorsque le système est sous pression.
Pour conclure
Une bonne limitation de débit est un contrat entre l’API et le client. Le serveur choisit un algorithme équitable, généralement un compartiment à jetons ou une fenêtre coulissante, et explique clairement ses limites avec un 429 et un Retry-After. Le client respecte ces signaux et recule avec gigue. Commencez avec des limites généreuses par clé API, surveillez l’utilisation réelle et resserrez-les uniquement là où vous en avez besoin.
Questions fréquentes
Qu’est-ce que la limitation du débit de l’API ?
Il s’agit d’une limite au nombre de requêtes qu’un client peut adresser à une API dans un temps donné, par exemple 100 par minute. Les demandes dépassant la limite sont rejetées ou retardées.
Que signifie HTTP 429 ?
429 Trop de demandes signifie que le client a dépassé une limite de débit. La réponse inclut souvent un en-tête Retry-After indiquant combien de temps attendre.
Qu’est-ce que l’algorithme du token bucket ?
Chaque requête utilise un jeton d’un seau qui se remplit à un rythme constant jusqu’à un maximum. Il permet des rafales courtes tout en imposant un débit moyen.
Quelle est la différence entre la limitation de débit et la limitation ?
La limitation du débit rejette généralement les demandes dépassant la limite. La limitation les ralentit souvent ou les met en file d’attente. Les termes sont fréquemment utilisés de manière interchangeable.
Comment un client doit-il gérer les limites de taux ?
Respectez Retry-After, réessayez avec un espacement exponentiel et une instabilité, limitez le nombre de tentatives et ralentissez lorsque les demandes restantes sont faibles.
Continuez à apprendre
Besoin d’aide pour les tests, WordPress ou le référencement ?
Nous aidons les équipes à proposer de meilleurs logiciels et à développer leurs sites. Dites-nous sur quoi vous travaillez et nous vous proposerons une prochaine étape claire.
Plus de guides sur tests, IA et développement moderne
Vous avez apprécié cet article ? Choisissez un sujet et accédez directement à nos nouveaux didacticiels.
Les hallucinations de l'IA expliquées : pourquoi les modèles de langage inventent les choses et comment les réduire
Les hallucinations de l'IA ont expliqué : ce qu'elles sont, pourquoi les modèles de langage les produisent, les types courants, comment les réduire …
Lire l'article →DevOpsVariables d'environnement expliquées : fichiers .env, secrets et configuration correctement effectués
Variables d'environnement expliquées avec des exemples Python que nous avons exécutés : lecture et définition, pourquoi chaque valeur est une chaîne…
Lire l'article →Développement logicielLimitation du débit de l'API expliquée : algorithmes, en-têtes et gestion des erreurs 429
Limitation du débit de l'API expliquée avec une simulation que nous avons exécutée : compartiment de jetons, fenêtre fixe, fenêtre glissante, 429 ré…
Lire l'article →Automatisation des testsSe moquer en Python : unittest.mock, patch et les erreurs qui font mentir les tests
Moquerie en Python avec unittest.mock, testée : Mock et return_value, side_effect, correctifs où un nom est utilisé, spec et autospec, dates moqueus…
Lire l'article →Tests logicielsCouverture du code expliquée : couverture des lignes par rapport aux branches et ce que signifie réellement 100 %
Couverture du code expliquée avec une véritable exécution de cover.py : couverture des lignes, des branches et des conditions, lecture d'un rapport,…
Lire l'article →Bases de données et SQLDELETE vs TRUNCATE vs DROP dans SQL : différences, restauration et quand les utiliser
DELETE vs TRUNCATE vs DROP expliqué avec SQL testé : ce que chacun supprime, restauration, déclencheurs, réinitialisation d'identité, vitesse et en …
Lire l'article →Bases de données et SQLVues SQL expliquées : créez, utilisez et évitez les pièges, avec des exemples
Vues SQL expliquées avec les requêtes que nous avons exécutées : création d'une vue, résultats en direct, masquage des colonnes, performances, vues …
Lire l'article →Développement logicielInterface TypeScript vs Type : différences réelles, testées avec le compilateur
Interface TypeScript vs type, vérifiée avec le vrai compilateur : ce que les deux peuvent faire, ce qu'un seul peut faire, la fusion des déclaration…
Lire l'article →Développement logicielPython *args et **kwargs expliqués : fonctions flexibles avec des exemples clairs
Python *args et **kwargs expliqués avec le code que nous avons exécuté : emballage et déballage, ordre des paramètres, arguments de mot-clé uniqueme…
Lire l'article →Développement logicielFonctions Python Lambda : syntaxe, utilisations réelles et quand ne pas les utiliser
Fonctions Python lambda expliquées avec le code que nous avons exécuté : syntaxe, tri avec clé, carte et filtre, fermetures, piège de liaison tardiv…
Lire l'article →Intelligence artificielleQue sont les agents IA ? Comment ils fonctionnent, avec une simple boucle d'agent que vous pouvez exécuter
Que sont les agents IA ? Un guide simple sur la boucle d'agent, les outils, la mémoire et les garde-fous, avec une petite boucle Python que nous avo…
Lire l'article →DevOpsSurveillance vs observabilité : la vraie différence, avec les journaux, les métriques et les traces
Surveillance et observabilité expliquées : échecs connus et inconnus, journaux, métriques et traces, présentation pas à pas des incidents de paiemen…
Lire l'article →DevOpsVolumes Docker vs montages Bind : conserver les données du conteneur de la bonne manière
Volumes Docker et montages de liaison expliqués : pourquoi les données du conteneur disparaissent, volumes nommés, montages de liaison, tmpfs, exemp…
Lire l'article →Automatisation des testsTDD vs BDD : principales différences, une fonctionnalité conçue dans les deux sens et quand les utiliser
TDD vs BDD expliqué avec une fonctionnalité de remise construite dans les deux sens : refactor rouge-vert avec le code que nous avons exécuté, scéna…
Lire l'article →Développement logicielGit merge vs rebase : différences, exemples et quand utiliser chacun
Git merge vs rebase expliqué avec de vraies sorties de log, un tableau comparatif, la règle d'or du rebase et un flux de travail sûr pour les équipes.
Lire l'article →Développement logicielList comprehension Python : syntaxe, exemples et cas à éviter
La list comprehension Python expliquée : syntaxe, filtres, if-else, boucles imbriquées, temps mesurés et cas où une boucle for classique est préféra…
Lire l'article →Bases de données et SQLJointures SQL expliquées avec exemples (Inner, Left, Right, Full)
Comprenez les jointures SQL pas à pas avec des tables et requêtes d'exemple : INNER, LEFT, RIGHT, FULL, CROSS et SELF, plus erreurs courantes et con…
Lire l'article →