[2010–02–11] Cache a ukládání „callbacků“ kvůli dramatickému zrychlení
- David Grudl
- Nette Core | 8218
Smazání cache je běžná operace po nahrání nové verze aplikace na server. V tu chvíli ovšem dostane server docela zabrat, protože musí vybudovat cache novou. Získání některých dat může být dost náročné, příkladem je třeba cache RobotLoaderu. A pokud navíc v krátkém okamžiku přijde třeba 30 požadavků a všechny se snaží o totéž, náročnost ještě násobně roste.
Řešením by bylo upravit chování aplikace tak, aby data vytvářelo vždy jen jedno vlákno a ostatní čekala. Což je docela implementační oříšek. Mám dobrou zprávu, oříšek našel docela jednoduché řešení! V poslední revizi Nette stačí postup
$result = buildData(); // náročná operace
$cache->save($key, $result); // uložení do cache
napsat jako
$result = $cache->save($key, function() { // nebo callback(...)
return buildData(); // náročná operace
});
a to je vše, buildData() bude volat jen jedno vlákno a ostatní budou čekat. Pokud vlákno z nějakého důvodu selže, dostane šanci jiné.
Toto chování jsem naučil RobotLoader. Výsledky jsou úžasné, při simulaci 30 vláken se ukázalo, že vytížení serveru klesá na pár procent vytížení původního.
- Patrik Votoček
- Člen | 2221
wow tak to je mazec! musím kouknout jak je to implementováno porotože to je hodně cool hračička…
- Honza Kuchař
- Člen | 1662
super! Přesně takhle jsem si to představoval! :-)
Editoval honzakuchar (13. 2. 2010 16:23)
- v6ak
- Člen | 206
Rozhodování, zda jde o data nebo callback, může být trochu zvláštní.
- „Do keše lze ukládat jakékoliv struktury, nemusí to být jen řetězec.“ ( https://doc.nette.org/cs/caching )
- Do callbacků může patřit i array(‚Foo‘, ‚bar‘) a další.
- David Grudl
- Nette Core | 8218
Callback musí být buď objekt Closure (pro PHP 5.3, který ani nelze serializovat) nebo Nette\Callback (pro 5.2, 5.3), takže array se vždy uloží jako obyčejné pole.
- Honza Kuchař
- Člen | 1662
Mám trošku problém s tím, jak vlastně ty callbacky zapisovat.
callback(function(){
...
});
nebo jen
function(){...}
ale co potom napsat do PHPDoc? Píšu to pokaždé jinak a už mě to docela štve. Jak to zapsat správně? Resp. nejsprávněji. :-)
- Honza Kuchař
- Člen | 1662
Teď mně tak napadá, toto asi nebude fungovat s DummyStorage? Nebo se mýlím?
- Honza Kuchař
- Člen | 1662
Hlásí se někdo dobrovolně o vytvoření ArrayDummyStorage
?
Na druhou stranu, přes takovýto storage už nezjistíš, co se stane, pokud
aplikaci někdo pod rukama smaže tempy. Ale pravda, je to dost málo
pravděpodobné.
- paranoiq
- Člen | 392
ke zprovoznění postačí i tohle (proti verzi 0.9.4 pro PHP 5.2):
diff --git a/Caching/DummyStorage.php b/Caching/DummyStorage.php
index 9f4d0b5..6cbb885 100644
--- a/Caching/DummyStorage.php
+++ b/Caching/DummyStorage.php
@@ -21,6 +21,11 @@
class DummyStorage extends Object implements ICacheStorage
{
+ /** @var array */
+ private $data = array();
+
+
+
/**
* Read from cache.
* @param string key
@@ -28,6 +33,8 @@ class DummyStorage extends Object implements ICacheStorage
*/
public function read($key)
{
+ if (isset($this->data[$key])) return $this->data[$key];
+
return NULL;
}
@@ -42,6 +49,16 @@ class DummyStorage extends Object implements ICacheStorage
*/
public function write($key, $data, array $dp)
{
+ if (!empty($dp[Cache::TAGS]) || isset($dp[Cache::PRIORITY]) || !empty($dp[Cache::ITEMS])
+ || !empty($dp[Cache::EXPIRE]) || !empty($dp[Cache::SLIDING])) {
+ throw new NotSupportedException('Tags, priority, dependent items, expiration and sliding expiration are not supported by DummyStorage.');
+ }
+
+ if ($data instanceof Callback || $data instanceof Closure) {
+ $data = $data->__invoke();
+ }
+
+ $this->data[$key] = $data;
}
@@ -53,6 +70,7 @@ class DummyStorage extends Object implements ICacheStorage
*/
public function remove($key)
{
+ unset($this->data[$key]);
}
@@ -64,6 +82,7 @@ class DummyStorage extends Object implements ICacheStorage
*/
public function clean(array $conds)
{
+ $this->data = array();
}
}
stejně se mi to ale celé nelíbí. je to hack a porušení SRP. úložiště se teď musí starat nejen o ukládání dat, ale i o jejich vytváření
- David Grudl
- Nette Core | 8218
paranoiq napsal(a):
stejně se mi to ale celé nelíbí. je to hack a porušení SRP. úložiště se teď musí starat nejen o ukládání dat, ale i o jejich vytváření
Souhlas, přepíšu to.
- David Grudl
- Nette Core | 8218
Aktualizoval jsem první příspěvek.
Zavolání callbacku se přesunulo mimo Storage, takže je v režii třídy Cache. Zároveň metoda save() vrací uložená data, tak se k nim dá dostat i bez nutnosti z cache číst.
- uestla
- Backer | 799
Dovolím si dotaz ohledně callbacku – většinou mám metody modelu v podobném stylu:
class ArticleModel
{
/** @var Cache */
protected $cache;
private $table = 'clanky';
// inicializace cache, apod.
public function getArticle($id)
{
$key = 'article-' . $id;
if (!isset($this->cache[$key])) {
$article = $this->connection
->select('*')
->from($this->table)
->where("[id] = %i", $id)
->fetch();
if ($article) {
$this->cache->save($key, $article, array(
Cache::EXPIRE => time() + Tools::WEEK,
Cache::SLIDING => TRUE,
));
}
} else {
$article = $this->cache[$key];
}
return $article;
}
// ...
}
Tzn., že kdybych chtěl tento způsob nahradit novým, výše doporučovaným, musel bych nějakým způsobem callbacku předat parametr $id, na kterém metoda lpí… Existuje nějaké řešení (nevím jak pro closures, ale osobně vyvíjím na PHP 5.2)?
- wdolek
- Člen | 331
David Grudl napsal(a):
Tobě to s DummyStorage nefunguje?
abych pravdu rekl, nezda se mi, ze to neco dela… pouzivam to s callbacky a mam takovy pocit, ze se callback ani nespusti, cimz nedojde ani k vytvoreni nejakych dat… takze:
- nic se mi necachuje – coz je u
DummyStorage
cilem - bohuzel se mi data z callbacku neposlou do aplikace – protoze se asi ani zadne nevyrobi
… ale musim se priznat, ze jsem to do hloubky nestudoval, mel sem na spech,
tak sem DummyStorage
simuloval neustalym restartovanim
memcached