Úložiště
Úložiště určuje, kde data z cache fyzicky leží: v souborech na disku, na serveru Redis nebo Memcached, v databázi SQLite nebo v PSR-16 cache jiného frameworku. Vyměnit ho můžete, aniž byste sáhli na kód, který cache používá. Tato stránka vám pomůže:
- vybrat správné úložiště a zjistit, co které umí
- nastavit ho v aplikaci Nette, případně cache vypnout
- použít úložiště z Nette jako PSR-16 cache, a obráceně
- napsat si vlastní úložiště
Výběr úložiště
Pokud si nejste jistí, nechte výchozí FileStorage. Stačí mu adresář, je bezpečné, i když najednou čte
a zapisuje mnoho požadavků, a podporuje všechny druhy závislostí.
Po RedisStorage sáhněte, když aplikace běží na více serverech. Se soubory by měl každý server vlastní
cache, a když byste smazali článek podle tagu na jednom serveru, na ostatních by zůstaly jeho zastaralé kopie. Server Redis
sdílejí všechny.
Úložiště se liší tím, které závislosti umí respektovat:
| Úložiště | Tagy, priority | Soubory, konstanty, callbacky | Jiné položky | Klouzavá expirace | Mazání jmenných prostorů | Ochrana proti stampede |
|---|---|---|---|---|---|---|
| FileStorage | s journalem | ano | ano | ano | ano | ano |
| RedisStorage | ano | ano | ne | ano | ano | ano |
| MemcachedStorage | s journalem | ano | ne | ano | ne | ne |
| SQLiteStorage | jen tagy | ne | ne | ano | ano | ne |
| MemoryStorage | ano | ignoruje | ignoruje | ano | ano | ne |
| Psr16Storage | s journalem | ano | ne | ne | ne | ne |
| DevNullStorage | nic neukládá |
Úložiště, které některou závislost neumí, vyhodí Nette\NotSupportedException, místo aby ji potichu
ignorovalo, takže na to přijdete hned, a ne až podle zastaralých dat. Jedinou výjimkou je MemoryStorage, které
ignoruje, co neumí.
Ochrana proti stampede znamená, že když o chybějící položku požádá najednou mnoho požadavků, generátor z
load() spustí jen první z nich a ostatní počkají na jeho výsledek. Bez ní hodnotu počítají všechny
zároveň.
Konfigurace
V aplikaci postavené na Nette poskytuje DI kontejner FileStorage, které ukládá data do podadresáře
cache v adresáři pro dočasné
soubory, a k němu i journal. Dostanete ho pomocí dependency injection, když si řeknete o typ
Nette\Caching\Storage.
Pokud chcete jiné úložiště, nahraďte v konfiguraci službu cache.storage:
services:
cache.storage: Nette\Caching\Storages\RedisStorage('10.0.0.5')
Kontejner obsahuje tyto dvě služby:
| Název | Typ | Popis |
|---|---|---|
cache.storage |
Nette\Caching\Storage | úložiště |
cache.journal |
Nette\Caching\Storages\Journal | journal pro tagy a priority |
Vypnutí cache
Pokud chcete cache vypnout, třeba v testech, použijte DevNullStorage, které nic neukládá:
services:
cache.storage: Nette\Caching\Storages\DevNullStorage
Na cache šablon Latte a DI kontejneru to nemá vliv, ty jsou samostatné a toto úložiště nepoužívají. Během vývoje je ostatně není potřeba vypínat.
FileStorage
Nette\Caching\Storages\FileStorage zapisuje každou položku do souboru v zadaném adresáři, který musí
existovat. Tagy a priority potřebují journal:
$journal = new Nette\Caching\Storages\SQLiteJournal('/path/to/temp/journal.s3db');
$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp', $journal);
Všechny operace jsou atomické. Nikdy nepřečtete soubor, který jiný požadavek ještě nedopsal, a nikdo vám ho nemůže smazat pod rukama, zatímco ho čtete. Úložiště je proto bezpečné i pod velkou zátěží.
Navíc chrání před cache stampede. Když o chybějící položku požádá najednou mnoho požadavků, třeba hned po nasazení, první z nich soubor zamkne a spustí generátor. Ostatní počkají na zámek a pak přečtou čerstvou hodnotu, místo aby ji počítaly znovu.
Vyexpirované soubory se mažou postupně. Čas od času jim konstruktor věnuje nejvýš 20 ms, takže ani velká cache
požadavek znatelně nezdrží. Všechny vyexpirované soubory najednou smažete voláním
$storage->clean([]).
Data se ukládají pomocí PHP funkce serialize(). Pokud chcete jiný formát, vytvořte potomka a přepište
metody serializeData() a unserializeData().
RedisStorage
Nette\Caching\Storages\RedisStorage ukládá data na server Redis. Tagy a priority
si drží sám, takže nepotřebuje journal, a chrání před cache stampede stejně jako FileStorage.
Vyžaduje PHP rozšíření redis.
services:
cache.storage: Nette\Caching\Storages\RedisStorage('10.0.0.5')
Konstruktor přijímá adresu serveru a port, výchozí je 6379. Pokud už máte připojený objekt Redis,
předejte ho místo adresy.
Všechny klíče dostanou prefix nette.cache:, takže databázi můžete sdílet s jinými daty; změníte ho
parametrem prefix. Prázdný prefix znamená, že celá databáze patří cache, a smazání cache pak databázi
vyprázdní.
Zatímco jeden proces chybějící položku generuje, ostatní na ni čekají, standardně nejvýš 30 sekund (parametr
lockTimeout). Generátor, který trvá déle, znamená jen to, že stejnou hodnotu spočítá i další proces,
nikdy to, že se uloží špatná hodnota.
Úložiště nepodporuje závislost na jiných položkách.
MemcachedStorage
Nette\Caching\Storages\MemcachedStorage ukládá data na server Memcached.
Konstruktor přijímá adresu serveru a port, výchozí je 11211. Tagy a priority potřebují journal.
Vyžaduje PHP rozšíření memcached.
services:
cache.storage: Nette\Caching\Storages\MemcachedStorage('10.0.0.5')
Memcached neumí vypsat své klíče, takže úložiště neumí mazat jmenné prostory. Nepodporuje ani závislost na jiných položkách. Smazání celé cache vyprázdní celý server Memcached včetně dat jiných aplikací.
Když Memcached položku odmítne uložit, typicky takovou, která přesahuje jeho limit velikosti, úložiště vyvolá varování.
SQLiteStorage
Nette\Caching\Storages\SQLiteStorage drží celou cache v jediném souboru databáze SQLite. Tagy si ukládá do
vlastní tabulky, takže nepotřebuje journal.
Vyžaduje PHP rozšíření pdo a pdo_sqlite.
services:
cache.storage: Nette\Caching\Storages\SQLiteStorage('%tempDir%/cache.db')
Databáze běží v režimu WAL, ve kterém čtení nečeká na zápis. SQLite si pak vedle databáze zakládá další dva soubory, takže PHP musí mít právo zapisovat do jejího adresáře.
Úložiště podporuje expiraci, klouzavou expiraci, tagy a mazání jmenných prostorů. Nepodporuje priority ani závislosti na souborech, konstantách, callbackech a jiných položkách.
MemoryStorage
Nette\Caching\Storages\MemoryStorage drží data v PHP poli, takže se s koncem požadavku ztratí. Hodí se pro
testy a pro cachování v rámci jednoho požadavku.
Respektuje expiraci, klouzavou expiraci, tagy, priority a mazání jmenných prostorů. Závislosti na souborech, konstantách, callbackech a jiných položkách potichu ignoruje. Myslete na to, když jím v testech nahrazujete skutečné úložiště: položka, která by tam vyexpirovala, tady zůstane.
Psr16Storage
Nette\Bridges\Psr\Psr16Storage vám umožní použít jako úložiště pro Nette libovolnou PSR-16 cache, třeba cache jiného frameworku, který už vaše aplikace
používá. Tagy a priority potřebují journal:
// $simpleCache je libovolná Psr\SimpleCache\CacheInterface
$storage = new Nette\Bridges\Psr\Psr16Storage($simpleCache, journal: $journal);
PSR-16 povoluje v klíčích jen některé znaky, proto úložiště každý klíč převede na hash. Před každý z nich
přidá parametr prefix, nejvýš 32 písmen, číslic, podtržítek a teček.
PSR-16 neumí prodloužit platnost položky ani vypsat klíče, takže úložiště nepodporuje klouzavou expiraci ani
mazání jmenných prostorů. Nepodporuje ani závislost na jiných položkách. Smazání celé cache zavolá
clear() na PSR-16 cache, takže smaže i data všech, kdo ji sdílejí, bez ohledu na prefix.
DevNullStorage
Nette\Caching\Storages\DevNullStorage nic neukládá, takže každé load() zavolá generátor.
Použijte ho pro vypnutí cache.
Journal
FileStorage, MemcachedStorage a Psr16Storage neumí své položky vyhledávat podle tagu
nebo priority, proto si vedou samostatný index, tzv. journal. Vestavěný Nette\Caching\Storages\SQLiteJournal ho
ukládá do databáze SQLite:
$journal = new Nette\Caching\Storages\SQLiteJournal('/path/to/temp/journal.s3db');
Vyžaduje PHP rozšíření pdo a pdo_sqlite.
V aplikaci postavené na Nette se journal vytvoří za vás jako služba cache.journal v souboru
journal.s3db a úložišti se předá automaticky. Nahradit ho může vaše vlastní implementace rozhraní
Nette\Caching\Storages\Journal:
services:
cache.journal: MyJournal
Bez journalu tato úložiště fungují také, jen uložení položky s tagy nebo prioritou vyhodí výjimku.
Úložiště jako PSR-16 cache
Některé knihovny očekávají PSR-16 cache. Adaptér
Nette\Bridges\Psr\PsrCacheAdapter jim ji poskytne nad libovolným úložištěm z Nette:
$psrCache = new Nette\Bridges\Psr\PsrCacheAdapter($storage);
$psrCache->set('key', 'value', 3600); // uloží hodnotu na 1 hodinu
$value = $psrCache->get('key', 'default');
Adaptér podporuje všechny metody PSR-16 včetně getMultiple(), setMultiple() a
deleteMultiple(). Jak PSR-16 vyžaduje, odmítne prázdný klíč i klíč obsahující některý ze znaků
{}()/\@:, třeba user:1.
Opačný směr, tedy PSR-16 cache použitou jako úložiště pro Nette, pokrývá Psr16Storage.
Vlastní úložiště
Úložiště je třída, která implementuje rozhraní Nette\Caching\Storage se čtyřmi metodami:
read(string $key): mixedvrátí uloženou hodnotu, nebonull, když položka neexistuje nebo vyexpirovalawrite(string $key, mixed $data, array $dependencies): voidhodnotu uložíremove(string $key): voidpoložku smažeclean(array $conditions): voidsmaže položky podle podmínek metody clean(); prázdné pole znamená odstranit vyexpirované položky
Závislosti přicházejí jako pole s klíči Cache::*, které už Cache zjednodušila: expirace je
počet sekund od teď, ze souborů a konstant se staly callbacky a jiné položky jsou dané svými klíči v úložišti. Při
čtení položky ověřte její callbacky pomocí Nette\Caching\Cache::checkCallbacks(). Pro závislost nebo
podmínku, kterou vaše úložiště neumí, vyhoďte Nette\NotSupportedException.
Další schopnosti přidávají volitelná rozhraní. Nette\Caching\BulkReader a
Nette\Caching\BulkWriter umožní úložišti číst a zapisovat více položek najednou.
Nette\Caching\LockingReader přidává metodu readOrGenerate(), která chrání před cache stampede:
dostane generátor z load() jako funkci a musí ji spustit pod zámkem. Funkce si výsledek uloží sama, pomocí
write() nebo remove() téhož úložiště, takže tyto metody musí fungovat, i když je zámek
držený.
Krátkým úplným příkladem je zdrojový kód SQLiteStorage.