Transactional outbox: keď sa databáza zapíše a správa neodošle

Objednávka môže existovať v databáze, hoci sklad nedostal udalosť. Návrh outboxu, identita správ, hranice potvrdenia a obnova po páde workera.

Sieťový kábel pripojený k ethernetovým portom

Medzi commitom a odoslaním existuje medzera

Ilustračný objednávkový systém najprv uloží objednávku a potom oznámi skladu, že má rezervovať tovar. Ak proces po commite spadne, objednávka zostane uložená, ale správa neodíde. Obrátenie poradia vytvorí iný problém: sklad môže dostať požiadavku, hoci následný zápis objednávky zlyhá. Obe operácie potrebujú jasnú spoločnú hranicu.

Vzor transactional outbox ukladá obchodný záznam aj úmysel odoslať udalosť v jednej lokálnej databázovej transakcii. Samostatný worker odosiela iba potvrdené udalosti. Tento princíp opisuje AWS; konkrétna implementácia závisí od databázy a komunikačnej služby. Lokálna transakcia sama osebe nevracia vzdialený účinok a nedáva celej integrácii automaticky vlastnosť exactly once.

Udalosť dostane identitu pri vzniku

V navrhovanom modeli udalosť nesie vlastné event_id, identifikátor zdrojového systému, klienta, obchodného objektu a verziu jeho zmeny. Pri ďalšom pokuse sa nemení. Príjemca tak dokáže rozlíšiť opätovné doručenie od novej udalosti. CloudEvents používa dvojicu source a id na identifikáciu udalosti v definovanom rozsahu; obchodný identifikátor objednávky môže zostať samostatným údajom.

Do payloadu patrí informácia, ktorú príjemca potrebuje, a verzia formátu. Celý interný databázový riadok by zbytočne spájal cudziu integráciu s privátnou schémou. Pri rozhodovaní medzi údajmi v udalosti a následným čítaním cez API zohľadnite, či príjemca potrebuje stav pri vzniku udalosti alebo najnovší stav objektu.

Zápis do outboxu musí zlyhať spolu s objednávkou

Nasledujúci pseudokód ilustruje iba atómový lokálny zápis. Obe tabuľky predpokladajú rovnakú transakčnú databázu. Ak vloženie udalosti zlyhá, transakcia nesmie potvrdiť samotnú objednávku. API má navyše vlastnú ochranu opakovaného obchodného zámeru; outbox nebráni klientovi vytvoriť druhú objednávku novým requestom.

Worker začne až nad potvrdenými riadkami. Záznam outboxu môže mať stav, počet pokusov, čas ďalšieho pokusu a krátku rezerváciu spracovania. Rezervácia potrebuje vlastníka aj dobu platnosti, aby sa úloha po páde procesu opäť uvoľnila. Sieťové volanie počas dlhej databázovej transakcie by zvyšovalo čas držania zámkov a stále by nevyriešilo stratené potvrdenie od brokeru.

Ilustračná hranica transakcie; odosielanie vykonáva samostatný worker po commite.pseudocode
event_id = new_event_id()

transaction:
  order = insert_order(validated_input)
  insert_outbox(
    event_id=event_id,
    source='orders',
    aggregate_id=order.id,
    aggregate_version=1,
    type='order.created.v1',
    payload={ order_id: order.id }
  )
commit

return order.id

Potvrdenie brokeru a spracovanie príjemcom sa líšia

Worker odošle udalosť, broker ju prijme a spojenie sa preruší pred odpoveďou. Worker nedokáže určiť výsledok a po obnovení pošle tú istú udalosť. Aj pád po úspešnej odpovedi, ale pred označením outboxu ako odoslaného, môže vytvoriť duplicitné doručenie. Návrh preto počíta s opakovaním rovnakej identity.

Príjemca môže uložiť spracovanú identitu spolu s lokálnou rezerváciou skladu v jednej transakcii. Vzdialené vedľajšie účinky potrebujú ďalší kontrakt. Záznam sent v outboxe znamená dohodnuté potvrdenie od transportu, nie automaticky vybavenú objednávku. Ak podnikový proces potrebuje potvrdenie rezervácie, má sledovať samostatný obchodný stav a výslednú udalosť zo skladu.

Poradie určujte pre konkrétny obchodný objekt

Pri udalostiach order.created a order.cancelled môže zmena poradia poškodiť výsledok. Čas vytvorenia správy sám osebe nestačí pri súbehu či paralelnom odosielaní. V ilustračnom modeli má objednávka rastúcu verziu. Príjemca podľa kontraktu spracuje očakávanú verziu, rozpozná starú udalosť alebo pozastaví objekt pri medzere.

Rozhodnite, či chyba jednej objednávky má zastaviť aj ostatné objednávky. Často postačí poradie v rozsahu jedného objektu, no konkrétny proces môže vyžadovať širšiu väzbu. Po presune problémovej udalosti do samostatného zoznamu zostane potrebné určiť, čo sa stane s jej následníkmi. Mechanické odblokovanie celej fronty môže porušiť dohodnuté poradie.

Prevádzka potrebuje vek udalostí a možnosť dohľadania

Počet riadkov vo fronte bez kontextu veľa nepovie. Sledujte vek najstaršej čakajúcej udalosti, chyby podľa príjemcu a čas do obchodného potvrdenia. Krátka veľká dávka môže byť v poriadku, kým jedna stará rezervácia vyžaduje zásah. Retry má obmedzený časový rozpočet, odstup s náhodnou zložkou a rozlíšenie dočasnej chyby od neplatného payloadu.

Pri ručnom opakovaní zachovajte identitu udalosti a dôvod zásahu. Pravidlá retencie musia pokryť obnovu po výpadku aj potrebnú auditnú stopu. Nasledujúce skúšky overia miesta, kde sa výsledok stáva nejasný. Tím pri nich kontroluje objednávku, outbox, transport aj skladový účinok; úspešný log workera by bol len jednou časťou dôkazu.

  • Pád pred commitom neuloží objednávku ani udalosť.
  • Pád po commite ponechá udalosť dostupnú pre ďalší worker.
  • Stratené potvrdenie vyvolá ďalší pokus s rovnakým event_id.
  • Doručenie tej istej udalosti dvakrát nevytvorí druhú lokálnu rezerváciu.
  • Oneskorené zrušenie a medzera vo verziách skončia dohodnutým obchodným stavom.

Zdroje a dokumentácia

Pri implementácii skontrolujte dokumentáciu verzie, ktorú používate.

Od témy ku konkrétnemu riešeniu.

Máte proces,
ktorý potrebuje zmenu?

Začnime tým, ako dnes pracujete. Technológiu vyberieme podľa toho.

Prebrať váš projekt