PHP optimaliseren op Windows Server IIS

Reading Time: 33 Minutes

Niemand wil een trage PHP website of WordPress blog. Het doel van dit artikel is daarom om jou inzicht te geven in welke mogelijkheden en technologieën je kunt gebruiken om de snelheid van jouw PHP-website te verbeteren. Onder een PHP-website valt niet alleen een zelf geprogrammeerde site, maar ook CMS’en zoals WordPress, Joomla, Drupal en e-commerce oplossingen zoals PrestaShop, Zen-Cart en Magento.

Hoe verbeter je PHP performance op Windows Server IIS?

Op internet is heel veel informatie te vinden over PHP optimalisatie of code optimalisatie. Ik raad je zeker aan dat door te nemen, maar veel beschikbare informatie is niet of nauwelijks toegespitst op het Windows Server besturingssysteem met IIS webserversoftware.

Dit PHP optimalisatie-artikel wél.

Let op: Dit is een ouder artikel overgezet vanaf Sysadmins of the North.

In dit artikel laat ik IIS 6.0, en dus heel Windows Server 2003, buiten beschouwing. Ondanks dat bepaalde instellingen ook daarop van toepassing zijn, richten we ons op Windows Server 2008 R2 en hoger (IIS 7.5, 8.0, 8.5 en IIS 10). Als je het onderwerp PHP optimalisatie, en website optimalisatie in het algemeen, interessant vindt, lees dan ook zeker naar mijn WordPress optimaliseren-posts!

Je vraagt je wellicht af:

“Waarom PHP?”

PHP is een scripttaal die veel gebruikt wordt voor dynamische websites. De scripttaal PHP is laagdrempelig, vol op in ontwikkeling en de community is erg actief. Hierdoor wordt PHP steeds meer en meer gebruikt voor websites. Vooral ook omdat er erg veel scripts en content management systemen (CMS) eenvoudig beschikbaar zijn.

Door de manier waarop PHP uitgevoerd werd op IIS 6.0, denken gebruikers op dat dit ook het geval is op IIS 7.5 en hoger, en dus traag. Dit artikel laat je zien dat dit niet het geval is, zolang je met bepaalde instellingen rekening houdt en ingebouwde opties gebruikt.

Waar nodig zijn bijbehorende configuratiewijzigingen in het web.config-bestand hier weergegeven. Een web.config bestand is het configuratiebestand waarin IIS zijn instellingen opslaat.

Ook zijn nodige stukjes PHP broncode in dit document opgenomen, of links naar PHP-script voorbeelden.

We tunen PHP websites door middel van de volgende punten om de performance te verbeteren:

  1. HTTP-verzoeken minimaliseren
  2. CSS-sprites
  3. Content offloading (Content Delivery Network, of CDN)
  4. Volgorde van bestanden laden
  5. Compressie (gzip)
  6. Browser cache
  7. Website cache
  8. PHP byte-code cache (ook wel opcode-cache genoemd)
  9. Specifieke IIS instellingen

    • IIS uitvoercaching (output cache)
    • IIS File– en UriCacheModule
    • Applicatiepool mode
  10. Sessie- en cache-handler (applicatie/CMS specifiek)
  11. MySQL query_cache
  12. Kwaliteit afbeeldingen en dimensies daarvan
  13. Conclusie, afsluiting en reacties

HTTP-verzoeken minimaliseren

Internetbrowsers (of webbrowsers) zoals Internet Explorer, Mozilla Firefox en Google Chrome kunnen standaard een ‘x’-aantal HTTP-verzoeken tegelijkertijd maken, naar een specifieke hostnaam zoals www.example.com. Bijvoorbeeld tien. Eén HTTP-verzoek is het ophalen van één enkel bestand van de website.

Dit betekent dat als jouw website bestaat uit in totaal 100 bestanden (inclusief Javascript en CSS), de browser tien keer opnieuw verbinding moet maken met de server en website.

Omdat al deze HTTP-verzoeken via internet naar de website worden gestuurd, kan dit maar zo één of twee seconden extra wachttijd kosten. Dit wordt ook wel page load time genoemd. Erg vervelend voor bezoekers.

Het is dus belangrijk om het aantal HTTP-verzoeken te minimaliseren. Dit kan eenvoudig op twee manieren:

  • Afbeeldingen samenvoegen tot CSS-sprites
  • Verschillende soorten content offloaden via een Content Delivery Network (CDN)

Deze twee methodes worden in de volgende onderwerpen verder uitgelegd.

CSS-sprites

Afbeeldingen samenvoegen tot zo min mogelijk aantal bestanden met behulp van CSS-sprites vermindert het aantal HTTP-verzoeken dat een browser moet uitvoeren. Ook vermindert dit het aantal bytes dat vanaf de website gedownload wordt; het kost minder bandbreedte. Win-win-situatie dus!

