Lo hice y lo entendí

El blog de Vicente Navarro
02 dic

Usando el mod_cache de Apache para que el mod_deflate no incremente la carga del servidor

En la entrada anterior, Probando el mod_deflate de Apache, vimos cómo conseguir que el Apache comprima los contenidos antes de mandarlos.

Sin embargo, parece un despropósito que el servidor web tenga que estar comprimiendo una y otra vez los mismos contenidos. Supone estar forzando al procesador a usar ciclos que pueden sernos necesarios para otra tarea en algo que debería de poder evitarse. Afortunadamente, gracias al módulo mod_cache, podemos conseguir evitar esta sobrecarga inútil para el servidor.

En realidad, este módulo tiene su máxima utilidad para configurar Apache como servidor Proxy o como Proxy Inverso (mediante el mod_proxy), ya que gracias a la caché, podemos evitar que Apache vuelva a pedir el mismo contenido al servidor final repetidas veces. Para un servidor normal, no parece que una caché pueda proporcionarnos una gran mejora de rendimiento, incluso aunque cacheemos en memoria. Sin embargo, si lo combinamos con el mod_deflate, sí que podemos conseguir que los documentos servidos se almacenen ya comprimidos en la caché ahorrándonos así un elevado e inútil uso de la CPU causado por la compresión repetidas veces de lo mismo.

El documento de referencia para conocer cómo Apache cachea a través del mod_cache es el Apache HTTP Server Version 2.2: Caching Guide, y proporciona sus servicios a través de otros tres módulos que dependen de él: mod_disk_cache, mod_mem_cache, mod_file_cache.

El mod_file_cache es el menos flexible de los tres. Sirve para cachear ficheros concretos en disco o memoria, pero si modificamos alguno de dichos ficheros sólo podremos conseguir que Apache sirva la versión actualizada con un reinicio del gestor:

So whenever one of the mapped files changes on the filesystem you have to restart the server. To reiterate that point: if the files are modified in place without restarting the server you may end up serving requests that are completely bogus. You should update files by unlinking the old copy and putting a new copy in place. Most tools such as rdist and mv do this.

El mod_mem_cache nos permite cachear los ficheros en memoria. Sin embargo, lo que teóricamente podría suponer un a gran mejora de rendimiento del servidor, puede quedarse en mucho menos por dos motivos (In-Memory Caching):

  • Por un lado, forzar a Apache a usar una gran cantidad de memoria para cachear los ficheros del sistema puede causar que el sistema se vaya quedando corto de memoria para otras tareas llegando a swapear y causando que el rendimiento caiga en picado.
  • Por otro lado, los sistemas operativos modernos hacen un excelente trabajo cacheando ficheros que se usan frecuentemente, especialmente si hay memoria de sobra. Además, saben perfectamente cuándo el fichero se ha modificado y hay que renovarlo en la caché y saben cuando hay que dejar de cachear porque el sistema se va quedando corto de memoria. ¿Realmente necesitamos que Apache cachée esos mismos ficheros?

El mod_disk_cache va almacenando en un directorio los documentos que se van solicitando, así como sus cabeceras. Si el mod_deflate está activo y hay unos clientes que aceptan compresión y otros que no, se cacheará el documento comprimido y sin comprimir. Por supuesto, no debemos olvidar que la caché del sistema operativo está actuando, así que estos ficheros de caché, si hay memoria suficiente, también serán cacheados en memoria por el kernel.

Por tanto, el mecanismo de caché para almacenar el contenido comprimido que parece más conveniente en muchas situaciones es el del mod_disk_cache y en él nos vamos a centrar en lo sucesivo.

mod_disk_cache

En Debian, podemos habilitar el mod_disk_cache y su dependencia, el mod_cache usando el comando a2enmod (¡gracias Robert!):

# a2enmod disk_cache
Enabling cache as a dependency
Module cache installed; run /etc/init.d/apache2 force-reload to enable.
mod_disk_cache needs configuration before being able to work.
See the comments in /etc/apache2/mods-available/disk_cache.conf
for details.
Module disk_cache installed; run /etc/init.d/apache2 force-reload to enable.

Y podemos ver que se han creado los enlaces necesarios correctamente:

