Redis: Almacén de Datos en Memoria para Velocidad
Guía técnica profunda sobre Redis como capa de datos de alto rendimiento. Cubre estructuras de datos, mensajería Pub/Sub, estrategias de caché con TTL y eviction, gestión de sesiones, rate limiting, colas de trabajo con BullMQ, scripting Lua, pipelining, transacciones, Redis Stack, optimización de memoria, y alta disponibilidad con Cluster y Sentinel.
Índice
- 1. Estructuras de Datos
- 2. Mensajería Pub/Sub
- 3. Estrategias de Caché
- 4. Gestión de Sesiones
- 5. Rate Limiting
- 6. Colas Redis: Bull y BullMQ
- 7. Modo Cluster y Sentinel
- 8. Scripting Lua
- 9. Pipelining y Transacciones
- 10. Redis Stack
- 11. Optimización de Memoria
- 12. Experiencia Real
- Redis 8: Distribución Unificada (2025-2026)
1. Estructuras de Datos
Redis no es solo un key-value store. Provee estructuras de datos especializadas, cada una optimizada para patrones de acceso específicos. Elegir la estructura correcta es la decisión de diseño Redis más importante.
- Strings: Tipo más simple. Almacena texto, números o datos binarios hasta 512MB. Soporta incremento/decremento atómico (INCR/DECR). Usa para contadores, cachés simples, feature flags
- Hashes: Mapas campo-valor dentro de una sola clave. Como un mini-objeto. O(1) por acceso a campo. Usa para perfiles de usuario, datos de sesión, configuración. Más eficiente en memoria que claves string separadas
- Lists: Secuencias ordenadas. O(1) push/pop en head/tail. Usa para colas (LPUSH + RPOP), feeds de actividad, elementos recientes. LRANGE para paginación. Máx 4 mil millones de elementos
- Sets: Colecciones desordenadas de strings únicos. O(1) verificación de pertenencia (SISMEMBER). Operaciones de conjunto: SUNION, SINTER, SDIFF. Usa para tags, visitantes únicos, permisos
- Sorted Sets (ZSETs): Sets con un score por miembro. Ordenados por score. O(log N) add/remove. ZRANGEBYSCORE para range queries. Usa para leaderboards, colas de prioridad, series temporales
- Streams: Log append-only con consumer groups. Como topics de Kafka pero más simple. XADD para escribir, XREADGROUP para consumir. Usa para event sourcing, logs de actividad, mensajería entre servicios
- Bitmaps: Operaciones a nivel de bits sobre strings. SETBIT/GETBIT para bits individuales, BITCOUNT para conteo de población, BITOP para AND/OR/XOR entre claves. Extremadamente eficiente en memoria para estado booleano por usuario/entidad (1 millón de usuarios = 125KB). Usa para usuarios activos diarios, feature flags por usuario, implementaciones de bloom filter
// ioredis examples for each data structure
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
// Strings - counter
await redis.incr('bookings:today'); // Atomic increment
await redis.set('feature:dark-mode', 'true', 'EX', 86400); // With 24h TTL
// Hashes - user session
await redis.hmset('session:abc123', {
userId: '42', name: 'Jose', role: 'admin', loginAt: Date.now().toString()
});
const role = await redis.hget('session:abc123', 'role'); // O(1) field access
// Lists - recent activity
await redis.lpush('activity:user:42', JSON.stringify({ action: 'booking', id: 789 }));
await redis.ltrim('activity:user:42', 0, 99); // Keep only last 100
const recent = await redis.lrange('activity:user:42', 0, 9); // Get last 10
// Sets - unique daily visitors
await redis.sadd('visitors:2026-03-17', 'user:42', 'user:78');
const count = await redis.scard('visitors:2026-03-17'); // Unique count
// Sorted Sets - leaderboard
await redis.zadd('leaderboard:march', 150, 'user:42', 280, 'user:78');
const top10 = await redis.zrevrange('leaderboard:march', 0, 9, 'WITHSCORES');
// Streams - event log
await redis.xadd('events:bookings', '*', 'type', 'created', 'bookingId', '789');
// Bitmaps - daily active users (user ID as bit offset)
await redis.setbit('dau:2026-03-17', 42, 1); // Mark user 42 as active
await redis.setbit('dau:2026-03-17', 78, 1); // Mark user 78 as active
const isActive = await redis.getbit('dau:2026-03-17', 42); // Check if active
const totalActive = await redis.bitcount('dau:2026-03-17'); // Count active users
2. Mensajería Pub/Sub
Redis Pub/Sub habilita mensajería fire-and-forget entre servicios. Los publishers envían mensajes a canales; todos los subscribers de ese canal reciben el mensaje en tiempo real. Los mensajes no se persisten -- si no hay subscriber escuchando, el mensaje se pierde.
- Canales: Flujos de mensajes nombrados. SUBSCRIBE/PUBLISH. Suscripciones por patrón con PSUBSCRIBE (ej:
events:*coincide con todos los canales de eventos) - Sin persistencia: Los mensajes se entregan solo a subscribers conectados. Si un subscriber se desconecta y reconecta, pierde los mensajes enviados durante la caída
- Sin acknowledgment: El publisher no sabe si algún subscriber recibió el mensaje. Usa Redis Streams si necesitas entrega garantizada
- Conexiones dedicadas: Una conexión suscriptora entra en "modo subscribe" y no puede ejecutar otros comandos. Usa conexiones separadas para pub/sub y comandos regulares
- Casos de uso: Notificaciones en tiempo real, invalidación de caché entre instancias, broadcasting WebSocket, notificaciones de eventos entre servicios
// Pub/Sub with ioredis - cache invalidation pattern
import Redis from 'ioredis';
// Publisher (on write operations)
const pub = new Redis(process.env.REDIS_URL);
async function updateUser(userId, data) {
await db.updateUser(userId, data);
await pub.publish('cache:invalidate', JSON.stringify({
type: 'user', id: userId
}));
}
// Subscriber (on each API server instance)
const sub = new Redis(process.env.REDIS_URL); // Separate connection!
sub.subscribe('cache:invalidate');
sub.on('message', (channel, message) => {
const { type, id } = JSON.parse(message);
localCache.delete(`${type}:${id}`); // Clear local in-memory cache
});
3. Estrategias de Caché
Patrones de Caché
- Cache-aside (lazy loading): La aplicación verifica caché primero. En miss, consulta base de datos, almacena resultado en caché con TTL. Patrón más común. Simple pero puede causar cache stampede en claves populares
- Write-through: La aplicación escribe en caché y base de datos simultáneamente. Caché siempre actualizado. Mayor latencia de escritura pero elimina lecturas stale
- Write-behind (write-back): La aplicación escribe solo en caché. Un proceso en background sincroniza a base de datos asíncronamente. Menor latencia de escritura pero riesgo de pérdida de datos si Redis cae antes del sync
- Read-through: El caché mismo es responsable de cargar desde base de datos en miss. La aplicación siempre lee del caché. Requiere capa de datos cache-aware
// Cache-aside pattern with stampede protection
async function getUserById(userId: string): Promise<User> {
const cacheKey = `user:${userId}`;
// 1. Check cache
const cached = await redis.get(cacheKey);
if (cached) return JSON.parse(cached);
// 2. Acquire lock to prevent cache stampede
const lockKey = `lock:${cacheKey}`;
const acquired = await redis.set(lockKey, '1', 'EX', 5, 'NX'); // 5s lock
if (!acquired) {
// Another instance is loading this data -- wait and retry
await new Promise(r => setTimeout(r, 100));
return getUserById(userId);
}
try {
// 3. Load from database
const user = await db.findUserById(userId);
// 4. Store in cache with TTL
await redis.set(cacheKey, JSON.stringify(user), 'EX', 3600); // 1 hour TTL
return user;
} finally {
await redis.del(lockKey); // Release lock
}
}
TTL y Políticas de Eviction
- TTL (Time to Live): Configura con EX (segundos) o PX (milisegundos) en SET, o con comando EXPIRE. Siempre configura TTL en claves de caché para prevenir crecimiento ilimitado de memoria
- allkeys-lru: Desaloja claves menos recientemente usadas cuando la memoria está llena. Mejor política de uso general para cargas de caché
- volatile-lru: Solo desaloja claves con TTL configurado. Claves sin TTL nunca se desalojan. Usa cuando mezclas datos persistentes y de caché en una instancia
- allkeys-lfu (Redis 4.0+): Desaloja claves menos frecuentemente usadas. Mejor que LRU para cargas con pocas claves muy calientes y muchas frías
- noeviction: Retorna errores cuando la memoria está llena. Usa para datos que nunca deben desalojarse (colas, sesiones). Requiere planificación cuidadosa de memoria
- maxmemory: Configura con
maxmemory 2gben redis.conf. Siempre configura esto en producción. Sin esto, Redis usará toda la memoria disponible y el SO lo matará con OOM
4. Gestión de Sesiones
Redis es el backend estándar para almacenamiento de sesiones en aplicaciones distribuidas. Almacenar sesiones en Redis (en vez de memoria del servidor) habilita escalado horizontal -- cualquier servidor puede manejar cualquier request porque los datos de sesión son compartidos.
- Sesiones basadas en hash: Almacena campos de sesión en un hash Redis. HMSET para escrituras batch, HGET para lecturas de campo individual. Más eficiente que serializar toda la sesión en cada request
- TTL para expiración: Configura TTL igual al timeout de sesión (ej: 24 horas). EXPIRE se reinicia en cada request para implementar expiración deslizante
- Seguridad de Session ID: Genera IDs de sesión criptográficamente aleatorios (128+ bits). Nunca uses IDs secuenciales o predecibles. Regenera el ID después de autenticación para prevenir session fixation
- express-session + connect-redis: Setup estándar para Express.js. Maneja serialización, gestión de cookies y TTL automáticamente
- JWT vs sesiones Redis: JWTs son stateless pero no pueden revocarse hasta que expiren. Sesiones Redis pueden revocarse instantáneamente (borrar la clave). Usa sesiones Redis cuando necesites revocación inmediata (logout, incidentes de seguridad)
// Express.js session with Redis (connect-redis)
import session from 'express-session';
import RedisStore from 'connect-redis';
import Redis from 'ioredis';
const redisClient = new Redis(process.env.REDIS_URL);
app.use(session({
store: new RedisStore({
client: redisClient,
prefix: 'sess:',
ttl: 86400 // 24 hours in seconds
}),
secret: process.env.SESSION_SECRET,
resave: false, // Don't save session if unmodified
saveUninitialized: false, // Don't create session until something stored
cookie: {
secure: true, // HTTPS only
httpOnly: true, // Not accessible via JavaScript
maxAge: 86400000, // 24 hours in milliseconds
sameSite: 'lax'
}
}));
// Force logout (immediate session revocation)
async function forceLogout(sessionId: string) {
await redisClient.del(`sess:${sessionId}`);
}
5. Rate Limiting
Redis es ideal para rate limiting porque provee operaciones atómicas y latencia sub-milisegundo. Rate limiting protege APIs de abuso, previene agotamiento de recursos y asegura uso justo.
- Ventana fija: Cuenta requests por ventana de tiempo (ej: 100 requests por minuto). Simple pero permite ráfagas en límites de ventana (hasta 2x la tasa)
- Log de ventana deslizante: Almacena timestamp de cada request en un sorted set. Cuenta entradas dentro de la ventana. Preciso pero mayor uso de memoria para endpoints de alto volumen
- Contador de ventana deslizante: Combina conteos de ventana actual y anterior ponderados por tiempo transcurrido. Buen balance de precisión y eficiencia de memoria
- Token bucket: El bucket contiene tokens, cada request consume uno. Los tokens se reponen a tasa fija. Permite ráfagas controladas. Mejor para rate limiting de APIs
// Sliding window rate limiter with Redis
async function checkRateLimit(userId: string, limit: number, windowSec: number): Promise<boolean> {
const key = `ratelimit:${userId}`;
const now = Date.now();
const windowStart = now - (windowSec * 1000);
const member = `${now}:${Math.random()}`; // Unique member ID
// Atomic pipeline: remove old entries, add new, count, set TTL
const pipeline = redis.pipeline();
pipeline.zremrangebyscore(key, 0, windowStart); // Remove expired entries
pipeline.zadd(key, now, member); // Add current request
pipeline.zcard(key); // Count entries in window
pipeline.expire(key, windowSec); // Set TTL for cleanup
const results = await pipeline.exec();
const count = results[2][1] as number;
if (count > limit) {
await redis.zrem(key, member); // Remove the entry we just added
return false; // Rate limited
}
return true; // Allowed
}
// Usage in Express middleware
app.use(async (req, res, next) => {
const allowed = await checkRateLimit(req.ip, 100, 60); // 100 req/min
if (!allowed) {
res.setHeader('Retry-After', '60');
return res.status(429).json({ error: 'Too many requests' });
}
next();
});
6. Colas Redis: Bull y BullMQ
BullMQ es la cola de trabajo estándar basada en Redis para Node.js. Provee procesamiento confiable de jobs con reintentos, delays, prioridades, rate limiting y control de concurrencia. Construido sobre Redis Streams y Sorted Sets.
- Ciclo de vida del job: waiting -> active -> completed/failed. Jobs fallidos pueden reintentarse con backoff exponencial. Jobs completados pueden limpiarse después de N días
- Jobs con delay: Programa jobs para ejecutarse en un momento específico o después de un delay. Usa para enviar emails 30 minutos después del registro, reportes programados, delays de reintento
- Colas de prioridad: Asigna prioridad a jobs (número menor = mayor prioridad). Jobs de procesamiento de pagos antes de notificaciones de email
- Rate limiting: Limita cuántos jobs se procesan por ventana de tiempo. Prevén abrumar APIs externas (Stripe, proveedores de email)
- Concurrencia: Controla cuántos jobs corren simultáneamente por worker. Configura basado en requerimientos CPU/memoria de tu tipo de job
- Eventos: Escucha completitud de jobs, fallos, actualizaciones de progreso. Usa para actualizaciones de UI en tiempo real vía WebSocket
- Jobs repetibles: Scheduling estilo cron. Ejecuta un job cada hora, cada día a las 3 AM, etc. Usa sorted sets de Redis para scheduling
// BullMQ producer and worker
import { Queue, Worker } from 'bullmq';
import Redis from 'ioredis';
const connection = new Redis(process.env.REDIS_URL, { maxRetriesPerRequest: null });
// Producer: add jobs to queue
const emailQueue = new Queue('email', { connection });
await emailQueue.add('booking-confirmation', {
userId: 42,
bookingId: 789,
templateId: 'booking_confirmed'
}, {
attempts: 3, // Retry up to 3 times
backoff: { type: 'exponential', delay: 5000 }, // 5s, 10s, 20s
removeOnComplete: { age: 86400 }, // Clean up after 24h
removeOnFail: { age: 604800 }, // Keep failed jobs for 7 days
priority: 2 // Lower = higher priority
});
// Repeatable job (daily report at 3 AM)
await emailQueue.add('daily-report', { type: 'daily' }, {
repeat: { pattern: '0 3 * * *' } // Cron syntax
});
// Worker: process jobs
const worker = new Worker('email', async (job) => {
const { userId, bookingId, templateId } = job.data;
await job.updateProgress(10);
const user = await db.findUser(userId);
const booking = await db.findBooking(bookingId);
await job.updateProgress(50);
await ses.sendTemplatedEmail(user.email, templateId, { user, booking });
await job.updateProgress(100);
return { sent: true, email: user.email };
}, {
connection,
concurrency: 5, // Process 5 jobs simultaneously
limiter: { max: 10, duration: 1000 } // Max 10 jobs per second
});
worker.on('completed', (job, result) => {
console.log(`Job ${job.id} completed: ${result.email}`);
});
worker.on('failed', (job, err) => {
console.error(`Job ${job.id} failed: ${err.message}`);
});
7. Modo Cluster y Sentinel
Redis Sentinel
Sentinel provee alta disponibilidad para Redis. Monitorea nodos master y réplica, ejecuta failover automático cuando el master cae, y sirve como mecanismo de descubrimiento de servicios.
- Monitorea salud del master con umbrales configurables (down-after-milliseconds)
- Failover automático: promueve una réplica a master cuando el master actual falla. Requiere quórum (mayoría de sentinels están de acuerdo)
- Configuración de cliente: Los clientes se conectan a Sentinel para descubrir el master actual. ioredis soporta Sentinel nativamente
- Mínimo 3 instancias Sentinel para quórum. Ubícalas en diferentes zonas de disponibilidad
- Ideal para: Setups de master único donde los datos caben en la memoria de un servidor
Redis Cluster
Redis Cluster distribuye datos entre múltiples nodos master usando hash slots (16384 slots). Cada master maneja una porción del keyspace. Provee escalado horizontal para datos que exceden la memoria de un solo nodo.
- Data sharding: 16384 hash slots distribuidos entre masters. Slot de una clave = CRC16(key) mod 16384
- Hash tags: Usa {prefix} en claves para forzar claves relacionadas al mismo slot. Requerido para operaciones multi-clave (MGET, pipelines, scripts Lua)
- Mínimo 3 masters + 3 réplicas (6 nodos) para producción. Cada master tiene al menos una réplica para failover
- Redirecciones ASK/MOVED: El cliente debe manejar migración de slots durante resharding. ioredis maneja esto automáticamente
- Ideal para: Datasets grandes que exceden memoria de un nodo, o cuando necesitas escalado de escrituras entre múltiples masters
Configuración ioredis
// ioredis with Sentinel
const redis = new Redis({
sentinels: [
{ host: 'sentinel-1', port: 26379 },
{ host: 'sentinel-2', port: 26379 },
{ host: 'sentinel-3', port: 26379 }
],
name: 'mymaster', // Sentinel master group name
password: process.env.REDIS_PASSWORD,
sentinelPassword: process.env.SENTINEL_PASSWORD,
enableReadyCheck: true,
maxRetriesPerRequest: 3,
retryStrategy(times) {
return Math.min(times * 200, 5000); // Cap retry delay at 5s
}
});
// ioredis with Cluster
const cluster = new Redis.Cluster([
{ host: 'redis-1', port: 6379 },
{ host: 'redis-2', port: 6379 },
{ host: 'redis-3', port: 6379 }
], {
redisOptions: { password: process.env.REDIS_PASSWORD },
scaleReads: 'slave', // Read from replicas for read-heavy workloads
enableReadyCheck: true,
maxRedirections: 16,
retryDelayOnFailover: 300
});
8. Scripting Lua
Redis embebe un intérprete Lua, permitiéndote ejecutar scripts atómicamente en el servidor. Los scripts Lua corren como una única operación atómica -- ningún otro comando puede ejecutarse entre pasos del script. Esto elimina race conditions sin locks distribuidos.
- EVAL / EVALSHA: EVAL envía el script cada vez. EVALSHA envía solo el hash SHA1 después del primer load con SCRIPT LOAD. Usa EVALSHA en producción para reducir ancho de banda
- KEYS y ARGV: Pasa nombres de claves como KEYS[] y argumentos como ARGV[]. Redis usa KEYS para determinar qué nodo del cluster ejecuta el script. Nunca hardcodees nombres de claves dentro del script
- Atomicidad: El script completo se ejecuta atómicamente. Ningún otro cliente puede correr comandos mientras un script Lua está corriendo. Mantén los scripts cortos para evitar bloquear otros clientes
- Casos de uso: Rate limiting con lógica compleja, operaciones compare-and-swap, transacciones multi-paso que necesitan atomicidad, actualizaciones condicionales, locks distribuidos (Redlock)
- Limitaciones: Sin I/O externo (sin llamadas HTTP, sin filesystem). Los scripts deben ser determinísticos para replicación. Tiempo máximo de ejecución controlado por
lua-time-limit(5 segundos por defecto)
// Lua script via ioredis - atomic rate limiter
const rateLimitScript = `
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
-- Remove expired entries
redis.call('ZREMRANGEBYSCORE', key, 0, now - window * 1000)
-- Count current entries
local count = redis.call('ZCARD', key)
if count < limit then
-- Add new entry and set TTL
redis.call('ZADD', key, now, now .. ':' .. math.random())
redis.call('EXPIRE', key, window)
return 1 -- Allowed
end
return 0 -- Denied
`;
// Load script once, use SHA for subsequent calls
const sha = await redis.script('LOAD', rateLimitScript);
// Execute with EVALSHA (atomic, no race conditions)
const allowed = await redis.evalsha(sha, 1, `ratelimit:${userId}`, 100, 60, Date.now());
// ioredis defineCommand for reusable Lua commands
redis.defineCommand('rateLimit', {
numberOfKeys: 1,
lua: rateLimitScript
});
const result = await redis.rateLimit(`ratelimit:${userId}`, 100, 60, Date.now());
9. Pipelining y Transacciones
Pipelining
Pipelining envía múltiples comandos a Redis sin esperar respuestas individuales. Redis procesa todos los comandos y envía todas las respuestas de vuelta de una vez. Esto elimina la latencia de round-trip por comando.
- Rendimiento: Enviar 1000 comandos individualmente toma 1000 round trips. Con pipelining, toma 1 round trip. Mejora de throughput de 10-100x para operaciones masivas
- Sin atomicidad: Los comandos en pipeline no son atómicos. Otros clientes pueden intercalar comandos entre ellos. Usa MULTI/EXEC si necesitas atomicidad
- Memoria: Redis almacena todas las respuestas en memoria. Pipelinea miles, no millones, de comandos a la vez para evitar problemas de memoria
Transacciones (MULTI/EXEC)
MULTI/EXEC provee ejecución atómica de un grupo de comandos. Todos los comandos entre MULTI y EXEC se encolan y ejecutan como una única operación atómica.
- MULTI: Inicia una transacción. Los comandos subsiguientes se encolan, no se ejecutan inmediatamente
- EXEC: Ejecuta todos los comandos encolados atómicamente. Retorna array de resultados
- WATCH: Locking optimista. WATCH una clave antes de MULTI. Si la clave cambia antes de EXEC, la transacción se aborta (retorna null). Reintenta la operación
- DISCARD: Cancela una transacción y limpia la cola de comandos
// Pipelining with ioredis - bulk operations
const pipeline = redis.pipeline();
for (let i = 0; i < 1000; i++) {
pipeline.set(`key:${i}`, `value:${i}`, 'EX', 3600);
}
const results = await pipeline.exec(); // Single round trip for 1000 commands
// MULTI/EXEC transaction - atomic transfer
async function transfer(from: string, to: string, amount: number) {
const multi = redis.multi();
multi.decrby(`balance:${from}`, amount);
multi.incrby(`balance:${to}`, amount);
const results = await multi.exec(); // Atomic: both succeed or neither does
if (!results) throw new Error('Transaction failed');
return results;
}
// WATCH + MULTI for optimistic locking
async function incrementIfBelow(key: string, max: number): Promise<boolean> {
while (true) {
await redis.watch(key);
const current = Number(await redis.get(key) || 0);
if (current >= max) {
await redis.unwatch();
return false;
}
const result = await redis.multi()
.set(key, current + 1)
.exec();
if (result) return true; // Success
// result is null = key changed, retry
}
}
10. Redis Stack
Redis 8.0 (GA mayo 2025) fusionó todos los módulos de Redis Stack en el core de Redis. RediSearch, RedisJSON, RedisTimeSeries y RedisBloom ahora son built-in -- no se necesita instalación separada de módulos. Esta consolidación entrega significativamente menos latencia de comandos comparado con la arquitectura basada en módulos. Los módulos a continuación son ahora capacidades nativas:
- RediSearch: Motor de búsqueda full-text integrado en Redis. Crea índices secundarios en campos hash o JSON. Soporta matching difuso, stemming, matching fonético, auto-complete y agregación. Elimina la necesidad de un motor de búsqueda separado (Elasticsearch) para muchos casos de uso
- RedisJSON: Almacenamiento nativo de documentos JSON con consultas JSONPath. Almacena, actualiza y recupera JSON anidado sin overhead de serialización. Combina con RediSearch para crear almacenes de documentos JSON buscables
- RedisGraph (Fin de Vida): RedisGraph alcanzó fin de vida en febrero 2025. Para necesidades de base de datos de grafos, considera FalkorDB (fork comunitario) que continúa desarrollo activo con soporte completo de Cypher
- RedisTimeSeries: Estructura de datos de series temporales con downsampling automático, agregación y políticas de retención. Usa para métricas, datos de sensores IoT, precios de acciones
- RedisBloom: Estructuras de datos probabilísticas -- filtros Bloom, filtros Cuckoo, Count-Min Sketch, Top-K. Testing de pertenencia aproximada y conteo eficiente en memoria
// RediSearch - full-text search on user profiles
// Create index (run once)
await redis.call('FT.CREATE', 'idx:users', 'ON', 'HASH', 'PREFIX', '1', 'user:',
'SCHEMA', 'name', 'TEXT', 'SORTABLE', 'email', 'TEXT', 'city', 'TAG', 'age', 'NUMERIC');
// Add data (just regular HSET)
await redis.hset('user:42', { name: 'Jose Nobile', email: '[email protected]', city: 'Medellin', age: '28' });
// Search with full-text and filters
const results = await redis.call('FT.SEARCH', 'idx:users', '@name:Jose @city:{Medellin}', 'LIMIT', '0', '10');
// RedisJSON - nested document operations
await redis.call('JSON.SET', 'product:100', '$', JSON.stringify({
name: 'Premium Plan', price: 49.99, features: ['unlimited', 'priority', 'api']
}));
// Update nested field without reading the whole document
await redis.call('JSON.NUMINCRBY', 'product:100', '$.price', 10);
const features = await redis.call('JSON.GET', 'product:100', '$.features');
10b. Vector Sets
Redis 8.0 introduce Vector Sets, un nuevo tipo de datos para búsqueda por similitud de vectores de alta dimensión. Crítico para cargas de trabajo AI/ML incluyendo búsqueda semántica, sistemas de recomendación y Retrieval-Augmented Generation (RAG). Los Vector Sets almacenan vectores de embedding y soportan consultas de vecinos más cercanos aproximados (ANN) con latencia sub-milisegundo.
- VADD / VREM / VEMB: Agrega, elimina y recupera vectores en un set. Cada vector está asociado con un nombre de elemento string y atributos JSON opcionales (VSETATTR / VGETATTR) usados para búsqueda filtrada
- VSIM: Encuentra los elementos más similares a un vector de consulta o a un elemento existente. Los resultados se ordenan por score de similitud (1 = idéntico, 0 = opuesto), con opciones COUNT, EPSILON, EF y FILTER para ajustar recall, corte de distancia y filtrado
- Casos de uso: Búsqueda semántica sobre embeddings de documentos, recomendaciones de productos a partir de vectores de comportamiento de usuario, pipelines RAG combinando LLMs con recuperación de vectores, similitud de imágenes y detección de anomalías
10c. Expiración de Campos de Hash
Redis 7.4+ introdujo TTL por campo dentro de hashes, y Redis 8.0 agregó nuevos comandos para operaciones atómicas de get-set-expire. Previamente, el TTL solo podía configurarse en claves enteras. Ahora los campos individuales de hash pueden expirar independientemente, habilitando invalidación de caché granular dentro de un solo hash.
- HEXPIRE / HPEXPIRE: Configura TTL en campos individuales de hash (segundos o milisegundos). Los campos se borran automáticamente cuando expiran
- HGETEX (Redis 8.0): Obtén uno o más campos y configura/actualiza su expiración atómicamente. Elimina la race condition entre HGET y HEXPIRE
- HSETEX (Redis 8.0): Configura uno o más campos con expiración en una sola operación atómica. Reemplaza el patrón de dos pasos HSET + HEXPIRE
- HGETDEL (Redis 8.0): Obtén y borra campos atómicamente. Útil para tokens de un solo uso, patrones de claim de jobs y acceso tipo cola dentro de hashes
10d. Licenciamiento de Redis
El licenciamiento de Redis ha cambiado significativamente. Redis 7.4 (marzo 2024) pasó de BSD a licencia dual SSPL/RSALv2, restringiendo a proveedores cloud de ofrecer Redis como servicio gestionado. Redis 8.0 (mayo 2025) agregó AGPLv3 como tercera opción, haciendo a Redis triple-licenciado (SSPL, RSALv2 o AGPLv3). La opción AGPLv3 re-abre Redis a la comunidad open-source manteniendo protecciones contra free-riding de proveedores cloud. Elige la licencia que mejor se adapte a tu modelo de despliegue.
11. Optimización de Memoria
Redis almacena todo en memoria, así que optimizar el uso de memoria reduce directamente costos de infraestructura. Pequeñas decisiones de estructuras de datos se acumulan en ahorros significativos a escala.
- Usa hashes para objetos pequeños: Redis internamente usa encoding ziplist para hashes pequeños (hasta
hash-max-ziplist-entries 128yhash-max-ziplist-value 64bytes). Esto es 10x más eficiente en memoria que claves string separadas - Nombres de clave cortos: Los nombres de clave consumen memoria. Usa
u:42:nen vez deuser:42:namecuando tienes millones de claves. Guarda el mapeo en constantes de código - Encoding de enteros: Redis almacena enteros (hasta 10000 por defecto) en un pool de objetos compartidos. Almacena IDs numéricos como enteros, no strings
- OBJECT ENCODING: Usa
OBJECT ENCODING keypara inspeccionar cómo Redis almacena una clave. Ziplist, intset y embstr son encodings compactos. Hashtable y skiplist consumen más memoria - MEMORY USAGE: Usa
MEMORY USAGE keypara ver bytes exactos consumidos por una clave incluyendo overhead. Perfila tus claves calientes - Compresión: Comprime valores grandes antes de almacenar (gzip, snappy). Cambia CPU por memoria. Especialmente efectivo para payloads JSON y objetos serializados
- TTL en todas partes: Siempre configura TTL en claves de caché. Audita periódicamente claves sin TTL usando
OBJECT IDLETIMEpara encontrar claves olvidadas consumiendo memoria
// Memory-efficient hash packing - store 100 users per hash bucket
// Instead of 100 separate keys, pack into hash buckets
function getUserBucket(userId: number) {
const bucket = Math.floor(userId / 100); // Group by 100s
const field = (userId % 100).toString();
return { key: `users:${bucket}`, field };
}
async function setUser(userId: number, data: object) {
const { key, field } = getUserBucket(userId);
await redis.hset(key, field, JSON.stringify(data));
}
async function getUser(userId: number) {
const { key, field } = getUserBucket(userId);
const raw = await redis.hget(key, field);
return raw ? JSON.parse(raw) : null;
}
// Monitor memory usage
const info = await redis.info('memory');
// used_memory, used_memory_peak, mem_fragmentation_ratio
const memUsage = await redis.call('MEMORY', 'USAGE', 'user:42'); // Bytes for one key
mem_fragmentation_ratio en INFO MEMORY. Un ratio arriba de 1.5 indica fragmentación de memoria significativa. Reinicia Redis o habilita active defrag (activedefrag yes) para reclamar memoria desperdiciada.12. Experiencia Real
En producción, Redis fue un componente de infraestructura crítico potenciando caché, sesiones y colas de trabajo en todos los microservicios.
- Sesión y caché: Redis como almacén de sesiones principal para todos los servidores API. Patrón cache-aside para perfiles de usuario, horarios de clases y datos de precios. TTLs desde 5 minutos (datos de horario) hasta 24 horas (configuración estática)
- Cliente ioredis: Usé ioredis en todos los servicios Node.js con configuración Sentinel para failover automático. Connection pooling con lazyConnect para uso eficiente de recursos durante cold starts
- Cola de reservas: Construye un sistema de reservas usando sorted sets de Redis combinado con ZeroMQ para comunicación entre servicios. Sorted sets contenían reservas pendientes con timestamps de expiración como scores, habilitando limpieza automática de holds expirados
- Colas de trabajo BullMQ: Notificaciones de email, procesamiento de pagos, generación de reportes y tareas programadas corriendo a través de BullMQ. Colas de prioridad aseguraron que webhooks de pago se procesaran antes que emails de marketing
- Invalidación de caché: Pub/Sub para invalidación de caché en tiempo real entre instancias de servidor API cuando se actualizaban datos. Combinado con TTL como red de seguridad para prevenir datos stale incluso si se pierde un mensaje Pub/Sub
13. Redis 8: Distribución Unificada (2025-2026)
Redis 8 GA: Open Source Unificado
Redis 8 fusiona Redis Stack y Redis Community Edition en una sola distribución unificada llamada Redis Open Source. Toda la funcionalidad previamente modular -- JSON, Time Series, tipos de datos probabilísticos (Bloom filter, cuckoo filter, count-min sketch, top-k, t-digest) y Redis Query Engine -- ahora está integrada en el paquete core. Los módulos standalone RediSearch, RedisJSON, RedisTimeSeries y RedisBloom ya no son necesarios. Esto elimina la complejidad de gestión de módulos y simplifica el despliegue.
Vector Sets y Rendimiento
Redis 8 introduce vector sets (beta), el primer nuevo tipo de datos core en años, habilitando búsqueda nativa de similitud vectorial para cargas de trabajo AI/ML como pipelines RAG y motores de recomendación. Las mejoras de rendimiento entregan más de 30 optimizaciones con comandos hasta 87% más rápidos y 2x de throughput en operaciones clave.
Redis 8.2 (agosto 2025)
Redis 8.2 (GA agosto 2025) extendió el Query Engine con el índice vectorial SVS-VAMANA, que agrega compresión de vectores para reducir el consumo de memoria de la búsqueda por similitud, y el parámetro SHARD_K_RATIO para ajustar el balance entre precisión y latencia en consultas KNN de vectores sobre un clúster. También añadió nuevos comandos de Streams (XDELEX y XACKDEL) que combinan borrado y acknowledgment, nuevos operadores de BITOP (DIFF, DIFF1, ANDOR, ONE) y el operador de filtro IN para VSIM sobre vector sets, junto con más de 15 mejoras de rendimiento y uso de recursos.
Redis 8.4, 8.6 y 8.8 (2025-2026)
Redis 8.4 (GA noviembre 2025) agregó migración atómica de slots del clúster (CLUSTER MIGRATION), métricas por slot (CLUSTER SLOT-STATS), MSETEX, extensiones atómicas de compare-and-set / compare-and-delete para claves string (DELEX, DIGEST), la opción CLAIM para XREADGROUP y búsqueda híbrida FT.HYBRID. Redis 8.6 (GA febrero 2026) entregó reducciones sustanciales de memoria para hashes codificados como hashtable y sorted sets codificados como skiplist, escrituras idempotentes en Streams (IDMP / IDMPAUTO en XADD), dos políticas de evicción por menos recientemente modificado (volatile-lrm, allkeys-lrm) y el comando HOTKEYS para detectar claves calientes. Redis 8.8 (GA mayo 2026) es la línea estable actual: introduce el tipo de dato Array, el rate limiter de ventana INCREX y XNACK para liberar explícitamente mensajes pendientes de Streams.
Licenciamiento: Opción AGPLv3
Redis Open Source (desde Redis 8) ahora ofrece tres opciones de licencia: Redis Source Available License v2 (RSALv2), Server Side Public License v1 (SSPLv1) y GNU Affero General Public License v3 (AGPLv3). La adición de AGPLv3 hace a Redis completamente open source nuevamente bajo una licencia ampliamente reconocida aprobada por OSI, respondiendo a las preocupaciones de la comunidad por el cambio de licencia de 2024.