DB wrapper s využitia súborové cache
Pri načítaní opakujúcich súboru som testoval kombináciu načítanie z databázy (v teste MySQL) a uloženie do pomocného súboru. Keďže sa niektoré načítané dáta načítajú stále dookola po každom načítaní stránky, je toto možné riešenie. Aj premenná $ _SESSION má isté limity.
Výhody tohto riešenia sú predovšetkým v cachovanie už výsledkov. Tým spolu s, dnes už bežnými, SSD disky dávajú možnosti použitia väčšej rýchlosti načítavania pri menšom zaťažení DB.
Samotná DB tiež používa vlastnú cache, ale pred tým testuje, dáta vyhľadá, triedi a kontroluje či sa dáta nezmenili. To ale zaberie istý čas, ktorý, ako dokazujú výsledky testu, napomáhajú súborové cache, ktoré dáta iba prečíta. Aj preto bol test vykonaný 1000x za sebou.
K testovaniu som použil tunajšie PDO wrapper, ktorý som si upravil na možnosť načítanie tak z DB tak zo súboru podľa zadaných parametrov. Samozrejme ide iba o jednu funkciu, ale je možné spôsob praktizovať aj na ostatných.
Súbory sa ukladajú do samostatnej zložky. Do tejto zložky je samozrejme nutné nastaviť povolenia zápisu. Do zložky sa budú ukladať súbory, kde názov súboru bude vlastný SQL dotaz, ktorý pre jedinečnosť bude natiahnutý cez MD5 () a prípona ".cache":
$cacheSoubor = self::$cacheDir . md5($dotaz) . '.cache';
Aby sa predišlo prípadným kolíziám v MD5 () - pravdepodobnosť je naozaj, ale naozaj malá, je spolu s dátami z DB, uložený aj dotaz. Ten je pri načítaní zo súboru testovaný s pôvodným dotazom a ak sa zhodujú, je považovaný za správny a odoslaný.
Veľké množstvo programov vytvorených v PHP, už cache využíva. Napríklad fórum PHPBB, redakčný systém PHP-Fusion, ale aj Nette Framework a množstvo ďalších. Prevažne ale uchovávajú celý výsledný obsah HTML.
SQL dotaz je rovnaký, ak pôjde o otázku DB:
Db:: dotazVse (' SELECT * FROM `clanky`');
Ak budete chcieť využiť súborové cache, otázka sa ľahko upraví:
Db:: dotazVse (' SELECT * FROM `clanky`', array(), TRUE);
Vlastný zdrojový kód
Tu jednotlivé základné funkcie:
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; }
Funkcia dotazVse () má tieto parametre:
- $ Pripomienky - dotaz SQL (rovnaké ako pôvodné)
- $ Parametre - parametre dotazu (rovnaké ako pôvodné)
- $ ZCache - či sa má súbor pokúsiť načítať z cache (TRUE alebo FALSE), defaultne FALSE
- $ CasChache - čas, kedy je súbor považovaný za aktuálnu (v sekundách), defaultne 30 sekúnd
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); }
Funkcia setCache () uloží súbor a funkcie getCache () načíta požadovaný súbor. Tu stojí snáď len za zmienku, že sú súbory uložené pomocou json_encode (respektíve json_decode), aby bol ich výstup vrátený ako pole. Pre podporu slovenského jazyka je využitý parameter JSON_UNESCAPED_UNICODE, ktorý podporuje UTF-8. Na snáď bolo ešte riešenie príkazom serialize (), ale ten - okrem iného - neumožňuje českej kódovania - UTF-8 (pripomienka Davida Čapka).
Pri načítaní normálneho súboru (dotazVse ()) je navyše len jedna podmienka a to, či sa majú dáta načítať z cache (if ($ zCache)). To je jediné meškanie oproti normálnemu dotazu, ktorých bude samozrejme viac. Ak je podmienka splnená, vytvorí sa názov požadovaného súboru, ktorý je otestovaný v adresári cache (tu / dbcache) a ďalej sa testuje doba za ktorú sa súbor považuje za aktuálne - defaultne 30 sekúnd. Ak podmienka platí, súbor je načítaný z adresára cache. Ak nie, je načítaný z DB a následne sa súbor uloží príp. nahradí novým.
Vykonal som otestovanie rýchlosti. V prvom rade upozorňujem, že požadovaný program bol testovaný na hostingu (konkrétne na WEDOS) a nie na lokálnom PC. Ďalej na WEDOS VPS serveri, ale výsledky boli rovnaké.
Vlastný test
Otázka bol vykonaný príkazom SQL rôzneho typu (bigint, datetime, smallint i Varchar). Jeden samotný dotaz mal 14 SQL polí a priemernú veľkosť 280 bytov. Samotný test sa skladal z 3 typu podtesty a to:
- Neupravený pôvodnej PDO ovládač
- Upravený PDO ovládač, ale výsledok iba z DB
- Upravený PDO ovládač, výsledok iba z cache
Ďalej som u každého testu vykonal postupne SQL dotaz s LIMIT 50, 30, 15, 10, 5, 3 a 1 a to všetko opakoval 1000 krát s týmito výsledkami:
Časy v sekundách / Počet otázok | 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 |
Veľkosť cache (bytov) | 12902 | 7824 | 3947 | 2624 | 1352 | 839 | 325 |
Poli v cache | 700 | 420 | 210 | 140 | 70 | 42 | 14 |
Záver
Z výsledku vyplýva pomerne vysoká rýchlosť načítanie dát zo súboru a to do maximálne 140 otázok a maximálnej veľkosti 2 500 bytov v súbore. Pri väčšom súboru rýchlosť klesá a je výhodnejšie načítavať dáta priamo z DB.
Z výsledných dát je zrejmé aj veľmi malé zaťaženie SQL dotazu, ak ide pôvodné čítanie z DB, ktorých je viac a ktorý takmer nezasiahne do celkového času. Toto by sa mohlo hodiť najmä pre weby, kde sa loguje užívateľ. Jeho ID sa uloží do SESSION (prípadne cookie), ale detaily tohto používateľa (posledný prihlásenie, adresy, individuálne nastavenie používateľa, počet článkov atď.), Sa dajú takto veľmi rýchlo a ľahko ošetriť. Naviac sa databázy nezaťažuje neustále rovnakými otázky, čo sa v prípade väčšieho počtu ľudí môže hodiť.
Poznámka
Čo sa týka veľkosti zložky s cache - pri použití príkazu register_shutdown_function ( 'xxx') vykonávam zakončovací práce (časová kontrola zmeny emailu aj zmeny hesla a podobne) a tiež príkazy na kontrolu a vyhodnocovanie chýb (o tom by som mohol napísať, ak bude záujem, nabudúce). Tiež je možné nastaviť, že sa raz za deň, hodinu, týždeň alebo mesiac adresár vymaže.