Afbeeldingen die op dezelfde pagina getoond worden en altijd gezamenlijk, zijn goede kandidaten om samengevoegd te worden tot een sprite. Bijvoorbeeld een set van Social Media-icoontjes die op elke pagina weer bij elkaar getoond worden.

Als je meer wilt weten over CSS-sprites kijk dan even bij Google:
https://developers.google.com/speed/docs/best-practices/rtt?hl=nl#SpriteImages. Je vindt hier meer informatie over het maken ervan.

Content “offloading” (“Content Delivery Network”, of CDN)

Een tweede manier om het aantal HTTP-verzoeken te verminderen is om verschillende typen bestanden te laden vanaf verschillende URL’s. Dit heet offloading.

Ik heb uitgelegd dat een browser maximaal ‘x’ (10) HTTP-verzoeken tegelijkertijd kan uitvoeren, naar een specifieke hostnaam. Dat wil zeggen dat als je content laadt vanaf twee URL’s de browser niet tien, maar 20 HTTP-verzoeken tegelijkertijd maakt. Hierdoor halveert het aantal nieuw te maken verbindingen naar uw website, van tien naar vijf!

Je creëert al snel jouw eigen self-hosted Content Delivery Network (of CDN) door content te laden vanaf twee of meer hostnamen. Hiervoor hoef je helemaal geen dure cloud- of CDN-dienst af te nemen! Je stelt dit eenvoudig en makkelijk in via hostheaders (subdomeinen) voor domeinnaam en website.

Een CDN-aanbieder biedt je wel het voordeel dat de bestanden op verschillende locaties op internet beschikbaar zijn, in de cloud. Dichterbij de bezoeker, en dus sneller gedownloaded.

As a rule of thumb: houd de website beschikbaar via de www.-hostnaam en offload verschillende content, als javascript, stylesheets en afbeeldingen van verschillende CDN-hostnamen. Om het overzicht niet te verliezen mixen we geen javascripts, stylesheets en afbeeldingen vanaf dezelfde CDN-hostnaam.

Iedere hostheader komt standaard uit in de www-map, of de webroot, van de website. Dit is handig omdat we dan iedere hostnaam kunnen koppelen aan verschillende submappen, en dat meerdere malen. Onze mapstructuur met bijbehorende URL wordt dan bijvoorbeeld:

www - www.saotn.nl
images	- cdn-01.saotn.nl
css - cdn-02.saotn.nl
includes\css - cdn-02.saotn.nl
includes\js - cdn-03.saotn.nl

in PHP gebruiken we de define()-functie voor het definiëren van de verschillende URL’s:

define('_IMG_URL', 'http://cdn-01.saotn.nl' . '/images');
define('_CSS_URL', 'http://cdn-02.saotn.nl' . '/css');
define('_JS_URL', 'http://cdn-03.saotn.nl' . '/includes/js');
define('_CSS2_URL', 'http://cdn-02.saotn.nl' . '/includes/css');

Herdoor kun je, in plaats van steeds de volledige URL op te nemen in de links, de verkorte PHP-code’s (shortcodes) _IMG_URL, _CSS_URL, _JS_URL en _CSS2_URL gebruiken, bijvoorbeeld:

<link href="<?php echo _CSS_URL;?>/ie.css"
media="screen" rel="stylesheet" type="text/css">

Op IIS 7.5+ webservers kun je ook gebruik maken van IIS Outbound Rules voor het opzetten van een self-hosted Content Delivery Network (CDN), om daarmee jouw content parallel te downloaden. Ben je een beetje thuis in PHP en .htaccess-bestanden? Je kunt ook eenvoudig een eigen Origin Pull CDN opzetten, waarbij de content wordt verdeeld over -bijvoorbeeld- meerdere websites en/of locaties.

Pro Tip: Maak gebruik van Google of Microsoft CDN voor het hosten van bepaalde Javascripts, zoals JQuery: de tijd die nodig is om het .js-bestand van de externe locatie op te halen is verwaarloosbaar. Tevens vermindert dit het totale aantal HTTP-verzoeken naar de website.
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js"></script>

Volgorde van bestanden laden

De vuistregel van het laden van bestanden is dat zoveel mogelijk stylesheets (.CSS) geladen worden vóór javascript bestanden (.JS) Bij voorkeur stylesheets in de <head> en javascript voor de afsluitende </body>. Javascript moet zoveel mogelijk asynchroon (non blocking) geladen worden.

Om javascript asynchroon en non-blocking te laden gebruik je hiervoor het attribuut defer of async

