3

Configuring the Swoole Application Server

With Laravel Octane, instead of RoadRunner, you can use another type of application server. In addition to RoadRunner, we can configure and use Swoole. Both tools allow you to implement an application server. Obviously, there are differentiating elements between the two tools, and sometimes deciding which one to use can be difficult.

As we saw, RoadRunner is a separate executable, which means that its installation, as seen in Chapter 2, Configuring the RoadRunner Application Server, is quite simple and does not affect the heart of the PHP engine. This means that in all probability, it has no side effects on other tools that work in the heart of the engine. These tools (such as Xdebug) are typically diagnostic, monitoring, and debugging tools.

The advice I would like to give is that, in the process of analysis and selection of the application server, evaluate the various other tools that may be useful in the development process (such as Xdebug) and evaluate their compatibility with Swoole.

Despite there being a greater complexity in the management of Swoole, Swoole offers benefits such as additional and advanced features, such as the management of an in-memory cache (benefits on performance), Swoole Table, which is a data store for sharing information between different processes (and facilitates information sharing and better cooperation between processes), and the ability to start asynchronous functions and processes at a specific interval (thus enabling asynchronous and parallel execution of functions).

In the chapter, we will see how to install and configure Swoole with Laravel Octane and how to best use the specific features provided by Swoole. In this chapter, we will explore features of Swoole such as executing concurrent tasks, executing interval tasks, using the highly performant cache and storage mechanisms provided by Swoole, and accessing metrics about the usage of workers. All these features are available via Octane thanks to the Swoole application server.

In particular, we will cover the following:

  • Setting up Laravel Octane with Swoole using Laravel Sail
  • Installing Open Swoole
  • Exploring Swoole features

Technical requirements

This chapter will cover the Swoole and Open Swoole application server setup (installation and configuration).

Unlike what we did for RoadRunner, in this case, we have to install a PHP Extension Community Library (PECL) extension to allow PHP to be able to operate with Swoole.

What is PECL?

PECL is the repository for PHP extensions. The PECL extensions are written in C language and have to be compiled to be used with the PHP engine.

Because of the complexity of installing the PECL module with all the dependencies required by the compilation, configuration, and setup of the PHP extension, we are going to use a container approach – so instead of installing all the necessary tools for compiling the PHP extension on our personal operating system, we will use Docker.

This allows us to have a running operating system (container) hosted within our real operating system. The purpose of having an isolated operating system in a container is to contain everything you need for the PHP development environment.

This means that if we install dependencies and tools, we will do it within an isolated environment without affecting our real operating system.

In the previous chapter, we did not use this approach as the requirements were simpler. However, many people use the container method for every development environment, even the simplest ones.

