Nette Framework: Cache

Cache (čtěte „keš“) je označení pro rychlou mezipaměť, do které se duplikují často používaná a přitom těžko dostupná data, aby se přístup k nim urychlil. Ukážeme si, co v této oblasti nabízí Nette Framework

Seriál: Začínáme s Nette Framework (17 dílů)

  1. Nette Framework: zvyšte svoji produktivitu 10.3.2009
  2. Nette Framework: Odvšivujeme 17.3.2009
  3. Nette Framework: MVC & MVP 24.3.2009
  4. Nette Framework: Refactoring 31.3.2009
  5. Nette Framework: Chytré šablony 7.4.2009
  6. Nette Framework: adresářová struktura aplikace 14.4.2009
  7. Nette Framework: AJAX 21.4.2009
  8. Nette Framework: AJAX (pokračování) 28.4.2009
  9. Nette Framework: AJAX (dokončení) 5.5.2009
  10. Nette Framework: Sessions 12.5.2009
  11. Nette Framework: Přihlašování uživatelů 19.5.2009
  12. Nette Framework: Ověřování oprávnění a role 26.5.2009
  13. Nette Framework: Neprůstřelné formuláře 2.6.2009
  14. Nette Framework: Neprůstřelné formuláře II 9.6.2009
  15. Nette Framework: Neprůstřelné formuláře III 16.6.2009
  16. Nette Framework: Cache 23.6.2009
  17. Nette Framework: Co se do seriálu nevešlo? 30.6.2009

Mezi strojovým časem a paměťovým prostorem existuje jakási nepřímá úměra. Kešování je jedním ze způsobů, jak čas transformovat do prostoru. Idea je prostá: mějme časově náročnou operaci, která pro vstupní parametry vygeneruje výstup. Ten si uložíme do mezipaměti a příště budeme moci pro stejné vstupní parametry získat výsledek bez volání oné náročné operace.

Kešování je tedy další vrstva, kterou přidáme do aplikace.

Jenže celé to má řadu úskalí. Je potřeba si zodpovědět otázky:

  • má na výsledek operace vliv ještě něco jiného kromě vstupních parametrů? (a pokud ano, jak tyto vlivy pojmout?)
  • bývá vůbec operace volána se stejnými vstupními parametry často?
  • jakou má kešovací vrstva režii?

Při špatném nastavení nebo odhadu situace se zcela klidně může stát, že implementace cache způsobí znatelný pokles výkonu aplikace a přitom zvýší její náročnost na paměťové zdroje. Jinými slovy, cache není samospasitelná a měla by být implementována s rozmyslem a erudovanou osobou.

Existuje celá řada úrovní, na které lze data kešovat. Je třeba dobré vědět, jestli např. databázový server disponuje keší (pravděpodobně ano) a dokázat ji správně využít. Přičemž duplikovat tuto cache na straně aplikace, tj. ukládat do paměti PHP skriptu všechny SQL dotazy a jejich výsledky, může být potom kontraproduktivní. Daleko většího efektu se dosáhne při kešování finální podoby dat, tedy vygenerovaného HTML fragmentu.

Téma optimalizace výkonnosti a kešování by vystačilo na samostatný seriál, nebudu proto zabíhat do podrobností a vrátím se k oblasti bezprostředně související s Nette Framework.

Opcode cache

PHP je interpretovaný jazyk, což znamená, že při každém HTTP požadavku musí server všechny skripty naparsovat a zkompilovat do binární podoby nazývané opcode. Pod termínem opcode cache se pak rozumí mechanismus, který zkompilovaný kód udržuje v mezipaměti, takže není potřeba jej generovat pokaždé znovu.

Existuje celá řada opcode cache implementací, např. eAccelerator, Zend Optimizer, ionCube, APC. Co s nimi má společného Nette Framework? Celkem důležitou věc. Framework totiž používá takové konstrukce a postupy, které lze pomocí opcode cache dobře akcelerovat a zároveň se vyhýbá všemu, co je z pohledu opcode cache překážkou. Příkladem spolupráce jsou šablony, které se kompilují do PHP skriptů a ukládají do dočasného adresáře jako soubory s příponou .php. Naopak typickou překážkou je příkaz eval(), na kterém framework nestaví.

