L'exactly-once est un mensonge : concevoir des intégrations pour les doublons

Trois copies du même tuile de message convergeant vers une seule tuile traitée

Quelque part dans chaque contrat d'intégration se trouve une phrase du type « chaque commande sera transmise exactement une fois ». Ce n'a jamais été vrai, pas une seule fois. Le réseau s'est partitionné en plein milieu d'une requête et l'émetteur a relancé ; la file d'attente a redistribué le message après le crash d'un consommateur ; un opérateur d'entrepôt a appuyé deux fois sur le bouton d'export. Les doublons ne sont pas un mode de défaillance - ce sont les intempéries.

Des clés d'idempotence, choisies avec soin

La solution est ancienne et peu glamour : chaque message porte une clé, et le traitement est un no-op quand la clé a déjà été vue. Toutes les décisions intéressantes résident dans le choix de la clé.

  • Les clés naturelles battent les clés générées. Une synchronisation de commande indexée sur order_id + status survit à la régénération de sa boîte d'envoi par l'émetteur ; un UUID aléatoire créé au moment de l'envoi n'y survit pas.
  • Rattachez la clé à l'effet, pas au message. Si un message crée une facture et réserve du stock, ce sont deux effets avec deux clés - sinon, un échec partiel vous empêche de relancer l'un ou l'autre en toute sécurité.
  • Faites expirer les clés délibérément. Un magasin de déduplication qui grossit indéfiniment est une bombe à retardement ; un qui expire trop tôt réintroduit des doublons. Nous appliquons par défaut 3 fois l'horizon maximal de relance de l'émetteur.

Le test qui compte

Chaque intégration que nous livrons passe ce que nous appelons en interne le test du double-tap : rejouer l'intégralité du journal des messages d'hier contre le système d'aujourd'hui, deux fois, et comparer l'état résultant. Si quoi que ce soit a changé lors du deuxième passage, le pipeline n'est pas terminé.

C'est un test brutal et il échoue constamment pendant le développement - sur les horodatages, sur les champs d'audit « créé par », sur les compteurs de séquence. Chaque échec est un endroit où une redistribution à 3 heures du matin aurait discrètement corrompu des données. Il est bien moins coûteux de le détecter en développement qu'en production.