Apache es un servidor HTTP de código libre. Es el servidor web más popular en internet. Una encuesta realizada por NetCraft en Noviembre de 2015 acerca de los servidores web, muestra que alrededor del 37% de las webs están alojadas en un servidor Apache -el resto se dividen en Microsoft ISS (27%), NGinx (17%) y Google (2%).
Vamos a ver en este post cómo podemos configurar Apache para optimizarlo adecuadamente y explotar su máximo potencial.
El rendimiento de un servidor Apache se puede mejorar añadiendo a nuestro sistema algunos recursos de hardware adicionales, tales como más memoria RAM, una CPU más rápida, etc; no obstante, la mayoría de las veces, podemos lograr el mismo resultado si realizamos una configuración personalizada del servidor. En este post nos centraremos en cómo conseguir el máximo rendimiento de Apache aprovechando los recursos de hardware existentes en el servidor, específicamente en los sistemas Linux. En las dos primeras secciones hablaremos de la configuración en el momento de la compilación y en el momento de la ejecución. Posteriormente, veremos las ventajas del almacenamiento en caché y la compresión HTTP. Por último, hablaremos de la configuración de servidores separados para servir contenido estático y dinámico. Este post presupone que tienes conocimientos básicos de compilación y configuración de Apache en Linux.
Apache es un software modular donde el administrador del sistema puede escoger qué funcionalidades quiere incluir en el servidor mediante la selección de un conjunto de módulos. Los módulos pueden ser compilados estáticamente en el binario HTTP, o bien pueden estar compilados como Dynamic Shared Object (DSO). Los módulos DSO se pueden compilar cuando se realiza la instalación de Apache, o bien se puede utilizar la utilidad apx para agregarlos posteriormente. Eso sí, para habilitar el soporte DSO, primero deberemos haber compilado el módulo mod_so en el núcleo de Apache en modo estático.
Debes ejecutar Apache sólo con los módulos necesarios, de este modo reducirás el consumo de memoria y por lo tanto el servidor rendirá más. Los módulos cargados estáticamente consumen menos RAM que los módulos cargados de forma dinámica, pero tienen la desventaja de que debe recompilarse Apache de nuevo cada vez que deseamos habilitar un módulo estático. Aquí es donde DSO resulta muy útil. Una vez que el módulo mod_so ha sido compilado estáticamente, podemos añadir cualquier módulo simplemente con el comando LoadModule en el archivo de configuración HTTP (obviamente, deberás haber compilado el módulo necesario en el momento de la instalción de Apache, o tendrás que usar la utilidad apx si no lo hiciste entonces).
Apache incluye de serie algunos Módulos de MultiProcesamiento (MPM’s), que son responables del manejo de los puertos del servidor, aceptando solicitudes y enviando los procesos hijo a manejar estas solicitudes. No se puede tener funcionando más de un MPM de forma simultánea.
Utilizar un MPM u otro depende de varios factores, como por ejemplo si el procesador acepta hilos (threads), la cantidad de memoria disponible, la estabilidad frente a la escalabilidad, etc. En los sistemas Linux podemos optar por utilizar un MPM Threaded como el llamado MPM Worker o un MPM no Threaded como el MPM Prefork.
El MPM Worker utiliza múltiples procesos hijo, es multi hilo dentro de cada hijo, y cada hilo maneja una sola conexión. Worker es rápido y muy escalable, y el consumo que hace de la memoria es relativamente bajo. Es muy adecuado para procesadores múltiples. Por otra parte, Worker es menos tolerante a los módulos defectuosos y los Threads defectuosos pueden afectar a todos los hilos en un proceso hijo.
El MPM Prefork utiliza múltiples procesos hijo, y cada hijo se encarga de una sola conexión. Prefork es más adecuado para sistemas con procesadores de uno o dos núcleos, la velocidad es muy parecida a la del MPM Worker, y es más tolerante que éste con los módulos defectuosos y los procesos hijo rotos. Al mismo tiempo, el uso que hace de la memoria es alto, por lo que cuanto más tráfico recibe más consumo de memoria realiza.
La directiva HostnameLookups permite la búsqueda de DNS’s para que se puedan registrar y realizar llamadas a los nombres de host en lugar de a la IP. Esto añade latencia a cada solicitud, ya que debe completarse la búsqueda de DNS’s antes de completar la solicitud. La directiva HostnameLookups está deshabilitada por defecto en las versiones de Apache 1.3 y superiores. Déjalo deshabilitado y utiliza un programa de post-procesado como logresolve para resolver las direcciones IP en los archivos de acceso de Apache. Logresolver se incluye con Apache.
Cuando utilices las directivas Allow from o Deny from, usa la dirección IP en lugar de un nombre de dominio o un nombre de host. De lo contrario se realizará una búsqueda de DNS’s doble para asegurar que el nombre de dominio o el nombre de host no están siendo falsificados.
Si la directiva AllowOverride no está establecida en «Ninguno», Apache intentará abrir el .htaccess (según lo especificado en la directiva AccessFileName) en cada directorio que visite. Por ejemplo:
DocumentRoot /var/www/html <Directory/> AllowOverride All </Directory>
Si se realiza una solicitud para la URI /index.html, entonces Apache intentará abrir /.htaccess, /var/.htaccess, /var/www/.htaccess y /var/www/html/.htaccess. Estas operaciones de búsqueda en el sistema de archivos influyen también en el aumento de la latencia. Si se quiere usar un .htaccess para un directorio en particular, debes habilitarlo únicamente para ese directorio.
Si la directiva FollowSymlinks está activada, el servidor seguirá los enlaces simbólicos en ese directorio. Si se activa la directiva SymlinksIfOwnerMatch , el servidor seguirá los enlaces simbólicos sólo si el archivo o directorio es propiedad del mismo usuario que el enlace.
Si activas la directiva SymlinksIfOwnerMatch, Apache tendrá que realizar llamadas adicionales para comprobar quién es el propietario del enlace y del archivo de destino. También se realizan llamadas adicionales cuando la directiva FollowSymplinks está desactivada. Por ejemplo:
DocumentRoot /var/www/html <Directory/> Options SymLinksIfOwnerMatch </Directory>
Para una solicitud hecha hacia la URI /index.html, Apache realizará un lstat() en /var, /var/www, /var/www/html y /var/www/html/index.html. Estas llamadas adicionales se sumarán a la latencia. Además, los resultados de lstat() no se almacenan en la caché, por lo que se realizarán con cada solicitud.
Para un máximo rendimiento, establece la directiva FollowSymlinks en All y desactive la directiva SymlinksIfOwnerMatch. O bien, si necesitas tener activado SymlinksIfOwnerMatch para un directorio, configúralo sólo para ese directorio.
Para una respuesta rápida, evita la negociación de contenido. Si necesitas la negociación de contenido para una web, utiliza archivos type-map en lugar de la directiva Option Multiviews. Con Multiviews, Apache tiene que escanear el directorio de archivos, lo que incrementa también la latencia.
La directiva MaxClients establece el número máximo de conexiones simultáneas que pueden ser soportadas por el servidor. No debe configurarse con un valor demasiado bajo para que las nuevas conexiones no deban ponerse en cola, lo que provocaría un timeout en las conexiones y los recursos del servidor se quedarían sin usar. Si por el contrario establecemos este valor demasiado alto, el servidor podría saturarse y el tiempo de respuesta se degradará drásticamente. El valor adecuado para la directiva MaxClients se puede calcular así:
MaxClients = RAM dedicada a Apache / Profundidad máxima de los procesos hijo (el tamaño de los procesos hijo para servir archivos estáticos está sobre los 2-3 MB. Para servir archivos dinámicos está sobre los 15MB).
Si hay más usuarios simultáneos que los configurados en MaxClients, las peticiones se pondrán en cola hasta el número especificado en la directiva ListenBackLog. Debes aumentar la directiva ServerLimit si quieres aumentar la directiva MaxClients por encima de 256.
Las directivas MaxSpareServers y MinSpareServers determinan cuántos procesos hijo se mantendrán a la espera de las solicitudes. Si MinSpareServers tiene un valor demasiado bajo y llegan muchas peticiones, Apache tendrá que generar procesos adicionales secundarios para atender esas peticiones. La creación de procesos hijo debe evitarse en lo posible. Si el servidor está ocupado creando procesos hijo, no podrá atender las solicitudes de los clientes de forma inmediata. La directiva MaxSpareServers no debe ser demasiado alta, ya que podría causar problemas de recursos porque los procesos hijo los consumen.
Configura MaxSpareServers y MinSpareServers para que Apache no necesite crear frecuentemente más de 4 procesos hijo por segundo (Apache puede crear un máximo de 32 procesos hijo por segundo). Cuando se creen más de 4 hijos por segundo se generará un registro en el error log.
La directiva StartServers establece el número de procesos hijo creados al inicio. Apache continuará creando procesos hijo hasta que alcance el valor estipulado en la directiva MinSpareServers. Esto no no tiene mucha incidencia en el rendimiento del servidor si éste no se reinicia con frecuencia. Si hay muchas peticiones y Apache se reinicia con frecuencia, ajusta esta directiva a un valor relativamente alto.
La directiva MaxRequestsPerChild establece el número máximo de solicitudes que un proceso hijo puede manejar. Después de alcanzar ese valor, el proceso hijo morirá. Este valor es «0» por defecto, lo que significa que el proceso hijo nunca caducará. Es conveniente otorgarle a esta directiva un valor de unos miles para ayudar a prevenir un uso excesivo de la memoria del servidor, ya que después de cumplir con cierto número de peticiones el proceso hijo morirá. No establezcas un valor demasiado bajo para que las solicitudes que debe atender el proceso hijo nunca estén por encima.
La directiva KeepAlive permite que se reciban múltiples solicitudes a través de la misma conexión TCP. Esto será útil sobretodo cuando sirva páginas con gran cantidad de imágenes. Si KeepAlive estuviese desactivado, el servidor realizaría una conexión diferente para cada imagen.
KeepAliveTimeout establece cuánto tiempo tendrá que esperar hasta recibir la próxima petición. Ajústalo a un valor bajo, entre 2 y 5 segundos. Si el valor es demasiado alto, los procesos hijo estarán a la espera sin ser productivos hasta que se agote el tiempo para poder seguir sirviendo a nuevos clientes.
La compresión HTTP está completamente especificada en HTTP/1.1. El servidor utiliza GZip o Deflate para comprimir el contenido antes de enviarlo al cliente. No es necesario instalar ningún software adicional del lado del cliente, ya que todos los navegadores lo soportan por defecto. El uso de compresión hará que ahorres ancho de banda y mejorará el tiempo de respuesta del servidor, pudiendo mejorar hasta un 75% según algunos estudios. La compresión HTTP se puede activar en Apache mediante el módulo mod_deflate. El contenido será comprimido sólo si el navegador solicita la compresión, de lo contrario se servirá sin comprimir. El navegador informa al servidor de que prefiere el contenido comprimido a través de la cabecera Accept-Encoding: gzip, deflate. Entonces el servidor responderá con los datos comprimidos y la cabecera de respuesta será Content-Encoding: gzip.
Con el almacenamiento en caché, una copia de los datos se almacena en el lado del cliente o en un servidor proxy, de modo que el contenido no tiene que ser recuperado desde el servidor cada vez que se realiza una petición. Esto ahorra ancho de banda, disminuye la carga del servidor y reduce el tiempo de respuesta. El control de la Caché se realiza a través de las cabeceras HTTP. En Apache esto se puede lograr con la utilización de los módulos mod_expires y mod_headers. También existe el almacenamiento en caché del lado del servidor, con el que el contenido al que más se accede es almacenado en la memoria del servidor de manera que pueda ser servido rápido. El módulo mod_cache se utiliza para la activación de la Caché del lado del servidor, y se ofrece de forma estable y nativa desde Apache 2.2.
Los procesos de Apache que sirven contenido dinámico consumen entre 3MB y 20MB de Ram. Aumenta para acomodar el contenido que tiene que servir y no disminuye hasta que el proceso muere. Digamos que un proceso de Apache crece hasta 20MB para servir contenido dinámico. Después de completar la solicitud quedará libre para atender a cualquier otra petición. Si entra la solicitud de una imagen, entonces este proceso de 20MB estará sirviendo a un proceso estático que podría ser servidor por un proceso de 1MB. Esto significa que la memoria se utiliza de manera ineficiente.
Usa un servidor Apache como servidor front-end, con el mínimo número de módulos compilados, para servir contenido estático. Reenvía las solicitudes de contenido dinámico a un servidor Apache más potente configurado con todos los módulos necesarios. El uso de un servidor mínimo como front-end tiene la ventaja de que el contenido estático se servirá más rápido sin utilizar mucha memoria y el contenido dinámico irá a parar al servidor pesado.
La solicitud de reenvío de peticiones se puede conseguir utilizando los módulos mod_proxy y mod_rewrite. Supongamos que hay un servidor Apache mínimo escuchando en el puerto 80 y el servidor Apache pesado escucha en el puerto 8080. La siguiente configuración se podría utilizar en el servidor Apache mínimo para enviar todas las solicitudes al servidor pesado, excepto las imágenes.
ProxyPassReverse / http://%{HTTP_HOST}:8088/ RewriteEngine on RewriteCond %{REQUEST_URI} !.*.(gif|png|jpg)$ RewriteRule ^/(.*) http://%{HTTP_HOST}:8088/$1 [P]
Todas las peticiones, excepto las peticiones de imágens, irán dirigidas al servidor Apache potente. El servidor Apache mínimo recibirá la respuesta y la enviará al navegador, que interpretará que la respuesta proviene de un solo servidor.
La configuración de Apache para el máximo rendimiento no es fácil, no hay reglas rápidas. Lo importante es comprender las opciones del servidor web y experimentar con diferentes parámetros para obtener el mejor resultado. Utiliza herramientas como ab y httperf para medir el rendimiento del servidor web. También puede utilizar servidores web ligeros como NGinx o Lighttpd como servidor front-end. Si utilizas un servidor de base de datos asegúrate también de que está configurado para no generar ningún cuello de botella.