V souvislosti s výkonem musím zmínit, že Nette Framework na rychlosti hodně lpí (v nezávislém testu na mateřském serveru Root.cz byl vyhodnocen jako jeden z nejrychlejších frameworků vůbec) a proto za běhu kešuje i některé kritické interní struktury. Sem patří například správa hierarchie komponent.

Cache do vaší aplikace

Konečně se dostáváme k tomu, co je z pohledu programátora nejzajímavější. Tedy jaké nástroje dává framework k tomu, aby mohl do cache ukládat vlastní data. Platí zde totéž, co pro ostatní části frameworku. Na straně API je kladen důraz na přehlednost a jednoduchost syntaxe, na straně backendu se používají rozhraní (interfaces) a s nimi spojená možnost chování zcela přizpůsobit specifickým potřebám.

Pokud již víte, jak se pracuje se sessions, bude vám použití cache připadat důvěrně známé:

require 'Nette/loader.php';

// pokud používáte verzi pro PHP 5.3, odkomentujte následující řádek:
// use NetteEnvironment;

// získáme přístup do jmenného prostoru cache 'myData'
$cache = Environment::getCache('myData');

// zápis do cache; $value může obsahovat jakoukoliv strukturu
$cache[$key] = $value;

// čtení z cache
$cachedData = $cache[$key];

// mazání cache
unset($cache[$key]); 

Cache mají na starost třídy a rozhraní ze jmenného prostoru NetteCaching .

Ukažme si ještě kešovací obal nad časově náročnou funkcí:

// výpočetně náročná funkce
function veryExpensiveFunction($input)
{
 return ...;
}

// obal, který výpočet kešuje
function cachedFunction($input)
{
        $cache = Environment::getCache(__FUNCTION__);
        if (isset($cache[$input])) {
                return $cache[$input]; // buď vrátíme výsledek rovnou z cache
        } else { // nebo jej spočítáme a uložíme do cache
                return $cache[$input] = veryExpensiveFunction($input);
        }
} 

Metoda Environment::getCache() vrací objekt NetteCachingCache, který slouží k pohodlné manipulaci s daty, nicméně sám nikam nic neukládá. Kam se tedy data uloží? To záleží na konkrétním úložišti, tj. objektu implementujícím rozhraní NetteCachingICacheStorage. Výchozím úložištěm je NetteCachingFi­leStorage, které, jak je z názvu patrné, ukládá data do souborů. Výhoda rozdělení mezi Cache a úložiště spočívá v tom, že když se nyní rozhodnete úložiště změnit (například nahradit diskové soubory za Memcached), není potřeba v kódu nic měnit.

NetteCachingFi­leStorage

U výchozího úložiště NetteCachingFi­leStorage se ještě na chvíli zastavme. Jelikož výkonný Memcached byste na sdíleném hostingu hledali marně, budou soubory asi tím nejčastějším místem pro mezipaměť ve vašich aplikacích. Dobrou zprávou je, že toto úložiště je v Nette Framework velmi dobře optimalizované pro výkon. Ale především: zajišťuje plnou atomicitu operací.

Co to znamená? Že při použití cache se vám nemůže stát, že přečtete soubor, který ještě není (jiným vláknem) kompletně zapsaný, nebo že by vám jej někdo „pod rukama“ smazal. Použití cache je tedy zcela bezpečné.

Invalidace dat

S ukládáním dat do mezipaměti vznikají dva problémy. Jednak je tu pochopitelně hrozba, že se úložiště zcela zaplní a nebude možné další data zapisovat. A také se může stát, že některá dříve uložená data se stanou v průběhu času neplatná. Nette Framework proto nabízí mechanismus, jak omezit platnost dat nebo je řízeně mazat (v terminologii frameworku „invalidovat“).

Platnost dat je třeba nastavit už v okamžiku ukládání. Syntaxe přiřazení $cache[$key] = $value k tomu prostor nenabízí, proto pro uložení použijeme metodu save a platnost budeme specifikovat třetím parametrem:

// pokud používáte verzi pro PHP 5.3, odkomentujte následující řádek:
// use NetteCachingCache;

$cache->save($key, $value, array(
    Cache::EXPIRE => '+ 20 minutes', // lze zadat v sekundách nebo jako UNIX timestamp
)); 

Z kódu je patrné, že hodnotu jsme uložili s platností 20 minut. Po uplynutí této doby bude cache hlásit, že pod klíčem $key žádný záznam nemá. Pokud bychom chtěli obnovit dobu platnosti s každým přístupem (tj. čtením hodnoty), lze toho docílit takto:

$cache->save($key, $value, array(
    Cache::EXPIRE => '+ 20 minutes',
    Cache::SLIDING => TRUE,
)); 

Šikovná je možnost nechat data vyexpirovat v okamžiku, kdy byl změněn určitý soubor:

$cache->save($key, $value, array(
    Cache::FILES => 'soubor.php', // lze uvést i pole více souborů
)); 

Kritérium Cache::FILES je samozřejmě možné kombinovat i s časovou expirací  Cache::EXPIRE.

Velmi užitečným invalidačním nástrojem je tzv. tagování. Mějme třeba HTML stránku s článkem a komentáři, kterou uložíme do cache, abychom ji nemuseli pokaždé renderovat znovu.

$id = (string) $_GET['id'];

$cache = Environment::getCache();
if (isset($cache[$id])) {
        // buď vykresli stránku z cache
        echo $cache[$id];
} else {
        // nebo ji vyrenderuj
        ob_start();
        ...
        ... // kreslíme stránku
        ...
        $cache->save($id, ob_get_flush(), array( // a ulož do cache
                Cache::TAGS = array("article/$id", "comment/$id"),
        ));
} 

Tedy HTML stránce článku s ID např. 10 jsme přiřadili tagy article/10 a comment/10. Přesuňme se do administrace. Tady najdeme formulář pro editaci článku. Společně s uložením článku do databáze zavoláme příkaz:

// smažeme z cache položky s určitým tagem
Environment::getCache()->clean(array(
        Cache::TAGS = array("article/$id"),
));
} 

Stejně tak v místě přidání nového komentáře (nebo editace komentáře) neopomeneme invalidovat příslušný tag:

Environment::getCache()->clean(array(
        Cache::TAGS = array("comment/$id"),
));
} 

Čeho jsme tím dosáhli? Že se nám HTML (nebo i jiná) cache bude invalidovat sama. Kdykoliv uživatel změní článek s ID 10, dojde k vynucené invalidaci tagu article/10 a HTML stránka, která uvedený tag nese, se z cache smaže. Totéž nastane při vložení nového komentáře pod příslušný článek.

Pokračování příště

V příštím díle, posledním před prázdninami, si projdeme několik užitečných tříd, na které dosud nezbyl čas.


Autor článku je vývojář na volné noze, specializuje se na návrh a programování moderních webových aplikací. Vyvíjí open-source knihovny Texy, dibi a Nette Framework a pravidelně pořádá školení pro tvůrce webových aplikací, které od podzimu 2009 nabídne kurz vývoje AJAXových aplikací.

David Grudl školí, je autorem PHP knihoven Nette Framework, databázové vrstvy dibi a formátovače HTML kódu Texy!.

Věděli jste, že nám můžete zasílat zprávičky? (Jen pro přihlášené.)

Komentáře: 11

Přehled komentářů

Mastodont Rejpy rejpy po ránu
plistiak Re: Rejpy rejpy po ránu
hasan Re: Rejpy rejpy po ránu
David Grudl Re: Rejpy rejpy po ránu
michal Re: Rejpy rejpy po ránu
Mastodont Re: Rejpy rejpy po ránu
keff Tagy? Super!
JD Re: Tagy? Super!
blizzy Není tam chyba?
Karl-von-bahnhof Vypnutí cache
v6ak Re: Vypnutí cache
Zdroj: https://www.zdrojak.cz/?p=3038