As the development environment begins to require additional dependencies, it may become unmanageable. It could become especially unmanageable in the case of evolution: try to think of the simultaneous management of several versions of PHP, which may require additional versions of dependencies. To isolate and limit all this within a consistent environment, it is recommended to use a container approach. To do this, the installation of Docker Desktop (https://www.docker.com/products/docker-desktop/) is suggested.

Once we have installed Docker Desktop, we will configure a specific image for PHP with the extensions we need.

All these new packages that we are going to install will be stored within this image. When we delete the development environment, simply delete the image used. The only tool installed on our operating system will be Docker Desktop.

So, to install Docker Desktop, simply download and proceed with the installation wizard specific to your operating system. In the case of macOS, please refer to the type of reference chip (Intel or Apple).

If you do not have a thorough knowledge of Docker, do not worry – we are going to use another powerful tool in the Laravel ecosystem: Laravel Sail.

Laravel Sail is a command-line interface that exposes commands for managing Docker, specifically for Laravel. Laravel Sail simplifies the use and configuration of Docker images, allowing the developer to focus on the code.

We are going to use Laravel Sail commands for the creation of a development environment, but under the hood, they will result in Docker commands and ready-to-use Docker configurations.

Source code

You can find the source code of the examples used in this chapter in the official GitHub repository of this book: https://github.com/PacktPublishing/High-Performance-with-Laravel-Octane/tree/main/octane-ch03.

Setting up Laravel Octane with Swoole using Laravel Sail

In order to have an environment up and running with Swoole as the application server and using a Docker container, you have to follow some steps:

  1. Set up Laravel Sail
  2. Install Laravel Octane
  3. Set up Laravel Octane and Swoole

Setting up Laravel Sail

First, create your Laravel application:

laravel new octane-ch03
cd octane-ch03

Or, if you already have your Laravel application you can use the composer show command to check whether Laravel Sail is installed. This command also shows you some additional information about the package:

composer show laravel/sail

If Laravel Sail is not installed, run composer require laravel/sail --dev.

Once you have Sail installed, you have to create a docker-compose.yml file. To create a docker-compose.yml file, you can use the sail command, sail:install:

php artisan sail:install

The sail:install command will create Docker files for you. The sail:install process will ask you which service you want to enable. Just to start, you can select the default item (mysql):

Figure 3.1 – The Laravel Sail services

Figure 3.1 – The Laravel Sail services

Answer the questions in the sail:install command to determine which services to include. The Sail configuration for Docker starts from a set of ready-to-use templates (stubs), and the sail:install command includes the necessary ones. If you are curious about which templates are included and how they are implemented, look here: https://github.com/laravel/sail/tree/1.x/stubs.

If you take a look at the templates, you will see that they make use of environment variables such as ${APP_PORT:-80}. This means that you can control the configuration through environment variables, which are configurable through the .env file. The .env file is automatically generated by the installation of Laravel. If for some reason the .env file is not present, you can copy the .env file from the .env.example file (for example, when you clone an existent repository that uses Laravel Octane, probably the .env file is included in the .gitignore file). In the example, if you want to customize the port where the web server receives the request (APP_PORT), simply add the parameter to the .env file:

APP_PORT=81

In this case, the web server that will serve your Laravel application will do so via port 81. The default is 80 as seen from ${APP_PORT:-80}.

Note

The examples in the Using Swoole features section will use Laravel Sail with APP_PORT set to 81, so all examples will refer to the host http://127.0.0.1:81

If you have made this change to the .env file, you can now launch the command from your Laravel project directory:

./vendor/bin/sail up

This will start the Docker container. The first time, the execution may take some time (a few minutes) because the preconfigured images with nginx, PHP, and MySQL will be downloaded.

Once the execution of the command is complete, you can visit the http://127.0.0.1:81 page and see your Laravel welcome page:

Figure 3.2 – Laravel welcome page

Figure 3.2 – Laravel welcome page

The main difference with this container approach is that the tools used to render the page (nginx, PHP) are included in the Docker image and not in your main operating system. With this method, you may not even have the PHP and nginx engine on your main operating system. The Laravel Sail configuration instructs Docker to use the container for PHP and all the needed tools, and points to your local source code (the root of your Laravel project) on your local filesystem.

Now that we have Laravel Sail installed with your Laravel application, we have to add Laravel Octane stuff to use the Swoole package already included in the Laravel image provided by Sail.

Let’s begin by installing Laravel Octane.

Installing Laravel Octane

We are going to install Laravel Octane through the container provided by Laravel Sail.

So while sail up is still running (is running as a server), launch composer require with the Octane package. Launching the command with sail will execute your command inside your container:

./vendor/bin/sail composer require laravel/octane

Setting up Laravel Octane and Swoole

In order to adjust the command that launches the server, you have to publish Laravel Sail files with the following command:

./vendor/bin/sail artisan sail:publish

This command will copy the Docker configuration files from the package in the docker directory in the root project directory.

It creates a docker directory. Inside the docker directory, more than one directory is created, one for each PHP version: 7.4, 8.0, 8.1.

In the docker/8.1 directory, you have the following:

  • A Dockerfile.
  • The php.ini file.
  • The start-container script used to launch the container. This script refers to the supervisor.conf file with the bootstrap configuration.
  • The supervisor.conf file with the configuration of the supervisor script.
Figure 3.3 – The Docker configuration files (after sail:publish execution)

Figure 3.3 – The Docker configuration files (after sail:publish execution)

The supervisord.conf file is important because it includes the bootstrap command of the web server of the container. By default, the supervisord.conf file contains the command directive:

command=/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80

Now, we have Laravel Octane, so instead of using the classic artisan serve, we have to change it and use artisan octane:start; so, in the docker/supervisor.conf file, you have to adjust the command line:

command=/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --server=swoole --host=0.0.0.0 --port=80

If you look, you will see that with artisan octane:start, the Swoole server with the --server=swoole parameter is also defined.

Note

If you are confused by ports 80 and 81, just to clarify: the application server inside the container will listen internally to port 80. With APP_PORT=81, we are instructing Docker to map the external connections from port 81 to internal port 80.

When you change some Docker configuration files, you have to rebuild the image in order for the container to use the changed files. To rebuild the image, use the build option:

./vendor/bin/sail build --no-cache

This command takes a while to be completed, but once it is completed, you can execute the sail up command:

./vendor/bin/sail up

When Octane is ready, you will see the INFO Server running… message:

Figure 3.4 – The Octane server is running

Figure 3.4 – The Octane server is running

If you open your browser, you can access http://localhost:81. The message in your console says that the server is listening to port 80, but as we previously mentioned, the process wherein the server is listening to port 80 is the internal process inside the Docker container. The processes external to the Docker container (your browser) have to refer to the exposed port (81 according to the APP_PORT configuration).

In your browser, you will see the default welcome Laravel page. You can tell that this page is served by Octane and Swoole from the HTTP server header from the response. A way to see this is to launch the curl command with the -I option to show the header, and filter the header needed with the grep command:

curl -I 127.0.0.1:81 -s | grep ^Server

The output will be as follows:

Server: swoole-http-server

This means that the Laravel application is served by Octane and the Swoole application server.

So, we can start to use some Swoole functionalities – but before that, let us install Open Swoole.

Installing Open Swoole

Laravel Sail uses by default an image with PHP that includes Swoole modules. Swoole is distributed as a PECL module and you can find it here: https://pecl.php.net/package/swoole. The sources are here: https://github.com/swoole/swoole-src.

Some developers forked the source of Swoole, creating the Open Swoole project to address security concerns.

The reason for the fork is reported here: https://news-web.php.net/php.pecl.dev/17446.

So, if you want to use Swoole as the engine for Laravel Octane, you could decide to use the Open Swoole implementation. If you want to use Open Swoole, the installation and the configuration are the same as Swoole; Open Swoole is also distributed as a PECL module.

Laravel Octane supports both.

For demonstration purposes, I will install Open Swoole for a new Laravel project directly in the operating system (no Docker):

# installing new Laravel application
laravel new octane-ch03-openswoole
# entering into the new directory
cd octane-ch03-openswoole
# installing Pecl module
pecl install openswoole
# installing Octane package
composer require laravel/octane
# installing Laravel Octane files
php artisan octane:install
# launching the OpenSwoole server
php artisan octane:start

To check that the HTTP response is created by the Open Swoole server, in another terminal session, launch the following curl command:

curl -I 127.0.0.1:8000 -s | grep ^Server

Here’s the output:

Server: OpenSwoole 4.11.1

So, Open Swoole is a fork of the Swoole project. We will refer to it as Swoole; you can decide which you want to install. The features discussed in the Exploring Swoole features section are supported both by Swoole and Open Swoole.

Before exploring the Swoole features, we should install one more package that improves the developer experience.

Before editing the code

We are going to use Swoole functionalities, implementing some example code. When you change (or edit) your code and Laravel Octane has already loaded the worker, you have to reload the worker. Manually, you can use an Octane command. If you are using Laravel Sail (so Docker), you have to run the command in the container. The command is as follows:

php artisan octane:reload --server=swoole

If you are running in a container, you have to use the sail command:

vendor/bin/sail php artisan octane:reload --server=swoole

If you want to avoid manually reloading workers every time you edit or change your code and you want that Octane watches automatically the file changes, you have to do the following:

  • Install the chokidar node package used by Octane in watch mode
  • Change the supervisord configuration file in order to launch Octane with the --watch option
  • Rebuild the image to reflect the changes
  • Execute Sail again

So, first, let’s install chokidar:

npm install --save-dev chokidar

In docker/8.1/supervisord.conf, add the --watch option to the octane:start command directive:

command=/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --server=swoole --host=0.0.0.0 --port=80 --watch

Then, rebuild the Docker image to be sure that the update to the configuration takes effect and then launch the Laravel Sail service:

vendor/bin/sail build
vendor/bin/sail up

Now, when you edit the code (in the PHP files of your Laravel application), the workers will be automatically reloaded. In the output messages of Octane, you will see the following:

INFO  Application change detected. Restarting workers…

Now that we have the auto-reload functionality, we can explore the Swoole features.

Exploring Swoole features

Swoole has a lot of features that we can use in the Laravel Octane application in order to improve the performance and speed of our application. In this chapter, we are going to explore these functionalities and then in the subsequent chapters, we will use these functionalities. The Swoole functionalities that we are going to explore are as follows:

  • Concurrent tasks
  • Interval command execution
  • Caching
  • Tables
  • Metrics

Concurrent tasks

With Swoole, it is possible to execute multiple tasks in parallel. To demonstrate this, we are going to implement two functions whose execution takes some time.

To simulate the fact that these two functions are time-consuming, we will use the sleep() function, which suspends execution for a certain number of seconds.

These two functions return strings: the first one returns “Hello” and the second one returns “World”.

We are going to set the execution time to 2 seconds, via the sleep() function.

In a classic scenario of sequential execution of the two functions, the total time taken would be 4 seconds plus a millesimal due to overheads.

We are going to track the execution time using the hrtime() function.

Note

When you need to track the execution time of a series of instructions, the use of hrtime() is recommended since it is a function that returns monotonic timestamps. A monotonic timestamp is a time calculated based on a reference point (thus relative) and is not affected by system date changes such as automatic clock adjustments (NTP or Daylight Savings Time updates).

We are also going to use two anonymous functions because this will come in handy in the second example (the example with concurrent execution) to be able to make an easier comparison.

Another consideration, before we take a look at the code, we are going to implement the examples directly in the routes/web.php file for simplicity and focus on the example code. You can use that code, especially Octane::concurrently() in your controllers or other parts of your Laravel application.

Note

For access to these examples, we are going to use the configuration with Laravel Sail and Swoole, with APP_PORT set to 81. If you are going to use your local Open Swoole installation, refer to 127.0.0.1:8000 instead of 127.0.0.1:81.

The example shows the sequential execution:

Route::get('/serial-task', function () {
    $start = hrtime(true);
    [$fn1, $fn2] = [
        function () {
            sleep(2);
            return 'Hello';
        },
        function () {
            sleep(2);
            return 'World';
        },
    ];
    $result1 = $fn1();
    $result2 = $fn2();
    $end = hrtime(true);
    return "{$result1} {$result2} in ".($end - $start) /
      1000000000 .' seconds';
});

If you access the http://127.0.0.1:81/serial-taskpage with your browser, you should see this output on your page:

Hello World in 4.001601125 seconds

The two functions are executed sequentially, which means that the execution time is the sum of the execution time of the first function and that of the second function.

If you call the two functions with the Octane::concurrently() method (passing your functions as an array of Closure), you can execute the functions in parallel:

use LaravelOctaneFacadesOctane;
Route::get('/concurrent-task', function () {
    $start = hrtime(true);
    [$result1, $result2] = Octane::concurrently([
        function () {
            sleep(2);
            return 'Hello';
        },
        function () {
            sleep(2);
            return 'World';
        },
    ]);
    $end = hrtime(true);
    return "{$result1} {$result2} in ".($end - $start) /
      1000000000 .' seconds';
});

If you open your browser to http://127.0.0.1:81/concurrent-task, you will see the following message:

Hello World in 2.035140709 seconds

Another thing to note is that for concurrent functions, the execution time depends on what happens inside the function. For example, if you want to execute two or more functions in parallel that take an unpredictable amount of time because they depend on third-party factors such as the response time of web services or the workload of a database, or when a huge file is being parsed, you probably can’t make any assumptions about the order of the execution or the duration time.

In the next example, we have two simple functions: the first one takes more time to be executed, so even though it is the first function, it is completed after the second one. This is obvious for people who are used to working with parallel tasks but probably is less obvious for people who are used to using a strong synchronous language (such as PHP without Swoole or other tools that add asynchronous functionalities):

use LaravelOctaneFacadesOctane;
Route::get('/who-is-the-first', function () {
    $start = hrtime(true);
    [$result1, $result2] = Octane::concurrently([
        function () {
            sleep(2);
            Log::info('Concurrent function: First');
            return 'Hello';
        },
        function () {
            sleep(1);
            Log::info('Concurrent function: Second');
            return 'World';
        },
    ]);
    $end = hrtime(true);
    return "{$result1} {$result2} in ".($end - $start) /
      1000000000 .' seconds';
});

The result is to print the second statement in the log file before the first one. If you take a look at the storage/logs/laravel.log file, you will see the following:

local.INFO: Concurrent function: Second
local.INFO: Concurrent function: First

It means that the execution of the functions called by Octane::concurrently start more or less at the same time, but the exact moment they are completed, the execution, depends on the time needed by the execution of the function.

Why is this important to keep in mind?

Because everything might be okay if the two functions are totally independent of each other. On the other hand, if the functions operate using the same resources (reading and writing to the same database table, for example), we need to consider the dependencies between the two operations. For example, one function might alter the data in the table, while the other function might read it. The time at which the data is read is relevant: think about whether the data is read before it is written or whether it is read after it is written. In this case, we might have two totally different behaviors.

Regardless, we will go more deeply into the concurrently method in the next chapter, where we will use Octane in a more real-life scenario – for example, using concurrently to retrieve data from multiple database queries and multiple API calls.

Interval command execution

Sometimes, you have to execute a function every X second(s). For example, you want to execute a function every 10 seconds. With Swoole, you can use the Octane::tick() function, where you can provide a name (as the first parameter) and the function defining a Closure as the second parameter.

The best place to call the tick() function is in the boot() method of one of your service providers. Typically, I use the default AppServiceProvider in the app/Providers directory, already created for you when you set up a new Laravel application (with the laravel new command, for example).

In app/Providers/AppServiceProvider.php, in the boot() method, call the Octane::tick() function with a very simple feature that logs a message with the timestamp. The log message will be tracked in the storage/logs/laravel.log file unless you have some special configuration in the .env file:

    public function boot()
    {
        Octane::tick('simple-ticker', fn () =>
        Log::info('OCTANE TICK.', ['timestamp' => now()]))
        ->seconds(10)
        ->immediate();
    }

In the preceding code snippet, we are using Octane and Log classes, so remember to include them at the top of the AppServiceProvider.php file:

use LaravelOctaneFacadesOctane;
use IlluminateSupportFacadesLog;

The Octane::tick() method returns the InvokeTickCallable object that implements a few methods:

  • __invoke(): A special method used for invoking the tick() listener; it is the method responsible for executing the function passed as the second parameter to the tick() method.
  • seconds(): A method for indicating how often the listener should be automatically invoked (via the __invoke() method). It accepts an integer parameter in seconds.
  • immediate(): A method indicating that the listener should be invoked on the first tick (so, as soon as possible).

Note

The tick() method from the application service is called when you start the octane:start command. If you are using Laravel Sail, the application service is loaded and booted when you run sail up, just because at the end, Sail launches supervisord and the configuration of supervisord has the octane:start command.

Once Octane is started in storage/logs/laravel.log, you can see the message logged by the tick() function:

[2022-07-29 08:23:11] local.INFO: OCTANE TICK. {"timestamp":"2022-07-29 08:23:11"}
[2022-07-29 08:23:22] local.INFO: OCTANE TICK. {"timestamp":"2022-07-29 08:23:21"}
[2022-07-29 08:23:31] local.INFO: OCTANE TICK. {"timestamp":"2022-07-29 08:23:31"} [2022-07-26 20:45:19] local.INFO: OCTANE TICK. {"timestamp":"2022-07-26 20:45:19"}

In the snippet here, the Laravel log shows us the execution of the tick() method.

Note

For displaying the log in real time, you can use the tail -f command – for example, tail -f storage/logs/laravel.log.

Caching

Managing a caching mechanism to momentarily save some data in a system based on workers, where each worker has its own memory space, might not be so straightforward.

Laravel Octane provides a mechanism to non-permanently save data shared between different workers. This means that if a worker needs to store a value and make it available to subsequent executions by other workers, it is possible to do so through the cache mechanism. The caching mechanism in Laravel Octane is implemented through Swoole Table, which we will see in detail later.

In this specific case, we are going to use the Cache class exposed directly by Laravel. Laravel’s caching mechanism allows us to use different drivers, such as, for example, databases (MySQL, Postgresql, or SQLite), Memcache, Redis, or other drivers. Because we installed Laravel Octane and used Swoole, we can use a new Octane-specific driver. If we wanted to use Laravel’s caching mechanism, we would use the Cache class with the classic store method to store a value. In the example, we are going to store a key called last-random-number in the Octane driver, to which we are going to associate a random number. In the example, we’re going to call the function responsible to store the value in the cache via the Octane tick seen earlier with an interval set to 10 seconds. We will see that every 10 seconds, a new cached value is generated with a random number. We are also going to implement a new route, /get-number, where we are going to read this value and display it on a web page.

To obtain the Cache instance with the Octane provider you can use Cache::store('octane'). Once you have the instance, you can use the put() method to store the new value.

In the app/Providers/AppServiceProvider.php file, in the boot() method, add the following:

        Octane::tick('cache-last-random-number',
            function () {
                $number = rand(1, 1000);
                Cache::store('octane')->put(
                       'last-random-number', $number);
                Log::info("New number in cache: ${number}",
                         ['timestamp' => now()]);
                return;
            }
        )
        ->seconds(10)
        ->immediate();

Be sure that you include the right classes at the beginning of your file. The classes needed by this code snippet are as follows:

use IlluminateSupportFacadesCache;
use LaravelOctaneFacadesOctane;
use IlluminateSupportFacadesLog;

If you want to check, you can see the log messages printed by the tick() function in the storage/logs/laravel.log file.

Now, we can create a new route in the routes/web.php file, where we get the value (last-random-number) from the cache:

use IlluminateSupportFacadesCache;
Route::get('/get-random-number', function () {
    $number = Cache::store('octane')->get(
      'last-random-number', 0);
    return $number;
});

If you open your browser and you go to your /get-random-number path (in my case, http://127.0.0.1:81/get-random-number because I’m using Laravel Sail and I set APP_PORT=81 in the .env file), you can see the random number. If you refresh the page, you will see the same number for 10 seconds, or in general for the interval time set with the tick() function (in the service provider file).

Note

The storage in the Octane cache is not permanent; it is volatile. This means that you can lose the values every time the server is restarted.

With the Octane cache, we have also some other nice methods, such as increment and decrement, for example:

Route::get('/increment-number', function () {
    $number =
      Cache::store('octane')->increment('my-number');
    return $number;
});
Route::get('/decrement-number', function () {
    $number =
      Cache::store('octane')->decrement('my-number');
    return $number;
});
Route::get('/get-number', function () {
    $number = Cache::store('octane')->get('my-number', 0);
    return $number;
});

Now, open your browser and the /increment-number path load multiple times and then load /get-number; you will see the incremented value.

Other useful functions for managing multiple values are putMany(), which saves an array of items, and many(), which retrieves more items at a time:

Route::get('/save-many', function () {
    Cache::store('octane')->putMany([
        'my-number' => 42,
        'my-string' => 'Hello World!',
        'my-array' => ['Kiwi', 'Strawberry', 'Lemon'],
    ]);
    return "Items saved!";
});
Route::get('/get-many', function () {
    $array = Cache::store('octane')->many([
        'my-number',
        'my-string',
        'my-array',
    ]);
    return $array;
});

If you open your browser first at the /save-many path and then at /get-many, you will see the following on the page:

{"my-number":42,"my-string":"Hello World!","my-array":["Kiwi","Strawberry","Lemon"]}

If you saved a value as an item of an array with the putMany() method, you can retrieve it as a single item using the array key as the cache key:

Route::get('/get-one-from-many/{key?}', function ($key = "my-number") {
    return Cache::store('octane')->get($key);
});

If you open the /get-one-from-many/my-string page, you will see the following:

Hello World!

This means that the value with the "my-string" key is retrieved from the cache.

In the end, we are using the Laravel cached mechanism with Swoole as the backend provider.

Why should we use Swoole as a backend cache provider? What advantages does it have over other providers such as databases, for example? The way Swoole is made, we have seen that performance is certainly one of its main values. Using this kind of cache allows us to share information between workers in a very efficient way. Swoole’s official documentation reports that it supports a read-write rate of 2 million per second.

The Laravel Octane cache is implemented via Swoole Table. In the next section, we will look at Swoole Table.

Swoole Table

If you want to share data across workers in a more structured way than a cache, you can use Swoole Table. As already mentioned, the Octane cache uses Swoole Table, so let me explain a bit more about Swoole Table.

First, if you want to use Swoole Table, you must define the structure of the table. The structure is defined in the config/octane.php file in the tables section.

Figure 3.5 – The configuration of a table in a config/octane.php file

Figure 3.5 – The configuration of a table in a config/octane.php file

By default, when you execute the octane:install command, a config/octane.php file is created for you, and a table is already defined: a table named example with a maximum of 1,000 rows (example:1000) with 2 fields. The first one is called name and is a string with a maximum of 500 characters, and the second field is called votes and the type is an integer (int).

In the same way, you can configure your table. The field types are as follows:

  • int: For integers
  • float: For floating-point numbers
  • string: For strings; you can define the max number of characters

Once you have defined the table in the config file, you can set the values of the table.

For example, I’m going to create a my-table table of max 100 rows with some fields. In the config/octane.php file in the tables section, we are going to create the following:

  • A table named my-table with a maximum of 100 rows
  • A UUID field with a string type (maximum of 36 characters)
  • A name field with a string type (maximum of 1000 characters)
  • An age field with an int type
  • A value field with a float type

Therefore, the configuration is as follows:

        'my-table:100' => [
            'uuid' => 'string:36',
            'name' => 'string:1000',
            'age' => 'int',
            'value' => 'float',
        ],

When the server starts, Octane will create the table in memory for us.

Now, we are going to create two routes: the first one is for seeding the table with some fake data, and the second one is for retrieving and showing information from the table.

The first route, /table-create, will do the following:

  • Obtain the table instance for my-table. my-table is the table configured in config/octane.php.
  • Create 90 rows, filling fields for uuid, name, age, and value. For filling the values, we are going to use the fake() helper. This helper allows you to generate random values for UUIDs, names, integer numbers, decimal numbers, and so on.

The code in routes/web.php is as follows:

Route::get('/table-create', function () {
    // Getting the table instance
    $table = Octane::table('my-table');
    // looping 1..90 creating rows with fake() helper
    for ($i=1; $i <= 90; $i++) {
        $table->set($i,
        [
            'uuid' => fake()->uuid(),
            'name' => fake()->name(),
            'age' => fake()->numberBetween(18, 99),
            'value' => fake()->randomFloat(2, 0, 1000)
        ]);
    }
    return "Table created!";
});

The snippet creates the table once the /table-create URL is called.

If you open http://127.0.0.1:81/table-create, you will see a table is created with some rows.

On your page, it’s possible that you have an error such as this one:

SwooleTable::set(): failed to set('91'), unable to allocate memory

Be sure that the size of the table in the configuration file is greater than the number of rows that you are creating. When we work with a database table, we don’t have to set the maximum number of rows; in this case, with this kind of table (in a memory table shared across the workers), we have to be aware of the number of rows.

Once everything is fine, you will see Table created! on your web page. This means that the rows were created in the right way.

To verify this, we are going to create a new route, /table-get, where we do the following:

  • Obtain the table instance for my-table (the same table we used in /table-create)
  • Get the rows with index 1
  • Return the row (associative array, where the items are the fields of the row with uuid, name, age, and value)

In the routes/web.php file, define the new route:

Route::get('/table-get', function () {
    $table = Octane::table('my-table');
    $row = $table->get(1);
    return $row;
});

Opening https://127.0.0.1:81/table-get (after opening /table-create because you have to create rows before accessing them), you should see something such as the following:

{"uuid":"6e7c7eb6-9ecf-3cf8-8de9-5034f4c44ab5","name":"Hugh Larson IV","age":81,"value":945.67}

You will see something different in terms of content because rows were generated using the fake() helper, but you will see something similar in terms of structure.

You can also walk through Swoole Table using foreach(), and you can also use the count() function (for counting elements of a table) on the Table object:

Route::get('/table-get-all', function () {
    $table = Octane::table('my-table');
    $rows=[];
    foreach ($table as $key => $value) {
        $rows[$key] = $table->get($key);
    }
    // adding as first row the table rows count
    $rows[0] = count($table);
    return $rows;
});

In the preceding example, for counting rows, you can see that in the end, we can access the Swoole object and use the functions and methods implemented by it. Another example of using a Swoole object is accessing the server object, as in the next section for retrieving metrics.

Metrics

Swoole offers a method to retrieve some metrics about the resource usage of the application server and the workers. This is useful if you want to track the usage of some aspect – for example, the time, the number of requests served, the active connections, the memory usage, the number of tasks, and so on. To retrieve metrics, first of all, you have to access the Swoole class instance. Thanks to the service container provided by Laravel, you can resolve and access objects from the container. The Swoole server object is stored in the container by Octane so, via App::make, you can access the Swoole server class instance. In the routes/web.php file, you can create a new route where you can do the following:

  • You can retrieve the Swoole server via App::make
  • Once you can access the object, you can use their methods, such as, for example, stats
  • The Swoole server Stats object is returned as a response:
use IlluminateSupportFacadesApp;
Route::get('/metrics', function () {
    $server = App::make(SwooleHttpServer::class);
    return $server->stats();
});

On the new /metrics page, you can see all the metrics provided by the Swoole server.

For all methods, you can see directly the server documentation provided by Swoole: https://openswoole.com/docs/modules/swoole-server-stats.

Summary

In this chapter, we looked at how to install and configure an environment with Swoole. Then, we also analyzed the various functionalities exposed by this application server.

We walked through the functionalities with simple examples so that we could focus on the individual features. In the next chapter, we will look at using Laravel Octane with an application server in a more real-world context.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset