|

Auditar caché CDN antes de migrar buckets: guía 2026

En pocas palabras: Para saberlo, medí dos números: el hit ratio ponderado por bytes y el fan-out por objeto único. Con Cloudflare, los headers cf-cache-status y age marcan HIT o MISS; si entregás 5 TB con 95% de hit ratio, solo unos 250 GB facturan desde el bucket de origen.

Auditar caché CDN antes de mover un bucket se reduce a dos números: el hit ratio ponderado por bytes y los pedidos por objeto único (fan-out). Así lo planteó un análisis técnico publicado en DEV, con scripts listos para correr.

La auditoría de caché CDN es el proceso de verificar si tu CDN entrega copias guardadas o vuelve al origen en cada pedido. Se hace con headers HTTP como cf-cache-status, x-cache y age, y con logs donde cada respuesta queda registrada como HIT o MISS. La cifra que importa es el hit ratio ponderado por bytes: el porcentaje del tráfico servido desde caché, porque es el único que se corresponde con la factura de egress.

En 30 segundos

  • Dos medidores facturan distinto: el CDN cobra cada byte entregado al navegador; el bucket solo cobra los bytes que el CDN le pidió, o sea tus MISS.
  • Ejemplo del análisis: 5 TB entregados con 95% de hit ratio implican unos 250 GB de egress de origen; esa es la única cifra que cambia al migrar el bucket.
  • Dos curls bastan para empezar: si la segunda petición a la misma URL responde MISS, tu cache key es inestable y ningún TTL lo arregla.
  • Presigned URLs significan hit ratio cero: la firma y la expiración de S3 cambian en cada generación, así que cada pedido es un objeto nuevo para el borde.
  • Fan-out cercano a 1 es el disparador de migración: archivos generados una vez justifican R2 o B2; todo lo demás se arregla con caché.

¿Qué es el hit ratio ponderado por bytes y por qué no coincide con tus descargas?

El hit ratio ponderado por bytes es el porcentaje de los bytes que llegaron al usuario desde la caché, y casi nunca coincide con tus métricas de descarga porque hay dos medidores facturando por separado. El primero es el egress del CDN: cada byte entregado al navegador, venga de caché o no. El segundo es el egress del bucket: solo los bytes que el CDN tuvo que ir a buscar al origen.

Ponele que servís 5 TB mensuales con un 95% de hit ratio: tu bucket emitió apenas 250 GB. Esa es la única cifra que una migración de storage puede cambiar. Los otros 4,75 TB los factura quien corre el borde, y mover el bucket no los toca ni de casualidad. (Spoiler: la mayoría de los equipos nunca separó estos dos números.)

Subís el bucket, le ponés una CDN adelante, todo vuela, la factura baja un mes, al siguiente sube, revisás configuraciones, cambiás TTLs, probás otros paths de caché, seguís parecido, y nadie te avisó que estabas mirando el medidor equivocado desde el día uno. Sobre eso hablamos en nuestra guía sobre gestión de secretos en Cloudflare Workers.

Hay una confusión clásica con AWS acá: el tránsito de S3 a CloudFront es gratis, así que poner CloudFront adelante del bucket no elimina el egress, lo reetiqueta como data-out de CloudFront. Ese “ahorro” solo cambia el remitente de la factura. Cloudflare, según la publicación original en DEV, no mide ancho de banda del CDN por GB, aunque a mediados de 2026 sus términos restringen servir grandes volúmenes de contenido no-HTML en planes no enterprise. Ojo con eso si tu caso es servir terabytes de contenido multimedia.

¿Cómo auditar la caché CDN con dos curls y cero presupuesto?

Con dos curls a la misma URL alcanza: el primero llena la caché y el segundo debería pegarle. Si el segundo también contesta MISS, tenés una cache key inestable, y ninguna afinación de TTL te salva. Un header Age que arranca de 0 en cada pedido es el mismo síntoma con cara amable.

curl -sD - -o /dev/null "https://cdn.tudominio.com/assets/logo.webp" | grep -E '^(cf-cache-status|x-cache|age|cache-control|vary):'
curl -sD - -o /dev/null "https://cdn.tudominio.com/assets/logo.webp" | grep -E '^(cf-cache-status|x-cache|age|cache-control|vary):'

