¿Qué es Redis?
Redis es un servidor de estructuras de datos en memoria. Mantiene todo su conjunto de datos en la RAM y responde a los comandos en microsegundos. Cada valor se almacena bajo una clave de tipo string, y el valor puede ser un string, un hash, una lista, un set, un sorted set, un stream, un bitmap o un HyperLogLog.
Surgió en 2009 como una forma de acelerar un producto de analíticas en tiempo real y rápidamente se convirtió en la caché predeterminada para la web. La razón no es solo la velocidad. Redis también ofrece un conjunto de comandos pequeño y componibles, estructuras de datos útiles, TTLs, replicación y scripting; lo suficiente para que los equipos lo utilicen para mucho más que el simple almacenamiento en caché.
Piensa en Redis como una estructura de datos en memoria compartida que cualquier proceso puede ver. Este enfoque explica tanto sus fortalezas como sus límites.
Por qué Redis es rápido
Hay tres razones por las que Redis es rápido, y ninguna de ellas es un secreto.
Primero, los datos residen en memoria. No hay búsquedas en disco ni pools de buffers que gestionar en la ruta de lectura. El acceso a la memoria se mide en nanosegundos; un viaje de ida y vuelta por la red se mide en fracciones de milisegundo.
Segundo, no hay un planificador de consultas (query planner). Un comando como GET user:42 es una búsqueda en una tabla hash, y ZADD es una inserción en una skip-list. No hay parseo de un lenguaje de consultas, ni pasos de optimización ni joins. La operación es fija y directa.
Tercero, los comandos se ejecutan uno a la vez en un único hilo. Esto podría parecer una debilidad, pero elimina la contención de bloqueos (lock contention) y hace que cada comando sea atómico. El event loop gestiona miles de conexiones y ejecuta los comandos secuencialmente, razón por la cual una sola instancia puede ofrecer un rendimiento enorme.
El inconveniente es que un comando lento bloquea todo lo demás. Un KEYS * sobre millones de claves, o un ZRANGE sobre un sorted set gigante, detiene a todos los demás clientes. Mantén los comandos acotados y utiliza SCAN en lugar de KEYS en producción.
Las estructuras de datos que importan
Elegir la estructura adecuada es la parte más importante de la habilidad. Aquí tienes para qué sirve cada una.
Los Strings almacenan texto, JSON, contadores y datos binarios. SET, GET, INCR, APPEND y SETEX son las herramientas fundamentales.
SET user:42:name "Ada"
INCR page:home:views
SETEX token:abc123 900 "opaque-token-value"
Los Hashes almacenan un objeto como pares de campo-valor bajo una sola clave. Son perfectos para sesiones y registros que actualizas campo por campo, ya que evitas serializar el objeto completo en cada escritura.
HSET session:9f2c userId 42 role admin
HINCRBY session:9f2c pageViews 1
HGET session:9f2c role
Las Lists son secuencias ordenadas con push y pop O(1) en ambos extremos. Sirven para crear colas simples, feeds de actividad reciente y logs limitados.
LPUSH queue:emails "welcome:42"
RPOP queue:emails
LTRIM feed:global 0 99
Los Sets contienen miembros únicos y no ordenados. Úsalos para etiquetas, seguidores y pruebas de membresía.
SADD post:7:tags redis database
SISMEMBER post:7:tags redis
SINTER user:1:follows user:2:follows
Los Sorted sets son la estrella. Cada miembro tiene una puntuación (score), por lo que el conjunto permanece ordenado por puntuación mientras que las búsquedas se mantienen en O(log n). Las tablas de clasificación (leaderboards), las colas de prioridad y las ventanas de rate-limit los utilizan.
ZADD leaderboard 1500 ada
ZINCRBY leaderboard 50 ada
ZREVRANGE leaderboard 0 9 WITHSCORES
Los Streams son un log de solo anexado (append-only) con grupos de consumidores, más cercanos a Kafka que a una lista. Úsalos cuando necesites replay, confirmación (acknowledgement) y múltiples consumidores.
XADD events * type signup userId 42
XREADGROUP GROUP workers alice COUNT 10 STREAMS events ">"
Los Bitmaps y HyperLogLogs gestionan conteos especializados. Los Bitmaps rastrean booleanos por usuario en un solo bit cada uno, ideal para usuarios activos diarios; HyperLogLog estima conteos únicos con unos 12 KB y una tasa de error pequeña.
SETBIT active:2026-09-16 42 1
PFADD visitors:2026-09-16 user:42
PFCOUNT visitors:2026-09-16
Si recuerdas una sola regla, que sea esta: elige la estructura que coincida con el patrón de acceso, no la que coincida con el JSON que ya tienes.
Nombramiento de claves y namespaces
Redis no tiene tablas, ni esquemas, ni namespaces. La única estructura es la propia clave, por lo que tu convención de nombres es, en esencia, tu esquema.
Un patrón común es object:id:attribute mediante segmentos separados por dos puntos:
user:42
user:42:sessions
session:9f2c
order:1001:items
rate:203.0.113.7:2026091612
El uso de claves cortas y predecibles reduce el consumo de memoria y facilita el escaneo y la depuración. Añade un prefijo de versión o de entorno cuando varias aplicaciones compartan una instancia: app:v2:user:42. Evita las claves derivadas de entradas de usuario sin límite y nunca utilices espacios ni saltos de línea.
Dado que las claves son strings, puedes listarlas para depurar, pero hazlo con precaución. SCAN con un cursor es seguro; KEYS no lo es, ya que bloquea el servidor mientras recorre todo el keyspace.
Expiración, TTL y desalojo (eviction)
La expiración es lo que convierte a Redis en una caché en lugar de una fuga de memoria. Casi cada clave que crees para almacenamiento en caché debería tener un TTL.
SET user:42 '{"name":"Ada"}' EX 300 # seconds
SET user:42 '{"name":"Ada"}' PX 300000 # milliseconds
EXPIRE user:42 300
TTL user:42
PERSIST user:42
EXPIRE y sus variantes asignan un tiempo de espera; TTL informa los segundos restantes. La expiración es perezosa (lazy) y activa: las claves se eliminan cuando se accede a ellas después de su tiempo límite, y un ciclo en segundo plano muestrea y limpia las claves expiradas para que la memoria se recupere incluso si nunca se vuelven a leer.
Los TTL por sí solos no son suficientes cuando el conjunto de datos crece más rápido de lo que expira. Configura maxmemory y una maxmemory-policy:
noeviction— rechaza las escrituras cuando está lleno; el valor predeterminado seguro para datos duraderos.allkeys-lru— desaloja la clave menos utilizada recientemente (LRU) de cualquier tipo; la opción común para cachés.allkeys-lfu— desaloja la clave menos utilizada frecuentemente (LFU), mejor cuando importa un conjunto pequeño de datos muy activos (hot set).volatile-lru/volatile-ttl— desaloja solo las claves que tienen una expiración, de modo que las claves persistentes quedan protegidas.
CONFIG SET maxmemory 2gb
CONFIG SET maxmemory-policy allkeys-lru
La elección es importante. allkeys-lru desalojará alegremente una sesión si es la clave más fría; volatile-ttl no lo hará, porque las sesiones siempre llevan una expiración.
Patrones de caché e invalidación
El patrón más común es el cache-aside. La aplicación consulta primero Redis, recurre a la base de datos en caso de un miss y escribe el resultado de vuelta con un TTL.
async function getUser(id) {
const key = `user:${id}`;
const cached = await redis.get(key);
if (cached) return JSON.parse(cached);
const user = await db.users.findById(id);
if (user) await redis.set(key, JSON.stringify(user), { EX: 300 });
return user;
}
Vale la pena conocer otros dos patrones. El write-through actualiza la caché y la base de datos simultáneamente, lo que mantiene las lecturas optimizadas (warm) pero duplica la ruta de escritura. El write-behind escribe primero en Redis y vuelca los datos a la base de datos de forma asíncrona, lo cual es rápido pero conlleva el riesgo de perder escrituras.
La invalidación es la parte difícil. Un TTL garantiza la corrección eventual; los borrados explícitos la hacen inmediata. El hábito más fiable es actualizar primero la base de datos, luego borrar la clave de la caché y aceptar una breve ventana de tiempo donde un lector concurrente pueda repoblar la caché con datos obsoletos.
async function updateUser(id, patch) {
const user = await db.users.update(id, patch);
await redis.del(`user:${id}`);
return user;
}
Evita la tentación de mantener una caché para siempre solo porque la invalidación es molesta. Los datos obsoletos y la memoria sin límites son problemas peores que una tasa de aciertos (hit rate) ligeramente inferior.
Sesiones, rate limiting y tablas de clasificación
Redis destaca siempre que el estado deba compartirse entre procesos y sobrevivir a los reinicios.
Las sesiones son un hash o una cadena serializada con un TTL que se desliza en cada solicitud.
await redis.set(`session:${sid}`, JSON.stringify(data), { EX: 3600 });
await redis.expire(`session:${sid}`, 3600); // sliding window
El rate limiting es un contador con fecha de expiración. Una ventana fija requiere dos comandos; una ventana deslizante utiliza un sorted set de marcas de tiempo.
const minute = Math.floor(Date.now() / 60_000);
const key = `rate:${ip}:${minute}`;
const replies = await redis.multi().incr(key).expire(key, 60).exec();
if (replies[0] > 100) throw new Error("rate_limited");
Las tablas de clasificación (leaderboards) son un sorted set. ZADD registra una puntuación, ZINCRBY la ajusta y ZREVRANGE devuelve a los mejores jugadores con sus puntuaciones. ZRANK indica la posición de un jugador individual, que es exactamente lo que necesita una página de perfil.
ZADD leaderboard 1500 ada
ZINCRBY leaderboard 50 ada
ZREVRANGE leaderboard 0 9 WITHSCORES
ZRANK leaderboard ada
Estos tres patrones se basan en las mismas dos propiedades: los comandos son atómicos y cada clave puede expirar.
Colas y pub/sub
Las listas permiten crear una cola de trabajo confiable. Productores LPUSH, workers BRPOP; el pop bloqueante significa que un worker permanece inactivo hasta que llega un trabajo en lugar de realizar polling.
LPUSH queue:emails "welcome:42"
BRPOP queue:emails 30
Para tener una cola fiable, mueve los trabajos a una lista de procesamiento antes de manejarlos y elimínalos solo cuando tengan éxito. De esa manera, si un worker falla, no se pierde el trabajo silenciosamente.
Pub/sub es mensajería de tipo “dispara y olvida” (fire-and-forget): los publishers PUBLISH envían y los subscribers reciben en canales. Es excelente para notificaciones en vivo y señales de cache-busting, pero no tiene persistencia ni garantía de entrega. Si un subscriber está offline, el mensaje se pierde.
PUBLISH notifications:user:42 "You have a new follower"
SUBSCRIBE notifications:user:42
Cuando necesites persistencia, confirmación (acknowledgement) y capacidad de reproducción (replay), utiliza streams con consumer groups. Los streams son la respuesta moderna a “quiero pub/sub, pero que sea fiable”.
Pipelines, transacciones y Lua
Un comando representa un viaje de ida y vuelta por la red. Si necesitas diez valores, diez GET secuenciales pasarán la mayor parte del tiempo esperando la respuesta de la red. Un pipeline los envía todos juntos y lee todas las respuestas de una sola vez.
const [name, plan, logins] = await redis
.multi()
.hget("user:42", "name")
.hget("user:42", "plan")
.hget("user:42", "logins")
.exec();
Un pipeline no es automáticamente una transacción. Envolver los comandos en MULTI/EXEC hace que se ejecuten como un único bloque atómico, sin que se intercalen comandos de otros clientes. Redis no realiza un rollback ante el error de un comando como lo hace SQL; simplemente continúa, por lo que debes validar las entradas antes de encolarlas.
Para la lógica que debe ejecutarse atómicamente en el servidor, utiliza un Lua script. Este se ejecuta como un único comando, puede leer y escribir varias claves, y es la herramienta correcta para operaciones de compare-and-set, como liberar un bloqueo solo si todavía eres el propietario.
const release = `
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
end
return 0
`;
await redis.eval(release, { keys: ["lock:order:1001"], arguments: [token] });
Los scripts deben ser pequeños y deterministas. No ejecutes nada sin un límite definido dentro de uno, ya que todo el servidor quedará a la espera de que termine.
Persistencia: RDB y AOF
Que los datos estén en memoria no significa necesariamente que sean efímeros, pero la durabilidad es un compromiso que debes elegir.
RDB escribe snapshots del conjunto de datos en el disco en un momento determinado, ya sea mediante una programación o bajo demanda. Los snapshots son compactos, se restauran rápidamente y son ideales para copias de seguridad. El coste es que, en caso de un crash, se pierde todo lo escrito desde el último snapshot.
SAVE # blocking snapshot, avoid in production
BGSAVE # fork and snapshot in the background
AOF añade cada comando de escritura a un log. Con appendfsync everysec pierdes como máximo un segundo de escrituras; con always no pierdes casi nada, pero pagas un coste de escritura mucho más alto. Los archivos AOF pueden reescribirse en segundo plano para mantenerse compactos.
CONFIG SET appendonly yes
CONFIG SET appendfsync everysec
Muchas implementaciones activan ambos: RDB para restauraciones rápidas y AOF para reducir la ventana de pérdida de datos. Si utilizas Redis puramente como caché, puedes desactivar la persistencia por completo y dejar que una caché fría se rellene desde la base de datos. Si lo utilizas para colas o contadores importantes, mantén la persistencia activada y comprende exactamente cuántos datos podría costar un crash.
Replication, Sentinel y Cluster
Una replica se conecta a un primario y recibe un flujo de escrituras, por lo que mantiene una copia en tiempo casi real. Las replicas pueden atender lecturas, lo que descarga al primario, y son la base para el failover.
Sentinel supervisa a un primario y a sus replicas, y promociona una replica cuando el primario falla. También informa a los clientes la dirección del nuevo primario. Sentinel proporciona alta disponibilidad para un conjunto de datos que quepa en un solo nodo.
Redis Cluster fragmenta los datos entre nodos utilizando 16,384 hash slots. Cada clave se mapea a un slot mediante el hash de su nombre, y cada nodo es dueño de un rango de slots. Los clientes pueden comunicarse con cualquier nodo y son redireccionados al correcto. Cluster ofrece escalabilidad horizontal y failover, a costa de la complejidad: las operaciones con múltiples claves deben mantener las claves en el mismo slot, generalmente mediante hash tags como user:{42}:name.
El camino habitual es comenzar con una instancia única, luego añadir una replica, después Sentinel y, finalmente, Cluster solo cuando la memoria ya no quepa en una sola máquina.
Anti-patrones comunes
- Redis como única base de datos. Sin persistencia y replicación configuradas para la durabilidad, un fallo puede provocar la pérdida de datos. Mantén un sistema de registro duradero.
- Claves sin límite. Cada clave en caché necesita un TTL; de lo contrario, la memoria se llena y la política de expulsión empezará a eliminar elementos que necesitas.
- Claves y colecciones masivas. Un único hash con millones de campos, o una lista con millones de entradas, es lento de eliminar y puede bloquear el servidor.
KEYSen producción. Bloquea el event loop. UsaSCAN.- Una sola clave para todo. Serializar un objeto completo en cada cambio provoca una amplificación de escritura. Usa hashes y actualiza campos específicos.
- Ignorar la política de expulsión.
allkeys-lrupuede expulsar sesiones y bloqueos, no solo entradas de caché.
Conexión desde Node
Dos clientes dominan el ecosistema de Node: node-redis y ioredis. Ambos hablan el mismo protocolo y exponen un método por comando, por lo que la elección suele depender de la preferencia de la API y el soporte para clustering.
import { createClient } from "redis";
const redis = createClient({ url: process.env.REDIS_URL });
redis.on("error", (err) => console.error("redis", err));
await redis.connect();
await redis.set("health", "ok", { EX: 60 });
const value = await redis.get("health");
Crea el cliente una sola vez al iniciar la aplicación y compártelo. Una conexión es un socket con una cola de comandos, y abrir una por cada solicitud añade latencia y consume descriptores de archivo. Adjunta siempre un manejador de error: sin él, una conexión caída puede manifestarse como una excepción no controlada.
La mayoría de los comandos devuelven valores simples, pero las opciones se pasan como un objeto. SET acepta { EX: 300 } para los segundos, { NX: true } para escribir solo si la clave no existe, y { KEEPTTL: true } para preservar un tiempo de expiración existente. NX junto con un TTL es la receta estándar para un bloqueo distribuido.
const acquired = await redis.set(`lock:order:${id}`, token, {
NX: true,
EX: 30,
});
Para un despliegue fragmentado (sharded), elige un cliente que entienda las redirecciones de cluster y los hash tags. Ambos clientes principales lo hacen; lo importante es mantener las claves relacionadas en el mismo slot cuando un comando afecta a más de una.
Monitoreo de una instancia en ejecución
Unos pocos comandos te permiten conocer casi todo sobre la salud de una instancia de Redis.
INFO memory
INFO stats
DBSIZE
SLOWLOG GET 10
INFO memory reporta used_memory, maxmemory y la política actual. INFO stats incluye keyspace_hits y keyspace_misses, cuya proporción es tu cache hit rate (tasa de aciertos de caché), el número clave a observar al ajustar los TTLs. DBSIZE cuenta las claves, y SLOWLOG registra los comandos que superaron un umbral de latencia.
CONFIG SET slowlog-log-slower-than 10000 # microseconds
CONFIG RESETSTAT
Hay otras dos herramientas que son útiles pero peligrosas. MONITOR transmite en tiempo real cada comando que el servidor procesa; es invaluable para el debugging, pero demasiado costosa para dejarla activa en producción. SCAN recorre el keyspace en lotes del tamaño del cursor y es segura, a diferencia de KEYS.
Vigila tres números en tu dashboard: el uso de memoria frente a maxmemory, el recuento de evictions (evicciones) y el hit rate. Un hit rate que cae mientras las evictions aumentan generalmente significa que los TTLs son demasiado cortos o que el conjunto de datos ya no cabe, y es mejor añadir memoria o corregir las claves que subir el límite y permitir que la instancia haga swap.
Mejores prácticas
- Establece un TTL en cada clave almacenada en caché y elige una política de desalojo (eviction policy) que se adapte a los datos.
- Utiliza un esquema de nomenclatura de
object:id:attributeconsistente y documéntalo. - Elige la estructura de datos que mejor se adapte al patrón de acceso antes de escribir el código.
- Reutiliza una única conexión de cliente y utiliza pipelining para comandos independientes.
- Usa Lua o
MULTIpara operaciones atómicas de varios pasos y mantenlas breves. - Prefiere
SCANsobreKEYSy limita el tamaño de cualquier clave individual. - Habilita la persistencia y la replicación para los datos que no puedas reconstruir, y realiza pruebas de restauración.
- Monitorea la memoria, la tasa de aciertos (hit rate), los desalojos y los comandos lentos antes de que se conviertan en incidentes.
Errores comunes
- Implementar caching sin un TTL y agotar la memoria lentamente.
- Usar
KEYS *para buscar claves y bloquear a todos los demás clientes. - Tratar pub/sub como una cola fiable y perder mensajes cuando un suscriptor está caído.
- Almacenar un objeto grande como una sola cadena JSON y reescribirlo ante cada pequeño cambio.
- Asumir que
MULTIhace rollback como una transacción SQL; no es así. - Permitir que una “hot key” o un sorted set enorme se conviertan en un cuello de botella.
- Olvidar que
allkeys-lrupuede desalojar sesiones, locks y contadores de rate-limit. - Ejecutar Redis con la persistencia desactivada y sin réplicas, perdiendo los datos al reiniciar.
Próximos pasos
Redis es la cara práctica del almacenamiento en caché, y comprenderlo hace que los patrones de caching del backend roadmap sean concretos. Combínalo con un sistema de registro duradero: PostgreSQL para datos relacionales o MongoDB para documentos. Luego, conéctalo a un servicio de Node y revisa los Node.js basics para ver cómo encaja el cliente en el flujo de tu solicitud.