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)

    $ 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.

    Perguntas frequentes