Salida sana, primera pasada y segunda:

CF-Cache-Status: MISS
CF-Cache-Status: HIT
Age: 42

Los nombres cambian según el proveedor: cf-cache-status en Cloudflare, cuyos valores están en la documentación oficial de Cloudflare, y x-cache en CloudFront. ¿Segunda respuesta en MISS? Entonces el problema no es el tiempo de vida del objeto, sino la clave que usa el borde para identificarlo. Y un detalle a tener presente: contar solo HIT como acierto es un criterio conservador, porque un EXPIRED también cuesta un viaje al origen.

¿Cómo calcular el byte-weighted hit ratio desde tus logs?

Dos curls describen un objeto; los logs describen tu factura. Para el número real, parseá un mes de access logs de CloudFront, que vienen tabulados con una línea #Fields: que conviene mapear por nombre en vez de hardcodear posiciones:

#!/usr/bin/env python3
import sys
hits = misses = pedidos = 0
unicos = set()
campos = None
for linea in open(sys.argv):
 linea = linea.strip()
 if linea.startswith("#Fields:"):
 campos = linea.split()[1:]
 continue
 if not linea or campos is None:
 continue
 fila = dict(zip(campos, linea.split("\t")))
 b = int(fila["sc-bytes"])
 pedidos += 1
 if fila["x-edge-result-type"] == "Hit":
 hits += b
 else:
 misses += b
 unicos.add(fila["cs-uri-stem"])
if not pedidos:
 sys.exit("sin filas: revisá la ruta del log")
total = hits + misses
print(f"hit ratio ponderado : {100*hits/total:.1f}%")
print(f"egress de origen : {misses/1024**3:.2f} GB")
print(f"objetos únicos : {len(unicos)}")
print(f"pedidos por objeto : {pedidos/len(unicos):.1f}")

Si usás Cloudflare Logpush (JSON por línea), los mismos números salen con jq y awk:

jq -r '[.CacheCacheStatus, .EdgeResponseBytes, .ClientRequestURI] | @tsv' logpush.json \
| awk '{bytes+=$2; if($1=="hit")hits+=$2; if(!($3 in seen)){seen[$3]=1;u++} END{printf "hit ratio : %.1f%%\nURIs únicas : %d\nreq/URI : %.1f\n",100*hits/bytes,u,NR/u}'

Ejemplo ilustrativo de salida:

hit ratio ponderado : 61,3%
egress de origen : 187,40 GB
objetos únicos : 42.318
pedidos por objeto : 3,7

Una salvedad que hace el propio autor: EdgeResponseBytes cuenta bytes enviados al viewer, así que las descargas por rangos o abortadas lo vuelven una aproximación, buena para dimensionar una decisión y mala para conciliar una factura. Dos números. Una tarde. Eso cuesta dejar de adivinar.

¿Qué rompe tu caché en silencio? Los cinco culpables habituales

Casi nunca es el TTL, que es justo lo primero que todos revisan. Lo que tumba el hit ratio es una cache key que no se mantiene estable entre pedidos de los mismos bytes. Según el análisis, estos son los sospechosos de siempre: Cubrimos ese tema en detalle en evitar caídas por un DNS mal configurado.

  • Presigned URLs de S3. Traen X-Amz-Signature y X-Amz-Expires en el query string, y ambos cambian en cada generación. Si tu política incluye query params en la clave, cada pedido es un objeto único para el borde y el hit ratio es cero por construcción. ¿El más común de todos? Este, por goleada.
  • Parámetros de cache-busting dinámicos. Un componente de imágenes o un wrapper de analytics que agrega ?v=timestamp fragmenta el objeto sin querer.
  • Cookies reenviadas a la clave. Si incluís una cookie de sesión, tu hit ratio pasa a ser tu tasa de re-descarga por usuario.
  • Variantes de transformación. Los query params de capas de imagen parten un archivo en muchas versiones distintas.
  • Hostnames múltiples. El mismo asset servido desde dos dominios distintos parte la tasa de aciertos por la mitad.

