Welche Leistungssteigerung kann man von einer Symfony 5 App mit PHP OPcache Preloading erwarten?

Seit der Einführung von PHP 7.4 und dem OPcache Preloading, sind nun etwa sechs Monate vergangen. Hierbei handelt es sich um eine Funktion, die die Ausführung komplexer Anwendungen effizienter zu machen verspricht, indem Teile davon im dauerhaften Speicher gehalten werden. Zu Beginn war es schwierig, komplexe Anwendungen damit zum Laufen zu bringen.

Das lag am Symfony-Framework, und Abhängigkeiten wie Doctrine sowie PHP selbst erforderten alle etwas Aufwand, um mit der Funktion zu interagieren. Seit den ersten Versuchen, die eZ Platform mit OPcache Preloading zu betreiben, ist das PHP/Sympfony Ökosystem nun so weit gereift, dass man mit dem Feature echte Leistungsverbesserungen erwarten kann.

In den ersten Monaten des Jahres 2020 erhielt PHP eine Reihe von Patches, die die neueste Version auf 7.4.6 brachten. Diese enthielten Fehler- und Sicherheitskorrekturen sowie Kompatibilitätsverbesserungen für das Preloading. Auch die Unterstützung des Symfony-Frameworks für diese Funktion ist von der anfänglichen Kompatibilität in 4.4 zu einer besser konfigurierbaren und robusteren Unterstützung in Symfony 5.1 gereift. Außerdem wurden andere wichtige Abhängigkeiten wie Doctrine aktualisiert und sind mit OPcache Preloading kompatibel.

Inzwischen hat eZ Platform auch bedeutende Upgrades erfahren, vor allem ein Upgrade auf Symfony 5 in der neuesten Hauptversion: eZ Platform v3.0. Einige Wochen später wurde die vollständige Kompatibilität mit PHP 7.4 ermöglicht. Dies qualifiziert eZ Platform für ein Benchmarking, um herauszufinden, wie viel Leistungssteigerung Benutzer von Symfony 5 durch die Aktivierung des OPcache-Preloadings erwarten können.

OPcache-Preloading zum Laufen bringen

Symfony 5.1 ist die aktuellste stabile Version von Symfony. eZ Platform 3.0 wurde veröffentlicht, als Symfony noch bei 5.0 war, so dass mit dem letzten offiziellen Release nicht alle aktuellen Verbesserungen zum OPcache-Preloading, die an Symfony-Komponenten vorgenommen wurden, zur Verfügung standen. Die nächste Version, eZ Platform 3.1, die bis Ende Juni 2020 veröffentlicht werden soll, wird jedoch ein Upgrade auf Symfony 5.1 enthalten.

Als Benchmarking-Plattform verwendete ich eine Kopie von Windows, die auf einem LattePanda Alpha SBC mit 8 GB RAM und 512 MB SSD lief. Für den Software-Stack, der zum Ausführen von Symfony-Anwendungen wie eZ Platform erforderlich ist, verwendete ich Ubuntu 20.20 (mit installiertem Nginx), wie in unserem früheren Blogbeitrag beschrieben: Setting up a PHP development environment for Symfony with Windows and WSL2

Sie könnten WSL2 als unkonventionelle Benchmarking-Plattform betrachten, aber man sollte dabei nicht den absoluten Datendurchsatz, sondern die relativen Verbesserungen (mit und ohne OPcache-Preloading) im Auge behalten. Während der Tests lief unsere App ausschließlich auf LattePanda. Zur Generierung der Auslastung verwendete ich meinen Laptop über Gigabit-Ethernet, der an denselben Switch wie der SBC angeschlossen war. Die Auslastung wurde mit hey erzeugt. TLS/SSL (später HTTP/2/3) waren auf Nginx nicht aktiviert.

Zum Zeitpunkt der Erstellung dieses Artikels gab es noch keine offizielle Veröffentlichung der eZ Platform mit Symfony 5.1, aber ich habe einen Schnappschuss des Master-Zweiges verwendet und die neuesten Abhängigkeiten auf den neuesten Stand gebracht (einschließlich Symfony 5.1):

