Que es la Cache
La cache es un mecanismo que almacena temporalmente copias de datos en un lugar de acceso rapido. Reduce los accesos a la fuente de datos original (base de datos, API, etc.) y acorta el tiempo de respuesta.
Efecto de la Cache: Si una consulta a la base de datos tarda 100ms, la obtencion desde la cache puede completarse en menos de 1ms.
Capas de Cache
Cache del Navegador
↓
Cache de CDN
↓
Cache de Aplicacion (Redis, etc.)
↓
Cache de Base de Datos
↓
Base de Datos
Patrones de Cache
Cache-Aside
La aplicacion gestiona directamente la cache y la base de datos.
async function getUser(userId) {
// 1. Verificar cache
const cached = await cache.get(`user:${userId}`);
if (cached) {
return JSON.parse(cached);
}
// 2. Cache miss: obtener de la BD
const user = await db.users.findById(userId);
// 3. Guardar en cache
await cache.setex(`user:${userId}`, 3600, JSON.stringify(user));
return user;
}
Ventajas: Simple, resistente a fallos Desventajas: Latencia en cache miss
Read-Through
La propia cache se encarga de obtener los datos.
// Configuracion de la libreria de cache
const cache = new Cache({
loader: async (key) => {
// Se llama automaticamente en cache miss
const userId = key.replace('user:', '');
return await db.users.findById(userId);
}
});
// Uso (simple!)
const user = await cache.get(`user:${userId}`);
Write-Through
Al escribir, se actualizan simultaneamente la cache y la BD.
async function updateUser(userId, data) {
// Actualizar BD
const user = await db.users.update(userId, data);
// Actualizar cache simultaneamente
await cache.setex(`user:${userId}`, 3600, JSON.stringify(user));
return user;
}
Ventajas: Alta consistencia de datos Desventajas: Aumento de latencia en escritura
Write-Behind
Se escribe inmediatamente en la cache y la reflexion en la BD se hace de forma asincrona.
async function updateUser(userId, data) {
// Actualizar cache inmediatamente
await cache.setex(`user:${userId}`, 3600, JSON.stringify(data));
// Agregar escritura a la BD a la cola
await writeQueue.add({ userId, data });
return data;
}
// Worker en segundo plano
writeQueue.process(async (job) => {
await db.users.update(job.userId, job.data);
});
Ventajas: Escritura rapida Desventajas: Riesgo de perdida de datos
Invalidacion de Cache
TTL (Time To Live)
Expira automaticamente despues de un tiempo determinado.
// Expira despues de 60 segundos
await cache.setex('key', 60, 'value');
Invalidacion Basada en Eventos
Se elimina explicitamente la cache al actualizar datos.
async function updateUser(userId, data) {
await db.users.update(userId, data);
// Invalidar caches relacionadas
await cache.del(`user:${userId}`);
await cache.del(`user:${userId}:profile`);
await cache.del(`users:list`);
}
Invalidacion Basada en Patrones
// Eliminar todas las caches relacionadas con el usuario
const keys = await cache.keys('user:123:*');
await cache.del(...keys);
Problemas de Cache y Soluciones
Cache Stampede (Avalancha)
Problema donde muchas solicitudes tienen cache miss simultaneamente.
// Solucion: Usar bloqueo
async function getWithLock(key, loader) {
const cached = await cache.get(key);
if (cached) return JSON.parse(cached);
// Obtener bloqueo
const lockKey = `lock:${key}`;
const locked = await cache.set(lockKey, '1', 'NX', 'EX', 10);
if (!locked) {
// Otro proceso esta cargando → esperar y reintentar
await sleep(100);
return getWithLock(key, loader);
}
try {
const data = await loader();
await cache.setex(key, 3600, JSON.stringify(data));
return data;
} finally {
await cache.del(lockKey);
}
}
Recalculo Probabilistico Temprano
Actualiza la cache probabilisticamente antes de que expire el TTL.
async function getWithProbabilisticRefresh(key, loader, ttl) {
const data = await cache.get(key);
const remainingTtl = await cache.ttl(key);
// Si queda poco TTL, recalcular probabilisticamente
if (data && remainingTtl < ttl * 0.1) {
if (Math.random() < 0.1) {
// 10% de probabilidad de actualizacion en segundo plano
loader().then(newData => {
cache.setex(key, ttl, JSON.stringify(newData));
});
}
}
if (data) return JSON.parse(data);
const newData = await loader();
await cache.setex(key, ttl, JSON.stringify(newData));
return newData;
}
Diseno de Claves de Cache
// Buen diseno de clave
const key = `user:${userId}:profile:v2`;
// Componentes:
// - Prefijo: Tipo de entidad
// - Identificador: ID unico
// - Subrecurso: Datos especificos
// - Version: Compatibilidad al cambiar esquema
Guia de Diseno de TTL
| Tipo de Datos | TTL | Razon |
|---|---|---|
| Contenido estatico | 1 dia - 1 semana | Casi nunca cambia |
| Perfil de usuario | 1 - 24 horas | Baja frecuencia de cambio |
| Informacion de configuracion | 5 - 30 minutos | Se actualiza moderadamente |
| Datos en tiempo real | 1 - 5 minutos | Cambia frecuentemente |
| Sesion | 30 min - 24 horas | Balance entre seguridad y UX |
Resumen
La cache es una tecnica fundamental para la optimizacion del rendimiento. Entendiendo patrones como Cache-Aside y Write-Through, y disenando estrategias apropiadas de TTL e invalidacion, puede construir sistemas rapidos y escalables. Considere el balance entre la complejidad y los beneficios de la cache al implementarla.
← Volver a la lista