Article

Timeout, retry, outbox : choisir une protection adaptée à la panne

Un service ralentit ou un message revient en double : timeout, retry, outbox et idempotence répondent à des risques différents. Voici comment les combiner.

Un service devient lent. Tous ses clients réessaient immédiatement et lui envoient encore plus de demandes : le mécanisme de reprise aggrave la panne. Pour éviter cela, il faut limiter l'attente et les nouvelles tentatives, puis traiter les doublons possibles. Timeout, retry et outbox répondent chacun à une partie du problème ; leur choix dépend de l'opération à protéger.

Chaque mécanisme répond à un risque précis

Avant d'ajouter un mécanisme, nommez le risque : une attente trop longue, trop d'appels simultanés, une opération exécutée deux fois ou un message jamais publié. Le tableau associe chaque protection au problème qu'elle traite.

La boîte à outils de la résilience en trois groupes : borner l'attente, réessayer sans nuire, garantir la convergence.
Trois familles d'outils pour tenir une frontière incertaine, avec l'idempotence comme pièce centrale.
FamilleOutilCe qu'il traite
Borner l'attentetimeoutle temps accordé à l'autre
circuit breakerl'acharnement sur une dépendance en panne
bulkheadla saturation qui se propage
Réessayer sans nuireretry bornél'erreur transitoire
backoff et jitterles clients qui réessaient tous ensemble
Fiabiliser le traitement des messagesidempotencele doublon et le rejeu
outboxl'événement perdu entre la base et le bus
inboxl'événement republié en double
file de rebutce qui n'a pas pu être traité

Aucun de ces mécanismes ne décide si une vente peut attendre ou si un affichage ancien reste acceptable. Cette décision métier détermine les réglages : durée d'attente, nombre d'essais et réaction après leur échec.

Limiter l'attente et empêcher la panne de se propager

Un timeout fixe le temps pendant lequel le client attend une réponse. Son expiration ne prouve pas que le serveur a arrêté son travail. Un paiement peut avoir réussi alors que le client reçoit une erreur de délai ; le relancer demande une protection contre le double débit.

Un circuit breaker, ou coupe-circuit, suspend temporairement les appels après des échecs répétés. Il permet ensuite quelques appels de test avant de rétablir le trafic. Les états fermé, ouvert et semi-ouvert décrivent ces phases, détaillées par Martin Fowler.

Un bulkhead isole les ressources utilisées par des traitements différents. Vous pouvez, par exemple, réserver un nombre limité de tâches simultanées à la messagerie pour qu'un blocage du fournisseur ne consomme pas toutes les capacités de l'application. Le nom vient des cloisons étanches des navires.

Espacer et limiter les nouvelles tentatives

Le retry traite les erreurs transitoires, à trois conditions :

  • un nombre de tentatives borné ;
  • un backoff, idéalement exponentiel : l'attente grandit à chaque échec ;
  • un peu de jitter, une part de hasard dans cette attente, pour que les clients ne réessaient pas tous au même instant.

Si tous les clients attendent exactement la même durée, ils risquent de recommencer ensemble. Le jitter ajoute une variation aléatoire pour étaler leurs appels. Les simulations publiées par Marc Brooker chez AWS en 2015 montrent l'intérêt de cette variation par rapport à un délai croissant sans hasard.

Surtout, ne réessayez que les erreurs rejouables. Réessayer une requête malformée ne la rendra pas valide, et ajoutera du bruit à la panne.

Conserver les messages et reconnaître les doublons

L'idempotence permet de répéter une demande sans multiplier son effet métier. Elle peut être naturelle, comme fixer une valeur, ou obtenue en reconnaissant une demande déjà traitée. Elle est particulièrement utile lorsqu'une nouvelle tentative suit une réponse perdue.

Une clé stable peut identifier la demande ; un enregistrement fiable permet de retrouver son résultat. Il faut également empêcher deux traitements simultanés d'exécuter la même demande. Cette protection traite les doublons, tandis qu'un numéro de version ou un transport ordonné traite les messages reçus dans le mauvais ordre.

