Redis: estratégias avançadas de cache
Redis vai muito além de um simples cache de chave-valor — é a espinha dorsal de performance de aplicações escaláveis. Cache-aside, write-through, pub/sub para notificações em tempo real, filas de jobs e rate limiting distribuído são padrões que eliminam gargalos de banco de dados e permitem que uma VPS sirva 10× mais requisições.
Padrões de cache: Cache-Aside e Write-Through
Os dois padrões mais usados em produção:
import { createClient } from 'redis'
const redis = createClient({ url: 'redis://localhost:6379' })
await redis.connect()
// ── Cache-Aside (Lazy Loading) ────────────────────────────
// Lê do cache; busca no banco apenas se não encontrar
async function getUsuario(id: string) {
const chave = `usuario:${id}`
const cached = await redis.get(chave)
if (cached) return JSON.parse(cached)
const usuario = await db.query('SELECT * FROM usuarios WHERE id = $1', [id])
if (usuario) {
await redis.setEx(chave, 300, JSON.stringify(usuario)) // TTL 5 min
}
return usuario
}
// ── Write-Through ─────────────────────────────────────────
// Atualiza banco E cache na mesma operação
async function atualizarUsuario(id: string, dados: object) {
const usuario = await db.query(
'UPDATE usuarios SET ... WHERE id = $1 RETURNING *', [id]
)
// Atualizar cache imediatamente após o banco:
await redis.setEx(`usuario:${id}`, 300, JSON.stringify(usuario))
return usuario
}
// ── Cache Invalidation ────────────────────────────────────
// Invalidar cache quando dados mudam
async function deletarUsuario(id: string) {
await db.query('DELETE FROM usuarios WHERE id = $1', [id])
await redis.del(`usuario:${id}`)
// Invalidar listas que contêm o usuário:
await redis.del('usuarios:lista:*') // glob pattern com SCAN
}Rate limiting distribuído com Redis
Rate limiting que funciona com múltiplas instâncias da API:
// Rate limiter com sliding window usando Redis:
async function checkRateLimit(
ip: string,
limite: number = 100,
janela: number = 60 // segundos
): Promise<{ allowed: boolean; remaining: number }> {
const chave = `ratelimit:${ip}`
const agora = Date.now()
const inicio = agora - janela * 1000
const pipeline = redis.multi()
pipeline.zRemRangeByScore(chave, '-inf', inicio) // remover entradas antigas
pipeline.zAdd(chave, { score: agora, value: `${agora}` })
pipeline.zCard(chave) // contar requisições na janela
pipeline.expire(chave, janela)
const [,, count] = await pipeline.exec() as [null, null, number, null]
const allowed = count <= limite
return { allowed, remaining: Math.max(0, limite - count) }
}
// Middleware Express:
app.use(async (req, res, next) => {
const { allowed, remaining } = await checkRateLimit(req.ip!)
res.setHeader('X-RateLimit-Remaining', remaining)
if (!allowed) return res.status(429).json({ error: 'Rate limit exceeded' })
next()
})Pub/Sub para notificações em tempo real
Comunicação entre instâncias e invalidação de cache distribuída:
// Publisher — notificar quando dado muda:
const publisher = createClient({ url: 'redis://localhost:6379' })
await publisher.connect()
async function atualizarProduto(id: string, dados: object) {
await db.update(id, dados)
// Notificar todos os servidores para invalidar cache:
await publisher.publish('cache:invalidar', JSON.stringify({
tipo: 'produto',
id,
}))
}
// Subscriber — cada instância da API ouve:
const subscriber = createClient({ url: 'redis://localhost:6379' })
await subscriber.connect()
await subscriber.subscribe('cache:invalidar', (mensagem) => {
const { tipo, id } = JSON.parse(mensagem)
if (tipo === 'produto') {
localCache.delete(`produto:${id}`) // invalidar cache local
console.log(`Cache invalidado: produto ${id}`)
}
})
// SSE (Server-Sent Events) com Pub/Sub para notificações ao browser:
app.get('/eventos', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream')
const sub = createClient({ url: 'redis://localhost:6379' })
sub.connect().then(() => {
sub.subscribe('notificacoes', (msg) => {
res.write(`data: ${msg}
`)
})
})
req.on('close', () => sub.disconnect())
})Filas de jobs com Bull/BullMQ
Processar tarefas pesadas em background com Redis:
// BullMQ — fila de jobs com Redis:
// npm install bullmq
import { Queue, Worker } from 'bullmq'
const connection = { host: 'localhost', port: 6379 }
// Criar fila:
const emailQueue = new Queue('emails', { connection })
const pdfQueue = new Queue('pdfs', { connection })
// Adicionar jobs:
await emailQueue.add('enviar-boas-vindas', {
para: 'usuario@email.com',
nome: 'João',
}, {
attempts: 3, // tentar 3 vezes em caso de erro
backoff: { type: 'exponential', delay: 1000 },
removeOnComplete: 100, // manter últimos 100 jobs completados
})
// Worker que processa os jobs:
const emailWorker = new Worker('emails', async (job) => {
await enviarEmail(job.data.para, job.data.nome)
return { enviado: true }
}, {
connection,
concurrency: 5, // processar 5 emails simultâneos
})
emailWorker.on('completed', (job) => {
console.log(`Email enviado: ${job.id}`)
})
emailWorker.on('failed', (job, err) => {
console.error(`Falha no job ${job?.id}:`, err.message)
})Monitorar Redis e otimizar uso de memória
Ferramentas para diagnosticar o Redis em produção:
# Monitorar comandos em tempo real:
redis-cli monitor
# Estatísticas do servidor:
redis-cli info stats | grep -E "instantaneous|total_commands|rejected"
redis-cli info memory | grep -E "used_memory_human|maxmemory|mem_fragmentation"
# Hit rate do cache (deve ser > 90%):
redis-cli info stats | grep -E "keyspace_hits|keyspace_misses"
# Hit rate = hits / (hits + misses)
# Ver chaves mais grandes (latência!):
redis-cli --bigkeys
# Ver chaves com TTL próximo de expirar:
redis-cli --scan --pattern "usuario:*" | head -20
# Configurar eviction policy (o que fazer quando memória enche):
redis-cli config set maxmemory 512mb
redis-cli config set maxmemory-policy allkeys-lru # LRU — mais usado em cache
# Persistência no redis.conf:
# save 900 1 # salvar se 1 chave mudou em 15 min
# save 300 10 # salvar se 10 chaves mudaram em 5 min
# appendonly yes # AOF — durabilidade máxima (mais lento)$ runstack deploy --plan starter
Não quer configurar manualmente?
Não quer configurar manualmente? Implante o VPS para APIs em menos de 3 minutos com a Runstack. Infraestrutura da OPEN DATACENTER, com servidores no Brasil.