Idempotencia API: ako opakovať požiadavku bez druhej objednávky

Timeout nepotvrdzuje neúspech. Návrh idempotentnej operácie, databázová ochrana pred súbehom a testy, ktoré odhalia duplicitné zápisy.

Sieťové káble zapojené do serverového vybavenia

Timeout necháva výsledok otvorený

E-shop pošle objednávku do ERP. ERP ju zapíše, ale spojenie sa preruší skôr, ako odpoveď dorazí späť. E-shop vidí timeout a požiadavku zopakuje. Ak druhý pokus vytvorí ďalšiu objednávku, obsluha musí rozhodnúť, ktorú zrušiť, a preveriť rezerváciu skladu či následnú faktúru. Toto je ilustračný scenár integrácie; samotné opakovanie prenosu ho nevyrieši.

Timeout znamená, že volajúci nedostal výsledok v určenom čase. Nepotvrdzuje, či vzdialený systém operáciu vykonal. V rozhraní preto potrebujete aj stav „výsledok zatiaľ nepoznáme“. Používateľ môže vidieť požiadavku čakajúcu na potvrdenie a technický proces môže zisťovať jej výsledok. Zobraziť okamžite definitívne zlyhanie a ponúknuť vytvorenie novej objednávky by mohlo vyvolať ďalší obchodný zámer.

Idempotentná operácia umožňuje zopakovať ten istý zámer bez ďalšieho obchodného účinku v rámci dohodnutého kontraktu. Treba určiť, čo presne považujete za rovnakú operáciu, ako dlho si ju server pamätá a ako sa správa pri súbehu. Bez týchto pravidiel je hlavička s kľúčom iba text navyše.

HTTP metóda a obchodný účinok

RFC 9110 rozlišuje idempotenciu podľa zamýšľaného účinku požiadavky. PUT, DELETE a bezpečné metódy majú túto vlastnosť v definovanej HTTP sémantike. Odpovede pritom nemusia byť identické: druhé vymazanie môže vrátiť iný stav, hoci výsledný zdroj zostane odstránený. Opakované zápisy do prevádzkového logu tiež samy osebe túto vlastnosť neporušujú.

Pri POST /orders však nemôžete predpokladať, že druhé volanie iba potvrdí prvé. Vlastné API môže zaviesť idempotenciu aj pre POST, musí ju však implementovať a zdokumentovať. Zmena názvu metódy neopraví kód, ktorý pri každom pokuse znovu odošle faktúru alebo spustí rezerváciu. Pri návrhu vypíšte všetky vedľajšie účinky vrátane tých, ktoré vykonáva ďalšia služba.

Kľúč reprezentuje zámer, nie obsah formulára

V navrhovanom kontrakte klient vytvorí náhodný identifikátor pri začatí jednej objednávky a uloží ho spolu s lokálnou úlohou. Každý ďalší pokus tejto úlohy použije rovnaký kľúč. Keď zákazník naozaj objedná ďalší rovnaký produkt, vznikne nový kľúč, aj keď je telo požiadavky totožné. Samotný hash JSON dokumentu by tieto dva zámery nedokázal odlíšiť.

Server môže kľúč ukladať v rozsahu účtu a typu operácie: tenant_id, operation, idempotency_key. Dva účty si tak neblokujú požiadavky rovnakým identifikátorom. Overenie používateľa a jeho oprávnení musí prebehnúť aj pri opakovaní. Znalosť kľúča nesmie nahrádzať autorizáciu ani umožniť získať výsledok patriaci inému účtu.

S kľúčom uložte aj odtlačok normalizovaného vstupu. Vlastné pravidlá normalizácie musia jednoznačne riešiť implicitné hodnoty, poradie polí a reprezentáciu čísel. Ak klient použije starý kľúč s inou sumou či adresou, server v tomto návrhu vráti konflikt a nič nezmení. Klient nesmie túto chybu automaticky „opraviť“ novým kľúčom; najprv treba rozhodnúť, či vzniká nový zámer.

Lokálny zápis a výsledok patria do jednej transakcie

Pre operáciu, ktorej všetky účinky zostávajú v jednej databáze, možno spojiť rezerváciu kľúča, vytvorenie objednávky a uloženie odpovede v jednej transakcii. Nasledujúci pseudokód je návrh kontraktu, nie hotová implementácia. Predpokladá databázové unikátne obmedzenie nad rozsahom kľúča a postup pre čítanie výsledku po konflikte podľa použitej úrovne izolácie.

Databázová ochrana je dôležitá aj pri viacerých serveroch. Kontrola „najprv SELECT, potom INSERT“ bez unikátneho obmedzenia umožní dvom procesom súčasne usúdiť, že kľúč ešte neexistuje. PostgreSQL vie unikátnosť vynucovať v databáze. Aplikácia potom musí obslúžiť konflikt, čakanie na konkurenčnú transakciu aj prípadný rollback; nemá ho premeniť na druhé vytvorenie objednávky.