Une outbox enregistre dans la même transaction le changement métier et le message à publier. Si la transaction réussit, les deux sont conservés. Un relais récupère ensuite les messages en attente pour les envoyer, comme le décrit le pattern Transactional outbox.

Cette organisation évite le cas où les données sont modifiées puis où le processus s'arrête avant d'enregistrer le message. Le relais doit encore être surveillé et reprendre les envois qui échouent.

Le relais peut envoyer deux fois le même événement si la confirmation du premier envoi est perdue. Le destinataire doit donc reconnaître les doublons. Une inbox conserve les identifiants des messages traités, avec des garanties d'enregistrement cohérentes avec l'effet métier. D'autres traitements peuvent être naturellement idempotents.

Un effet métier unique dépend ainsi du traitement complet, pas seulement du transport du message. Tyler Treat explique cette distinction à propos des garanties de livraison.

Une file de rebut, ou dead letter queue (DLQ), conserve les messages qui n'ont pas pu être traités. Elle permet aux autres de continuer lorsque c'est acceptable. Il faut une alerte, un responsable et une procédure de correction ou de reprise ; déplacer le message ne répare pas son échec.

Prévoir la réponse à l’utilisateur et la reprise

Pour chaque dépendance, précisez ce que l'application fait si elle ne peut plus obtenir la réponse attendue. L'arbitrage de CAP aide à raisonner sur les pertes de communication ; d'autres retards peuvent aussi apparaître dans un monolithe, par exemple avec une tâche de fond.

Le besoin de protection dépend du mécanisme réel. Un appel local dans une transaction, une file asynchrone et une requête réseau ne présentent pas les mêmes pannes. Après avoir choisi les responsabilités des modules, vérifiez les conditions dans lesquelles leurs échanges peuvent échouer.

La méthode tient en quatre partis pris :

  • rendre les compromis visibles plutôt que les masquer : un mode dégradé que personne n'a écrit se découvre en production ;
  • n'exiger la cohérence forte que là où elle protège un invariant critique (une règle métier qui doit toujours tenir), jamais par confort ;
  • accepter ailleurs une convergence contrôlée : cohérence à terme assumée, réconciliation, compensation ;
  • documenter les comportements de dégradation et de reprise, pour qu'ils soient choisis et non subis.
À chaque frontière, une checklist : source de vérité, invariant immédiat, comportement si l'autre ne répond pas, vue de l'utilisateur, détection, réconciliation, compensation, idempotence, traçabilité.
La même grille de questions, à poser devant chaque frontière de communication.

En pratique, ces partis pris donnent une grille de questions à poser avant de franchir une frontière :

  • quelle est la source de vérité ?
  • quel invariant doit être vrai immédiatement ?
  • que se passe-t-il si le composant distant ne répond pas ?
  • l'opération peut-elle être différée sans casser le métier ?
  • que voit l'utilisateur pendant le délai ?
  • comment un désalignement se détecte-t-il, et comment se réconcilie-t-il ?
  • faut-il compenser, et par quelle action métier ?
  • l'opération est-elle idempotente ?
  • existe-t-il une trace corrélée pour comprendre, après coup, ce qui s'est passé ?

Je n'ajouterais pas tous ces mécanismes par défaut. Un appel local simple peut suffire là où une file et ses reprises multiplieraient les états à surveiller. Choisissez chaque protection à partir d'une panne identifiée et vérifiez qu'elle produit bien le comportement prévu.

Sources

Explorer

Sur le même sujet

Choisis dans l’index d’après les étiquettes de cet article — les plus proches d’abord, hors de sa série. Rien n’est écrit à la main : un article publié demain avec les mêmes étiquettes y prendra place.

Laisser un commentaire

Elle n'est jamais affichée ni publiée. Elle sert à regrouper vos commentaires et, si vous vous inscrivez un jour avec elle, à vous les rendre.

Votre commentaire sera relu avant d'être publié.