Consejos para acelerar la descarga de páginas web - PARTE I
La velocidad de carga de las páginas web viene determinada por muchos factores, pero los más importantes son la combinación de la latencia y el número de recursos a descargar, y por otro lado el ancho de banda y el tamaño de estos recursos. Como programadores, lo único que podemos hacer para disminuir la latencia entre nuestros servidores y nuestros usuarios es colocar los servidores lo más cerca posible de nuestros usuarios, o distribuirlos por todo el planeta si nuestra página web no tiene un uso mayoritario en un solo país. Pero estas soluciones suelen ser muy caras y no están al alcance de todo el mundo.
En la práctica, la latencia y el ancho de banda del usuario lo tendremos que tomar como elementos incontrolables. Como el ancho de banda ha ido creciendo exponencialmente desde hace unos años, este elemento ya no tiene la importancia tan crítica que tenía hace tan solo unos años, y el tiempo de espera debido a la latencia y el número de recursos a descargar ha cobrado más importancia. Veamos pues, en detalle, el proceso de descarga de una página web, y por qué afecta tanto la latencia al tiempo total de descarga.
Cuando el navegador tiene que mostrar una página web nueva, digamos http://www.ejemplo.com/, primero tiene que convertir el nombre del dominio a una dirección IP con la que poder conectar, asi que contacta con los DNS de nuestro ISP para conseguir esta dirección IP. Con la dirección IP en su mano, intenta establecer una conexión TCP con ese servidor, siguiendo el proceso conocido como “three-way handshake”. El navegador envía un paquete SYN y espera pacientemente la respuesta del servidor, que será un paquete SYN + ACK. Cuando el navegador reciba el paquete respuesta del servidor ya puede mandar un paquete ACK de respuesta e inmediatamente lanzar una petición HTTP GET del recurso que nos interesa (en nuestro ejemplo, “/”), a lo que el servidor responderá, por ejemplo, mandando el contenido del fichero “index.html”.
Si el tiempo que tarda un paquete en llegar de nuestro navegador al servidor es de 100 ms, necesitaremos 400 ms debido a la latencia antes de que el navegador consiga el primer byte de contenido real, y ya podemos tener una flamante conexión a 20 Mb/s que no reducirá nada este tiempo de espera. A partir de aquí, y hasta que el navegador termina de recibir “index.html” pasará un tiempo dominado por el tamaño de este fichero y el ancho de banda de que dispongamos. Si “index.html” pesa 100 KB y tenemos una conexión de 4 Mb/s tardará unos (100 * 8) Kb / (4 * 1024) Kb/s * 1.2 = 235 ms. El factor “mágico” de 1.2 tiene cuenta de la sobrecarga típica de datos a enviar debido a la encapsulación de estos datos en paquetes.
Una vez con el contenido del HTML, el navegador procede a interpretarlo y, típicamente, encontrará dentro del HTML referencias a otros recursos, como hojas de estilo CSS, Javascript, imágenes, etc. Para cada uno de estos recursos extra se repite todo el proceso anterior, asi pues si hacemos referencia a 10 recursos, un número muy conservador hoy en día, perderíamos 10 * 400 ms = 4 s extra debido a la latencia.
Afortunadamente el navegador paraleliza estas peticiones, pero para no sobrecargar al servidor no hace más de dos peticiones a la vez al mismo dominio. Mención especial merecen los ficheros Javascript, que no se descargan en paralelo. Si en nuestro HTML no usamos ningún fichero Javascript, los 10 recursos añadirán entonces 10 * 400 ms / 2 = 2 s extra.
¿Cómo podemos “engañar” al navegador para que descargue más ficheros en paralelo de nuestro servidor? Hay un truco muy sencillo, que es crear varios subdominios que apunten al mismo servidor, y dividir el enlace en el HTML a nuestros recursos entre estos subdominios. El navegador empieza a descargar en paralelo un máximo de 2 recursos por subdominio, sobre un máximo de 4 dominios simultáneamente. Haciendo esto el tiempo perdido estableciendo las conexiones TCP y empezando a descargar los recursos será de 0.8 s (400 ms para establecer las primeras 8 conexiones + 400 ms para las 2 últimas conexiones).
Hay una forma aún más sencilla de reducir este tiempo de espera. Si el servidor no cierra la conexión TCP con el cliente en cada petición HTTP, usando KeepAlives, dividimos por dos el efecto de la latencia en cada recurso que se descargue en una conexión ya abierta. El efecto de KeepAlives depende del número de subdominios que tengamos, si lo servimos todo desde el mismo dominio, en lugar de los 4 s anteriores tardaremos 400 ms para establecer las 2 primeras conexiones en paralelo + 200 ms por cada uno de los otros 8 recursos a bajar / 2 (número de recursos que se descargan en paralelo) = 400 + 200 * 4 = 1.2 s. Sobre 4 subdominios tendremos una espera usando KeepAlives de 0.6 s, ya que los 2 últimos recursos se descargan sin necesidad de establecer otra vez la conexión TCP.
Hay que tener en cuenta que en algunos servidores web la activación de KeepAlives tiene un impacto sobre el rendimiento muy significativo. Os recomiendo utilizar un servidor web alternativo, como lighttpd, nginx o cherokee al menos para vuestro contenido estático, con KeepAlives activado.
Un cambio que puede tener un efecto significativo en el tiempo de descarga es concatenar todos los ficheros CSS a descargar en un solo fichero (o 2, uno general al sitio y otro particular a la página), concatenar los ficheros Javascript, e incluso concatenar las imágenes, seleccionando la subimagen a mostrar usando CSS Sprites. Así podremos reducir significativamente el número de recursos a descargar.
Otro de los factores que sorprendentemente puede influir en el tiempo de descarga es el tamaño de las peticiones que hace el navegador. Cuando el navegador le pide al servidor una página, le envía también información extra, como el nombre del navegador, el idioma preferido por el usuario, las cookies, etc. Muchos de estos datos (como las cookies) no suelen influir en el resultado devuelto por el servidor para los ficheros CSS, JS o imágenes, y si nuestras cookies son cross-subdomain podemos evitar que el navegador tenga que mandarlas si colocamos los ficheros estáticos en otro dominio. Yahoo! por ejemplo usa el dominio yimg.com para servir este tipo de ficheros. A pesar de que las cookies son relativamente pequeñas, hemos de recordar que típicamente los usuarios navegan con conexiones asimétricas, donde la velocidad de subida puede ser 50 veces inferior a la velocidad de bajada de datos.
Una vez aplicados estos cambios en nuestras páginas, podremos concentrarnos en la segunda parte de mayor influencia en la velocidad, el tamaño de los recursos a descargar, y las distintas formas en que podemos influenciar la caché del navegador para evitar tener que descargar todo cada vez que un usuario llegue a nuestra página. Pero esa es otra historia de la que hablaremos otro día.