Simple PHP Cache
Avand in vedere ca nu am mai avut chef sa lucrez in seara asta, m-am gandit sa postez despre o chestie la care am lucrat in trecut si care, cel putin in cazul meu, mi-a fost de folos.
Este vorba de o solutie simpla si home-made de a face cache la anumite date in PHP. Cel mai simplu exemplu ar fi urmatorul: ai o pagina cu un articol, pagina respectiva se modifica destul de rar, dar e accesata destul de des. Ce rost ar avea sa preiei/procesezi din nou informatiile de fiecare data cand e accesata pagina, cand ai putea sa salvezi datele si sa le trimiti direct catre partea ce se ocupa cu afisarea lor. Sper sa nu gresesc, dar daca am vorbi despre o arhitectura Model-View-Controller in PHP, practic sari in mare parte peste Model.
Stiu ca exista solutii gata facute pentru cache, majoritatea framework-urilor au asa ceva, exista un pachet PEAR pentru cache, dar cred ca e mai fun sa incerci sa faci unele chestii de unul singur, poti invata multe lucruri noi si poti sa controlezi fiecare aspect a ceea ce vrei sa realizezi.
Revenind acum la subiectul principal. Deoarece am avut nevoie de o solutie simpla de a face cache la anumite pagini, am facut cu ceva timp in urma o clasa destul de simpla ce permite sa stochez informatiile pe care le afisez, fara a le mai mesteca inca o data.
(1. Vreau pagina x) -> (2.1. Macina datele primite -> 2.2 Returneaza datele) -> (3. Afiseaza continutul)
Schema de mai sus putem spune ca e ceea ce se intampla in mod normal. Punctul 2. este de obicei cel care consuma si foloseste cele mai multe resurse. Cred ca deja se poate vedea directia pe care vreau sa merg. De ce nu am incerca sa scoatem punctul 2.1? Nu are rost ca acelasi proces sa se repete de foarte multe ori, mai ales cand datele returnate sunt aceleasi. Ce facem mai departe? Luam datele “macinate”, le salvam, iar data viitoare cand vom mai avea nevoie de ele, le luam direct din cache, fara a mai repeta inca o data procesul cel mai greoi.
Solutia a fost destul de eficienta pentru mine. Nu am testat chiar atat de serios si nu am testat eficienta decat pe un computer de test, dar rezultatul a fost destul de satisfacator.
Primele doua valori din grafic nu sunt reprezentative, deoarece am testat la rece sistemul, nici varful acela de la seria fara cache. Cu toate acestea, este clar ca la prima accesare, in cazul de fata, momentul in care s-au salvat datele in cache, timul de procesare si afisare a paginii a fost mai ridicat, dar pe parcurs, facandu-se media, se poate vedea ca solutia a fost eficienta.
Clasa are 3 metode principale: constructorul, get si set, care sunt prezentate pe scurt mai jos.
In constructor, in cazul in care exista date de configurare (directorul, timeout si active), luam datele si le salvam, apoi se verifica daca putem scrie in directorul in care salvam fisierele, daca nu se poate, nu se poate.
public function __construct($config = array()) { if (count($config)) { foreach($config as $var => $value) if (in_array($var, $this->config)) $this->$var = $value; } if (!is_writable($this->dir)) { throw new Exception('Cache directory not writable.'); $this->active = false; } }
set() e folosita pentru a salva datele. Se verifica daca id-ul este ok, mai verificam directorul in care scriem, verificam si daca putem sa scriem fisierul si salvam datele. Nimic mai simplu.
public function set($id, $data) { if (!$this->active) return false; $this->data = $data; if (preg_match("/(^[a-zA-Z0-9_-]{3,64}$)/", $id)) { $cache_file = $this->dir.$id; } else { throw new Exception('Invalid cache ID'); return false; } if (!is_dir($this->dir)) { throw new Exception('Cache directory not available'); return false; } else { if (!$handle = fopen($cache_file, 'w')) { throw new Exception('Cannot open file'); return false; } else { if (fwrite($handle, serialize($this->data))===false) { throw new Exception('Cannot write to file'); return false; } fclose($handle); } } return true; }
get() e folosita pentru a prelua datele de cache. Facem din nou niste verificari, daca fisierul exista si e valid (din punct de vedere al timeout-ului), preluam datele si le returnam.
public function get($id) { if (!$this->active) return false; if (preg_match("/(^[a-zA-Z0-9_-]{3,64}$)/", $id)) { $cache_file=$this->dir.$id; } else { throw new Exception('Invalid cache ID'); return false; } if (($this->active)&&(file_exists($cache_file))) { if ((time()-filemtime($cache_file))>($this->timeout*60)) { unlink($cache_file); return false; } $this->data = unserialize(@file_get_contents($cache_file)); $this->hit++; return $this->data; } }
Am sa postez si un mic exemplu, care poate parea cam greoi, ca si cum ai omora o musca cu pusca, dar nu mi-a venit pe moment o idee de un exemplu mai complex.
function salut() { $mesaj = "Salut, uite ora la care m-au executat :". date("Y-m-d H:i:s"); return $mesaj; } try { $cache = new Cache(); if (!$mesaj = $cache->get("f-salut")) { $mesaj = salut(); $cache->set("f-salut", $mesaj); } echo $mesaj; } catch (Exception $e) { echo 'Ceva n-a mers: '. $e->getMessage(); }
Clasa, impreuna cu un exemplu, poate fi downloadata de aici: PHP Simple Cache. Un exemplu live poate fi gasit aici, dar e cu doua ore in urma pentru ca am uitat sa setez fusul orar . In exemplul respectiv se poate vedea data la care a fost geneerat fisierul de cache, data se actualizeaza la fiecare 15 minute, adica la durata de viata setata in acest moment pentru cache.
In cazul in care cineva are sugestii, critici sau orice fel de comentarii, le primesc pe toate cu placere.
O mica sugestie: ce face mecanismul tau cand se fac 2 set-uri asupra unui acelasi element din 2 request-uri HTTP diferite? Lucru destul de probabil cand ai un site web “busy”. Eu cred ca atunci esti la “mila” sistemului de fisiere, si ca rezultatul e imprevizibil.
O alta mica sugestie: scrierea pe disc este destul de ineficienta in general. In primul rand fiindca discurile se misca incet (chiar daca datele scrise de tine vor ramane o perioada in memorie la un sistem de operare modern), si apoi fiindca si filesystem-ul are si el un overhead destul de mare.
Baietii de la memcache s-au gandit de la problema asta si au rezolvat-o destul de elegant. Eu te felicit pentru initiativa de a te “murdari” pe maini si a vedea ce-i in spatele mecanismelor, e un lucru pe care nu-l mai face cata lume ar trebui
Concluzia mea este ca trebuie sa existe un echilibru intre experimentele personale si reutilizarea solutiilor consacrate (de genul memcache). Solutia aleasa depinde mult de scenariul in care o folosesti.
Da, esti la mila sistemului de fisiere.
In mare nu e o solutie ce poate sa fie folosita la scara larga, a fost ceva pentru a vedea cum merg unele lucruri si cum se poate imbunatati in unele cazuri performanta.
Intotdeauna e recomandat sa folosesti unele solutii gata facute, cum e si memcache. Dar uneori e bine si sa experiemntezi si sa incerci tot felul de solutii suplimentare, pentru ca altfel cum mai inveti? Ma gandeam ca pe viitor sa trec de la stocarea datelor pe hdd la stocarea lor direct in memorie, asta doar ca sa experimentez in continuare si fara sa incurajez lumea sa foloseasca solutia asta.