# cd /etc/apache2/mods-enabled/
# ll *cache*
lrwxrwxrwx 1 root root 28 2007-12-02 12:41 cache.load -> ../mods-available/cache.load
lrwxrwxrwx 1 root root 33 2007-12-02 12:41 disk_cache.conf -> ../mods-available/disk_cache.conf
lrwxrwxrwx 1 root root 33 2007-12-02 12:41 disk_cache.load -> ../mods-available/disk_cache.load

Como el propio mensaje nos indica, hay un archivo de configuración para el módulo, el disk_cache.conf, que por defecto lleva los siguientes parámetros:

<IfModule mod_disk_cache.c>
        CacheRoot /var/cache/apache2/mod_disk_cache

#        CacheEnable disk /

        CacheDirLevels 5
        CacheDirLength 3
</IfModule>

Con la directiva CacheRoot indicamos dónde queremos almacenar los ficheros de caché. Con la directiva “CacheEnable disk /” (vemos que está comentada por defecto en Debian) activamos la caché de disco para todos los ficheros (/). Si tenemos varios servidores virtuales, esta directiva deberíamos de ponerla en la configuración del servidor virtual, ya que podemos no querer cachear los contenidos de todos ellos.

Por tanto, ponemos una directiva CacheEnable en la configuración global o en la del servidor virtual y tras reiniciar los procesos, el Apache comenzará a cachear.

El mod_disk_cache almacenará los documentos bajo CacheRoot pero usando una jerarquía de directorios un tanto críptica. Para que no se acumulen un gran número de ficheros en el mismo directorio, lo que penaliza severamente el rendimiento en muchos sistemas de ficheros, crea un árbol de directorios de hasta CacheDirLevels niveles y hasta con CacheDirLenght caracteres para cada nombre de directorios, no debiendo ser CacheDirLevels*CacheDirLength mayor de 20.

Por ejemplo, con CacheDirLevels a 2 y CacheDirLength a 3, tras activar el módulo, si probamos a descargar contenidos con y sin compresión, encontraremos algo similar a esto en el CacheRoot:

# find /var/cache/apache2/mod_disk_cache/
/var/cache/apache2/mod_disk_cache/
/var/cache/apache2/mod_disk_cache/ReF
/var/cache/apache2/mod_disk_cache/ReF/doS
/var/cache/apache2/mod_disk_cache/ReF/doS/rODnhKXGMI6ovV@g.header.vary
/var/cache/apache2/mod_disk_cache/ReF/doS/rODnhKXGMI6ovV@g.header.vary/O17
/var/cache/apache2/mod_disk_cache/ReF/doS/rODnhKXGMI6ovV@g.header.vary/O17/Br7
/var/cache/apache2/mod_disk_cache/ReF/doS/rODnhKXGMI6ovV@g.header.vary/O17/Br7/qI@hwNC5yYk1CAAQ.data
/var/cache/apache2/mod_disk_cache/ReF/doS/rODnhKXGMI6ovV@g.header.vary/O17/Br7/qI@hwNC5yYk1CAAQ.header
/var/cache/apache2/mod_disk_cache/ReF/doS/rODnhKXGMI6ovV@g.header
/var/cache/apache2/mod_disk_cache/QCD
/var/cache/apache2/mod_disk_cache/QCD/rx6
/var/cache/apache2/mod_disk_cache/QCD/rx6/QOskIIubUJNJmygQ.header.vary
/var/cache/apache2/mod_disk_cache/QCD/rx6/QOskIIubUJNJmygQ.header.vary/SCw
/var/cache/apache2/mod_disk_cache/QCD/rx6/QOskIIubUJNJmygQ.header.vary/SCw/3in
/var/cache/apache2/mod_disk_cache/QCD/rx6/QOskIIubUJNJmygQ.header.vary/SCw/3in/PxoVDs1I9YrzsrMw.data
/var/cache/apache2/mod_disk_cache/QCD/rx6/QOskIIubUJNJmygQ.header.vary/SCw/3in/PxoVDs1I9YrzsrMw.header
/var/cache/apache2/mod_disk_cache/QCD/rx6/QOskIIubUJNJmygQ.header
/var/cache/apache2/mod_disk_cache/WV7
/var/cache/apache2/mod_disk_cache/WV7/WLD
/var/cache/apache2/mod_disk_cache/WV7/WLD/hzVrlCKIU3NsSxGg.header.vary
/var/cache/apache2/mod_disk_cache/WV7/WLD/hzVrlCKIU3NsSxGg.header.vary/j3A
/var/cache/apache2/mod_disk_cache/WV7/WLD/hzVrlCKIU3NsSxGg.header.vary/j3A/Zfi
/var/cache/apache2/mod_disk_cache/WV7/WLD/hzVrlCKIU3NsSxGg.header.vary/j3A/Zfi/s1vDfLMvhTrcLz3Q.header
/var/cache/apache2/mod_disk_cache/WV7/WLD/hzVrlCKIU3NsSxGg.header.vary/j3A/Zfi/s1vDfLMvhTrcLz3Q.data
/var/cache/apache2/mod_disk_cache/WV7/WLD/hzVrlCKIU3NsSxGg.header.vary/2gD
/var/cache/apache2/mod_disk_cache/WV7/WLD/hzVrlCKIU3NsSxGg.header.vary/2gD/U0F
/var/cache/apache2/mod_disk_cache/WV7/WLD/hzVrlCKIU3NsSxGg.header.vary/2gD/U0F/0o2XTbizgzSFEtZw.header
/var/cache/apache2/mod_disk_cache/WV7/WLD/hzVrlCKIU3NsSxGg.header.vary/2gD/U0F/0o2XTbizgzSFEtZw.data
/var/cache/apache2/mod_disk_cache/WV7/WLD/hzVrlCKIU3NsSxGg.header.vary/y6D
/var/cache/apache2/mod_disk_cache/WV7/WLD/hzVrlCKIU3NsSxGg.header.vary/y6D/Fls
/var/cache/apache2/mod_disk_cache/WV7/WLD/hzVrlCKIU3NsSxGg.header.vary/y6D/Fls/02CqmIFDXTYoW8Eg.data
/var/cache/apache2/mod_disk_cache/WV7/WLD/hzVrlCKIU3NsSxGg.header.vary/y6D/Fls/02CqmIFDXTYoW8Eg.header
/var/cache/apache2/mod_disk_cache/WV7/WLD/hzVrlCKIU3NsSxGg.header

Vemos que los directorios tienen como máximo dos niveles y tres caracteres para el nombre: ReF/doS, QCD/rx6, WV7/WLD.

Por otro lado, encontramos ficheros .data, que contienen el documento cacheado, y ficheros .header, con las cabeceras que aplican a dicho documento:

Vemos que algunos ficheros de contenido están comprimidos y otros no:

# file /var/cache/apache2/mod_disk_cache/WV7/WLD/hzVrlCKIU3NsSxGg.header.vary/j3A/Zfi/s1vDfLMvhTrcLz3Q.data
/var/cache/apache2/mod_disk_cache/WV7/WLD/hzVrlCKIU3NsSxGg.header.vary/j3A/Zfi/s1vDfLMvhTrcLz3Q.data: ASCII text

# file /var/cache/apache2/mod_disk_cache/WV7/WLD/hzVrlCKIU3NsSxGg.header.vary/y6D/Fls/02CqmIFDXTYoW8Eg.data
/var/cache/apache2/mod_disk_cache/WV7/WLD/hzVrlCKIU3NsSxGg.header.vary/y6D/Fls/02CqmIFDXTYoW8Eg.data: gzip compressed data, from Unix

En función de si el documentos se ha solicitado y cacheado comprimido o sin comprimir:

# cat /var/cache/apache2/mod_disk_cache/WV7/WLD/hzVrlCKIU3NsSxGg.header.vary/j3A/Zfi/s1vDfLMvhTrcLz3Q.header
http://www.example.com/style.css?Last-Modified: Mon, 26 Nov 2007 21:40:38 GMT
ETag: "1bb8b0-105-d0a98180"
Accept-Ranges: bytes
Content-Length: 261
Vary: Accept-Encoding
Date: Sun, 02 Dec 2007 12:12:51 GMT
Expires: Mon, 03 Dec 2007 01:40:04 GMT
Content-Type: text/css

