Sunday, April 06, 2008

Consejos para acelar las páginas web - Parte II

Ya hemos hablado de la importancia de la latencia en el tiempo de descarga de las páginas web. Reducir el número de "three-way handshakes" activando KeepAlives, el número de ficheros a descargar, paralelizarlos sobre un máximo de 4 dominios, etc. son consejos importantes para reducir el impacto de la latencia, pero hay algo que sobrepasa a cualquier otra técnica para reducir el tiempo de espera cuando queremos usar un recurso (imágen, hoja de estilo, javascript, etc.), y es no mandarle nada al navegador, siempre que este tenga en caché una copia del recurso en cuestión.

El navegador divide los recursos que tiene en su caché en dos tipos, los "frescos" y los "dudosos". Los recursos frescos pueden usarse sin preguntarle nada al servidor. Los recursos dudosos se pueden usar, pero antes hay que preguntarle al servidor que nos lo envió si sigue siendo válido, o si hay que tirarlo a la basura y usar una nueva versión que el servidor le mandará al navegador.

¿Cómo controlar si un recurso entra en la caché, y si lo hace el tiempo qué permanece fresco? Con las cabeceras Cache-Control o Expires. La cabecera Expires contiene una fecha a partir de la cual el recurso debe ser considerado como dudoso. Si la fecha está en el pasado, o si no es una fecha (como "0"), se considerará inmediatamente al recurso como dudoso. La cabecera Cache-Control ofrece muchas más opciones, permitiendo por ejemplo especificar si un proxy que se encuentre entre el usuario y el servidor puede guardar el recurso en caché o si sólo el navegador puede hacerlo. Podéis leer la especificación para ver todas las subopciones de Cache-Control, siendo una de las más importantes max-age, que especifica durante cuantos segundos se tiene que considerar el recurso como fresco. Si se usa Cache-Control, toma prioridad sobre Expires.

Recordemos que si un recurso es fresco, el navegador lo usa directamente sin decirle nada al servidor. Más rápido imposible.

Si el recurso es dudoso, ¿cómo puede el navegador comprobar si ha cambiado en el servidor o no? La pregunta, en prácticamente todos los casos es completamente irrelevante. Es mejor que vuestros recursos siempre se mantengan frescos, poniendo un Expires o un Cache-Control max-age de muchos años. O están en la caché y pueden usarse directamente, o no están y hay que pedírselos al servidor.

¿Por qué muchas páginas los dejan pasar al estado "dudoso" (poniendo pequeños tiempos de expiración)? Porque quieren que si cambian el recurso, el navegador se baje la nueva versión. Esto es extremadamente problemático, porque en la práctica es imposible predecir cuando se va a cambiar un recurso, asi que no le podemos decir al navegador hasta cuando lo puede mantener fresco, o nos arriesgamos a que el navegador use una versión antigua de algunas de nuestras imágenes, páginas de estilo, javascript, etc.

Tenemos tres alternativas para atacar este problema:


  1. Ignorarlo. Es lo que hacen todos los que ponen un tiempo corto en Expires. Es incorrecto porque el cliente puede ver una nueva versión de nuestra página web con una mezcla de recursos antiguos y nuevos. Es ineficiente, porque hace que el cliente le haga una petición al servidor para comprobar la validez del recurso una vez caduca, requiriendo un mínimo de una ida y vuelta al servidor, y más típicamente dos idas y vueltas (ver el artículo anterior sobre la importancia de la latencia)

  2. Poner el Expires a 0. Es correcto, ya que el cliente siempre ve la última versión del recurso. Es ineficiente, ya que siempre es necesario comprobar la validez del recurso.

  3. Nunca cambiar un recurso. Más especificamente, cuando quieras crear una versión nueva de un recurso, no le des el mismo nombre que otro recurso ya existente en tu página web. Es correcto, ya que la versión en la caché y en el servidor de un recurso siempre es la misma. Es eficiente, ya que si se sigue usando ese recurso y está en la caché, el navegador nunca le pregunta al servidor.



