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:

typescript
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:

typescript
// 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:

typescript
// 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:

typescript
// 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:

bash
# 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)

Não quer configurar manualmente?

Implante o VPS para APIs em menos de 5 minutos com a Runstack. Infraestrutura da OPEN Datacenter, com servidores em São Paulo.

Perguntas frequentes

allkeys-lru: remove as chaves menos usadas recentemente quando a memória enche, ideal para cache puro onde você não se importa qual chave é removida. volatile-lru: remove apenas chaves com TTL definido. noeviction: retorna erro quando memória enche, use apenas se não pode perder dados. Para cache de API: allkeys-lru com maxmemory configurado.

Memcached: mais simples, melhor para cache de strings puras com alto throughput. Redis: estruturas de dados ricas (lists, sets, sorted sets, hashes), pub/sub, persistência, Lua scripts, streams. Em 2026: Redis é a escolha padrão, Memcached só faz sentido em casos muito específicos de cache de sessão em escala enorme.

Cache stampede: quando o TTL expira, muitas requisições simultâneas vão ao banco ao mesmo tempo. Soluções: (1) Probabilistic early expiration, renovar o cache antes de expirar com probabilidade crescente. (2) Lock: usar SETNX para garantir que apenas uma requisição regenera o cache. (3) Stale-while-revalidate: servir cache expirado enquanto regenera em background.

Por padrão: sim (modo sem persistência). Para não perder dados: habilitar RDB snapshots (save 900 1) ou AOF (appendonly yes). RDB: snapshot periódico, menor overhead mas pode perder últimas alterações. AOF: registra cada comando, mais durável mas arquivo maior. Para cache puro: sem persistência é aceitável. Para sessões e filas: AOF recomendado.

BullMQ com Redis: ideal para até ~1.000 jobs/segundo, retries automáticos, cron jobs, prioridades e delay. Para volumes maiores ou message ordering garantido: RabbitMQ ou Kafka. Para simplicidade máxima com baixo volume: pg-boss (usa PostgreSQL). Se você já tem Redis na stack: BullMQ adiciona zero infraestrutura nova.