Cache-ul este o zonă temporară de stocare de informatii, care duplică niste date considerate originale, creat pentru acces direct si rapid asupra acestor date. Aceasta mapare a datelor faţă de locul lor de stocare iniţial, se face pentru  acele date care sunt greu de accesat în mod direct, care se afla în zone partajate, care ar avea un timp de prelucrare ridicat si care ar fi solicitate în mod frecvent, iar rezultatul ar fi de fiecare dată acelaşi.

Sisteme de cache se întalnesc implementate în microprocesoare(exemplul fiind cache-ul de nivel 2), în hard disk-uri(pentru a micşora timpul de citire al datelor), în sisteme de management al bazei de date(MySQL va ţine în cache rezultatul unei interogari, iar la primirea aceleiaşi interogări va returna rezultatul din cache, făra a interoga tabelele în sine), in browser-ele pe car ele folosim zi de zi(Firefox de exemplu îşi va face cache la imagini, ca la un refresh sa nu fie nevoit sa le preia de pe server din nou), chiar şi Google deţine propriul cache din care poate furniza conţinutul paginilor web.

Aşadar, scopul declarat al cache-ului este de a economisi timp.

Teoria Cache-ului

Există cateva concepte cheie în teoria cache-ului care trebuiesc respectate în implementarea unui astfel de sistem

  1. identificatorul unic – care va fi folosit la identificarea elementului în cache
  2. durata de viaţă – defineşte cât timp un element din cache va fi considerat valid
  3. preluarea condiţionată – astfel încat părţile din cod care ar accesa informaţiile originale să fie evitate, dar să si permită reîmprospătarea
  4. resetarea la cerere – pentru păstrarea consistenţei informaţiilor din cache cu cele din locaţia originală, este necesară posibilitatea ca la o modificare a datelor din această locaţie, cache-ul sa fie marcat ca invalid şi reconstruit

Astfel, un algoritm general de folosire a cache-urilor ar fi:

  • dacă elementul din cache cerut de aplicaţie există atunci el va fi returnat intocmai
  • dacă elementul din cache cerut nu există atunci datele acelui cache se vor aduce din locaţia originală, se va crea elementul corespunzător în cache, iar datele vor fi returnate aplicaţiei

Aplicare

Aşadar, vom presupune o apicaţie web, pentru care avem un număr mare de accesări atât din partea vizitatorilor dar şi din partea celor care administrează respectivul website. Pentru o şi mai buna exemplificare, vom considera cazul standard al unui magazin online, în care avem listări de categorii, listări de produse din fiecare categorie şi afisări detaliate de produse(preţ, descriere, detalii tehnice etc). În spatele site-ului, respectiv în aplicaţia de administrare a acestuia, avem un număr de operatori care lucrează necontenit la imbunătăţirea informaţiilor prezentate pe acel site. Mai mult decât atât, să mai luam în calcul existenţa unor aplicaţii care periodic sincronizează preţurile şi stocurile produselor cu cele existente la furnizorii direcţi. Pentru a îmbunătăţi imaginea de ansamblu să considerăm ca magazinul are câteva zeci de mii de produse. În cuvinte mult mai simple şi mai tehnice: o mulţime de interogări sql de tip insert, update dar mai ales select.

Dezavantajul unui astfel de scenariu este evident cel al supraîncărcării bazei de date cu interogări care de cele mai multe ori se vor repeta şi vor furniza acelaşi set de date. Cu toate că, de exemplu, MySQL deţine un cache propriu din care returnează un set de date al unei interogari la o repetare a acesteia, aplicaţia PHP care interogheză baza de date va trebui sa realizeze tot protocolul de comunicare, să furnizeze interogarea şi să primească datele, deci nişte timpi deşi mici, deloc de neglijat în contextul unui volum de trafic ridicat.

Continuând scenariul nostru de “groază” mai trebuie luat în considerare faptul ca la o interogare de tip insert sau update, cele cauzate de aplicaţia de administrare, pot apărea lock-uri pe câmpurile, înregistrările sau chiar tabelele din baza de date, deci până la terminarea execuţiei şi scrierea ori modificarea cu succes a datelor în bază, o instrucţiune select, nu va putea citi baza de date pentru preluarea informaţiilor şi va fi pusă în aşteptare până la terminarea tranzacţiei. Rezultă un timp mort şi mai mare. Cache-ul ar trebui sa intervină în astfel de momente, când putem spune ca majoritatea interogarilor vor furniza acelaşi set de date pentru perioade definite de timp,  iar rularea lor nu ne-ar aduce decât dezavantaje.

