28 de Junio, 2010

Acelerando la web con la caché

Desde que cambiara el blog, he estado experimentando con distintas ideas para mejorar el rendimiento de cualquier web (aunque esta ya va bien, ¿no?).

Hace unos años hablaba de respuestas HTTP condicionales, y lo interesante que era:

  1. El cliente nos pregunta: ¿ha cambiado el contenido desde la última vez que lo visité?
  2. El servidor contesta:
    • Sí, y se modificó en tal fecha, entregando el contenido completo.
    • No, y devolvemos una respuesta 304, entregando solo unos bytes de cabeceras HTTP.

Esto es un ahorro considerable de ancho de banda, porque no enviamos el contenido cada vez, y de CPU, porque comprobar las fechas es menos costoso que generar el contenido si éste es dinámico.

En el caso del RSS el ahorro es además mayor, porque son contenidos que se piden periódicamente:

  425 suscriptores (calculado por Google Webmaster Tools)
x   1 petición cada hora (muchas veces es menos tiempo)
x  68 KB de tamaño del RSS (actual)
=  20 MB al día

Teniendo en cuenta que puedo tardar incluso una semana en actualizar la bitácora, eso supone desperdiciar 0.5 GB (aprox) al mes enviando contenido que no ha cambiado en una bitácora con tan pocos lectores como esta :P.

Pero esta técnica es complicada para aplicarla a cualquier contenido, y puede condicionar mucho el diseño de nuestra aplicación, así que muchas veces será suficiente con saber controlar la gestión de la caché y olvidarnos del asunto.

¿Que es una caché?

Es un mecanismo mediante el cual se mejora la transferencia de información. Se guardan datos de una petición de forma que, en el futuro, si la petición se repite, se puedan entregar esos datos de una forma más rápida.

En el caso de la web, se reduce el tiempo de espera y el consumo de recursos de red, con lo que generalmente es positivo.

El único problema es que tenemos que estar seguros de que la caché no afecta a la frescura de los datos (recordaremos los problemas que hubo hace años con el infame proxy caché de Telefónica).

La idea es emplear las cabeceras HTTP adecuadas, para indicar que el contenido dinámico que estamos sirviendo no va a cambiar en cierto tiempo.

En mi caso esto es perfecto para las anotaciones con más de 15 días, que automáticamente tienen los comentarios cerrados. Es decir: son en la práctica anotaciones estáticas (salvo por la columna de la derecha, pero es algo con lo que podemos vivir sin problemas :P).

El protocolo que he seguido yo para esas anotaciones estáticas es el siguiente:

  • Indicar que se puede cachear el contenido y por cuanto tiempo, usando la cabecera Cache-Control:
    Cache-Control: max-age=3600, must-revalidate

    He decidido que esa vista se guarde por 3600 segundos, aunque podría ser más tiempo.

  • Indicar un critero de validación, usando la cabecera Last-Modified, con lo que el gestor de la caché sabrá cuándo cambió por última vez el contenido (guardo un campo updated con esa información por cada anotación).

En principio parece sencillo, ¿no? Pues no lo es tanto, porque además hay que tener en cuenta que:

  • Una petición que incluya autenticación (o HTTPS), no será cacheada (lógico).
  • Una petición que incluya una cookie, no será cacheada (o una página podría acabar en el usuario que no corresponde :D).

Al final es por culpa de las cookies que todo se complica, y hay que darle una vuelta para evitar que se pierda la eficiencia de la caché.

Las cookies en realidad son menos necesarias de lo que imaginamos, y se suele abusar de ellas. En esta bitácora no es así, pero utilizo las medidas anti-XSRF que trae Tornado, porque resultan bastante adecuadas para para mucho spam automatizado; y eso conlleva el envío invariablemente de una cookie :(.

Después de pelearme con el framework, he añadido a tornado.web.RequestHandler un método que me permite evitar el envío de cookies una vez que detecto que se trata de una página estática:

def reset_cookies(self):
        """Reset the cookies before setting the headers"""
        if hasattr(self, "_new_cookies"):
            self._new_cookies = []

Esto no es perfecto, porque el usuario puede navegar por una página que tenga formulario y, por lo tanto, la cookie anti-XSRF sea necesaria. Si luego visita una página que consideramos estática, no podrá aprovechar el caché. Pero aún con esa pega, hay un beneficio claro cuando un usuario llega desde Google, por ejemplo, a una anotación vieja.

¿Dónde se usa la caché?

Básicamente hay tres tipos de caché web:

  • En el cliente: es la que todos los usuarios utilizan, y es con la que jugábamos cuando devolvíamos el 304 en las peticiones al RSS.
  • En un proxy HTTP: como por ejemplo SQUID, que actúa como intermediario entre los servidores y el cliente, acelerando el proceso gracias a cachear información de forma eficiente. Lo encontraremos sobretodo en organizaciones, y algunas veces de forma transparente para el usuario (como aquel de Telefónica).
  • En un proxy HTTP inverso: como el mismo SQUID configurado de forma especial, o una solución dedicada como Varnish, que se encarga de mejorar el rendimiento añadiendo una caché intermedia entre nuestro servidor y los clientes.

Como los dos primeros no dependen de nuestro servidor, he decidido implementar el tercer caso y ponder un proxy inverso para ver cómo se comporta.

En estos momentos Cherokee no soporta caché en el proxy HTTP (por ahora, y es lo único que no se beneficia de su magnífica caché), así que lo he implementado con Varnish, y los resultados me están gustando mucho :P.

Pero por hoy creo que ya está bien. Más adelante, cuando tenga datos, explicaré cómo he desplegado Varnish para acelerar más todavía esta bitácora gracias a la caché ;).

Anotación por Juan J. Martínez, clasificada en: blog, cache.

Los comentarios están cerrados: los comentarios se cierran automáticamente una vez pasados 30 días. Si quieres comentar algo acerca de la anotación, puedes hacerlo por e-mail.

Algunas anotaciones relacionadas: