Otevřené otázky¶
Co ještě není vyřešené. Tahle stránka je tady schválně — dokumentace, která má jen odpovědi, je podezřelá.
Po jedenácti kolech ale platí něco, co jsem nečekal: žádný nevyřešený bod už nezůstal. Všechny čtyři sporné body i všech sedm mezer jsou uzavřené. Co zbývá, jsou účty — místa, kde odpověď neobstála celá.
A jedno z nich je důležitější než všechna ostatní dohromady.
Vyřešeno: M6¶
Uzavřeno v kole 8 (21. 8. 2026)
Když jsi tuhle stránku četl dřív, stálo tu, že M6 je jediná známá díra ve správnosti a že se musí vybrat z jedné ze tří cest. Ukázalo se, že to bylo špatně položené. Nechávám to tu i s tím, jak to dopadlo — je to hezký příklad toho, k čemu ta debata je.
Jak se to jevilo: čítače se ukládají v PostgreSQL a do Redisu se posílají přes frontu. V mezeře mezi zápisem a zpracováním fronty projde staré naplnění cache kontrolou, která by ho mělo zachytit.
Jak to je doopravdy: rozebralo se všech devět možných pořadí, ve kterých ty operace můžou proběhnout. Sedm z nich je v pořádku — buď se položka při dopadu fronty sama zneplatní, nebo se vůbec nezapíše.
Nebezpečné je jedno jediné, a je zákeřné takhle:
- Dotaz do databáze si vezme svůj pohled na data před změnou.
- Změna se zapíše. Čítač se zvedne.
- Fronta doběhne, Redis o změně ví.
- Teprve teď ten pomalý dotaz doběhne a vrátí stará data.
- Kód k nim doplní čítač — a přečte ten nový.
- Do cache jde stará hodnota označená jako aktuální.
Taková položka projde všemi budoucími kontrolami. Fronta ji neopraví, protože svoji práci už odvedla. Opraví ji až vypršení platnosti nebo další změna toho samého údaje.
Kde jsem se spletl já
Myslel jsem si, že tohle nastane jenom při čtení z databázové repliky, která se opozdila. Není to pravda — stačí pomalý dotaz na hlavní databázi. Takže se to může stávat i tam, kde žádné repliky nejsou, a je to podstatně pravděpodobnější, než jsem odhadoval.
Oprava¶
Jedna věta, a je povinná:
Data i čítače se musí přečíst v jednom okamžiku, z jednoho pohledu na databázi. A do cache se zapisují ty čítače z databáze — ne ty, co se dočtou z Redisu potom.
Z toho plyne příjemný důsledek: když databáze vrátí novou hodnotu, ale Redis o změně ještě neví, zápis do cache se prostě neprovede. Odpověď se uživateli pošle normálně — jenom se neuloží. Použitelná odpověď a odpověď, kterou si smím schovat, jsou dvě různé věci.
Zbylá mezera mezi zápisem a frontou pak přestává být dírou a stává se z ní vlastnost, kterou lze změřit a napsat do dokumentace: „změna je vidět do X sekund".
Dvě věci navrch¶
Redaktor musí vidět svoji změnu hned. Po uložení dostane značku a dokud Redis nedožene, čte se mu mimo cache. Ostatní návštěvníci žádnou takovou záruku nepotřebují.
Musí být poznat, že se fronta zasekla. A ne tak, že „proces běží" — proces může běžet a přitom stát. Nejostřejší věta z celého kola:
Worker nesmí označit, že je hotový do události 150, když událost 143 selhala a 144–150 prošly.
Kolo 8. Predikce vyšla 3 z 5, s jednou čistou prohrou.
Vyřešeno: M7 — kdo vlastní hodnotu¶
Redaktor opraví popis produktu. V noci přijde import z ERP a přepíše ho zpátky. Ráno je jeho práce pryč.
Otázka „kdo má přednost" je špatně položená. Vede k žebříčku typu redaktor > ERP > AI, který se rozpadne hned: u ceníkové ceny má mít přednost ERP, u marketingového názvu redaktor, u alt textu možná AI. A u fotogalerie nejde o přednost vůbec — jde o slučování a pořadí.
Správná otázka zní: kdo smí pro tenhle konkrétní údaj navrhovat změny, kdo je jeho autoritou, a co se stane, když si dva vstupy odporují.
Jak to funguje¶
Autorita je pravidlo zapsané ve schématu, ne vlastnost toho, kdo píše. A platí pro adresu jemnější než „pole":
(produkt:8842, price.amount) → autorita je ERP
(produkt:8842, description, cs-CZ) → ERP dodá, redaktor smí přebít
(produkt:8842, gallery[foto-17].popisek) → vlastní pravidlo
Pravidel je osm. Kromě samozřejmých („tohle vlastní ERP", „tohohle se import nedotkne") i takové, které mě nenapadly:
- poslední přijatý, ne poslední doručený — příchod importu není obchodní rozhodnutí
- override s platností do data — ruční výjimka nemusí být navěky
- podmíněné — ERP je autorita, dokud produkt někdo neoznačí jako marketingově spravovaný
Co se uloží¶
Tři oddělené věci, a to oddělení je celý trik:
| co | k čemu |
|---|---|
| co zdroj tvrdí | neměnný záznam „ERP tehdy poslal tohle" |
| jak se rozhodlo | která hodnota vyhrála a podle kterého pravidla |
| výsledek | hotová hodnota, ze které se staví web |
Když v noci přijde import a redaktorův override pořád platí, ERP hodnota se uloží, ale nic se nezmění. Nevznikne nová verze produktu, cache se nezneplatní, web se nepřepočítá. Jenom se někde poznamená, že se ERP a web rozešly — což je informace, kterou dneska nikdo nemá.
Dvě věci, které tam praskají nejdřív
Import 12 000 produktů nesmí vyrobit 12 000 nových verzí. Verze vzniká jenom tam, kde se výsledek opravdu změnil. Bez toho ti databáze nabobtná za měsíc.
„Nic neposlal", „poslal prázdno", „poslal null" a „řekl smaž" musí být čtyři rozlišitelné stavy. Když kterékoli dva splynou, je to tichá ztráta dat.
Kde jsem se spletl¶
Myslel jsem si, že je to volba mezi dvěma zly: buď zdrojovou hodnotu zahodit (rychlé, ale ztratíš informaci), nebo skládat výslednou hodnotu až při čtení (čisté, ale rozbije to řazení a filtrování).
Je to falešné dilema. Skládá se to při zápisu. Do databáze se uloží i zdroj, i rozhodnutí, i hotový výsledek — a dotazy čtou ten hotový výsledek, takže indexy fungují normálně.
Věta, kterou to celé uzavírá¶
Nejtěžší není databázový model. Nejtěžší je udělat vlastnictví viditelné v redakčním UI. Pokud uživatel u každé hodnoty nepozná „odkud je, proč právě tahle vyhrála a co se stane při příštím importu", systém je z provozního hlediska vadný.
Kolo 9. Predikce 5 z 5 — podruhé za celou debatu.
Vyřešeno: M2 — migrace obsahu¶
80 000 produktů v provozu a mění se schéma. Ne přidání sloupce — volný text se stane číselníkem, jedno pole se rozdělí na dvě, komponenta změní to, co potřebuje, a existuje jí 4 000 instancí.
Nejdřív ta nepříjemná otázka¶
Verze produktu je neměnná. To je celý základ toho, proč jde vrátit release zpátky. Jenže ta verze vznikla pod starým schématem a čte se pod novým. Co s tím?
První věta odpovědi mi vyvrátila předpoklad, který jsem měl schovaný v otázce:
Starou verzi nesmíte číst pod aktuálním schématem. To je chybný předpoklad. Verze je čitelná jen spolu s tím schématem, pod kterým vznikla.
Takže: historie se nemigruje vůbec. Zůstane, jaká byla, a rozumí se jí jenom s jejím vlastním schématem.
Migruje se to, co je živé:
- co je právě na webu
- rozdělaná práce redaktorů
- otevřené náhledy
- pár posledních releasů, na které by šlo přepnout zpátky
Zbytek je archiv. A to je v pořádku.
Jak to poběží¶
Ne tak, že se v pátek večer vypne web. Takhle:
- Zmrazí se výběr — tohle se bude migrovat, nic jiného.
- Vedle stávajících dat se staví nová sada. Web mezitím normálně jede nad starou.
- Co se během toho změnilo, se dohání.
- Na chviličku se zastaví zápisy a přepne se jeden ukazatel.
A protože se stará sada nepřepisovala, jde přepnout i zpátky. Návrat není zpětná migrace — je to přepnutí toho samého ukazatele na druhou stranu.
Věc, která mě nenapadla a je důležitá
Během té přípravy se nesmí sahat na cache. Kdyby se migrace tvářila jako 80 000 změn obsahu, spustila by invalidační bouři — přestože návštěvník pořád vidí starý web a nic se pro něj nemění.
Cache se posune až při tom přepnutí, jednou.
Ne všechny migrace jsou stejné¶
Tady jsem to měl moc jednoduché. Myslel jsem, že jsou dvě třídy — mechanické a hádající. Jsou čtyři:
| třída | příklad | co s tím |
|---|---|---|
| beze ztráty | celé číslo → větší celé číslo | spustí se |
| se ztrátou, ale jednoznačná | text → číselník s úplnou tabulkou | spustí se, ztráta se zapíše |
| nejistá | „modrozelená" — do které položky číselníku? | návrh ke schválení |
| vyžaduje rozhodnutí | pole bylo společné pro všechny jazyky, teď je pro každý zvlášť | produktová otázka, ne technická |
Ta poslední je zákeřná, protože technicky je triviální: hodnotu nakopíruju do všech jazyků a je hotovo. Jenže znamená to, že mám deset schválených překladů? Nebo že má devět jazyků čekat na překladatele? To není transformace dat, to je rozhodnutí.
Co všechno se změnou schématu rozbije¶
Tady byla odpověď nejnepříjemnější. Čekal jsem seznam. Přišlo pět kategorií a kolem osmdesáti položek — a nejsou to výmysly:
rozdělaná práce a její merge base · uložené dotazy · filtry a řazení · facety a našeptávač · instance komponent a jejich vazby na data · šablony e-mailů a feedů · strukturovaná data pro Google · mapování importu z ERP · webhooky · SDK · prompty, které se odkazují na názvy polí · oprávnění · pravidla autority · monitoring · testovací data
A tady je ten háček
Úplnost toho seznamu je podmíněná: pokud názvy polí unikají ven do cizích integrací a analytiky, musí systém evidovat, kdo je konzumuje — jinak úplný výčet nezískáte.
Uvnitř systému je to udělatelné. Venku — něčí BI dashboard, cizí aplikace, tabulka v Excelu — je to zbožné přání. A ven to uniká vždycky.
Je to počtvrté, co se objevilo správné kritérium, které nejde provést (účet DD). Viz níž.
Staré releasy: tři třídy, ne jedna¶
Napadlo mě, že staré releasy prostě po čase přestanou fungovat. Správně, ale hrubě — jsou tři úrovně a stojí za to je rozlišit:
| co to znamená | |
|---|---|
| auditovatelný | dohledám, co v něm bylo. Nespustím ho. |
| reprodukovatelný | umím z něj znovu vyrobit výstup |
| okamžitě nasaditelný | přepnu na něj teď hned |
Sto releasů neznamená stonásobek dat. Znamená to až sto kompatibilitních konfigurací. Největší cena není disk; je to bezpečnostní údržba starých runtime a rendererů.
Držet posledních pár plně spustitelných a starší jako archiv je rozumná odpověď. Požadovat, aby šel kdykoli spustit release z roku 2029, je nesmírně drahé a skoro vždycky zbytečné.
Kolo 10. Predikce 4,5 z 5 — minul jsem strategii: čekal jsem postupný přechod se zdvojeným zápisem, Codex ho výslovně odmítl jako to, co se nejčastěji zvrhne.
Vyřešeno: M4 — co smí agent¶
Agent má „aktualizovat SEO popisky" a sáhne na 12 000 entit. Nebo má „opravit překlepy v češtině" a přepíše pole vlastněné ERP. Nebo běží v noci bez dohledu a ráno je potřeba zjistit, co provedl.
Nepotřebuje vlastní systém oprávnění. Potřebuje ten samý — jenže ten je postavený pro lidi a končí u otázky „smí změnit tohle pole?". Pro agenta chybí čtyři vrstvy:
| vrstva | otázka |
|---|---|
| mandát | co má v tomhle běhu udělat |
| rozpočet dopadu | kolika entit, polí a jazyků se smí dotknout |
| procesní omezení | kam smí zapisovat a kdo to musí schválit |
| pojistky | kdy se to samo zastaví |
Nejlepší myšlenka celého kola¶
Před velkým během se udělá zkušební průchod, který nic nezapíše, jen spočítá dopad: tolik entit, tolik polí, takové rozložení podle jazyků. Tím se zamkne mandát. A pak platí tohle:
Alarmy mají být relativní k deklarovanému plánu. Čtyři tisíce změn nejsou anomálie, pokud byl schválen limit 5 000. Jsou katastrofa, pokud agent deklaroval 200.
To je celý problém objemu vyřešený líp než jakýmkoli pevným limitem. Nejde o to, kolik toho agent udělá. Jde o to, jestli to je tolik, kolik řekl, že udělá.
Kde jsem se spletl¶
Myslel jsem si, že stačí kontrolovat celek: deset tisíc zápisů projde jednotlivě a každý po právu, nebezpečný je až souhrn — tak ať se kontroluje souhrn.
Druhá půlka platí, první z toho neplyne:
Kontrola jen na začátku dávky je bezpečnostní díra. Během běhu se můžou změnit oprávnění, schéma, autorita pole nebo stav entity.
Kontroluje se na třech úrovních zároveň: každá jednotlivá operace, celý běh, a průběžně narůstající dopad.
Za koho agent jedná¶
Ne za člověka, který ho spustil. To by z auditu udělalo lež — „změnil Jan Novák" je nepravda, když hodnotu vyrobil agent.
Agent je samostatný subjekt a má dva režimy:
Když ho spustí uživatel, dostane průnik svých schopností, práv toho uživatele a mandátu. Nikdy víc, než má ten člověk. To brání triku, kdy nevinně vypadající zadání využije široká systémová práva agenta.
Noční údržba může legitimně potřebovat víc práv než ten, kdo ji nastavil. Ale pak to není jednání za uživatele, je to samostatná služba se samostatným schválením — a nesmí jít spustit obyčejným tlačítkem.
Čtení je samostatný problém¶
Napadlo mě, že když agent smí číst hodně, může vygenerovaným textem vynést ven to, co by jinak nepřečetl. Odpověď je ostřejší:
Pokud uživatel nemá přístup k datům, agent je nesmí číst jeho jménem. Třetí možnost — použít široký servisní účet a výsledek ukázat neprivilegovanému uživateli — je klasický únik oprávnění.
A dvě věci, které mě nenapadly vůbec:
Co z dat vznikne, dědí jejich oprávnění. Shrnutí neveřejného obsahu je pořád neveřejné. Totéž platí pro embeddingy a klasifikace.
Obsah je nedůvěryhodný vstup. Když někdo do popisu produktu napíše „ignoruj předchozí instrukce", je to text, ne příkaz:
Instrukce nalezená v datech je data, ne příkaz.
Když se to pokazí¶
Agent udělal 4 000 změn a v 500 se spletl.
Zastavení musí mít fencing — jinak UI ukáže „zastaveno" a rozběhnuté workery vesele commitují dál.
Návrat nesmí být slepý. Když po agentovi hodnotu upravil člověk nebo ERP, přepsání starou hodnotou by zničilo novější práci. Vrátit se smí jenom tam, kde je současná verze pořád ta agentova. Zbytek je konflikt k rozhodnutí.
Kolo 11. Predikce 4,5 z 5.
Účty, které někdo musí zaplatit¶
Menší věci, kde odpověď z debaty neobstála celá. Označené písmeny, detail v syntézách jednotlivých kol.
Rodina „slib místo mechanismu" — H, S, Y, DD a GG¶
Pětkrát se stalo, že vzniklo správné kritérium, které nejde provést:
- H: dotaz má mít spočítanou cenu. Jenže cena roste s daty, aniž by se dotaz změnil. Co se stane s publikovanou stránkou, která rozpočet přeroste?
-
S: „každá povolená kombinace musí být publikovatelná." Kombinací je varianta × hustota × tón × ořez × sousední komponenty × šířka okna × délka textu. To se neprojde.
-
Y: když se zasekne fronta, systém má přestat věřit cache a číst všechno z databáze. Jenže o odstavec dál je přiznané, že pokud databáze takovou zátěž neunese, není to dostupný fallback.
-
DD: aby šlo vyjmenovat, co se rozbije změnou schématu, musí systém evidovat všechny, kdo jeho data konzumují. Uvnitř ano. Venku ne.
-
GG: hromadnou změnu 12 000 položek nikdo neprojde ručně, takže se má zkontrolovat reprezentativní vzorek. Jenže vzorek dá vzorek, ne kontrolu — což je táž námitka jako S, jen otočená.
Podle pravidel debaty se to nemá opravovat po jednom — je to jedna třída, a ta se musí pojmenovat. Po pátém výskytu je to jednoznačně nejsilnější věc z celé debaty.
Rodina „práce, která musí být hotová předem" — U, Z a II¶
U: prostor vizuálních možností musí někdo vytvořit a otestovat dřív, než AI udělá první stránku.
Z: kolo 8 vypsalo dvacet způsobů, jak se dá invalidace naprogramovat špatně, a uzavřelo to tím, že jsou potřeba testy s bariérami přesně v pěti bodech běhu. To není poznámka na konec — je to testovací aparát velikostí srovnatelný s tím mechanismem, a jeho cena nezazněla.
II: oprávnění musí být součástí klíče cache, jinak jeden uživatel uvidí data druhého. Klíč ale nesmí být ani hrubý (únik), ani jemný (cache přestane fungovat). Že mezi tím vede použitelná cesta, je tvrzení — a chyba v té jediné funkci se projeví jako tichý únik dat, který vypadá jako obyčejný cache hit.
Všechno tři je stejný tvar: podmínka důvěryhodnosti, která se platí předem.
Rodina „nevyčíslená cena" — R, W a AA¶
Kolik verzí si systém může dovolit držet? Kolik současných preview? Jak dlouho se uchovávají staré releasy?
A AA z kola 9: hodnota se skládá přes čtyři reprezentace za sebou, každá odvozená z předchozí. Že se můžou rozejít, je v seznamu selhání a předepisují se na to pravidelné kontroly — jenže kontrola nad 80 000 entitami krát jazyky krát poli je dávková úloha s vlastní cenou.
Návrh na všech třech staví a ani u jednoho nespočítal objem ani retenci.
Rodina „hranice releasu versus jazyky" — P a T¶
Nejzajímavější z otevřených věcí, protože je to skutečný rozpor v jádru, ne opomenutí.
Jazyky se mají publikovat nezávisle. Fallback ale hranici releasu prostupuje: vydáš češtinu a tím se změní německá stránka, i když němčinu nikdo nepublikoval.
Sourozenec: německý text je zhruba o 30 % delší. Přes fallback může stránka zobrazit text v jazyce, pro který ta vizuální varianta nikdy nebyla ověřená.
A kolo 9 to potvrdilo potřetí, z úplně jiné strany: změna pravidla fallbacku změní výslednou hodnotu u tisíců údajů, aniž by se u kteréhokoli z nich změnila verze. Takže se musí vydat nový release, i když nikdo nic neupravil.
Třikrát nezávisle nalezený problém už není domněnka.
Drobnější¶
| účet | co je | kde |
|---|---|---|
| E, F, G | meze Redisu a jestli je „důvěryhodný diff" z ERP realistický | kolo 2 |
| I, J | pět registrů profilů jako konfigurační peklo; proč má redaktor vidět cenu dotazu | kolo 3 |
| K, L | TypeScript pro editor si protiřečí se zákazem dvojí implementace | kolo 4 |
| Q | dotaz musí umět i rozdíl proti minulému stavu, což v kole 3 nezaznělo | kolo 5 |
| V | která pole se smí přepsat kontextově a která ne | kolo 7 |
| X | „naplňuj cache z hlavní databáze" ruší repliky přesně ve chvíli, kdy jsou po nočním importu nejvíc potřeba | kolo 8 |
| BB | osm pravidel autority × pole × jazyk × verze — námitka o konfiguračním pekle z kola 3 platí beze změny | kolo 9 |
| CC | override s platností do data vrací původní problém na časovači: po vypršení redaktorův text jednu noc zmizí | kolo 9 |
| EE | „krátké" zastavení zápisů při přepnutí má nevyčíslené trvání — a závisí na tom, jestli dohánění vůbec doběhne | kolo 10 |
| FF | rozdělení pole mění adresu autority, takže pravidlo pro novou adresu vytváří sama migrace. Kdo to schvaluje, nezaznělo | kolo 10 |
| HH | redaktor zmáčkne „přegeneruj SEO", což vyžaduje širší práva, než má sám. Tichá cesta je zakázaná správně, hlasitá nepopsaná | kolo 11 |
Co ještě nikdo nenapadl¶
Devět bodů, na kterých se oba návrhy shodly hned od začátku. Shoda dvou modelů ale není důkaz — kolo 7 ukázalo, že i spor může být ze dvou třetin jenom o slova. Stejná past číhá ve shodě.
Čtyři z nich už kola mimochodem prověřila. Tři jsou pořád nedotčené:
- schéma jako jediný zdroj pravdy
- napovídání v editoru i rozhraní pro AI ze stejného schématu
- konektory na cizí systémy jako vyměnitelné adaptéry
Kandidát na další kolo debaty se zadáním je napadnout, ne potvrdit.
Celkově¶
Jedenáct kol, čtyři spory, sedm mezer. Všechno uzavřené.
Zbývá jedna věc, a není technická:
Pětkrát se objevilo správné kritérium, které nejde provést
Cost model se má přepočítat, ale data rostou bez zásahu.
Všechny kombinace variant se mají otestovat, ale je jich neprojitelně moc.
Při zaseknuté frontě se má číst všechno z databáze, ale ta to neunese.
Všichni konzumenti dat se mají evidovat, jenže polovina je mimo systém.
Hromadná změna se má zkontrolovat na vzorku, ale vzorek není kontrola.
Jednou by to byla nedbalost. Pětkrát je to vzorec, a znamená to tohle: návrh na několika místech předpokládá práci, kterou nikdo neudělá, protože je nekonečná. Pokaždé je to řečené správně a pokaždé to při 80 000 entitách přestane platit.
Kdyby se mělo řešit jediné, tak tohle — a dřív než první řádek kódu.
Na návrh, který nikdy neopustil dokument, je to podle mě dobrý stav. A hlavně: všechno na týhle stránce je napsané, ne zapomenuté.