Hola FrankenPHP! Laravel Octane Servers Comparison: Pushing the Boundaries of Performance

Emre Çalışkan
Beyn Technology
Published in
5 min readJan 3, 2024
Photo by Lars Kienle on Unsplash

As the complexity of web applications continues to grow, developers are increasingly focusing on performance and scalability. Laravel, known as a powerful PHP framework, addresses these needs. However, various solutions are being developed to further enhance the performance and scalability of this framework.

Laravel Octane is one such solution, designed to make Laravel applications faster and more scalable. However, there are significant differences in the types of servers Octane uses to achieve these goals. Used mostly on three servers so far with Laravel Octane. Open Swoole, Swoole and RoadRunner

In an unexpected turn, a new participant has joined the array of Octane servers: FrankenPHP. This new server is crafted to push the boundaries of performance even further, offering an optimized solution for Laravel applications. In this article, we’ll evaluate FrankenPHP alongside the traditional servers, Open Swoole, Swoole and RoadRunner, comparing the innovations and performance features each brings to the table.

For Laravel developers, this article will provide an in-depth analysis performance and resource consumption of Octane servers, helping them understand which server option may be more effective in specific scenarios. By exploring the advantages of Open Swoole, Swoole and RoadRunner and introducing FrankenPHP, we aim to guide you in selecting the most suitable server to maximize your Laravel applications. Are you ready for an exciting journey into the universe of Octane servers, now featuring this new contender?

Test Endpoints

These endpoints represent three distinct API routes in your Laravel application, each serving a different purpose:

  1. Health Check Endpoint (/health-check): This endpoint is used to assess the overall health of the application. When called, it performs a health check, evaluating the status of targeted services, and returns an HTTP 204 No Content response. This signifies that the core functionality of the application is active, and the health status is positive.
  2. Static Information Endpoint (/static): This endpoint provides static information about the application by returning a simple JSON response. The JSON response typically includes constant values, versions, or other static details about the application. This endpoint offers quick and easy access for clients to gather basic information about the application.
  3. HTTP Requests Endpoint (/http-request): This endpoint utilizes Laravel's Http service to send an HTTP GET request to another API (in this case, "http://whoami/api"). The received response demonstrates the ability of the Laravel application to interact with external resources. This functionality can be utilized in scenarios such as fetching data from external APIs or accessing resources.
    The traefik/whoami package is a simple example application that, upon receiving an HTTP request, provides detailed information about the source of the request. This application delivers an informative response containing HTTP headers, client IP address, request timestamp, and other relevant details.

These three endpoints represent different usage scenarios, allowing for various tests to evaluate the overall performance of the application.

<?php

use App\Services\HttpService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "api" middleware group. Make something great!
|
*/

Route::get('/health-check', fn (Request $request) => Response::noContent())->name('health-check');
Route::get('/static', fn (Request $request) => Response::json(['status' => true]))->name('static');
Route::get('/http-request', fn (Request $request, HttpService $httpService) => Response::json(json_decode($httpService->get('http://whoami/api'))))->name('http-request');

Docker Images

Open Swoole

FROM php:8.3-alpine

COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer

RUN install-php-extensions pcntl sockets openswoole

COPY . /var/www
COPY .env.example /var/www/.env

WORKDIR /var/www

RUN composer install --no-dev

ENTRYPOINT ["php", "artisan", "octane:start", "--server=swoole", "--port=9801", "--workers=16", "--host=0.0.0.0"]

Swoole

FROM php:8.3-alpine

COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer

RUN install-php-extensions pcntl sockets swoole

COPY . /var/www
COPY .env.example /var/www/.env

WORKDIR /var/www

RUN composer install --no-dev

ENTRYPOINT ["php", "artisan", "octane:start", "--server=swoole", "--port=9802", "--workers=16", "--host=0.0.0.0"]

RoadRunner

FROM php:8.3-alpine

COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
COPY --from=ghcr.io/roadrunner-server/roadrunner:latest /usr/bin/rr /usr/local/bin/rr

RUN install-php-extensions pcntl sockets

COPY . /var/www
COPY .env.example /var/www/.env

WORKDIR /var/www

RUN composer install --no-dev

ENTRYPOINT ["php", "artisan", "octane:start", "--server=roadrunner", "--port=9803", "--workers=16", "--host=0.0.0.0"]

