Share This
//Boost Application Performance with Laravel Octane

Boost Application Performance with Laravel Octane

As you may know, traditional Laravel applications process one request (request) at a time. However, with Laravel Octane, you can handle multiple requests simultaneously, significantly increasing your website’s speed.

1. What is Laravel Octane?

Laravel Octane is an open-source package designed to speed up Laravel applications. It starts your application once, keeps it in memory (RAM), and then all subsequent requests to the server will reuse the operations stored in RAM, rather than restarting the application from scratch.

Request Lifecycle in Laravel

Request Lifecycle in Laravel Octane

Another feature of Laravel Octane is its ability to use multiple workers to handle requests in parallel. As a result, instead of processing one request at a time as before, you can now handle multiple requests simultaneously.

Request Processing Diagram in Laravel Octane

Request Processing Diagram in Laravel Octane

Octane is built on top of FrankenPHP, Swoole, and RoadRunner—three asynchronous utilities for PHP. In this article, we will focus on the Swoole platform.

PHP Swoole is designed based on the principles of Erlang, Node.js, and Netty, but specifically for PHP. However, since Swoole is only compatible with the Linux Kernel, it currently only works on Linux, OS X, Cygwin, or WSL.

2. Differences Between PHP Swoole and PHP-FPM

Here is a comparison table between PHP Swoole and PHP-FPM.

PHP SwoolePHP-FPM
Supports TCP, UDP, HTTP, HTTP2, and Unix SocketYesNo. Must use additional libraries
Uses Non-blocking I/OYesNo
Distributes worker processes to each CPU – supports concurrent processingYesNo
Pre-loads PHP files into memoryYesNo
Supports long-live connections for WebSocket servers or TCP/UDP serversYesNo

3. Speed Comparison Between Laravel Octane and PHP-FPM

To test the real performance of Laravel Octane, we will conduct a small demo and benchmark to see the results between PHP Octane and PHP-FPM.

We will test the speed of Laravel Octane and PHP-FPM on a virtual machine (VMware):

Test bench stats:

  • CPU: 2 cores (11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz)
  • RAM: 4GB
  • OS: CentOS 7

Test applications:

  • Laravel 10.10
  • PHP 8.2

Web servers:

  • PHP-FPM
  • NGINX
  • Laravel Octane

We will use the tool wrk to test the speed.

(wrk is a tool to test website load by simulating multiple concurrent connections to your website.)

Using 4 threads (threads) and 100 concurrent connections (connections), and testing on the default welcome page provided when you install a Laravel website.

Speed Test with wrk command Laravel + Nginx + PHP-FPM

Laravel + Nginx + PHP-FPM

Speed Test with wrk command Laravel Octane + Nginx

Laravel Octane + Nginx

As you can see from the results, Laravel Octane is 5 times faster than PHP-FPM (Laravel Octane can handle ~42 requests/second compared to ~8 requests/second for PHP-FPM).

Below are the actual response times when we test using a browser (browser) with the itsgoingd/clockwork extension:

Speed Test on Browser Laravel + Nginx + PHP-FPM

Laravel + Nginx + PHP-FPM

Speed Test on Browser Laravel Octane + Nginx

Laravel Octane + Nginx

We can see that the response time for the first request from both Laravel Octane and PHP-FPM is almost the same. However, for subsequent requests, Laravel Octane responds much faster.

4. Installation and Usage

a. Installing Octane

Octane can be installed via Composer:

composer require laravel/octane

After installing Octane, run the Artisan command octane:install to install the configuration files into your Laravel application.

php artisan octane:install

b. Installing Swoole

Since we are focusing on the Swoole platform in this article, let’s proceed with installing Swoole.

To use Swoole in Octane, you need to install the Swoole PHP extension. This can be done using the PECL command:

pecl install swoole

c. Starting Octane

You can start the Octane server using the Artisan command octane:start. By default, this command will use the config/octane.php file to run.

Octane will start the server on port 8000. You can access your website at http://localhost:8000.

To change the port, just add the --port 8888 option.

php artisan octane:start --port 8888

d. Starting Octane with HTTPS