La última opción es obviamente la ideal, y la que más trabajo necesita por parte del desarrollador, ergo es la opción menos usada. En realidad no es tan dificil de implementar. En lugar de poner en el HTML enlaces como "imagen.png", pondremos un enlace llamado "imagen.png?v=1". El servidor evidentemente ignorará el "?v=1", pero el navegador eso no lo sabe. Cuando actualicemos "imagen.png", cambiamos el número de versión, y en el HTML ponemos "imagen.png?v=2". Asi pues, en el mismo instante en que tengamos una versión nueva de "imagen.png" el navegador tendrá que pedírsela al servidor (él no tiene en su caché nada sobre "imagen.png?v=2"). Si queremos aumentar la legibilidad, o evitar que algún proxy no cachée nuestra imagen por usar un ? en la URL, podemos escribir imagen.v2.png, y eliminar el .v2 con mod_rewrite en Apache. Con un poco de esfuerzo, todo el proceso puede automatizarse. En Panoramio por ejemplo tenemos una tabla con el MD5 de todos los recursos estáticos qué usamos, y su número de versión (excepto para las imágenes que suben los usuarios). Cuándo subimos una nueva versión de Panoramio, un script comprueba el MD5 de todos los recursos, y si este cambia, aumentamos su número de versión. Todas las URLs a nuestros recursos las generamos a través de una función que le añade a la URL el número de versión que hemos calculado anteriormente. Todo esto se basa en que nuestro HTML no debe de estar cacheado, o los clientes puede recibir una versión antigua del HTML con una versión nueva de algunos recursos.

Si teneis algún recurso para el que este tipo de solución no es práctica, entonces tendreis que dejar que vuestros recursos pasen al estado dudoso (o no cachearlo en absoluto). Para comprobar si un recurso sigue siendo válido, el navegador le pregunta al servidor si ese recurso ha cambiado desde que se metió en la caché, usando la cabecera If-Modified-Since. Si no ha cambiado, el servidor responde con HTTP/304 Not Modified, y si ha cambiado responde enviando el nuevo contenido.

Si nuestro recurso no tiene una fecha de modificación clara (por ejemplo, si se ha generado dinámicamente), este método de verificación no nos servirá. Si ese es nuestro caso, al enviar el recurso la primera vez tendremos que añadir la cabecera ETag, que contendrá un identificador único que deberá cambiar cuando cambie el recurso. Los ETag que tanto Apache como IIS generan no dependen exclusivamente del contenido del fichero, si no que también dependen de datos especificos del servidor (por ejemplo, el inode donde se encuentra el fichero). Si usamos varios servidores, este ETag será inutilizable. En la práctica hay muy pocas razones para usar un ETag, siendo la validación por fecha suficiente en la mayoría de los casos.

En el anterior artículo de esta serie preguntasteis por herramientas para poder medir el tiempo de descarga de páginas web. Una formidable es Fiddler, desgraciadamente sólo disponible para Windows. Especificamente para Firefox tenemos firebug, sin duda el plugin de desarrollo más útil que existe para Firefox, y que incluye un panel donde podemos ver el tiempo que tarda cada petición al servidor, pero ¡cuidado al interpretar los resultados!, firebug muestra cuando las peticiones entran en la cola de peticiones de Firefox, no cuando se despacha la petición realmente al servidor.

El autor de Fiddler tiene un artículo donde habla también sobre el rendimiento de páginas web, y donde explica con algo más de detalle las diferentes cabeceras involucradas en todo el proceso de cacheo.

Queda bastante que hablar de este tema, sobre la compresión de los recursos en el servidor, y sobre todo de los distintos bugs en servidores y clientes que le ponen un poco de pimienta a la vida, pero ya es tarde. ¡Hasta la próxima!

1 comment:

Crystian said...

muy interesante, gracias!