Pourquoi limiter le débit (rate limiting)
Toute API a une capacité finie, et tous les clients ne se comportent pas correctement. Un scraper, un client buggé coincé dans une boucle de tentatives (retry loop), ou un pic de trafic légitime peuvent épuiser vos connexions à la base de données et rendre le service indisponible pour tout le monde. Le rate limiting plafonne la vitesse à laquelle un client peut effectuer des requêtes afin qu’aucun utilisateur unique ne puisse consommer l’intégralité des ressources du système.
Cela permet également de rendre les coûts prévisibles, d’imposer un usage équitable entre les tenants et les forfaits, et vous donne un levier pour freiner les abus avant qu’ils ne provoquent une panne.
Que limiter
Une limite n’a de sens que par rapport à une identité. Voici les choix les plus courants :
- Clé API — la meilleure option pour les communications serveur à serveur et les clients tiers.
- ID utilisateur — l’approche naturelle pour les applications avec authentification.
- Adresse IP — une solution de repli pour le trafic anonyme, bien que plusieurs utilisateurs puissent partager la même IP.
- Combinaison — clé plus IP, ou utilisateur plus endpoint, pour plus de précision.
- Endpoint ou niveau (tier) — des limites plus strictes sur les opérations coûteuses comme la recherche ou les exports.
Déterminez également la portée : une limite globale, une limite par endpoint, ou les deux. Une limite globale protège le service, tandis que les limites par endpoint protègent des tâches spécifiques et gourmandes en ressources.
Algorithmes
Quatre algorithmes couvrent pratiquement tous les besoins.
Fixed window (fenêtre fixe) — compte les requêtes sur un intervalle fixe et réinitialise le compteur à la limite de l’intervalle.
limit: 100 per minute
key: client:123:2026-09-15T10:05
C’est une solution simple et peu coûteuse, mais un client peut envoyer 100 requêtes à la fin d’une fenêtre et 100 au début de la suivante, doublant ainsi efficacement le débit à la limite de la fenêtre.
Sliding window (fenêtre glissante) — compte sur les N dernières secondes plutôt que sur un bloc fixe, généralement en combinant la fenêtre actuelle et la précédente avec une moyenne pondérée. Plus fluide que la fenêtre fixe pour un coût similaire.
Token bucket (seau à jetons) — un seau se remplit à un rythme constant, et chaque requête consomme un jeton. Cela permet de courtes rafales (bursts) jusqu’à la capacité du seau tout en imposant un débit moyen, ce qui correspond au comportement réel des clients.
// token-bucket.js
const capacity = 20;
const refillPerSecond = 5;
let tokens = capacity;
let last = Date.now();
function allow() {
const now = Date.now();
tokens = Math.min(capacity, tokens + ((now - last) / 1000) * refillPerSecond);
last = now;
if (tokens < 1) return false;
tokens -= 1;
return true;
}
Leaky bucket (seau percé) — les requêtes entrent dans une file d’attente qui se vide à un rythme fixe. Cela lisse le trafic pour obtenir une sortie constante, ce qui est utile lorsque les systèmes en aval nécessitent une charge stable.
Répondre aux limites
Informez vos clients de la situation en utilisant des signaux standards.
HTTP/1.1 429 Too Many Requests
Retry-After: 30
RateLimit-Limit: 100
RateLimit-Remaining: 0
RateLimit-Reset: 30
- 429 Too Many Requests est le code de statut approprié.
- Retry-After indique au client quand réessayer, soit en secondes, soit via une date.
- Les headers RateLimit-* exposent la limite, le nombre de requêtes restantes et l’heure de réinitialisation afin que les clients puissent adapter leur rythme.
Renvoyer 200 ou ignorer silencieusement les requêtes masque le problème et entraîne des bugs clients mystérieux. Les clients bien conçus lisent Retry-After et réduisent automatiquement leur cadence.
Limitation distribuée
Si vous exécutez plusieurs instances, les compteurs doivent être partagés. Les limites en mémoire s’appliquent par instance ; ainsi, avec dix serveurs, un client bénéficie concrètement de dix fois la limite.
// redis.js
const key = `ratelimit:${apiKey}`;
const count = await redis.incr(key);
if (count === 1) await redis.expire(key, 60);
if (count > 100) {
// reject with 429
}
Utilisez des opérations atomiques, ou une bibliothèque utilisant un script Lua, afin que les incrémentations et les expirations soient exemptes de conditions de concurrence (race conditions). Redis est le stockage habituel car il est rapide et prend en charge les scripts atomiques ainsi que l’expiration. Les API gateways et les plateformes edge peuvent appliquer les limites avant même que le trafic n’atteigne votre application, ce qui est l’endroit le plus efficace pour le faire.
Quotas vs rate limits
Ils répondent à des problématiques différentes et coexistent souvent.
- Rate limit — la vitesse : 100 requêtes par minute.
- Quota — la quantité : 100 000 requêtes par mois, liées à un forfait.
Un client peut rester en dessous de son rate limit tout au long du mois et tout de même épuiser son quota. Suivez les quotas séparément, généralement avec un compteur à plus longue durée de vie, et renvoyez une erreur distincte lorsqu’un quota est épuisé afin que les clients puissent faire la différence.
Éviter de nuire aux utilisateurs réels
Les limites doivent stopper les abus sans pénaliser l’utilisation normale.
- Définissez les limites en vous basant sur le trafic mesuré, tout en prévoyant une marge pour les pics.
- Autorisez les pics de trafic avec un système de token bucket plutôt qu’une coupure nette.
- Utilisez des limites différentes selon le niveau d’abonnement (tier) et le point de terminaison (endpoint).
- Excluez les health checks, les appels internes et les ressources statiques.
- Privilégiez les ralentissements temporaires aux bannissements permanents.
- Surveillez les taux de rejet et ajustez-les ; un pic de réponses 429 peut signifier qu’une limite est trop restrictive.
Bonnes pratiques
- Identifiez les clients avec la clé stable la plus spécifique disponible.
- Choisissez un algorithme adapté à la forme du trafic.
- Retournez une erreur 429 avec
Retry-Afteret les headers de rate limit. - Partagez les compteurs dans Redis ou appliquez les limites au niveau de l’edge.
- Séparez les rate limits des quotas et exposez les deux.
- Autorisez les bursts et définissez les limites à partir de mesures réelles.
- Journalisez et surveillez les rejets, et configurez des alertes en cas de pics soudains.
Erreurs courantes
- Compter par instance et multiplier la limite effective.
- Utiliser uniquement l’IP, ce qui pénalise les utilisateurs derrière un NAT partagé.
- Retourner un code 200 ou ignorer les requêtes silencieusement.
- Fixer des limites si strictes que les clients normaux sont bloqués.
- Oublier d’expirer les compteurs, entraînant des fuites de mémoire.
- Traiter les rate limits comme un substitut à l’authentification et à l’autorisation.
Et après ?
Le rate limiting permet de maintenir une API disponible même sous forte pression. Appuyez-vous sur la conception REST et le guide HTTP, couplez-le avec le versionnage d’API dans le cadre du cycle de vie, et implémentez-le sur Node.js. Ajoutez ensuite un limiteur à l’un de vos endpoints et observez son comportement lors d’un test de charge.