DevGuideTK
← Trilha Backend

Cache

Estratégias para guardar respostas caras, e o problema difícil de saber quando descartá-las.

As decisões de cache são cinco: como o cache conversa com a origem, onde ele fica, como é invalidado, como se protege de picos e o que acontece quando ele enche.

Estratégias de leitura e escrita

EstratégiaComo funcionaVantagemCuidado
Cache-asideA aplicação confere o cache; na falha, lê a origem e grava no cacheSimples; o cache só guarda o que é usadoA primeira leitura de cada item é lenta; a aplicação cuida da invalidação
Read-throughUma biblioteca ou camada de cache busca na origem sozinha, na falhaO código só fala com o cacheDepende de uma biblioteca que conheça a origem
Write-throughCada escrita grava na origem e no cacheO cache está sempre atualizadoToda escrita fica mais lenta, inclusive de dados que ninguém lê
Write-behindA escrita vai para o cache e é gravada na origem depois, em loteEscritas muito rápidasRisco de perder dados se o cache cair antes de gravar
Atualização antecipadaItens populares são recarregados antes de expirarQuase não há falhas para os itens mais lidosTrabalho extra para recarregar itens que talvez não sejam lidos

Cache-aside é o ponto de partida para a maioria dos sistemas. É o padrão mais fácil de entender e de depurar, e uma falha do cache não impede o sistema de funcionar: ele só fica mais lento.

Evite write-behind para dados que não podem ser perdidos. Ele faz do cache a cópia mais recente do dado, e um cache não é feito para ser a única cópia de nada.

Em memória ou distribuído

Cache em memória do processoCache distribuído (Redis, Memcached)
VelocidadeA mais alta: sem redeUma ida e volta pela rede
Compartilhado entre instânciasNão: cada instância tem a sua cópiaSim
InvalidaçãoPrecisa chegar a todas as instânciasUm único lugar
Sobrevive a um deployNãoSim
Limite de tamanhoA memória do processoA memória do servidor de cache
Custo operacionalNenhumMais um serviço para operar e monitorar

Com várias instâncias da aplicação, o cache em memória tem um problema: uma instância invalida a sua cópia, e as outras continuam com a antiga até o TTL vencer. Por isso ele serve bem para dados que mudam raramente e toleram alguns segundos de atraso, como configurações e listas de referência, com TTL curto.

Os dois podem trabalhar juntos: uma camada em memória com TTL de segundos na frente de um Redis com TTL de minutos. As leituras mais frequentes nem saem do processo.

Invalidação

TécnicaComo funcionaQuando usar
Só por TTLO item expira sozinhoQuando um atraso igual ao TTL é aceitável
Apagar na escritaDepois de alterar o dado, o código apaga a chaveO padrão para cache-aside
Atualizar na escritaDepois de alterar o dado, o código grava o novo valor no cacheQuando a próxima leitura não pode ser lenta
Chave com versãoA chave inclui um número de versão: produto:7:v12; mudar a versão abandona as cópias antigasPara invalidar grupos de chaves de uma vez
Por eventoUma mensagem avisa todas as instâncias para apagar a chaveCaches em memória em várias instâncias

Apagar costuma ser melhor que atualizar. Atualizar o cache na escrita abre espaço para uma condição de corrida: duas escritas simultâneas podem gravar no cache na ordem inversa da que gravaram no banco, e o cache fica com o valor mais antigo. Apagar evita isso, porque a próxima leitura busca o valor atual no banco.

E apague depois de confirmar no banco. Se o cache for apagado antes do COMMIT, uma leitura no meio do caminho pode buscar o valor antigo, que ainda está no banco, e gravá-lo de volta no cache.

Proteção contra picos: o efeito manada

Uma chave muito lida expira. No mesmo segundo, 500 requisições encontram o cache vazio, e as 500 vão ao banco fazer a mesma consulta. O banco, que estava protegido pelo cache, recebe de uma vez a carga que o cache evitava. Esse efeito se chama manada (cache stampede ou thundering herd).

TécnicaComo funciona
Uma busca por vezSó uma requisição busca na origem; as outras esperam o resultado dela. Dentro de um processo, isso se chama single-flight; entre processos, usa-se uma trava no Redis
Servir o valor velho enquanto recarregaO item tem dois prazos: depois do primeiro, ainda é servido, e uma requisição o recarrega em segundo plano
Expiração com variação aleatóriaSomar alguns segundos aleatórios ao TTL evita que milhares de chaves criadas juntas expirem juntas
Atualização antecipadaItens populares são recarregados antes de expirar

