Cache v .NET

Oct 08 2015

Máme cache na data a tzv. output cache. Spousta principů je stejných, tady se zaměřím na output cache a o té datové se jen zmíním na okraj. Co je to cache. Česky mezipaměť. Je to místo, kam si mohu uložit „drahá“ data a pak se na ně zeptat. Nejdůležitějším aspektem cache je, že co do cache vložim, to tam NEmusím zase najít. Takže je nutné vždy testovat jestli jsem data z cache dostal a případně je znovu vytvořit (a do cache vložit). Principiálně ani technicky nelze vložit do cache prvek tak, abych ho tam jistě zase našel. Kdy položka z cache vypadne? Je několik důvodů.

  • kdykoliv se k tomu správce cache rozhodne – třeba je málo paměti
  • uplyne zadaný časový interval (sliding nebo absolute)
  • je porušena závislost (dependency)

První důvod je ta v uvozovkách horší vlastnost. Zbylé 2 důvody jsou to co je na cache skvělé (protože to můžeme řídit).

Závislosti (Dependency)

Závislosti jsou jedna z nejdůležitějších vlastností cache. Díky nim je možné určit, které zdroje mají vliv na změnu položky vložené v cache, a tedy kdy přestane položka v cache platit. Závislost může být několika druhů:

  • soubor na disku
  • SQL (SqlCacheDependency)
  • jiná položka cache
  • vlastní (dědíme z CacheDependency)

SqlCacheDependency

Je až od .NET 2 a rozumně použitelná až s MS SQL 2005. Používá mechanismus MS SQL notifikací (push metoda). Před MS SQL 2005 se používaly triggery, extra tabulka a pull metoda (dál se tím nebudu zabývat). SqlCacheDependency jednoduše zkonstruujeme na základě dotazu do SQL serveru. Jakmile by se výsledky dotazu změnily, je tato závislost porušena a příslušná položka je vyhozena z cache. Aby SqlCacheDependency zkonstruovaná na základě SqlCommand fungovala, musíme ji nastartovat a ukončit. Obvykle v Application_Start a Application_End voláme SqlDependency.Start(connectionString) a SqlDependency.End(connectionString). Jinou možností je definovat database entry ve web.config a konstruovat SqlCacheDependency pomocí názvu db entry a názvu tabulky. Pak nemusíme SqlDependency startovat, ale jinak to není moc praktické. Pozor, SqlDependency a SqlCacheDependency jsou rozdílné třídy. Zjednodušeně SqlDependency startujeme a v cache používáme SqlCacheDependency. „‚Pro pokročilé:“‚ SqlCacheDependency je v podstatě jenom obal pro SqlDependency. SqlDependency je objekt, který háže event ve chvíli, kdy dojde ke změně v hlídaných datech. SqlCacheDependency tento event zpracuje tak, že shodí cache.

Data Cache

Data cache je místo, kam si můžeme uschovat vlastní datové objekty. Platí pro ni vše výše uvedené obecně o cache. To nejdůležitější je, že objekt do cache vložený tam NEmusíme znovu najít. Najdeme ji na Page.Cache. Když do ní přidáváme prvek, můžeme definovat dependency, absolute i sliding expiraci. Zároveň je možné definovat onRemoveCallback, což je delegát, který bude zavolán ve chvíli, kdy správce cache z jakéhokoliv důvodu odstraní položku z cache. Kdyby někoho napadlo udělat „permanentní cache tak, že v onRemoveCallback delegátu prvek znovu do cache přidá, tak vězte, že to nejde, protože prvek je z cache nejdřív odstraněn a pak je teprve zavolán onRemove delegát a navíc není jisté, kdy přesně je volán. Víc už se o data cache nebudu zmiňovat, protože tohle píšu kvůli output cache.

Output Cache

Co je output cache. Jde o cachování specificky pro ASP.NET (tedy pro weby) a cachuje se výsledná stránka nebo její části. Tedy ne data potřebná k vytvoření stránky, ale výsledný obsah. Pokud je cachována celá stránka a požadavek klienta je uspokojen z cache, tak vůbec neproběhnou události jako Load stránky. Malá odbočka ke cachování stránek na cestě mezi uživatelem a serverem. Na webu může být libovolný zdroj (stránka, soubor) cachována na několika místech. U klienta, na proxy a v našem případě i na úrovni ASP.NET (serveru). Budu se zabývat hlavně věcmi okolo cachování na úrovni ASP.NET, teď jen pár základů ke cachování na ostatních místech. Mimo cachování na úrovni ASP.NET neexistují závislosti jako takové. Cachování funguje jen na principu If-Modified-Since nebo Expire. V případě If-Modified-Since posílá klient (proxy) request s příslušnou hlavičkou a server vrací data nebo odpověď „nic se nezměnilo“. V případě expirace klient (proxy) vůbec nežádá o data, pokud jeho kopie není starší než uvedená expirace. Cachování na úrovni proxy funguje tak, že klient se neptá na data (request) cílového serveru, ale proxy serveru. Proxy server se rozhodne, jestli pošle dotaz na cílový server (nebo další proxy v pořadí) nebo ho uspokojí z vlastní cache. Server v hlavičkách odpovědi udává jakým způsobem je možné konkrétní stránku cachovat (jestli vůbec, expirace), ale pak už nad cache nemá žádnou vládu – nedokáže vynutit uvolnění položky z cache klienta nebo proxy serveru. Cachování na úrovni ASP.NET je plně pod kontrolou serveru a zná závislosti. Využívá paměť serveru. Aktivuje se direktivou v aspx nebo ascx. Atributy projdeme později.