FrankenPHP

FROM php:8.3-alpine

COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer

RUN install-php-extensions pcntl sockets

COPY . /var/www
COPY .env.example /var/www/.env

WORKDIR /var/www

RUN apk add jq

RUN wget -O/usr/local/bin/frankenphp $(wget -O- https://api.github.com/repos/dunglas/frankenphp/releases/latest | jq '.assets[] | select(.name=="frankenphp-linux-x86_64") | .browser_download_url' -r) && chmod +x /usr/local/bin/frankenphp

RUN composer install --no-dev

ENTRYPOINT ["php", "artisan", "octane:start", "--server=frankenphp", "--port=9804", "--workers=16", "--host=0.0.0.0"]

Docker Compose

version: "3.9"

services:
whoami:
container_name: "whoami"
image: "containous/whoami"
openswoole:
container_name: "openswoole"
image: "ghcr.io/thecaliskan/laravel-benchmark:openswoole"
ports:
- "9801:9801"
swoole:
container_name: "swoole"
image: "ghcr.io/thecaliskan/laravel-benchmark:swoole"
ports:
- "9802:9802"
roadrunner:
container_name: "roadrunner"
image: "ghcr.io/thecaliskan/laravel-benchmark:roadrunner"
ports:
- "9803:9803"
frankenphp:
container_name: "frankenphp"
image: "ghcr.io/thecaliskan/laravel-benchmark:frankenphp"
ports:
- "9804:9804"

Install

To perform benchmark tests on your server, follow these steps to set up the application:

wget https://raw.githubusercontent.com/thecaliskan/laravel-benchmark/master/docker-compose.yml

docker compose up -d

Benchmark

Now that your Laravel application is up and running, you can conduct benchmark tests for various server options. Use the following commands for each server:

OpenSwoole

wrk -t16 -c100 -d30s --latency  http://127.0.0.1:9801/api/health-check
wrk -t16 -c100 -d30s --latency http://127.0.0.1:9801/api/static
wrk -t16 -c100 -d30s --latency http://127.0.0.1:9801/api/http-request

Swoole

wrk -t16 -c100 -d30s --latency  http://127.0.0.1:9802/api/health-check
wrk -t16 -c100 -d30s --latency http://127.0.0.1:9802/api/static
wrk -t16 -c100 -d30s --latency http://127.0.0.1:9802/api/http-request

RoadRunner

wrk -t16 -c100 -d30s --latency  http://127.0.0.1:9803/api/health-check
wrk -t16 -c100 -d30s --latency http://127.0.0.1:9803/api/static
wrk -t16 -c100 -d30s --latency http://127.0.0.1:9803/api/http-request

FrankenPHP

wrk -t16 -c100 -d30s --latency  http://127.0.0.1:9804/api/health-check
wrk -t16 -c100 -d30s --latency http://127.0.0.1:9804/api/static
wrk -t16 -c100 -d30s --latency http://127.0.0.1:9804/api/http-request

Stack

Software

PHP: v8.3.1
Laravel: v10.39.0
Laravel Octane: v2.2.6
Open Swoole: v22.1.2
Swoole: v5.1.1
RoadRunner: v2023.3.8
FrankenPHP: v1.0.3
Docker Engine: v24.0.7
Operating System: Fedora Linux 39 (Workstation Edition)
Kernel Version: Linux 6.6.8–200.fc39.x86_64

Hardware

Intel i9 9900K (8 Core/16 Thread, 3.6 Ghz, Turbo 5.0 Ghz)
32 GB DDR4 3200 Mhz
Samsung 970 EvoPlus SSD (3.500w/3.300r MB/s)

Test Results

Conclusion

In this article, we delved into different server options used for Laravel Octane: Open Swoole, Swoole, RoadRunner, and the newcomer, FrankenPHP. The comparisons included benchmark tests to measure their performances.

Based on the test outcomes, you can choose the server that best fits the needs of your project. Tests conducted through health checks, static information, and HTTP requests helped identify the standout features of each server.

It’s crucial to note that since each application has unique requirements and features, careful consideration should be given to selecting the right server to achieve optimal performance. This article aims to provide Laravel Octane users and performance enthusiasts with a guide to making the most effective server choice for their projects.

Thank you for reading!

--

--