Wat is 'ie snel

Door E-sites, E-sites
27 april 2012 - 2092 x bekeken - Categorieën: Tech

In september 2010 is de vernieuwde versie van www.sport1.nl gelanceerd. Een fris design en een duidelijke, eenvoudige structuur. De klant is er blij mee, wij zijn er blij mee en de rest van Nederland blijkbaar ook – hoewel de bezoekersaantallen al verre van slecht waren liepen deze na de lancering flink op.

Een paar maanden later werd ook de mobiele variant (m.sport1.nl) vernieuwd met ook hier als doel een eenvoudig te gebruiken site. Ook hier gingen de bezoekersaantallen nog verder omhoog.

De 'gewone' en mobiele site draaien op hetzelfde platform met dezelfde onderliggende code. We hebben één webserver en één database server in gebruik. Moet genoeg zijn toch? In eerste instantie wel, maar door de toenemende bezoekersaantallen èn de steeds groter wordende dataset werd de site tijdens piek uren toch wel erg traag – soms werden de servers zelfs zo erg belast dat één van de twee er niet zo'n zin meer in had, waardoor de website niet meer bezocht kon worden.

Onlangs kregen Joris en ik een week de tijd om Sport1 helemaal kapot te optimaliseren, zowel de front-end als de back-end. Als rasechte back-ender ga ik het hier niet over de front-end hebben – daarvoor moet je bij Joris zijn.

Als je iets gaan optimaliseren is de eerste stap om te kijken wat er dan niet optimaal is. Zowel Apache (webserver) en MySQL (database) gaven ten tijden van grote drukte aan dat zij teveel verbindingen te verwerken hebben. De eerste stap is dan kijken wat er dan precies problemen geeft. Om dit tegen te gaan kun je twee dingen doen: zorgen dat de betreffende server meer verbindingen aankan, of zorgen dat er minder verbindingen tegelijk nodig zijn.

Zorgen dat de server meer verbindingen aankan, is veelal een kwestie van meer hardware tegen het probleem aangooien. Een snelle, eenvoudige en verre van schaalbare oplossing. Dit is een leuke quick win, maar op lange termijn nou niet bepaald ideaal.

Het aantal verbindingen verminderen kan op twee manieren: Bij teveel bezoekers deze nieuwe bezoekers in een wachtrij zetten tot het wat rustiger wordt of zorgen dat een verbinding simpelweg minder lang open hoeft te blijven zodat er bij hetzelfde bezoekersaantal minder verbindingen tegelijk nodig zijn.

Voor MySQL is dit redelijk simpel: Maak je queries zo snel mogelijk en doe zo min mogelijk queries. Nu zijn we sowieso al geen fan van trage queries en al helemaal niet van onnodige queries – op puur database vlak viel dus niet zo heel veel te halen.

Bij Apache is het een tweeledig proces. Als de pagina zelf bij de bezoeker is, houd Apache de verbinding nog een tijdje beschikbaar voor de bezoeker, zodat deze de nodige resources kan downloaden (keep-alive noemen we dit). Denk hierbij aan afbeeldingen, CSS en JavaScript code. Door deze te verkleinen kan de verbinding eerder weer dicht.

Maar het begint natuurlijk al bij het genereren van de HTML die naar de browser moet. Hoe sneller de HTML namelijk ingeladen is, hoe sneller de resources kunnen beginnen. Tijd dus om naar de code te gaan kijken: een MVC-style codebase met minimale overhead. Na wat benchmarks bleek al snel dat de code juist weer vooral aan het wachten is op de database.

Dit lijkt een paradox: De PHP code wacht vooral op de database, bij de database valt niet veel te halen. Maar eigenlijk is het best logisch. We hebben ook geen problemen, tot er in een uur tijd 90.000 bezoekers langskomen. (Fun fact: aan zowel Google Analytics als onze server load grafieken kunnen we vrij goed zien wanneer er bijvoorbeeld een Champions League wedstrijd bezig was.)