When running the application with Octane, the URL will default to http://. If you want to use https://, simply add the OCTANE_HTTPS variable to the .env file and set its value to true.

OCTANE_HTTPS=true

Or change the config/octane.php file.

'https' => env('OCTANE_HTTPS', true),

e. Starting Octane with Nginx

In production environments, applications should be run through web servers like Nginx or Apache. This allows the web server to serve static content like images, CSS, etc., as well as manage your SSL certificates.

As shown in the example below, Nginx will serve static content while the Octane server runs on port 8000:

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}
 
server {
    listen 80;
    listen [::]:80;
    server_name domain.com;
    server_tokens off;
    root /home/forge/domain.com/public;
 
    index index.php;
 
    charset utf-8;
 
    location /index.php {
        try_files /not_exists @octane;
    }
 
    location / {
        try_files $uri $uri/ @octane;
    }
 
    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }
 
    access_log off;
    error_log  /var/log/nginx/domain.com-error.log error;
 
    error_page 404 /index.php;
 
    location @octane {
        set $suffix "";
 
        if ($uri = /index.php) {
            set $suffix ?$query_string;
        }
 
        proxy_http_version 1.1;
        proxy_set_header Host $http_host;
        proxy_set_header Scheme $scheme;
        proxy_set_header SERVER_PORT $server_port;
        proxy_set_header REMOTE_ADDR $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
 
        proxy_pass http://127.0.0.1:8000$suffix;
    }

}

5. Important Considerations When Using Laravel Octane

a. Handling File Changes While Octane is Running

Since Octane starts your application once and keeps it in memory (RAM), any changes made to files in the application will not be reflected until you reload your browser. For example, if you change the logic of the store function in the CustomerController, it will not be reflected until the server is restarted. For convenience, you can use the --watch option to instruct Octane to automatically restart the server whenever any file changes in your application:

php artisan octane:start --watch

Before using this feature, ensure that Node is installed in your development environment. Additionally, you should install the chokidar (file-watching) library into your application:

npm install --save-dev chokidar

You can configure the folders and files to be watched by changing the config in the config/octane.php file:

For example, if you are using the nwidart/laravel-modules library and want the Modules folder to be reflected when changes occur, you can do as follows:

<?php
 
'watch' => [
    'app',
    'bootstrap',
    'config',
    'database',
    'public/**/*.php',
    'resources/**/*.php',
    'routes',
    'composer.lock',
    'Modules/**/*.php',  //laravel module folder
    'Modules/**/*.js',     //laravel module folder
    'Modules/**/*.css',  //laravel module folder
    '.env',
],

Note: If file watching doesn’t work as expected with the above approach, you may need to adjust the file vendor\laravel\octane\bin\file-watcher.cjs and set the value of usePolling to true to enable file watching functionality.

const watcher = chokidar.watch(paths, {
    ignoreInitial: true,
    usePolling: true,
});

Alternatively, if you’re using docker-compose.yml, you can add the environment variable CHOKIDAR_USEPOLLING with the value true to enable this feature.

environment:
    CHOKIDAR_USEPOLLING: true

b. Specifying the Number of Workers

When you start the Laravel Octane application, the number of workers will correspond to the number of CPU cores on your server (for example, if your server has 2 cores, there will be 2 workers by default). These workers will be used to handle incoming HTTP requests to your application.

Checking progress when starting Octane

Checking progress when starting Octane

You can also manually specify the number of workers by using the --worker option when running the octane:start command.

php artisan octane:start --workers=4

Checking progress when starting Octane with 6 workers

Checking progress when starting Octane with 4 workers

Note: If you use more workers than the CPU cores on the server, it could lead to resource contention and decreased performance. Be sure to carefully consider the number of workers you configure.

c. Memory Leaks

Octane keeps your application in memory (RAM) between requests. Therefore, adding data to static objects will lead to memory leaks. Here’s an example:

Add a route to the routes/web.php file.

<?php
 
use App\Http\Controllers\TestMemoryLeakerController;
use Illuminate\Support\Facades\Route;
 
Route::get('/test', [TestMemoryLeakerController::class, 'test']);

