Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

DB wrapper s využití souborové cache

Při načítání opakujících souboru jsem testoval kombinaci načítání z databáze (v testu MySQL) a uložení do pomocného souboru. Jelikož se některé načtené data načítají pořád dokola po každém načtení stránky, je toto možné řešení. I proměnná $_SESSION má jisté limity.

Výhody tohoto řešení jsou především v cachování již výsledků. Tím spolu s, dnes již běžnými, SSD disky dávají možnosti použití větší rychlosti načítání při menším zatížení DB.

Samotná DB také používá vlastní cache, ale před tím testuje, data vyhledá, třídí a kontroluje zda se data nezměnili. To ale zabere jistý čas, který, jak prokazují výsledky testu, napomáhají souborové cache, která data pouze přečte. I proto byl test proveden 1000x za sebou.

K testování jsem použil zdejší PDO wrapper, který jsem si upravil na možnost načítání jak z DB tak ze souboru dle zadaných parametrů. Samozřejmě jde pouze o jednu funkci, ale je možné způsob praktikovat i na ostatní.

Soubory se ukládají do samostatné složky. Do této složky je samozřejmě nutno nastavit oprávnění zápisu. Do složky se budou ukládat soubory, kde název souboru bude vlastní SQL dotaz, který pro jedinečnost bude přetažen přes MD5() a přípona „.cache“:

$cacheSoubor = self::$cacheDir . md5($dotaz) . '.cache';

Aby se předešlo případným kolizím v MD5() - pravděpodobnost je opravdu, ale opravdu malá, je spolu s daty z DB, uložen i dotaz. Ten je při načítání ze souboru testován s původním dotazem a pokud se shodují, je považován za správný a odeslán.

Velké množství programů vytvořených v PHP, již cache využívá. Například fórum PHPBB, redakční systém PHP-Fusion, ale i Nette Framework a spousta dalších. Převážně ale uchovávají celý výsledný obsah HTML.

SQL dotaz je stejný, pokud půjde o dotaz DB:

Db:: dotazVse (' SELECT * FROM `clanky`');

Pokud budete chtít využít souborové cache, dotaz se lehce upraví:

Db:: dotazVse (' SELECT * FROM `clanky`', array(), TRUE);

Vlastní zdrojový kód

Zde jednotlivé základní funkce:

private static $cacheDir = './dbcache/';    // složka s dočasnými soubory


public static function dotazVse($dotaz, $parametry = Array(), $zCache = FALSE, $casChache = 30) {

    if ($zCache) {      //chceme nacitat z cache

        $cacheSoubor = self::$cacheDir . md5($dotaz) . '.cache';

        if (is_file($cacheSoubor) && (time() - filemtime($cacheSoubor)) < $casChache) { //test pritomnosti souboru a test doby ulozeni

        $vystupFile = self::getCache($cacheSoubor);

        if ($vystupFile[0] == $dotaz) { //kontrola shody samotneho dotazu - mozna kolize MD5

            $vystup = $vystupFile[1];

        } else { // je kolize s MD5 - nacti z DB

            $statement = self::$db->prepare($dotaz);
            $statement->execute($parametry);
            $vystup = $statement->fetchAll(PDO::FETCH_ASSOC);
        }

        } else { // soubor neni nebo je prilis stary - nacti z DB

        $navrat = self::$db->prepare($dotaz);
        $navrat->execute($parametry);
        $vystup = $navrat->fetchAll(PDO::FETCH_ASSOC);

        $vystupFile[] = $dotaz;
        $vystupFile[] = $vystup;
        self::setCache($cacheSoubor, $vystupFile);
        }

    } else { //nacti pouze z DB

        $statement = self::$db->prepare($dotaz);
        $statement->execute($parametry);
        $vystup = $statement->fetchAll(PDO::FETCH_ASSOC);

    }

    return $vystup;
}


// ulož do cache
private static function  setCache($soubor, $data) {
    $fh = fopen($soubor, 'w');
    fwrite($fh, json_encode($data, JSON_UNESCAPED_UNICODE));
    fclose($fh);
}

// čti z cache
private static function  getCache($soubor) {
    $fh = fopen($soubor, 'r');
    $nactenaData = fread($fh, filesize($soubor));
    $nactenaData = (array) json_decode($nactenaData);
    return $nactenaData;
}

Funkce dotazVse() má tyto parametry:

  • $dotaz - dotaz SQL (stejné jako původní)
  • $parametry - parametry dotazu (stejné jako původní)
  • $zCache - zda se má soubor pokusit načíst z cache (TRUE nebo FALSE), defaultně FALSE
  • $casChache - čas, kdy je soubor považován za aktuální (v sekundách), defaultně 30 sekund