<script type="text/javascript" src="https://www.saotn.org/myjs1.js" async>
<script type="text/javascript" src="https://www.saotn.org/myjs2.js" defer>

HTML <script> defer attribuut

Een javascript met het defer attribuut wordt niet geladen totdat de gehele pagina geladen is. Dit attribuut is alleen beschikbaar voor extern geladen scripts, met een src attribuut.

HTMl <script> async attribuut

Javascript met het async attribuut wordt asynchroon geladen zodra het beschikbaar is. Evenals defer is dit attribuut alleen beschikbaar voor extern geladen scripts, met een src attribuut.

Compressie (gzip)

Door content te comprimeren met gzip wordt de content verkleind (samengevoegd) en dus sneller verzonden van de webserver naar de browser.

In IIS 7.5 staat comprimeren voor dynamische- en statische content standaard ingeschakeld.

Onder dynamische content vallen onder andere scripttalen als ASP en PHP. Onder statische content vallen onder andere .css-stylesheets, HTML-documenten en .js-javascripts, maar geen afbeeldingen. Afbeeldingen zijn al gecomprimeerd. Voor prestaties van de website kán het soms verstandig zijn dynamische compressie uit te schakelen. Vooral als de website over compressie-modules beschikt.

Tegenwoordig ondersteunen de meeste browsers het ontvangen van gzip gecomprimeerde bestanden. We controleren dit met bijvoorbeeld fiddler:

GET http://www.saotn.nl/ HTTP/1.1
User-Agent: Fiddler
Accept-Encoding: gzip, deflate
Host: www.saotn.nl

Hierbij hebben we handmatig aangegeven gzip- en deflate-compressie te ondersteunen met de Accept-Encoding: gzip, deflate HTTP-verzoekheader. Een webbrowser doet dit standaard. De server reageert met een Content-Encoding: gzip HTTP-reactieheader om aan te geven dat de reactie gecomprimeerd is.

HTTP/1.1 200 OK
Content-Type: text/html
Content-Encoding: gzip
Vary: Accept-Encoding
Server: Microsoft-IIS/7.5
X-Powered-By: ASP.NET
Date: Tue, 24 Apr 2012 18:37:03 GMT
Content-Length: 1934

Browser cache

Cache (spreek uit “kesj” of “kasj”) is een opslagplaats waarin gegevens tijdelijk worden opgeslagen om sneller toegang tot deze data mogelijk te maken. Essentieel van een cache is dat deze transparant is in die zin dat het bij het ophalen van de data niet zichtbaar is of het bij de originele bron wordt opgehaald of uit de cache wordt gehaald, afgezien van dat de gegevens sneller beschikbaar zijn.

Een simpel voorbeeld van cache is de browser cache, of te wel de tijdelijke internet bestanden. Doordat een browser bestanden downloadt en lokaal opslaat zijn deze bij het volgende bezoek sneller beschikbaar.

Zolang bepaalde content ongewijzigd is op de server kun je de browser vertellen om de content uit de lokale cache te gebruiken, of daarin op te slaan indien het niet daar aanwezig is (bijvoorbeeld bij het eerste bezoek aan de website). Door een HTTP-reactieheader 304 Not Modified te sturen naar de client vertelt de webserver dat de content uit de lokale cache gehaald moet worden.

If the client has performed a conditional GET request and access is allowed, but the document has not been modified, the server SHOULD respond with this status code. The 304 response MUST NOT contain a message-body, and thus is always terminated by the first empty line after the header fields.

Door het uitvoeren van een hard refresh in Chrome omzeil je de browser cache.

Interessant voor jou:  POP3 of IMAP e-mail lezen via telnet

We configureren deze server reactieheader met behulp van IIS Manager, via het onderdeel HTTP-reactieheaders en de optie Veelgebruikte headers instellen…. Deze instelling is per map te maken. Voor content die over een lange periode hetzelfde blijft, bijvoorbeeld afbeeldingen, stel een ruime periode van bijvoorbeeld 30 dagen in. Stel bijvoorbeeld acht of 15 dagen in voor content die soms verandert, zoals javascripts, stylesheets of HTML-documenten.

In dit voorbeeld stellen we Webinhoud laten verlopen op de webroot in na acht dagen en voor de map /images op 30 dagen. Laat HTTP-keepalive inschakelen aangevinkt staan (standaard).

In een web.config-configuratiebestand kunnen we de volgende code gebruiken:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <staticContent>
      <clientCache 
        cacheControlMode="UseMaxAge"
        cacheControlMaxAge="30.00:00:00" />
    </staticContent>
  </system.webServer>
</configuration>

Plaats eenzelfde bestand in de map /css en /images, met cacheControleMaxAge="30.00:00:00".

Een HTTP-verzoek op het stylesheet-bestand (URL) /css/layout.css geeft nu, na een refresh als reactieheader:

