...

Fragmentación de memoria en el alojamiento web: obstáculo para el rendimiento de PHP y MySQL

Fragmentación de la memoria En el alojamiento web, PHP-FPM y MySQL se ralentizan, aunque aparentemente haya suficiente RAM disponible, porque la memoria se divide en muchos bloques pequeños y las asignaciones más grandes fallan. Muestro de forma práctica cómo la fragmentación encarece las consultas, activa el intercambio y por qué el ajuste específico de PHP y MySQL mejora visiblemente los tiempos de carga, la fiabilidad y la escalabilidad.

Puntos centrales

  • PHP-FPM Reciclar: reiniciar los procesos periódicamente mediante pm.max_requests.
  • Tampón Dosificar: mantener conservador el búfer por conexión de MySQL
  • Intercambiar Evitar: reducir la swappiness, tener en cuenta NUMA.
  • tablas Mantener: comprobar Data_free, optimizar de forma específica
  • Monitoreo Aprovechar: detectar las tendencias a tiempo y actuar

¿Qué significa la fragmentación de memoria en el día a día del alojamiento web?

En el alojamiento web Fragmentación en procesos de larga duración que solicitan y liberan memoria constantemente, lo que crea huecos en el espacio de direcciones. Aunque la suma de la RAM libre parece grande, faltan bloques contiguos para asignaciones más grandes, lo que ralentiza los intentos de asignación. Lo veo en los trabajadores PHP-FPM y en mysqld, que parecen cada vez más „hinchados“ después de horas. El efecto encarece mínimamente cada solicitud y aumenta notablemente los tiempos de respuesta bajo carga. Esto hace que los picos, como las promociones de ventas o las copias de seguridad, se conviertan en un freno, aunque la CPU y la red sigan funcionando con normalidad.

Por qué PHP-FPM genera fragmentación

Cada trabajador PHP‑FPM carga código, complementos y datos en su propio Espacio de direcciones, atiende las solicitudes más diversas y deja huecos dispersos al liberar recursos. Con el tiempo, los procesos crecen y liberan memoria interna, pero no necesariamente al sistema operativo, lo que aumenta la fragmentación. Los diferentes scripts, trabajos de importación y procesamiento de imágenes refuerzan esta mezcla y dan lugar a patrones de asignación cambiantes. Observo esto como un aumento gradual de la RAM, aunque la carga y el tráfico parecen constantes. Sin reciclaje, esta fragmentación interna ralentiza la asignación y dificulta la planificación cuando hay un gran número de visitantes.

Consecuencias notables para los tiempos de carga y la fiabilidad

Los procesos fragmentados generan más Sobrecarga en la gestión de la memoria, lo que se traduce en backends administrativos más lentos y procesos de pago más lentos. Las tiendas de WordPress o las grandes instancias de CMS, en particular, reaccionan con lentitud cuando muchas solicitudes simultáneas se encuentran con trabajadores fragmentados. Esto da lugar a tiempos de espera, errores 502/504 y un aumento de los reintentos en NGINX o Apache. Interpreto estas situaciones en métricas como picos de tiempo de respuesta, aumento de la línea base de RAM y crecimiento repentino del uso de swap. Ignorar esto supone desperdiciar rendimiento, empeorar la experiencia del usuario y aumentar la tasa de abandono en embudos críticos.

Configurar PHP-FPM correctamente: límites, grupos, reciclaje

Apuesto por objetivos realistas. Límites, piscinas separadas y reciclaje sistemático para reducir la fragmentación. Termino pm.max_requests de manera que los trabajadores se reinicien periódicamente sin molestar a los visitantes actuales. Para perfiles de tráfico con picos de carga, pm = dynamic suele ser más adecuado, mientras que pm = ondemand ahorra RAM en sitios tranquilos. Mantengo el memory_limit por sitio deliberadamente moderado y lo ajusto teniendo en cuenta los scripts reales; el tema ofrece una introducción. Límite de memoria PHP. Además, separo los proyectos con mucha carga en grupos propios, para que un devorador de memoria no afecte a todos los sitios.

OPcache, precarga y PHP-Allocator a simple vista

