Caching: El Código Más Rápido Es el que No Corre
Caching es la optimización de rendimiento más impactante. Una respuesta servida del caché es 10-1000x más rápida que una generada desde cero. Pero caching introduce complejidad: datos stale, invalidación y problemas de consistencia.
Las Capas de Caché
Usuario → Caché Browser → CDN Edge → Caché Servidor (Redis) → App → BD
~0ms ~20ms ~1ms ~50ms ~100ms
Caché del Browser (Cache-Control)
Cache-Control: public, max-age=3600 # 1 hora
Cache-Control: public, max-age=31536000, immutable # 1 año (assets con hash)
Cache-Control: no-store # nunca cachear
Cache-Control: no-cache # cachea pero siempre revalida
| Directiva | Significado |
|---|
public | Cualquier caché puede almacenar |
private | Solo el browser puede cachear |
max-age=N | Cachear por N segundos |
s-maxage=N | Duración del caché CDN |
stale-while-revalidate=N | Servir stale por N segundos mientras busca nueva versión |
immutable | Contenido nunca cambia |
Qué Cachear Dónde
| Contenido | Estrategia | Cache-Control |
|---|
| Assets estáticos | Caché largo + hash en nombre | public, max-age=31536000, immutable |
| HTML estático | CDN con revalidación | public, s-maxage=3600, stale-while-revalidate=600 |
| Respuestas API públicas | Caché CDN corto | public, s-maxage=60 |
| Respuestas específicas del user | Private o no-store | private, no-cache |
Caché Server-Side (Redis)
async function getProduct(id) {
const cached = await redis.get(`product:${id}`);
if (cached) return JSON.parse(cached);
const product = await db.products.findById(id);
await redis.set(`product:${id}`, JSON.stringify(product), 'EX', 300);
return product;
}
async function updateProduct(id, data) {
await db.products.update(id, data);
await redis.del(`product:${id}`); // invalida el caché
}
Invalidación de Caché (La Parte Difícil)
- Basada en TTL: Caché expira después de N segundos.
- Basada en eventos: Invalidar cuando datos cambian.
- Claves versionadas:
product:42:v3. - Stale-while-revalidate: Sirve stale inmediatamente, actualiza en background.