janit@W1ND0Z1337:/var/www$ git clone https://github.com/ezsystems/ezplatform.git
Cloning into 'ezplatform'...
remote: Enumerating objects: 86, done.
remote: Counting objects: 100% (86/86), done.
remote: Compressing objects: 100% (48/48), done.
remote: Total 16214 (delta 49), reused 45 (delta 38), pack-reused 16128
Receiving objects: 100% (16214/16214), 10.72 MiB | 438.00 KiB/s, done.
Resolving deltas: 100% (8300/8300), done.
janit@W1ND0Z1337:/var/www$ cd ezplatform
janit@W1ND0Z1337:/var/www/ezplatform$ git checkout da04a3e7fe14e5ee1c76d1924dbf77b53755f62c
HEAD is now at da04a3e Merge remote-tracking branch 'origin/3.0'
janit@W1ND0Z1337:/var/www/ezplatform$ composer update

Nach Fertigstellung konfigurierte ich OPcache mit den folgenden Flags, die zur Standardinstallation hinzugefügt wurden:

opcache.preload_user=www-data
opcache.preload=/var/www/ezplatform/var/cache/prod/App_KernelProdContainer.preload.php
opcache.memory_consumption=1024
opcache.interned_strings_buffer=256
opcache.max_accelerated_files=30000
opcache.validate_timestamps=0

Als Nächstes konfigurierte ich die Symfony-Umgebung so, dass sie aus der Nginx-Konfiguration erzeugt wurde, und fuhr mit dem Neustart von Nginx und PHP-FPM fort. Bei Nginx ging der Neustart in Ordnung, aber PHP-FPM konnte nicht neu gestartet werden:

janit@W1ND0Z1337:/var/www/ezplatform$ sudo /etc/init.d/nginx restart
 * Restarting nginx nginx                                                        [ OK ]
janit@W1ND0Z1337:/var/www/ezplatform$ sudo /etc/init.d/php7.4-fpm restart
 * Restarting PHP 7.4 FastCGI Process Manager php-fpm7.4                         [fail]

Nginx startete nicht. Ich hatte angenommen, dass die automatisch generierte Preload-Datei (App_KernelProdContainer.preload.php) nicht existierte, also löschte ich die Caches, um sie zu generieren:

janit@W1ND0Z1337:/var/www/ezplatform$ ./bin/console cache:clear --env=prod

 // Clearing the cache for the prod environment with debug false

 [OK] Cache for the "prod" environment (debug=false) was successfully cleared.

janit@W1ND0Z1337:/var/www/ezplatform$ head var/cache/prod/App_KernelProdContainer.preload.php
<?php

// This file has been auto-generated by the Symfony Dependency Injection Component
// You can reference it in the "opcache.preload" php.ini setting on PHP >= 7.4 when preloading is desired

use Symfony\Component\DependencyInjection\Dumper\Preloader;

require dirname(__DIR__, 3).'/vendor/autoload.php';
require __DIR__.'/ContainerTmRMTu6/App_KernelProdContainer.php';
require __DIR__.'/ContainerTmRMTu6/EzcSystemInfoWrapper_de0c8e5.php';
janit@W1ND0Z1337:/var/www/ezplatform$

Auch danach startete PHP-FPM nicht. Nachdem ich ein wenig in den Protokollen nachgeforscht hatte, fand ich folgenden Fehler:

Jun  9 13:52:58 |ERROR| PHP    PHP message: PHP Fatal error:  Uncaught Error: Class
    'Symfony\Cmf\Component\Routing\ChainRouterBaseBcLayer' not found in
    /var/www/ezplatform/vendor/ezsystems/routing/src/ChainRouterBcLayer.php:41

Ich nahm an, dass es hier ein Kompatibilitätsproblem mit dem Preloading von OPcache gibt:

// $classes[] = 'eZ\Publish\Core\MVC\Symfony\Routing\ChainRouter';

Sobald diese Zeile gelöscht war, wurde PHP-FPM hochgefahren. Mittels phpinfo() bestätigte ich, dass das Preloading aktiv war:

Benchmarking-Durchsatz der eZ Platform

Für das Benchmarking wollte ich verstehen, wie sich OPcache in verschiedenen Szenarien verhält. Zuerst wird die Titelseite mit einigen Inhalten über die Templating-Rendering-Pipeline geladen:

hey -c 10 -n 10000 http://192.168.42.10/

Um über die API-Leistung zu berichten, habe ich einen ähnlichen Aufruf an die eZ Platform REST API durchgeführt:

hey -c 10 -n 10000 http://192.168.42.10/api/ezp/v2/content/locations/1/2

Ich habe beide Tests dreimal mit unterschiedlichen Übereinstimmungen durchgeführt, mit und ohne aktiviertes Preloading von OPcache. Die Durchschnittswerte aller drei Durchläufe sind in den folgenden Diagrammen dargestellt.