Create a file named App/Services/TestMemoryLeakerService.php.

<?php
 
namespace App\Services;
 
class TestMemoryLeakerService
{
    // Static array object
    public static array $staticArray = [];
 
    // Non-static array object
    public array $array = [];
}

Create a file named App/Http/Controllers/TestMemoryLeakerController.php.

<?php
 
namespace App\Http\Controllers;
 
use App\Services\TestMemoryLeakerService;
use Illuminate\Support\Str;
 
class TestMemoryLeakerController extends Controller
{
    public function test()
    {
        // Create string data with 1MB size
        TestMemoryLeakerService::$staticArray[] = Str::repeat('a', 1024 * 1024);
        $memoryUsage = memory_get_peak_usage(true) / 1024 / 1024;
 
        // dd the result and you will see memory usage increase every time you refresh the page
        dd($memoryUsage . "MB");
    }
}

The purpose of the above code is to push 1MB of data into an array. Since this is a static object, when a user sends a request, the data is added to the array, increasing memory usage. This happens everywhere the data is added to the array, leading to memory overflow and server crashes.

To fix this, you can use Dependency Injection, for example, the following code:

<?php
 
namespace App\Http\Controllers;
 
use App\Services\TestMemoryLeakerService;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
 
class TestMemoryLeakerController extends Controller
    {
    public function test(TestMemoryLeakerService $testMemoryLeakerService)
    {
        // Create string data with 1MB size
        $testMemoryLeakerService->array[] = Str::repeat('a', 1024 * 1024);
        $memoryUsage = memory_get_peak_usage(true) / 1024 / 1024;
 
        // dd the result and you will see memory not change after multiple F5 refresh
        dd($memoryUsage . "MB");
    }
}

Also, using the barryvdh/laravel-debugbar tool on Octane can also lead to memory leaks, but the developers of barryvdh have yet to fix the issue. This is an inconvenience when using this tool.

In such cases, don’t worry, you can replace it with itsgoingd/clockwork. It works as well as barryvdh/laravel-debugbar and supports Octane without causing memory leaks.

Simply search for itsgoingd/clockwork on Google and you’ll find many guides on how to install it step by step.

d. Specifying Max Request Count

To prevent memory leaks, Octane will restart workers that have handled 500 requests (this is the default value when processing by a worker). When the worker is restarted, it will free up all the memory it was using. This default value can be adjusted using the --max-requests option:

php artisan octane:start --max-requests=250

e. Singleton

When applying Octane to a Laravel application, special attention is needed to manage singleton objects. It is important that our application is only instantiated once, and any changes made will persist until the Octane server is stopped.

Passing Application Instance to Singletons

Do not pass the application instance directly to the singletons. Instead, pass a callback that returns an instance.

<?php
 
use App\Service;
use Illuminate\Contracts\Foundation\Application;
 
// Do not do this
$this->app->singleton(Service::class, function (Application $app) {
    return new Service($app);
});
<?php
 
use App\Service;
use Illuminate\Container\Container;
use Illuminate\Contracts\Foundation\Application;
 
// Do this
$this->app->bind(Service::class, function (Application $app) {
    return new Service($app);
});
 
// Or do this
$this->app->singleton(Service::class, function () {
    return new Service(fn () => Container::getInstance());
});

Singletons persist between requests, meaning that the application instance passed into the singleton and resolved the first time will be used when the service is called in subsequent requests.

Passing the Request Instance into Singletons

Similarly to above:

<?php
 
use App\Service;
use Illuminate\Contracts\Foundation\Application;
 
// Do not do this
$this->app->singleton(Service::class, function (Application $app) {
    return new Service($app['request']);
});
<?php
 
use App\Service;
use Illuminate\Contracts\Foundation\Application;
 
// Do this
$this->app->bind(Service::class, function (Application $app) {
    return new Service($app['request']);
});
 
// Or do this
$this->app->singleton(Service::class, function () {
    return new Service(fn () => $app['request']);
});

6. New Features in Laravel Octane

a. Concurrent Tasks

