Learning
Všechny články
Engineering14. září 2026 6 min

Granularita klíče rozhoduje o cache víc než velikost

Klíč na cestu a na deployment znamená trvale studenou cache

Studená cache není problém kapacity, ale způsobu klíčování

Než CDN vrátí odpověď, musí zjistit, čemu požadovaná cesta vlastně odpovídá. /blog/hello-world se mapuje na /blog/[slug], ten se může dál mapovat na /blog/[slug].rsc pro React Server Components. Každý request tak nejdřív potřebuje metadata routingu a teprve pak obsah.

Původní návrh ukládal metadata pro každou cílovou cestu zvlášť. Vypadá to jako správná granularita: stáhneš jen to, co potřebuješ. Jenže ke každému klíči se pak musíš dostat samostatně a každý klíč má vlastní první minutí.


Nový deployment vynuloval celou cache

Klíče byly navíc vázané na deployment. To má dobrý důvod, protože stará metadata nesmí přežít nasazení nové verze, jenže důsledek je drsný: po každém releasu byl první lookup pro každou cestu minutí.

U nasazení se statisíci cestami to znamená, že cache nikdy nedosáhne ustáleného stavu. Provoz je rozprostřený, dlouhý ocas cest se trefí jednou za čas, a než se nahřeje, přijde další deployment. Není to jev, který se projeví na průměru. Projeví se na P99, protože ten měří přesně ty requesty, které trefily studený klíč.

Z toho plyne obecnější pravidlo. Když klíč obsahuje verzi nasazení, invalidace je zdarma a nahřátí je drahé. Ten obchod se nedá zrušit, dá se jen zmenšit počet klíčů, které je potřeba nahřát.


Shard sdílí jedno stažení mezi tisíce cest

Řešením bylo přestat ukládat metadata po cestách a začít je ukládat po balících o zhruba 200 KB. Jedno stažení takového shardu naplní cache pro mnoho cest najednou, takže dlouhý ocas se nahřeje prvním requestem svého souseda.

Aby se z toho nestal jiný problém, má shard vestavěný index. Routing binárně hledá v zakódovaných cestách podle byte offsetů spočítaných při buildu a parsuje jen tu jednu hodnotu, která odpovídá. Stáhne se víc, ale neparsuje se víc. To je podstatné, protože jinak by se latence jen přesunula ze sítě do CPU.

Velikost shardu není libovolná a vyladila se měřením v produkci. Vícemegabajtové shardy přetekly lokální cache a tím zrušily celý zisk. Drobné shardy zvýšily počet regionálních minutí a chovaly se jako původní stav. Zhruba 200 KB vyšlo jako praktická rovnováha.


Filtr před lookupem a co se nedělalo

Před samotným lookupem stojí Bloom filtry, které vyloučí cesty, jaké v daném nasazení nemohou existovat. Je to laciná vrstva, která ušetří celý dotaz, a stojí za povšimnutí, že zůstala i po přechodu na shardy. Nová vrstva nenahradila starou, jen zlevnila to, co filtr nevyloučí.

Stejně poučné je, co se nedělalo. Další komprese se zvažovala a zamítla, protože práce s kódováním, kompatibilitou a postupným rolloutem nevyvážila zisk. Když už máš P99 na 19,1 ms, další procenta se kupují disproporčně draho.

Výsledek: P99 z 215,8 ms na 19,1 ms, průměr z 8,59 ms na 1,81 ms, a k tomu asi 16,6 sekundy ušetřených při každém buildu, protože se nahrává méně objektů.


Co si z toho odnést

Když měříš cache a vidíš špatný hit rate, neptej se nejdřív na velikost cache. Ptej se, kolik různých klíčů tvůj provoz vůbec potřebuje nahřát a jak často se ty klíče mění. Počet klíčů a jejich životnost určují hit rate dřív než kapacita.

Z toho plyne konkrétní pohyb, který lze zkopírovat: seskup malé položky do balíku takové velikosti, aby se vešel do nejbližší vrstvy cache, a dovnitř balíku dej index, aby stahování víc dat neznamenalo zpracovávat víc dat. Tím oddělíš granularitu přenosu od granularity čtení, což jsou dvě věci, které se implicitně slučují a nemusí.

A poslední věc: ideální velikost balíku se nedá odvodit. Vyplyne z toho, kam se vejde, a to zjistíš jedině měřením v provozu.

Zdroje

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