MySQL performance boost met array caching
Wanneer je verschillende en misschien nogal zware queries moet laden op één pagina, maak je best gebruik van caching. In deze blogpost bespreken we array caching, hierbij gaan we onze databaseresults in een array plaatsen en vervolgens opslaan op het bestandssysteem. Dit heeft als voordeel dat je veel flexibeler kan omgaan met je gegevens dan wanneer je bijvoorbeeld volledige HTML-pagina’s gaat cachen.

De functies
Hieronder bespreek ik in het kort de verschillende functies die het je mogelijk maken om te werken met array caching.
Set Cache
Voordat we onze array gaan opslaan op het bestandssysteem, gaan we deze eerst serializen. Dit maakt van onze array een leesbare string. Deze string gaan we vervolgens wegschrijven door middel van file_put_contents. Als bestandsnaam voor onze cache gebruiken we een groepsnaam en cache-id. Onze groepsnaam kan bijvoorbeeld ‘artikels’ zijn, waar het cache-id dan het uniek id van één artikel is. Omdat we ook willen dat onze cache na een bepaalde tijd automatisch zou vervallen, geven we deze ook nog een bewerkingsdatum mee door middel van de touch-functie.
public function setCache($group, $id, $ttl, $data)
{
$filename = "/cache/".$group."_".md5($id);
if ($fp = fopen($filename, 'wb')){
if (flock($fp, LOCK_EX)){
file_put_contents($fp, $data, );
}
fclose($fp);
touch($filename, time()+$ttl);
}
}
Get Cache
Nu onze array is opgeslagen op het bestandssysteem, hebben we natuurlijk ook de mogelijkheid om deze weer te gaan ophalen. Dit doen we door middel van de file_get_contents functie.
public function getCache($group, $id)
{
$filename = "/cache/".$group."_".md5($id);
return file_get_contents($filename);
}
Is Cached
Om te controleren of er een geldige cache bestaat, maken we gebruik van file_exists. Hierna doen we ook controle of de cache niet verouderd is en vervolgens verwijderd mag worden.
public function isCached($group, $id)
{
$filename = "/cache/".$group."_".md5($id);
if (file_exists($filename) && filemtime($filename) > time()){
return true;
}
@unlink($filename);
return false;
}
Destroy Cache
Indien de content in de array niet meer up-to-date zou zijn, kan je deze best verwijderen. Hierna zou er dan een nieuwe cache met verse data aangemaakt kunnen worden. Bij het verwijderen van de cache heb je de keuze of je één enkele cache of een volledige groep wil verwijderen. Bij een groep verwijder je dus alle artikels die we eerder hebben aangemaakt. We verwijderen onze cache-bestanden met behulp van glob en de unlink-functie.
public function destroyCache($group, $id = false)
{
if($id){
$filename = "/cache/".$group."_".md5($id);
@unlink($filename);
}
else{
$loc = "/cache/".$group."_*";
foreach (glob($loc) as $filename) {
@unlink($filename);
}
}
}
In de praktijk
Onderstaande voorbeelden geven aan hoe je dit het best in de praktijk kan toepassen.
Front-end
Eerst controleren we of er een cache bestaat van ons artikel. Indien er een geldige cache beschikbaar is, kunnen we deze rechtsreeks gebruiken zonder database-queries. Indien er geen cache beschikbaar is of indien deze verouderd zou zijn, gaan we al onze data ophalen in de database. Vervolgens gaan we deze data opslaan in onze cache en geven we hem een 'time to live' mee van 2u.
if (!$article = Cache::getCache("articles", $id)) {
$article = model('Articles/Articles')->get($id);
$article['tags'] = model('Tags/Tags')->get('article',$id);
Cache::Put('articles', $id, 7200, $article);
}
print_r($article);
Back-end
Als we een artikel aanpassen gaan we de cache van dat bepaalde artikel verwijderen. Indien je niet beschikt over het id van het artikel of als je een reeks artikels moet verwijderen, kan je best een volledige cache groep verwijderen.
Cache::Destroy("articles", $id);
Voor- & nadelen
Voordelen
- Het is snel en gemakkelijk te implementeren
- Er is geen extra software zoals Memcached nodig
- Je kan zeer flexibel omgaan met de gecachte data. Bijvoorbeeld één bepaalde cache/array kan je totaal anders gaan weergeven op verschillende pagina's.
- Je merkt een groot snelheid verschil in de positieve zin bij een grotere query.
Nadelen
- Bij één simpele query kan het soms sneller zijn om deze niet te cachen en gewoon rechtstreeks uit te lezen in de database.
- Indien je data zeer frequent aangepast wordt, moet je er zeer correct op toezien dat je cache tijdig en correct leegemaakt wordt.
- Je moet hiermee rekening houden in je volledige code en de caching wordt dus best ook geïmplementeerd in de ontwikkelingsfase.
Array caching kan dus best enkel toepast worden op grotere queries. Indien je beschikt over een gecachte array is je data veel sneller beschikbaar en heb je nog steeds alle mogelijkheden om deze op verschillende manieren in verschillende pagina's weer te geven. De database-load zal ook drastisch verlagen omdat deze zware queries niet meer zo frequent uitvoerd worden. Hierdoor zullen de kleinere queries ook sneller hun weg vinden naar de front-end.
Heb jij al ervaring met array caching? Tips en suggesties zijn ook altijd welkom!