HTTP/1.1 304 Not Modified
Cache-Control: max-age=691200
Last-Modified: Mon, 16 Apr 2012 11:14:24 GMT
Accept-Ranges: bytes
Etag: "cb8b6214c21bcd1:0"
Server: Microsoft-IIS/7.5
X-Powered-By: ASP.NET
Date: Wed, 25 Apr 2012 09:34:40 GMT

Succesvol het bestand uit onze lokale cache gehaald!

Etag: Je ziet hierboven een Etag:-header staan. Yahoo YSlow raadt aan om Etag headers te verwijderen. Als je wilt weten hoe je dit doet, zie dan het artikel Remove ETags HTTP response header on IIS.

Website cache

Een waardevolle manier voor het versnellen van een PHP-website, is door de PHP-uitvoer van iedere pagina op te slaan in kleine cache-bestanden. Zolang een pagina niet verandert, hoeft PHP niet opnieuw de gehele uitvoer te genereren, maar kan het simpelweg de inhoud van het cache-bestand naar de browser sturen.

De PEAR/Cache_Lite class van PHP is een PHP-klasse waar je gebruik van kunt maken om jouw eigen cache-mechanisme te ontwerpen. Ook kun je gebruik maken van PHP Output Buffering Control functies.

Om van PEAR/Cache_Lite gebruik te maken moet je deze klasse downloaden van http://pear.php.net/package/Cache_Lite/ en na het uitpakken uploaden naar de website. Bijvoorbeeld in een map genaamd Cache_Lite in de map www. Het bestand Cache_LiteLite.php moet daarna opgenomen worden in de PHP-code, en hiermee komen de verschillende functies en mogelijkheden beschikbaar. Een kort, vereenvoudigd PHP-voorbeeld is:

<?php
// neem de klasse op
require_once('Cache_Lite/Lite.php');

$options = array(
    'cacheDir' => '../database/cache/',
    'lifeTime' => 3600 
);
$cache = new Cache_Lite($options);

$id = '123';
if (!($data = $cache->get($id))) {
    /* geen content gevonden in de cache, maak een cache bestand aan */
    $data = '<html><head><title>test</title></head><body>
	  dit is een test</body></html>';
    $cache->save($data);
}
else {
	echo($data);
}
?>

Voor dit voorbeeld is een map “cache” aangemaakt om de cache-bestanden in op te slaan. Deze map moet beschrijfbaar zijn voor de webserver.

De werking is heel eenvoudig: $id (‘123’) moet per cache-bestand uniek zijn. Voor ieder unieke $id wordt een cache-bestand aangemaakt met de content $data. Is er een cache-bestand aanwezig voor $id, dan wordt de inhoud hiervan getoond. Tenzij dat cache-bestand ouder is dan lifeTime.

Je vindt uitgebreidere documentatie op http://pear.php.net/package/Cache_Lite/docs,
maar ook op http://mahtonu.wordpress.com/2009/09/25/cache-php-output-for-high-traffic-websites-pear-cache_lite/ en http://kevin.vanzonneveld.net/techblog/article/speedup_your_website_with_cache_lite/.

Met behulp van Cache_Lite is de laadtijd van een PHP-pagina te verlagen van 0,023 seconden naar bijvoorbeeld 0,0065 seconden! Door een eigen cache-handler te schrijven en cache-bestanden op te slaan als HTML, kan daarnaast gebruik worden gemaakt van de webserver IIS eigen cache- en compressie mogelijkheden, wat de laadtijd nog verder naar beneden kan brengen. Let wel, in de tests werd gebruik gemaakt van standaard HTML en javascript, zonder database-verbindingen en dynamische data.

Laten we de uitgebreide logica acherwege, dan is dit feitelijk hetzelfde als bijvoorbeeld de WordPress plugins WP-Super-Cache en W3 Total Cache doen. Je vindt alle informatie hierover in het artikel Caching concepten in WordPress: wat is caching en hoe maakt dit WordPress sneller?

Je vindt voorbeelden over hoe je iets soortgelijks voor elkaar maakt met PHP’s Output Buffering Control functies (functies ob_start(), ob_get_contents() en ob_end_flush()) op websites als http://www.the-art-of-web.com/php/buffer/, http://www.webgeekly.com/tutorials/php/learn-how-to-cache-content-with-php-in-under-5-minutes/ of http://www.zeeshanmkhan.com/post/11/how-to-implement-donut-caching-in-php

basically if you call ob_start() at the start of your program, it suppresses all output until you specifically flush the output buffer, therefore you can easily get the output of any PHP script, write it to a static (html) file and serve this static file to all upcoming requests for a specified amount of time

Joomla noemt dit gebufferde uitvoer.