Para reducir la fragmentación en el proceso PHP, apuesto por un dimensionamiento limpio. OPcache. Un opcache.memory_consumption generoso, pero no excesivo, y suficientes cadenas internas reducen las asignaciones repetidas por solicitud. Observo la tasa de aciertos, el desperdicio y la capacidad restante; si el desperdicio aumenta con el tiempo, es mejor planificar una recarga que dejar que los trabajadores crezcan sin control. La precarga puede mantener el código caliente estable en la memoria y, por lo tanto, suavizar los patrones de asignación, siempre que la base del código esté preparada adecuadamente. Además, presto atención a la Elección del asignador: Dependiendo de la distribución, PHP‑FPM y las extensiones funcionan con diferentes implementaciones de Malloc. Los asignadores alternativos, como jemalloc, reducen notablemente la fragmentación en algunas configuraciones. Sin embargo, solo implemento estos cambios después de haberlos probado, ya que la depuración, el perfilado DTrace/eBPF y los volcados de memoria reaccionan de forma diferente según el asignador.

Para tareas que requieren mucha memoria, como el procesamiento de imágenes o las exportaciones, prefiero utilizar grupos separados con límites más estrictos. De este modo, el grupo principal no crece de forma descontrolada y la fragmentación permanece aislada. Además, limito las extensiones que consumen mucha memoria (por ejemplo, mediante variables de entorno) y utilizo la contrapresión: las solicitudes que requieren grandes búferes se reducen o se transfieren a colas asíncronas, en lugar de sobrecargar a todos los trabajadores al mismo tiempo.

Comprender la memoria de MySQL: búferes, conexiones, tablas

En MySQL, distingo entre global Tampón como el búfer InnoDB, el búfer por conexión y las estructuras temporales, que pueden crecer con cada operación. Los valores demasiado altos provocan un aumento exponencial de las necesidades de RAM y una mayor fragmentación a nivel del sistema operativo cuando la carga de conexiones es elevada. Además, las tablas se fragmentan debido a las actualizaciones/eliminaciones y dejan partes de datos libres, lo que dificulta el aprovechamiento del buffer pool. Por lo tanto, compruebo regularmente el tamaño, los índices de aciertos y el número de tablas temporales en disco. La siguiente descripción general me ayuda a identificar con precisión los síntomas típicos y a sopesar las medidas a tomar.

Síntoma Causa probable Medida
La RAM aumenta constantemente, comienza el intercambio Buffer pool demasiado grande o muchos buffers por conexión Limitar el tamaño de la piscina, reducir mediante el búfer de conexión.
Muchas combinaciones/uniones lentas Índices faltantes, búferes sort/join excesivos Comprobar índices, mantener sort/join conservador
Gran cantidad de datos libres en tablas Actualizaciones/eliminaciones importantes, páginas fragmentadas OPTIMIZE específico, archivado, optimización del esquema
Picos en tablas de disco temporales tmp_table_size demasiado pequeño o consultas inadecuadas Aumentar los valores moderadamente, reestructurar las consultas

Ajuste de la memoria de MySQL: elegir tamaños en lugar de excederse

Selecciono el búfer InnoDB de tal manera que el Sistema operativo mantiene suficiente capacidad para la caché del sistema de archivos y los servicios, especialmente en servidores combinados con web y base de datos. Escalo de forma conservadora los búferes por conexión, como sort_buffer_size, join_buffer_size y read-Buffer, para que muchas conexiones simultáneas no provoquen tormentas de RAM. Configuro tmp_table_size y max_heap_table_size de manera que las operaciones sin importancia no requieran enormes tablas en memoria. Para más ajustes, encuentro en Rendimiento de MySQL Ideas útiles para reflexionar. Lo decisivo sigue siendo: prefiero ajustar un poco más y medir, en lugar de abrir a ciegas y arriesgarme a la fragmentación y al intercambio.

InnoDB en detalle: estrategias de reconstrucción e instancias de grupo

Para mantener MySQL más „compacto“ internamente, tengo previsto realizar Reconstrucciones para tablas con una gran proporción de escrituras. Un OPTIMIZE TABLE específico (o una reconstrucción en línea mediante ALTER) combina datos e índices y reduce Sin datos. Para ello, selecciono franjas horarias con poca carga, ya que las reconstrucciones requieren un uso intensivo de E/S. La opción innodb_file_per_table Lo mantengo activo porque permite reconstrucciones controladas por tabla y reduce el riesgo de que algunos „elementos problemáticos“ fragmenten todo el archivo del espacio de tabla.

