Wars of Symfony Runtimes: A Performance Odyssey

Emre Çalışkan
Beyn Technology
Published in
7 min readFeb 1, 2024

In the ever-evolving landscape of web application development, complexities arise as new technologies emerge daily. This rapid transformation provides developers with a plethora of options, but it also introduces challenges in making the right choices. Symfony, being a flexible and powerful PHP web application framework, offers developers a wide array of runtime options. However, making the correct choice among these options is crucial for the performance, scalability, and maintenance ease of an application. In this article, we will delve into a comparative analysis of Symfony’s different runtimes, exploring the advantages, disadvantages, and use-case scenarios for each. Choosing the right runtime for your Symfony applications can be a decisive factor in the success of your project.

This article aims to conduct a comprehensive comparison of notable Symfony runtimes, namely Swoole, Open Swoole, Road Runner, and FrankenPHP. By delving into the strengths, weaknesses, and optimal use cases of each runtime, we aim to guide developers in making informed decisions for their Symfony projects. The careful selection of a runtime can significantly influence the success of a Symfony application, and understanding the nuances of these alternatives is pivotal for developers navigating the ever-expanding landscape of web development.

Runtime Selection Criteria

Certainly, when choosing a runtime, performance is undoubtedly a crucial factor, but it’s not solely dependent on performance. Other significant factors include:

  1. Developer Experience: Runtime selection impacts the developer experience. Developers can work more effectively when using a runtime that aligns well with their preferences. Factors such as user-friendly monitoring tools, debugging capabilities, and documentation significantly affect the developer experience.
  2. Community and Ecosystem Support: The presence of a strong community and a broad ecosystem behind a runtime provides developers with easier access to resources and faster issue resolution. Community-supported runtimes may have advantages in terms of adding new features and providing security updates.
  3. Project Requirements: The specific features and requirements of a project can be determinative in choosing the appropriate runtime. For example, certain runtimes might integrate better with specific databases or other external systems.
  4. Scalability and Resource Usage: The size and scale of a project can influence the choice of an appropriate runtime. Lightweight and scalable runtimes might be advantageous for smaller projects, while more robust runtimes that effectively manage resource usage may be preferred for larger-scale applications.
  5. Compatibility with Existing Infrastructure: Compatibility with other tools, services, and infrastructure elements used in the project can influence runtime selection. It’s essential for a runtime to be compatible with existing infrastructure and facilitate easy integration.
  6. Security and Update Policies: The runtime should adhere to security standards and receive regular updates to ensure the project’s long-term security.

These factors, in addition to performance, play a crucial role in guiding the selection of an ideal runtime. Evaluating these considerations in a balanced manner can enhance the overall success and sustainability of a project.

Now that we have considered essential criteria such as developer experience, community support, documentation, security, and compatibility, it is pivotal to delve deeper into the performance aspects of the identified Symfony runtimes. Conducting a comprehensive performance analysis involves scrutinizing key metrics like Request per Second (RPS), latency values, and transfer speeds across different scenarios. By closely examining how each runtime handles these performance indicators, we can gain insights into their efficiency under various workloads. This deeper performance evaluation will guide us in making an informed decision, ensuring that not only do the chosen runtimes meet the project’s non-functional requirements but also deliver optimal speed and responsiveness crucial for a successful Symfony application.

Test Endpoints

These endpoints represent three distinct API routes in your Symfony 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 Symfony'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 Symfony 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

namespace App\Controller;

use App\Services\HttpService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

#[Route('/api', name: 'app_api_')]
class ApiController extends AbstractController
{
#[Route('/health-check', name: 'health_check')]
public function healthCheck(): Response
{
return new Response('', 204);
}

#[Route('/static', name: 'static')]
public function static(): JsonResponse
{
return $this->json([
'status' => true,
]);
}

#[Route('/http-request', name: 'http_request')]
public function httpRequest(HttpService $httpService): JsonResponse
{
return $this->json(json_decode($httpService->get('http://whoami/api')));
}
}

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

WORKDIR /var/www

RUN composer install --no-dev

ENV APP_RUNTIME="Runtime\Swoole\Runtime"

ENTRYPOINT ["php", "public/index.php"]

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

WORKDIR /var/www

RUN composer install --no-dev

ENV APP_RUNTIME="Runtime\Swoole\Runtime"

ENTRYPOINT ["php", "public/index.php"]

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

WORKDIR /var/www

RUN composer install --no-dev

ENV APP_RUNTIME="Runtime\RoadRunnerSymfonyNyholm\Runtime"

ENTRYPOINT ["rr", "serve"]

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

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

ENV APP_RUNTIME="Runtime\FrankenPhpSymfony\Runtime"
ENV APP_PUBLIC_PATH="/var/www/public"
ENV MAX_REQUESTS="500"
ENV REQUEST_MAX_EXECUTION_TIME="500"
ENV CADDY_SERVER_ADMIN_PORT="3823"
ENV CADDY_SERVER_LOG_LEVEL="WARN"
ENV CADDY_SERVER_LOGGER="json"
ENV CADDY_SERVER_SERVER_NAME="http://:9804"
ENV CADDY_SERVER_WORKER_COUNT="16"
ENV CADDY_SERVER_EXTRA_DIRECTIVES=""

ENTRYPOINT ["frankenphp","run", "-cCaddyfile"]

Docker Compose

version: "3.9"

services:
whoami:
container_name: "whoami"
image: "containous/whoami"
openswoole:
container_name: "openswoole"
image: "ghcr.io/thecaliskan/symfony-benchmark:openswoole"
ports:
- "9801:9801"
swoole:
container_name: "swoole"
image: "ghcr.io/thecaliskan/symfony-benchmark:swoole"
ports:
- "9802:9802"
roadrunner:
container_name: "roadrunner"
image: "ghcr.io/thecaliskan/symfony-benchmark:roadrunner"
ports:
- "9803:9803"
frankenphp:
container_name: "frankenphp"
image: "ghcr.io/thecaliskan/symfony-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/symfony-benchmark/master/docker-compose.yml

docker compose up -d

Benchmark

Now that your Symfony 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
Symfony: v7.0.2
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

The benchmark tests evaluating the performance of Symfony Framework reveal a distinct advantage of OpenSwoole and Swoole runtimes compared to others. This is particularly evident in “Health Check,” “Static,” and “Http Request” scenarios, where low latency values and high Request Per Second rates are prominent.

The superiority of OpenSwoole and Swoole in these aspects is a significant advantage for ensuring applications run quickly and efficiently. These runtimes effectively respond to user requests with low latency values and successfully handle high request loads with their high RPS rates.

Developers should focus on optimizing RoadRunner and FrankenPHP based on these benchmark results. Doing so will contribute to improving the performance of Symfony projects overall, positively impacting user experience and fostering a more efficient application development process.

When it comes to selecting the runtime environment for Symfony projects, what are the key factors that influence your decision? Which runtime do you prefer for your projects, and how do considerations such as performance, developer experience, or specific project requirements play a role in your choice? Please share your own preferences and the reasons you take into account when making these decisions.

Thank you for reading!

--

--