PHP byte-code- of opcode-cache

PHP-code wordt voor ieder verzoek door de webserver/PHP-parser gecompileerd tot “computertaal” (ook wel byte-code of opcode genoemd) en daarna uitgevoerd. Het resultaat daarvan, de uitvoer, wordt naar de browser gestuurd. Omdat dit voor ieder verzoek weer moet gebeuren is dit erg inefficiënt. En dat kan natuurlijk sneller!

De IIS-webserver extensie FastCGI, waarbinnen PHP uitgevoerd wordt, houdt PHP geladen in het geheugen. Het php-cgi.exe-proces hoeft niet meer voor ieder HTTP-verzoek gestart te worden en dat scheelt kostbare milliseconden.

Doordat PHP geladen blijft in het geheugen is het mogelijk om de byte-code óók daar in op te slaan. Een extra extensie die deze byte-code in het geheugen kan opslaan is de PHP-accelerator Windows Cache Extensie, of kortweg WinCache. WinCache is dé PHP accelerator voor Windows.

De WinCache extensie slaat de byte-code van een PHP-script op in het werkgeheugen van de server, waardoor deze sneller beschikbaar is voor herhaalde uitvoeringen van hetzelfde script. Dit verhoogt de algemene prestaties.

Geavanceerde PHP-ontwikkelaars kunnen hiernaast gebruik maken van een file system cache, dat ook bestandslocaties opslaat in datzelfde geheugen. Hierdoor kan PHP de bestanden sneller terugvinden op het bestandssysteem van de server. Ook is er een Application Programmers Interface (of API) beschikbaar waarmee ontwikkelaars eigen PHP-code, zoals objecten en variabelen kunnen opslaan in het gedeelde werkgeheugen van de server.

Door hiervan gebruik te maken hoeft de data niet steeds opnieuw gecompileerd te worden bij nieuwe verzoeken. Zie Setting up PHP and WinCache on IIS voor meer informatie.

Handler: In IIS is een handler een uitvoerbaar bestand, dat via een naam (bijvoorbeeld PHP of PERL) gekoppeld is aan een bestandsextensie (.php of .cgi). De handler vertelt eigenlijk door welk programma een bepaalde scripttaal verwerkt wordt.

Specifieke IIS instellingen

Webserver File- en UriCacheModule
Omdat PHP gaandeweg met heel veel verschillende bestandjes werkt, voor sessies, cache en dergelijke, is het verstandig de IIS webserver modules UriCacheModule en FileCacheModule in te stellen. In het web.config-bestand neem je op:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
	<modules>
            <add name="UriCacheModule" />
            <add name="FileCacheModule" />
        </modules>
    </system.webServer>
</configuration>

Deze twee modules slaan in het geheugen van de webserver locaties van bestanden op, om snellere toegang tot deze bestanden mogelijk te maken. Hiervoor moet de Output Caching feature van IIS ingeschakeld zijn.

Belangrijk is ook om de PHP instelling realpath_cache_size waarde goed in te stellen.

IIS uitvoercaching (output cache)
Je kunt in IIS instellen dat de uitvoer van bestandstypen in de IIS gebufferd moeten worden. Dit heet IIS Output Cache en kan ingesteld worden voor Kernel-mode en User-mode.

Een HTTP request wordt pas gecachet na een aantal hits en je kunt onderscheid maken in Vary-headers en querystring waarden. Het is verstandig om de .php-extensie niet te lang te cachen, anders blijft de uitvoer steeds gelijk.

Let wel, de hostingprovider moet hier ondersteuning op geven. Ook kunnen andere cache-mechanismen hierdoor de mist in gaan, bijvoorbeeld doordat er geen 304 Not Modified meer teruggestuurd wordt.

No Managed Code – applicatiepool mode

Als jouw website 100% bestaat uit PHP-code, en geen gebruik maakt van Classic ASP of ASP.NET scripts, dan kun je de applicatiepool mode van de website laten instellen op No Managed Code. Dit betekent onder andere dat het gehele .NET-Framework voor de website uitgeschakeld wordt, en dat scheelt overhead aan modules en bestanden die door de webserver IIS geladen moet worden.

Echter, hiermee verlies je de opties zoals Output Cache en UriCacheModule, FileCacheModule!

In het web.config configuratiebestand kun je instellen dat bepaalde modules en defaultDocument pagina’s uit de webserver-configuratie verwijderd moeten worden. Als de website volledig uit PHP-bestanden bestaat is het vrij onzinnig om de webserver te laten zoeken naar index.html als defaultDocument bestand.

Plaats in het web.config-bestand:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <defaultDocument>
      <files>
        <clear />
        <add value="index.php" />
      </files>
    </defaultDocument>
  </system.webServer>