Utilizo varios Instancias de grupo de búferes (innodb_buffer_pool_instances) en relación con el tamaño del grupo y los núcleos de la CPU para aliviar los bloqueos internos y distribuir los accesos. Esto no solo mejora la paralelidad, sino que también suaviza los patrones de asignación en el grupo. Además, compruebo el tamaño de los registros de rehacer y la actividad de los subprocesos de purga, ya que el historial acumulado puede ocupar memoria y E/S, lo que aumenta la fragmentación a nivel del sistema operativo. Es importante cambiar los ajustes gradualmente, medirlos y mantenerlos solo si las latencias y las tasas de error realmente disminuyen.

Evitar el intercambio: configuración del núcleo y NUMA

Tan pronto como Linux activa el intercambio, los tiempos de respuesta aumentan en órdenes de magnitud, porque los accesos a la RAM se convierten en E/S lentas. Reduzco considerablemente vm.swappiness para que el kernel utilice la RAM física durante más tiempo. En hosts con múltiples CPU, compruebo la topología NUMA y, si es necesario, activo el entrelazado para reducir el uso desigual de la memoria. Para obtener información sobre los antecedentes y la influencia del hardware, me ayuda la perspectiva de Arquitectura NUMA. Además, planifico reservas de seguridad para la caché de páginas, ya que una caché agotada acelera la fragmentación de toda la máquina.

Páginas enormes transparentes, sobrecompromiso y elección del asignador

Páginas enormes transparentes (THP) pueden provocar picos de latencia en las bases de datos, ya que la fusión/división de páginas grandes se produce en momentos inoportunos. Configuro THP en „madvise“ o lo desactivo si MySQL reacciona con demasiada lentitud bajo carga. Al mismo tiempo, tengo en cuenta Overcommit: Con una configuración demasiado generosa de vm.overcommit_memory, se corre el riesgo de sufrir OOM kills precisamente cuando la fragmentación hace que los bloques contiguos grandes sean escasos. Yo prefiero configuraciones conservadoras de overcommit y compruebo regularmente los registros del kernel en busca de signos de presión de memoria.

En Elección del asignador A nivel del sistema, vale la pena echar un vistazo. glibc‑malloc, jemalloc o tcmalloc se comportan de manera diferente en lo que respecta a la fragmentación. Siempre pruebo las alternativas de forma aislada, mido el historial RSS y las latencias, y solo implemento los cambios si las métricas se mantienen estables con tráfico real. La utilidad varía mucho según la carga de trabajo, la combinación de extensiones y la versión del sistema operativo.

Detectar la fragmentación: métricas e indicaciones

Presto atención al aumento gradual líneas de base en RAM, más respuestas 5xx bajo carga y retrasos en las acciones administrativas. Un vistazo a las estadísticas PM de PHP-FPM muestra si los hijos alcanzan los límites o viven demasiado tiempo. En MySQL, compruebo los ratios de aciertos, las tablas temporales en el disco y los datos libres por tabla. Paralelamente, las métricas del sistema operativo, como los fallos de página, el intercambio de entrada/salida y los indicadores de fragmentación de memoria, ayudan en función de la versión del núcleo. Quien combine estas señales, detectará patrones de forma temprana y podrá planificar medidas.

Profundizar en la monitorización: cómo combino las señales

Yo correlaciono Métricas de aplicación (latencias p95/p99, tasas de error) con métricas de proceso (RSS por trabajador FPM, memoria mysqld) y Valores OS (Pagecache, Slab, Major Faults). En PHP‑FPM utilizo la interfaz de estado para las longitudes de cola, los hijos activos/generados y la vida útil de los trabajadores. En MySQL, observo Created_tmp_disk_tables, Handler_write/Handler_tmp_write y Buffer‑Pool‑Misses. Además, miro los mapas de procesos (pmap/smaps) para averiguar si se han creado muchas áreas pequeñas. Para mí es importante la orientación a las tendencias: no es el pico individual, sino el desplazamiento gradual a lo largo de horas/días lo que determina si la fragmentación se convierte en un peligro real.

Rutina práctica: mantenimiento y gestión de datos

Limpio regularmente. Datos En: sesiones caducadas, registros antiguos, revisiones innecesarias y cachés huérfanas. Para tablas muy variables, planifico ventanas OPTIMIZE específicas para fusionar páginas fragmentadas. Distribuyo los trabajos de importación grandes o las oleadas de cron en el tiempo para que no todos los procesos soliciten el máximo búfer al mismo tiempo. En proyectos en crecimiento, separo la web y la base de datos en una fase temprana para aislar los patrones que consumen mucha memoria. Esta disciplina mantiene la memoria más coherente y reduce el riesgo de latencias repentinas.

