Vrstvy cache se liší vlastníkem, ne rychlostí
Cache ve webové aplikaci se kreslí jako schodiště: prohlížeč, CDN, aplikační cache, databáze. Z toho obrázku plyne dojem, že jde o jednu věc uloženou čtyřikrát a že se liší jen tím, jak je která rychlá.
Užitečnější osa je jiná. Vrstvy se liší klíčem, pod kterým položku najdou, a tím, kdo ji smí smazat. Druhá půlka rozhoduje o architektuře, protože invalidace je jediná operace, která je u cache opravdu těžká.
Dva druhy podle sdílení
HTTP zná privátní a sdílenou cache a ten rozdíl není technický, je bezpečnostní. Privátní cache patří jednomu klientovi, obvykle prohlížeči, a proto v ní smí ležet personalizovaná odpověď. Sdílená cache, tedy CDN nebo reverzní proxy, obsluhuje mnoho uživatelů, takže personalizovaná odpověď v ní je únik dat.
Tomu odpovídají směrnice. private zakazuje uložení ve sdílené cache. s-maxage nastavuje životnost jen pro sdílené cache a přebíjí v nich max-age, takže můžeš CDN nechat držet obsah dlouho, zatímco prohlížeč ho ověřuje často.
Sem patří i Vary. Když se odpověď liší podle hlavičky, Vary: Accept-Language z jedné položky udělá několik. Proto se Vary: User-Agent nepoužívá: hodnot je tolik, že se cache rozpadne na skoro samé unikáty a hit rate spadne k nule.
Čerstvost není totéž co ověření
Tři nejčastěji zaměňované směrnice popisují tři různé věci. no-store zakazuje uložit. no-cache uložit dovolí, ale zakáže použít bez ověření u serveru. A max-age říká, jak dlouho se položka smí použít bez ptaní.
Ověření samo je levné. S ETag pošle klient If-None-Match, a když se otisk shoduje, server odpoví 304 Not Modified bez těla. Přeneseš hlavičky, ne obsah.
Jenže i to 304 stojí jeden round trip. Proto existuje stale-while-revalidate: prošlá odpověď se odbaví okamžitě a obnova běží na pozadí. Tím se mění, co vlastně slibuješ. Ne že uživatel uvidí čerstvá data, ale že je uvidí při příštím zobrazení.
Když směrnice nepošleš vůbec, cachování se nevypne. Nastoupí heuristika: podle Last-Modified se odhadne životnost jako zhruba desetina doby od poslední změny. Rok starý soubor se tak sám od sebe uloží na měsíc.
Kam ruka nedosáhne
Teď to podstatné. Položku v aplikační cache i v Redisu smažeš, protože oba běží u tebe. CDN má purge API, tedy dosah máš zprostředkovaný a se zpožděním. Cache v prohlížeči uživatele smazat nemůžeš vůbec. Nemáš tam žádný kanál.
Odtud plyne obvyklá dvojice nastavení, která se učí jako recept, ale je to důsledek. Statické assety dostanou verzi v názvu a Cache-Control: public, max-age=31536000, immutable. HTML dokument dostane no-cache.
Logika je celá v dosahu. U assetu se invalidace neprovádí, obchází se: nové vydání má jiné URL, takže staré položce nikdy nevadí, že tam zůstane navždycky. Jediné, co se opravdu musí měnit pod stejnou adresou, je vstupní dokument, který na ty nové adresy odkazuje. A ten se proto při každém načtení ověřuje.
Co si z toho odnést
Ke každé vrstvě, kterou v aplikaci zavádíš, si zapiš dvě věci: co je klíč a kdo ji umí zneplatnit. Vrstva, kterou neumíš zneplatnit, není zakázaná, ale musí být adresovaná neměnným klíčem. Verzované URL je právě tohle, ne trik na prohlížeč.
Rozlišuj taky mezi „data jsou čerstvá" a „ověřím čerstvost". Většina rozhodnutí, která se tváří jako volba doby platnosti, je ve skutečnosti volba mezi round tripem navíc a rizikem zastaralého zobrazení. stale-while-revalidate tu volbu posouvá o jedno zobrazení dál, což u seznamů obvykle nikoho netrápí a u detailu po odeslání formuláře trápí hodně.
A když nastavuješ hlavičky, začni od private versus veřejné. Chybně sdílená personalizovaná odpověď není výkonnostní problém, je to únik dat mezi uživateli.