</configuration>

Hiermee verwijder je alle defaultDocument-bestanden (index.html, index.htm, index.php, index.pl, default.aspx, enz) en voegt alleen index.php weer toe.

Als een snelle website belangrijk voor je is, kun je in sommige gevallen het beste afstappen van het gebruik van Helicon Ape. Als alternatief gebruikt je de in de webserver IIS ingebouwde URL Rewrite module. Ape is namelijk ook een extra module die door IIS geladen moet worden. Iedere extra geladen module kost weer een beetje extra geheugen en laadtijd.

Veel PHP-pakketten en CMS-en, waaronder Joomla, Drupal en WordPress, bieden tegenwoordig standaard ondersteuning aan voor IIS URL Rewrite in combinatie met de rewrite-instellingen in het web.config-bestand.

Sessie- en cache-handler

PHP sessies
Sommige PHP-applicaties slaan standaard sessie-informatie op in een MySQL database. Voor ieder bezoek moet de website die sessie valideren, wat betekent dat er voor ieder bezoek een verbinding met de MySQL-server opgezet moet worden om op te vragen of de sessie valide is en om het resultaat van dat verzoek te verkrijgen. Een geoptimaliseerde database is dus belangrijk.

Het valideren van de sessie is belangrijk, omdat hierop ook de rechten van de bezoeker worden bepaald (Access Control Lists of ACL’s). Dit genereert onnodig veel website → MySQL database →website verkeer:

Interessant voor jou:  MySQL-database beheren

Enkele van dit soort PHP-applicaties zijn Joomla, osCommerce en Zen-Cart. Het is verstandig om:

  • deze database-sessies uit te schakelen en sessie-informatie op de webserver op te slaan
  • om deze sessie-informatie op te slaan binnen de webruimte van jouw site

Om dit voor elkaar te maken, maken we als eerste via FTP een map aan, specifiek bedoeld voor sessies. Deze map moet staan op een voor de website beschrijfbare locatie, maar buiten de webroot. Om een en ander gescheiden te houden, maken we een map genaamd sessions aan.

Ten tweede maken we een tekstbestand met de naam php.user.ini, jouw hostingprovider moet dit ondersteunen, met daarin de volgende regels:

; sla sessie-bestanden binnen de website-ruimte op
; http://nl.php.net/manual/en/session.configuration.php#ini.session.save-path
session.save_path = "/path/to/sessions"
; en sla sessie-bestanden op als bestanden:
session.save_handler = files

Regels die beginnen met een puntkomma (;) zijn commentaar en worden door PHP genegeerd. Upload dit bestand naar de webroot van de website.

Let op: vanaf nu ruimt PHP niet zelf de verlopen sessiebestanden op! Na verloop van tijd kan hier een groot aantal bestanden terecht komen. Verwijder deze regelmatig via FTP om problemen te voorkomen.

Uitgaande van Joomla versie 2.5, download via FTP het bestand configuration.php, open deze met een teksteditor en maak de volgende wijziging (rond regel 59):

// public $session_handler = 'database';
public $session_handler = 'none';

Sla de wijziging op en upload het bestand weer naar de website.
Een tweede mogelijkheid is het wijzigen van het index.php bestand: rond regel 27 verander de regel:

// $app = JFactory::getApplication('site');
$app = JFactory::getApplication('site',array('session'=>false));

Eenvoudiger is: zet via de Joomla-administrator omgeving de instelling Session handler op None in plaats van Database (via Global Configuration → System). Je vindt meer van dit soort Joomla tips in de post 6 Tips to improve Joomla! performance.

In Zen-Cart (en waarschijnlijk ook osCommerce) is dit instelbaar via Configuration → Sessions. Of met het volgende MySQL statement:

UPDATE `configuration`
 SET `configuration_value` '/path/to/sessions'
 WHERE configuration_key = 'SESSION_WRITE_DIRECTORY';

PHP cache
In sommige PHP-applicaties is het mogelijk om de snelheid nog verder te verbeteren door een cache handler in te stellen. In Joomla bijvoorbeeld, is het standaard mogelijk om Cache_Lite of File te gebruiken als cache handler. Is WinCache beschikbaar op de server, dan staat WinCache ook in onderstaande Joomla-voorbeeld:

Gebruik er in ieder geval één!

Let op:
juist vanwege de kracht van WinCache moet dit jouw laatste toevluchtsoord zijn. Indien foutief gebruikt kan het meer problemen veroorzaken dan performance winst geven. Voor alle soorten caching geldt: een ontwikkelaar moet rekening houden met welke gegevens wanneer gecachet moeten worden. Je wilt niet dat een afbeelding of tekst, die alleen voor ingelogde gebruikers zichtbaar moet zijn, zichtbaar is voor alle bezoekers door foutieve – of te agressieve – caching!