Calcular tamaños con precisión: dimensionar límites y grupos

Determino pm.max_children basándome en la RAM realmente disponible para PHP. Para ello, mido el RSS medio de un trabajador bajo carga real (incluidas las ampliaciones y OPcache) y añado márgenes de seguridad para los picos. Ejemplo: en un host de 16 GB, reservo entre 4 y 6 GB para el sistema operativo, la caché de páginas y MySQL. Quedan 10 GB para PHP; con 150 MB por trabajador, esto da teóricamente 66 hijos. En la práctica, establezco pm.max_children en ~80-90% de este valor para dejar margen para picos, es decir, entre 52 y 58. pm.max_requests Elijo de tal manera que los trabajadores se reciclen antes de que se produzca una fragmentación notable (a menudo entre 500 y 2000, dependiendo de la combinación de códigos).

Para MySQL, calculo el Buffer Pool del tamaño de los datos activos, no del tamaño total de la base de datos. Las tablas e índices importantes deben caber, pero la caché del sistema operativo necesita espacio para los binlogs, los sockets y los activos estáticos. Para el búfer por conexión, calculo con el paralelismo máximo realista. Si son posibles 200 conexiones, no dimensiono de manera que se disparen varios gigabytes por conexión, sino que establezco límites estrictos que no supongan un riesgo de intercambio, incluso en momentos de pico.

Desacoplar colas, procesamiento de imágenes y tareas secundarias

Muchos problemas de fragmentación surgen cuando trabajos secundarios los mismos grupos que las solicitudes frontend. Para exportaciones, rastreos, conversiones de imágenes o actualizaciones de índices de búsqueda, utilizo grupos FPM separados o trabajos CLI con claros ulimitLímites. Además, limito las operaciones con imágenes con GD/Imagick mediante límites de recursos adecuados, para que las conversiones individuales de gran tamaño no „fragmenten“ todo el espacio de direcciones. Planifico los trabajos de forma escalonada y les asigno sus propios límites de concurrencia, para que no saturen la ruta del frontend.

Contenedores y virtualización: cgroups, OOM y efectos globo

En los contenedores observo Límites de memoria y el OOM Killer con especial atención. La fragmentación hace que los procesos se ejecuten antes en los límites de Cgroup, aunque todavía haya RAM libre en el host. Ajusto pm.max_children estrictamente al límite del contenedor y mantengo suficiente reserva para amortiguar los picos. Evito el intercambio dentro de los contenedores (o en el host) porque la indirección adicional aumenta los picos de latencia. En las máquinas virtuales compruebo el ballooning y KSM/UKSM; la deduplicación agresiva ahorra RAM, pero puede causar latencia adicional y distorsionar el panorama de la fragmentación.

Breve lista de verificación sin viñetas

Primero establezco un objetivo realista. límite_de_memoria por sitio y observo cómo se comporta el pico de uso a lo largo de los días. A continuación, ajusto PHP-FPM con los valores pm adecuados y un pm.max_requests razonable, para que los trabajadores fragmentados funcionen según lo previsto. Para MySQL, me centro en un tamaño de búfer adecuado y un búfer por conexión conservador, en lugar de aumentos generales. En cuanto al kernel, reduzco la swappiness, compruebo la configuración NUMA y mantengo reservas libres para la caché de páginas. Por último, evalúo las tablas con anomalías Data_free y planifico optimizaciones fuera de la actividad diaria.

En resumen: lo que cuenta en la empresa

El mayor efecto contra la fragmentación de la memoria lo consigo con un reciclaje El trabajador PHP-FPM, límites moderados y pools limpios. MySQL se beneficia de tamaños razonables para el buffer pool y el buffer por conexión, así como de tablas ordenadas. Evito el intercambio de forma proactiva prestando atención a la capacidad de intercambio y NUMA, y reservando RAM libre para la caché de archivos. La supervisión detecta patrones ocultos antes de que los usuarios se den cuenta y permite intervenciones tranquilas y planificadas. Quien utilice estas herramientas de forma disciplinada mantendrá PHP y MySQL más rápidos, fiables y rentables sin necesidad de actualizaciones inmediatas de hardware.

Artículos de actualidad