By default, PHP is a synchronous, single-threaded language that can only process one task at a time. However, when using Swoole, you can handle multiple tasks concurrently. This can be achieved with the Concurrent feature. Multiple task handling with Concurrent feature

Multiple task handling with the Concurrent feature

Concurrent tasks use “task workers” to process tasks. The number of task workers is specified by the --task-workers option in the octane:start command.

php artisan octane:start --workers=4 --task-workers=6

The following example will help you understand better:

<?php namespace App\Http\Controllers; class TestConcurrentController extends Controller { public function test() { $startTime = microtime(true); // Operation 1 sleep(5); // Operation 2 sleep(2); // Operation 3 sleep(2); $endTime = microtime(true); $totalTime = $endTime - $startTime; dd($totalTime); // 9.** } } 

The output of the $totalTime variable in this case will be around 9 seconds, which is 5 + 2 + 2, the time needed to complete these operations.

<?php namespace App\Http\Controllers; use Laravel\Octane\Facades\Octane; class TestConcurrentController extends Controller { public function test() { $startTime = microtime(true); Octane::concurrently([ // Operation 1 fn() => sleep(2), // Operation 2 fn() => sleep(2), // Operation 3 fn() => sleep(2), // Operation 4 fn() => sleep(2), // Operation 5 fn() => sleep(2), // Operation 6 fn() => sleep(2), // Operation 7 fn() => sleep(2), ]); $endTime = microtime(true); $totalTime = $endTime - $startTime; // --task-workers=6 dd($totalTime); // 4.** } } 

We have 9 tasks with 6 workers specified, each worker handles one task, so we have 3 remaining tasks, and the execution time for this task is still around 2 seconds. Once the previous 6 tasks are completed, the remaining 3 tasks will continue to be processed. Thus, two workers will run fn() => sleep(2) twice, leading to a processing time of approximately 4 seconds.

When using the concurrently method, you should not provide more than 1024 tasks because this is the limit of Swoole.

b. Octane Cache

When using Swoole, you can use Octane’s cache driver, which provides read and write speeds of up to 2 million operations per second. Therefore, Octane’s cache driver is an excellent choice for applications requiring extremely high read/write speeds from the cache.
All data stored in the cache is shared across all workers on the server. However, the data stored in the cache will be cleared when the server restarts.

<?php namespace App\Http\Controllers; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Str; class TestCacheController extends Controller { public function test() { Cache::store('octane')->put('a', Str::repeat('a', 10), 30); $a = Cache::store('octane')->get('a'); dd($a); } } 

Note: You cannot use the command php artisan cache:clear to clear Octane’s cache. Instead, you need to restart the server to clear the cache.

c. Octane Table

When using Swoole, you can define and interact with your custom Swoole tables. These tables provide extremely high retrieval speeds, and all workers on the server can access the data stored in these tables.
The data here is stored in rows and columns, just like in a database table. To start using this feature, you must first set up the tables in the config/octane.php file as shown in the example below:

<?php 'tables' => [ 'students:1000' => [ 'fullname' => 'string:1000', 'class' => 'string:20', 'created_at' => 'int', ], 'classes:1000' => [ 'class_name' => 'string:1000', 'class_code' => 'string:20', 'created_at' => 'int', ], ], 

For the table students:1000, the 1000 represents the maximum number of rows that the table can hold.

For a column string:n, 1000 and 20 represent the maximum size (in bytes) that the column can hold.

If you want to increase these limits, you can modify the cache array in the config/octane.php file:

<?php 'cache' => [ 'rows' => 1000, 'bytes' => 10000, ], 

Example:

<?php namespace App\Http\Controllers; use Carbon\Carbon; use Laravel\Octane\Facades\Octane; class TestTableController extends Controller { public function test() { Octane::table('students')->set('class_monitor', [ 'fullname' => 'Joel Bradley', 'class' => '12A7', 'created_at' => Carbon::now()->timestamp, ]); Octane::table('students')->set('student', [ 'fullname' => 'Reece Bates', 'class' => '12A7', 'created_at' => Carbon::now()->timestamp, ]); $classMonitor = Octane::table('students')->get('class_monitor'); $student = Octane::table('students')->get('student'); dd([ 'class_monitor' => $classMonitor, 'student' => $student, ]); } } 

Output:

array:2 [ "class_monitor" => array:3 [ "fullname" => "Joel Bradley" "class" => "12A7" "created_at" => 1706549430 ] "student" => array:3 [ "fullname" => "Reece Bates" "class" => "12A7" "created_at" => 1706549430 ] ] 

Here are a few considerations when using Octane Table:

  • Data stored in the Octane table is temporary and will be lost when the server restarts.
  • Swoole only supports the following column data types: string, int, float. This data structure is stored temporarily in memory and cannot replace a database.
  • And once again, the more data you store in the table, the more memory (RAM) it will consume.

d. Ticks & Intervals

With Swoole, you can use the Ticks feature to perform a repetitive action after a certain time interval. These are also referred to as internals, similar to the setInterval method in JavaScript.

<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; use Laravel\Octane\Facades\Octane; class AppServiceProvider extends ServiceProvider { /** * Bootstrap any application services. */ public function boot(): void { Octane::tick('simple-ticker', fn () => var_dump('Ticking...'))->seconds(2); } } 

'simple-ticker' is the name of the ticker fn () => var_dump('Ticking...') — this is a callable and will be executed after a certain time interval.

Note: We cannot stop this ticks command when the Octane server is running, so be careful when using this feature.

You can also use the immediate method to execute the ticks command immediately when the Octane server starts and then continue executing the ticks command every n seconds afterwards:

<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; use Laravel\Octane\Facades\Octane; class AppServiceProvider extends ServiceProvider { /** * Bootstrap any application services. */ public function boot(): void { Octane::tick('simple-ticker', fn () => var_dump('Ticking...'))->seconds(2)->immediate(); } } 

7. Conclusion

Laravel Octane is an optimal solution for application performance, especially when handling multiple concurrent tasks. However, attention should be paid to compatibility and adjustment for the application’s libraries and functions. With its new features, Laravel Octane promises to provide convenience and powerful support for Laravel application development, significantly boosting performance for projects that require such capabilities.


References

>>>>> Laravel Octane

>>>>> Free Performance + Extra features for Laravel using Octane and Swoole

>>>>> Laravel Octane: With Great Performance Comes Great Responsibility

Read more articles below to increase the speed of your website

>>>>> OPTIMIZING WEBSITE SPEED WITH ASYNCHRONOUS AND DEFERRED JAVASCRIPT

Ngụy Minh Tuấn
PHP Developer

APPLY NOW






    Benefits

    SALARY & BONUS POLICY

    RiverCrane Vietnam sympathizes staffs' innermost feelings and desires and set up termly salary review policy. Performance evaluation is conducted in June and December and salary change is conducted in January and July every year. Besides, outstanding staffs receive bonus for their achievements periodically (monthly, yearly).

    TRAINING IN JAPAN

    In order to broaden staffs' view about technologies over the world, RiverCrane Vietnam set up policy to send staffs to Japan for study. Moreover, the engineers can develop their career paths in technical or management fields.

    ANNUAL COMPANY TRIP

    Not only bringing chances to the staffs for their challenging, Rivercrane Vietnam also excites them with interesting annual trips. Exciting Gala Dinner with team building games will make the members of Rivercrane connected closer.

    COMPANY'S EVENTS

    Activities such as Team Building, Company Building, Family Building, Summer Holiday, Mid-Autum Festival, etc. will be the moments worthy of remembrance for each individual in the project or the pride when one introduces the company to his or her family, and shares the message "We are One".

    INSURANCE

    Rivercrane Vietnam ensures social insurance, medical insurance and unemployment insurance for staffs. The company commits to support staffs for any procedures regarding these insurances. In addition, other insurance policies are taken into consideration and under review.

    OTHER BENEFITS

    Support budget for activities related to education, entertainment and sports. Support fee for purchasing technical books. Support fee for getting engineering or language certificates. Support fee for joining courses regarding technical management. Other supports following company's policy, etc.

    © 2012 RiverCrane Vietnam. All rights reserved.

    Close