RoadRunner y Swoole en 2026: PHP como servidor persistente de alto rendimiento

PHP nació así: llega una petición, el servidor lanza un proceso PHP, ese proceso carga el autoloader, lee la configuración, arranca el framework y genera la respuesta. Cuando termina, todo muere. La próxima petición repite el ciclo desde cero.

Con PHP-FPM esto mejora un poco. En vez de lanzar un proceso nuevo cada vez, FPM mantiene un pool de workers que se reutilizan entre peticiones. Pero el estado interno sí se destruye: cada worker empieza limpio con cada request.

El coste de arranque en frameworks como Laravel no es trivial. Cargar el framework, los service providers, el container y la configuración puede suponer entre 20 y 80 ms en producción, según la app. OPcache ayuda mucho, pero hay un techo que no se puede romper si el proceso muere con cada petición.

El modelo persistente: el worker no muere

La idea de RoadRunner, Swoole y FrankenPHP es distinta: el worker PHP arranca una vez, carga el framework una vez y luego procesa miles de peticiones sin volver a inicializarse. La respuesta a la segunda petición llega sin pagar el coste de arranque.

Para una app Laravel con 50 ms de bootstrap, pasar al modelo persistente puede dejar ese coste en 2-5 ms en requests subsecuentes. No es magia, es que el trabajo pesado ya estaba hecho.

Pero el modelo persistente introduce un problema que no existe en el clásico: el estado entre peticiones. Si una clase guarda algo en una variable estática, ese valor sigue ahí cuando llega la siguiente request. Un bug difícil de reproducir en local y fácil de ignorar hasta que aparece en producción.

RoadRunner: Go gestiona el HTTP, PHP hace el trabajo

RoadRunner, desarrollado por Spiral Scout, separa las responsabilidades de forma clara. El servidor HTTP lo escribe en Go, que es muy eficiente gestionando conexiones concurrentes. Ese servidor pasa las peticiones al worker PHP mediante pipes y recoge la respuesta.

Para instalarlo necesitas el binario rr y un archivo de configuración .rr.yaml. Desde ahí defines los workers PHP, el número de procesos y las opciones del servidor.

En Laravel, la integración más directa es Laravel Octane con el driver de RoadRunner:

composer require laravel/octane
php artisan octane:install --server=roadrunner
php artisan octane:start --server=roadrunner

En Symfony, el bundle baldinof/road-runner-bundle hace el mismo trabajo de integración.

Laravel Octane: la abstracción oficial para servidores persistentes

Octane es el paquete de Laravel que te abstrae de los detalles de cada servidor persistente. Lo instalas, eliges el driver y Octane se encarga de resetear el estado del container entre peticiones: borra bindings, limpia el contexto de autenticación y reinicia lo que necesita reiniciarse.

Los tres drivers disponibles son RoadRunner, Swoole y FrankenPHP. En desarrollo, la opción --watch reinicia los workers automáticamente cuando cambias un archivo:

php artisan octane:start --server=roadrunner --watch

Para verificar que el servidor está levantado y cuántos workers están activos:

php artisan octane:status

Octane gestiona bastante bien el reset automático, pero no cubre todo. Las variables estáticas que definas en tus propias clases, los listeners de eventos que se registren más de una vez o cualquier caché en memoria que acumule datos entre requests son responsabilidad tuya.

Si te interesa profundizar en otras técnicas de rendimiento dentro de Laravel, aquí tienes un repaso completo: rendimiento en Laravel: técnicas que marcan la diferencia.

Swoole: PHP asíncrono sin proceso externo

Swoole toma un camino diferente. Es una extensión de PHP escrita en C++ que añade coroutines, canales y un servidor HTTP asíncrono directamente al intérprete. No necesitas un proceso Go ni ningún servidor externo: PHP mismo gestiona el servidor.

Un ejemplo de petición HTTP asíncrona con coroutines de Swoole:

Corun(function() {
    $client = new CoHttpClient('api.ejemplo.com', 443, true);
    $client->get('/datos');
    echo $client->body;
});

La contrapartida es que necesitas compilar e instalar la extensión en el servidor, lo que añade fricción en entornos donde no controlas el hosting. En Docker es manejable; en hosting compartido, prácticamente imposible.

Con Laravel Octane también puedes usar Swoole como driver, igual que RoadRunner. La elección entre uno y otro depende más de tu infraestructura que de diferencias de rendimiento apreciables en la mayoría de apps.

FrankenPHP: PHP integrado en un servidor moderno

FrankenPHP, creado por Kévin Dunglas (también creador del componente HttpClient de Symfony), lleva el planteamiento un paso más lejos. En vez de poner PHP detrás de un servidor, embebe PHP directamente dentro de Caddy, el servidor HTTP escrito en Go que gestiona TLS automático y HTTP/3.

El resultado: un solo proceso que sirve PHP, assets estáticos y gestiona los certificados SSL sin tocar Nginx ni Apache. Para entornos Dockerizados es especialmente limpio.

Como driver de Octane:

php artisan octane:start --server=frankenphp

En 2026 FrankenPHP 1.x ya es production-ready. La adopción en la comunidad Laravel y Symfony ha crecido bastante, sobre todo en proyectos nuevos que arrancan con Docker desde el principio.

Si quieres entender mejor cómo encaja todo esto con las comunicaciones en tiempo real, aquí tienes contexto útil: PHP en tiempo real: de WebSockets a servidores persistentes.

Gestión del estado entre peticiones: lo que puede salir mal

Este es el punto donde más gente tropieza al migrar a un servidor persistente. En el modelo clásico, el estado muere con el proceso y nunca es un problema. En el persistente, tienes que pensar en ello.

Octane cubre el reset del container de Laravel, pero no puede saber qué haces tú en tu código. Los sitios más habituales donde aparecen bugs de estado:

  • Variables con static $cache = [] en clases de servicio que acumulan datos entre requests.
  • Listeners de eventos registrados dentro de un request que se vuelven a registrar en el siguiente, duplicándose.
  • Singletons que guardan datos específicos de un usuario y se los sirven al siguiente.

La solución no es técnica sino de disciplina: evitar el estado mutable global y revisar cualquier uso de propiedades estáticas antes de desplegar en modo persistente.

Cuándo tiene sentido y cuándo no

El modelo persistente brilla en APIs de alto tráfico donde la latencia importa, especialmente si el bootstrap del framework es pesado. Si tienes una app Laravel con 50 ms de arranque y miles de requests por minuto, el ahorro es real y medible.

Para sitios con tráfico medio y un PHP-FPM bien configurado con OPcache y JIT activado, la ganancia de añadir RoadRunner o Swoole puede no justificar la complejidad extra. OPcache ya elimina la recompilación de bytecode, y JIT da un empujón adicional en código con mucho procesamiento.

En microservicios pequeños que se despliegan en contenedores y escalan horizontalmente, el modelo clásico con FPM sigue siendo perfectamente válido. El coste de arranque se amortiza repartiendo la carga entre más instancias, y te evitas gestionar el estado entre peticiones.

La elección tiene que partir de medir: levanta la app con ambas configuraciones, lanza un benchmark con herramientas como wrk o k6 y mira los números. Las decisiones de arquitectura sin datos son apuestas.

Imagen: Pexels / Brett Sayles

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP