Introducción a la compresión de datos en la web

Optimiza el tamaño y velocidad de tu aplicación web gracias al uso correcto de la compresión de datos.

Introducción

La compresión web, esa pequeña e incomprendida funcionalidad que utilizamos constantemente para enviar y recibir datos de manera más rápida y generando un volumen de datos menor.

En este artículo veremos cómo funciona y las distintas opciones de las que disponemos.

Negociación

El cliente y el servidor acuerdan qué metodo de compresión utilizar mediante la negociación.

Accept-Encoding

Cuando el cliente solicita un recurso web al servidor, puede indicarle qué formatos de compresión desea utilizar. Esto lo hace a través de la cabecera Accept-Encoding (RFC 7231, sección 5.3.4), indicando los algoritmos de compresión que acepta. Ejemplo:

GET /encrypted-area HTTP/1.1
Host: www.example.com
Accept-Encoding: gzip, deflate

En este caso el cliente indica al servidor que acepta los métodos de compresión gzip y deflate. El orden es importante ya que indica la preferencia del cliente (gzip sería la primera opción a utilizar y deflate la segunda).

Para controlar la preferencia también puede utilizarse el modificador ;q=, indicando un valor cualitativo (q-value). Esto expresa la prioridad en unidades "weight" que van desde 0 hasta 1. Ejemplo:

GET /encrypted-area HTTP/1.1
Host: www.example.com
Accept-Encoding: deflate;q=0.8, gzip;q=1.0

En este caso, a pesar de que gzip se encuentra segundo en la lista, sería el primero en cuanto a prioridad.

Content-Encoding

Tras la petición, llega la respuesta. En este caso el servidor es quién decide el método de compresión a utilizar. Realmente, el servidor puede hacer lo que le plazca, pero lo normal sería respetar los deseos del cliente y utilizar el algorimo de compresión con mayor prioridad especificado por el cliente. En el ejemplo anterior sería gzip. Si gzip no estuviera disponible se utilizaría deflate, y así sucesivamente. Si ninguno estuviera disponible, el servidor no debería comprimir el contenido.

Aparte de comprimir el cuerpo de la respuesta, el servidor añade la cabecera Content-Encoding (RFC 7231, sección 3.1.2.2) en la que especifica el algoritmo de compresión que finalmente se ha utilizado. Ejemplo:

HTTP/1.1 200 OK
Content-Length: 309
Content-Type: text/html; charset=UTF-8
Content-Encoding: gzip
¡Recuerda!

No hay que confundir Content-Type con Content-Encoding. Aunque se comprima la respuesta, ésta sigue teniendo un formato, como por ejemplo: text/html, video/mp4 o image/jpg. Por lo tanto, no olvides especificar la cabecera Content-Type para indicar el contenido del mensaje, esté comprimido o no.

zlib

Jean-loup Gailly y Mark Adler crearon en 1995 la librería de compresión zlib. Con el mismo nombre nació el formato zlib (especificado en el RFC 1950), que consiste en una cabecera (header), un cuerpo (body) comprimido mediante un algoritmo de compresión (generalmente deflate, aunque se pueden implementar otros) y por último incluye una suma de verificación (checksum) en formato Adler-32.

Así pues, la librería zlib producía ficheros en formato zlib.

Esta breve información nos será útil para entender los varios algoritmos de compresión.

Formatos de compresión

Formatos de compresión hay unos cuantos pero sin duda gzip domina la web.

Los valores posibles en las cabeceras Accept-Encoding y Content-Encoding son: deflate, gzip, br, identity, compress y *.

Vamos a echarles un vistazo.

deflate

El caso de deflate es curioso a la vez que confuso. Está definido como:

El formato "zlib" definido en RFC 1950 en combinación con el mecanismo de compresión "deflate" descrito en RFC 1951.

The "zlib" format defined in RFC 1950 in combination with the "deflate" compression mechanism described in RFC 1951.

Vamos por partes para intentar entenderlo mejor.

Resulta que según el RFC 2616, deflate equivale al formato zlib, comprimiendo el cuerpo utilizando el algortimo deflate (es decir, ningún otro algoritmo puede ser utilizado).

¿Confuso verdad? Resulta que deflate es tanto un formato como un algoritmo utilizado dentro de ese formato.

Esto llevó a la confusión también a los ingenieros de Microsoft que implementaron el formato, por lo que durante años los navegadores no sabían si deflate se refería al formato o al algoritmo de compresión, por lo que funcionaban por descarte: si no era uno, era otro.

Esta ambigüedad a la hora de definir el formato zlib/deflate provocó que el formato gzip se extendiera rápidamente y se acabase convirtiendo en el rey de la compresión web.

Para entenderlo mejor, veamos la implementación en Node.js de la librería zlib.

Resulta que tenemos estos dos métodos:

Ya lo habrás adivinado, pero el método zlib.Deflate se refiere al formato deflate (incluyendo la cabecera y la suma de verificación), mientras que el método zlib.DeflateRaw se refiere al algoritmo de compresión en sí, sin ninguna cabecera ni envoltorio de ningún tipo.

Cómo diferenciar Deflate y DeflateRaw

Primero, vamos a ver que ocurre cuando comprimimos la cadena foo utilizando ambos métodos:

  • JavaScript
zlib.deflateSync('foo')
<Buffer 78 9c 4b cb cf 07 00 02 82 01 45>
  • JavaScript
zlib.deflateRawSync('foo')
<Buffer 4b cb cf 07 00>

El resultado de deflate como formato produce una cabecera con los bytes 78 9c, un cuerpo que contiene los bytes 4b cb cf 07 00 (en común con deflate como algoritmo) y una suma de verificación con los bytes 02 82 01 45.

Es en los bytes de cabecera, más concretamente en el primer byte (78 en nuestro ejemplo) en donde está la distinción.

Un byte se compone de dos nibbles. Al ser 78 un valor hexadecimal, el 7 es un nibble y el 8 es el otro nibble. Siendo el primero el alto (más significante) y el segundo el bajo (menos significante), aunque este orden depende de la arquitectura/plataforma en la que te encuentres, pero vamos a obviar esto.

Si el nibble bajo (el segundo) es un 8, entonces estamos ante el formato deflate/zlib, mientras que si no es un 8, estaremos ante el algoritmo de compresión. Es una regla que siempre se cumple.

Por lo tanto, en nuestro ejemplo podemos ver claramente cual es un formato deflate y cual es un algoritmo de compresión deflate (conocido en la implementación de zlib de Node.js como deflateRaw).

gzip

Basado en el algoritmo deflate, gzip es un formato abierto que comprime el mensaje utilizando el algoritmo LZ77 y añade, al igual que el formato zlib, una cabecera y una suma de verificación, aunque ésta en formato CRC-32.

La cabecera del formato gzip contiene más bytes de los que contiene el formato zlib (en total 12 bytes más). Además, la suma de verificación utilizando CRC-32 es más lenta de generar que mediante Adler-32, pero estas desventajas apenas son perceptibles con la potencia de los dispositivos de hoy en día. Ésto, sumado a que evitaremos la confusión del formato/algoritmo deflate, ha convertido a gzip en la opción ideal para adoptar cuando hablamos de compresión web.

Brotli

Brotli es un formato de compresión creado por Google en 2015 para comprimir fuentes para la web en formato WOFF, pero rápidamente encontró su sitio en el campo de la compresión web por sus ventajas. Utiliza un algoritmo de compresión propio, basado en LZ77.

Su compatibilidad en navegadores es actualmente del 62%, más que suficiente teniendo en cuenta que si Brotli no estuviera disponible se utilizaría el siguiente de la lista.

Actualización 2023

Hoy en día su compatiblidad es prácticamente total, situándose en un 96.45%.

En las cabeceras HTTP Accept-Encoding y Concent-Encoding debe especificarse con el valor br.

En cuanto a las ventajas frente a gzip, promete en torno a un 20% extra de compresión siendo prácticamente igual de rápido en compresión/descompresión.

* (asterisco)

Cuando se utiliza el valor * se está indicando al servidor que se acepta compresión de datos pero no se especifica ninguna preferencia en cuanto al formato.

Éste es el valor por defecto cuando omitimos la cabecera Accept-Encoding.

identity

La función de identity es la de indicar de manera explícita al servidor que no se desea ninguna compresión.

compress

Este formato basado en el algoritmo LZW cayó en desuso debido a problemas de patentes y apenas quedan navegadores que lo soporten.

Zstandard

Facebook posee un proyecto open source llamado Zstandard (zstd), que promete mejores resultados que Brotli, tanto en compresión como en velocidad. Desgraciadamente, por el momento no cuenta con soporte en ningún navegador así que el uso de este formato no es realmente válido en compresión web. Aún así, se merece una mención.

Comprimiendo lo comprimido

Hay formatos de archivo que pueden estar ya comprimidos como por ejemplo algunas imágenes PNG y GIF o las tipografías web en formato WOFF.

En el caso de que estos datos ya estuvieran comprimidos, comprimirlos nuevamente sería un malgasto de recursos, ya que el tamaño final sería incluso superior (añadiendo una cabecera y una suma de verificación innecesarias) y además el servidor estaría empleando ciclos de CPU en comprimir algo que no va a aportar ningún beneficio.

Tampoco es favorable comprimir dos veces el mismo fichero. Si algo ya está comprimido, mejor dejarlo así.

Conclusión

Aplicar de manera inteligente la compresión de datos en nuestras aplicaciones web puede ahorrarnos mucha transferencia de datos mientras conseguimos un extra de velocidad para nuestros usuarios.

En cuanto a los formatos, lo óptimo sería hacer uso de nuevas opciones como Brotli para optimizar los recursos al máximo. Como la compresión web degrada fácilmente, no hay motivo para dejar de lado a los navegadores más antiguos, así que podemos utilizar gzip como segunda opción y dejar que sea el cliente el que elija.

Puedes apoyarme para que pueda dedicar aún más tiempo a escribir artículos y tener recursos para crear nuevos proyectos. ¡Gracias!