Página 2 de 82

NGINX: Reducir tiempo de carga

En este artículo veremos como realizar dos optimizaciones básicas que podemos aplicar fácilmente a la instalación que por defecto se realiza de NGINX en un sistema operativo Linux. Concretamente veremos como:

  1. Utilizar compresión gzip para enviar los archivos
  2. Controlar la caché del navegador

Estas dos optimizaciones, bastante sencillas de realizar, pueden reducir considerablemente el tiempo de carga de nuestra web. Para los siguientes pasos se asume que tienes una instalación «por defecto» de nginx en un sistema Linux y que tienes acceso al shell con un usuario que puede ejecutar comandos mediante sudo.

nginx-tiempo-carga

Crear archivos de prueba

Antes de comenzar, crearemos algunos archivos de prueba en el sitio por defecto de nginx suficientemente grandes como para realizar nuestras pruebas. Los archivos de la web están ubicados en /var/www/html/, por tanto los crearemos allí, utilizando un tamaño mínimo de 1k, ya que en la configuración que vamos a realizar, nginx no comprimirá ningún archivo que sea muy pequeño.

Hay que tener en cuenta cómo nginx determina el tipo de archivo. En lugar de analizar todo el contenido del archivo, lo que podría llevar demasiado tiempo para una respuesta rápida por parte del servidor web, nginx simplemente utiliza la extensión del archivo para determinar el tipo MIME y averiguar el propósito del archivo. Esto nos permite utilizar un archivo con cualquier tipo de contenido para realizar nuestras pruebas sin necesidad de crear archivos con contenido real. Para ello utilizaremos el comando truncate que nos permite expandir o comprimir el tamaño de un archivo. Haremos esto para un archivo html, una hoja de estilos, un archivo javascript y una imagen:

$ sudo truncate -s 1k /var/www/html/test.html
$ sudo truncate -s 1k /var/www/html/test.jpg
$ sudo truncate -s 1k /var/www/html/test.css
$ sudo truncate -s 1k /var/www/html/test.js

Habilitar compresión gzip en nginx

El primer paso será comprobar cuál es el comportamiento inicial de nuestro servidor. Para ello usamos curl con la opción -I para recuperar la información de uno de los archivos de test

$ curl -H "Accept-Encoding: gzip" -I http://localhost/test.html

HTTP/1.1 200 OK
Server: nginx/1.10.0 (Ubuntu)
Date: Fri, 20 Jan 2017 18:58:30 GMT
Content-Type: text/html
Content-Length: 1024
Last-Modified: Fri, 20 Jan 2017 18:57:18 GMT
Connection: keep-alive
ETag: "5881cece-8b11"
Accept-Ranges: bytes
Content-Encoding: gzip

¡Bien! Nuestro servidor usa la compresión gzip por defecto para los archivos html. Pero… qué pasa con los css, js…

$ curl -H "Accept-Encoding: gzip" -I http://localhost/test.css

HTTP/1.1 200 OK
Server: nginx/1.10.0 (Ubuntu)
Date: Fri, 20 Jan 2017 18:59:42 GMT
Content-Type: text/css
Content-Length: 1024
Last-Modified: Fri, 20 Jan 2017 18:57:19 GMT
ETag: "5881cfaa-45a"
Connection: keep-alive
Accept-Ranges: bytes

¡Vaya! parece que este tipo de archivos se envía sin comprimir.

Afortunadamente, nginx ya tiene las líneas de configuración por defecto, pero están comentadas. Vamos a editar el archivo de configuración ubicado en /etc/nginx/nginx.conf para aplicar la configuración:

$ sudo vim /etc/nginx/nginx.conf

