How Much of a Performance Boost Can You Expect for a Symfony 5 App with PHP OPcache Preloading?
It's now been roughly six months since the launch of PHP 7.4 and its OPcache Preloading, a feature that promises to make running complex apps more efficiently by keeping parts of them in persistent memory. In the beginning it was difficult to get complex applications to run with it.
This was because of the Symfony framework, and dependencies like Doctrine as well as PHP itself all needed a bit of work so that they would work with the feature. Since the first steps of trying to get eZ Platform to run with OPcache Preloading, the PHP/ Symfony ecosystem has now matured enough that you can expect to gain real world performance improvements with the feature.
In the first months of 2020, PHP received a number of patch releases bringing the latest version to 7.4.6. These have included bug and security fixes as well as compatibility improvements for Preloading. The Symfony framework support for the feature has also matured from initial compatibility in 4.4 to more configurable and robust support in Symfony 5.1. Finally other major dependencies like Doctrine have been updated and are compatible with OPcache Preloading.
Meanwhile eZ Platform has also received significant upgrades, most notably an upgrade to Symfony 5 in the latest major version: eZ Platform v3.0. A few weeks later, it received full PHP 7.4 compatibility. This makes eZ Platform a good candidate for benchmarking to find out how much of a performance boost users of Symfony 5 can expect by enabling OPcache Preloading.
Getting OPcache Preloading up and running
Symfony 5.1 is now the latest stable version of Symfony. eZ Platform 3.0 was released when Symfony was still at 5.0, so with the latest official release does not have access to all the latest OPcache Preloading improvements done to Symfony components. However the next version, eZ Platform 3.1, due for release by the end of June 2020, will include an upgrade to Symfony 5.1.
As a benchmarking platform, I decided to use a copy of Windows running on a LattePanda Alpha SBC with 8GB of RAM and 512MB SSD. For the software stack required to run Symfony apps like eZ Platform I used Ubuntu 20.20 (with Nginx installed) as described in our earlier blog post: Setting up a PHP development environment for Symfony with Windows and WSL2
You could consider WSL2 an unconventional benchmarking platform, but keep in mind that you should keep an eye on relative improvements (with and without OPcache Preloading) instead of absolute throughput. During testing the LattePanda was solely running our app. For load generation I used my a laptop over Gigabit ethernet connected to the same switch as the SBC. Load was generated using hey. TLS/SSL (subsequently HTTP/2/3) were not enabled on Nginx.
At the time of writing there was no official release of eZ Platform with Symfony 5.1, but I used a snapshot of the master branch and upgraded latest dependencies (including 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
Once complete, I configured OPcache with the following flags added to the standard installation:
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
Next I configured the Symfony environment to prod from Nginx config and proceeded with restarting it and PHP-FPM. For Nginx the restart went ok, but PHP-FPM failed to restart:
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 did not start. I thought the reason was that the automatically generated preload file (App_KernelProdContainer.preload.php) did not exist, so I cleared caches to generate it:
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$
Even after this PHP-FPM did not start. I did a bit of digging in the logs and found this error:
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
I figured there is a compatibility issue with OPcache Preloading here, so I commented it out:
// $classes = 'eZ\Publish\Core\MVC\Symfony\Routing\ChainRouter';
Once this line was out PHP-FPM booted up. I confirmed via phpinfo() that preloading was active:
Benchmarking throughput of eZ Platform
For benchmarking I wanted to understand how OPcache performs in different scenarios. First loading the front page with some content through the templating rendering pipeline:
hey -c 10 -n 10000 http://192.168.42.10/
To report of API performance, I did a similar call to the eZ Platform REST API:
hey -c 10 -n 10000 http://192.168.42.10/api/ezp/v2/content/locations/1/2
I ran both tests three times with varying concurrencies, with and without OPcache Preloading enabled. Average values of all of the three runs are used in the charts below.
Throughput and response times for rendered content
Enabling Preloading consistently results in increased throughput. Improvements range from 9 to 13 percent depending on concurrency. Somewhat curiously the peak is at 10 concurrent reqs, so there might be some internal inefficiencies in the operating system or the runtime(s) at play.
For response times, results are in line with the throughput results. The Preloading enabled set of benchmark results consistently indicate lower latency for responses. It is notable that response times are significantly lower at lower concurrency. Investing in ample resources does yield a better user experience even if sufficient throughput can be achieved with limited resources.
Throughput and response times of REST API
Comparing the throughput of the REST API call to fetch a location object also results in higher throughput across the board. When Preloading is enabled the result is a 3% improvement, where as without it is around 1.5%. The calls are not 1:1, but the difference in displaying data through the rendering pipeline does not significantly impact throughput in our application architecture.
For response times Preloading also exhibits consistent improvements. At low latencies the response times are almost equal in practice, but at higher concurrencies the non-Preloaded setup yields around 15% slower response times, a difference of roughly 20 milliseconds.
Performance improvements offered by OPcache Preloading with PHP 7.4 are definitely notable. Without any changes to our application simply enabling OPcache Preloading on PHP 7.4 resulted in an increase of 14% in throughput and a decrease of 12.5% in average response times.
Real traffic patterns result in more varying results, but even a realistic 10% average improvement in the measured metrics are significant. They will directly translate into lower cost, improved user experience and reduced environmental impact of running an online service at large scale.
Usability of OPcache Preloading has improved leaps and bounds since the release of 7.4.0 on December 1st 2019. While there are still likely quite a few of corner cases, the core stack for Symfony 5 based apps is mature enough to be considered for production deployment in 2020.
For applications building on Symfony that is not necessarily the case. For eZ Platform we will still need more testing and validation (like fixing EZP-31679) until we're there. And that's not even to mention the project implementations. Manual tweaks to Preloading that are required today are impractical to real world deployment, but as a whole it seems like we're off to a promising start.
Our engineering team is hard at work to ship eZ Platform 3.1 later this month. There will new end user features as well as improvements for developers. Join us for the webinar which will reveal the new features by clicking on the button below and to find out more check the product roadmap and stay tuned in the next few weeks for new blog posts detailing the next version.
eZ Platform is now Ibexa DXP
Ibexa DXP was announced in October 2020. It replaces the eZ Platform brand name, but behind the scenes it is an evolution of the technology. Read the Ibexa DXP v3.2 announcement blog post to learn all about our new product family: Ibexa Content, Ibexa Experience and Ibexa Commerce