El diagnóstico es mecánico: revisá las listas de query strings, headers y cookies de tu cache policy, volvé a correr los dos curls con una URL limpia y vas a saber cuál de los cinco es el tuyo.

¿Cómo reemplazar presigned URLs por tokens cuantizados en el tiempo?

Una presigned URL no se puede volver cacheable: la firma rotante es su naturaleza. O dejás de usarlas en el hot path, o aceptás que esos bytes sean siempre egress de origen. El patrón que propone el artículo es un token por ruta con ventana temporal cuantizada, donde cada pedido dentro de la ventana produce URLs byte-idénticas:

VENTANA = 3600 # segundos; también es tu peor caso de revocación
slot = timestamp() // VENTANA
firma = hmac_sha256(SECRETO, ruta + str(slot))
url = f"{base}{ruta}?slot={slot}&sig={firma}"

Tu borde, sea un worker, Lambda@Edge o tu propio origin, recalcula el HMAC para el slot actual y el anterior, y rechaza cualquier otra cosa. El trade-off existe y hay que nombrarlo: la revocación es gruesa, un link queda válido hasta una ventana completa después de cortar el acceso. Para avatars y exports, zafa bárbaro. Para objetos sensibles, seguí con presigned URLs y asumí el egress.

¿Cuándo conviene migrar? La tabla de decisión

Los dos números juntos te dicen qué proyecto firmaste sin saberlo: un arreglo de caché de una tarde o una migración de storage. Esta es la matriz que deja el análisis:

Pedidos por objetoHit ratioDiagnósticoQué hacer
Alto (mucho reuso)AltoTodo funcionaNada; el egress de origen ya es chico
AltoBajoCache key inestable o TTL muy cortosArreglar cacheabilidad; migrar esconde el bug
Cerca de 1Bajo y no subeCola larga genuina, archivos one-offStorage sin cobro de egress
Bajo globalBytes concentrados en pocos objetosUn puñado de archivos grandes dominaTTL largos en esos; tocar el resto lo mínimo
auditar caché cdn diagrama explicativo

La tercera fila es la que todos pasan por alto. Los assets generados por IA, un PDF renderizado, una imagen hecha a pedido, un export que se baja una vez y jamás vuelve, tienen fan-out cercano a 1: cachearlos da igual, pagás el fetch de origen de todas formas y el objeto se evicta antes de que llegue el segundo pedido, que nunca llega. Ahí entra el storage sin egress: Cloudflare R2 elimina el medidor por completo, mientras que Backblaze B2 cubre gran parte del camino con egress gratis hasta un múltiplo de lo que almacenás, y gratis hacia la red de Cloudflare vía Bandwidth Alliance hasta que tu ratio lectura/almacenamiento supere ese múltiplo. Esto se conecta con lo que analizamos en cold starts comparados entre plataformas edge.

S3, R2 y B2: qué cifra realmente mueve la factura

Los tres proveedores juegan con monedas distintas sobre el egress, y la diferencia práctica es esta:

ProveedorEgress de origenDónde está la trampa
S3 (+ CloudFront)Se paga por GB servido desde el bucket (los MISS); el tránsito S3 a CDN es gratisLa factura no desaparece, se reetiqueta como data-out del CDN
Cloudflare R2Cero egressLas escrituras Class A del lado upload son el medidor caro
Backblaze B2Gratis hasta un múltiplo de tu storageSuperado ese múltiplo vuelve a facturar; mirá tu ratio lectura/almacenamiento

Detalle que casi nadie mira: las operaciones. Con fan-out cercano a 1, cada objeto entregado implica casi un GET de origen, y en R2 las escrituras Class A pesan más que en otros lados. El storage puro suele quedar como error de redondeo, motivo por el cual rankear proveedores por dólares por GB almacenado te ordena la lista al revés.

Paso a paso: un mes de logs antes de tocar nada

Antes de mover un solo bucket, este es el circuito completo:

  1. Corré el script contra 30 días de logs y anotá los miss bytes.
  2. Multiplicá esa cifra por tu tarifa vigente de egress, o por cero si caés dentro de algún allowance.
  3. Proyectá el costo en el proveedor nuevo sumando operaciones, porque con fan-out bajo cada entrega es un GET de origen.
  4. Restá y decidí: la ecuación es miss bytes por tarifa, menos operaciones del proveedor nuevo.

Ejemplo con los números de la fuente: 250 GB mensuales de misses. A una tarifa pública del orden de USD 0,09/GB, hablamos de unos USD 22,50 al mes. Si el arreglo de cacheabilidad baja los misses a 60 GB, quedan USD 5,40, y el caso de migración se evapora directo. El autor lo resume con experiencia propia: el fix aterriza primero, el número se mueve, y recién ahí sabés si la migración era necesaria o cosmética. Para equipos que facturan en pesos y pagan nubes en dólares, cada gigabyte de miss evitado es margen directo.

Errores comunes al auditar caché CDN

Estos tropiezos aparecen una y otra vez en discusiones sobre egress, y todos se esquivan con la misma auditoría inicial:

  • Mirar el ratio por pedidos en vez de por bytes. Un 99% de aciertos por cantidad no significa nada si el 1% restante son tus videos. Solo la versión ponderada por bytes se mapea a dinero.
  • Migrar primero y diagnosticar después. Hay equipos que pagan una migración completa para esconder un bug de cache key que se arreglaba en una tarde. Es un bug, no una decisión.
  • Culpar al TTL. El tiempo de vida corto es el sospechoso de moda, pero la causa raíz suele ser la clave inestable. Fijate en la clave primero.
  • Comparar proveedores por storage por GB. El almacenamiento es el error de redondeo de la factura; las operaciones y el egress mandan.

Preguntas Frecuentes

¿Cuál es mi hit ratio real del CDN y cómo lo mido?

Tu hit ratio real es el ponderado por bytes, y sale de parsear logs, no de mirar paneles. Corré dos curls a la misma URL para detectar claves inestables y después procesá un mes de access logs dividiendo bytes servidos desde caché sobre bytes totales. Ya lo cubrimos antes en migrar buckets a R2 sin costo de egreso.

¿Por qué mi factura de egress sigue alta si uso una CDN?

Porque la CDN solo reduce el egress del origen mediante aciertos de caché; los bytes entregados al usuario los sigue facturando quien opera el borde. Si tu hit ratio es bajo por claves inestables, el ahorro nunca llega por más CDN que le pongas.

¿Las presigned URLs rompen la caché del CDN?

Sí, por diseño: la firma y la expiración cambian en cada generación, así que cada URL es un objeto distinto para el borde. El reemplazo viable es un token por ruta con ventana temporal cuantizada, aceptando revocación gruesa de hasta una ventana completa.

¿Qué hit ratio es bueno para assets de usuarios?

Para assets compartidos como avatars y thumbnails, apuntá a 90% o más ponderado por bytes. Para archivos generados una sola vez con fan-out cercano a 1, incluso 30% puede ser el techo, y ahí el problema no es la caché sino el modelo de precios del storage.

¿Cuándo vale la pena migrar a R2 o B2?

Cuando el fan-out sea de verdad cercano a 1 y la caché no tenga nada más para dar. R2 elimina el cargo de egress por completo; B2 lo deja en cero hasta un múltiplo de tu almacenamiento. Si tu problema es una clave inestable, migrar solo esconde el bug y te cobra por hacerlo.

Conclusión

Lo que cambió con esta publicación es el orden de los pasos: primero medir, después migrar. El hit ratio ponderado por bytes y el fan-out cuestan una tarde de trabajo y te dicen si firmaste un bug de caché o una decisión estructural de arquitectura. Clave inestable: arreglala y la factura baja sin cambiar de proveedor. Fan-out rozando 1: la caché ya dio todo lo que podía dar, y el storage sin egress es la respuesta honesta.

Mi recomendación después de años viendo estas migraciones: corré el script esta semana, antes de que alguien del equipo se enamore de un plan nuevo.

Fuentes

Te puede interesar...