In een CMS als WordPress ben je afhankelijk van extra cache-plugins, bijvoorbeeld WP-Super-Cache of W3 Total Cache. Al dan niet in combinatie met WinCache.

Je vindt meer informatie over de globale werking van onder het hoofdstuk Website- en webserver cache. Qua performance is het af te raden om WinCache als session handler te gebruiken. Althans, ik heb geen winst bemerkt. Laat dit daarom ingesteld op eerder genoemde files of cachelite.

MySQL query_cache

MySQL-databaseservers kunnen query_cache geconfigureerd hebben. Dit betekent dat het resultaat van uitgevoerde MySQL queries op de databaseserver opgeslagen wordt. Een volgende keer dat een website dezelfde query uitvoert, hoeft de databaseserver deze query niet nogmaals uit te voeren. De server kan simpelweg het resultaat uit de query_cache terugsturen.

Dat is natuurlijk veel sneller. De cache verloopt automatisch als de data in de tabel veranderd wordt. Als je geen gebruik wilt maken van MySQL query_cache, construeer de MySQL queries dan met SQL_NO_CACHE:

SELECT SQL_NO_CACHE `id`,
  `naam`,
  `salaris`
FROM `werknemers`
WHERE `salaris` > '2500';

Voor meer technische PHP-ontwikkelaars is het mogelijk om, naast de standaard MySQL query_cache, queries en het resultaat daarvan binnen de webruimte op te slaan als kleine cache-bestandjes. Voor nog snellere data toegang. Hiervoor is de PHP/Zend class Zend_Cache te gebruiken, een PHP-klasse van het Zend Framework. Je vindt hier een voorbeeld implementatie: MySQL query caching met PHP/Zend_Cache. Sowieso is MySQL optimalisatie (server én database) erg belangrijk, en daarom alom vertegenwoordigd hier op Saotn.org.

Kwaliteit afbeeldingen en dimensies daarvan

Afbeeldingen zijn vaak een bottleneck in de performance van een website. Afbeeldingen zijn te groot, niet geschaald, met te hoge compressie-kwaliteit gecomprimeerd of dimensies worden niet juist in de HTML broncode opgenomen.

Enkele belangrijke aandachtspunten zijn:

  • Gebruik gecomprimeerde afbeeldingsformaten zoals PNG en JPG/JPEG
  • Afbeeldingen moeten lossless gecomprimeerd zijn, maar niet van te hoge compressie-kwaliteit. Op een schaal van 0 (minste kwaliteit) tot 9 (hoogste kwaliteit) is 6 of 7 goed genoegd. Je kunt afbeeldingen comprimeren met behulp van de GDLib library van PHP, bijvoorbeeld de functie imagepng().
    <?php
    	$im = imagecreatefrompng("./images/afbeelding.png");
    	header("Content-Type: image/png");
    	imagepng($im, "./images/afbeelding_optimized.png", 6);
    	imagedestroy($im);
    ?>

    Bovenstaande code-voorbeeld comprimeert de afbeelding afbeelding.png met compressie-kwaliteit 6 en slaat de afbeelding op als afbeelding_optimized.png. Compressie-kwaliteit 6 is ruim voldoende voor webafbeeldingen.

    Verschillende tools, zoals OptiPNG, zijn beschikbaar voor het comprimeren van afbeeldingen in de eigen ontwikkelomgeving. De artikelen bulk optimize images for the web en minify JavaScript, CSS and compress images leggen eenvoudig uit hoe deze tools te gebruiken zijn.

  • Afbeeldingen die samengevoegd kunnen worden tot CSS-sprites moeten samengevoegd worden tot CSS-sprites, zoals eerder uitgelegd
  • Afbeeldingen moeten worden geschaald worden naar thumbnails, en dimensies moeten opgenomen worden in de img-tag:
    <img src="images/afbeelding_optimized.png" width="176" height="45">

    hiermee wordt voorkomen dat de browser:

    1. de grote afbeelding moet verkleinen tot opgegeven dimensies (176×45 pixels)
    2. de dimensies van de geschaalde afbeelding moet berekenen

Als je graag meer hierover wilt weten, je vindt de Google Performance Best Practices hier:
https://developers.google.com/speed/docs/best-practices/payload?hl=nl-NL#CompressImages

Meer PHP optimalisatietips)

Enkele tips met betrekking tot het maken, en optimaliseren, van een PHP website:

  • Houd je aan de code-standaard, voor nette en overzichtelijke PHP-code
  • Houd je aan de syntaxis van de PHP functies. Heel vaak komen we tegen:
<?php
  $link = mysql_connect('hostnaam','gebruikersnaam','wachtwoord');
  $db = mysql_select_db('databasenaam');
