Speed in today's modern world is a critical thing, isn't it? Users jump aboard if our website or webapp isn't loading fast enough, Google punishes us with bad rankings, our revenue decreases, and we are falling behind our competitors, even though we might provide good value to our customers.
With the increased hunger for speed, trade-offs are inevitable sometimes. So let's try to build a setup using Symfony framework that is as fast as possible, but still doesn't impose too many programming restrictions on ourselves.
Server / Docker Setup
Optimizing the server setup is the first ground to win for us, and it should be a pretty easy one too. Optimizing at this stage also imposes nearly none restrictions for developers, basically making this a one time investment with long term effects.
1. Using OPcache Byte Code Cache
The OPcache stores the compiled PHP files which avoids recompilation of those for each request.
Installing OPcache for Ubuntu 19.04
To install OPcache on Ubuntu 19.04, execute the following command:
sudo apt install php7.2-opcache
Installing OPcache for Docker php:7.x-apache
To install OPcache using the PHP Docker image, let's add the following line to our Dockerfile:
RUN docker-php-ext-install opcache
Enabling and configuring OPcache
Of course, only installing the extension won't magically turn it on, that's why we need to put the following configuration into our php.ini file.
; enables opcache opcache.enable=1 ; maximum memory that OPcache can use to store compiled PHP files opcache.memory_consumption=256 ; maximum number of files that can be stored in the cache opcache.max_accelerated_files=20000 ; don't check for timestamps opcache.validate_timestamps=0
This will not only enable opcache for our PHP environment, it will also tweak the settings to get the best performance out of our opcache.
2. PHP Tweaks
Another thing we are able to do is to allow PHP to cache a lot more of those relative to absolute path mappings it has to do at runtime by setting the following configuration option in our php.ini file.
; maximum memory allocated to store the results realpath_cache_size=4096K ; save the results for 1 hour (3600 seconds) realpath_cache_ttl=3600
This reduces the amount of times PHP has to look up a mapping for a relative path to an absolute path dramatically, since nearly all the mappings in a typical symfony application are now able to be cached, improving overall performance of our website.
3. Compressing our assets
Configuring our webserver to compress assets before delivering them to our end-users also improves the performance of our website.
Of course, enabling compression of assets will also result in more work being done on the server before sending the files, but for nearly all use cases the compression performance of the server outperforms the connection with which our end-users are receiving our content.
Let's add the following configuration to our vhost configuration in apache to enable compression.
4. To .htaccess or not to .htaccess
Last but not least let's quickly talk about a .htaccess file. Having one is quite common for most projects out there running on apache. But if we have the luxury to, we should avoid using it.
Why should we avoid using it? Those .htaccess files are dynamically loaded, which made them quite popular in the first place, and because they are easy to use when we are tied to a webspace only solution.
But having those files loaded over and over again with every request is just adding overhead to every request. A better way to do this is to put whatever is in our .htaccess file, into the vhost server configuration itself. This way it's only going to be loaded once while the server boots up, and kept in memory for every request.
After configuring our server for maximum performance, we should also consider optimizing our deployment procedure. 'Cause the server is only executing what we are handing over to it, so let's optimize a little of what we are handing over.
5. Environment aware build process
We are speaking about symfonys
APP_ENV variable here. It is advisable to set this variable to prod before we are executing commands with
Why? At first,
APP_ENV=prod is the settings which tells symfony to skip loading up dependencies installed in our application which are only necessary for development purposes. Since we are building our application for production, setting this variable to prod seems to make sense.
Second, by default symfony registers hooks which are clearing the caches and installing the assets. These hooks are environment aware, so ensuring that these hooks are able to work their best is a must when striving for good web performance.
6. Composer Optimizations
Excluding development dependencies from runtime bootstrapping is good, but what is even better is not shipping them at all. It's advisable to use the following command for installing our dependencies when building for production.
composer install --optimize-autoloader --no-dev --classmap-authoritative
So, what is this doing?
--optimize-autoloaderThis prevents PHP from having to do a lot of filesystem look-ups for PSR-4/PSR-0 rules. These lookups are resolved beforehand and saved using relative paths.
--no-devAs the name implies, this prevents all the development dependencies from being installed, reducing vendor folder size and classmap file size.
--classmap-authoritativeThis basically prevents PHP from dynamically looking up classes that are not present in the classmap generated by composer.
The above steps all are one-time investments, which means we have to fiddle with those settings only once during the lifetime of our website or project.
If our website is not doing all too much heavy lifting, these optimizations should be sufficient to get us and our customers happy with the performance.
If we are still not happy, we can also change the way our application is loading itself up and how content is served to our end users.
7. Lazy services
Using lazy services allows you to inject services lazily into your services. Which means technically only a reference to a proxy is injected into your service, instead a reference to the already fully bootstrapped service.
In order to use lazy services, you'll need the package
composer install symfony/proxy-manager-bridge
This in turn allows you to write service definitions like the following in your services.yaml.
services: App\Twig\AppExtension: lazy: true
This will make the AppExtension service a lazy one, which means it will only be constructed if it is used actively by another service or controller in your application.
Having enabled some or all of the above steps should reduce the ramp up and delivery time of you symfony webapp or website a lot! There are of course always other and maybe also more advanced methods to further optimize your application, but in this post we were focusing only on the things you are able to do which are not or not heavily interfering with the way you are writing your code.