Rendimiento de PHP
Un PHP rápido se ajusta, no se compra.
Nuestro equipo trabaja con PHP desde hace veinticinco años y sabe dónde pierde el tiempo una petición. Lo encontramos en su servidor y lo recuperamos, capa por capa.
Anatomía de una petición
Una página es una cadena de esperas: encontrar el servidor, abrir la conexión, hacer cola para PHP, compilar, ejecutar, consultar la base de datos, enviar. Cambie de estado para ver qué eslabones acortamos.
-
Consulta DNS La responde el resolvedor del visitante. Igual en todos los estados: esta parte es la red del visitante. Igual en todos los estados: esta parte es la red del visitante.
-
Conexión y TLS Una configuración TLS antigua, una conexión nueva para cada archivo. TLS 1.3 y una única conexión HTTP/2 reutilizada para cada archivo. TLS 1.3 y una única conexión HTTP/2 reutilizada para cada archivo.
-
Esperando un worker de PHP Todos los workers están ocupados, así que la petición espera en la cola. Un pool dimensionado según la memoria: hay un worker libre listo. No hace falta ningún worker de PHP.
-
Compilando los scripts PHP lee y compila cada archivo en cada petición. OPcache sirve los scripts compilados desde memoria. Nada que compilar.
-
Buscando archivos y clases El autoloader recorre directorios para cada clase. Un autoloader con classmap y una caché de realpath ya caliente. Nada que cargar.
-
Ejecutando su código El trabajo propio de la aplicación. Casi igual: el JIT rara vez cambia esto en un sitio típico. Su código no se ejecuta en esta visita.
-
Consultas a la base de datos Faltan índices, y la misma consulta en cada página. Índices añadidos, respuestas repetidas guardadas en Redis. No se envía ninguna consulta.
-
Enviando la página Se envía sin comprimir. Comprimida antes de salir del servidor. La página terminada, directamente desde la caché de páginas.
Las barras de arriba son un dibujo. Esta cifra es una medición.
El resto del tiempo es la distancia entre nuestros servidores en Alemania y tú. Esa parte depende de dónde estés, así que mídela desde tu propia conexión.
Mídelo desde tu conexiónLos ajustes que eliminan la mayor parte de la espera
Compilar, buscar archivos y cargar clases ocurre antes de que su código ejecute una sola línea. Con estos ajustes ocurre una vez, no en cada petición.
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0
opcache.preload=/srv/app/preload.php
opcache.preload_user=www-data
opcache.jit=tracing
opcache.jit_buffer_size=64M
realpath_cache_size=4096K
realpath_cache_ttl=600
$ composer install --no-dev --classmap-authoritative
-
OPcache
PHP compila cada archivo antes de ejecutarlo. OPcache guarda el resultado compilado en memoria compartida, y la siguiente petición se ahorra ese trabajo. Dimensionamos su memoria y su número de archivos según el sitio y leemos su tasa de aciertos para confirmar que no se descarta nada.
En producción PHP deja de comprobar si cada archivo ha cambiado; cada despliegue vacía la caché por sí mismo.
-
Precarga
En un framework, la precarga carga sus archivos principales una sola vez al arrancar PHP y los mantiene listos para cada petición. Cambiar esos archivos exige un reinicio, así que el reinicio forma parte del despliegue.
-
JIT
El JIT convierte el código PHP más usado en código máquina. Ayuda en el trabajo que mantiene ocupado al procesador: procesamiento de imágenes, cálculos, bucles largos. Un sitio típico pasa su tiempo esperando a la base de datos y a la red, y ahí el JIT cambia poco. Lo activamos donde una medición muestra una diferencia, no en todas partes.
-
Caché de realpath
Cada include pregunta al disco dónde está realmente un archivo. Una caché de realpath más grande guarda esas respuestas en memoria, algo que cuenta en frameworks que cargan cientos de archivos por petición.
-
Autoloader de Composer
Un classmap autoritativo encuentra cada clase en una sola consulta en lugar de recorrer directorios, y los paquetes de desarrollo se quedan fuera del servidor.
El dial de ajuste: PHP-FPM
PHP-FPM mantiene un pool de workers de PHP. Si son pocos, las peticiones hacen cola; si son demasiados, el servidor se queda sin memoria y se ralentiza para todos. El pool se fija según lo que el servidor tiene de verdad.
pm = static
pm.max_children = 64
Un número fijo de workers, siempre activos
Para un servidor dedicado a un sitio con mucho tráfico. No se pierde tiempo arrancando procesos y la memoria que usa se conoce de antemano.
pm = dynamic
pm.max_children = 64
pm.start_servers = 8
pm.min_spare_servers = 4
pm.max_spare_servers = 12
Algunos workers listos, más bajo carga
Mantiene unos pocos workers en espera y añade más cuando sube el tráfico, hasta el límite. La elección habitual para un sitio con picos diarios.
pm = ondemand
pm.max_children = 64
pm.process_idle_timeout = 10s
Workers solo cuando llega una petición
Para muchos sitios pequeños en un servidor: la memoria va a los sitios que se visitan, y la primera petición tras un rato tranquilo paga un breve arranque.
Cómo fijamos pm.max_children
pm.max_children = memoria para PHP ÷ memoria por worker
$ ps --no-headers -o rss -C php-fpm8.3 | awk '{s+=$1} END {print s/NR/1024 " MB"}'
Un ejemplo de cálculo, no una recomendación: las cifras reales salen de su servidor, y dejamos margen para los picos.
- pm.max_requests
- Renueva un worker tras un número fijo de peticiones, para que una fuga lenta de memoria nunca se convierta en una caída.
- request_slowlog_timeout
- Una petición que tarda más escribe su traza de pila en el registro de lentitud, que señala la función que hay que revisar.
- request_terminate_timeout
- Detiene una petición descontrolada antes de que retenga un worker para siempre.
- pm.status_path
- Muestra la cola y cuántas veces el pool llegó a su límite: lo primero que miramos cuando un sitio se ralentiza en horas punta.
Dos caminos del servidor web a PHP
Trabajamos con los dos y elegimos según lo que necesita el sitio. En ambos casos, la petición más rápida es la que PHP nunca tiene que responder.
LiteSpeed + LSAPI
- Visitante
- LiteSpeed
- LSCachecaché de páginas
- LSAPI
- lsphp
LiteSpeed se comunica con PHP mediante su propio protocolo LSAPI y guarda páginas completas en LSCache, que WordPress y otras aplicaciones pueden purgar justo cuando cambia el contenido.
Nginx + PHP-FPM
- Visitante
- Nginx
- fastcgi_cachecaché de páginas
- FastCGI
- PHP-FPM
Nginx sirve él mismo los archivos estáticos, pasa PHP a un pool de PHP-FPM por un socket local y puede guardar páginas terminadas en su caché FastCGI para los visitantes sin sesión iniciada.
-
Redis para objetos y sesiones
Las respuestas que una página pide una y otra vez a la base de datos se quedan en memoria, y las sesiones dejan de tocar el disco.
-
Una caché de página completa
Un visitante sin sesión iniciada recibe una página terminada sin que se consulte a PHP ni a la base de datos. Carritos, cuentas y pagos quedan fuera de ella.
-
HTTP/2 y compresión
Una sola conexión cifrada transporta todos los archivos de la página, el texto se comprime antes de salir y los archivos estáticos llevan cachés largas con nombres versionados.
-
Tiempo en la base de datos
El registro de consultas lentas y EXPLAIN muestran qué consulta espera y por qué; normalmente un índice o una consulta repetida explica casi todo.
Cómo optimizamos bases de datos
Primero medimos, luego cambiamos una sola cosa
Cada cambio se hace frente a una medición previa, para poder mostrarle lo que consiguió. Estas son las mediciones.
opcache_get_status()Tasa de aciertos, memoria libre y si se están expulsando scripts.pm.status_pathLa cola de espera y cuántas veces el pool llegó a su límite.slowlogLa traza de pila de cada petición que tardó demasiado.slow_query_log + EXPLAINQué consultas esperan, cuántas filas leen y qué índice falta.redis-cli INFO statsAciertos frente a fallos de caché, y si se están expulsando claves.curl -w %{time_starttransfer}El tiempo hasta el primer byte, medido igual antes y después.
¿Quiere la otra mitad de la cifra, la distancia entre nuestros servidores y usted? Hacer la prueba de velocidad

Díganos qué página va lenta
Envíenos la dirección y dónde se aloja hoy. Primero revisamos el servidor, después le decimos qué cambiaríamos y qué debería conseguir, y presupuestamos el trabajo tras esa conversación.