Interogând baza de date pentru a obţine informaţiile despre produsele din categoria “Monitoare LCD” vom obţine unset de date reprezentat printr-un vector cu produsele din acea categorie. Datele din acest vector pot fi introduse în cache. Conform algoritmului general descris mai sus putem scrie urmatorul cod PHP

if (!($data = loadFromCache('cache_for_category_id_' . $categoryId)))
{
    $data = loadDataFromDatabase($categoryId);
    saveToCache('cache_for_category_id_' . $categoryId, $data, 3600);
}

Plecăm astfel de la premisa ca informaţia căutată se afla în cache şi chiar încercăm să o preluăm. Dacă functia loadFromCache() va întoarce o valoare nulă atunci înseamnă ca datele nu se află în cache şi ele vor trebui aduse din baza de date, lucru ce se va face prin functia loadDataFromDatabase() iar apoi salvate în cache cu ajutorul funcţiei saveToCache(), cache valabil o ora. Chiar dacă presupunerea noastră iniţială referitoare la existenţa datelor în cache este adevărată sau nu, după executarea acestei porţiuni de cod vom avea în variabila $data informaţiile necesare.

Trebuie avut în vedere faptul că în tot acest timp datele considerate valide sunt cele din baza de date, cache-ul fiind doar o copie locala a acesteia. Deşi sistemul ne va reseta automat cache-ul după expirarea perioadei de viaţă, vor exista situaţii când cache-ul va deveni inconsistent, adică nu va mai reflecta realitatea din baza de date. Deci, la adăugarea unui produs nou în baza de date în categoria “Monitoare LCD”, cache-ul construit mai devreme nu mai este consistent(nu conţine si acest nou produs). Cum varianta în care aşteptăm trecerea celor 3600 de secunde pentru a se recrea cache-ul nu ne multumeşte(perioada putând fi mult mai mare), aplicaţia de administrare va trebui sa intervină asupra cache-ului şî să invalideze înregistrarea ce conţine datele din această categorie. În acest mod vom forţa recreerea cache-ului cu noile informaţii la următoarea accesare a categoriei respective.

resetCache('cache_for_category_id_' . $categoryId);

În tot scenariul de mai sus am considerat crearea de cache-uri pe categorii şi nu unul global care să conţină toate categoriile existente pe site, din considerente de acces si de resetare. Este mai simplu sa alegem direct cache-ul categoriei pe care dorim să o afişăm decât sa încărcăm toate categoriile prin care să o căutăm pe cea dorită, precum este mai normal ca la introducerea produsului nou în categorie să resetăm doar cache-ul categoriei respective şi nu cel al tuturor produselor.

Unelte

PHP nu deţine nativ funcţii de lucru cu cache-ul, însă există extensii PECL care pot fi instalate şi cu care se pot lucra, printre care enumerăm Memcache şi APC.

Folosirea extensiei Memcache:

$cache = new Memcache();
$cache->addServer('localhost');

if(!($data = $cache->get('cache_id'))
{
    $data = getData();
    $cache->add('cache_id', $data);
}
$cache->delete('cache_id');

Folosirea extensiei APC:

if(!($data = apc_fetch('cache_id'))
{
    $data = getData();
    apc_add('cache_id', $data);
}
apc_delete('cache_id');

Diferenţa dintre cele 2 extensii este aceea că Memcache va stoca informaţiile în memoria RAM a serverului, pe când APC le va stoca in fişiere pe hard disc.

UPDATE:

A Practical Guide to Data Caching with Zend Server scrisă de Shahar Evron, Product Manager la Zend Technologies, Inc., este o lucrare apărută recent şi pe care o recomandăm celor interesati de acest subiect.

Până la un subiect mai serios, care se află deja în scriere, vin cu o problemă, ce se adresează celor care se considera începători. Mi s-a întamplat sa dau această “problemă” unora de nivelul celui menţionat mai devreme, iar răspunsurile să  mă surprindă de fiecare dată.

Aşadar, dându-se un array (vector) cu 2 milioane de elemente, indexate corespunzător de la 0 la 1.999.999 cu valori de tip string, să zicem sub 1 KB ca dimensiune. 

Scrieţi o funcţie care să determine dacă vectorul în cauză este sau nu mulţime. Evident, se cere un timp de execuţie al funcţiei minim

Un vector este considerat mulţime dacă are toate valorile distincte