SYSTEM DESIGN · CACHE · LEITURA DE ~12 MIN

Estratégias de Cache

Cache não é apenas “colocar Redis na frente do banco”. A forma como a aplicação lê, escreve, atualiza e invalida os dados em cache muda completamente o comportamento do sistema.

Diagrama desenhado à mão mostrando usuário, aplicação, cache e banco, com destaque para hit, miss e TTL.
CACHE · BANCO DE DADOS · CAMADAS DE LEITURA

SEÇÃO 01

O que é cache?

Cache é uma camada de armazenamento rápida usada para manter temporariamente dados que seriam mais caros ou demorados para buscar novamente.

SEM CACHEFLUXO
Usuário
   ↓
Aplicação
   ↓
Banco de dados
COM CACHEFLUXO
Usuário
   ↓
Aplicação
   ↓
Cache
   ├── dado encontrado → retorna rapidamente
   └── dado ausente → consulta o banco

Imagine que 10 mil pessoas consultem o mesmo produto. Sem cache, são 10 mil requisições e 10 mil consultas ao banco. Com cache, a primeira consulta busca o dado no banco, salva no cache e as próximas requisições são atendidas pela camada rápida.

O cache funciona como uma camada de proteção, reduzindo leituras repetidas, latência e pressão sobre o banco de dados. Entretanto, surge um novo desafio: como manter o cache sincronizado com a fonte original dos dados?

Comparação visual entre cache-aside e read-through, mostrando quem consulta o banco quando ocorre cache miss.
CACHE-ASIDE X READ-THROUGH · QUEM CARREGA O DADO?

SEÇÃO 02

1. Cache-aside

Cache-aside é uma das estratégias mais comuns. Nela, a aplicação é responsável por consultar tanto o cache quanto o banco.

CACHE-ASIDELEITURA
Aplicação
   ↓
Cache
   ├── Cache hit → retorna
   └── Cache miss
           ↓
        Banco
           ↓
      Salva no cache
           ↓
        Retorna

O fluxo é direto: a aplicação procura o dado no cache. Se encontrar, retorna. Se não encontrar, consulta o banco, salva o resultado no cache e retorna o dado.

EXEMPLOPYTHON
def get_user(user_id):
    user = cache.get(f"user:{user_id}")

    if user is None:
        user = database.get_user(user_id)

        if user is not None:
            cache.set(f"user:{user_id}", user, ttl=600)

    return user

A primeira requisição pode ser mais lenta porque precisa consultar o banco. As próximas são atendidas pelo cache. Essa estratégia também é conhecida como lazy loading, pois somente os dados realmente solicitados são armazenados.

Atualizações no cache-aside

Quando um dado é alterado, normalmente a aplicação atualiza o banco e invalida o cache. A próxima leitura busca o valor atualizado.

INVALIDAÇÃOPYTHON
def update_user(user_id, values):
    database.update_user(user_id, values)
    cache.delete(f"user:{user_id}")
  • Vantagens: implementação relativamente simples, uso de memória apenas para dados acessados e grande redução de leituras no banco.
  • Desvantagens: a aplicação precisa gerenciar cache e banco, a primeira leitura sofre cache miss, e dados podem ficar desatualizados se a invalidação falhar.

SEÇÃO 03

2. Read-through

Read-through é parecido com cache-aside, mas existe uma diferença importante: a aplicação consulta apenas o cache. Quando o dado não existe, o próprio cache busca no banco.

READ-THROUGHLEITURA
Aplicação
   ↓
Cache
   ├── encontrou → retorna
   └── não encontrou
           ↓
      Cache consulta o banco
           ↓
      Cache armazena o dado
           ↓
          retorna

A aplicação executa algo parecido com cache.get("user:123"). Internamente, a infraestrutura de cache é responsável por carregar o dado.

DIFERENÇA CENTRAL

No cache-aside, a aplicação consulta o banco em um cache miss. No read-through, o próprio cache consulta o banco.

  • Vantagens: simplifica o código da aplicação, centraliza a lógica de carregamento e padroniza o comportamento de cache.
  • Desvantagens: exige uma camada de cache mais inteligente, aumenta o acoplamento com a solução de cache e nem toda ferramenta oferece suporte nativo.

SEÇÃO 04

3. Write-through

No write-through, toda escrita passa pelo cache. O cache salva o dado e grava de forma síncrona no banco antes de confirmar a operação.

WRITE-THROUGHESCRITA SÍNCRONA
Aplicação
   ↓
Cache
   ↓
Banco de dados
   ↓
Confirmação

Quando o usuário atualiza seu nome, a aplicação envia a atualização ao cache, o cache grava a alteração no banco, mantém o novo valor e só então a aplicação retorna sucesso.

Fluxo write-through: aplicação escreve no cache, cache grava no banco de forma síncrona e só depois retorna OK.
WRITE-THROUGH · CACHE ATUALIZADO, BANCO CONFIRMADO

Como o banco precisa ser atualizado antes da resposta, a escrita pode ser mais lenta. Entretanto, quando a operação termina, o cache já possui o valor atualizado.

  • Vantagens: cache e banco ficam sincronizados, há menor risco de retornar dados desatualizados e leituras realizadas logo após a escrita são rápidas.
  • Desvantagens: a escrita continua dependendo da velocidade do banco, cada escrita no cache gera uma escrita no banco e dados que nunca serão lidos também podem ocupar cache.
