Estrategias de Cache - Fundamentos da Otimizacao de Performance

14 min leitura | 2024.12.26

O que e Cache

Cache e um mecanismo que armazena temporariamente copias de dados em um local de acesso rapido. Reduz o acesso a fonte de dados original (banco de dados, API, etc.) e diminui o tempo de resposta.

Efeito do Cache: Se uma consulta ao banco de dados leva 100ms, a recuperacao do cache pode ser concluida em menos de 1ms.

Camadas de Cache

Cache do Navegador
      ↓
Cache CDN
      ↓
Cache da Aplicacao (Redis, etc.)
      ↓
Cache do Banco de Dados
      ↓
Banco de Dados

Padroes de Cache

Cache-Aside

A aplicacao gerencia diretamente o cache e o banco de dados.

async function getUser(userId) {
  // 1. Verificar cache
  const cached = await cache.get(`user:${userId}`);
  if (cached) {
    return JSON.parse(cached);
  }

  // 2. Cache miss: buscar no DB
  const user = await db.users.findById(userId);

  // 3. Salvar no cache
  await cache.setex(`user:${userId}`, 3600, JSON.stringify(user));

  return user;
}

Vantagens: Simples, resistente a falhas Desvantagens: Latencia no cache miss

Read-Through

O proprio cache e responsavel pela obtencao de dados.

// Configuracao da biblioteca de cache
const cache = new Cache({
  loader: async (key) => {
    // Chamado automaticamente em cache miss
    const userId = key.replace('user:', '');
    return await db.users.findById(userId);
  }
});

// Uso (simples!)
const user = await cache.get(`user:${userId}`);

Write-Through

Atualiza o cache e o DB simultaneamente durante a escrita.

async function updateUser(userId, data) {
  // Atualizar DB
  const user = await db.users.update(userId, data);

  // Atualizar cache simultaneamente
  await cache.setex(`user:${userId}`, 3600, JSON.stringify(user));

  return user;
}

Vantagens: Alta consistencia de dados Desvantagens: Aumento da latencia de escrita

Write-Behind

Escreve imediatamente no cache e reflete no DB de forma assincrona.

async function updateUser(userId, data) {
  // Atualizar cache imediatamente
  await cache.setex(`user:${userId}`, 3600, JSON.stringify(data));

  // Adicionar escrita no DB a fila
  await writeQueue.add({ userId, data });

  return data;
}

// Worker em background
writeQueue.process(async (job) => {
  await db.users.update(job.userId, job.data);
});

Vantagens: Escrita rapida Desvantagens: Risco de perda de dados

Invalidacao de Cache

TTL (Time To Live)

Expira automaticamente apos um determinado tempo.

// Expira apos 60 segundos
await cache.setex('key', 60, 'value');

Invalidacao Baseada em Eventos

Remove explicitamente o cache ao atualizar dados.

async function updateUser(userId, data) {
  await db.users.update(userId, data);

  // Invalidar caches relacionados
  await cache.del(`user:${userId}`);
  await cache.del(`user:${userId}:profile`);
  await cache.del(`users:list`);
}

Invalidacao Baseada em Padrao

// Excluir todos os caches relacionados ao usuario
const keys = await cache.keys('user:123:*');
await cache.del(...keys);

Problemas de Cache e Solucoes

Cache Stampede (Avalanche)

Problema onde muitas requisicoes causam cache miss simultaneamente.

// Solucao: usar lock
async function getWithLock(key, loader) {
  const cached = await cache.get(key);
  if (cached) return JSON.parse(cached);

  // Obter lock
  const lockKey = `lock:${key}`;
  const locked = await cache.set(lockKey, '1', 'NX', 'EX', 10);

  if (!locked) {
    // Outro processo esta carregando → esperar e tentar novamente
    await sleep(100);
    return getWithLock(key, loader);
  }

  try {
    const data = await loader();
    await cache.setex(key, 3600, JSON.stringify(data));
    return data;
  } finally {
    await cache.del(lockKey);
  }
}

Recalculo Probabilistico Antecipado

Atualiza o cache probabilisticamente antes do TTL expirar.

async function getWithProbabilisticRefresh(key, loader, ttl) {
  const data = await cache.get(key);
  const remainingTtl = await cache.ttl(key);

  // Se TTL restante e baixo, recalcular probabilisticamente
  if (data && remainingTtl < ttl * 0.1) {
    if (Math.random() < 0.1) {
      // 10% de chance de atualizacao em background
      loader().then(newData => {
        cache.setex(key, ttl, JSON.stringify(newData));
      });
    }
  }

  if (data) return JSON.parse(data);

  const newData = await loader();
  await cache.setex(key, ttl, JSON.stringify(newData));
  return newData;
}

Design de Chaves de Cache

// Bom design de chave
const key = `user:${userId}:profile:v2`;

// Componentes:
// - Prefixo: tipo de entidade
// - Identificador: ID unico
// - Sub-recurso: dados especificos
// - Versao: compatibilidade em mudancas de schema

Diretrizes de Design de TTL

Tipo de DadosTTLMotivo
Conteudo estatico1 dia a 1 semanaRaramente muda
Perfil de usuario1 a 24 horasBaixa frequencia de mudanca
Informacoes de configuracao5 a 30 minutosAtualizado moderadamente
Dados em tempo real1 a 5 minutosMuda frequentemente
Sessao30 minutos a 24 horasEquilibrio entre seguranca e UX

Resumo

Cache e uma tecnica fundamental de otimizacao de performance. Ao entender padroes como Cache-Aside e Write-Through e projetar estrategias apropriadas de TTL e invalidacao, voce pode construir sistemas rapidos e escalaveis. Considere o equilibrio entre complexidade e beneficios do cache ao implementa-lo.

← Voltar para a lista