PHP ha madurado, ha sobrevivido a modas y, sin embargo, los problemas de rendimiento que lo rodean siguen siendo sorprendentemente parecidos, servidores saturados, sesiones inconsistentes, cargas que se disparan con picos de tráfico y configuraciones de PHP-FPM que no acaban de funcionar.

Escalar PHP no se trata de magia ni de “poner más máquinas”. Se trata de entender cómo se reparten las peticiones y cuánta memoria consume realmente cada proceso.

Es por ello que en este artículo quiero describir cómo escalar y optimizar PHP desde el punto de vista técnico y sobre todo conceptual, para mantener tu aplicación PHP rápida, estable y preparada para crecer.

1. Dónde empieza todo: las sesiones

En entornos monolíticos, las sesiones en disco funcionan sin mucho drama, pero basta añadir un segundo servidor y un balanceador para que todo empiece a complicarse.

Si las sesiones viven en el sistema de archivos local (/var/lib/php/sessions), el usuario que entre por un nodo distinto perderá su estado, con consecuencias como login olvidado, carrito vacío, token desaparecido, etc. 

Es el clásico síntoma de una arquitectura que crece sin un almacén de estados compartido.

Redis como solución

Redis es rápido, persistente en memoria y compatible con PHP desde hace años. La diferencia entre usarlo “bien” o “a medias” marca si tu escalado horizontal será una pesadilla o una anécdota.

La configuración básica luce así:

Pero en un entorno multi-servidor con la idea de escalar nuestra aplicación PHP esto no basta. Si cada instancia apunta a su propio Redis local (127.0.0.1), sigues teniendo el mismo problema: sesiones aisladas. Lo correcto es un Redis accesible para todos los app servers, idealmente en una red interna privada o VPC, protegido por TLS y ACLs.

Además de la conexión segura, hay dos detalles que mejoran la estabilidad:

  • Usar el serializer igbinary, que reduce el tamaño y la CPU en serialización.
  • Activar el locking de sesión para evitar problemas en escrituras concurrentes que podrían sobreescribirse entre servidores.

Con esta configuración, cualquier instancia PHP puede leer y escribir sesiones de cualquier usuario sin necesidad de sticky sessions. El balanceador deja de preocuparse por mantener afinidad con el resto de servidores y puede distribuir tráfico libremente, logrando un escalado limpio y simétrico.

2. WebSockets y el reto de las 100.000 conexiones

Si las sesiones fueron el primer enemigo de la escalabilidad web tradicional, las conexiones persistentes lo son de la moderna.

WebSockets no son simples peticiones HTTP, ya que mantienen un canal activo, estado y memoria durante minutos u horas. Y eso cambia las reglas.

Cuando las soluciones simples se rompen

Un único servidor Node o PHP puede manejar 10.000 conexiones sin esfuerzo, pero a 80.000 el sistema empieza a sufrir. A menudo el problema no es CPU, sino distribución, ya que los balanceadores que usan la IP de origen para asignar sesiones se vuelven ineficientes cuando miles de usuarios comparten una misma IP detrás de un proxy corporativo.

Primera parada: HAProxy

El veterano HAProxy sigue siendo el líder en conexiones persistentes. Con un balance source básico puedes empezar, pero para escenarios grandes conviene avanzar hacia persistencia por cookie o token, integrando la sesión del usuario en el handshake.

Este tipo de persistencia evita el “efecto embudo” de las NATs, reparte las conexiones de forma uniforme y reduce la probabilidad de que una caída afecte a miles de usuarios a la vez.

Otras alternativas

Nginx, con su módulo stream, funciona bien y ofrece mejor integración con tráfico HTTP. Pero a gran escala consume más memoria (~40% más que HAProxy para la misma carga).

Un balanceador programado a medida con hashing consistente por user_id, da la máxima flexibilidad y telemetría, pero a costa de complejidad y mantenimiento continuo.

La propuesta es empezar con HAProxy y migra solo si tus requisitos lo exigen.

3. PHP-FPM: dimensionar por memoria, no por núcleos

El mayor error en producción es medir FPM por CPU. PHP-FPM es multi-proceso, no multi-thread, cada worker maneja una petición a la vez. Si abres demasiados, agotarás memoria y el sistema empezará a intercambiar.

El cálculo correcto es simple pero potente:

pm.max_children = floor(memoria_total_para_FPM / RSS_medio_por_worker)

Supongamos un servidor con 8 GB de RAM, de los cuales 6 GB puedes dedicar a PHP (dejando 2 para Nginx, Redis y el sistema). Si cada proceso PHP usa unos 120MB en promedio, el límite razonable será:

floor(6000 / 120) = 50 → ajusta a 44 para dejar margen

Un ejemplo de configuración sólida:

Y en Nginx, hay que alinear los timeouts para evitar falsos 502/504:

Finalmente, OPcache es el acelerador oculto que más impacto tiene:

Un OPcache saturado equivale a recompilar código en cada petición: la peor inversión posible de CPU.

4. Quitando peso al request: colas, cachés y querys eficientes

Cuando una aplicación PHP se siente lenta, casi nunca es por el PHP en sí, sino por querer hacer demasiadas tareas en el camino de la respuesta.

La regla de oro es que responde rápido y delega el trabajo costoso y esto se logra con colas de tareas y cachés inteligentes.

Tareas en segundo plano con PHP

Enviar correos, procesar imágenes o generar PDFs no debería frenar al usuario. Con Redis como cola básica puedes implementar un sistema simple y robusto:

En frameworks modernos también tenemos Symfony Messenger o Laravel Queues que hacen este trabajo de forma nativa, incluso distribuyendo workers en múltiples máquinas.

Cachear antes de consultar

Los datos que cambian poco no deben salir de la base de datos en cada request. Redis o Memcached reducen tiempos de respuesta y carga de CPU:

En bases de datos, aplicar paginación real y batch processing mantiene bajo el consumo de memoria y evita tiempos de respuesta sin sentido:

Si los datos son grandes, procesa en lotes y fuerza la Garbage Collection (GC):

5. Mantener el servidor vivo: rate limiting y control de abusos

Un tráfico descontrolado puede matar incluso al mejor código. Bots, scrapers o usuarios intensos que deben encontrar límites razonables.

Un patrón común es usar Redis como contador por IP:

Este enfoque, simple pero efectivo, previene picos incontrolados y mantiene la plataforma estable sin penalizar a usuarios normales.

6. Observabilidad, lo que no mides, no existe

La mitad de los problemas de rendimiento se resuelven mirando. PHP-FPM ofrece un endpoint /status que muestra el número de procesos activos, en cola y máximos alcanzados. Si la cola se mantiene por encima de pm.max_children/4 durante más de un minuto, hay un cuello de botella claro, o falta memoria o alguna ruta del código bloquea demasiado tiempo.

Los slow logs son otra joya olvidada, con request_slowlog_timeout = 2s, cada petición lenta genera un stack trace en tiempo real que te dice qué función o query se está llevando el tiempo.

Complementa esto con métricas de Redis (latencia, keys expiradas, uso de memoria) y con un APM o profiler (Blackfire, Tideways, New Relic) para ver el panorama completo.

7. Validar con datos para pruebas de carga realistas

Nada sustituye una prueba de carga. Saber cuántas peticiones por segundo aguanta tu setup es la única forma de anticiparte a los fallos.

Con k6, una herramienta escrita en JavaScript, puedes definir escenarios con precisión:

Puedes dividir la ejecución entre varias máquinas con --execution-segment y exportar métricas. También existen Locust (Python, con UI web) y JMeter (el clásico, con ecosistema enorme). Lo importante no es la herramienta, sino el propósito, medir, ajustar y volver a medir.

8. Saber cuándo cambiar de modelo

Hay límites naturales que ni la mejor configuración de FPM puede evitar. Tareas largas, generación de informes, procesamiento de vídeo, machine learning, etc, deben ir a colas y workers dedicados.

Para streaming o WebSockets intensivos, PHP-FPM no es el modelo ideal, en esos casos, tecnologías como Node.js, Go o runtimes PHP persistentes (Swoole, RoadRunner) pueden ofrecer ventajas reales. No se trata de abandonar PHP, sino de poner cada pieza donde mejor encaje.

Conclusión

Escalar PHP no es cuestión de suerte ni de hardware caro. Es cuestión de diseño. Redis resuelve el estado y libera el balanceador. HAProxy maneja decenas de miles de conexiones sin despeinarse. PHP-FPM, cuando se dimensiona por memoria y no por CPU, se vuelve predecible. Y la combinación de colas, cachés, SQL optimizado y observabilidad cierra el círculo.

La meta no es presumir de benchmarks, sino llegar al punto en el que los p95 sean, literalmente, aburridos.

Cuando tu aplicación sigue respondiendo igual de rápido a las 2:47AM, con 100.000 usuarios conectados y sin un solo 502, sabes que lo has hecho bien. Porque la estabilidad, en el fondo, también es una forma de elegancia técnica.

Referencias:

· Reducing Server Load with PHP: Optimizing Algorithms for High-Volume Requests

· Scaling PHP Sessions with Redis Across Multiple Servers: A Detailed Guide

· Load Balancing 100,000 WebSocket Connections: HAProxy vs. Nginx vs. Custom

· Meet PHP-FPM — How requests flow, pools, and settings that keep your app responsive

Compartir es construir