Zjednodušený model drží transakciu iba počas krátkej lokálnej práce. Ak by sa v jej strede volalo pomalé cudzie API, databáza by držala zámky a zároveň by nedokázala vrátiť už vykonaný vzdialený účinok. Taký proces potrebuje samostatný stavový model, stabilný identifikátor aj na vzdialenej strane a postup overenia neznámeho výsledku.

Ilustračný kontrakt pre účinky v jednej databáze; claim musí používať databázovú unikátnosť.pseudocode
authorize(account, create_order)
input = validate_and_normalize(request)
key = require_idempotency_key(request)

transaction:
  claimed = claim_unique(account.id, 'create_order', key, hash(input))
  if not claimed:
    previous = load_committed_result(account.id, 'create_order', key)
    require_same_payload(previous, hash(input))
    return previous.status, previous.body

  order = insert_order(input)
  save_result(account.id, 'create_order', key, status=201, body={ order_id: order.id })
commit

return 201, { order_id: order.id }

Súbeh a odpoveď musia byť súčasťou kontraktu

Pri dvoch súčasných pokusoch jeden proces získa právo vykonať operáciu a druhý čaká alebo dostane dohodnutý stav „spracúva sa“. Čakanie musí mať limit. Ak druhému pokusu uplynie čas, zachová pôvodný kľúč a neskôr zisťuje výsledok. Pri asynchrónnej operácii sa môže vracať identifikátor úlohy a samostatné rozhranie na jej stav.

Príjemca potrebuje použiteľnú odpoveď aj po stratenom potvrdení. V ilustračnom modeli sa opäť vráti identifikátor pôvodnej objednávky a uložený stav odpovede. Treba rozhodnúť, či vraciate pôvodnú reprezentáciu, alebo odkaz na aktuálny zdroj; obe možnosti majú iné správanie po ďalších zmenách. Neukladajte zbytočne citlivý obsah, ktorý na zopakovanie výsledku netreba.

Retry potrebuje časový rozpočet aj koniec

Politiku opakovania nastavte podľa kontraktu poskytovateľa. Vybrané prechodné chyby či obmedzenie frekvencie môžu pripúšťať ďalší pokus; chyba oprávnení alebo neplatný vstup spravidla vyžadujú zásah. Rovnaký HTTP status môže mať pri rôznych API odlišný význam. Retry má zachovať identitu operácie, rešpektovať podporovaný Retry-After a mať celkový časový aj početný limit.

AWS opisuje exponenciálny odstup s náhodnou zložkou ako spôsob rozloženia opakovaných pokusov. Pri návrhu zároveň overte, či už opakovanie nevykonáva SDK, proxy alebo nadradený worker. Viaceré vrstvy môžu násobiť zaťaženie. Pre túto integráciu by preto jeden vlastník riadil pokusy a po vyčerpaní rozpočtu by úlohu odovzdal na kontrolu s dohľadateľným výsledkom.

Pamäť kľúčov má konečnú životnosť. Stripe napríklad dokumentuje možnosť odstrániť kľúče po najmenej 24 hodinách; ich opätovné použitie po odstránení môže začať novú požiadavku. Túto hodnotu nemožno prevziať ako univerzálne pravidlo. Vlastná retencia musí pokryť oneskorené pokusy, obnovu po výpadku aj ručné opravy. Pri obchodne významnom zázname môže pomôcť trvalý externý identifikátor nezávislý od dočasného kľúča.

Testujte miesta, kde sa stráca istota

Test „dva rovnaké requesty za sebou“ overí len najľahší prípad. Užitočná sada cielene preruší spojenie po commite, spustí súbeh na rôznych inštanciách a ukončí proces pred dokončením transakcie. Pri každom teste overte počet obchodných záznamov, uložený výsledok a správanie ďalšieho pokusu. Samotný úspešný HTTP status nepreukazuje, že nevznikla duplicitná rezervácia.

Do prevádzkového záznamu patria identifikátor operácie, číslo pokusu a výsledný stav, podľa potreby aj korelačný identifikátor. Rozlišujte dokončenú, čakajúcu a nevyjasnenú operáciu. Tím potom dokáže opraviť konkrétny prenos bez opakovaného spúšťania celého exportu a bez hľadania objednávky podľa nejednoznačného mena zákazníka.

  • Po strate odpovede nasledujúci pokus vráti rovnaký identifikátor objednávky.
  • Súbežné požiadavky na dvoch serveroch vytvoria iba jeden obchodný záznam.
  • Rovnaký kľúč s odlišným normalizovaným vstupom skončí dohodnutým konfliktom.
  • Rollback rezervácie kľúča aj objednávky umožní bezpečný ďalší pokus.
  • Test po uplynutí retencie overí zdokumentované správanie vrátane oneskoreného prenosu.
  • Opakovanie pod iným účtom nevráti cudziu odpoveď a každé volanie overí oprávnenia.

Zdroje a dokumentácia

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

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

Súvisiaca realizácia: FaxCopy a.s.

Máte proces,
ktorý potrebuje zmenu?

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

Prebrať váš projekt