Optimizing Laravel Part 4: Queues, Front-end & Opcache Considerations

#

In my last article we looked at what object caching is and how and when to use it to improve the performance of a Laravel application.

In this article, we’re going to look at some smaller, but still very useful, tactics for improving the performance of a Laravel application including:

  • Using queues to handle time-consuming tasks.
  • Improving the performance of loading front-end assets (compressing assets, vendor extraction, cache busting).
  • Using a modern version of PHP with Opcache enabled.

Use Queues for Heavy Lifting

One simple way to instantly improve the performance of a Laravel application is to offload time-consuming tasks to a queue job. The idea here is that, as long as the user doesn’t require the information in the UI quickly, a task can be deferred and can be run in the background by a separate process at a later time (e.g. sending an email). This can dramatically improve the speed of the web requests in your app.

Laravel comes with support for a number of queue drivers out of the box (e.g. database, Beanstalkd, Amazon SQS, Redis) and also comes with a built-in queue worker that can be run using the following Artisan command:

php artisan queue:work

I’m not going to explain how to set up a queue in Laravel as part of this article, as the Laravel docs do a great job of that. Suffice to say that creating a job class is simple, and adding the job to a queue in Laravel is as simple as calling dispatch on the job class:

SendEmailJob::dispatch($to, $body);

If you end up using queues extensively in your application, it might be worth considering a slightly more robust solution for your queue worker like Laravel Horizon (we use this for SpinupWP). Horizon only supports the Redis queue driver but gives you a dashboard which allows you to easily monitor key metrics of your queue such as job throughput, runtime, and job failures. It’s also very easy to configure and offers features such as “balancing” jobs across multiple queues, tagging, and notifications.

Make Front-end Assets Load Faster

The back-end PHP code is not the only place where we need to consider how performance can impact our Laravel application. In fact, more often than not the performance of loading the assets on the front-end is what can make the difference between your application feeling slow or feeling snappy.

Thankfully Laravel has a built-in tool to make it easy to work with front-end assets called Laravel Mix. Mix has support for all sorts of operations like pre/post-processing CSS, generating source maps, vendor extraction, copying files/folders, BrowserSync reloading, etc. It even has a pre-configured Webpack configuration to make getting up and running with modern Javascript frameworks quick and easy.

For example, the default Mix config file has these lines:

mix.js('resources/js/app.js', 'public/js')
   .sass('resources/sass/app.scss', 'public/css');

Then running one of predefined package.json scripts to compile the assets is as simple as:

npm run dev

Compressing Assets

By default, assets will be bundled together but not compressed. Thankfully compressing assets is as simple as running the predefined “production” script instead:

npm run production

Having assets compressed will make them load much quicker than before due to the reduced file size.

Vendor Extraction

Mix supports Vendor Extraction which allows you to extract common “vendor” libraries into a separate bundle script so that they can be cached for longer. This means the browser won’t have to re-download all of the unchanged vendor code every time your application code is updated, potentially making page loads much quicker.

Using vendor extraction is as simple as using the extract() function in your Mix config:

mix.js('resources/js/app.js', 'public/js')
   .extract(['vue', 'lodash']);

Instead of generating a single, large file this will now generate three separate files:

  • public/js/manifest.js – The Webpack manifest runtime
  • public/js/vendor.js – Your vendor libraries
  • public/js/app.js – Your application code

Cache Busting

Another helpful feature of Laravel Mix is Cache Busting. The idea here is that you suffix your assets with a timestamp or unique token to force browsers to load the fresh assets instead of serving stale (cached) copies of the code. This is especially useful when you’re serving static assets with a far-future expiration time or when you’re offloading them to a CDN.

Setting up cache busting is as simple as using the version() function in your Mix config. As versioned files are normally unnecessary in development, it’s common to only do this for production:

mix.js('resources/js/app.js', 'public/js');

if (mix.inProduction()) {
    mix.version();
}

Then in your Laravel view you can load the asset using the mix() helper function:

<script src="{{ mix('/js/app.js') }}"></script>

In production, the output will look something like this:

<script src="/js/app.js?id=fdfc97ad79c0c17270fa"></script>

Use Modern PHP with Opcache

The last optimization we’re going to cover in this article isn’t directly related to Laravel itself but will make a big difference to the performance of a Laravel application. And it’s simply this: use a modern version of PHP with Opcache enabled.

Simply using PHP 7.2/7.3 as opposed to PHP 5.6 can increase the performance of a Laravel application by 100% (~340 requests/sec to ~700 requests/sec). It’s worth noting here that Laravel has required PHP 7.0+ since v5.5 and PHP 7.1+ since v5.6. So there is really no reason not to be using a modern version of PHP with Laravel.

Taking this one step further, you can enable Opcache in PHP which improves the performance by storing precompiled script bytecode in shared memory, removing the need for PHP to load and parse scripts on each request (think of it as a low-level cache).

Since PHP 7, Opcache is enabled by default (but not optimized for production) if the module is installed. If the module is not installed, you’ll need to install it yourself (the process of installing the module will be different depending on your host). To make sure it’s enabled you’ll need to check your php.ini for the following:

opcache.enable=1

The opcache settings you should customize will depend on the environment (the size of the server, the amount of traffic, etc.) but PHP recommends the following base configuration:

opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=60
opcache.fast_shutdown=1
opcache.enable_cli=1

I’m not going to explain what each setting does here but you can read up about them in the PHP docs. To give you an idea of the performance impact of enabling opcache, on a 1GB/1CPU DigitalOcean Droplet you can get ~300% increase depending on your config (~10 requests/sec to ~40 requests/sec). It’s worth noting that with opcache enabled and tuned for production you will need to reload PHP after every deployment for your changes to take effect.

Over to You

So there you have it. Three relatively simple optimizations you can make to your Laravel application that can significantly increase performance. Combined with the previous articles in this series, you should now have a blazing fast Laravel app! And that brings us to the end of this short series on optimizing Laravel.

Have you ever used any of the tactics in this article to optimize Laravel before? If so, how did you get on? If not, are you going to try them now? Got any other tips? Let us know in the comments.

About the Author

Gilbert Pellegrom

Gilbert loves to build software. From jQuery scripts to WordPress plugins to full blown SaaS apps, Gilbert has been creating elegant software his whole career. Probably most famous for creating the Nivo Slider.