Cache no back-end: muito além do Redis

Quando falamos sobre cache no back-end, muita gente pensa automaticamente em Redis.

E faz sentido. Redis é uma ferramenta extremamente usada para guardar dados temporários, sessões, resultados de consultas, tokens, rankings, rate limits e várias outras coisas.

Mas cache não é sinônimo de Redis.

Redis é apenas uma das formas de aplicar uma ideia muito mais ampla: guardar temporariamente uma informação já calculada para evitar repetir o mesmo trabalho várias vezes.

O que é cache?

Cache é uma estratégia para armazenar dados temporariamente em uma camada de acesso mais rápido.

Em vez de toda requisição precisar consultar o banco, chamar uma API externa ou processar uma regra pesada, o sistema pode reutilizar uma resposta que já foi gerada antes.

Um exemplo simples:

Primeira requisição:
cliente → aplicação → banco de dados → resposta
 
Próximas requisições:
cliente → cache → resposta

Na prática, o servidor faz uma pergunta antes de executar o trabalho pesado:

Eu já tenho esse resultado salvo em algum lugar?

Se sim, ele retorna o dado salvo.

Se não, ele busca a informação na fonte original, salva em cache e retorna a resposta.

Cache hit e cache miss

Dois termos aparecem bastante quando falamos sobre cache: cache hit e cache miss.

Cache hit acontece quando o dado procurado já existe no cache.

requisição chega
→ procura no cache
→ encontrou
→ retorna o dado salvo

Cache miss acontece quando o dado não está no cache ou já expirou.

requisição chega
→ procura no cache
→ não encontrou
→ consulta banco/API
→ salva no cache
→ retorna resposta

Esse fluxo é muito comum em APIs que precisam lidar com dados acessados repetidamente.

Cache não acontece em um lugar só

Um ponto importante é entender que cache pode existir em várias camadas da arquitetura.

Ele pode estar:

  • dentro da própria aplicação;
  • em servidores como Redis ou Memcached;
  • em proxies reversos como Nginx ou Apache;
  • em CDNs;
  • no navegador;
  • no banco de dados;
  • no sistema operacional;
  • e até em níveis mais baixos, como o processador.

Ou seja, cache não é uma ferramenta específica. É uma estratégia aplicada em diferentes pontos do caminho entre o usuário e o dado final.

Um fluxo real pode ser parecido com isso:

Navegador

CDN

Nginx / Apache

Aplicação

Redis

Banco de dados

A resposta pode ser encontrada em qualquer uma dessas camadas.

Quanto mais cedo ela for encontrada, menos trabalho chega até o back-end.

Cache em memória local

A forma mais simples de cache é guardar dados na própria memória da aplicação.

Em Node.js, por exemplo, poderíamos usar um Map:

const cache = new Map();
 
async function getProducts() {
  const key = "products:list";
 
  if (cache.has(key)) {
    return cache.get(key);
  }
 
  const products = await database.products.findMany();
 
  cache.set(key, products);
 
  return products;
}

Esse tipo de cache é extremamente rápido, porque a aplicação acessa a própria memória do processo.

Mas ele tem limitações importantes.

Se a aplicação reiniciar, o cache é perdido. Se existirem várias instâncias da API rodando, cada uma terá seu próprio cache. Isso pode gerar inconsistência entre servidores.

Por isso, cache em memória local funciona melhor para dados pequenos, simples e que podem ser recalculados sem grandes problemas.

Cache com Redis

Redis costuma ser usado quando precisamos de um cache compartilhado entre várias instâncias da aplicação.

Imagine três instâncias da mesma API:

API 1 ┐
API 2 ├── Redis
API 3 ┘

Todas consultam o mesmo lugar.

Isso resolve um problema comum do cache local: cada processo ter sua própria memória isolada.

Um exemplo simplificado:

async function getProducts() {
  const key = "products:list";
 
  const cached = await redis.get(key);
 
  if (cached) {
    return JSON.parse(cached);
  }
 
  const products = await database.products.findMany();
 
  await redis.set(key, JSON.stringify(products), {
    EX: 60
  });
 
  return products;
}

Nesse exemplo, o resultado da listagem de produtos fica salvo por 60 segundos.

