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
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 Swoole | PHP-FPM | |
Supports TCP, UDP, HTTP, HTTP2, and Unix Socket | Yes | No. Must use additional libraries |
Uses Non-blocking I/O | Yes | No |
Distributes worker processes to each CPU – supports concurrent processing | Yes | No |
Pre-loads PHP files into memory | Yes | No |
Supports long-live connections for WebSocket servers or TCP/UDP servers | Yes | No |
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.
Laravel + Nginx + PHP-FPM
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:
Laravel + Nginx + PHP-FPM
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
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 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 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
>>>>> 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 |