Casos de Uso do Redis: Cache, Filas e Rate Limiting
Você construiu uma aplicação web que funciona bem com alguns usuários, mas conforme o tráfego cresce, você percebe lentidão no carregamento de páginas, tarefas em segundo plano duplicadas e APIs sendo sobrecarregadas por clientes abusivos. Esses são problemas clássicos de escalabilidade que o Redis pode resolver de forma elegante. O Redis é um armazenamento de dados em memória que se destaca em três tarefas principais: cache, filas e rate limiting. Neste artigo, exploraremos cada caso de uso com exemplos práticos e boas práticas.
Por que usar Redis para esses casos de uso?
O Redis armazena dados na memória, tornando-o incrivelmente rápido — muitas vezes com tempos de resposta abaixo de milissegundos. Ele suporta estruturas de dados ricas como strings, listas, conjuntos, conjuntos ordenados e hashes, que se mapeiam naturalmente para cache, filas e rate limiting. Ele também oferece operações atômicas, pub/sub e opções de persistência, tornando-o uma ferramenta versátil para desenvolvedores backend.
1. Cache com Redis
Cache é o caso de uso mais comum do Redis. Ao armazenar dados acessados com frequência no Redis, você reduz a carga do banco de dados e acelera as respostas.
Como funciona o cache
Quando uma requisição chega, verifique se os dados existem no Redis. Se sim (cache hit), retorne-os diretamente. Se não (cache miss), busque no banco de dados, armazene no Redis com um tempo de expiração e retorne. Esse padrão é chamado de cache-aside.
// Exemplo em 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;
}
Boas práticas para cache
- Defina um TTL: Sempre defina um tempo de expiração para evitar dados obsoletos e inchaço de memória.
- Use estruturas de dados apropriadas: Para chave-valor simples, use strings. Para objetos, use hashes para atualizar campos individualmente.
- Lide com invalidação de cache: Atualize ou exclua entradas de cache quando os dados subjacentes mudarem.
- Monitore a taxa de acertos: Uma taxa de acertos baixa significa que seu cache não é eficaz; ajuste chaves ou TTLs.
Estratégias de invalidação de cache
Invalidação é difícil. Estratégias comuns incluem:
- Baseada em tempo: Confie no TTL. Simples, mas pode servir dados obsoletos.
- Write-through: Atualize o cache sempre que atualizar o banco de dados.
- Write-behind: Atualize o cache primeiro, depois atualize o banco de dados de forma assíncrona (risco de perda de dados).
2. Filas com Redis
Filas desacoplam tarefas demoradas do ciclo de requisição-resposta. Por exemplo, enviar e-mails, processar imagens ou gerar relatórios podem ser feitos de forma assíncrona.
Implementando uma fila simples
Listas do Redis são perfeitas para filas. Use LPUSH para adicionar tarefas à esquerda e BRPOP para bloquear e remover da direita (FIFO).
// Produtor: adiciona tarefa
await redis.lpush('email_queue', JSON.stringify({ to: 'user@example.com', subject: 'Bem-vindo' }));
// Consumidor: processa tarefa (em um worker)
while (true) {
const job = await redis.brpop('email_queue', 0); // 0 = bloqueia indefinidamente
const { to, subject } = JSON.parse(job[1]);
await sendEmail(to, subject);
}
Filas confiáveis com BRPOPLPUSH
Para evitar perder tarefas se um worker travar, use BRPOPLPUSH para mover atomicamente a tarefa para uma lista de processamento. Após o processamento bem-sucedido, remova-a da lista de processamento. Se o worker morrer, outro worker pode recuperar tarefas da lista de processamento.
const job = await redis.brpoplpush('email_queue', 'processing_queue', 0);
try {
// processa tarefa
await redis.lrem('processing_queue', 1, job);
} catch (err) {
// trata erro, talvez recolocar na fila
}
Bibliotecas dedicadas a filas
Para produção, considere bibliotecas como Bull (Node.js), RQ (Python) ou Sidekiq (Ruby). Elas fornecem retentativas, agendamento e monitoramento prontos para uso.
3. Rate Limiting com Redis
Rate limiting protege sua API contra abusos e garante uso justo. As operações atômicas do Redis o tornam ideal para contar requisições.
Rate limiting de janela fixa
Incremente um contador para cada usuário por janela de tempo. Se a contagem exceder o limite, rejeite a requisição.
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