Apache Tunning. Ajustando recursos en un servidor web con poca memoria

apacheInstalar un servidor Apache en un entorno Linux debe de ser lo más fácil del mundo. Configurarlo para que funcione de forma aceptable sirviendo una o dos instancias web también es sencillo. Ajustarlo para que funcione de forma eficiente en entornos escasos de recursos o para que escale bien ante grandes cargas ya es algo en lo que muy pocos se meten. Si siempre has trabajado con servidores compartidos no te habrás dado cuenta de esto último porque alguien te lo ha dado ya hecho. Pero tanto si has tenido que gestionar servidores propios y/o dedicados o pequeños VPS posiblemente hayas tenido algún problema en este sentido. Y si no, creeme: los vas a tener…

La forma en que Apache gestiona la multitarea a la hora de atender a los clientes del servidor web se configura mediante los módulos MPM. Los dos más comúnmente usados son Prefork y Worker pero existe por ahí alguno más. Al instalar apache2 en Debian (y en todas las distribuciones, me atrevería a decir) se instala por defecto el paquete apache2-mpm-prefork y este es el módulo MPM que funcionará por defecto. Para probar con worker basta con instalarlo (apt-get install apache2-mpm-worker en una Debian) y automáticamente desinstalará el otro mpm (sólo puede haber uno en cada momento) y reiniciará el servicio de apache para que los cambios tomen efecto.

La primera pregunta que se nos plantea es, lógicamente, cual elegir. A grandes rasgos la principal diferencia entre ambos es que Prefork crea siempre nuevos procesos para gestionar las conexiones entrantes mientras que con Worker los procesos hijos, a su vez, crean múltiples hebras cada uno de ellos para esta tarea. Tienes información más precisa de como funcionan en los enlaces de hace unas líneas. Prefork tiene a gala ser más estable y funcionar mejor en máquinas escasas de recursos, mientras que Worker presume de ser más escalable y eficiente para servidores más ambiciosos. Para novatos o instalaciones sin necesidades especiales es mejor quedarse con la opción por defecto (Prefork) puesto que, además, incluso algo tan simple como instalar PHP es trivial y habitual con este módulo mientras que hay que «enredar» bastante más para hacerlo funcionar con Worker.

Si el servidor no lo hemos instalado nosotros y queremos saber que MPM está activo basta con recordar que sólo puede haber uno, así que podemos mirar que paquete está instalado (aptitude search apache2-mpm-* en Debian, de nuevo) o interrogar directamente a apache con la opción -l:

root@invernalia:/etc/apache2# apache2 -l
Compiled in modules:
  core.c
  mod_log_config.c
  mod_logio.c
  prefork.c
  http_core.c
  mod_so.c

Pero independientemente del módulo que escojamos, el comportamiento final del servidor web será muy parecido. Pensemos que se trata de un servicio diseñado para atender múltiples peticiones simultaneas de decenas (o centenares, si tenemos una web con éxito) de clientes y hacerlo de forma eficiente y en el menor tiempo posible. Nuestro servidor permanecerá a la espera de estas peticiones y, cuando vayan llegando, destinará a un proceso (o a una hebra de un proceso) a atenderlas. Cuantos más peticiones se cursen de forma simultanea, más procesos/hebras necesitaremos y consumiremos más recursos de la máquina. Sobre todo memoria. Un servidor web se comporta como un insaciable devorador de RAM. Este va a ser el recurso que debemos de aprender a gestionar: la RAM que no se usa cuando se necesita se desperdicia y nos hace perder visitantes o que estos sean atendidos más lentamente. Pero si no controlamos el consumo y el servidor la agota comenzará a hacer swapping a disco y evitar esto es la regla de oro: Si llegamos a este punto lo habremos perdido y, posiblemente, tendrás que reiniciar el servicio (o la máquina, si no tienes tiempo o paciencia) para volver a tomar el control.

Los principales ajustes que necesitamos hacer se encuentran en nueve líneas del archivo /etc/apache2/apache2.conf. A continuación tienes los valores por defecto en una instalación básica de Debian 6: Ten cuidado porque las cinco últimas se encontraran duplicadas en el interior de sentencias condicionales segun el módulo MPM que estemos usando. Las que nos interesan hoy son las que están tras la sentencia <IfModule mpm_prefork_module>.

Timeout 		300
KeepAlive 		On
MaxKeepAliveRequests	100
KeepAliveTimeout   	15
StartServers        	5
MinSpareServers     	5
MaxSpareServers     	10
MaxClients		150
MaxRequestsPerChild	0

