Retry Pattern
Comment gérer les défaillances transitoires dans les systèmes distribués en réessayant les opérations avec des stratégies de backoff pour améliorer la résilience.
Quel problème résout-il
Dans les systèmes distribués, les appels entre services peuvent échouer pour des raisons transitoires : problèmes réseau momentanés, services en cours de redémarrage, limites de rate limiting temporaires ou pics de charge. Ces défaillances sont temporaires et l’opération réussira probablement si on la réessaie.
Sans Retry :
Service A ──► Service B
❌ Erreur 503 (surcharge temporaire)
Le Service A renvoie une erreur à l'utilisateur
(alors que B se rétablit en 2 secondes)
Le problème, c’est que sans nouvelles tentatives, une défaillance transitoire de quelques millisecondes se transforme en une erreur visible pour l’utilisateur, alors qu’il aurait suffi d’attendre un instant et de réessayer pour résoudre le problème.
Défaillances transitoires courantes
| Type de défaillance | Cause | Durée typique |
|---|---|---|
| Timeout réseau | Congestion momentanée | Millisecondes à secondes |
| HTTP 503 | Service en redémarrage ou surchargé | Secondes |
| HTTP 429 | Rate limit atteint | Secondes à minutes |
| Connection refused | Service en cours de démarrage | Secondes |
| DNS resolution failure | Cache DNS expiré | Millisecondes |
Comment ça fonctionne
Le pattern Retry intercepte les appels ayant échoué et les réessaie automatiquement selon une stratégie configurée. Tout l’enjeu réside dans le moment de réessayer, le nombre de tentatives et la durée d’attente entre chaque tentative.
Avec Retry :
Service A ──► Service B
❌ Tentative 1 : Erreur 503
⏳ Attente 1 seconde
❌ Tentative 2 : Erreur 503
⏳ Attente 2 secondes
✅ Tentative 3 : OK 200
L'utilisateur reçoit une réponse réussie
Stratégies d’attente (backoff)
| Stratégie | Formule | Exemple (3 tentatives) | Usage |
|---|---|---|---|
| Fixed delay | delay constant | 1s, 1s, 1s | Cas simples |
| Exponential backoff | delay × 2^tentative | 1s, 2s, 4s | Standard recommandé |
| Exponential + jitter | random(0, delay × 2^tentative) | 0,7s, 1,3s, 3,8s | Évite le thundering herd |
| Linear backoff | delay × tentative | 1s, 2s, 3s | Incrément prévisible |
Exponential backoff avec jitter
La stratégie la plus recommandée est l’exponential backoff avec jitter (variation aléatoire). Sans jitter, lorsqu’un service se rétablit, tous les clients réessaient en même temps, créant un pic de charge qui peut le faire retomber (thundering herd problem).
Sans jitter (thundering herd) :
Client 1 : retry à 1s, 2s, 4s
Client 2 : retry à 1s, 2s, 4s ← Tous en même temps
Client 3 : retry à 1s, 2s, 4s
Le Service B reçoit 3 requêtes simultanées à chaque intervalle
Avec jitter :
Client 1 : retry à 0,7s, 1,8s, 3,2s
Client 2 : retry à 1,2s, 2,3s, 4,5s ← Répartis dans le temps
Client 3 : retry à 0,9s, 1,5s, 3,9s
Le Service B reçoit des requêtes réparties
Paramètres de configuration
| Paramètre | Description | Valeur typique |
|---|---|---|
| Max retries | Nombre maximal de nouvelles tentatives | 3-5 |
| Initial delay | Temps d’attente avant la première nouvelle tentative | 100ms - 1s |
| Max delay | Temps d’attente maximal entre les tentatives | 30s - 60s |
| Backoff multiplier | Facteur de multiplication exponentielle | 2 |
| Retryable errors | Codes d’erreur qui justifient une nouvelle tentative | 408, 429, 500, 502, 503, 504 |
Que réessayer et que ne pas réessayer
✅ Réessayer (défaillances transitoires) :
- HTTP 408 Request Timeout
- HTTP 429 Too Many Requests
- HTTP 500 Internal Server Error (avec précaution)
- HTTP 502 Bad Gateway
- HTTP 503 Service Unavailable
- HTTP 504 Gateway Timeout
- Connection timeout / Connection refused
❌ NE PAS réessayer (défaillances permanentes) :
- HTTP 400 Bad Request (données invalides)
- HTTP 401 Unauthorized (identifiants incorrects)
- HTTP 403 Forbidden (sans autorisation)
- HTTP 404 Not Found (ressource inexistante)
- HTTP 409 Conflict (conflit d'état)
- HTTP 422 Unprocessable Entity (validation échouée)
Avantages
- Résilience face aux défaillances transitoires : la plupart des erreurs réseau se résolvent d’elles-mêmes
- Transparent pour l’utilisateur : la nouvelle tentative se produit sans que l’utilisateur le remarque
- Implémentation simple : c’est l’un des patterns de résilience les plus faciles à implémenter
- Configurable : les paramètres s’ajustent selon le contexte et la criticité
- Complémentaire à d’autres patterns : fonctionne bien avec le Circuit Breaker et le Timeout
Compromis / Inconvénients
- Latence cumulée : chaque nouvelle tentative ajoute du temps d’attente total
- Charge supplémentaire : les nouvelles tentatives génèrent davantage de trafic vers le service qui est déjà en défaillance
- Opérations non idempotentes : réessayer une opération déjà exécutée peut causer des doublons
- Faux positifs : réessayer une erreur permanente gaspille des ressources
- Thundering herd : sans jitter, les nouvelles tentatives synchronisées peuvent aggraver la situation
- Complexité de configuration : des paramètres incorrects peuvent causer plus de problèmes qu’ils n’en résolvent
Quand l’utiliser
- Appels HTTP vers des services externes ou des microservices
- Opérations réseau susceptibles d’échouer pour des raisons transitoires
- Requêtes vers des bases de données susceptibles d’échouer en raison de connexions épuisées
- Publication de messages vers des brokers pouvant être temporairement indisponibles
- Toute opération idempotente qui échoue de manière intermittente
Quand l’éviter
- Opérations non idempotentes où réessayer peut provoquer des effets secondaires dupliqués
- Lorsque l’erreur est clairement permanente (400, 401, 403, 404)
- Opérations de longue durée où le timeout total serait inacceptable
- Lorsque vous avez déjà un Circuit Breaker ouvert (le retry ne doit pas tenter de passer par un circuit ouvert)
- Systèmes où la latence supplémentaire des nouvelles tentatives est inacceptable
Technologies et implémentations courantes
| Catégorie | Options |
|---|---|
| Bibliothèques | Resilience4j (Java), Polly (.NET), tenacity (Python), axios-retry (Node.js) |
| HTTP Clients | Configuration native dans HttpClient (.NET), OkHttp (Java), Got (Node.js) |
| Cloud | AWS SDK (retry intégré), Azure SDK (retry policies), Google Cloud Client Libraries |
| Service Mesh | Istio, Linkerd, Envoy (retry au niveau de l’infrastructure) |
Relation avec d’autres patterns
Le Retry est rarement utilisé seul. Il fait partie d’une chaîne de résilience :
Requête
│
▼
┌──────────┐
│ Timeout │ Définit combien de temps attendre par tentative
└────┬─────┘
▼
┌──────────┐
│ Retry │ Réessaie si la tentative échoue
└────┬─────┘
▼
┌──────────┐
│ Circuit │ Coupe s'il y a trop de défaillances
│ Breaker │
└────┬─────┘
▼
Service downstream
- Timeout : définit combien de temps attendre à chaque tentative individuelle avant de la considérer comme échouée
- Circuit Breaker : si les nouvelles tentatives échouent de façon constante, le circuit breaker s’ouvre et empêche de nouvelles tentatives
- Idempotence : les services downstream doivent être idempotents pour que les nouvelles tentatives soient sûres
- Bulkhead : isole les ressources afin que les nouvelles tentatives d’un service n’affectent pas les autres
Prochaines étapes
Le Retry est la première ligne de défense contre les défaillances transitoires. Pour définir combien de temps attendre à chaque tentative, explorez le pattern Timeout. Pour garantir que les nouvelles tentatives ne causent pas d’effets dupliqués, consultez le pattern d’Idempotence.