Book Image

High Performance with Laravel Octane

By : Roberto Butti
5 (2)
Book Image

High Performance with Laravel Octane

5 (2)
By: Roberto Butti

Overview of this book

Laravel Octane is a very powerful component in the Laravel ecosystem that can help you achieve remarkable app performance. With Laravel Octane, you will find tools (queues, cache, and tables) that facilitate a new asynchronous approach for improving application performance. This book highlights how Laravel Octane works, what steps to take in designing an application from the start, what tools you have at your disposal, and how to set up production environments. It provides complete coverage of the strategies, tools, and best practices to make your apps scalable and performant. This is especially important as optimization is usually the overlooked part in the application development lifecycle. You will explore the asynchronous approach in Laravel and be able to release high-performing applications that have a positive impact on the end-user experience. By the end of this book, you will find yourself designing, developing, and releasing high-performance applications.
Table of Contents (14 chapters)
1
Part 1: The Architecture
3
Part 2: The Application Server
6
Part 3: Laravel Octane – a Complete Tour
9
Part 4: Speeding Up

Understanding performance measurement in Laravel Octane

We have said that introducing Laravel Octane in your application allows for a performance boost, mainly because the objects and the various instances of the classes used by the framework are no longer initialized at every single HTTP request but at the start of the application server. As a result, for each HTTP request, framework objects are reused. Reusing framework objects saves time in serving the HTTP request.

While, on a logical and understandable level, this can have a positive impact in terms of performance, the goal of this part is to get pragmatic feedback on this performance boost by trying to recover some metrics and values.

In order to provide a rough indication of the benefits and improved response time performance for a request, let us try to perform a simple performance test.

To do this, we are going to install a tool to generate and execute some HTTP-concurrent requests. There are several such tools, one of which is wrk (https://github.com/wg/wrk).

If you have a macOS environment, you could use the brew command (provided by Homebrew) to install the wrk tool. To install the tool, use brew install as shown:

brew install wrk

With wrk, you can generate concurrent requests for a defined amount of time.

We will conduct two tests for comparison: one test will be conducted with a classical web application on nginx (http://octane.test), and the other one with an application served by an application server on Laravel Octane (http://octane.test:8000).

The two URLs are resolved as shown:

  • http://octane.test/ is resolved with local address 127.0.0.1 and will reply nginx
  • http://octane.test:8000/ is resolved with local address 127.0.0.1 and port 8000 is bound by Swoole

The wrk execution will use 4 threads, open 20 connections, and take 10 seconds of tests.

So, to test NGINX, launch the wrk command with these arguments:

wrk -t4 -c20 -d10s http://octane.test

You will see the following output:

Running 10s test @ http://octane.test
  4 threads and 20 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    51.78ms   61.33ms 473.05ms   88.54%
    Req/Sec   141.79     68.87   313.00     66.50%
  5612 requests in 10.05s, 8.47MB read
  Non-2xx or 3xx responses: 2
Requests/sec:    558.17
Transfer/sec:    863.14KB

To test Laravel Octane (RoadRunner), use the following command:

wrk -t4 -c20 -d10s http://octane.test:8000

You will see the following output:

Running 10s test @ http://octane.test:8000
  4 threads and 20 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   134.58ms  178.24ms   1.09s    79.75%
    Req/Sec   222.72    192.63     1.02k    73.72%
  7196 requests in 10.02s, 8.06MB read
Requests/sec:    718.51
Transfer/sec:    823.76KB

This test is very basic because there are no special server-side logic or query databases involved, but it is good to run the test to understand the raw difference in bootstrapping basic objects for Laravel (application container, requests, etc.) and perceive their flavor.

The difference is not so great (7,196 requests versus 5,612 requests) – around 22% – but consider that this difference grows if you add new packages and libraries (more code to be bootstrapped for each request).

Consider also that RoadRunner and Swoole provide other additional tools for improving performances such as enabling concurrency and executing concurrent tasks. The additional tools will be shown later in Chapters 2 and 3.

To better explain why Laravel Octane allows you to achieve this improvement, let me demonstrate how and when service providers are instanced and loaded into a service container.

Typically, in a classic Laravel application service, providers are loaded in each request.

Create a new service provider named MyServiceProvider in the app/Providers directory:

<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class MyServiceProvider extends ServiceProvider
{
    public function __construct($app)
    {
        echo "NEW     - " . __METHOD__ . PHP_EOL;
        parent::__construct($app);
    }
    public function register()
    {
        echo "REGISTER     - " . __METHOD__ . PHP_EOL;
    }
    public function boot()
    {
        echo "BOOT     - " . __METHOD__ . PHP_EOL;
    }
}

The new service provider simply shows a message when the service provider is created, registered, and booted.

The lifecycle of a service provider starts with three phases: creation, registration, and boot.

The register() and boot() methods are needed for dependency resolution. First of all, every service provider is registered. Once they are all registered, they could be booted. If a service provider needs another service in the boot method, you can be sure that it is ready to be used because it is already registered.

Then, you have to register the service provider, so in config/app.php in the providers array, add App\Providers\MyServiceProvider::class.

In a classical Laravel web application, for every HTTP request, the MyServiceProvider service provider is instanced, and the construct, register, and boot methods are called every time, showing this output:

NEW          - App\Providers\MyServiceProvider::__construct
REGISTER     - App\Providers\MyServiceProvider::register
BOOT         - App\Providers\MyServiceProvider::boot

With Laravel Octane, something different happens.

For a better understanding, we are going to launch the Laravel Octane server with two parameters:

  • workers: The number of workers that should be available to handle requests. We are going to set this number to 2.
  • max-requests: The number of requests to process before reloading the server. We are going to set this number to a maximum limit of 5 for each worker.

To start the Octane server with two workers and reload the server after processing five requests, we enter the following command:

php artisan octane:start --workers=2 --max-requests=5

After launching Octane, try to perform more than one request with the browser accessing this URL: http://127.0.0.1:8000.

The following is the output:

NEW          - App\Providers\MyServiceProvider::__construct
REGISTER     - App\Providers\MyServiceProvider::register
BOOT         - App\Providers\MyServiceProvider::boot
  200    GET / ...................................................... 113.62 ms
NEW          - App\Providers\MyServiceProvider::__construct
REGISTER     - App\Providers\MyServiceProvider::register
BOOT         - App\Providers\MyServiceProvider::boot
  200    GET / ....................................................... 85.49 ms
  200    GET / ........................................................ 7.57 ms
  200    GET / ........................................................ 6.96 ms
  200    GET / ........................................................ 6.40 ms
  200    GET / ........................................................ 7.27 ms
  200    GET / ........................................................ 3.97 ms
  200    GET / ........................................................ 5.17 ms
  200    GET / ........................................................ 8.41 ms
worker stopped
  200    GET / ........................................................ 4.84 ms
worker stopped

The first 2 requests take around 100 milliseconds (ms), the next requests take 10 ms, and the register() and boot() methods are called on the first two requests.

So we can see the first two requests (two because we have two workers) are a bit slower (113.62 ms and 85.49 ms) than the next requests (from the third to the tenth request, where we have a response time of less than 10 ms).

Another important thing to mention is that the register and boot methods are called for the first two requests until the tenth request (two workers multiplied by five max requests). This behavior is repeated for subsequent requests.

And so, installing Laravel Octane in your web application allows you to improve the response time of your application.

All this without having involved certain tools such as concurrency management provided by application servers such as Swoole and RoadRunner.