Durante esse tempo, a API pode responder usando o Redis sem consultar novamente o banco.

Cache no Nginx, Apache e CDN

Cache também pode acontecer antes da requisição chegar na aplicação.

Por exemplo:

cliente → Nginx → aplicação

Se o Nginx tiver uma resposta cacheada, ele pode devolver essa resposta diretamente, sem chamar a API.

O mesmo conceito pode ser aplicado com Apache ou com uma CDN como Cloudflare.

Esse tipo de cache é muito útil para:

  • páginas públicas;
  • arquivos estáticos;
  • imagens;
  • CSS e JavaScript;
  • respostas públicas de APIs;
  • páginas de blog;
  • documentações.

Exemplo:

Primeira requisição:
cliente → CDN → Nginx → aplicação → banco
 
Próximas requisições:
cliente → CDN → resposta cacheada

Nesse cenário, a aplicação nem precisa ser acionada.

TTL: tempo de vida do cache

TTL significa Time To Live.

Ele define por quanto tempo um dado pode permanecer válido no cache.

Exemplos:

posts públicos → 5 minutos
dashboard → 1 minuto
ranking → 10 minutos
imagens estáticas → 30 dias
configurações → 1 hora

Quanto mais sensível ou mutável for o dado, menor tende a ser o TTL.

Um cache de imagem pode durar dias ou meses. Já um cache de dashboard talvez dure apenas alguns segundos ou minutos.

O problema da invalidação

Salvar algo em cache é fácil.

O difícil é saber quando aquele dado não deve mais ser usado.

Imagine que você cacheou uma listagem de produtos:

products:list

Depois, um produto foi atualizado no banco.

Se o cache continuar existindo, o usuário pode receber uma resposta antiga.

Para lidar com isso, existem algumas estratégias.

A mais simples é usar expiração por tempo:

products:list → expira em 60 segundos

Outra abordagem é apagar o cache quando algo muda:

await updateProduct(id, data);
 
await redis.del("products:list");
await redis.del(`product:${id}`);

Assim, na próxima requisição, a aplicação será obrigada a buscar dados atualizados.

Cuidado com dados privados

Um erro perigoso é criar chaves de cache genéricas para dados que dependem do usuário.

Por exemplo:

dashboard:overview

Se esse dashboard contém dados privados, essa chave é perigosa.

O correto seria incluir o contexto:

dashboard:overview:user:15

Ou, em sistemas multi-tenant:

dashboard:overview:company:8:user:15

Cache mal isolado pode fazer um usuário receber dados de outro. Esse é um dos piores tipos de bug em sistemas web.

Quando usar cache?

Cache faz sentido quando:

  • o dado é acessado muitas vezes;
  • a consulta é pesada;
  • existe repetição de processamento;
  • a resposta pode ficar alguns segundos ou minutos desatualizada;
  • você quer reduzir carga no banco ou em APIs externas.

Bons exemplos são dashboards, rankings, listagens públicas, páginas de blog, configurações e respostas de APIs externas.

Quando evitar cache?

Cache exige cuidado quando o dado precisa ser altamente consistente.

Exemplos:

  • saldo bancário;
  • pagamento;
  • estoque crítico;
  • permissões sensíveis;
  • dados que mudam o tempo todo;
  • respostas específicas de um usuário sem isolamento correto.

Isso não significa que nunca se usa cache nesses cenários, mas a estratégia precisa ser muito mais cuidadosa.

Conclusão

Cache não é Redis.

Redis é uma ferramenta poderosa, mas cache é um conceito muito maior.

Ele pode existir na memória da aplicação, em proxies reversos, em CDNs, no navegador, no banco de dados, no sistema operacional e em várias outras camadas.

No fim das contas, cache é uma troca.

Você ganha performance, reduz carga e melhora o tempo de resposta, mas assume complexidade com expiração, invalidação, segurança e consistência.

Por isso, cache não deve ser usado como uma gambiarra para esconder código lento.

Antes de cachear, vale olhar para queries ruins, ausência de índices, excesso de chamadas externas e problemas de modelagem.

Depois disso, cache entra como uma ótima estratégia para evitar trabalho repetido e tornar o sistema mais eficiente.