<%@ outputcache Duration="30" varybyparam="none" %>

 

Rozdělení nacachovaných výsledků

Nemůžeme dávat na každý dotaz totéž, takže output cache rozděluje výsledky podle mnoha kritérií. Samozřejmě podle adresy requestu. Zvlášť jsou get parametry (atribut VaryByParam). Může rozdělit cache podle hlaviček requestu anebo pomocí programátorem definovaného mechanismu.

VaryByParam

Rozlišení cache podle GET parametrů v URL a hodnot formuláře v POST. Možné hodnoty:

none
parametry jsou ignorovány, vrací se stejná stránka bez ohledu na parametry
*
berou se v úvahu všechny parametry
čárkami oddělený seznam parametrů
berou se v úvahu vyjmenované paramatery

VaryByHeader

Rozlišení cache podle hlaviček HTTP requestu. Například @@VaryByHeader=“Accept-Language“@@ zaručí rozdělení výsledků podle jazyka, který udává browser klienta.

VaryByCustom

Nejmocnější z Vary atributů. Obsahuje čárkami oddělený seznam řetězců. Některé jsou předdefinované, o jiných musíte rozhodnout sami. Předdefinované hodnoty:

browser
rozděluje cache podle udaného prohlížeče klienta. Lepší než VaryByHeader=“User-Agent“, protože se nenechá ošálit různými tvary této hlavičky.

Vlastní řetězce. V Global.asax přepíšete metodu string GetVaryByCustomString (string). Jako parametr dostanete váš VaryByCustom string a podle vráceného řetězce je cache rozdělena. Například rozdělení cache podle zalogovaného uživatele dosáhnete vrácením jeho loginu.

Další atributy outputcache

Location
kde je dovoleno stránku cachovat (server, client, …)
Duration
sliding expirace pro output cache (v sekundách)

Shazování cache

Jednoduše definujeme závislosti pro output cache jako pro jakoukoliv jinou cache. Něco málo je možné nastavit deklarativně atributem „SqlDependency“ direktivy „outputcache“, ale to je dost omezené a lepší je nastavovat závislosti v kódu. Voláním této metody můžete nastavit všechny druhy závislostí (na SQL, souboru, …).

Response.AddCacheDependency(param CacheDependency[])

 

Vlastní validace

Potomek třídy CacheDependency nebo připojení delegáta pomocí Response.Cache.AddValidationCallback. Pozor, takový delegát bude žít po celou dobu existence cache item (pokud by to nebyla statická metoda, tak bude žít i příslušný objekt a vš na co se tento objekt odkazuje).

Částečné cachován

Někdy je vhodné cachovat části stránky jinak. Rozlišujeme 2 metody, Fragment Caching a Post-Cache substitution.

Fragment Caching

Direktivu outputcache můžeme definovat i u ascx kontrolu. Výsledek rendrování takového kontrolu je uložen do cache podle podobných pravidel jako stránka a při generování stránky je nacachovaný výsledek vložen do stránky. To umožňuje nastavit pro cachování controlů jiná pravidla (například jinou expiraci). Control nemá vlastní dependency (takže se dá měnit jenom expirace). Pokud je stránka cachována déle než control, tak stránka vyhrává a control se nemění! Příklad: expirace stránky je nastavena na 100 sekund a expirace controlu na 50 sekund. Kontrol se obnovuje jednou za 100 sekund.

Post-Cache substitution

Opak Fragment Caching. Stránka je cachována a jsou do ní vkládány necachované hodnoty. Bohužel se jedná jenom o string hodnoty. Control


nebo pomocí api (metoda WriteSubstitution). Oboje dostává jako parametr (nebo atribut) delegáta na metodu, která vrací string. Tento delegát je volán vždy při přístupu na stránku a řetězec, který vrátí je vložen na příslušné místo.

Zatím žádná reakce

Vítám tvoje reakce