Siempre me resulta interesante comparar marcos de trabajo con fines similares, para mí es parte de un análisis previo para elegir la tecnología más adecuada en un nuevo proyecto y no provocar una deuda técnica.
Desde que nació Node JS como un entorno de ejecución para Javascript, es probablemente el rey en la generación de proyectos nuevos de lado de backend con funcionamiento asíncrono, por ejemplo manejadores de eventos y websockets. La asincronía es algo que PHP no tiene nativamente y por eso se creó el proyecto de Swoole.
Open Swoole es un marco de red de alto rendimiento basado en un modelo de programación de corrutinas de E/S sin bloqueo, asíncrono y basado en eventos para PHP. Diseñado para construir sistemas de concurrencia a gran escala. Está escrito en C/C++ e instalado como una extensión de PHP.
Con el rendimiento que ofrece Swoole se pueden escribir aplicaciones PHP escalables para servidores web, APIs, servidores de juegos, sistemas de chat, plataformas CMS, microservicios y servicios web en tiempo real, etc. Características que vienen muy bien a ciertos proyectos a gran escala.
Este es un ejercicio de rendimiento y pruebas de estrés para servidores montados en Docker:
Lenguaje | Servidor |
---|---|
PHP | PHP server native |
PHP | Swoole |
Javascript | Node Js HTTP module |
Las pruebas constan de un algoritmo simple llamado Serie de Fibonacci, que básicamente trata de una secuencia de números naturales infinita, a partir del 0 y el 1 y se van sumando en pares, de manera que cada número es igual a la suma de sus dos anteriores:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55…
El algoritmo a desarrollar es el resultado de las ecuaciones:
f0 = 0
f1 = 1
fn = f n − 1 + f n − 2
Este tipo de ejercicios suelen utilizarse para entender la lógica de la recursividad.
Al final los resultados de la serie se arrojan en formato JSON, simulando una API. El código de los tres servidores se encuentra aquí y lo explicaré detalladamente.
PHP Fibonacci
Básicamente con las fórmulas anteriores escribí el siguiente código, donde si el número es < 1 (0, 1) devolvemos el número, caso contrario, atendemos la formula: fn = f n − 1 + f n − 2.
function fibonacci($n)
{
if ($n <= 1) {
return $n;
}
return fibonacci($n - 1) + fibonacci($n - 2);
}
Ejecutamos un ciclo for que pushea el resultado de llamar a fibonacci 20 veces:
$array = [];
for ($i = 0; $i < 20; $i++) {
$array[] = fibonacci($i);
}
Al final se imprime un JSON:
header('Content-Type: application/json');
echo json_encode([
'status' => 'success',
'results' => $array
]);
PHP Swoole Fibonacci
Al ser el mismo lenguaje la implementación es similar, en el caso de la función fibonacci seguirá siendo la misma, la diferencia radica en utilizar el servidor que Swoole ofrece:
<?php
use Swoole\Http\Server;
use Swoole\Http\Request;
use Swoole\Http\Response;
$hostname = "0.0.0.0";
$port = 3001;
function fibonacci($n)
{
if ($n <= 1) {
return $n;
}
return fibonacci($n - 1) + fibonacci($n - 2);
}
$http = new Server($hostname, $port);
$http->on("request", function (Request $request, Response $response) {
$array = [];
for ($i = 0; $i < 20; $i++) {
$array[] = fibonacci($i);
}
$response->header("Content-Type", "application/json");
$response->end(json_encode([
'status' => 'success',
'results' => $array
]));
});
$http->start();
Javascript Fibonacci
En este punto seguimos con un copy & paste con la diferencia de que es un lenguaje distinto:
const http = require("http");
const hostname = "0.0.0.0";
const port = 3000;
const fibonacci = (n) => {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
};
const server = http.createServer((req, res) => {
const array = [];
for (let i = 0; i < 20; i++) {
array.push(fibonacci(i));
}
res.statusCode = 200;
res.setHeader("Content-Type", "application/json");
res.end(
JSON.stringify({
status: "success",
results: array,
})
);
});
server.listen(port, hostname);
Pruebas de estrés
Para la ejecución de los benchmark utilice una herramienta de benchmarking http llamada wrk que arroja resultados impresionantes.
Como mencioné anteriormente el repositorio de código de estás pruebas esta aquí.
Si ya descargaste el repositorio tendrías que levantar los servicios ejecutando:
docker-compose up
- Si hiciste una modificación y no ves cambios, intenta ejecutar docker-compose up –build, debido a que levantamos los servicios con docker-compose y no hay un volumen asociado, los archivos de los servidores se copian en tiempo de buildeo a los contendores.
Levantados los servicios tendremos tres servidores:
http://localhost:3000 (Node ks)
http://localhost:3001 (PHP Swoole)
http://localhost:3002 (PHP native)
Podríamos ejecutar las pruebas para cada servidor:
wrk -t2 -c3 -d5s http://0.0.0.0:3000/
wrk -t2 -c3 -d5s http://0.0.0.0:3001/
wrk -t2 -c3 -d5s http://0.0.0.0:3002/
Si no tienes instalado wrk, puedes utilizar docker, por ejemplo hacía google.com:
docker run --rm williamyeh/wrk -t2 -c3 -d5s https://google.com
Daré un poco de contexto para antes de continuar con las pruebas más profundas, wrk recibe los siguientes parámetros:
- -t2: Esto genera dos subprocesos independientes.
- -c3: Cada subproceso abrirá 3 conexiones. Cada conexión simula por ejemplo, un navegador visitando la URL indicada.
- -d5s: La duración de la prueba.
Una prueba con dos subprocesos y 3 conexiones cada uno, durante 5 segundos, no es para nada estresante, para una prueba más profunda use una máquina con las siguientes características:
Ubuntu 20.04
Intel i5 6 núcleos, 12 hilos
32GB RAM
Los parámetros que realice son 4 hilos, 500 conexiones cada uno, durante 30 segundos, los resultados son los siguientes:
NodeJs
docker run --rm williamyeh/wrk -c500 -d30s -t4 http://0.0.0.0:3000
Running 30s test @ http://0.0.0.0:3000
4 threads and 500 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 58.90ms 3.79ms 100.24ms 89.52%
Req/Sec 2.13k 218.13 2.53k 71.67%
254405 requests in 30.05s, 60.90MB read
Requests/sec: 8466.78
Transfer/sec: 2.03MB
PHP Swoole
docker run --rm williamyeh/wrk -c500 -d30s -t4 http://0.0.0.0:3001
Running 30s test @ http://0.0.0.0:3001
4 threads and 500 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 39.17ms 5.80ms 135.92ms 85.55%
Req/Sec 3.20k 118.85 3.45k 76.34%
382665 requests in 30.06s, 93.42MB read
Requests/sec: 12728.04
Transfer/sec: 3.11MB
PHP native
docker run --rm williamyeh/wrk -c500 -d30s -t4 http://0.0.0.0:3002
Running 30s test @ http://0.0.0.0:3002
4 threads and 500 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 137.51ms 273.67ms 2.00s 90.44%
Req/Sec 264.39 144.28 590.00 63.48%
29306 requests in 30.06s, 6.93MB read
Requests/sec: 975.05
Transfer/sec: 236.15KB
Para que nos demos una idea apuntar a https://google.com nos arroja lo siguiente:
docker run --rm williamyeh/wrk -c500 -d30s -t4 https://google.com
Running 30s test @ https://google.com
4 threads and 500 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 184.69ms 32.15ms 941.58ms 97.84%
Req/Sec 675.31 121.46 0.97k 70.84%
79723 requests in 30.06s, 53.45MB read
Requests/sec: 2651.84
Transfer/sec: 1.78MB
Los resultados son sencillos de analizar:
Running 30s test @ https://google.com
4 threads and 500 connections
Thread Stats Avg Stdev Max +/- Stdev
- Avg: Promedio.
- Stdev: Desviación estándar.
- Max: Máximo registro.
- +/- Stdev: Es el porcentaje de los registros que están igual o por debajo de Stdev.
Esto aplica para los registros de Latency y Req/Sec, los resultados los podriamos analizar:
Servidor | Avg | Stdev | Max | +/- Stdev |
---|---|---|---|---|
PHP Native | Latency: 137.51ms Req/Sec: 264.39 | Latency: 273.67ms Req/Sec: 144.28 | Latency: 2.00s Req/Sec: 590.00 | Latency: 90.44% Req/Sec: 63.48% |
NodeJs | Latency: 58.90ms Req/Sec: 2.13k | Latency: 3.79ms Req/Sec: 218.13 | Latency: 100.24ms Req/Sec: 2.53k | Latency: 89.52% Req/Sec: 71.67% |
PHP Swoole | Latency: 39.17ms Req/Sec: 3.20k | Latency: 5.80ms Req/Sec: 118.85 | Latency: 135.92ms Req/Sec: 3.45k | Latency: 85.55% Req/Sec: 76.34% |
Realmente como suponía PHP nativo quedaría a deber 2s tardó la maxima conexión en responder, la competencia está entre Node Js y Swoole que es muy reñido, pero Swoole gana al otorgar una latencia más baja, el número de peticiones por segundo se incrementa, la desviación estándar nos indica que no son muy alejados los valores del promedio.
Suponía que Swoole tendría un rendimiento bueno y no me decepcionó. Si te llega a la cabeza probar con un servidor encima como Nginx podría ser una buena idea, te invito a intentarlo. ¡Saludos!