12 reacties tot nu toe
Jan Boden zei 44 weken geleden:
Frank zei 44 weken geleden:
Tom Claus zei 44 weken geleden:
Thijs Feryn zei 44 weken geleden:
De codebase van Zend_Cache (of gelijkaardige componenten van andere frameworks) wordtrouwens maintained door een volledig community, terwijl jullie dit nu zelf moeten onderhouden.
Ik blijf toch voorstander van Memcached als caching backend,maar als je dit liever niet gebruikt, kun je gewoon memory tables gebruiken in je database. Bijvoorbeeld SQLite::memory of de Memory storage engine van MySQL
En zoals anderen het hier ook aanhalen: MySQL query caching is waarschijnlijk wel sneller dan manuele filecaching. Natuurlijk is dit op voorwaarde dat je query cache goed getuned is.
Het is wel zo dat dit een geldige, simpele en makkelijk te beheren oplossing is. Maar het is helaas oud nieuws en wordt standaard ondersteund door alle PHP frameworks.
David Candreva zei 44 weken geleden:
De snelheid van een query zal door het gebruik van deze class inderdaad niet versnellen. Voor de bezoeker lijkt het wel alsof data uit MYSQL sneller retourneert.
Dirk Bonhomme zei 44 weken geleden:
@Thijs Ook voor mensen met een ander (of geen) framework zijn de voorbeelden van Tom snel toe te passen. Al is het maar om beter te begrijpen hoe dit er achter de schermen aan toe gaat bij de ongetwijfeld veel complexere library van Zend.
Jurriaan Persyn zei 44 weken geleden:
Tip: je kan elk object/type serializen, dus ook elk object/int/string cachen, niet enkel arrays.
Chris R. zei 44 weken geleden:
Misschiens dat we binnekort opnieuw een blogpost schrijven over verschillende caching technieken maar dat is niet de bedoeling van deze post.
Al moet ik toegeven dat de titel een beetje misleidend is, je gaat niet de snelheid van je database boosten maar de snelheid van je website door de database net te omzeilen. Maar het resultaat is in de meeste gevallen hetzelfde toch? :)
Jan Boden zei 44 weken geleden:
Uiteraard is het resultaat hetzelfde, maar deze optie hadden we, zoals Thijs al aangaf, al in gebruik. Vol ongeduld wacht ik al enkele weken/maanden op een verhelderende blogpost over mysql caching. Ik had gehoopt dat deze die blogpost ging worden bij het lezen van de titel, vandaar dat ik op mn honger bleef zitten :-)
Kim zei 44 weken geleden:
Natuurlijk bestaan er, zoals eerder gezegd, betere oplossingen maar om die technieken goed te implementeren heb je meestal wel wat tijd nodig.
Pieter Maes zei 44 weken geleden:
Aangezien je met filecache extra IO gaat genereren + wat als je met meerdere servers werkt? dan ben je afhankelijk van nfs of dergelijken.. die dan caches fouten kunnen doen bevatten.
Het nog leuke van memcache is, je kan niet alleen array's makkelijk storen, zelfs ganse objecten ;)
trust me, i have done both ;)
Chris R. zei 44 weken geleden:
Als je het object nadien uit de cache haalt en deserialized kan je zonder al te veel moeite die resources opnieuw herstellen.