Wij krijgen wel eens de vraag: hoe komt het dat jullie applicaties zo snel zijn? Voor onze C#-applicaties is dat antwoord meestal vrij simpel: dit is een razendsnelle taal, gebouwd op een razendsnel framework. Maar voor PHP ligt dat wat anders; die heeft wat meer hulp nodig.
PHP is namelijk, zowel als taal als qua manier van het verwerken van requests, van oorsprong behoorlijk traag. Maar, met de opkomst van wat ik 'moderne applicatieservers' noem, is dit in de afgelopen jaren aan het veranderen. In deze blogreeks ga ik het hierover hebben.

Er is altijd veel rivaliteit tussen de aanhangers van verschillende programmeertalen. C#'ers zijn vaak gemene pestkoppen, primitief en onbeschoft. PHP'ers zijn dat niet. Die zijn meestal intelligent, netjes, beschaafd en heel objectief. 😁
Traditionele PHP
Het traditionele PHP-model werkt met een process-per-request model. Dat wil zeggen dat er een nieuw PHP-proces wordt gestart voor ieder verzoek dat binnenkomt op je server. Dit betekent dat alles 'nieuw' is. Alle codebestanden, libraries en omgevingsvariabelen moeten worden ingeladen, er moet een verbinding opgezet worden met de database, het framework moet worden opgestart. Pas dan vindt het verwerken van het verzoek plaats, en stuur je een respons terug. Daarna wordt het PHP-proces beëindigd.
De gehele levensduur van dit PHP-proces is dus verbonden aan die van het request. De volgende keer dat je een verzoek binnenkrijgt, begin je weer van voor af aan.
Deze aanpak heeft een groot voordeel: het maakt dingen ongelooflijk simpel. Bij het schrijven van de code hoef je geen rekening te houden met mogelijke 'memory leaks', want je hele proces is daarna toch weer verdwenen. Je 'state' is een stuk simpeler, want alles gebeurt maar één keer, en is altijd 'vers'. Je deelt geen gegevens met andere processen.
Maar deze aanpak heeft ook één groot nadeel: het is behoorlijk traag. Want een groot deel van de stappen die je moet uitvoeren, zijn iedere keer hetzelfde. Het inladen van het framework, het opzetten van database-verbindingen, etc. Een manier waarop het soms wordt uitgedrukt is dat je de hele wereld moet scheppen en afbreken, voor ieder request. Of een vergelijking die ik zelf mooi vind: voor iedere brief die je ontvangt, moet je eerst een heel postkantoor bouwen (en vervolgens weer afbreken).
Zou het niet handiger zijn als we het postkantoor gewoon laten staan?

Moderne applicatieservers
Dit is dus precies de oplossing die moderne PHP-applicatieservers, zoals RoadRunner, OpenSwoole en FrankenPHP bieden. Zij brengen een splitsing aan tussen deze twee 'onderdelen', waarbij het opstarten eenmalig gebeurt, en je PHP-proces daarna langdurig in leven wordt gehouden om zo requests af te handelen.

Zo simpel is het niet
Dat klinkt goed, dus waar wacht je op? Iedereen overstappen dan maar!
Zo simpel is het natuurlijk niet. Omdat PHP altijd gewerkt heeft op basis van process-per-request, is ook de meeste code geschreven op een manier die dit vereist. Oudere frameworks, zoals het razend populaire WordPress, zijn op een traditionele manier geschreven waarbij deze aanpak totaal niet ondersteund wordt. Modernere frameworks zoals Symfony en Laravel daarentegen waren al op een manier geschreven die dit wel mogelijk maakte, en bieden daarom ook al enkele jaren ondersteuning hiervoor aan.

WordPressers hebben voorlopig nog pech. Er zal erg veel moeten veranderen voordat zij gebruik kunnen maken van moderne applicatieservers.
Swoole
Bij Webenable hebben we performance altijd belangrijk gevonden. Net als iedereen begonnen we jaren geleden ook op het traditionele PHP-model, maar was ons Hydrazine Framework dusdanig ontworpen om toch een goede performance te halen.
Leuk feitje: de eerste versie van het Hydrazine Framework was gebouwd bovenop de WordPress API, toen deze oorspronkelijk zelfs alleen nog maar beschikbaar was via een plugin. Via complexe caching en onze eigen lagen kregen we het voor elkaar om iets te bouwen dat toch snel kon werken, en je tegelijkertijd toegang had tot de vele features van het WordPress CMS. Maar voor latere versies van het framework stapten we toch van deze aanpak af.
We bleven altijd bezig met het verbeteren van de performance van het Hydrazine Framework, en maakten daarom, inmiddels ook alweer jaren geleden, de stap naar Swoole. Swoole is een PHP-runtime die het mogelijk maakte om een langdraaiend stateful PHP-proces te hebben, en biedt daarvoor ook de nodige tools voor het ontvangen en versturen van de requests en responses. In andere woorden: we hoefden niet voor ieder request eerst weer de hele wereld te scheppen.
In de eerste jaren hadden we de nodige groeipijnen, voordat we het Hydrazine Framework er helemaal goed werkend op hadden, maar het zorgde wel altijd voor fantastische snelheden van onze applicaties.

Groeipijnen. Onze responses gaven regelmatig errors - maar wel in recordtijd!
Swoole is erg uitgebreid. Het biedt niet alleen ondersteuning voor ons langlopende proces, maar het heeft nog veel meer features, zoals ondersteuning voor async programming. Een heel krachtige en handige feature. Maar wij hebben die opzettelijk niet gebruikt, om te voorkomen dat we afhankelijk zouden raken van Swoole als runtime, en niet meer eenvoudig konden wisselen. In andere woorden: we wilden vendor lock-in voorkomen.
En dat was maar goed ook, want vorig jaar zijn we begonnen met onze migratie naar een andere PHP-runtime.
FrankenPHP
Vorig jaar zijn we met onze migratie begonnen van Swoole naar FrankenPHP. Niet dat er iets mis is met Swoole - sterker nog, de performance lijkt zelfs iets beter dan FrankenPHP én het heeft meer features. Maar FrankenPHP heeft twee voordelen boven Swoole: het zit veel meer verweven in het Symfony- ecosysteem en het heeft betere documentatie.
Wij willen dus nooit meer terug naar het traditionele model van PHP. Dit nieuwe paradigma bracht een hoop nieuwe mogelijkheden met zich mee die we konden benutten, maar de transitie ging natuurlijk niet zonder slag of stoot. In ons volgende artikel ga ik hier meer over vertellen.