Timeout, el primero de ellos, define el tiempo en segundos que Apache esperará a determinados eventos antes de cerrar o abortar una conexión. En servidores con pocos recursos 300 segundos puede ser una cantidad bastante elevada. Reducir ese tiempo sustancialmente ayudará a gestionar mejor la memoria del servidor. Valores de 30 o 40 son adecuados en servidores con pocos recursos. Un valor de 10, incluso, podría mejorar el rendimiento en determinados entornos.

Los tres parámetros siguientes (KeepAlive, MaxKeepAliveRequest y KeepAliveTimeout) definen la posibilidad de usar conexiones persistentes y la forma de tratarlas. En concreto, el número de peticiones que se permitirán sobre cada conexión (MaxKeepAliveRequest) y el tiempo de espera sin peticiones antes de abortarlas. Tener habilitadas este tipo de conexiones reduce sustancialmente la carga del servidor y los tiempos de respuesta y el número de peticiones se puede mantener en torno a 100 en casi cualquier entorno. Es, de nuevo, el Timeout por defecto de 15 segundos el que puede resultar demasiado elevado en servidores con poca RAM y podríamos reducirlo a un valor de 5 segundos o incluso menos.

Los cinco parámetros restantes son los que definen el comportamiento del módulo Prefork. StartServers, MinSpareServers y MaxSpareServers ajustan, respectivamente, el número de procesos apache que se crearan inicialmente, y el mínimo y máximo de estos que mantendremos inactivos a la espera de que lleguen peticiones de posibles clientes. Los procesos inactivos definidos por estos parámetros estarán consumiendo memoria pero nos permitirán dar una respuesta más rápida a los clientes (los procesos están ya listos para usarse cuando llegan peticiones y no hay que perder tiempo en crearlos), así que en un servidor con grandes recursos y que espera cargas elevadas querríamos tener valores más elevados de los que vienen por defecto mientras que en un servidor pequeño podríamos reducirlos todos a, por ejemplo, 3 o 4.

MaxClients es, posiblemente, el parámetro más importante. Define el máximo número de procesos simultaneos que nuestro servidor podrá crear para atender peticiones. Un número más pequeño del que podemos permitirnos desperdiciará memoria y ralentizará las peticiones de muchos clientes (que permanecerán a la espera de que uno de estos procesos se libere para atenderle) mientras que un número demasiado elevado agotará la memoria del servidor y lo obligará a hacer swapping a disco. Y aquí no hay fórmulas mágicas. Lo primero que se nos puede ocurrir es que dividiendo la memoria que tenemos disponible entre lo que ocupa cada proceso de Apache obtendríamos el número que necesitamos, pero el problema es que la memoria disponible es difícil de calcular de forma exacta (ya que, posiblemente, tendremos también un servidor mysql, servicios de correo, etc). Además, los procesos de apache no consumen todos la misma cantidad de memoria. Si tenemos distintas instancias de apache con características muy diferentes la diversidad será aún mayor. Podemos verlo en el siguiente pantallazo de htop tomado de un servidor apache funcionando con varias instancias web diferentes:
Vista de htop en un servidor apache que estamos monitorizando

La columna MEM% nos da el porcentaje de memoria que está ocupando el proceso de apache y, como vemos, tenemos cifras entre 0.5% y 4.2%, es decir, entre 4 y 32 Mbytes (la máquina es un VPS con 768Mbytes). Si aproximamos que disponemos de 600Mbytes disponibles para que apache los use nos saldrían cifras de entre 150 y 18 procesos simultaneos. Una horquilla demasiado grande. Si somos muy, muy conservadores podemos irnos al borde inferior de la horquilla y asegurar que nunca entraremos en zona de riesgo. Podemos también dedicarle tiempo a estudiar el comportamiento medio de nuestros clientes y hacer medias de la utilización de memoria por los procesos de Apache. Lo más rápido para empezar es tomar un valor medio, por ejemplo de un 2.5% o un 3% y ajustar por ahí el número máximo de procesos. Entre 25 y 40 en este caso. El hecho de que hayamos bajado mucho los Timeouts ayudará a que los procesos se liberen rápidamente y se turnen atendiendo a los clientes que están a la espera con lo que no deberíamos de notar demasiado la espera.