A variação aleatória custa uma linha de código e deve estar sempre presente. As outras técnicas são para as chaves que realmente concentram tráfego.

Quando o cache enche

Um cache tem memória limitada. Quando enche, precisa decidir o que descartar:

PolíticaDescarta
LRUO item usado há mais tempo
LFUO item usado com menos frequência
TTL mais próximoO item que ia expirar primeiro
NenhumaNada: novas gravações passam a falhar

Desenho das chaves

PráticaExemplo
Prefixo por tipo de dadoproduto:7, usuario:42:permissoes
Tudo o que muda a resposta entra na chavebusca:pt-BR:teclado:pagina:2
Dados de usuário sempre com o usuário na chavecarrinho:usuario:42, nunca só carrinho
Versão do formato quando a estrutura mudav2:produto:7, para não ler um JSON antigo com o código novo

O erro mais grave de cache é de chave: uma resposta personalizada guardada numa chave que não identifica o usuário é entregue a todos os outros. É um vazamento de dados, e não um problema de desempenho.

O que não guardar em cache

DadoPor quê
Dados usados para decidir uma transação, como saldo e estoque na compraUma cópia velha leva a decisões erradas; consulte a origem
Dados sensíveis sem necessidadeO cache costuma ter menos controle de acesso e auditoria que o banco
Dados que mudam a cada leituraA taxa de acerto fica perto de zero
Algo que só existe no cacheUm cache pode ser apagado a qualquer momento

Resumo da decisão

  1. Estratégia: cache-aside, salvo motivo para outra.
  2. Local: Redis para dados compartilhados; memória do processo, com TTL curto, para dados de referência muito lidos.
  3. Invalidação: apagar a chave depois do COMMIT, sempre com TTL como garantia.
  4. Picos: TTL com variação aleatória sempre; uma busca por vez nas chaves mais quentes.
  5. Capacidade: maxmemory e política de descarte configuradas.
  6. Chaves: tudo o que muda a resposta entra na chave, a começar pelo usuário.

O próximo nível mostra cache-aside com Redis nas cinco linguagens, a proteção contra a manada e a invalidação depois da escrita.

Prompts para aprofundar

Ao abrir no ChatGPT ou no Claude, o prompt é enviado ao serviço escolhido pela URL e também fica copiado na sua área de transferência.

  • Escolher a estratégia de cache

    Quero escolher a estratégia de cache certa para o meu sistema.
    
    Faça estas perguntas, uma por vez, e espere as respostas:
    - Quais dados ou consultas estão lentos ou sobrecarregando o banco?
    - Com que frequência esses dados mudam, e quanto atraso é aceitável?
    - Quantas instâncias da aplicação existem?
    - Já existe Redis ou outro cache na infraestrutura?
    
    Com as respostas, explique para o meu caso as estratégias cache-aside, read-through, write-through, write-behind e atualização antecipada, e recomende uma. Explique também se o cache deve ficar em memória, distribuído ou nos dois, e como a invalidação vai funcionar.
    
    Termine com a recomendação, a forma de medir se ela funcionou e os riscos que ela traz.
  • Proteger o banco do efeito manada

    Quero entender o efeito manada no cache e as formas de evitá-lo.
    
    Explique com uma linha do tempo o que acontece quando uma chave muito lida expira e centenas de requisições chegam ao mesmo tempo.
    
    Depois compare as técnicas de proteção:
    1. Uma busca por vez dentro do processo (single-flight).
    2. Trava distribuída no Redis.
    3. Servir o valor velho enquanto um processo recarrega.
    4. Expiração com variação aleatória.
    5. Atualização antecipada probabilística.
    
    Para cada técnica, diga o que ela resolve, o que ela não resolve e o custo de implementá-la. Pergunte qual é o volume de tráfego das minhas chaves mais lidas e recomende uma combinação.
  • Configurar e operar o Redis como cache

    Quero configurar e operar corretamente um Redis usado como cache.
    
    Explique:
    1. Por que a política padrão de descarte do Redis é perigosa para um cache, e as alternativas (allkeys-lru, allkeys-lfu, volatile-*) com quando usar cada uma.
    2. Como dimensionar a memória.
    3. Por que separar o Redis de cache do Redis que guarda dados que não podem ser descartados, como filas e sessões.
    4. Quais métricas acompanhar (taxa de acerto, chaves descartadas, memória, latência) e onde encontrá-las.
    5. O que acontece com a aplicação quando o Redis cai, e como ela deve se comportar.
    6. Persistência e réplicas: quando fazem sentido para um cache.
    
    Pergunte se eu uso um serviço gerenciado ou um Redis próprio, e adapte as recomendações.