Casos de uso de Redis: Caché, Colas y Limitación de Tasa
Has construido una aplicación web que funciona bien con pocos usuarios, pero a medida que crece el tráfico, notas cargas de página lentas, trabajos en segundo plano duplicados y APIs bombardeadas por clientes abusivos. Estos son problemas clásicos de escalabilidad que Redis puede resolver elegantemente. Redis es un almacén de datos en memoria que sobresale en tres tareas principales: caché, colas y limitación de tasa. En este artículo, exploraremos cada caso de uso con ejemplos prácticos y mejores prácticas.
¿Por qué Redis para estos casos de uso?
Redis almacena datos en memoria, lo que lo hace increíblemente rápido, a menudo con tiempos de respuesta inferiores a un milisegundo. Admite estructuras de datos ricas como cadenas, listas, conjuntos, conjuntos ordenados y hashes, que se asignan naturalmente a caché, colas y limitación de tasa. También ofrece operaciones atómicas, pub/sub y opciones de persistencia, lo que lo convierte en una herramienta versátil para desarrolladores backend.
1. Caché con Redis
El caché es el caso de uso más común de Redis. Al almacenar datos de acceso frecuente en Redis, reduces la carga de la base de datos y aceleras las respuestas.
Cómo funciona el caché
Cuando llega una solicitud, verifica si los datos existen en Redis. Si es así (acierto de caché), devuélvelos directamente. Si no (fallo de caché), obténlos de la base de datos, guárdalos en Redis con un tiempo de expiración y devuélvelos. Este patrón se llama cache-aside.
// Ejemplo en Node.js usando ioredis
const Redis = require('ioredis');
const redis = new Redis();
async function getUser(userId) {
const cacheKey = `user:${userId}`;
const cached = await redis.get(cacheKey);
if (cached) return JSON.parse(cached);
const user = await db.query('SELECT * FROM users WHERE id = ?', [userId]);
await redis.set(cacheKey, JSON.stringify(user), 'EX', 3600); // TTL de 1 hora
return user;
}
Mejores prácticas para el caché
- Establece un TTL: Siempre establece un tiempo de expiración para evitar datos obsoletos y acumulación de memoria.
- Usa estructuras de datos apropiadas: Para clave-valor simple, usa cadenas. Para objetos, usa hashes para actualizar campos individualmente.
- Maneja la invalidación de caché: Actualiza o elimina las entradas de caché cuando los datos subyacentes cambien.
- Monitorea la tasa de aciertos: Una tasa de aciertos baja significa que tu caché no es efectivo; ajusta las claves o los TTL.
Estrategias de invalidación de caché
La invalidación es difícil. Las estrategias comunes incluyen:
- Basada en tiempo: Confía en el TTL. Simple pero puede servir datos obsoletos.
- Write-through: Actualiza el caché cada vez que actualizas la base de datos.
- Write-behind: Actualiza el caché primero, luego actualiza la base de datos de forma asíncrona (riesgo de pérdida de datos).
2. Colas con Redis
Las colas desacoplan tareas que consumen tiempo del ciclo de solicitud-respuesta. Por ejemplo, enviar correos electrónicos, procesar imágenes o generar informes se puede hacer de forma asíncrona.
Implementando una cola simple
Las listas de Redis son perfectas para colas. Usa LPUSH para agregar trabajos a la izquierda y BRPOP para bloquear y extraer de la derecha (FIFO).
// Productor: agregar trabajo
await redis.lpush('email_queue', JSON.stringify({ to: 'user@example.com', subject: 'Welcome' }));
// Consumidor: procesar trabajo (en un worker)
while (true) {
const job = await redis.brpop('email_queue', 0); // 0 = bloquear indefinidamente
const { to, subject } = JSON.parse(job[1]);
await sendEmail(to, subject);
}
Colas confiables con BRPOPLPUSH
Para evitar perder trabajos si un worker falla, usa BRPOPLPUSH para mover atómicamente el trabajo a una lista de procesamiento. Después de un procesamiento exitoso, elimínalo de la lista de procesamiento. Si el worker muere, otro worker puede recuperar trabajos de la lista de procesamiento.
const job = await redis.brpoplpush('email_queue', 'processing_queue', 0);
try {
// procesar trabajo
await redis.lrem('processing_queue', 1, job);
} catch (err) {
// manejar error, quizás reencolar
}
Bibliotecas de colas dedicadas
Para producción, considera bibliotecas como Bull (Node.js), RQ (Python) o Sidekiq (Ruby). Proporcionan reintentos, programación y monitoreo listos para usar.
3. Limitación de tasa con Redis
La limitación de tasa protege tu API del abuso y asegura un uso justo. Las operaciones atómicas de Redis lo hacen ideal para contar solicitudes.
Limitación de tasa de ventana fija
Incrementa un contador para cada usuario por ventana de tiempo. Si el conteo excede el límite, rechaza la solicitud.
async function isAllowed(userId, limit = 100, windowSec = 60) {
const key = `rate:${userId}:${Math.floor(Date.now() / 1000 / windowSec)}`;
const count = await redis.incr(key);
if (count === 1) await redis.expire(key, windowSec);
return count