Tenemos aún otro parámetro más. MaxRequestsPerChild marca el número de peticiones que atenderá cada proceso antes de reciclarse. El valor por defecto es cero que indica un número indefinido. Poner un número elevado pero no ilimitado ayudará también a que nuestro servidor libere y sanee su memoria de forma regular. Un valor de entre 500 o 1000 podría ser adecuado para nuestra pequeña máquina. Nuestra configuración final podría quedar así:

Timeout 		10
KeepAlive 		On
MaxKeepAliveRequests	100
KeepAliveTimeout   	5
StartServers        	3
MinSpareServers     	3
MaxSpareServers     	4
MaxClients		30
MaxRequestsPerChild	600

No olvides que hay que reiniciar el servicio de apache (service apache2 restart) después de realizar cualquier modificación en este fichero de configuración para que los cambios tomen efecto.

15 comentarios

  1. Muchas gracias por tu post, me estaba volviendo loco con un VPS que tengo, voy a probar esto que dices a ver si me resulta. No obstante, hay algunos parámetros que me faltan, que no se como interpretar:

    StartServers 1
    MaxClients 30
    MinSpareThreads 1
    MaxSpareThreads 4
    ThreadsPerChild 25
    MaxRequestsPerChild 0

    ¿qué valores colocarías para un VPS con 512 de Ram?
    Gracias de antemano.

    1. Carlos: No hay fórmulas mágicas. Depende mucho de que tipo de páginas esté sirviendo tu Apache. No es lo mismo si son páginas estáticas html o páginas php, lo pesado que sea el código que ejecutan estas últimas, que más estás ejecutando en ese servidor… ¿Qué buscas mejorar y que síntomas observas?¿Se te queda colgado frecuentemente por exceso de consumo de memoria?¿el rendimiento del servidor es demasiado bajo?

  2. Buenos días 😉

    Empezaré ahora por el final:

    # ¿entre eaccelerator y xcache, tienes alguna preferencia?¿Has probado también APC?

    Sí, los tres y un tanto a fondo, en mi opinión, a APC aún le falta «algo» para llegar al rendimiento de los otros dos, lo veremos como el sistema de caching que vendrá «de serie» con PHP 6x. Sobre eaccelerator hay una cosa que tengo muy clara, en servers con pocos recursos y al llevar ese contenido al disco en lugar que en memoria va mejor ¿problema? hay que compilarlo (que son 3 min entre que lo quitas del php.ini y compilas) cada vez que entra un update de PHP y si no te acuerdas al hacer un upgrade te peta Apache.

    Por el contrario, xcache está en los sources y se actualiza junto al sistema. Ahora mismo estoy usando xcache en el mío y en el de muchos clientes de APACHEctl y estoy contento con el resultado aunque hablamos de máquinas con más recursos pero mira, el módulo de memcached es tal útil que por citarte un ejemplo, los foros que uso en Daboweb o Caborian (este último entre web y foros sobre 2 millones de pág vistas / mes http://www.caborian.com/stats) cuando vas a las opciones avanzadas, si está memcached instalado lo usa como sistema de caching.

    Si tienes además un eaccelerator o xcache «ayudando», al final, lo que tendrás serán varios escenarios cubiertos cacheando contenido tanto en disco como en memoria y desde luego que bajan los loads y la memoria asignada a Apache.

    Un abrazo !! Nos leemos 😉

  3. Dabo: eaccelerator si que me gusta y es verdad que ayuda a reducir sensiblemente la memoria que ocupan los procesos de apache (si sigo con esta serie escribiré sobre su instalación y configuración y las mejoras que se obtienen). Pero, al final, el procedimiento que aconsejaría sería el mismo: ver cuanta memoria ocupan los nuevos procesos de apache usando eaccelerator, calcular cuantos cabrían en la memoria disponible de la máquina y sacar a partir de ahí la cifra adecuada para MaxClients. memcached está más orientado a cachear datos que php y no debería reducir casi nada el tamaño de los procesos de apache ¿no?

    Y, ya por último, aprovecho ¿entre eaccelerator y xcache, tienes alguna preferencia?¿Has probado también APC?

  4. Hola 😉 Realmente, estaba aplicando alguna conf que tenía hace unos ¿3 años? (o quizás 4, se me van las fechas) en uno en el que estaba Daboweb, Caborian, DaboBlog, etc. Mucho WP y foros en SMF, era un Pentium III con 512 MB de Ram (dedicado, eso sí) y estaba más o menos con eso.

    En ese caso estaba con Prefork y luego pasé a Worker y sí que se notaba y de hecho por lo que veo a diario se nota, pero lo que más se notaba era junto a eaccelerator para los WordPress y foros y también el módulo de Memcached. Pero si me viese ahora mismo en esa tesitura (y mira que me gusta Apache) no tendría dudas, tiraría de Ngnix y PHP como fast CGI, no sin antes haber pasado por Worker.

    Y es que en este caso en concreto, si queda algún proceso o hilo de Apache sin cerrar debidamente, habiendo peticiones, esa «cola» a la espera de los 30 en MaxClients puede hacer que el propio Apache consuma más recursos de los deseados.

    Ya te digo, probaría en Prefork con por ejemplo xcache (ahí en la conf unos 64 MB de Ram) o en su defecto si la versión 0.96x de eaccelerator es compatible con tu versión de PHP, otros 32 en la caché de MySQL y 64 para Memcached. Sería un buen punto de partida. A partir de ahí, Worker o ya sin dudar Lighttpd o Ngnix.

    Verás cómo se nota, saludos 😉

  5. Buenas Dabo. Encantado de verte de nuevo por aquí 🙂
    En un servidor con contenido estático te daría la razón sin dudarlo un segundo. Pero en las instancias que se manejan hoy en día, hasta arriba de scripts y de plugins, la carga de memoria de cada proceso es tan burra que en un VPS básico, con 512 o 768 MBytes corres, siempre a mi juicio, demasiados riesgos de que se te cuelgue cada dos por tres con un número tan alto de MacClients. Te voy a poner un ejemplo real aún más drástico que el que ilustro aquí arriba. Se trataba de un servidor web en un VPS de 512 Mbytes. Usaba wordpress y estaba tan cargado de plugins que los procesos de apache se comían una barbaridad de memoria. Cuando el servidor tenía un pico de tráfico se quedaba congelado y la única forma de recuperarlo era resetearlo a través del panel de control del hosting. Yo estaba casi seguro de que se trataba de esto, porque monte un cacti y cuando se congelaba tenía la memoria al 100% y empezaba a hacer swapping a dico como un becerro. Para asegurarme le puse un script que listara los procesos en curso ordenados por uso de memoria y los guardara en disco cada dos minutos. Después de reiniciarlo tras el siguiente cuelgue cogí el último listado que había escrito y era este: http://pastebin.com/XP2uhiSD Fíjate bien que sólo tiene 17 procesos de apache corriendo pero, si haces las cuentas, consumen entre todos más del total de la memoria del servidor. Lo bajamos a 15 procesos y nunca más se ha caído. Por supuesto que a partir de la petición 16 esta se encola y los tiempos de respuesta van a ser más altos, por eso trato de bajar también al mínimo los Timeouts para que haya una rotación rápida (en el caso que te comento los tuve que bajar aún más para tener un resultado aceptable).
    En cuanto a worker, había oído que en servidores con poca RAM las diferencias que se obtienen son tan nimias que no merece la pena el esfuerzo extra, pero nunca he hecho una comparativa por mi cuenta, así que no porfío en ello. ¿Tienes tú resultados favorables en servidores en el rango del que estamos hablando?

  6. Y para acabar, ahora que me «releo», si tenemos poca memoria mejor pasar a worker y ya que como bien aludes es más complejo para quienes empiezan, que instalen memcached, junto a unos valores afinados de la caché de MySQL y otra solución en conjunto para realizar un sistema de «caching» de objetos en disco o memoria como eaccelerator o xcache (que cada vez me gusta más)

    Saludos

  7. Hola José María 😉 Como siempre te sigo con atención, sólo comentarte respecto al tema del «Max Clients» que lo que hace es especificar el límite de peticiones simultáneas que serán atendidas por la máquina en cuestión, en esa conf, con un valor de 30, lo que estaremos haciendo es «dejar a la espera» a partir de la petición (o IP) número 31 hasta que se cierren las de los otros 30 anteriores que, con un timeout (correcto así) de 30 seg, puede hacer que tengas muchas a la espera y obviamente en lugares con tráfico, que dejes de servir páginas cuando haya muchas peticiones simultáneas.

    Humildemente, para un VPS o Dedicado «normal» hablando de recursos, dejaría el MaxClients en unos 150 -200 (ojo también al de MySQL) y StartServers en 8, Min en 5 y max entre 12-15 pero vaya, como bien dices, cada server y cada configuración es un mundo.

    Un abrazo !

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Uso de cookies

Este sitio web utiliza cookies para que usted tenga la mejor experiencia de usuario. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies, pinche el enlace para mayor información

ACEPTAR
Aviso de cookies