[2010–02–11] Cache a ukládání „callbacků“ kvůli dramatickému zrychlení

před 10 lety

David Grudl
Nette Core | 6884
+
0
-

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.

před 10 lety

pekelnik
Člen | 468
+
0
-

nice :)

před 10 lety

Patrik Votoček
Člen | 2249
+
0
-

wow tak to je mazec! musím kouknout jak je to implementováno porotože to je hodně cool hračička…

před 10 lety

Honza Kuchař
Backer | 1655
+
0
-

super! Přesně takhle jsem si to představoval! :-)

Editoval honzakuchar (13. 2. 2010 16:23)

před 10 lety

v6ak
Člen | 206
+
0
-

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ší.

před 10 lety

David Grudl
Nette Core | 6884
+
0
-

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.

před 10 lety

v6ak
Člen | 206
+
0
-

Aha, to je ale celkem důležité zmínit, protože jinak je to celkem WTF chování.

před 10 lety

Honza Kuchař
Backer | 1655
+
0
-

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. :-)

před 10 lety

norbe
Backer | 404
+
0
-

Obalovat closuru callbackem mi přijde poněkud zbytečný…

před 10 lety

Majkl578
Moderator | 1379
+
0
-

Closuru určitě nedávat do callback(), nemá to význam.

před 10 lety

Honza Kuchař
Backer | 1655
+
0
-

Teď mně tak napadá, toto asi nebude fungovat s DummyStorage? Nebo se mýlím?

před 10 lety

paranoiq
Člen | 388
+
0
-

nemýlíš. nefunguje. asi bude třeba místo ní udělat nějaké dočasné úložiště (prostě pole)

před 10 lety

Honza Kuchař
Backer | 1655
+
0
-

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é.

před 10 lety

paranoiq
Člen | 388
+
0
-

vlastně bude úplně stačit, když se současné DummyStorage upraví, aby si pamatovalo callbacky (a nic jiného). večer pošlu patch

před 10 lety

paranoiq
Člen | 388
+
0
-

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í

před 10 lety

David Grudl
Nette Core | 6884
+
0
-

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.

před 10 lety

David Grudl
Nette Core | 6884
+
0
-

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.

před 10 lety

Honza Kuchař
Backer | 1655
+
0
-

Tak takto je to dokonalé! :-)

před 10 lety

jasir
Člen | 748
+
0
-

Je to pecka. Díky.

před 10 lety

paranoiq
Člen | 388
+
0
-

jo taky děkuju :]

mimochodem, http://cz.php.net/…-through.php

před 10 lety

uestla
Backer | 747
+
0
-

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)?

před 10 lety

v6ak
Člen | 206
+
0
-

Existuje, ale je to složitější. Jako callback lze totiž použít array(instance, ‚názevNetody‘), jen ho musíš přeložit.

před 9 lety

wdolek
Člen | 331
+
0
-

ten patch se zatim nejak neobjevil nikde, ne? (stahl jsem si 0.9.5 i 1.0 pro PHP3 a kde nic tu nic… ani v api dokumentaci)

před 9 lety

David Grudl
Nette Core | 6884
+
0
-

Tobě to s DummyStorage nefunguje?

před 9 lety

wdolek
Člen | 331
+
0
-

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:

  1. nic se mi necachuje – coz je u DummyStorage cilem
  2. 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