Durchsatz und Antwortzeiten für gerenderte Inhalte

Die Aktivierung des Preloadings führt konsequent zu einem erhöhten Durchsatz. Die Verbesserungen liegen je nach Gleichzeitigkeit zwischen 9 und 13 Prozent. Etwas merkwürdig ist, dass der Spitzenwert bei 10 gleichzeitigen Anforderungen liegt, so dass interne Ineffizienzen im Betriebssystem oder in der/den verwendeten Laufzeit(en) auftreten können.

Bei den Antwortzeiten stimmen die Ergebnisse mit den Durchsatzergebnissen überein. Der Satz von Benchmark-Ergebnissen mit Vorladefunktion zeigt durchweg eine geringere Latenzzeit für Antworten an. Bemerkenswert ist, dass die Antwortzeiten bei geringerer Gleichzeitigkeit deutlich niedriger sind. Die Investition in umfangreiche Ressourcen führt zu einem besseren Benutzererlebnis, auch wenn ein ausreichender Durchsatz mit begrenzten Ressourcen erreicht werden kann.

Durchsatz und Antwortzeiten der REST-API

Der Vergleich des Durchsatzes des REST API-Calls zum Abrufen eines Location-Objekts führt ebenfalls zu einem höheren Durchsatz auf der ganzen Linie. Wenn das Preloading aktiviert ist, ergibt sich eine Verbesserung um 3 %, während ohne Preloading eine Verbesserung um 1,5 % erreicht wird. Die Aufrufe sind nicht 1:1, aber der Unterschied bei der Anzeige von Daten durch die Rendering-Pipeline wirkt sich nicht wesentlich auf den Durchsatz in unserer Anwendungsarchitektur aus.

Auch bei den Reaktionszeiten weist das Preloading konsequente Verbesserungen auf. Bei niedrigen Latenzen sind die Antwortzeiten in der Praxis fast gleich, aber bei höheren Latenzen führt die nicht vorinstallierte Konfiguration zu etwa 15 % langsameren Antwortzeiten, was einem Unterschied von etwa 20 Millisekunden entspricht.

Schlussfolgerung

Die Leistungsverbesserungen, die OPcache Preloading mit PHP 7.4 bietet, sind definitiv bemerkenswert. Ohne Änderungen an unserer Anwendung führte die einfache Aktivierung des OPcache Preloading auf PHP 7.4 zu einer Steigerung des Durchsatzes um 14% und einer Verringerung der durchschnittlichen Antwortzeiten um 12,5%.

Echte Datenverkehrsmuster führen zu vielfältigeren Ergebnissen, aber selbst eine realistische durchschnittliche Verbesserung der gemessenen Metriken um 10 % ist signifikant. Diese werden sich unmittelbar in niedrigeren Kosten, verbesserter Benutzerfreundlichkeit und geringeren Umweltauswirkungen des Online-Betriebs in großem Maßstab bemerkbar machen.

Die Benutzerfreundlichkeit von OPcache Preloading hat sich seit der Veröffentlichung von 7.4.0 am 1. Dezember 2019 sprunghaft verbessert. Auch wenn es wahrscheinlich immer noch eine ganze Reihe von Problemfällen gibt, ist der Core Stack für Symfony 5-basierte Anwendungen reif genug, um für den Produktionseinsatz im Jahr 2020 in Betracht gezogen zu werden.

Für Anwendungen, die auf Symfony aufbauen, ist dies nicht unbedingt der Fall. Für die eZ Platform werden wir noch weitere Tests und Validierungen (wie die Fehlerbehebung bei EZP-31679) benötigen, bis wir so weit sind. Ganz zu schweigen von den Projektimplementierungen. Die heute erforderlichen manuellen Anpassungen des Preloadings sind für den Einsatz in der realen Welt unpraktisch, aber insgesamt scheint es, dass wir einen vielversprechenden Startpunkt erreicht haben.

Unser Engineering-Team arbeitet intensiv an der Bereitstellung der eZ Platform 3.1 Ende dieses Monats. Neben neuen Funktionen für Endbenutzer wird es auch Verbesserungen für Entwickler geben. Um mehr darüber zu erfahren, werfen Sie einen Blick auf die Produkt-Roadmap und halten Sie sich in den nächsten Wochen mit neuen Blog-Einträgen über die nächste Version auf dem Laufenden.

Insights and news