Icono del sitio Un lugar en el mundo…

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

Instalar 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:

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.

Salir de la versión móvil