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égia | Como funciona | Vantagem | Cuidado |
|---|---|---|---|
| Cache-aside | A aplicação confere o cache; na falha, lê a origem e grava no cache | Simples; o cache só guarda o que é usado | A primeira leitura de cada item é lenta; a aplicação cuida da invalidação |
| Read-through | Uma biblioteca ou camada de cache busca na origem sozinha, na falha | O código só fala com o cache | Depende de uma biblioteca que conheça a origem |
| Write-through | Cada escrita grava na origem e no cache | O cache está sempre atualizado | Toda escrita fica mais lenta, inclusive de dados que ninguém lê |
| Write-behind | A escrita vai para o cache e é gravada na origem depois, em lote | Escritas muito rápidas | Risco de perder dados se o cache cair antes de gravar |
| Atualização antecipada | Itens populares são recarregados antes de expirar | Quase não há falhas para os itens mais lidos | Trabalho 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 processo | Cache distribuído (Redis, Memcached) | |
|---|---|---|
| Velocidade | A mais alta: sem rede | Uma ida e volta pela rede |
| Compartilhado entre instâncias | Não: cada instância tem a sua cópia | Sim |
| Invalidação | Precisa chegar a todas as instâncias | Um único lugar |
| Sobrevive a um deploy | Não | Sim |
| Limite de tamanho | A memória do processo | A memória do servidor de cache |
| Custo operacional | Nenhum | Mais 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écnica | Como funciona | Quando usar |
|---|---|---|
| Só por TTL | O item expira sozinho | Quando um atraso igual ao TTL é aceitável |
| Apagar na escrita | Depois de alterar o dado, o código apaga a chave | O padrão para cache-aside |
| Atualizar na escrita | Depois de alterar o dado, o código grava o novo valor no cache | Quando a próxima leitura não pode ser lenta |
| Chave com versão | A chave inclui um número de versão: produto:7:v12; mudar a versão abandona as cópias antigas | Para invalidar grupos de chaves de uma vez |
| Por evento | Uma mensagem avisa todas as instâncias para apagar a chave | Caches 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écnica | Como funciona |
|---|---|
| Uma busca por vez | Só 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 recarrega | O item tem dois prazos: depois do primeiro, ainda é servido, e uma requisição o recarrega em segundo plano |
| Expiração com variação aleatória | Somar alguns segundos aleatórios ao TTL evita que milhares de chaves criadas juntas expirem juntas |
| Atualização antecipada | Itens 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ítica | Descarta |
|---|---|
| LRU | O item usado há mais tempo |
| LFU | O item usado com menos frequência |
| TTL mais próximo | O item que ia expirar primeiro |
| Nenhuma | Nada: novas gravações passam a falhar |
Desenho das chaves
| Prática | Exemplo |
|---|---|
| Prefixo por tipo de dado | produto:7, usuario:42:permissoes |
| Tudo o que muda a resposta entra na chave | busca:pt-BR:teclado:pagina:2 |
| Dados de usuário sempre com o usuário na chave | carrinho:usuario:42, nunca só carrinho |
| Versão do formato quando a estrutura muda | v2: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
| Dado | Por quê |
|---|---|
| Dados usados para decidir uma transação, como saldo e estoque na compra | Uma cópia velha leva a decisões erradas; consulte a origem |
| Dados sensíveis sem necessidade | O cache costuma ter menos controle de acesso e auditoria que o banco |
| Dados que mudam a cada leitura | A taxa de acerto fica perto de zero |
| Algo que só existe no cache | Um cache pode ser apagado a qualquer momento |
Resumo da decisão
- Estratégia: cache-aside, salvo motivo para outra.
- Local: Redis para dados compartilhados; memória do processo, com TTL curto, para dados de referência muito lidos.
- Invalidação: apagar a chave depois do
COMMIT, sempre com TTL como garantia. - Picos: TTL com variação aleatória sempre; uma busca por vez nas chaves mais quentes.
- Capacidade:
maxmemorye política de descarte configuradas. - 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.