Software Architecture
Caching y CDN
Explica caching y CDN desde ubicación, claves, frescura, invalidación, seguridad, stampedes y coherencia entre capas de caché.
- Última actualización
- Actualizada
- Nivel
- Aplicación
Software Architecture
Explica caching y CDN desde ubicación, claves, frescura, invalidación, seguridad, stampedes y coherencia entre capas de caché.
Una caché reutiliza resultados para reducir latencia y carga. A cambio introduce una segunda representación del dato, con reglas propias de frescura, invalidación, seguridad, memoria y recuperación.
Cachear no significa simplemente “guardar una respuesta para que sea más rápida”. Significa aceptar que durante un tiempo pueden coexistir:
fuente de verdad
+ copia reutilizableLa arquitectura debe definir:
Una caché bien diseñada acelera el sistema. Una caché añadida sin semántica puede producir resultados incorrectos y fallos difíciles de diagnosticar.
Supón un catálogo que recibe miles de lecturas por minuto y cambia pocas veces.
Sin caché:
cada request
→ consulta base
→ transforma datos
→ serializa respuestaLa aplicación repite el mismo trabajo y la base consume capacidad que podría reservarse para escrituras o consultas dinámicas.
Con caché:
request
→ clave
→ hit: devolver copia
→ miss: consultar origen, construir y guardarEl costo aparece cuando el precio cambia, el tenant tiene permisos distintos o millones de solicitudes fallan simultáneamente al expirar una entrada.
Pueden existir varias capas:
navegador
→ CDN
→ reverse proxy
→ caché distribuida
→ caché local
→ buffer pool de base
→ almacenamientoCada nivel tiene diferente:
Un dato puede estar fresco en Redis y antiguo en CDN. Diseñar solo una capa no garantiza coherencia completa.
Una caché aporta valor cuando:
Puede no aportar cuando:
La caché normalmente no posee la verdad.
PostgreSQL → autoridad
Redis → copia aceleradora
CDN → representación públicaSi Redis se pierde, el sistema debe poder:
Usar la caché como única copia cambia su categoría: entonces necesita durabilidad, backup, consistencia y recuperación propias.
La aplicación administra lectura y escritura.
1. buscar clave
2. si existe, devolver
3. si no, consultar origen
4. guardar con TTL
5. devolverEjemplo:
async function getProduct(
tenantId: string,
productId: string,
): Promise<ProductView | null> {
const key = `product:v2:${tenantId}:${productId}`;
const cached = await cache.get(key);
if (cached) return JSON.parse(cached);
const product = await repository.findById(tenantId, productId);
if (!product) {
await cache.set(key, "null", { ttlSeconds: 30 });
return null;
}
const view = toProductView(product);
await cache.set(key, JSON.stringify(view), { ttlSeconds: 300 });
return view;
}En producción hay que considerar errores de caché, stampede, tamaño, observabilidad y autorización.
El cliente pide el dato al sistema de caché y este carga el origen al faltar.
Ventajas:
Costos:
La escritura actualiza origen y caché antes de confirmar.
write
→ persistir origen
→ actualizar caché
→ responderReduce misses después de escribir, pero añade latencia y riesgo de error parcial. La fuente de verdad y el orden deben quedar claros.
La caché acepta el cambio y persiste después.
Beneficios:
Riesgos:
Debe reservarse para dominios donde esas garantías estén diseñadas explícitamente.
Se renueva una entrada antes de expirar si tiene alta probabilidad de uso.
Reduce misses visibles, pero puede refrescar datos innecesarios y aumentar carga constante.
Time-to-live define cuánto permanece una entrada.
No debe elegirse arbitrariamente.
Relaciona:
TTL
↔ costo del miss
↔ daño de staleness
↔ frecuencia de cambio
↔ capacidad del origenEjemplo:
Un TTL no garantiza que el dato se refresque exactamente al vencer; solo limita su reutilización según la capa.
Invalidar significa retirar o reemplazar una copia cuando cambia la autoridad.
Simple y resistente a eventos perdidos.
Costos:
Después de confirmar origen, se elimina la entrada.
La siguiente lectura reconstruye.
Riesgo:
Se actualiza la entrada inmediatamente.
Reduce miss, pero duplica lógica de representación y puede fallar después del commit.
El propietario publica un cambio y consumidores invalidan.
Aporta desacoplamiento, pero el evento puede retrasarse o duplicarse. Combina con TTL como red de seguridad.
La versión forma parte de la clave:
catalog:v17:tenant-1:category-10Cambiar versión abandona entradas antiguas sin purga global. Aumenta memoria temporal.
Escenario:
La entrada queda stale aunque se invalidó.
Mitigaciones:
No existe una solución universal sin costo.
Se sirve una copia antigua durante una ventana mientras un proceso la renueva.
fresh window
→ servir copia
stale window
→ servir copia + refrescar
after stale
→ esperar origen o fallarMejora disponibilidad y tail latency para contenido donde datos antiguos siguen siendo válidos.
No debe utilizarse para decisiones críticas como autorización o cobro sin validación adicional.
Una clave debe incluir toda dimensión que cambia el resultado.
Posibles dimensiones:
Ejemplo inseguro:
/product/123Ejemplo más explícito:
product:v3:tenant-9:branch-2:cop:es-CO:123Una dimensión ausente puede mezclar información entre usuarios o tenants.
Nunca asumas que una respuesta autenticada puede compartirse.
Opciones:
private.La seguridad debe evaluarse en navegador, CDN, proxy y aplicación.
HTTP ya ofrece un protocolo de reutilización.
Directivas comunes:
max-age.s-maxage.public.private.no-store.no-cache.stale-while-revalidate.stale-if-error.no-cache no significa necesariamente no almacenar; exige revalidación antes de reutilizar. no-store evita almacenamiento.
Representa una versión de la respuesta.
ETag: "catalog-8f31"El cliente revalida:
If-None-Match: "catalog-8f31"Si no cambió, el servidor responde 304 Not Modified, evitando payload.
Permite revalidación basada en tiempo, con menor precisión que un ETag fuerte.
Indica qué headers alteran la representación.
Vary: Accept-Encoding, Accept-LanguageUn Vary demasiado amplio destruye hit ratio; uno incompleto sirve respuestas incorrectas.
Una CDN almacena contenido cerca de usuarios.
Es ideal para:
Beneficios:
Costos:
La estrategia más segura usa nombres inmutables:
app.8f31a2.jsSe publican con TTL largo. Una nueva versión genera otro nombre, por lo que no necesita purga inmediata.
Ejemplo de catálogo público:
Cache-Control: public, s-maxage=300, stale-while-revalidate=60
Vary: Accept-EncodingDebe verificarse que:
Muchas solicitudes encuentran un miss simultáneo y reconstruyen la misma clave.
entrada expira
→ 10.000 misses
→ 10.000 queries al origen
→ saturaciónMitigaciones:
Una sola operación reconstruye; las demás esperan el mismo resultado.
El primer proceso adquiere lock. Debe tener timeout y fencing adecuados.
Se añade variación para evitar expiraciones sincronizadas.
Entradas calientes se renuevan antes.
Se sirve copia antigua mientras una operación actualiza.
Solicitudes repetidas a claves inexistentes atraviesan siempre al origen.
Mitigaciones:
No caches errores temporales como ausencia permanente.
Una clave extremadamente popular concentra carga en un nodo o partición.
Mitigaciones:
Duplicar una hot key complica invalidación; debe medirse.
La caché puede eliminar entradas antes del TTL por falta de memoria.
Políticas como LRU, LFU o aproximaciones priorizan diferentes patrones.
Consecuencias:
Mide evictions, memoria y working set.
Una entrada muy grande:
A veces conviene cachear piezas pequeñas; otras, una respuesta completa evita múltiples round trips. Decide por access pattern.
Beneficios:
Costos:
Beneficios:
Costos:
Una arquitectura puede combinar ambas, aceptando más staleness.
Define comportamiento antes del incidente.
La aplicación consulta la base.
Riesgo: el origen recibe todo el tráfico y puede caer.
Se rechaza trabajo.
Apropiado si consultar origen sería peligroso o la caché contiene estado imprescindible.
Se sirve copia local antigua, respuesta parcial o modo reducido.
Aplica circuit breaker, límites de concurrencia y load shedding para evitar avalanche al origen.
Requisitos:
Diseño posible:
CDN
→ catálogo público por tenant y locale
Redis
→ vistas por categoría y sucursal
PostgreSQL
→ autoridad de producto, precio e inventarioCachear autorización puede reducir carga, pero el daño de staleness es alto.
Diseño:
Una revocación debe tener un tiempo máximo de propagación documentado.
Un hit ratio alto no demuestra que la caché sea correcta. Puede estar sirviendo información antigua de manera eficiente.
Origen se actualiza y caché falla. La entrada antigua permanece. Usa invalidación, TTL y observabilidad.
La caché no se invalida. TTL limita daño y reconciliación detecta divergencia.
La deserialización falla. Elimina y reconstruye; alerta si es frecuente.
Una instancia nueva no entiende entradas antiguas. Versiona claves o soporta transición.
Se produce cold start. Protege origen con warm-up, coalescing y límites.
Una respuesta privada cacheada puede seguir visible. Diseña versión o no caches ese nivel.
Añade complejidad sin resolver un cuello. Primero identifica costo repetido.
Produce colisiones, fugas o respuestas incorrectas. Revisa todas las dimensiones.
Datos con riesgos distintos no deben compartir política.
Una lectura puede repoblar valor antiguo después de invalidar.
Su caída puede derribar el origen. Diseña degradación y límites.
Ignora frescura, seguridad y costo de miss.
Un fallo temporal se convierte en indisponibilidad persistente.
Conviene cuando el trabajo es reutilizable, costoso y tolera una política explícita de frescura.
No conviene cuando la corrección exige estado autoritativo en cada operación o el costo de mantener coherencia supera el ahorro.
no-cache y no-store?no-cache permite almacenar pero exige revalidar; no-store evita almacenar.Load balancing, proxies y API gateways explica cómo el tráfico llega a las instancias, cómo se distribuye y qué responsabilidades deben permanecer fuera de la capa de entrada.