Buscamos la sección de configuración de gzip y quitamos la marca de comentario (#) que quedaría así:

[...]
##
# Gzip Settings
##

gzip on;
gzip_disable "msie6";

gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
[...]

Recargamos la configuración de nginx y volvemos a probar con el archivo css:

$ sudo systemctl reload nginx
$ curl -H "Accept-Encoding: gzip" -I http://localhost/test.css

HTTP/1.1 200 OK
Server: nginx/1.10.0 (Ubuntu)
Date: Fri, 20 Jan 2017 19:01:14 GMT
Content-Type: text/css
Content-Length: 1024
Last-Modified: Fri, 20 Jan 2017 18:57:19 GMT
Connection: keep-alive
ETag: "5881cfaa-45a"
Accept-Ranges: bytes
Content-Encoding: gzip

Ahora sí, nuestro servidor también envía comprimidos los archivos css. En realidad, hace lo mismo con todos los tipos de archivo definidos en la configuración gzip_types. Podemos notar que, en la lista de tipos de archivo, no se incluyen las imágenes. Esto es debido a que los archivos jpg o png que normalmente se utilizan en la web ya utilizan algún método de compresión, lo que hace innecesario volver a comprimir con gzip (usaríamos recursos del servidor para no obtener mejora visible en el tamaño de archivo).

Ya que hablamos de uso de recursos del servidor, vamos a limitar el tamaño de los archivos que serán comprimidos por nginx, ya que a los archivos muy pequeños no merece la pena aplicar la compresión, incluso el archivo resultante podría ser de un tamaño mayor. Para ello agregamos la configuración gzip_min_length con un valor de 256 bytes.

[...]
##
# Gzip Settings
##

gzip on;
gzip_disable "msie6";

gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
gzip_min_length 256;
[...]

Recargamos el servicio para aplicar la configuración:

$ sudo systemctl reload nginx

El siguiente paso corresponde a la caché del navegador.

Controlar la cache del navegador

De nuevo comenzaremos comprobando el comportamiento por defecto de nuestra instalación mínima de nginx:

$ curl -H "Accept-Encoding: gzip" -I http://localhost/test.css

HTTP/1.1 200 OK
Server: nginx/1.10.0 (Ubuntu)
Date: Fri, 20 Jan 2017 19:11:14 GMT
Content-Type: text/css
Content-Length: 1024
Last-Modified: Fri, 20 Jan 2017 18:57:19 GMT
Connection: keep-alive
ETag: "5881cfaa-45a"
Accept-Ranges: bytes
Content-Encoding: gzip

Si nos fijamos en el resultado, podemos ver la cabecera ETag que contiene un valor único para identificar cada archivo solicitado. Un navegador web puede almacenar este valor y solicitar información sobre los cambios del archivo relacionado. Como no podía ser de otra manera, podemos hacerlo también con curl:

$ curl -I -H 'If-None-Match: "5881cfaa-45a"' http://localhost/test.html

HTTP/1.0 304 Not Modified
Server: nginx/1.10.0 (Ubuntu)
Date: Fri, 20 Jan 2017 19:12:23 GMT
Last-Modified: Fri, 20 Jan 2017 08:51:54 GMT
ETag: "5881cfaa-45a"
Connection: keep-alive

En este caso podemos comprobar que el servidor nos informa de que el archivo no ha sido modificado y que podemos usar la versión guardada en la caché del navegador.

Esto supone una mejora con respecto a descargar cada vez el archivo, pero, aún así, supone una consulta al servidor web, proceso que requiere un tiempo que, aunque pueda parecer pequeño, es importante, sobre todo si hablamos de muchas consultas.

La petición puede ser evitada informando al navegador de que no consulte el archivo hasta un tiempo después de haberlo almacenado en su caché. Esto se realiza mediante las cabeceras Expires o Cache-control. nginx permite la configuración de estos valores a través del módulo ngx_http_headers_module que viene en la instalación por defecto.

Estos valores se configuran para cada sitio, según se muestra en el siguiente ejemplo:

# Mapa para el cache del navegador
map $sent_http_content_type $expires {
default off;
text/html epoch;
text/css max;
application/javascript max;
~image/ max;
}

# Default server configuration
#
server {
listen 80 default_server;
listen [::]:80 default_server;

expires $expires;

root /var/www/html;

# Add index.php to the list if you are using PHP
index index.html index.htm index.nginx-debian.html;

server_name _;

location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
}

}

Primero agregamos un mapa que permite especificar el tipo de archivos que queremos que el navegador cachee:

  • default off: para cualquier tipo de archivo que no especifiquemos ningún comportamiento, no se incluye ninguna cabecera.
  • text/html epoch: con el valor epoch le decimos al navegador que pregunte antes de descargar el archivo.
  • text/css max y application/javascript max: tanto para los archivos de estilo como los scripts, establecemos el valor máximo posible.
  • ~image/ max: también indicamos al navegador que utilice el tiempo máximo para todos los archivos que contengan image/ en el tipo MIME del archivo (por ejemplo, image/jpg).

Una vez definido el comportamiento hay que incluirlo en la configuración del sitio, esto se realiza con la línea expires $expires.

Para que la nueva configuración tenga efecto es necesario reiniciar el servicio nginx:

$ sudo service nginx restart

Si solicitamos de nuevo el archivo observamos dos nuevas cabeceras: Expires y Cache-control.

$ curl -I http://localhost/test.html

HTTP/1.1 200 OK
Server: nginx/1.10.0 (Ubuntu)
Date: Fri, 20 Jan 2017 19:32:49 GMT
Content-Type: text/css
Content-Length: 1024
Last-Modified: Fri, 20 Jan 2017 18:51:54 GMT
Connection: keep-alive
ETag: "5881cfaa-45a"
Expires: Thu, 31 Dec 2037 23:55:55 GMT
Cache-Control: max-age=315360000
Accept-Ranges: bytes

Conclusión

Hoy en día, vuelve a estar al alza la optimización de la velocidad de carga de un sitio web, aunque a través de conexiones WiFi de alta velocidad pueda no ser un problema, el mundo de la movilidad sigue teniendo en su mayor parte conexiones muy lentas. La mayor parte de abandonos de nuestros visitantes se deben, entre otros factores, a la velocidad de carga de nuestra web; según Google &laguo;casi el 50% de los visitantes abandona un sitio web para móviles si las páginas no se cargan en tres segundos».

Pero el tiempo de carga no sólo importa a nuestros visitantes, sino también a los buscadores, que penalizarán la posición en la que indexan nuestra web teniendo (también) en cuenta la velocidad de carga.

Con una configuración mínima y sencilla podemos reducir drásticamente el tiempo de carga de nuestra web y mejorar la velocidad para evitarlo. No son las únicas medidas que podemos tomar, pero eso es harina de otro artículo…

Si eres diseñador o desarrollador web seguro que has utilizado la herramienta de Google para testear si tu diseño es compatible con los dispositivos móviles. Sí, me refiero a la que tienes disponible en https://search.google.com/search-console/mobile-friendly.

Con esta herramienta introduces la URL de la web que quieres testear y, tras ejecutar un pequeño análisis nos informa del nivel de optimización para dispositivos móviles y nos ofrece un informe sobre posibles problemas de usabilidad de dicha versión.

Ahora Google ha ido un poco más lejos y publica una API para acceder a este servicio, lo que permitirá utilizarlo de forma automatizada o como parte de otras herramientas o plugins e integrarlo en pruebas y test automatizados. La tenéis disponible en https://developers.google.com/webmaster-tools/search-console-api/ o, si queréis ir directamente a la documentación REST https://developers.google.com/webmaster-tools/search-console-api/reference/rest/v1/urlTestingTools.mobileFriendlyTest/run

Ya lo has integrado en tus tests, cuéntanos cómo en los comentarios.

A %d blogueros les gusta esto: