Qué comprime bien y qué ya está comprimido
Por qué la compresión funciona sobre unos formatos y no sobre otros, cómo medir el ratio real de tus recursos, y la lista de tipos que nunca hay que recomprimir.
La compresión sin pérdida funciona explotando redundancia, y la cantidad de redundancia que tiene un archivo depende por completo de su formato. El texto tiene muchísima; una imagen JPEG no tiene prácticamente ninguna, porque su propio formato ya la eliminó. Aplicar compresión donde no hay nada que ganar cuesta CPU en las dos puntas y no ahorra un solo byte, y es uno de los errores de configuración más comunes.
- Explicar de dónde sale la ganancia de un compresor sin pérdida.
- Clasificar los tipos de contenido según si merece la pena comprimirlos.
- Medir el ratio de compresión real de tus recursos desde el navegador.
- Configurar la lista de tipos comprimibles sin dejar fuera lo importante.
De dónde sale la ganancia
Los compresores sin pérdida de la familia que usa la web combinan dos técnicas.
Sustitución de repeticiones. Se recorre el contenido buscando secuencias que ya han aparecido antes, dentro de una ventana de los últimos N bytes, y se sustituyen por una referencia a la aparición anterior. En un HTML, cadenas como </div> o class=" aparecen cientos de veces, y a partir de la segunda cuestan unos pocos bits.
Codificación por entropía. Los símbolos resultantes se recodifican asignando menos bits a los más frecuentes. En texto, la letra e y el espacio aparecen muchísimo más que la k, así que darles códigos cortos ahorra.
De ahí se deduce todo lo demás. Un formato comprime bien si tiene repeticiones a corta distancia y una distribución de símbolos desigual. El texto tiene las dos cosas en abundancia. Un archivo ya comprimido no tiene ninguna: precisamente el objetivo de haberlo comprimido era eliminar la redundancia, y su salida se parece estadísticamente a ruido aleatorio.
Intentar comprimir ruido no solo no ahorra: puede hacer crecer el archivo, porque hay que añadir las cabeceras del formato de compresión. Los compresores lo detectan y almacenan el bloque sin comprimir, con lo cual el resultado es un byte o dos más grande que el original más el coste de CPU de haberlo intentado.
La tabla de tipos
| Tipo | ¿Comprimir? | Ratio típico con Brotli |
|---|---|---|
| HTML | Sí | 4:1 a 8:1 |
| CSS | Sí | 4:1 a 6:1 |
| JavaScript | Sí | 3:1 a 5:1 |
| JSON | Sí | 5:1 a 20:1 |
| SVG | Sí | 3:1 a 5:1 |
| XML y feeds | Sí | 5:1 a 10:1 |
| Texto plano y CSV | Sí | 3:1 a 10:1 |
| Fuentes TTF y OTF | Sí | 2:1 a 3:1 |
| Fuentes WOFF2 | No | Ya usa Brotli internamente |
| JPEG | No | 1:1 |
| PNG | No | Ya usa compresión sin pérdida |
| WebP y AVIF | No | 1:1 |
| GIF | No | Ya comprimido |
| MP4, WebM | No | 1:1 |
| Archivos ZIP y similares | No | 1:1 |
| WebAssembly | Sí | 2:1 a 4:1 |
Los ratios de la última columna son órdenes de magnitud y dependen mucho del contenido concreto. El JSON es el caso más espectacular porque repite las claves en cada elemento de un array: un array de mil objetos con las mismas ocho claves comprime extraordinariamente bien.
Merece la pena detenerse en dos filas.
WOFF2 es el caso más incomprendido. Ese formato de fuente incorpora compresión Brotli en su propia especificación, así que recomprimirlo no ahorra nada y añade coste. Los formatos anteriores, TTF y OTF, sí se benefician, y de hecho la diferencia entre servir una fuente en TTF comprimido y en WOFF2 es notable a favor del segundo, porque WOFF2 además reorganiza las tablas de la fuente para que comprima mejor.
SVG es el caso que más gente olvida. Es texto, comprime como el texto, y es habitual encontrarlo servido sin comprimir porque el tipo de contenido no está en la lista por defecto del servidor. Comprobarlo es de las revisiones más rentables que existen.
Casi todos los servidores traen una lista de tipos comprimibles por defecto que se quedó corta hace años. Es habitual que incluya text/html, text/css y text/plain y que deje fuera application/json, image/svg+xml, application/wasm y los tipos de fuente antiguos. El resultado es que tus respuestas de API viajan sin comprimir sin que nadie lo note, porque nada falla. Revisa la lista explícitamente en lugar de confiar en el valor por defecto.
Medir el ratio real
No hay que estimar nada: el navegador expone los dos tamaños de cada recurso.
// Ratio de compresion real de todos los recursos de la pagina.
const informe = performance.getEntriesByType('resource')
.filter((r) => r.decodedBodySize > 0 && r.transferSize > 0)
.map((r) => {
const cabeceras = r.transferSize - r.encodedBodySize; // aprox.
return {
recurso: r.name.split('/').pop().slice(0, 40),
original: r.decodedBodySize,
comprimido: r.encodedBodySize,
ratio: (r.decodedBodySize / r.encodedBodySize).toFixed(2),
cabeceras,
};
})
.sort((a, b) => Number(a.ratio) - Number(b.ratio));
console.table(informe);
Los tres campos que intervienen tienen significados precisos:
decodedBodySize: tamaño del cuerpo después de descomprimir.encodedBodySize: tamaño del cuerpo tal como viajó por la red.transferSize: lo anterior más una estimación de las cabeceras de la respuesta. Vale cero si la respuesta vino de la caché sin tocar la red.
La comprobación más útil es ordenar por ratio ascendente y mirar los primeros. Un ratio de 1,00 en un recurso de texto significa que no se está comprimiendo. Es la forma más rápida de encontrar tipos de contenido que se quedaron fuera de la configuración.
Y desde la línea de comandos, para comprobar un recurso concreto:
# Comparar tamano sin comprimir y con cada codificacion.
URL=https://ejemplo.com/api/productos
echo "sin comprimir:"
curl -s -H 'Accept-Encoding: identity' "$URL" | wc -c
echo "gzip:"
curl -s -H 'Accept-Encoding: gzip' --compressed-no-var "$URL" --output - | wc -c
# Ver que codificacion aplica el servidor por defecto.
curl -sI -H 'Accept-Encoding: gzip, br, zstd' "$URL" | grep -i content-encoding
Qué hacer con lo que no comprime
Que un formato no se beneficie de la compresión de transporte no significa que no se pueda reducir. Solo significa que la palanca es otra.
Imágenes. La reducción se consigue eligiendo un formato mejor y ajustando la calidad, no comprimiendo el transporte. Un JPEG de 400 KB convertido a AVIF con calidad perceptual equivalente puede bajar a 80 KB, y eso es un ahorro cinco veces mayor que cualquier cosa que haga un compresor de transporte.
Vídeo. Igual: el códec y el bitrate, no el transporte.
PNG. Su compresión interna deja margen. Herramientas de optimización sin pérdida pueden recortar un 10 a 30% reordenando y reoptimizando los datos, sin cambiar un solo píxel. Pero si el contenido es fotográfico, casi siempre gana más cambiar a otro formato.
Fuentes. Convertir a WOFF2 y, sobre todo, subconjuntar: quedarse solo con los glifos que el sitio usa. Una fuente con soporte para cirílico, griego y vietnamita en un sitio en español es la mitad del archivo desperdiciada.
Archivos que el usuario descarga. Si sirves un archivo comprimido para descarga, no lo recomprimas en el transporte. Añade tiempo de CPU en tu servidor y en el dispositivo del usuario para no ahorrar nada.
Se revisa el HTML, se revisa el CSS, se revisa el JavaScript, y nadie mira el tráfico que genera la aplicación después de cargar. Las respuestas de API son el contenido más comprimible que existe, porque el JSON repite las mismas claves en cada elemento de un array y su alfabeto real es minúsculo: ratios de diez a uno o mejores son habituales. Y son también las que más veces salen sin comprimir, porque suelen servirse desde un proceso de aplicación que no pasa por la misma configuración que los estáticos, o porque application/json no estaba en la lista de tipos del servidor. He visto una aplicación que enviaba respuestas de 900 kilobytes que comprimían a 45: veinte veces menos, y llevaban dos años viajando enteras. La comprobación cuesta un minuto: abre el panel de red, filtra por peticiones de datos, y mira si la cabecera de codificación de contenido está presente. Y hazlo después de interactuar con la aplicación, no solo tras la carga inicial, porque la mayor parte de ese tráfico ocurre más tarde y no aparece en ninguna auditoría automática, que solo mide la carga.