Retry With Jitter (anti retry storm)
Le pattern de retry qui ajoute du hasard contrôlé au backoff exponentiel pour briser la synchronisation des clients et éviter l'amplification des chocs en cas de panne partenaire.
Problème
Un partenaire AS2 tombe en panne 5 minutes. Pendant ces 5 minutes, 50 clients EDI tentent d'envoyer, échouent, et déclenchent leur retry. Si tous utilisent un backoff exponentiel pur (1s, 2s, 4s, 8s, 16s, 32s, 64s), au moment où le partenaire revient, les 50 retentent presque simultanément — le partenaire est immédiatement re-saturé et tombe à nouveau. C'est le retry storm ou thundering herd : la cure devient pire que le mal.
Forces
- Les retries sont nécessaires : sans retry, toute panne transitoire = message perdu.
- Le backoff exponentiel seul est insuffisant : il synchronise les clients sur des instants discrets.
- Le hasard doit être pseudo-aléatoire de qualité : un Math.random suffit, pas besoin de cryptographique.
- L'agrégat des retries doit être borné : après N tentatives, abandon et message vers Dead Letter Channel.
- L'idempotency doit être garantie : un retry envoie le même message, donc le serveur doit dédoublonner.
Solution
Ajouter un facteur de jitter aléatoire à l'intervalle de retry. Trois variantes principales documentées dans l'article AWS de référence (Marc Brooker, 2015) :
- Exponential Backoff + Full Jitter :
sleep = random(0, min(cap, base * 2^attempt)). Le cas de référence : la distribution est uniforme sur l'intervalle entier. - Equal Jitter :
sleep = min(cap, base * 2^attempt) / 2 + random(0, min(cap, base * 2^attempt) / 2). Compromis qui garantit un minimum mais reste partiellement aléatoire. - Decorrelated Jitter :
sleep = min(cap, random(base, sleep * 3)). Itératif, bon comportement statistique en simulation.
Les bibliothèques modernes (AWS SDK, Resilience4j, Polly .NET, Tenacity Python) intègrent Full Jitter par défaut.
Trois stratégies de retry : 1. Exponentiel pur (BAD — retry storm) tentative 1 : T+1s tentative 2 : T+2s tentative 3 : T+4s tentative 4 : T+8s → tous les clients retentent ensemble : amplification du choc 2. Exponentiel + Full Jitter (Marc Brooker, AWS 2015) tentative N : random(0, min(MAX, BASE * 2^N)) tentative 1 : random(0, 2s) → ex: 1.3s tentative 2 : random(0, 4s) → ex: 2.8s tentative 3 : random(0, 8s) → ex: 5.1s tentative 4 : random(0, 16s) → ex: 14.7s → la distribution est uniforme sur l'intervalle, casse la synchronisation 3. Decorrelated Jitter (variante AWS pour file d'attente) sleep = min(cap, random(BASE, sleep * 3)) → progresse de façon ergodique, équilibre exploration / convergence
Implémentation EDI
Cas concret : 50 envois AS2 simultanés vers le même partenaire,
timeout 30s. Le partenaire renvoie 503 + Retry-After 60. Sans
jitter : à T+60s, 50 clients réessaient, tombent dans une
même seconde, partenaire à nouveau saturé. Avec Full
Jitter : chaque client réessait après random(0, 120s),
distribué uniformément sur 2 minutes. Le partenaire encaisse 50
requêtes étalées, recouvre normalement, taux de succès remonte.
Code Java avec Resilience4j :
RetryConfig config = RetryConfig.custom().maxAttempts(5).intervalFunction(IntervalFunction.ofExponentialRandomBackoff(1000, 2.0, 30000)).build();.
Côté AS2 spécifique : respecter aussi Retry-After
quand fourni, et combiner avec
Circuit
Breaker pour ouvrir le circuit après seuil d'échecs. Pour les
flux fiscaux temps-contraint (clearance ZATCA, IRP Inde),
documenter dans le SLA la fenêtre de retry maximale.
Anti-patterns
- Retry exponentiel sans jitter : le retry storm reste possible, surtout dans des architectures à multiples instances.
- Retry sans plafond : 100 tentatives, 1h cumulée, message qui ne sortira jamais → bloque la file indéfiniment.
- Retry sans idempotency : le partenaire reçoit la même commande 5 fois, traite 5 fois → 5 livraisons, sinistre comptable.
- Retry de tout : retenter une erreur 401 Unauthorized ne sert à rien, le retry doit dépendre du type d'erreur (transitoire vs permanent).
- Jitter trop faible : random(0, 100ms) sur un sleep exponentiel à 60s ne casse rien — le jitter doit être du même ordre de grandeur que le sleep.
- Pas de circuit breaker associé : si la panne dure 10h, continuer à retenter charge inutilement le réseau et amplifie les coûts.
Patterns liés
- Retry, backoff exponentiel et circuit breaker — pattern parent général.
- Circuit Breaker — pattern complémentaire pour borner les tentatives.
- Idempotency — propriété requise pour que le retry soit safe.
- Dead Letter Channel — destination du message après épuisement des retries.
- Rate Limiter — pattern d'amont qui prévient les retry storms.
Sources
- Brooker M. — Exponential Backoff And Jitter, AWS Architecture Blog, 2015. L'article de référence qui établit l'intérêt du jitter mathématiquement. aws.amazon.com/blogs — exponential-backoff-and-jitter
- Nygard M. — Release It! Design and Deploy Production-Ready Software, Pragmatic Bookshelf, 2e éd. 2018. Pattern Timeouts et Circuit Breaker.
- RFC 5681 — TCP Congestion Control (IETF, septembre 2009). La référence du backoff exponentiel dans la pile IP. rfc-editor.org/rfc/rfc5681
- RFC 7231 §7.1.3 — HTTP/1.1 Retry-After header. rfc-editor.org/rfc/rfc7231
- Google SRE Workbook — Cascading Failures. Chapitre dédié aux retry storms et leur prévention. sre.google/workbook — cascading-failures
- Resilience4j Documentation. Bibliothèque Java de référence avec implémentation native de Full Jitter et Decorrelated Jitter. resilience4j.readme.io