?>
  • Het is niet netjes, maar kan prima als de website gebruik maakt van, en verbinding maakt naar, één database (server); PHP gebruikt de laatst geopende verbinding bij het uitvoeren mysql_select_db(), of probeert de verbinding te openen, om de database te selecteren. Omdat PHP ook meerdere verbindingen kan openen en databases selecteren, is de juiste PHP syntaxis:
<?php
  $link = mysql_connect('hostnaam','gebruikersnaam','wachtwoord');
  // $link2 = mysql_connnect('hostnaam2','gebruikersnaam2', 'wachtwoord2');
  // $link3 = mysql_connnect('hostnaam3','gebruikersnaam3', 'wachtwoord3');
  $db = mysql_select_db('databasenaam', $link);
  // $db2 = mysql_select_db('databasenaam2', $link2);
  // $db3 = mysql_select_db('databasenaam3', $link3);
?>
  • Op de website The PHP Benchmark vind je van een groot aantal, veel gebruikte voorbeelden hoe dit presteert qua snelheid en performance. Erg handig!

Google Developers heeft een pagina ontwikkeld met allerlei handleidingen om de prestaties van websites te verbeteren, genaamd Make the Web Faster. Een belangrijke tip is:

  • Maak geen onnodige variabelen aan:
    $description = strip_tags($_POST['description']);
    echo $description;

    doet hetzelfde als: echo strip_tags($_POST['description']);

  • waarbij deze laatste de helft van de hoeveelheid geheugen gebruikt. Vanuit een beveiligingsoogpunt is het declareren van variabelen soms wél verstandig.
  • Voer geen database-queries uit binnen lussen: voor iedere keer dat de lus doorlopen wordt, moet er opnieuw een database-verbinding opgezet worden. Het is verstandiger om één keer die database verbinding te maken, de resultaten van de lus op te slaan in (bijvoorbeeld) een array en die array te gebruiken in de query (of insert-statement).

Andere tips zijn:

  • Minimaliseer DNS-lookups (vertalingen van hostnamen naar IP-adressen – wat is DNS?), en pas op met MySQL hostnamen en IPv6-adressen. Soms is het mogelijk PHP sneller met een hostnaam te laten verbinden door éérst het IP-adres van de hostnaam op te vragen. Dat kan met de gethostbyname() functie van PHP.

    Dit is vooral van toepassing op IPv6 adressen.

    Door een bug in de mysqlnd-driver van PHP, kan deze geen IPv6-adressen opvragen en moet terugvallen naar IPv4. Dat kost relatief veel tijd (> 1 seconde). De functie gethostbyname() werkt alléén met IPv4-adressen en door deze functie te gebruiken wordt er niet geprobeerd om eerst een IPv6-adres te verkrijgen.

    (Serverbeheerders kunnen natuurlijk IPv6 uitschakelen, of voorkeur geven aan IPv4 boven IPv6)

    Voorbeeld:

  • <?php
      $link = mysql_connect(gethostbyname('hostnaam'),'gebruikersnaam','wachtwoord');
      $db = mysql_select_db('databasenaam');
    ?>
  • Waar mogelijk, gebruik caching-mechanismen, bijvoorbeeld de in dit document besproken Cache_Lite en PHP Output Buffering Controls, maar ook Zend_Cache en de eigen cache-modules/plugins van de CMS-systemen Joomla, Drupal, WordPress, CodeIgniter, …, ….
  • Trek PHP-code los van de layout (front-end), door gebruik te maken van een template-engine zoals Smarty, of frameworks zoals Symphony, DooPHP of Yii
  • Cast datatypen naar hun juiste type: een string is een string en een integer is een integer (nummeriek), maar een boolean (TRUE of FALSE, 1 of 0) is géén integer. Laat dit PHP niet zelf bepalen (Type Juggling). Bijvoorbeeld:
$bar = '1';
$foo = (string) $bar; // $foo is nu een string 1
$foo = (int) $bar; // $foo is nu integer 1
$foo = (bool) $bar; // $foo is nu een boolean 1 (TRUE)

Conclusie PHP optimalisatie

Met dit document heb ik je hopelijk laten zien dat PHP-websites op Windows IIS webservers niet traag hoeven te zijn. Je moet alleen gebruik maken van instellingen en opties die beschikbaar zijn om jóuw website en PHP-code te optimaliseren.

Wil je meer tips om jouw PHP-website sneller te maken? De hostingexperts van Vevida kunnen je daarover meer vertellen. Registreer of verhuis nù je domeinnaam.

Thanks! (-:

Eén gedachte over “PHP optimaliseren op Windows Server IIS”

Hoi! Praat mee en laat een reactie achter!