Fundamentos del caché en Redis: patrones, trampas y conceptos para entrevistas
El cacheo con Redis en Node.js: caché por separado, TTL, protección contra saturación y políticas de eliminación, además de las preguntas de entrevista sobre diseño de sistemas relacionadas con ellos.
Fuentes: Redis Isn’t Just a Cache: The Concepts Every Backend Developer Should Know
¿Por qué necesitamos un caché si ya tenemos una base de datos?
Imagínese un punto de extremo en su aplicación que se ve así:
GET /products/123
Un cliente accede a esa ruta, y su servidor consulta MongoDB:
API → MongoDB → Product
La base de datos devuelve el registro del producto, y todo funciona bien.
Ahora imagine que 10,000 personas solicitan ese mismo producto más o menos al mismo tiempo.
Terminaría repitiendo exactamente el mismo proceso miles de veces:
Request
↓
Database
↓
Same Product
Su base de datos sigue realizando el mismo trabajo una y otra vez para las mismas consultas.
¿No sería mejor si la respuesta pudiera guardarse en algún lugar?
Esencialmente, para eso sirve el cachéing.
¿Qué es el cachéing?
Un caché es una capa de almacenamiento más rápida que mantiene los datos solicitados con frecuencia por un breve período de tiempo, para que su aplicación no se vea obligada a acceder al almacenamiento de datos principal, que es más lento, en cada ocasión.
En lugar del flujo habitual:
Request → Database → Response
introducimos un atajo:
Request
↓
Cache
↓
Hit? ── Yes → Response
│
No
↓
Database
↓
Cache
↓
Response
El objetivo no es eliminar por completo la base de datos.
Sino reducir las operaciones en la base de datos que no es necesario realizar dos veces.
Acceso al caché exitoso vs. fallo en el caché
Estos dos conceptos surgen constantemente, por lo que vale la pena conocerlos bien.
Acceso al caché exitoso
Esto ocurre cuando los datos que busca ya se encuentran en el caché.
Request
↓
Redis
↓
Found ✅
↓
Response
Fallo en el caché
Esto ocurre cuando el caché no contiene nada para usted.
Request
↓
Redis
↓
Not Found ❌
↓
Database
↓
Store in Redis
↓
Response
Una métrica útil aquí es el índice de accesos exitosos al caché:
Cache Hit Ratio =
Cache Hits / Total Cache Requests
Una proporción más alta suele indicar que se está atendiendo más tráfico directamente desde la caché en lugar de desde la base de datos subyacente.
No obstante, un número mayor no significa automáticamente éxito. Si la caché está llena de entradas obsoletas o de poco valor, una alta tasa de aciertos no hace que sea útil.
¿Entonces, qué papel juega Redis?
Redis es un almacén de datos en memoria que se utiliza con frecuencia para la caché, aunque sus capacidades van mucho más allá de ese único caso de uso.
Ofrece una variedad de estructuras de datos, entre las que se incluyen:
- Cadenas de texto
- Hashes
- Listas
- Conjuntos
- Conjuntos ordenados
- Flujos
- JSON
- Además de otros tipos especializados
Para una caché sencilla, se puede pensar en ella de la siguiente manera:
Key Value
────────────────────────────
product:123 Product data
user:42 User data
Dado que Redis almacena datos frecuentemente utilizados en memoria, las lecturas desde la caché pueden ser extremadamente rápidas.
Redis en una aplicación Node.js
Para utilizar el cliente node-redis, debes instalarlo con:
npm install redis
Configurar una conexión básica solo requiere un par de líneas:
import { createClient } from "redis";
const redis = createClient();
await redis.connect();
A partir de ahí, almacenar y recuperar valores es sencillo:
await redis.set("user:42", "Hamza");
const user = await redis.get("user:42");
La documentación oficial de Redis indica actualmente a node-redis como el cliente recomendado para proyectos Node.js.
No obstante, conocer los comandos SET y GET solo te lleva hasta cierto punto.
La verdadera pregunta que merece respuesta es:
¿Cómo diseñas realmente una aplicación basada en Redis?
El patrón más importante: Cache-Aside
Si hay un patrón que todo desarrollador de backend debería aprender primero, es Cache-Aside.
En este patrón, la propia aplicación es responsable de gestionar el caché.
Request
↓
Check Redis
↓
┌───────────────┐
│ Cache Hit? │
└───────────────┘
↓ Yes ↓ No
Response Database
↓
Redis
↓
Response
Una versión simplificada en código se ve así:
const cached = await redis.get(key);
if (cached) return JSON.parse(cached);const product = await Product.findById(id);await redis.set(key, JSON.stringify(product), {
EX: 300
});
La opción EX: 300 establece un TTL de 300 segundos en la entrada cachée.
Cache-Aside es, sin duda, el patrón de caché más utilizado con Redis.
TTL: El caché no debe durar para siempre
Imaginemos que hemos cachéado lo siguiente:
product:123
¿Qué debería suceder si el precio del producto se actualiza posteriormente?
Sin un plazo de vencimiento, los usuarios podrían seguir viendo el precio antiguo indefinidamente.
Esa es la razón del TTL, o Time To Live.
Product stored
↓
TTL = 300 seconds
↓
Expires
↓
Next request → Database → Fresh cache
El TTL impone un límite al tiempo durante el cual se puede servir datos obsoletos.
Aun así, resista la tentación de elegir un TTL excesivamente largo.
El valor correcto depende por completo de hasta qué punto su aplicación puede permitirse la obsolescencia de los datos.
Invalidación de caché: el famoso problema difícil
Considere este escenario:
Database:
price = ₹799
Redis:
price = ₹799
Luego se actualiza la base de datos:
Database:
price = ₹899
Pero Redis sigue conservando:
price = ₹799
En este punto, su aplicación está sirviendo datos inconsistentes según de dónde los lea.
Una solución común es limpiar la caché cada vez que cambien los datos subyacentes:
await Product.findByIdAndUpdate(id, update);
await redis.del(`product:${id}`);
Después de eso, la siguiente solicitud sigue este camino:
Redis → Miss
↓
Database → ₹899
↓
Redis → Store fresh value
En los sistemas basados en el modelo cache-aside, borrar simplemente la entrada de caché afectada cada vez que se realiza una escritura suele ser más fácil y seguro que intentar mantener el caché en sincronía permanente con la base de datos.
Tres problemas de caché que deberías conocer
Estos constituyen excelentes temas de conversación en entrevistas.
1. Cache Stampede
Imagina que una clave muy utilizada llega a su fecha de vencimiento:
product:123 → EXPIRED
Inmediatamente después, llegan 1,000 solicitudes al mismo tiempo.
Todas ellas fallan al intentar acceder al caché.
Todas ellas terminan recayendo en la base de datos.
1000 requests
↓
1000 DB queries 💥
El caché estaba diseñado para proteger a la base de datos del sobrecargo, pero el evento de vencimiento provocó un aumento brusco en la carga.
Las formas típicas de evitar esto incluyen:
- El uso de bloqueos o solicitudes secuenciales, de modo que solo una solicitud rellene el caché mientras las demás esperan
La propia documentación de Redis aborda explícitamente las estrategias de protección contra ataques masivos en claves populares.
2. Penetración de caché
¿Qué sucede cuando los clientes siguen solicitando datos que simplemente no existen?
GET /users/999999
Redis devuelve “no encontrado”.
La base de datos responde “no se encontró”.
La siguiente solicitud idéntica repite exactamente el mismo ciclo.
Un actor malintencionado o un cliente defectuoso podría explotar esto para someter a la base de datos con búsquedas inútiles.
Una contramedida útil es el caché negativo: almacenar en caché el hecho de que algo está faltando:
user:999999 → NOT_FOUND
TTL → short
Con esto en su lugar, las búsquedas repetidas del mismo registro inexistente pueden responderse directamente desde Redis en lugar de acceder a la base de datos cada vez.
3. Avalancha de caché
Ahora imagine un gran lote de claves en caché que vencen aproximadamente al mismo tiempo.
Key A → expired
Key B → expired
Key C → expired
Key D → expired
↓
Massive DB traffic
Esta situación se conoce como avalancha de caché.
Una forma típica de reducir el riesgo es evitar asignar tiempos de vencimiento idénticos a grandes grupos de claves, por ejemplo, añadiendo un pequeño margen aleatorio a cada TTL.
Expulsión de caché: ¿Qué sucede cuando Redis se queda sin memoria?
La memoria de Redis no es infinita.
Cuando el uso de memoria alcanza el límite configurado, Redis comienza a eliminar claves según la regla de eliminación que hayas establecido. Redis incluye varios modos de eliminación, lo que te permite eliminar las entradas menos utilizadas en todo el espacio de claves o solo entre aquellas con fecha de vencimiento establecida, eliminar las entradas menos frecuentemente usadas en cualquiera de esos dos ámbitos, eliminar según las claves que estén más cerca de vencer, o rechazar por completo nuevas escrituras una vez que la memoria está llena.
LRU significa esencialmente:
Deshacerse de aquello que no se ha tocado en mucho tiempo.
LFU significa esencialmente:
Deshacerse de aquello que no se accede con frecuencia.
Elige el que mejor se adapte a tus patrones de acceso específicos.
Redis no es solo un caché
Este punto se plantea con frecuencia en las entrevistas.
Dado que Redis ofrece varias estructuras de datos integradas, puede servir para propósitos mucho más allá del caché:
Strings → Caching
Hashes → Objects / sessions
Lists → Queues
Sets → Unique collections
Sorted Sets → Leaderboards
Streams → Event processing
Por lo tanto, la afirmación de que:
"Redis es una base de datos de caché"
solo explica una parte de la historia.
Una descripción más precisa es:
Redis es un almacén de estructuras de datos en memoria, utilizado típicamente para el caché, pero también capaz de gestionar sesiones, colas, clasificaciones, limitación de velocidad, mensajería pub/sub y mucho más.
La documentación oficial de Redis sobre tipos de datos explica estas estructuras junto con otros tipos especializados que soporta.
¿Y qué pasa con la persistencia en Redis?
Dado que Redis está en memoria, ¿un reinicio borra todo?
No necesariamente.
Redis ofrece varias estrategias de persistencia:
- RDB — instantáneas periódicas en un momento determinado del conjunto de datos
- AOF — un registro que guarda cada operación de escritura
- RDB + AOF combinados
- Sin persistencia alguna
Si Redis solo funciona como caché, perder su contenido al reiniciarse suele ser aceptable, ya que la aplicación puede volver a cargarlo desde la base de datos principal.
Pero si Redis almacena datos que son realmente importantes por sí mismos, la configuración de persistencia se vuelve un problema mucho más grave.
Una trampa común en las entrevistas
“¿Por qué no almacenar todo simplemente en Redis?”
Porque la memoria de Redis es un recurso limitado y relativamente costoso en comparación con el almacenamiento persistente convencional.
Eso significa que no se debe usar la caché de forma indiscriminada.
Los buenos candidatos para el caché comparten estas características:
- Son solicitados con frecuencia
- Son costosos de obtener o calcular
- Se puede tolerar cierto grado de obsolescencia
- Vale la pena el costo en memoria por mantenerlos a mano
Trate a Redis como una capa de mejora del rendimiento que funciona junto a su sistema, y no como un sustituto total de su base de datos principal.
Caché local vs Caché distribuido
Imagínese una aplicación Node.js que se ejecuta en tres servidores separados:
Server A → Cache A
Server B → Cache B
Server C → Cache C
Cada uno de estos servidores acaba manteniendo su propio caché independiente.
Un usuario podría acceder al Servidor A y obtener un resultado del caché, y luego acceder al Servidor B y no encontrar el mismo dato en el caché.
La introducción de una instancia compartida de Redis cambia la situación:
┌→ Server A ─┐
Client ──────┼→ Server B ─┼→ Redis
└→ Server C ─┘
Ahora cada instancia de la aplicación accede al mismo caché compartido.
Esta ventaja del estado compartido es una razón importante por la cual Redis destaca especialmente cuando una aplicación se escala horizontalmente.
La imagen de la entrevista avanzada
Supongamos que un entrevistador plantea este desafío:
"Diseñe una estrategia de caché para una API de producto con alto tráfico."
Una respuesta débil se queda en:
"Usaré Redis."
Una respuesta más sólida explica el razonamiento paso a paso:
Request
↓
Redis
↙ ↘
Hit Miss
↓ ↓
Response Database
↓
Redis
↓
Response
Luego aborda cada una de estas decisiones de diseño:
1. Patrón de caché
Elegir el patrón cache-aside.
2. TTL
Establecerlo según el grado de obsolescencia que puedan tolerar los datos del producto.
3. Invalidación
Borrar la clave correspondiente cada vez que se actualicen los datos subyacentes.
4. Protección contra sobrecarga
Aplicar técnicas de bloqueo, consolidación de solicitudes o actualización temprana.
5. Expulsión
Elegir una política LRU o LFU adecuada para la carga de trabajo.
6. Diseño de claves
product:{id}
user:{id}
7. Monitoreo
Prestar atención a:
- Relación de aciertos
- Relación de fallos
- Latencia
- Uso de memoria
- Cuentas de expulsión
- Claves frecuentes
- Errores de Redis
Ese tipo de razonamiento estructurado proporciona una respuesta mucho más convincente sobre el diseño del sistema que simplemente afirmar que "Redis es rápido."
Revisión rápida de entrevistas
¿Por qué molestarse en usar caché?
Reduce la latencia y evita que el almacén de datos principal tenga que manejar una y otra vez las mismas lecturas costosas.
¿Por qué elegir específicamente Redis?
Ofrece acceso rápido en memoria, soporte para vencimiento, estructuras de datos versátiles y funcionalidades diseñadas para sistemas distribuidos.
¿Qué significa cache-aside?
La aplicación busca primero en la caché; si no está allí, consulta la base de datos y luego escribe ese resultado de vuelta en la caché.
¿Qué significa TTL?
Es la duración de vida que tiene una entrada en caché antes de vencerse automáticamente.
¿Qué significa invalidación de caché?
Significa eliminar o actualizar las entradas en caché cada vez que cambian los datos originales.
¿Qué es una estampida de caché?
Es una situación en la que un gran número de solicitudes no encuentran los datos en la caché al mismo tiempo y todas acceden a la base de datos simultáneamente.
LRU versus LFU?
LRU elimina lo que se ha utilizado con menos frecuencia recientemente. LFU elimina lo que se ha utilizado con menos frecuencia en general.
¿Merece cada consulta ser almacenada en caché?
En absoluto. Base la decisión en la frecuencia con la que se accede a los datos, cuán costoso es obtenerlos, qué nivel de obsolescencia se considera aceptable y cuanta memoria puedes destinar.
El modelo mental que vale la pena mantener
Omite memorizar los comandos de Redis como punto de partida. En su lugar, concéntrate en la arquitectura subyacente:
┌─────────────┐
Request ─────→│ Redis │
└──────┬──────┘
│
Cache Hit?
↙ ↘
YES NO
↓ ↓
Response Database
↓
Redis
↓
Response
Luego analiza cuatro preguntas:
¿Qué debe estar en el caché?
¿Cuánto tiempo debe permanecer allí?
¿Cuándo es necesario invalidarlo?
¿Qué sucede si el caché falla o expira bajo carga intensa?
Abordar estas preguntas es lo que distingue el conocimiento básico sobre caché del pensamiento de nivel profesional.
Conclusión
El caché no es simplemente:
"Coloca Redis delante de MongoDB."
Una estrategia sólida de caché se basa fundamentalmente en compromisos.
Estás intercambiando:
Memoria por velocidad
y, a veces:
Frescura por rendimiento
Redis proporciona los mecanismos, pero es la lógica de tu aplicación la que determina cómo se utilizan.
Si te estás preparando para entrevistas, no te conformes con decir:
"Redis es una base de datos en memoria."
Estar preparado para explicar:
Caché por separado, TTL, invalidación, ratio de aciertos, expulsión, efecto estampida, penetración, efecto avalancha, caché distribuido y consistencia.
Una vez que entiendas cómo se relacionan estos conceptos, habrás ido más allá de simplemente conocer Redis.
Ahora comprendes cómo los sistemas con alto tráfico protegen sus bases de datos manteniendo una alta velocidad.
Lecturas relacionadas
- Errores en la arquitectura backend que problemman a las equipos de React con enfoque en el frontend — Explica cinco defectos comunes en el diseño del backend en proyectos liderados por React, desde el uso incorrecto del paradigma API hasta despliegues frágiles, así como las soluciones arquitectónicas para lograr una fiabilidad de nivel profesional.
- Por qué los patrones regex de retroceso pueden hacer seguir inactivo a su servidor — Aprenda cómo la coincidencia codiciosa y el retroceso catastrófico pueden convertir un regex aparentemente correcto en una interrupción del servicio que agota la CPU, y cómo detectar dicho riesgo.