Por Lucas Yoris · Performate
CDN y headers de caché: pruebas de carga en hits, misses y riesgo de stampede
Ejercita comportamiento en edge de CDN, valida headers Age/TTL y detecta thundering herds cuando expiran cachés al unísono.
El dashboard de CDN muestra 95% de hit ratio en producción, pero la última prueba de carga martilleó una URL estática durante diez minutos. Esa prueba no demostró nada sobre el edge: caminos hit y miss difieren órdenes de magnitud en latencia, carga en origen y firmas de error (HTTP caching overview).
Las pruebas de carga de CDN y headers de caché validan diversidad realista de claves, comportamiento al expirar TTL y riesgo de stampede cuando muchos clientes fallan juntos. En esta guía verás por qué Age y Cache-Control importan bajo concurrencia, cómo etiquetar clases de hit en k6 y cuándo simular expiración coordinada.
Por qué el comportamiento de caché cambia el rendimiento—no solo el ancho de banda
Bajo carga, las capas de caché mueven el cuello de botella:
- Hits en edge sirven desde memoria del PoP—sub-ms a decenas de ms según geografía (pruebas geo).
- Misses en edge propagan a origen—de repente tu tier API ve picos de tráfico de marketing.
- Stale-while-revalidate responde rápido pero dispara fetches en background—la latencia se ve bien mientras RPS de origen sube en silencio.
- Headers Vary multiplican claves—una URL con distinto
Accept-Encodingo cookies crea caminos fríos paralelos.
Martillar una URL produce una fantasía de caché caliente. Producción sirve miles de claves con TTLs escalonados.
Cuando el hit ratio miente
Un hit ratio agregado alto oculta misses en cola en páginas de alto valor. Las pruebas deben variar URLs o claves según distribuciones de analytics—no un asset héroe.
Valida headers con checks de k6 mientras etiquetas hit_class:edge|origin|unknown según Age, X-Cache o CF-Cache-Status que exponga tu CDN.
Implementación práctica con k6: tráfico mixto hit/miss con checks de headers
Modela una mezcla realista de assets cacheables desde CSV o selección aleatoria ponderada. Clasifica cada respuesta por headers.
Script de ejemplo (ilustrativo—no es una prueba lista para producción). Usa headers CDN y paths ficticios.
Qué demuestra este ejemplo:
- Claves de caché variadas: IDs aleatorios de un pool simulan muchas URLs, no un objeto caliente.
- Clasificación por headers: checks parsean
AgeyX-Cachepara etiquetar métricas por clase de hit. - Escenario burst de miss: segundo escenario martillea URLs frescas para simular arranques en frío coordinados.
- Umbrales separados: hits en edge esperan p95 menor que misses en origen—latencia agregada oculta regresiones.
import http from 'k6/http';
import { check, sleep } from 'k6';
import { SharedArray } from 'k6/data';
const BASE = __ENV.CDN_BASE || 'https://cdn.staging.example.com';
const assets = new SharedArray('assets', () =>
Array.from({ length: 200 }, (_, i) => `/static/product-${1000 + i}.webp`)
);
function classifyHit(res) {
const age = Number(res.headers['Age'] || 0);
const xCache = (res.headers['X-Cache'] || '').toLowerCase();
if (xCache.includes('hit') || age > 0) return 'edge';
if (xCache.includes('miss')) return 'origin';
return 'unknown';
}
export const options = {
scenarios: {
steady_mix: {
executor: 'constant-arrival-rate',
rate: 80,
timeUnit: '1s',
duration: '5m',
preAllocatedVUs: 20,
maxVUs: 60,
exec: 'fetchAsset',
},
miss_burst: {
executor: 'constant-arrival-rate',
rate: 30,
timeUnit: '1s',
duration: '2m',
startTime: '5m',
preAllocatedVUs: 15,
maxVUs: 40,
exec: 'fetchColdAsset',
},
},
thresholds: {
'http_req_duration{hit_class:edge}': ['p(95)<120'],
'http_req_duration{hit_class:origin}': ['p(95)<600'],
http_req_failed: ['rate<0.01'],
},
};
export function fetchAsset() {
const path = assets[Math.floor(Math.random() * assets.length)];
const res = http.get(`${BASE}${path}`, { tags: { asset: 'warm_pool' } });
const hitClass = classifyHit(res);
check(res, {
'status 2xx': (r) => r.status >= 200 && r.status < 300,
'cache-control present': (r) => !!r.headers['Cache-Control'],
});
sleep(0.1);
}
export function fetchColdAsset() {
const coldPath = `/static/campaign-${__VU}-${__ITER}.jpg`;
const res = http.get(`${BASE}${coldPath}`, { tags: { asset: 'cold', hit_class: 'origin' } });
check(res, { 'cold fetch 2xx': (r) => r.status >= 200 && r.status < 300 });
sleep(0.05);
}
Patrones que funcionan
- Calienta cachés con pre-test a baja tasa o paso
setup()antes de medir hits. - Coordina expiración TTL en staging seguro purgando subconjuntos o usando assets con TTL corto—observa picos en origen (análisis de cuellos).
- Compara tiempos edge vs origen; si saturan edges primero, ajusta PoPs—no GC de aplicación.
Anti-patrones a evitar
- Probar solo hits porque «eso ven los usuarios»—tormentas de miss causan caídas.
- Ignorar headers
Varyque fragmentan efectividad de caché. - Usar APIs de purge de CDN en prod sin ventana aprobada.
Marco de decisión: mezcla estable vs simulación de stampede
| Situación | Acción recomendada |
|---|---|
| Validar tráfico cotidiano | URLs aleatorias ponderadas desde analytics; tag hit class |
| Lanzamiento de campaña con assets nuevos | Escenario burst de URLs frías tras warm-up estable |
| Alineación TTL entre clases de asset | Prueba de expiración escalonada en staging con fixtures TTL corto |
| Cambio de config CDN (PoP, tiered cache) | Re-corre mix idéntico; compara delta de RPS origen |
| Claves de caché con auth | Escenarios separados por variante cookie/header |
Usa mezcla estable si necesitas líneas base de regresión en comportamiento normal de caché.
Usa burst de miss si debes demostrar que origen aguanta arranques en frío coordinados.
Usa assertions de headers si mala config CDN devuelve 200 con Cache-Control incorrecto—deuda silenciosa.
Observabilidad, documentación y siguientes pasos
- Documenta vendor CDN, regiones PoP y nombres de headers para clasificación hit.
- Registra ratio hit/miss por escenario—no solo http_req_duration agregado.
- Alerta cuando RPS origen en prueba supere fracción acordada de capacidad origen.
- Correlaciona tags k6 con dashboards de analytics CDN en la misma ventana.
- Archiva definiciones del pool de assets para reruns con distribución idéntica.
Cómo Performate simplifica pruebas de carga conscientes de CDN
Ejemplo: reproducir journeys cache-aware sin bucles curl
- Importa colección con GETs de assets estáticos más llamadas API que fijan headers que varían caché. Problema resuelto: un workspace cubre HTML, API y paths CDN.
- Parametriza URLs de assets desde CSV de IDs de producto alineado a producción. Problema resuelto: diversidad realista de claves sin editar paths cada sprint.
- Agrega checks de Cache-Control y Age en el editor visual. Problema resuelto: autores no-script validan headers de forma consistente.
- Corre mix estable, duplica escenario como burst de miss con patrón de URL fresca. Problema resuelto: pruebas stampede son cambio de parámetro, no repo nuevo.
- Filtra reporte integrado por tag hit_class y compara p95 edge vs origen. Problema resuelto: un export para equipos CDN y backend.
- Exporta script k6 para smoke CI con checks de headers a baja tasa tras cambios de config CDN.
Cierre
El rendimiento CDN es una mezcla de hits y misses, no una URL caliente única. Varía claves de caché con realismo, valida headers bajo carga y trata picos de origen en bursts de miss como puerta de release.
Corre esta semana el escenario mixto hit/miss contra staging antes del próximo cambio de CDN o TTL—y anota dónde RPS origen cruza tu techo acordado.
¿Listo para optimizar el rendimiento de tu API?
Usa Performate para reproducir journeys conscientes de caché contra edges de staging sin improvisar bucles curl.