← Zpět · 15. července 2026 · 4 min čtení

Jak nerozbít produkci, když se zdroj dat mění sám

Patch-to-production pipeline: externí data validovaná kontraktem v CI, atomický swap symlinku a rollback bez re-extrakce. Vzor pro data mimo tvou kontrolu.

Máš aplikaci, která žije z dat, jež nevlastníš. Cenový feed, externí API se schématem, které se občas změní bez varování, katalog třetí strany. Dodavatel vydá novou verzi, kdy se mu zachce - tvůj release cyklus ho nezajímá. A ty potřebuješ ta data dostat do produkce bez výpadku a bez rizika, že nasadíš něco rozbitého.

Přesně tenhle problém řeším v exile2exile, open-source nástroji pro Path of Exile 2. Herní patch znamená nová data: strom pasivních dovedností, itemy, gemy, tisíce ikon. Všechno se extrahuje přímo z oficiálního patch serveru a aplikace nad nimi staví. Hra se patchuje klidně dvakrát týdně, takže "ručně to nasadím, až budu u počítače" nepřipadá v úvahu. PoE2 je tu ale jen konkrétní příklad - vzor, který popíšu, sedí na cokoliv, co "vydává patche" mimo tvou kontrolu.

Proč nestačí cron a přepsat soubory

Naivní řešení se nabízí samo: cron jednou za hodinu stáhne nová data a přepíše ty stará na disku. Jenže tím si koleduješ hned o tři průšvihy najednou:

  • Half-switched state. Přepisuješ tisíce souborů a mezitím chodí requesty. Uživatel dostane strom z nové verze a ikony ze staré. U velkých dat trvá přepis minuty, ne milisekundy.
  • Rozbitá data jdou rovnou do produkce. Dodavatel změnil strukturu, přejmenoval pole, půlka ikon chybí? Zjistíš to z error logů, až ti to uživatelé rozbijou.
  • Není cesta zpět. Stará data jsi právě přepsal. Rollback znamená znovu stáhnout a znovu zpracovat starou verzi - pokud ji dodavatel vůbec ještě nabízí.

Pipeline, která tohle řeší, má pět částí: detekci, izolovanou extrakci, kontrakt v CI, atomický swap a defaultní failure mode "žádná změna".

Detekce: watcher, ne webhook

GGG (vydavatel PoE2) žádné webhooky neposílá, takže zbývá polling: příkaz poe2:watch-patch se každých 5 minut zeptá patch serveru na aktuální verzi. Nová verze spustí dvě věci: notifikaci (Discord + webhooky pro odběratele) a extrakční job.

U cenového feedu nebo API to bude vypadat stejně - když dodavatel neumí push, polluj a porovnávej verzi nebo checksum. Důležité je, že detekce jen spouští pipeline, nikdy sama nemění živá data.

Izolovaná extrakce: nová verze vedle, ne přes

Extrakce zapíše novou verzi do releases/<version> - vedle živých dat, ne přes ně. Produkce celou dobu běží na staré verzi a o probíhající extrakci neví.

Samotná extrakce z herního formátu GGPK žije v poe2-toolkit, oddělené vrstvě MIT npm balíčků. Toolkit je čistě kód, žádná data - umí z patch serveru vytáhnout strom, itemy a ikony, ale nic neví o tom, jak je aplikace používá. Tohle oddělení se vyplácí: extraktor se dá testovat a verzovat samostatně a aplikace řeší jen "mám adresář s daty ve známé struktuře".

Po extrakci aplikace release zabalí do tarballu, spočítá sha256 a nastaguje ho ke stažení.

Kontrakt, ne důvěra

Tady je jádro celého vzoru. Nasadit data jen proto, že se povedlo je stáhnout a rozbalit, je důvěra. Já chci kontrakt: sadu testů, která ověří, že data mají přesně tvar, jaký aplikace očekává.

Aplikace po nastagování release dispatchne GitHub Actions workflow data-contract.yml a předá mu verzi a sha256. CI pak:

  1. stáhne tarball z aplikace a ověří checksum - má jistotu, že testuje přesně ty bajty, které watcher nastagoval, i kdyby mezitím vyšel další patch,
  2. rozbalí ho do checkoutu a pustí Contract suite (tests/Contract) - reálné Pest testy nad reálnými daty: struktura stromu, všechny odkazované ikony existují na disku, import z Path of Building projde end-to-end, seedované plány se načtou,
  3. na zelenou zavolá aktivační endpoint aplikace.

Klíčový detail: CI nikdy nesahá na zdrojová data dodavatele. Neextrahuje nic z GGPK - validuje přesně ten artefakt, který server nastagoval a který půjde do produkce. Build once, promote. Release, který jde live, je bajt po bajtu ten, co prošel testy.

