Exactly-once er en løgn: design integrationer til dubletter

Tre kopier af den samme beskedbrik, der samles i én enkelt behandlet brik

Et sted i enhver integrationskontrakt står en sætning som "hver ordre overføres nøjagtigt én gang". Det har aldrig nogensinde været sandt. Netværket blev partitioneret midt i en forespørgsel, og afsenderen gentog forsøget; køen genleverede efter et forbrugernedbrud; en lageroperatør trykkede på eksportknappen to gange. Dubletter er ikke en fejltilstand - de er vejret.

Idempotensnøgler, valgt med omhu

Løsningen er gammel og ugalant: hver besked bærer en nøgle, og behandlingen er en no-op, når nøglen allerede er set. De interessante beslutninger ligger alle i valget af nøglen.

  • Naturlige nøgler slår genererede. En ordresynkronisering nøglet på order_id + status overlever, at afsenderen regenererer sin outbox; en tilfældig UUID skabt på afsendelsestidspunktet gør ikke.
  • Afgræns nøglen til effekten, ikke beskeden. Hvis én besked opretter en faktura og booker lagerbeholdning, er det to effekter med to nøgler - ellers efterlader en delvis fejl dig ude af stand til trygt at forsøge nogen af dem igen.
  • Lad nøgler udløbe bevidst. Et dedup-lager, der vokser for evigt, er en tidsindstillet bombe; et der udløber for hurtigt, genindfører dubletter. Vi bruger som standard 3× afsenderens maksimale gentagelseshorisont.

Testen, der betyder noget

Hver integration, vi leverer, består det, vi internt kalder dobbelttryk-testen: afspil hele gårsdagens beskedlog mod dagens system, to gange, og diff den resulterende tilstand. Hvis noget ændrede sig i anden omgang, er pipelinen ikke færdig.

Det er en brutal test, og den fejler konstant under udvikling - på tidsstempler, på "oprettet af"-revisionsfelter, på sekvenstællere. Hver fejl er et sted, hvor en genlevering klokken tre om natten stille og roligt ville have korrumperet data. Langt billigere at fange det under udviklingen end i produktion.