Retry Pattern
Stratégie de résilience qui réexécute automatiquement une opération échouée avec délais croissants pour gérer les défaillances transitoires.
Mis à jour le 10 janvier 2026
Le Retry Pattern est un pattern architectural de résilience qui permet à une application de gérer automatiquement les défaillances transitoires en réessayant une opération échouée. Ce mécanisme, essentiel dans les architectures distribuées, implémente une logique de nouvelle tentative avec des délais progressifs (backoff) pour éviter de surcharger les systèmes défaillants tout en maximisant les chances de succès.
Fondements du Retry Pattern
- Distinction entre erreurs transitoires (réseau instable, timeout) et erreurs permanentes (authentification échouée, ressource inexistante)
- Stratégies de backoff : linéaire, exponentiel, exponentiel avec jitter pour éviter le thundering herd
- Limites de tentatives configurables pour éviter les boucles infinies et préserver les ressources système
- Idempotence des opérations critiques pour garantir qu'une réexécution ne crée pas d'effets de bord indésirables
Avantages techniques et métier
- Amélioration significative de la disponibilité globale du système face aux pannes réseau intermittentes
- Réduction des faux positifs d'alertes en absorbant les échecs temporaires sans intervention manuelle
- Optimisation des coûts d'infrastructure en évitant le sur-provisionnement pour compenser l'instabilité réseau
- Meilleure expérience utilisateur avec une transparence des micro-interruptions de service
- Facilitation de l'intégration avec des services tiers dont la fiabilité n'est pas garantie à 100%
Exemple concret d'implémentation
interface RetryConfig {
maxAttempts: number;
baseDelay: number;
maxDelay: number;
backoffMultiplier: number;
jitter: boolean;
}
class RetryService {
async executeWithRetry<T>(
operation: () => Promise<T>,
config: RetryConfig,
isRetryable: (error: Error) => boolean = () => true
): Promise<T> {
let lastError: Error;
for (let attempt = 1; attempt <= config.maxAttempts; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error as Error;
// Ne pas réessayer si l'erreur n'est pas transitoire
if (!isRetryable(lastError)) {
throw error;
}
// Dernière tentative échouée
if (attempt === config.maxAttempts) {
break;
}
// Calcul du délai avec backoff exponentiel
const delay = this.calculateDelay(attempt, config);
console.warn(`Attempt ${attempt} failed. Retrying in ${delay}ms...`);
await this.sleep(delay);
}
}
throw new Error(`Operation failed after ${config.maxAttempts} attempts: ${lastError.message}`);
}
private calculateDelay(attempt: number, config: RetryConfig): number {
let delay = config.baseDelay * Math.pow(config.backoffMultiplier, attempt - 1);
delay = Math.min(delay, config.maxDelay);
// Ajout de jitter pour éviter le thundering herd
if (config.jitter) {
delay = delay * (0.5 + Math.random() * 0.5);
}
return Math.floor(delay);
}
private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Utilisation avec API externe
const retryService = new RetryService();
const config: RetryConfig = {
maxAttempts: 5,
baseDelay: 1000,
maxDelay: 30000,
backoffMultiplier: 2,
jitter: true
};
const isNetworkError = (error: Error) =>
error.message.includes('ECONNRESET') ||
error.message.includes('ETIMEDOUT');
const data = await retryService.executeWithRetry(
() => fetch('https://api.example.com/data').then(r => r.json()),
config,
isNetworkError
);Mise en œuvre efficace
- Identifier les opérations candidates : appels réseau, requêtes base de données, accès à des services externes
- Classifier les erreurs en transitoires (timeout, 503, perte de connexion) et permanentes (401, 404, erreurs de validation)
- Définir une stratégie de backoff adaptée : exponentiel avec jitter pour la plupart des cas, linéaire pour des cas spécifiques
- Configurer des limites appropriées : 3-5 tentatives pour les opérations critiques, timeouts progressifs pour chaque tentative
- Implémenter une observabilité complète : logs structurés, métriques de taux de retry, alertes sur taux d'échec anormal
- Garantir l'idempotence des opérations ou utiliser des identifiants de déduplication pour les requêtes répétées
- Tester les scénarios de défaillance avec chaos engineering pour valider le comportement sous charge
Conseil de production
Combinez le Retry Pattern avec le Circuit Breaker pour éviter de surcharger un service déjà défaillant. Après 3-5 tentatives échouées consécutives, ouvrez le circuit pendant 30-60 secondes avant de réessayer. Ajoutez systématiquement du jitter (variance aléatoire de ±50%) aux délais pour éviter que tous les clients ne réessaient simultanément après une panne généralisée (thundering herd problem).
Outils et bibliothèques
- Polly (.NET) : bibliothèque de résilience complète avec retry, circuit breaker, timeout et fallback
- resilience4j (Java) : framework léger inspiré de Hystrix avec support natif du retry pattern
- axios-retry (JavaScript) : plugin pour axios permettant des retry configurables sur les requêtes HTTP
- Tenacity (Python) : bibliothèque de retry généraliste avec multiples stratégies de backoff
- AWS SDK : retry automatique intégré avec backoff exponentiel pour tous les services AWS
- Istio/Envoy : retry automatique au niveau du service mesh avec configuration déclarative
Le Retry Pattern constitue une fondation essentielle pour construire des systèmes résilients dans des environnements distribués. En gérant intelligemment les défaillances transitoires, il améliore directement la disponibilité perçue par les utilisateurs tout en réduisant les coûts opérationnels liés aux incidents et aux interventions manuelles. Son implémentation judicieuse, combinée à d'autres patterns de résilience, transforme des architectures fragiles en systèmes robustes capables de maintenir un niveau de service élevé malgré l'instabilité inhérente aux infrastructures cloud modernes.