Checksum má v pipeline ještě jednu praktickou roli: je součástí cache klíče, pod kterým si CI ukládá stažený tarball. Toho využívám při ručním spouštění - když workflow dispatchnu se stejnou verzí, ale záměrně jiným checksumem, cache se mine, tarball se stáhne znovu a Contract testy proběhnou čerstvě od nuly. Jednoduchý způsob, jak si vynutit nový běh testů bez čekání na další patch.

A protože kontrakt má dvě strany, běží stejná suite i při každém push do main - tentokrát proti verzi, kterou produkce právě používá. Změna kódu, která rozbije kontrakt s daty, tak neprojde zeleně, stejně jako data, která rozbijí kontrakt s kódem.

GGG patch server          Produkce                    GitHub Actions
      |                      |                              |
      |<-- poll (à 5 min) ---|                              |
      |--- nová verze ------>|                              |
      |                      |-- extrakce do releases/<v>   |
      |                      |   (živá data netknutá)       |
      |                      |-- tarball + sha256           |
      |                      |-- dispatch (verze, sha256) ->|
      |                      |<------ stáhne tarball -------|
      |                      |        ověří checksum        |
      |                      |        Contract suite        |
      |                      |                              |
      |          zelená:     |<--- POST /api/data/activate -|
      |                      |-- atomický swap symlinku     |
      |          červená:    |   žádný swap, jede            
      |                      |   poslední ověřená verze      

Atomický swap: jeden rename

Aplikace čte všechna herní data přes jediný symlink: storage/game-data/current -> releases/<version>. Aktivace nové verze je jeden atomický rename symlinku.

Proč na tom záleží: rename je na POSIX filesystémech atomická operace. Request, který přišel nanosekundu před swapem, dočte celou odpověď ze staré verze; request nanosekundu po něm čte celou z nové. Neexistuje okamžik, kdy by kdokoliv viděl mix obou - ten half-switched state z naivního řešení prostě nemůže nastat. Žádný maintenance mode, žádný restart, žádný výpadek.

Endpoint POST /api/data/activate je chráněný tokenem a idempotentní - opakované volání se stejnou verzí nic nerozbije.

Selhání = žádná změna

Nejcennější vlastnost celé pipeline je její defaultní failure mode. Červené testy? Žádný swap. Tarball se nepodařilo stáhnout? Žádný swap. Server nedostupný, checksum nesedí, workflow spadl v půlce? Žádný swap. Každá cesta selhání končí stejně: produkce jede dál na poslední ověřené verzi.

To je přesný opak cron řešení, kde selhání v půlce přepisu znamená rozbitou produkci. Tady se o nasazení nemusí nikdo aktivně starat - špatná verze se do produkce nedostane, dokud ji někdo (typicky nový patch s opravenými daty) nenahradí zelenou.

Watcher navíc hlídá zaseknuté validace: když staged verze dlouho čeká, re-dispatchne workflow, ale nejvýš jednou za 6 hodin. Bez limitu by nedostupné CI znamenalo dispatch každých 5 minut donekonečna.

Rollback bez re-extrakce

Staré release zůstávají na disku (starší se průběžně promazávají, pár posledních se drží). Rollback je tak stejné jedno volání POST /api/data/activate se starší verzí - jeden rename symlinku, hotovo za milisekundy. Žádné stahování, žádná re-extrakce, žádná závislost na tom, jestli dodavatel starou verzi ještě nabízí.

Když dodavatel vydá vadný patch a opraví ho až za den, přepneš zpátky a den v klidu počkáš.

Kde jinde se to hodí

Vzor stojí na čtyřech nohách a žádná z nich není specifická pro herní data:

  1. verzované release vedle sebe, živá data se nikdy nepřepisují,
  2. kontrakt v testech, který validuje přesný artefakt určený k nasazení,
  3. atomické přepnutí přes symlink (nebo pointer v DB, alias v S3, traffic switch),
  4. selhání nechává poslední ověřenou verzi.

Sedne všude, kde tě externí zdroj může rozbít nezávisle na tvém release cyklu: cenové a kurzovní feedy, katalogy produktů od dodavatelů, ML modely a jejich váhy, geodata, číselníky ze státních registrů, API třetích stran se schématem, které se "nemění" (dokud se nezmění). Pointa je vždycky stejná: nedůvěřuj datům, která jsi nevyrobil. Dej jim kontrakt a nech je ho podepsat v CI, dřív než se dotknou produkce.

Celá implementace je open source: github.com/rajtik76/exile2exile (pipeline, Contract suite, watcher) a github.com/rajtik76/poe2-toolkit (extrakce dat).

Stačí psát část slova · ⌘K otevře hledání odkudkoliv

No results found