Le rate limiting est un contrôle qui plafonne le nombre de requêtes d’un client par fenêtre ; le throttling ralentit l’excédent au lieu de le rejeter. Le rate limiting (limitation de débit) a un troisième frère — le quota, une allocation par période de facturation — et avec lui vous tenez le vocabulaire complet que la plupart des explications fondent en un seul mot. Ensemble, ces trois-là sont ce qui garde les API justes, rentables et en ligne.
Points clés
| Question | Réponse |
|---|---|
| Rate limit | Plafond dur par fenêtre courte — l’excédent reçoit un 429 |
| Throttle | L’excédent est ralenti ou mis en file, pas refusé |
| Quota | L’allocation de long terme — par jour/mois, par plan |
| La voix du serveur | 429 + Retry-After + headers de rate limit |
| Les bonnes manières du client | Respecter Retry-After ; backoff exponentiel avec jitter |
Ce que signifient la réponse 429 et les headers de rate limit
HTTP/1.1 429 Too Many Requests ← RFC 6585
Retry-After: 30 ← attendez ce délai (secondes ou une date)
X-RateLimit-Limit: 1000 ← votre budget pour cette fenêtre
X-RateLimit-Remaining: 0 ← ce qu'il en reste
X-RateLimit-Reset: 1767024000 ← quand il se recharge
RateLimit: "default";r=0;t=30 ← la forme standard émergente de l'IETF
La moitié du contrat qui revient au client — le code dont tout consommateur de SDK finit par avoir besoin :
// JavaScript / Node.js — Back4app JS SDK
// The client half of rate limiting: back off, with jitter, then retry
async function withBackoff(fn, attempt = 0) {
try {
return await fn();
} catch (e) {
if (e.code !== 155 || attempt >= 4) throw e; // 155: request limit hit
const wait = 2 ** attempt * 500 + Math.random() * 200;
await new Promise((r) => setTimeout(r, wait)); // exponential + jitter
return withBackoff(fn, attempt + 1);
}
}
const results = await withBackoff(() => query.find()); // Flutter / Dart — Back4app Flutter SDK
// The client half of rate limiting: back off, with jitter, then retry
Future<ParseResponse> withBackoff(Future<ParseResponse> Function() fn,
[int attempt = 0]) async {
final response = await fn();
if (response.success || attempt >= 4) return response;
final wait = (1 << attempt) * 500 + Random().nextInt(200);
await Future.delayed(Duration(milliseconds: wait)); // exponential + jitter
return withBackoff(fn, attempt + 1);
}
final response = await withBackoff(() => query.query()); // iOS / Swift — Back4app Swift SDK
// The client half of rate limiting: back off, with jitter, then retry
func withBackoff<T>(_ attempt: Int = 0,
_ fn: @escaping () async throws -> T) async throws -> T {
do { return try await fn() }
catch {
guard attempt < 4 else { throw error }
let wait = pow(2.0, Double(attempt)) * 0.5 + .random(in: 0...0.2)
try await Task.sleep(for: .seconds(wait)) // exponential + jitter
return try await withBackoff(attempt + 1, fn)
}
}
let results = try await withBackoff { try await query.find() } // Android / Kotlin — Back4app Android SDK
// The client half of rate limiting: back off, with jitter, then retry
suspend fun <T> withBackoff(attempt: Int = 0, fn: suspend () -> T): T {
return try {
fn()
} catch (e: ParseException) {
if (attempt >= 4) throw e
val wait = (1L shl attempt) * 500 + Random.nextLong(200)
delay(wait) // exponential + jitter
withBackoff(attempt + 1, fn)
}
}
val results = withBackoff { query.find() } Token bucket vs. leaky bucket vs. fenêtre fixe vs. fenêtre glissante
| Algorithme | Bursts | Précision | Mémoire | Verdict |
|---|---|---|---|---|
| Token bucket (seau à jetons) | Autorisés, jusqu’à la taille du bucket | Bonne | Minime | Le choix par défaut des API — des humains en bursts, une moyenne stable |
| Leaky bucket | Lissés en un goutte-à-goutte constant | Bonne | Faible | Shaping de trafic — débit constant, latence ajoutée |
| Fenêtre fixe | Burst de frontière : 2× la limite aux bords | Faible | Minime | Simple, et notoirement contournable |
| Fenêtre glissante | Contrôlés | La meilleure | Modeste | Le compromis d’échelle sur lequel atterrissent la plupart des plateformes |
Une note de bas de page distribuée que les diagrammes escamotent : avec plusieurs serveurs d’API, le bucket doit vivre dans un endroit partagé — typiquement un store en mémoire qui fait des incréments atomiques — et vous choisissez entre la précision stricte (chaque vérification interroge le store) et la vitesse (des compteurs locaux, une synchronisation lâche). La plupart des plateformes acceptent des limites légèrement lâches comme prix de la latence ; les quotas contractuels, eux, ont droit au traitement strict.
Portée : qui est limité, exactement ?
La limite par IP arrête les déferlantes anonymes et les fuites d’absorption DDoS, mais un NAT de bureau transforme des centaines d’utilisateurs en une seule adresse. La limite par clé associe les limites aux applications et aux plans de pricing — la portée qui fait le gros du travail. La limite par utilisateur empêche un compte de monopoliser la clé d’une app partagée. La limite par endpoint facture honnêtement les opérations coûteuses — les endpoints de recherche et d’export méritent des budgets plus serrés que les health checks. Les systèmes en production les superposent ; la question composite est toujours « quel budget cette requête a-t-elle dépensé ? »
Cas d’usage courants
- Protection des API publiques — le cas canonique : des budgets équitables par clé, publiés dans les headers, appliqués à la bordure.
- Application des tiers — plans gratuits vs. payants qui diffèrent précisément par le quota et la marge de burst.
- Défense contre l’abus et le scraping — des limites serrées pour les anonymes, généreuses pour les authentifiés.
- Contrôle des coûts sur les chemins coûteux — appels de LLM, exports, recherches : des budgets par endpoint qui reflètent le coût réel.
- Autodéfense côté client — backoff et fusion des requêtes face aux limites des autres, que vos intégrations doivent respecter pour ne pas se faire bannir.
Rejeter ou throttler ? Matrice de décision
| Rate limit (rejeter) quand… | Throttle (ralentir/mettre en file) quand… |
|---|---|
| Les clients savent réessayer intelligemment | Le travail doit finir par avoir lieu |
| Vous protégez de la capacité interactive | Vous lissez de la charge batch et d’arrière-plan |
| Le contrat porte sur des requêtes par fenêtre | Le contrat porte sur l’équité du service |
| Un retour rapide vaut mieux qu’un succès tardif | Un succès tardif vaut mieux qu’une erreur |
| Le trafic est anonyme ou non fiable | Les producteurs sont les vôtres, internes |
Et la méta-règle : quel que soit votre choix, publiez-le — des limites dans la documentation et dans les headers transforment un mur frustrant en un contrat d’ingénierie sur lequel les clients peuvent bâtir.
Limites et trade-offs
- Les limites sont des instruments grossiers. Une requête n’est pas une unité de coût — une lecture bon marché et un export monstrueux dépensent « 1 » l’un comme l’autre. La limitation sensible au coût (des points par opération) est le raffinement, au prix de la complexité.
- La précision distribuée coûte de la latence. Les compteurs globaux stricts se sérialisent sur un store partagé ; les compteurs locaux lâches admettent trop de trafic à la marge. Choisissez selon la garantie visée, pas selon la mode.
- Les 429 punissent la boucle de nouvelles tentatives innocente. Les clients sans jitter se synchronisent en thundering herds ; le design de votre limiter doit supposer le pire client, parce qu’il le rencontrera.
- Le throttling masque la surcharge. Les requêtes mises en file lissent les graphiques tout en accumulant un backlog invisible ; les files d’attente ont besoin de bornes et de politiques de délestage, sinon elles se transforment en pannes différées.
- Les limites sont des décisions produit déguisées en ops. Budgets, tiers et marges de burst façonnent l’expérience utilisateur et le chiffre d’affaires — fixez-les avec la page de pricing ouverte, pas seulement le dashboard.
Le rate limiting sur Back4app
Back4app est une plateforme open-source de Backend as a Service (BaaS) qui combine une base de données gérée, des API REST et GraphQL générées automatiquement, l’authentification, le stockage de fichiers et des fonctions serverless avec Cloud Code. La protection est un comportement par défaut de la plateforme plutôt qu’un projet : les limites de requêtes s’appliquent à la bordure, par app et par plan, les opérations coûteuses peuvent être placées derrière des fonctions Cloud Code avec leurs propres vérifications, et les clients reçoivent une sémantique 429 propre — que le pattern de backoff côté SDK, dans les onglets de code ci-dessus, transforme en comportement résilient au lieu d’écrans en échec. Vous ajustez des politiques ; vous ne construisez pas le limiter.
Questions fréquentes
Qu'est-ce que le rate limiting d'API ?
Un contrôle qui plafonne le nombre de requêtes qu'un client peut émettre dans une fenêtre de temps — disons mille par heure et par clé. Les requêtes qui dépassent le plafond sont rejetées, classiquement avec un HTTP 429 Too Many Requests. Il protège le service contre la surcharge et les abus, empêche un client gourmand de dégrader l'expérience de tous les autres, et rend la planification de capacité possible.
Quelle est la différence entre rate limiting, throttling et quota ?
Trois outils que l'on fond souvent en un seul mot. Le rate limiting rejette l'excédent de requêtes sans détour. Le throttling les ralentit ou les met en file d'attente à la place — la requête finit par aboutir, plus tard. Le quota est l'allocation de long terme : des requêtes par jour ou par mois, adossées à un plan ou à une facture. Les fenêtres courtes protègent l'infrastructure ; les quotas définissent l'accord commercial ; le throttling adoucit les bords.
Que signifie l'erreur 429 et comment la corriger ?
Vous avez dépassé les requêtes autorisées dans la fenêtre en cours. Le correctif est une discipline côté client : lisez le header Retry-After s'il est présent et attendez ce délai ; sinon, réessayez avec un backoff exponentiel plus du jitter. Le mauvais correctif — des nouvelles tentatives aveugles et immédiates — aggrave la situation pour vous et pour tout le monde, ce que le backoff existe précisément pour éviter.
Comment fonctionne l'algorithme du token bucket ?
Un bucket contient des tokens qui se rechargent à un rythme constant ; chaque requête en dépense un. Un bucket plein laisse passer un burst — la taille du bucket est la marge de burst — tandis que le rythme de recharge impose la moyenne soutenue. Cette forme accueillante pour les bursts explique pourquoi le token bucket est l'algorithme par défaut des API orientées utilisateur.
Pourquoi un rate limiter à fenêtre fixe pose-t-il problème ?
À cause du burst de frontière : avec une limite de 100 par minute, un client peut envoyer 100 requêtes à 11:59:59 et 100 de plus à 12:00:01 — 200 en deux secondes, et tout est réglementaire. Les algorithmes à fenêtre glissante pondèrent la fenêtre précédente pour fermer la brèche, au prix d'un peu plus de comptabilité. C'est la raison classique pour laquelle « requêtes par minute » exige une définition de la minute.
Quels sont les headers de rate limit ?
La convention d'abord : X-RateLimit-Limit, X-RateLimit-Remaining et X-RateLimit-Reset annoncent au client son budget, ce qu'il en reste et quand il se recharge — popularisés par les grands fournisseurs d'API. La standardisation arrive via les headers RateLimit et RateLimit-Policy de l'IETF. Dans un cas comme dans l'autre, publier les limites dans les headers est ce qui transforme le rate limiting d'un mur en un contrat.
Qu'est-ce que le backoff exponentiel avec jitter ?
La nouvelle tentative polie : attendre 1 s, puis 2 s, 4 s, 8 s après des échecs successifs — en ajoutant une part aléatoire (le jitter) à chaque attente. Le jitter compte plus qu'il n'y paraît : sans lui, tous les clients qui ont échoué ensemble réessaient ensemble, martelant le service en convalescence par vagues synchronisées. Le hasard brise le thundering herd.
Les limites doivent-elles être par IP, par clé d'API ou par utilisateur ?
En couches, parce que chaque portée échoue seule : la limite par IP arrête les déferlantes anonymes mais punit les bureaux derrière une adresse unique ; celle par clé associe les limites aux applications et aux plans ; celle par utilisateur empêche un compte de monopoliser une app partagée ; celle par endpoint protège spécifiquement les opérations coûteuses. Les systèmes en production combinent généralement au moins des limites par clé et par endpoint.