REGRA PRÁTICA

Write-through não existe para reduzir a quantidade de escritas no banco. Seu principal objetivo é garantir consistência entre cache e banco.

SEÇÃO 05

4. Write-behind

Write-behind, também chamado de write-back, prioriza velocidade de escrita. A aplicação grava no cache e recebe confirmação imediatamente. A persistência no banco acontece depois, de forma assíncrona.

Fluxo write-behind: aplicação grava no cache e responde rápido, enquanto fila e worker persistem no banco depois.
WRITE-BEHIND · RESPOSTA RÁPIDA, PERSISTÊNCIA DEPOIS
WRITE-BEHINDESCRITA ASSÍNCRONA
Aplicação
   ↓
Cache
   ↓
Resposta ao usuário

Enquanto isso:

Cache ou fila
   ↓
Worker
   ↓
Banco de dados

Essa estratégia é útil quando o sistema precisa responder rapidamente e pode aceitar consistência eventual. Ela também pode amortecer picos de escrita: 10 mil requisições entram no cache e em uma fila, enquanto o banco processa no ritmo suportado.

Também é possível consolidar várias alterações. Um contador de 1.000 visualizações pode ser atualizado no cache muitas vezes e persistido no banco como uma atualização consolidada.

  • Vantagens: resposta rápida, redução de picos de escrita, processamento em lote, maior throughput e desacoplamento entre aplicação e banco.
  • Desvantagens: risco de perda de dados, diferença temporária entre banco e cache, maior complexidade operacional, retries, monitoramento e controle de backlog.
CENÁRIO PERIGOSO

Cache atualizado, banco ainda não atualizado, cache falha: o dado pode ser perdido. Por isso, implementações robustas usam filas duráveis, persistência, idempotência e reprocessamento.

SEÇÃO 06

5. Refresh-ahead

Refresh-ahead atualiza os dados antes que o TTL expire. A ideia é manter dados populares sempre quentes no cache.

SEM REFRESH-AHEADTTL
10:00 → produto salvo no cache
10:10 → cache expira
10:11 → próximo usuário consulta
10:11 → aplicação busca no banco
COM REFRESH-AHEADTTL
10:00 → produto salvo no cache
10:08 → sistema percebe que ele continua popular
10:09 → atualiza antecipadamente
10:10 → item continua disponível

Enquanto o dado continuar sendo muito acessado, ele pode permanecer quente no cache. Isso reduz cache misses e evita que o usuário espere depois de uma expiração.

  • Vantagens: reduz cache misses, mantém dados populares disponíveis e melhora a latência de leituras previsíveis.
  • Desvantagens: pode atualizar dados que não serão usados novamente, aumentar consultas ao banco e depender de uma boa previsão de popularidade.
Linha do tempo de refresh-ahead: item popular é renovado antes da expiração para evitar cache miss.
REFRESH-AHEAD · RENOVAR ANTES DO MISS

SEÇÃO 07

Comparação entre as estratégias

Colocando as estratégias lado a lado, fica mais claro o que cada uma tenta otimizar.

EstratégiaQuem consulta o banco?Momento da escritaPrincipal objetivo
Cache-asideAplicaçãoDiretamente no bancoSimplicidade e redução de leituras
Read-throughCacheNão define a escritaSimplificar o carregamento
Write-throughCacheSíncronaConsistência
Write-behindWorker ou cacheAssíncronaVelocidade e controle de picos
Refresh-aheadProcesso automáticoAntes da expiraçãoEvitar cache miss em dados populares

SEÇÃO 08

Qual estratégia escolher?

Não existe uma estratégia universalmente melhor. Cache-aside costuma funcionar bem como ponto inicial para aplicações com muitas leituras. Write-through é indicado quando a aplicação precisa garantir que cache e banco estejam sincronizados antes de confirmar a escrita.

Write-behind faz sentido quando baixa latência e alto volume de escrita são mais importantes, desde que o sistema consiga lidar com consistência eventual. Refresh-ahead funciona melhor para dados populares e previsíveis. Read-through é interessante quando a infraestrutura pode esconder da aplicação toda a lógica de carregamento.

Também é possível combinar estratégias: cache-aside para leituras, invalidação após atualização e refresh-ahead para itens populares. Ou write-through para dados críticos e write-behind para métricas e contadores.

Guia visual de escolha entre cache-aside, read-through, write-through, write-behind e refresh-ahead.
GUIA DE DECISÃO · QUAL ESTRATÉGIA ESCOLHER?
CHECKLIST

Qual é o volume de leituras? Qual é o volume de escritas? O sistema aceita consistência eventual? Qual é o impacto de perder uma atualização? Quanto tempo o dado pode permanecer desatualizado?

Cache não é apenas uma ferramenta para armazenar dados temporários. Ele faz parte da arquitetura e influencia diretamente consistência, latência, disponibilidade e pressão sobre o banco de dados.

A melhor estratégia não é aquela que parece mais rápida no diagrama. É aquela que oferece o equilíbrio correto entre performance, consistência e complexidade para o contexto da aplicação.

FIM · OBRIGADO POR LER

Cache bom é decisão de arquitetura.

Quando o sistema cresce, a pergunta deixa de ser “tem cache?” e passa a ser “qual contrato esse cache tem com o banco?”.

LER SYSTEM DESIGN VOLTAR AOS CONTEÚDOS