Express.js
Caching HTTP y de aplicación
Explica caché HTTP y de aplicación, keys, TTL, invalidación, privacidad, stampede y coordinación entre Express.js, CDN, Redis y base de datos.
- Última actualización
- Actualizada
- Nivel
- Profundización
Express.js
Explica caché HTTP y de aplicación, keys, TTL, invalidación, privacidad, stampede y coordinación entre Express.js, CDN, Redis y base de datos.
Una cache reutiliza un resultado para evitar trabajo repetido. A cambio introduce staleness, invalidación, memoria y nuevas decisiones de seguridad.
request
↓
cache hit → respuesta rápida
↓ miss
fuente de verdad → guardar → responderLa cache no es la fuente de verdad. Debe existir una estrategia para expiración, invalidación y fallos.
Controlada por headers HTTP.
Comparte respuestas públicas cerca del cliente.
Memoria local o store compartido como Redis.
PostgreSQL y sistema operativo ya cachean páginas; no es una cache de resultados de negocio.
Cada nivel tiene alcance y consistencia diferentes.
Headers principales:
Cache-Control define quién puede cachear y por cuánto tiempo.ETag identifica una representación.Last-Modified permite validación temporal.Vary indica qué headers cambian la representación.Ejemplo público:
response.set('Cache-Control', 'public, max-age=60, s-maxage=300');max-age aplica al cliente; s-maxage a caches compartidas.
response.set('Cache-Control', 'private, no-store');private evita caches compartidas; no-store pide no almacenar. No marques como público contenido personalizado, autenticado o multi-tenant sin una clave de cache segura.
if (request.fresh) {
response.sendStatus(304);
return;
}Express puede gestionar freshness con ETag y headers. Un 304 no incluye el body y permite reutilizar la copia del cliente.
Cache-aside:
const key = `tenant:${tenantId}:product:${productId}`;
const cached = await cache.get(key);
if (cached) return JSON.parse(cached);
const product = await products.findById(tenantId, productId);
if (product) await cache.set(key, JSON.stringify(product), { ttl: 60 });
return product;El scope por tenant debe formar parte de la key. Serializa una representación estable y valida datos antiguos.
Estrategias:
No existe invalidación perfecta y gratuita.
Muchas requests fallan cache a la vez y golpean la fuente.
Mitigaciones:
Si actualizas DB y el proceso cae antes de borrar cache, queda dato viejo. TTL limita duración; outbox/eventos pueden hacer invalidación más confiable.
Una invalidación antigua puede borrar una entrada nueva. Usa versiones o timestamps cuando importa.
Guardar “no existe” reduce consultas repetidas, pero un recurso creado después puede seguir pareciendo ausente. Usa TTL corto y scope seguro.
Local: muy rápida, sin red; cada instancia tiene contenido distinto y se pierde al reiniciar.
Distribuida: compartida y coordinada; añade latencia, coste y dependencia externa.
Una cache local puede funcionar para configuración inmutable; datos mutables multi-instancia suelen requerir estrategia adicional.
Catálogo de productos:
product.updated en outbox.No caches una response autorizada únicamente por URL:
/orders/123Debe incluir actor/tenant/policy o no usar cache compartida. Un error aquí filtra datos entre usuarios.
A veces conviene cachear datos base y aplicar autorización después, no la response final.
Define qué staleness tolera el producto:
La cache no debe decidir invariantes críticas.
No cachees 500 por defecto. 404 puede cachearse brevemente solo si el contrato lo permite. Respeta headers y evita convertir una caída temporal en fallo prolongado.
Mide:
Un hit ratio alto no prueba valor si la fuente ya era barata o los datos son incorrectos.
Vary y 304.Primero mide y corrige la fuente.
Reverse proxies y trust proxy explica cómo la infraestructura modifica IP, protocolo, host y seguridad de cookies.