Vlastní testovací program:

for ($c = 1; $c < 1001; $c++) {

    $start = microtime(1);
    $user = SDb::dotazVse('SELECT * FROM `test` LIMIT 1', array(), TRUE);
    $end = microtime(1);

    $time += number_format($end - $start, 8);

}

Funkce setCache() uloží soubor a funkce getCache() načte požadovaný soubor. Zde stojí snad jen za zmínku, že jsou soubory uloženy pomocí json_encode (respektive json_decode), aby byl jejich výstup vrácen jako pole. Pro podporu českého jazyka je využit parametr JSON_UNESCAPED_U­NICODE, který podporuje UTF-8. Na snadě bylo ještě řešení příkazem serialize(), ale ten - mimo jiné - neumožňuje české kódovaní - UTF-8 (připomínka Davida Čapka).

Při načítání normálního souboru (dotazVse() ) je navíc pouze jedna podmínka a to, jestli se mají data načíst z cache ( if ($zCache)). To je jediné zpoždění oproti normálnímu dotazu, kterých bude samozřejmě víc. Pokud je podmínka splněna, vytvoří se název požadovaného souboru, který je otestován v adresáři cache (zde /dbcache) a dále se testuje doba za kterou se soubor považuje za aktuální - defaultně 30 sekund. Pokud podmínka platí, soubor je načten z adresáře cache. Pokud ne, je načten z DB a následně se soubor uloží příp. nahradí novým.

Provedl jsem otestování rychlosti. V první řadě upozorňuji, že požadovaný program byl testován na hostingu (konkrétně na WEDOS) a ne na lokálním PC. Dále na WEDOS VPS serveru, ale výsledky byly stejné.

Vlastní test

Dotaz byl vykonán příkazem SQL různého typu (bigint, datetime, smallint i Varchar). Jeden samotný dotaz měl 14 SQL polí a průměrnou velikost 280 bytů. Samotný test se skládal z 3 typu podtestu a to:

  1. Neupravený původní PDO ovladač
  2. Upravený PDO ovladač, ale výsledek pouze z DB
  3. Upravený PDO ovladač, výsledek pouze z cache

Dále jsem u každého testu provedl postupně SQL dotaz s LIMIT 50, 30, 15, 10, 5, 3 a 1 a to vše opakoval 1000 krát s těmito výsledky:

Časy v sekundách/Počet dotazů 50 30 15 10 5 3 1
Původní PDO 0,465 0,361 0,283 0,262 0,231 0,224 0,201
Upravené PDO-z DB 0,477 0,367 0,283 0,280 0,249 0,228 0,210
Upravené PDO-z cache 0,950 0,590 0,331 0,213 0,121 0,085 0,068
Velikost cache (bytů) 12902 7824 3947 2624 1352 839 325
Poli v cache 700 420 210 140 70 42 14

Výsledné hodnoty převedené do grafu:

Výsledný graf naměřených hodnot - Soubory a práce s nimi v PHP

Závěr

Z výsledku vyplývá poměrně vysoká rychlost načítání dat ze souboru a to do maximálně 140 dotazů a maximální velikosti 2 500 bytů v souboru. Při větším souboru rychlost klesá a je výhodnější načítat data přímo z DB.

Z výsledných dat je zřejmé i velmi malé zatížení SQL dotazu, pokud se jedná původní čtení z DB, kterých je více a který téměř nezasáhne do celkového času. Toto by se mohlo hodit zejména pro weby, kde se loguje uživatel. Jeho ID se uloží do SESSION (případně cookie), ale detaily tohoto uživatele (poslední přihlášení, adresy, individuální nastavení uživatele, počet článků atd.), se dají takto velmi rychle a snadno ošetřit. Navíc se databáze nezatěžuje neustále stejnými dotazy, což se v případě většího počtu lidí může hodit.

Poznámka

Co se týče velikosti složky s cache – při použití příkazu register_shut­down_function('xxx') provádím zakončovací práce (časová kontrola změny emailu i změny hesla a podobně) a také příkazy na kontrolu a vyhodnocování chyb (o tom bych mohl napsat, pokud bude zájem, příště). Také je možné nastavit, že se jednou za den, hodinu, týden nebo měsíc adresář vymaže.


Galerie

Program byl vytvořen v roce 2015.

 

Předchozí článek
Kvíz - Soubory v PHP
Všechny články v sekci
Soubory a práce s nimi v PHP
Program pro vás napsal Pavel
Avatar
Uživatelské hodnocení:
1 hlasů
...
Aktivity