User-Agent: Wget/1.10.2
Accept: */*
Host: www.example.com

# cat /var/cache/apache2/mod_disk_cache/WV7/WLD/hzVrlCKIU3NsSxGg.header.vary/y6D/Fls/02CqmIFDXTYoW8Eg.header
http://www.example.com/style.css?Last-Modified: Mon, 26 Nov 2007 21:40:38 GMT
ETag: "1bb8b0-105-d0a98180"
Accept-Ranges: bytes
Vary: Accept-Encoding
Content-Encoding: gzip
Date: Sun, 02 Dec 2007 12:12:45 GMT
Expires: Mon, 03 Dec 2007 01:39:58 GMT
Content-Type: text/css

User-Agent: Wget/1.10.2
Accept: */*
Host: www.example.com
Accept-Encoding: gzip

En la documentación del mod_cache encontramos información sobre los parámetros que afectan al mantenimiento de la caché.

También tenemos que tener en muy cuenta qué se puede cachear y qué no se puede/debe cachear, algo que podemos consultar en la sección “What Can be Cached?” de “Caching Overview”.

Y finalmente, comentar que la caché puede ser una fuente importante de problemas de seguridad que no hay que dejar de tener en mente.

Traceando el mod_cache

Si queremos ver que efectivamente el mod_cache está haciendo su trabajo, además de examinar los ficheros de CacheRoot, podemos poner el LogLevel a debug:

ErrorLog /var/log/apache2/error.log

# Possible values include: debug, info, notice, warn, error, crit,
# alert, emerg.
LogLevel debug

Tras reiniciar podremos ver en el fichero de log que tengamos definido (directiva ErrorLog) la entrada de un documento en la caché cuando se accede a él por primera vez:

[Sun Dec 02 14:05:28 2007] [debug] mod_cache.c(129): Adding CACHE_SAVE filter for /style.css
[Sun Dec 02 14:05:28 2007] [debug] mod_cache.c(136): Adding CACHE_REMOVE_URL filter for /style.css
[Sun Dec 02 14:05:28 2007] [debug] mod_deflate.c(447): [client 127.0.0.1] Zlib: Compressed 13904 to 2557 : URL /style.css
[Sun Dec 02 14:05:28 2007] [debug] mod_cache.c(602): cache: Caching url: /style.css
[Sun Dec 02 14:05:28 2007] [debug] mod_cache.c(608): cache: Removing CACHE_REMOVE_URL filter.
[Sun Dec 02 14:05:28 2007] [debug] mod_cache.c(651): cache: Added date header
[Sun Dec 02 14:05:28 2007] [debug] mod_disk_cache.c(954): disk_cache: Stored headers for URL http://www.example.com:80/style.css?
[Sun Dec 02 14:05:28 2007] [debug] mod_disk_cache.c(1043): disk_cache: Body for URL http://www.example.com:80/style.css? cached.

Y cuando el documento es servido desde la caché porque ya se ha cacheado anteriormente:

[Sun Dec 02 14:06:26 2007] [debug] mod_disk_cache.c(477): disk_cache: Recalled cached URL info header http://www.example.com:80/style.css?
[Sun Dec 02 14:06:26 2007] [debug] mod_disk_cache.c(750): disk_cache: Recalled headers for URL http://www.example.com:80/style.css?
[Sun Dec 02 14:06:26 2007] [debug] mod_cache.c(261): cache: running CACHE_OUT filter
[Sun Dec 02 14:06:26 2007] [debug] mod_cache.c(275): cache: serving /style.css

Limpiando la caché con htcacheclean

El mod_disk_cache va limpiando de la caché las entradas que han expirado, pero no gestiona un límite máximo de espacio en disco que se puede usar para la caché. Para evitar que crezca excesivamente, podemos usar la herramienta htcacheclean. Viene en el paquete apache2-utils y le especificamos con -p el directorio de caché y con -l el límite de espacio que puede usar la caché:

# htcacheclean -v -t -p/var/cache/apache2/mod_disk_cache -l10M
Statistics:
size limit 10.0M
total size was 40.0K, total size now 40.0K
total entries was 16, total entries now 16

Lo normal es ejecutarlo periódicamente desde el cron o con la opción -D para que se quede funcionando como un demonio. Con ello logramos contener el tamaño de la caché al valor deseado, tal y como muestra esta gráfica de la Caching Guide:

htcacheclean

¿Qué pasa con las URLs acabadas en /?

Cuando a un servidor web le solicitamos un directorio en vez de un fichero en concreto como por ejemplo http://www.example.com/ o http://www.example.com/directorio/ en vez de http://www.example.com/index.html o http://www.example.com/directorio/index.html, Apache nos devolverá uno de los ficheros especificados con la directiva DirectoryIndex del módulo mod_dir, por defecto el index.html, si existe alguno de ellos. En caso contrario, Apache nos sacará un listado del sistema de ficheros si hemos especificado la directiva “Option Indexes” y tenemos el módulo mod_autoindex habilitado o un error “403 Forbidden” si no tenemos ni una cosa ni otra.

En Debian, el valor por defecto de la directiva DirectoryIndex es:

# cat /etc/apache2/mods-available/dir.conf
<IfModule mod_dir.c>

          DirectoryIndex index.html index.cgi index.pl index.php index.xhtml

</IfModule>

Pues bien, curiosamente veo que el mod_cache no cachea la página cuando la URL es de un directorio. Por ejemplo, si ponemos en el navegador una URL como http://www.example.com/directorio/index.html el documento se cachea bien:

[Sun Dec 02 16:34:11 2007] [debug] mod_cache.c(129): Adding CACHE_SAVE filter for /directorio/index.html
[Sun Dec 02 16:34:11 2007] [debug] mod_cache.c(136): Adding CACHE_REMOVE_URL filter for /directorio/index.html
[Sun Dec 02 16:34:11 2007] [debug] mod_cache.c(602): cache: Caching url: /directorio/index.html
[Sun Dec 02 16:34:11 2007] [debug] mod_cache.c(608): cache: Removing CACHE_REMOVE_URL filter.
[Sun Dec 02 16:34:11 2007] [debug] mod_cache.c(651): cache: Added date header
[Sun Dec 02 16:34:11 2007] [debug] mod_disk_cache.c(954): disk_cache: Stored headers for URL http://www.example.com:80/directorio/index.html?
[Sun Dec 02 16:34:11 2007] [debug] mod_disk_cache.c(1043): disk_cache: Body for URL http://www.example.com:80/directorio/index.html? cached.

Sin embargo, si pedimos una URL como http://www.example.com/directorio/ que en realidad nos devuelve lo mismo, el index.html, no se cachea nada:

[Sun Dec 02 16:37:29 2007] [debug] mod_cache.c(129): Adding CACHE_SAVE filter for /directorio/
[Sun Dec 02 16:37:29 2007] [debug] mod_cache.c(136): Adding CACHE_REMOVE_URL filter for /directorio/

He intentado entender por qué ocurre esto, sin haber llegado a encontrar respuestas claras. Por ejemplo, en el 2004, en la lista de distribución de Apache, hablaban de que el Apache 2.0 tenía código a propósito para evitar que se cachearan URLs acabadas en / (mod_cache: allowing urls ending in “/” to be cached ), pero en Apache 2.2 dicho código ya no existe:

/* DECLINE urls ending in / ??? EGP: why? */
if (url[urllen-1] == '/') {
return DECLINED;
}

También encuentro un bug de Ubuntu en el que un usuario cuenta más o menos el mismo problema: mod cache doesn’t cache due to internal redirects y una pregunta sin responder en un foro que refleja exactamente mi perplejidad con el tema: [users@httpd] mod_cache behaviour.

Puestos a sospechar del paquete específico de Debian, he probado incluso en una Red Hat ES 4.0 con exactamente el mismo resultado.

¿Alguien sabe el motivo de que las URLs de directorios no se cacheen?

Conclusión

El mod_cache y mod_disk_cache combinados con el mod_deflate nos permiten mantener los contenidos comprimidos cacheados de forma que no sea necesario comprimirlos una y otra vez. Sin embargo, aunque todo esto aplica a la perfección a contenidos estáticos, con contenidos dinámicos como por ejemplo los que puede generar WordPress, todo esto hay que tratarlo de forma diferente. Lo veremos en la siguiente entrada: Comprimir y cachear las páginas generadas por WordPress

:wq

Entradas relacionadas

Tema LHYLE09, creado por Vicente Navarro