De vraag die je op zo'n moment kunt stellen, is hoe vaak in dat uur er daadwerkelijk iets veranderd aan de website. Het antwoord is over het algemeen: helemaal niets. Dat er niets veranderd kun je ook lezen als “de data blijft gelijk”. En tja, als de data gelijk blijft, waarom zou je het dan opnieuw aan de database vragen? Zou jij het fijn vinden als ik iedere minuut vroeg “welke dag is het vandaag”?

Wat je kunt doen, is in plaats van het aan de database te vragen, de resultaten tijdelijk cachen. Simpel gezegd: Als ik nu vraag hoe laat het is en het antwoord is “8:30”, dan kun je er best vanuit gaan dan dat grofweg een halve minuut zo blijft. Daarnaast is het wellicht om 8:31 ook nog wel prima om te weten “daarnet was het 8:30”.

Als we dit principe gaan toepassen op Sport1 dan is de conclusie al snel dat de verschillende lijstjes (voetbalnieuws, golfnieuws, overig sportnieuws, best gelezen artikelen, specials, …) nou niet bepaald iedere seconde veranderen, maar slechts een paar keer per uur. Het kan dus weinig kwaad om dit een paar minuten lang simpelweg te onthouden. Hiervoor gebruiken we Memcached, een in memory key-value store. Een aantal leuke eigenschappen van Memcached zijn O(1) schaalbaarheid (zowel lees als schrijf acties), lage latency, eenvoudige cluster mogelijkheden en gemakkelijk inzetbaar. Daarnaast is Memcached relatief nieuw en dus erg hip. ;)

Door deze wijziging wordt de database flink ontzien. Sterker nog, de database kan (even) uit, zonder dat dit de website beinvloed. Daarnaast was de website hierna al bloedje snel.

Maar we hadden nog tijd over en we wilde de website nog beter bestand maken tegen ook de grootste pieken van belasting. Al je data uit de cache halen is namelijk leuk, maar om te beginnen moeten we op basis van de URL gaan bepalen wat we dan precies moeten tonen en de juiste PHP code aftrappen. Er moet data worden opgehaald en dit alles moet naar de template engine. Allemaal niet traag, maar alles wat je niet hoeft te doen kost helemaal geen tijd. Wat we nu doen is op basis van de URL (+GET&POST data) de volledige pagina cachen. Niet te lang, maar zelfs al is het maar een minuut, dan kan dat best betekenen dat we 100 keer die pagina uit onze full page cache kunnen laden. Voor de full page cache gebruiken we geen Memcached, maar een simple file based cache: We schrijven de gehele HTML weg naar een tekstbestandje, welke we later weer uitlezen. Dit is wat minder snel als Memcached (hoewel het OS de bestanden naar een tijdje weer in een eigen RAM cache gaat houden) maar qua ruimte is de harde schijf een stuk goedkoper als het intern geheugen. Als over een tijdje blijkt dat de totale omvang van deze cache niet erg groot wordt kunnen we deze wellicht ook naar Memcached omzetten.

Na deze wijzigingen benaderen we qua performance de tijd die de webserver nodig heeft om een statisch bestand te serveren. Veel sneller is met de huidige infrastructuur gewoon niet meer mogelijk. (Al is snelheid an sich overrated - het is schaalbaarheid waar we voor gaan.) Met behulp van Apache Bench kan eenvoudig worden gemeten hoe de website zich houd bij erg veel gelijktijdige bezoekers. We hebben runs gedaan met 1000 hits, eerst 20 tegelijk, toen 100, 200, ... 1000. Dit deden we zowel met als zonder de ingebouwde caching. De resultaten spreken voor zich: Met caching is de gemiddelde responsetijd bij 20 gelijktijdige bezoekers al een stuk lager als zonder caching. Bij meer gelijktijdige bezoekers blijft de responsetijd met caching ook grofweg gelijk tot aan 900 - zonder caching loopt deze hard op en beginnen er vanaf 300 bezoekers ook daadwerkelijk bezoeken te falen.

Front-end optimization 3.0, een case study

Door E-sites

Wat komt er na een hevig geoptimaliseerde website? In de zomer van 2010 lanceerden we vol trots sport1.nl. Uiteraard opgeleverd volgens de toen… - Lees meer

Lees verder