Performance
Contents |
Inleiding
Hier een aantal tips en truuks om de performance van een website acceptabel te krijgen.
Een goede manier om een beetje een idee te krijgen van de performance van je site, is door deze te bekijken via Yslow. YSlow is een Firefox extensie waarmee je een aantal punten van je site kunt analyseren op performance. Elk onderdeel wordt beoordeeld met een letter A t/m F, waarbij A de hoogste score is, en F de laagste. YSlow heeft Firebug nodig (maar als rechtgeaarde web developer moet je die geinstalleerd hebben!).
Op de pagina van YSlow staat ook een set regels voor high performance site. YSlow gebruikt deze regels voor hun analyse. Lees het Exceptional performance verhaal voor meer info.
Handige links:
Lazy loading
Een relatief simpele manier om performance te verhogen is het principe "Lazy loading". Dit betekent dat je alleen de dingen inlaad die je op dat moment nodig hebt. Dit kan bijvoorbeeld gaan om bepaalde classes en/of files met functies. Verder zul je niet altijd je databaseverbinding nodig hebben. Ook clientside kan het handig zijn om delen van je pagina on-demand in te laden. Denk hierbij bijvoorbeeld aan een googlemap.
Keerzijde hiervan is wel dat het onoverzichtelijk welke dingen je op welke plaats tot je beschikking hebt. Ook zal het doorgaans niet al te veel overhead met zich meebrengen omdat vaak gebruikte code toch in het geheugen van je OS zal staan.
Caching
Algemeen
Het allerbelangrijkste om je site snel te krijgen: caching! Zorg dat je uitvoer gecached wordt. Meestal zal je framework hier wel functionaliteit voor hebben.
De volgende elementen zijn (oa.) te cachen:
- HTML cache (volledige HTML pagina's cachen; onhandig als je gebruiker specifieke info op een pagina hebt)
- HTML fragment cache (delen van HTML pagina's cachen; bijv. een inschrijfformulier of een nieuwsbericht)
- PHP object cache
- MySQL query cache (tip: bekijk de video in het hoofdstuk Database)
- Browser cache
Voor caching kun je de volgende media gebruiken:
- Filesystem cache (bestanden opslaan op schijf; is traag wegens disk I/O, maar wel persistent)
- Memcache (PHP objecten opslaan in RAM via een aparte Memcache server)
- Ramdisk cache (bestanden opslaan in het geheugen; vergelijkbaar met filesystem cache, minder disk I/O maar ook niet persistent)
- MySQL cache (je kunt evt. je cache opslaan in een database; weinig sjiek maar wellicht soms handig)
Browser cache
Het mooiste is natuurlijk om optimaal gebruik te maken van de clientside browser cache. Belangrijk punt daarbij is dat de juiste HTTP headers worden meegestuurd, die de browser vertellen dat het om gecachede content gaat. Om te zien welke headers er meegestuurd worden, kun je Firebug of de Live HTTP Headers extensie voor Firefox gebruiken.
Voor het hoe, wat, waar en waarom van clientside caching, is het verstandig om deze caching tutorial te lezen.
Omdat dat een hoop gedoe met headers kan zijn, kun je met een zogenaamde Cacheability Engine kijken of je HTTP request door een browser gecached wordt. Zie de volgende handige links:
- Caching Tutorial
- Algemene uitleg Cacheability Engine
- Cacheability Engine
- Web Sniffer
- Live HTTP headers voor Firefox
- Firebug
Memcache
Een andere manier om te cachen is memcache. Memcached is een applicatie die als een daemon kan draaien op een server en bewaart caches in het geheugen waardoor dit zeer snel kan zijn. Je kunt met de php-memcache module verbinding maken met deze server. Memcache werkt met een key => value constructie. Zie ook: Memcache
Wat kan Memcache voor je doen
Memcache is zeer geschikt voor vaakgebruikte onderdelen die een zware performance impact hebben. Hierbij kan gedacht worden aan bijvoorbeeld de laatste x artikelen uit een RSS-feed of de auteurs met de meeste posts.
Wat doet Memcache niet
Het is niet handig om volledige HTML pagina's in Memcache te zetten. Het geheugen kan dan vrij snel vollopen. Ook moet je goed bijhouden welke key je gebruikt. Memcache zal je namelijk niet vertellen welke keys hij gebruikt.
Voorbeeld
$memcache = new Memcache;
$memcache->connect('localhost', 11211) or die ("Could not connect");
$content = $memcache->get('RSSFeed');
// cache was invalid
if (!$content) {
// do your RSS retrieval and processing here and store it in $content
// ttl is in seconds
$ttl = 300;
$memcache->set('RRSFeed', $content, $ttl);
}
echo $content;
Let op: Memcache kent geen locking zoals een database dat doet. Wanneer er veel get en set acties naar memcache gaan kan het zo zijn dat er onverwachte data terugkomt door het overschrijven. In dat geval moet een soort van locking mechanisme bedacht worden.
CSS/Javascript
De meeste sites gebruiken inmiddels best veel Javascript en CSS bestanden, vooral als je met kleurenthema's werkt. Basically houdt dat in dat er per pagina request best veel HTTP verkeer over de lijn gaat. Als je hierbij rekening houdt met DNS requests, overhead op webservers, en het feit dat een browser maar 2 requests synchroon af kan handelen, kan het qua performance aantikken.
Om dat tegen te gaan kun je de Javascript en CSS tot 1 bestand combineren en gzippen. Hier zijn al handige PHP scripts voor, die dat voor je doet. Bijvoorbeeld een aangepaste versie van het script van http://rakaz.nl/extra/code/combine.
Herschrijfregels
Om het combine script te laten werken moet je een tweetal herschrijfregels opnemen in de .htaccess. Hieronder het glossy voorbeeld:
RewriteRule wp-content/themes/glossy/css/(.*\.css) wp-includes/im_shared/combine.php?type=css&files=$1 RewriteRule wp-content/themes/glossy/javascript/(.*\.js) wp-includes/im_shared/combine.php?type=javascript&files=$1
Minimaliseren
Javascript en CSS zijn heel goed te ontdoen van overbodig commentaar en onnodige (lees) tekens (zoals newlines en spaties). Hiermee houdt je de bestanden klein, en beperk je de bandbreedte en wachttijd.
Om je Javascript zo klein mogelijk te maken, kun je JSMin gebruiken. Een andere optie is minify. Minify kan niet alleen Javascript (middels JSMin) maar ook CSS inkorten (met 2 welgemikte reguliere expressies). Minify doet zelf ook een hoop dingen die combine.php doet, zoals caching en meerdere bestanden in 1 combineren. Minify doet echter geen GZIP, en daar is meestal meer winst mee te halen dan met minimaliseren.
Nadeel is wel dat je Javascript code en CSS niet meer leesbaar en onderhoudbaar zijn. Om dit tegen te gaan, is het best elegant om iets dergelijks in te bouwen in het combine script. Dan heb je zelf nog normale bestanden, maar wordt de overbodige informatie er on-demand uitgefilterd en gecached.
Quirks/FAQ
- Als een van de scripts/stylesheets uit je lijst mist op het filesysteem zullen alle anderen ook niet worden geladen.
- Sommige javascript libraries missen een ";" aan het eind van de file waardoor alle javascript op zijn bek gaat.
- Volgorde van stylesheets/javascript kan heel belangrijk zijn.
- Een print stylesheet moet altijd alleen worden ingeladen.
Configuratie
Je kunt wat herschrijfregels in Apache opnemen om CSS en Javascript files op globaal niveau door combine/combine_minify.php te halen. Daarvoor kun je (aangepaste versie van) onderstaande Apache config gebruiken:
<Location />
Options Indexes FollowSymLinks
# Rewrite all .js and .css files (that are not from trac.jorito.net)
# to our combine_minify.php script
# combine_minify will combine multiple JS/CSS files into one, strip them, cache them and deliver them
RewriteEngine On
RewriteCond %{SERVER_NAME} !trac.jorito.net
RewriteRule (.*\.css) /include/combine_minify.php?type=css&files=$1
RewriteRule (.*\.js) /include/combine_minify.php?type=javascript&files=$1
</Location>
PHP
Ook voor PHP zijn er een aantal handige truukjes om de code sneller te krijgen. Hier een leuke link:
Database
De grootste bottleneck bij een high performance site is bijna altijd de database. Uitgaande van MySQL, is het een goed idee om de volgende punten in het achterhoofd te houden:
- Kies de goede database engine (MyISAM of Innodb)
- Zorg ervoor dat je zo weinig mogelijk queries gebruikt
- Zorg ervoor dat je tabellen geoptimaliseerd zijn en de juiste indices gebruiken. (tip: MySQL's EXPLAIN syntax)
MyISAM vs. InnoDB
Globaal gesteld kies je bij veel insert/update queries voor InnoDB, bij veel select queries voor MyISAM.
Handige links:
- http://dev.mysql.com/doc/refman/4.1/en/explain.html
- http://dev.mysql.com/doc/refman/4.1/en/optimization.html
- http://www.petefreitag.com/item/566.cfm
Servers
Apache tweaks
Een aantal handige Apache tweaks om zoveel mogelijk performance te krijgen:
- Multiple Subdomains
- GZIP compressie
- Future Expires headers
- Cache Control headers
- Remove Last Modified header
- mod_cache
Wellicht dat http://www.askapache.com/ nog meer interessante goodies heeft.
Om je op weg te helpen, is hier een kant-en-klaar stukje Apache configuratie wat je kunt gebruiken op je server. Deze kun je plaatsen in de directory directive van je Apache DocumentRoot: NB: De tijden die hier worden aangegeven zouden eventueel kunnen worden getweaked.
<Directory /www/documentroot>
FileEtag MTime Size
<IfModule mod_expires.c>
# Default expiry headers for improved cacheability
ExpiresActive On
ExpiresDefault A86400
# 1 year for media files
<FilesMatch "\.(?i:flv|ico|pdf|avi|mov|ppt|doc|mp3|wmv|wav)$">
ExpiresDefault A9030400
</FilesMatch>
# 10 days for images and swf
<FilesMatch "\.(?i:jpg|jpeg|png|gif|swf)$">
ExpiresDefault A6048000
</FilesMatch>
# 3 days for js/css
<FilesMatch "\.(?i:js|css)$">
ExpiresDefault A259200
</FilesMatch>
# 3 hours for html/txt/xml
<FilesMatch "\.(?i:txt|xml|html)$">
ExpiresDefault A10800
</FilesMatch>
</IfModule>
# GZIP html, plain text, xml with mod_gzip (apache 1.3) or mod_deflate (apache 2.x)
# For apache 2.x
# AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript
# For apache 1.3
mod_gzip_on Yes
mod_gzip_item_include file \.js$
mod_gzip_item_include mime ^application/x-javascript$
mod_gzip_item_include file \.css$
mod_gzip_item_include mime ^text/css$
mod_gzip_item_include file \.html$
mod_gzip_item_include mime ^text/html$
mod_gzip_item_include file \.xml$
mod_gzip_item_include mime ^text/xml$
mod_gzip_item_include file \.txt$
mod_gzip_item_include mime ^text/plain$
</Directory>
Load balancing
Een overweging is om de websites te laten serveren door Apache (of andere heavy duty webserver), en alle statische materialen (plaatjes, CSS, Javascript ed.) te laten serveren door een lightweight daemon als Lighttpd. Of een getunede Apache met de overbodige materialen eruit, dat kan ook.
De meeste browsers kunnen maar 2 verbindingen naar een server open hebben. Omdat sites best veel plaatjes kunnen hebben, zou het dus fijn zijn als je deze afbeeldingen van andere servers op kunt halen, zodat je daar minder lang op hoeft te wachten. Een truukje hiervoor is om alle afbeeldingen/CSS/JS/SWF van een random server af te laten komen. Zo zou je, in het geval van Wordpress, alles uit wp-content/uploads en wp-content/themes/[thema] op kunnen vragen via static1.sitenaam.nl t/m static4.sitenaam.nl. Dit kan dan op server niveau onderschept worden en doorgestuurd naar een lichtere webserver. Het is verstandig om niet meer dan 4 extra domeinen aan te vragen aangezien dit weer overhead kan opleveren m.b.t. DNS lookups.
Linkjes:
- Lighttpd
- FastCGI @ wikipedia
- mod_php, LightTPD, FastCGI - What's fastest?
- PoundHowto - loadbalancing
- LVS - Linux virtual server project - Dit zal meestal voor ons niet zo van toepassing zijn maar toch een interessant concept.
Bannering
In het geval van bannering op een site kom je in de situatie dat er op elke pagina een aantal HTTP requests naar een externe server moeten gebeuren. Op zich niet zo'n probleem, ware het niet dat de banner servers heel veel gebruikt worden. Dat heeft voor de eindgebruiker als nadeel dat er soms gewacht moet worden op DNS requests (komt vrij veel voor) en op het inladen van de (soms grote) banners.
Een workaround zou het opnemen van de banner in een iframe op je pagina kunnen zijn. Nadeel daarvan is dat (vooral) MSIE6 er op staat om eerst de iframe te vullen, voordat de pagina verder gerendered wordt. De rest van de pagina blijft dus hangen totdat de banner ingeladen is.
Dat is niet wenselijk. Een beter alternatief is om middels javascript de banners achteraf in te laden, als de pagina al helemaal ingeladen en gerendered is.
Afbeeldingen
De sites die we doorgaans bouwen bevatten erg veel afbeeldingen. Die kunnen vaak geoptimaliseerd worden met een tool als OptiPNG voor PNGtjes.
NB: houd er rekening mee dat onderstaande scripts transparantie uit PNG en GIF afbeeldingen kunnen weghalen!!!
Een goeie all around tool hiervoor is littleutils, om alles in 1 keer te optimaliseren. Installeer deze, en gebruik de volgende commando's om al je afbeeldingen kleiner te krijgen:
find . -name "*.jp*g" -exec opt-jpg {} \;
find . -name "*.JP*G" -exec opt-jpg {} \;
find . -name "*.gif" -exec opt-gif {} \;
find . -name "*.png" -exec opt-png {} \;
(over genomen van http://linuxgazette.net/119/lindholm.html)
Vergeet niet om het pakket libjpeg-progs en lzma te installeren zodat opt-jpg ook beschikbaar komt...
Voegt weinig toe, maar goed om in het achterhoofd te houden. Ter illustratie, hier de resultaten voor de plaatjes van Styletoday:
Voor:
jorrith@supergrover:~/images$ du -shb --apparent-size . 1426175 .
Na:
jorrith@supergrover:~/images$ du -shb --apparent-size . 1375659 .
Oftewel een winst van wel 50516 bytes, dus zo'n 50kB.
En voor de Viva site:
Voor:
jorrith@supergrover:~/images$ du -shb --apparent-size . 1593086 .
Na:
jorrith@supergrover:~/images$ du -shb --apparent-size . 1514316 .
Toch weer 78770 bytes (76kB)
Image replacement
Voor Image Replacement (tekst vervangen door een plaatje, soms nodig ivm. gebruik van een niet-standaard lettertype) zijn er een paar opties:
Sifr
Sifr vervangt HTML code met Flash. Voordeel van Sifr is dat het SEO friendly is. Nadeel is dat je er Flash voor nodig hebt, en dat het clientside nogal traag kan zijn (zeker als je het veel gebruikt).
JIR
JIR vervangt HTML code via Javascript en een stukje PHP code. Voordeel van JIR is er eigenlijk niet, nadeel is dat het erg langzaam is voor de eindgebruiker (de gehele HTML DOM wordt een paar keer doorlopen om HTML code te vervangen) en dat je er Javascript voor nodig hebt. Ook is het niet erg SEO friendly.
IR in cache
IR in cache is eigenlijk hetzelfde als JIR. Alleen wordt de HTML tekst niet meer via Javascript vervangen door een plaatje, maar gebeurt dit in de cache. Bijvoorbeeld in de Wordpress WP-Cache plugin; in elke pagina die door de cache gehaald wordt, worden specifieke HTML tags middels PHP/reguliere expressies vervangen door een <img src=> tag. Voordeel hiervan is dat de eindgebruiker nergens last van heeft, en er niets van merkt. Nadeel is dat het niet erg SEO friendly is, en dat je geen grafische kopjes meer ziet als je de cache uit zet.