Octane acelera Laravel rompiendo una suposición
Cada petición en una aplicación Laravel normal empieza de cero. Octane mantiene la aplicación en memoria entre peticiones, y de ahí viene la velocidad y también los fallos.
Lo que hace indulgente a PHP es que olvida. Llega una petición, arranca el framework, ocurre el trabajo, el proceso sale, y todas las variables creadas por el camino desaparecen. Una fuga de memoria dura milisegundos. Una propiedad estática contaminada con los datos de un usuario desaparece antes de que llegue el siguiente.
Octane quita eso. La aplicación arranca una vez y se queda en memoria, atendiendo petición tras petición en el mismo proceso. Saltarse el arranque es de donde viene la velocidad, y el olvido estaba sosteniendo cosas que la mayoría de las bases de código nunca ha tenido que pensar.
Qué deja de ser cierto
Las propiedades estáticas persisten. Una caché estática que era por petición la comparten ahora todas las peticiones que atiende el worker. Si está indexada por usuario, es una fuga de datos.
Los singletons sobreviven a la petición que los creó. Un servicio resuelto una vez y registrado como singleton se queda con lo que capturase en la primera resolución, incluidos, en el peor caso, una petición o un usuario autenticado.
Las globales persisten. Todo lo escrito en una superglobal o en una variable global sobrevive.
Las fugas se acumulan. Un array que crecía un poco en cada petición era gratis. Ahora la memoria del worker sube hasta que algo lo reinicia.
El patrón que muerde de verdad
Rara vez es un array estático. Es un servicio que tomó una dependencia que debería haber resuelto más tarde:
// AppServiceProvider
$this->app->singleton(ReportBuilder::class, function ($app) {
return new ReportBuilder($app->make(Request::class));
});Registrado como singleton, esto captura la primera petición que atiende el worker y se la queda. Cada petición posterior recibe un builder que sostiene la entrada de otra persona.
Con PHP normal esto es inofensivo: el proceso muere, el singleton muere con él, y nadie llega a notar el fallo latente. Con Octane es una fuga de datos entre usuarios que aparece de forma intermitente y que es casi imposible de reproducir a partir de un reporte.
El arreglo es resolver el estado propio de la petición cuando hace falta, en lugar de cuando se construye el servicio:
$this->app->singleton(ReportBuilder::class, fn () => new ReportBuilder());
// y dentro del método que lo necesita
public function build(Request $request): Report { ... }Lo mismo vale para cualquier cosa que capture el usuario autenticado, el inquilino actual o el idioma en el momento de construirse. En una plataforma multiinquilino esto pasa de vergüenza a incidente serio, porque la frontera entre inquilinos es exactamente lo que se está cruzando.
Encontrarlos antes que producción
Lea primero los service providers. Cada enlace singleton, y cada bind cuyo
closure resuelva algo con forma de petición, es candidato.
Después busque propiedades estáticas en las que se escribe y no solo se lee:
grep -rn "protected static \|private static \|public static " app/ | grep -v "function\|const"La mayoría serán legítimas. Las que sostienen datos en lugar de configuración son la lista que hay que ir resolviendo.
Laravel ofrece ganchos para reiniciar estado entre peticiones, y los paquetes registran los suyos. Cubren bien los servicios del propio framework y no saben nada de los suyos.
La parte que no va de corrección
Incluso con el estado resuelto, un proceso de larga ejecución tiene otra forma operativa.
La memoria hay que vigilarla durante horas, no durante minutos: una fuga de cien kilobytes por petición es invisible en una prueba y mortal de madrugada. Los workers necesitan un máximo de peticiones para reciclarse antes de degradarse. Y un despliegue tiene que reiniciarlos, que es la misma lección que con los workers de cola: un proceso de vida larga sostiene el código con el que arrancó, así que el código nuevo no le llega hasta que algo le hace salir.
¿Merece la pena?
A veces, y menos a menudo de lo que sugieren los benchmarks.
El arranque es lo que Octane quita, y una petición cuyo tiempo se va en una consulta lenta o en una llamada a un tercero no gana absolutamente nada. Perfile primero un endpoint real. Si el arranque del framework es una parte significativa de su tiempo de respuesta, la ganancia es real; si lo es la base de datos, arregle la base de datos.
Y si la aplicación tiene singletons que capturan estado de petición, arréglelos decida lo que decida sobre Octane. Son fallos hoy: solo que PHP lleva tiempo tapándolos en silencio.
En una plataforma multiinquilino, un inquilino capturado cruza la única frontera sobre la que se vende el producto. Y un proceso que retiene el código con el que arrancó es el mismo fallo que un worker de colas con la versión de la semana pasada. Por eso un despliegue tiene que reiniciar los dos.
Si el arranque le está costando algo de verdad es una medición, y es lo primero que hacemos.
