Aller au contenu principal

HTTP 429 et backoff exponentiel

Mis à jour le 29 juillet 2026

Gérer les dépassements de limites

Dès que votre application franchit ses plafonds RPM ou TPM, l’API Grok répond par un code HTTP 429 (Too Many Requests). Il faut lire ce code pour ce qu’il est : non pas une panne, mais un signal de contrôle de flux. L’API vous dit « ralentissez », pas « c’est cassé ». Toute la différence entre une intégration robuste et une intégration fragile tient à la manière dont votre code interprète cette nuance. Un 429 mal traité ne reste jamais isolé : il se propage en cascade dans la file d’attente, puis dans les services appelants, et finit en incident visible par l’utilisateur final.

Ce qu’il ne faut surtout pas faire

La réaction instinctive, quand une requête échoue, consiste à la renvoyer immédiatement. C’est exactement le pire réflexe. Vous étiez déjà au-dessus de votre quota ; en réémettant aussitôt, vous consommez un appel supplémentaire qui échouera lui aussi, et vous prolongez la période de blocage au lieu de l’écourter. Un retry infini aggrave encore le tableau en mobilisant des threads pour rien. À l’opposé, traiter le 429 comme une erreur définitive et abandonner prive votre utilisateur d’un résultat qui serait arrivé quelques secondes plus tard. La bonne réponse se situe entre les deux : réessayer, mais en attendant, et en attendant de plus en plus longtemps.

Le backoff exponentiel

Le principe consiste à doubler le délai à chaque tentative infructueuse, en y ajoutant une variation aléatoire appelée jitter.

import time
import random

def appel_avec_retry(requete, max_retries=5):
    for tentative in range(max_retries):
        reponse = envoyer_requete(requete)

        if reponse.status_code != 429:
            return reponse

        delai_base = 2 ** tentative  # 1, 2, 4, 8, 16 secondes
        jitter = random.uniform(0, delai_base * 0.5)
        delai = delai_base + jitter

        time.sleep(delai)

    raise Exception("Limite de retries atteinte")

Le jitter mérite qu’on s’y arrête, car c’est la ligne qu’on supprime le plus volontiers en se disant qu’elle complique le code pour rien. Imaginez trente instances de votre service qui reçoivent un 429 à la même seconde. Sans jitter, elles calculent toutes le même délai et réessaient toutes exactement au même instant : vous avez reproduit le pic de charge à l’identique, une seconde plus tard, puis deux, puis quatre. C’est le phénomène du thundering herd. Le tirage aléatoire disperse les tentatives dans le temps et casse cette synchronisation involontaire. Notez aussi que le délai de base démarre à une seconde : ne descendez jamais en dessous pour la première tentative.

Passer à l’échelle : réguler avant d’échouer

Sur une application à fort volume, réagir aux 429 ne suffit plus ; mieux vaut les éviter. Un rate limiter côté client borne le nombre d’appels concurrents et impose un espacement minimal entre les envois, de sorte que la charge sortante reste sous le plafond au lieu de le tester en permanence.

import asyncio
from asyncio import Semaphore

# Limite à 25 requêtes/seconde (marge sous les 30 req/s autorisées)
semaphore = Semaphore(25)

async def requete_limitee(payload):
    async with semaphore:
        reponse = await envoyer_requete_async(payload)
        await asyncio.sleep(1.0 / 25)  # espacement minimal
        return reponse

Remarquez la marge délibérée : 25 requêtes par seconde au lieu des 30 autorisées. Cette réserve absorbe les irrégularités du réseau et les rafales de trafic sans jamais toucher le plafond.

Le circuit breaker complète le dispositif en coupant les appels lorsque les échecs s’enchaînent. Tant qu’il est fermé, les requêtes passent normalement. Après un nombre d’échecs consécutifs que vous fixez, il s’ouvre et refuse localement toutes les requêtes pendant un délai configuré : vous cessez de solliciter une API qui vous refuse, et vos threads restent disponibles pour le reste de l’application. À l’expiration du délai, il passe en semi-ouvert et laisse filer une seule requête de test ; si elle aboutit, le trafic normal reprend, sinon le compteur repart. Ce mécanisme protège autant votre service que l’API en face.

Piloter le 429 comme une métrique

Un dernier réflexe, souvent absent des intégrations : instrumenter le taux de 429. Ce chiffre ne mesure pas la qualité de votre code de retry, il mesure l’adéquation entre votre tier et votre trafic réel. Une courbe qui monte semaine après semaine annonce un sous-dimensionnement bien avant que les utilisateurs ne s’en plaignent, et vous laisse le temps d’agir à froid — répartir la charge, budgéter un passage au tier supérieur — au lieu d’improviser un vendredi soir.

Points clés à retenir

  • Le code HTTP 429 signale un dépassement de limite RPM ou TPM
  • Le backoff exponentiel avec jitter est la stratégie recommandée par xAI
  • En production, complétez le backoff par un rate limiter côté client et un circuit breaker
  • Ne retentez jamais immédiatement : attendez au minimum 1 seconde avant le premier retry
  • Surveillez le taux de 429 dans vos métriques comme indicateur de sous-dimensionnement