
🌐 También en: English · Deutsch · Français
Mi blog se notaba pesado. La portada rondaba los 1,5 MB y mi primer instinto
fue ir a por los sospechosos habituales: quizá sea la base de datos, quizá debería subir MariaDB
de versión mayor, quizá haya que afinar la configuración de TLS. Movimiento clásico de desarrollador
— buscar el arreglo antes de haber encontrado el bug. Así que esta vez hice primero lo
aburrido y correcto: medí. Los resultados fueron una
bofetada muy útil. 📏
🩺 Paso 1: encontrar el verdadero cuello de botella
Time to First Byte, tres pasadas desde un cliente medio en frío:
TTFB 0.173s | Total 0.174s | HTTP/2
TTFB 0.170s | Total 0.171s | HTTP/2
TTFB 0.185s | Total 0.187s | HTTP/20,17 segundos. El servidor entrega el HTML casi al instante. El propio HTML,
comprimido con gzip en la red, ocupa ~18 KB. Así que la base de datos, PHP, las capas de caché — todo
lo que tenía la tentación de «optimizar» — no son el problema.
El slow query log opinaba lo mismo: cero entradas. La base de datos ni se despeina, porque
la caché de objetos de Redis se come las consultas repetitivas antes de que lleguen a ella.
Subir MariaDB de versión mayor no me habría aportado absolutamente nada. 🤷
Los 1,5 MB eran casi por completo una sola cosa: imágenes. Alrededor de un
megabyte. Misterio resuelto antes de tocar un solo fichero de configuración.
😤 Paso 2: las imágenes ya estaban… bastante bien
Y aquí viene lo molesto: las imágenes no eran fruta al alcance de la mano. Ya
estaban en WebP (de eso se encarga un plugin de compresión),
con lazy loading (46 de 46 con loading="lazy") y
servidas con srcset/sizes correctos, de modo que las miniaturas cargan la variante
pequeña y no la gigante. Multiplexación HTTP/2, gzip, CSS/JS agregados —
todo estaba ya en su sitio. No era un sitio descuidado, sino uno bien optimizado
que simplemente tiene muchas imágenes (una portada llena de miniaturas de artículos).
Así que las mejoras restantes eran pequeñas y concretas. Exactamente dos.
🔐 Palanca 1: ECDSA en lugar de RSA (el ajuste honesto de TLS)
El certificado era RSA-2048. Las firmas RSA son comparativamente caras de calcular
para el servidor durante el handshake, así que configuré cert-manager para emitir un certificado
hoja ECDSA P-256 — handshake más barato, seguridad equivalente
(incluso mejor, se podría decir). En una configuración cert-manager/ingress-shim son tres
anotaciones:
cert-manager.io/private-key-algorithm: "ECDSA"
cert-manager.io/private-key-size: "256"
cert-manager.io/private-key-rotation-policy: "Always"cert-manager volvió a emitirlo en unos cuatro segundos, sin cortes, y
openssl confirmó el cambio: Public-Key: (256 bit) … NIST. ¿Reconozco con honestidad que es una mejora minúscula? Sí.
CURVE: P-256
Gracias a HTTP/2, el handshake ocurre una vez por página y ya estaba en ~44 ms.
Pero es gratis y permanente, así que: me lo quedo. ✅
🖼️ Palanca 2: AVIF — donde «convertido» ≠ «entregado»
AVIF suele superar a WebP en un 20–40 % con la misma calidad, así que hice que el
plugin lo convirtiera todo. Obediente, generó 955 ficheros AVIF,
un 60 % más pequeños en disco que los originales WebP. Genial. Volví a medir, esperando
una página más ligera, y obtuve…
<picture> tags: 0 | .avif references: 0 | .webp references: 220
image weight: 1066 KB (identical to before)Nada. Los ficheros AVIF existían, incluso eran accesibles por HTTP, pero la página
seguía sirviendo un 100 % de WebP — incluso cuando lo pedía con
Accept: image/avif. Es la trampa clásica de «plugin de imágenes de nueva generación
contra proxy inverso nginx». La mayoría de estos plugins entregan
AVIF mediante reglas de reescritura del servidor (.htaccess de Apache o
snippets de nginx). Mi WordPress corre detrás de un sidecar nginx en K3s
cuya configuración gestiona Ansible — las reglas de reescritura que genera el plugin
nunca llegan ahí. Así que las imágenes convertidas se quedaban en el disco, admirándose
a sí mismas. 🪞
Reconstruir la caché de páginas no sirvió de nada — porque la caché nunca fue el
problema, lo era la entrega. La solución fue cambiar el plugin de la reescritura en servidor
al método de entrega etiqueta picture / HTML, que
inyecta el AVIF en el momento del renderizado:
<picture>
<source type="image/avif" srcset="….webp.avif">
<img src="….webp"> <!-- fallback for the ~5% without AVIF -->
</picture>Vacías la caché para que el HTML estático se regenere con esas etiquetas y, de repente, el
navegador puede elegir — y elige el fichero más pequeño.
📊 El resultado
Las mismas 20 imágenes visibles, comparando peras con peras:
Before (WebP): 1038 KB
After (AVIF): 642 KB
Savings: 396 KB → 38% lighterAhora cada imagen sale como image/avif hacia los navegadores compatibles con AVIF
(~95 %+ de los visitantes); el resto vuelve automáticamente a WebP mediante el
<img>. Sin inconvenientes y ~40 % menos de peso en imágenes. 🎯
🧠 Lo que me llevo
- 📏 Mide antes de optimizar. Mi instinto decía «base de datos / TLS / actualización de MariaDB». Los datos decían «imágenes». El instinto se equivocaba y me habría costado horas para una ganancia nula.
- 🚦 El TTFB y el peso de la página son problemas distintos. Un TTFB de 0,17 s con 1,5 MB de carga significa: deja de tocar el backend y mira lo que estás enviando por la red.
- 🖼️ «Convertido a AVIF» no es «sirviendo AVIF». Compruébalo de extremo a extremo — revisa los bytes reales que viajan por la red con la cabecera
Acceptcorrecta, no solo que los ficheros existan. - 🔌 Los valores por defecto de los plugins dan por hecho Apache. En una configuración con proxy inverso nginx / K3s, la entrega por «reescritura» no hace nada sin avisar; el método de la etiqueta picture es tu amigo.
- ✅ Aprovecha igualmente las mejoras gratuitas. ECDSA en lugar de RSA no te cambiará la vida, pero es permanente y no cuesta nada.
Como siempre, todo el stack — la configuración de cert-manager, la de nginx, absolutamente
todo — es público y está versionado en mi repositorio de Ansible:
github.com/aptupgrademe/www_k3s.
Mide tu propio sitio algún día. Puede que te sorprenda lo que de verdad va lento. 🧑💻




