Lección 1 de 312 min
Caché: Redis y Cloudflare Cache
Aciertos y fallos, TTL, invalidación y expulsión, en memoria con Redis y en el edge con Cloudflare.
Imagina la ficha de un producto en una tienda online. Cada vez que alguien la abre, el servidor consulta la base de datos, calcula el precio con descuentos y monta la respuesta. Si mil personas la abren en un minuto, hace mil veces exactamente el mismo trabajo para obtener exactamente el mismo resultado.
Una caché guarda una copia de ese resultado en un sitio más rápido y más cercano, y la reutiliza mientras siga valiendo. En esta lección verás cómo se mide si una caché funciona, cómo se mantiene al día, qué hace cuando se llena y dos lugares muy distintos donde vivir: la memoria de un servidor con Redis y el borde de la red con Cloudflare.
Cómo funciona una caché
Aciertos, fallos y tasa de aciertos
Un acierto ahorra el viaje al origen; un fallo lo paga y, de paso, llena la caché.
- Acierto:
- hit
- Fallo:
- miss
Cuando pides un dato a la caché pueden pasar dos cosas. Si está, es un acierto (hit) y la respuesta llega en muy poco tiempo. Si no está, es un fallo (miss): hay que pedirlo al origen (la base de datos, una API, el servidor web), que es más lento, y normalmente se guarda una copia para la próxima vez.
La métrica clave es la tasa de aciertos (hit ratio): aciertos divididos entre peticiones totales. Con un 90 % de aciertos, solo una de cada diez peticiones llega al origen. Además, cada dato guardado lleva un TTL (time to live): el tiempo durante el que se considera válido antes de caducar.
La forma más habitual de usar una caché desde tu código, consultarla primero y rellenarla al fallar, se llama cache-aside y la verás en el módulo de arquitectura.
Úsalo cuando
Cuando el mismo dato se lee muchas más veces de las que cambia y calcularlo o traerlo cuesta: consultas pesadas, APIs externas lentas, páginas que ven miles de personas.
Ten en cuenta
Una caché solo ayuda si las peticiones se repiten. Si cada usuario pide cosas distintas, la tasa de aciertos será baja y solo añadirás complejidad. Mide antes y después.
Caché: 1 ms Origen: 80 ms 1000 peticiones
Tasa de aciertos 0 % → 1000 × 80 ms = 80 ms de media
Tasa de aciertos 90 % → 900 × 1 ms + 100 × 80 ms = 8,9 ms de media
Tasa de aciertos 99 % → 990 × 1 ms + 10 × 80 ms = 1,8 ms de mediaMantener la caché al día y hacerle sitio
Una caché plantea dos preguntas: ¿cuándo deja de valer una copia? (invalidación) y, como la memoria es limitada, ¿qué copia se tira cuando no cabe otra? (expulsión).
Caducidad por TTL
Cada copia caduca sola al cabo de un tiempo, sin que nadie tenga que avisar.
Con un TTL decides de antemano cuánto tiempo puede estar desactualizado un dato. Al guardar el tipo de cambio del euro con un TTL de 10 minutos, aceptas que alguien vea un valor de hace, como mucho, 10 minutos. Pasado ese tiempo, la siguiente petición encuentra la entrada caducada, va al origen y la renueva.
Es la estrategia más simple y robusta: aunque te olvides de invalidar algo, el error se corrige solo. El ajuste es un equilibrio: un TTL largo da más aciertos, pero datos más viejos; uno corto, datos frescos, pero más viajes al origen.
Úsalo cuando
Cuando puedes tolerar cierto retraso y conoces más o menos cada cuánto cambia el dato: cotizaciones, el tiempo, listados que se regeneran cada hora.
Ten en cuenta
Si muchas claves populares caducan a la vez, todas las peticiones van al origen en el mismo instante. Añadir un poco de variación aleatoria al TTL de cada clave reparte esa carga.
SET tipo-cambio:EUR-USD "1.08" EX 600 # caduca en 600 s
TTL tipo-cambio:EUR-USD # (integer) 597: segundos que le quedanInvalidación explícita
Cuando el dato cambia, borras o actualizas la copia en ese mismo momento.
En lugar de esperar a que caduque, el código que modifica el dato también borra su copia en la caché. Si el administrador cambia el precio del producto 42, el mismo proceso que actualiza la base de datos ejecuta DEL producto:42, y la siguiente lectura trae el precio nuevo.
Hay una frase famosa de Phil Karlton: «solo hay dos cosas difíciles en informática: invalidar la caché y ponerle nombre a las cosas». Lo difícil es saber todas las copias que dependen de un dato: el producto aparece en su ficha, en la búsqueda, en la portada y en el carrito.
Úsalo cuando
Cuando un dato desactualizado causa problemas reales (precios, stock, permisos) y controlas el código que lo modifica.
Ten en cuenta
Combínala con un TTL como red de seguridad: si una invalidación falla o se te olvida un caso, la copia acabará caducando igualmente. Y ojo con el orden: actualiza primero el origen y después borra la caché.
async function cambiarPrecio(id, precio) {
await db.query("UPDATE productos SET precio = $1 WHERE id = $2", [precio, id]);
// Borra todas las copias que dependen del producto
await redis.del(`producto:${id}`, "portada:destacados");
}LRU: el menos usado recientemente
Cuando no cabe nada más, expulsa la entrada que lleva más tiempo sin usarse.
- Siglas:
- Least Recently Used
LRU (Least Recently Used) parte de una intuición: lo que has usado hace poco probablemente lo volverás a usar pronto. Cada vez que lees una entrada, pasa al principio de la fila; cuando hace falta sitio, se tira la del final.
Es la política por defecto en muchísimas cachés porque funciona bien con la mayoría de cargas y es barata de implementar. Su punto débil es el escaneo secuencial: si un proceso recorre miles de claves una sola vez (un informe, una exportación), todas ellas pasan a ser «recientes» y expulsan a las que de verdad eran populares.
Úsalo cuando
Como opción por defecto, sobre todo cuando lo que interesa cambia con el tiempo: noticias, sesiones, lo último que ha visitado cada usuario.
Ten en cuenta
Un recorrido masivo puede vaciar la caché de lo valioso. Pruébalo en el simulador con el patrón «escaneo secuencial».
LFU: el menos usado en total
Expulsa la entrada que menos veces se ha pedido, aunque se pidiera hace un momento.
- Siglas:
- Least Frequently Used
LFU (Least Frequently Used) lleva la cuenta de cuántas veces se usa cada entrada y, cuando hace falta sitio, expulsa la que menos usos acumula. Una clave que se pide constantemente resiste aunque pase un escaneo de miles de claves que solo se usan una vez.
A cambio, tiene memoria larga: algo que fue muy popular la semana pasada puede quedarse ocupando sitio aunque ya nadie lo pida. Por eso las implementaciones reales, como la de Redis, hacen que los contadores decaigan con el tiempo.
Úsalo cuando
Cuando hay un conjunto estable de claves muy populares (el catálogo más vendido, la configuración) y procesos que recorren muchos datos de una sola vez.
Ten en cuenta
Una clave nueva empieza con pocos usos y es la primera candidata a salir, aunque vaya a ser popular. Si tus datos «de moda» cambian rápido, LRU suele ir mejor.
Pruébalo
En el simulador, elige «escaneo secuencial» con capacidad 4 y compara LRU con LFU. Después vuelve a «popular» y baja el TTL hasta que empiecen a aparecer entradas caducadas.
Interactivo
Simulador de caché
Elige un patrón de peticiones, el tamaño de la caché, el TTL y la política de expulsión, y mira qué peticiones aciertan.
Peticiones (una por segundo). Pulsa una para ver la caché en ese instante.
Caché después de la petición t = 0 s
- Aúltimo uso: t = 0 scaduca en t = 30 s
- Hueco libre
- Hueco libre
t = 0 s · GET A → MISS: no estaba. Se pide al origen (80 ms) y se guarda.
- Aciertos
- 13/24
- Tasa de aciertos
- 54 %
- Latencia media
- 37.2 ms
Valores ilustrativos: 1 ms si responde la caché, 80 ms si hay que ir al origen. Sin caché, todas tardarían 80 ms.
Unas pocas claves se llevan casi todas las peticiones: incluso una caché pequeña acierta mucho. Prueba a bajar el TTL o la capacidad.
Dónde vive la caché: Redis y Cloudflare
Redis
Un almacén clave-valor en memoria, compartido por todos tus servidores.
- Tipo:
- Clave-valor en memoria
- Alternativa:
- Valkey
Redis guarda los datos en RAM y responde en muy poco tiempo, normalmente por debajo del milisegundo dentro de la misma red. Como es un servicio aparte, todas las instancias de tu API ven la misma caché: lo que una guarda, otra lo encuentra. Además de textos admite listas, conjuntos, hashes y contadores.
Para usarlo como caché guardas cada valor con SET clave valor EX segundos y lo lees con GET. Cuando la memoria llega al límite que marca maxmemory, la directiva maxmemory-policy decide qué hacer: allkeys-lru y allkeys-lfu expulsan entre todas las claves, volatile-lru solo entre las que tienen TTL, y noeviction, el valor por defecto, rechaza las escrituras nuevas con un error.
En 2024 Redis cambió su licencia y, como respuesta, nació Valkey, un fork compatible mantenido bajo la Linux Foundation. Muchos servicios gestionados en la nube ofrecen ya uno u otro.
Úsalo cuando
Para cachear resultados de la base de datos o de APIs en el backend, sesiones y contadores compartidos entre varias instancias.
Ten en cuenta
Si lo usas como caché, configura maxmemory y una política de expulsión: con noeviction se llenará y empezará a dar errores. Y trátalo como prescindible: si se reinicia y pierde los datos, tu app debe seguir funcionando, solo que más despacio.
SET producto:42 '{"nombre":"Gazpacho","precio":4.5}' EX 300
GET producto:42 # el JSON, durante 5 minutos
TTL producto:42 # segundos que le quedan
DEL producto:42 # invalidación explícita
# redis.conf: Redis como caché pura
maxmemory 256mb
maxmemory-policy allkeys-lruCloudflare Cache
Una caché en el edge: copias en centros de datos cerca de cada usuario.
- Tipo:
- CDN / caché en el edge
- Cabecera:
- cf-cache-status
Cloudflare se coloca entre tus usuarios y tu servidor. Tiene centros de datos repartidos por todo el mundo y cada uno guarda copias de tus respuestas: es una caché en el edge (el borde de la red). Un usuario de Lima recibe la copia del centro más cercano sin que la petición llegue a tu servidor. La cabecera cf-cache-status de la respuesta te dice qué pasó: HIT, MISS, EXPIRED, DYNAMIC…
Por defecto cachea los archivos estáticos según su extensión (imágenes, CSS, JavaScript), pero no el HTML, y respeta la cabecera Cache-Control que envía tu servidor. Con las Cache Rules cambias ese comportamiento por ruta: cachear el HTML del blog, saltarse la caché en /api/ o fijar otro TTL en el edge.
Cada copia se identifica por su cache key, que por defecto incluye la URL completa con su query string: ?utm_source=x y ?utm_source=y son dos copias distintas. Cuando publicas cambios, puedes purgar (borrar) copias concretas por URL o todo el sitio de una vez.
Úsalo cuando
Para contenido que es igual para todos los usuarios y se pide desde muchos lugares: archivos estáticos, imágenes, páginas públicas, respuestas de API públicas.
Ten en cuenta
Nunca caches en el edge respuestas personalizadas (el carrito, el perfil) con una cache key que no distinga al usuario: le mostrarías los datos de uno a otro. Usa Cache-Control: private o una regla que se salte la caché.
# Archivo con hash en el nombre: puede cachearse un año
Cache-Control: public, max-age=31536000, immutable
# HTML público: el navegador revalida, el edge guarda 5 minutos
Cache-Control: public, max-age=0, s-maxage=300
# Datos del usuario: que no lo guarde ninguna caché compartida
Cache-Control: private, no-storeCache API en Cloudflare Workers
Leer y escribir la caché del edge desde tu propio código.
Cloudflare Workers ejecuta tu JavaScript en el edge, delante de tu servidor. Desde ahí tienes la Cache API: caches.default.match(peticion) busca una respuesta guardada y cache.put(peticion, respuesta) guarda una. Así decides tú qué se cachea, con qué clave y durante cuánto tiempo.
Es útil cuando las reglas declarativas no bastan: componer una respuesta a partir de una API externa lenta, normalizar la URL antes de usarla como clave o cachear el resultado de una petición POST convirtiéndola en una clave GET.
Úsalo cuando
Cuando ya usas Workers y necesitas lógica de caché a medida que no se puede expresar con Cache Rules.
Ten en cuenta
La Cache API trabaja por centro de datos: lo que guardas en uno no aparece en los demás. Y cache.put respeta el Cache-Control de la respuesta que guardas, así que fíjalo tú si el origen no lo envía.
export default {
async fetch(peticion, env, ctx) {
const cache = caches.default;
let respuesta = await cache.match(peticion);
if (!respuesta) {
const origen = await fetch(peticion);
respuesta = new Response(origen.body, origen); // copia editable
respuesta.headers.set("Cache-Control", "s-maxage=300");
// Guarda la copia sin hacer esperar al usuario
ctx.waitUntil(cache.put(peticion, respuesta.clone()));
}
return respuesta;
},
};Nota
Las cachés se apilan: el navegador guarda su copia (según max-age), el edge la suya (según s-maxage y tus reglas) y el backend consulta Redis antes que la base de datos. Cada capa que acierta ahorra trabajo a todas las de detrás.
Interactivo
Tres sitios donde guardar la caché
Activa o desactiva opciones para compararlas cara a cara.
| Característica | Memoria del proceso | Redis | Cloudflare Cache |
|---|---|---|---|
| Dónde vive | En la RAM de cada instancia de tu app | Un servidor aparte, cerca de tu backend | Centros de datos de Cloudflare por todo el mundo |
| Compartida entre servidores | No: cada instancia tiene la suya | Sí | Sí, aunque cada centro tiene sus copias |
| Qué suele guardar | Configuración, datos pequeños y muy leídos | Resultados de consultas, sesiones, contadores | Respuestas HTTP completas: estáticos, HTML público |
| Cómo se invalida | TTL; al reiniciar se pierde todo | TTL con EX y borrado con DEL | Cache-Control, Cache Rules y purga |
| Se acerca al usuario | No | No: acelera el backend | Sí: responde desde el centro más cercano |
Ponlo en práctica
Saber qué es cada cosa es la mitad del trabajo. La otra mitad es reconocer cuándo usarla.
Interactivo
¿Qué usarías?
Lee cada situación y elige la opción que mejor la resuelve. Fallar aquí no cuenta: es para practicar.
El administrador de la tienda cambia precios en cualquier momento y los clientes deben ver el precio nuevo al instante, no dentro de diez minutos.
Para recordar
- Una caché rinde cuando las lecturas se repiten: mídelo con la tasa de aciertos.
- El TTL limita cuánto puede estar desactualizado un dato; la invalidación explícita lo corrige al instante. Lo habitual es combinar ambos.
- LRU expulsa lo que lleva más tiempo sin usarse y sufre con los escaneos; LFU protege lo popular, pero tarda en olvidar.
- Redis es una caché compartida en memoria para el backend; Cloudflare guarda respuestas HTTP cerca del usuario.
- Nunca guardes en una caché compartida una respuesta personalizada con una clave que no distinga al usuario.
Comprueba lo aprendido
Cuestionario
Responde las 5 preguntas para completar la lección.
1.Leer de la caché tarda 1 ms y del origen 80 ms. Con una tasa de aciertos del 90 %, ¿cuál es la latencia media aproximada?
2.¿Por qué un escaneo secuencial perjudica tanto a una caché LRU?
3.Tu Redis de caché empieza a devolver errores al escribir cuando se llena la memoria. ¿Qué es lo más probable?
4.Pones Cloudflare delante de tu web y ves que las páginas HTML siempre llegan con
cf-cache-status: DYNAMIC. ¿Por qué?5.¿Qué diferencia hay entre
max-ageys-maxageenCache-Control?