english English

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.

Array caching

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!

RSS reacties feed

12 reacties tot nu toe

Jan Boden

Jan Boden zei 2 jaar geleden:

Heeft eigenlijk weinig tot niks met mysql zelf te maken, of zie ik dat verkeerd? Je gaat cachen of een query doen, t is niet dat er iets met de caching van mysql zelf gebeurd?
Frank

Frank zei 2 jaar geleden:

Heeft inderdaad niks met mysql te maken lijkt me.
Tom Claus

Tom Claus zei 2 jaar geleden:

@Jan & @frank : Je bent inderdaad niet verplicht dingen uit je MySQL databank te cachen. Maar aangezien wij bij Inventis voornamelijk met een LAMP opstelling werken is het voor ons bijna altijd van toepassing op gegevens uit een MySQL database.
Thijs Feryn

Thijs Feryn zei 2 jaar geleden:

Dit is standaard file cache. Op zich kan je dit ook met Zend_Cache doen en daar zitten nog tal van extra features bij ook. Als je plots je backend wil switchen naar Memcached, SQLite of MySQL, moet je weinig tot geen aanpassingen doen.

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

David Candreva zei 2 jaar geleden:

Je geeft de MYSQL-server wel een "boost" door de load te beperken. Dit is een goed systeem om ervoor te zorgen dat de MYSQL-server niet overbelast geraakt als er plots een groot aantal bezoekers op je website komt.

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

Dirk Bonhomme zei 2 jaar geleden:

Deze blogpost is een handige aanvulling op de presentatie van Chris over website performance (http://www.slideshare.net/chrisramakers/website-performance). De grootste bottleneck bij drukke/grote sites is vaak de database. Na het leggen van gepaste indexen en optimaliseren van queries is array caching vaak een goede oplossing om de algemene performance een boost te geven.

@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

Jurriaan Persyn zei 2 jaar geleden:

Moet het al niet een erg imperformante query zijn eer MySQL trager gaat zijn dan disk access? Wat is er mis met memory caching dat je zou willen gaan cachen in een flat file? (Mysql query caching & memcached zijn zoveel betere oplossingen dat file based.)

Tip: je kan elk object/type serializen, dus ook elk object/int/string cachen, niet enkel arrays.
Chris R.

Chris R. zei 2 jaar geleden:

@Iedereen, ik denk dat jullie de scope van dit artikel een beetje uit de context rukken. Tom licht toe hoe je makkelijk een filecache kan gebruiken als je website onder plotse load komt te staan. Jurriaan en thijs hebben een heel goed punt als je het hebt over memory caching met bijvoorbeeld memcache of query caching op mysql server niveau en dat deze performanter zijn maar zoals je weet zijn die caching technieken niet op 1-2-3 opgezet.

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

Jan Boden zei 2 jaar geleden:

@chris,

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

Kim zei 2 jaar geleden:

Ook moet je rekening houden met de limitaties van memcached, het systeem op zicht is prachtig en immens performant, maar dit configureren op een shared hosting is bijna onmogelijk (security is bijna onbestaande). Deze snelle en eenvoudige oplossing van Tom is daar wel geschikt voor. Sqlite in memory databases zijn inderdaad zeer snel, maar eens php gedaan heeft met de http request gaat de database ook weg zijn, dus in memory sqlite databases zijn volgens mij ook geen optie hier...

Natuurlijk bestaan er, zoals eerder gezegd, betere oplossingen maar om die technieken goed te implementeren heb je meestal wel wat tijd nodig.
Pieter Maes

Pieter Maes zei 2 jaar geleden:

Ik denk dat Memcache toch net iets perfomanter is.
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.

Chris R. zei 2 jaar geleden:

Pieter, objecten cachen gaat perfect met een filecache hoor. Met de __sleep() en __wakeup() methods van PHP5 classes kan je zelfs properties die niet te cachen vallen (bv resources als database connecties) cachen in eender welk cachesysteem als je het object serialized.

Als je het object nadien uit de cache haalt en deserialized kan je zonder al te veel moeite die resources opnieuw herstellen.

Reageer op dit artikel

Toegelaten tags: <a href="" title=""> <code> <em> <strong>

RSS Feed

Bekijk alle tags

Laatste reacties

  • Tom Claus: @Filip Bedankt voor de tip, CouchDB gaan we zeker even mee bekijken. @Fabio Deze...
  • Fabio Maggio: Is die presentatie van Masterizing PHP Data Structure ook nog ergens te bekijken?
  • Tom Hermans: Thx Tom, schone samenvatting en een massa interessante links, ideaal voor mensen die die dag...
  • Filip Stas: Als MongoDb je al boeit zeker ook eens kijken naar couchbase ook zeker de moeite!
  • Dragolin: Wij proberen in 2012 alvast sportief Vlaanderen warm te maken voor sportieve experiences :)