Learning
Všechny články
Engineering17. srpna 2026 6 min

Rychlost úložiště se ti tiše zapíše do architektury

Z jedné milisekundy na pět a smyčka se protáhla na minuty

Nejdražší předpoklad je ten, který nikdo nikdy nenapsal

Vercel přesunul stav warm poolu — zásoby předehřátých kontejnerů, díky kterým build nečeká na nové compute — z Redisu do DynamoDB. Na papíře nudná migrace: tokeny, stavy životního cyklu, mapování kontejneru na účtovaný deployment. Čtení zpomalilo z 1,29 ms na 5,13 ms na P95. Čtyři milisekundy. Supply loop se začal zadrhávat a jeho běhy se v nejhorších případech protahovaly na minuty.

Ta čtyři milisekundy nebyla příčina. Byla to jen věc, která zviditelnila předpoklad, který nikdo nikdy nezapsal: že čtení stavu trvá zhruba milisekundu.


Jak se předpoklad dostane do návrhu

Redis odpovídal v řádu jedné milisekundy. Za té ceny je úplně rozumné napsat smyčku tak, že před každým vytvořeným kontejnerem udělá jedno čtení stavu. Stovky čtení na jeden běh smyčky. Nikdo to nenavrhl jako N+1 vzor — nikdo o tom vůbec nerozhodoval. Prostě to bylo levné, takže to nikoho nestálo za pozornost.

Tohle je obecný mechanismus. Latence závislosti se nechová jako parametr, který si někde nastavíš. Chová se jako implicitní kontrakt, podle kterého se pak roky píše kód okolo. Čím je ta latence nižší, tím míň lidí ji vidí, a tím hlouběji se ten kontrakt zaryje. Redis byl tak rychlý, že se předpoklad jeho rychlosti stal neviditelným.

Když pak vyměníš úložiště, nezměnil jsi jen jedno číslo. Porušil jsi kontrakt, o kterém jsi nevěděl, že existuje — a rozbije se všechno, co se o něj opíralo, najednou.


Proč nestačilo dávkovat

První reflex je dávkování: místo stovek čtení udělej jedno hromadné. Vercel to odmítl s dobrým argumentem. Velikost dávky, kterou zvolíš, do kódu zapeče poměr latencí, který se s zátěží posune. Vyrobíš tím jen nový nevyslovený předpoklad, jen líp schovaný.

Zvolili jinou cestu: odstranit tu závislost, ne ji obejít. Volání supply loopu pro jednotlivé warm pooly začala běžet souběžně, takže žádný pool nečeká za tím předchozím. A kontejnery se vytvářejí podle posledního stavu, který smyčka viděla, ne podle čerstvě dotaženého. Cena je pár kontejnerů navíc vytvořených podle zastaralého obrázku. To je u zásobníku, který stejně jede před poptávkou, přijatelná daň.

Rozdíl mezi těmi dvěma řešeními stojí za zapamatování. Dávkování zrychlí čtení. Souběh a práce se zastaralým stavem odstraní potřebu být rychlý — smyčka přestane být na latenci citlivá vůbec.


Data tam byla celou dobu

Nejlepší část je retrospektiva. Když se do telemetrie podívali pořádně, ukázalo se, že smyčka se zadrhávala i pod Redisem, občas na minutu i víc. Ta data v telemetrii byla celou dobu. Nikdo je nikdy neotevřel, protože warm pool sedí před poptávkou, ne v cestě requestu — takže jeho zadrhnutí nikdy nevyrobilo stížnost, alert ani incident.

Tohle je typologie, ne náhoda. Komponenty, které pracují do zásoby, mají tlumič mezi svým selháním a jeho projevem. Cache, prefetch, předehřátý pool, replikační zpoždění, fronta s rezervou. Dokud je zásoba dost velká, degradace se ven neprojeví vůbec. Když se projeví, je pozdě a příčina je stará měsíce.

Migrace v tomhle příběhu nic nerozbila. Odebrala tlumič a donutila se podívat.


Co si z toho odnést

U každé výměny závislosti se neptej jen, jestli nová varianta zvládne stejné operace. Zeptej se, jaká čísla staré varianty se za ta léta stihla stát součástí návrhu kolem ní. Latence, atomicita, pořadí, chování při chybě — to všechno se v rychlé závislosti chová jako záruka, o kterou se kód začne nevědomky opírat.

A když najdeš horké místo, zvaž nejdřív, jestli se z citlivosti na latenci nedá vystoupit úplně — souběhem a prací se stavem, který nemusí být nejčerstvější — než ho začneš ladit dávkováním. Zvlášť u komponent, které jedou do zásoby: tam si degradaci musíš aktivně měřit, protože ti ji nikdo neohlásí.

Zdroje

Poslech místo čtení — 1 zdroj k načtení.