Cache: as quatro armadilhas que ninguém antecipa
- cache
- performance
- backend
Publicado em por Gabriel Dias · 5 min de leitura · Engenharia
Phil Karlton disse que existem apenas duas coisas difíceis em computação: invalidação de cache e dar nome às coisas. A piada envelheceu bem porque a primeira metade continua verdadeira.
Cache é guardar o resultado caro para não recalcular. A ideia é trivial. O que não é trivial é decidir quando aquele resultado deixou de valer — e o que acontece com o seu sistema no instante em que muitos deles deixam de valer ao mesmo tempo.
Onde cachear
Do mais perto do usuário para o mais longe:
- Navegador. Grátis, rápido, e você controla por cabeçalho HTTP. É a camada mais subutilizada.
- CDN. Serve conteúdo da borda, perto do usuário. Também controlada por cabeçalho, com a
diretiva
s-maxage. - Cache local da aplicação. Em memória, no próprio processo. Latência de nanossegundos. Problema: cada instância tem o seu, e eles divergem.
- Cache distribuído. Redis, Memcached. Compartilhado entre instâncias, consistente entre elas, com o custo de uma ida à rede.
- Cache do banco. Buffer pool, page cache do sistema operacional. Você não controla diretamente, mas ele explica por que a segunda execução da mesma query é muito mais rápida.
As três estratégias
Cache-aside é a mais comum: a aplicação procura no cache; se não achar, busca no banco e grava no cache. Simples, você controla tudo, e o cache pode cair sem derrubar a aplicação.
Write-through grava no cache e no banco ao mesmo tempo. Mais consistente, escrita mais lenta.
Write-behind grava no cache e no banco depois, em lote. Rápido e arriscado: se cair antes de descarregar, você perdeu escrita.
Para a maioria dos casos, cache-aside é a resposta certa. As outras duas resolvem problemas específicos e trazem modos de falha próprios.
Armadilha 1: dado velho
O TTL é a expressão numérica da sua tolerância a estar errado.
Não existe TTL certo em abstrato. Existe a resposta para: "quanto tempo esse dado pode estar desatualizado sem alguém se importar?".
Preço de produto: segundos. Nome de categoria: horas. Configuração de feature: minutos. Perfil do usuário logado: essa é a difícil, porque o próprio usuário percebe na hora — e aí a resposta é invalidar na escrita, não confiar no TTL.
O erro comum é escolher um TTL único para tudo, normalmente cinco minutos, porque foi o que estava no exemplo da documentação.
Armadilha 2: estouro de cache
Você populou o cache em um deploy, com TTL de uma hora. Uma hora depois, tudo expira no mesmo instante, e todo o tráfego bate no banco de uma vez.
A causa é a sincronização. A correção é o jitter: adicione uma variação aleatória ao TTL, tipo entre 50 e 70 minutos em vez de 60 exatos. Isso espalha as expirações.
É a mesma ideia do jitter em retry, e pelo mesmo motivo: eventos sincronizados criam picos que o sistema não aguenta, mesmo tendo capacidade média de sobra.
Armadilha 3: debandada
Uma chave popular expira. Mil requisições simultâneas descobrem isso no mesmo milissegundo e todas vão recalcular a mesma coisa.
O nome é thundering herd, e a solução é deixar apenas uma recalcular:
- Um lock por chave: quem pegar o lock recalcula; os outros esperam ou servem o valor antigo.
- Ou recomputação antecipada: quando faltar pouco para expirar, uma requisição sorteada renova em segundo plano enquanto as outras continuam servindo o valor atual.
A segunda é melhor, porque ninguém espera.
Armadilha 4: cache negativo
Alguém consulta um id que não existe. Você não encontra e não guarda nada. A próxima consulta ao mesmo id inexistente bate no banco de novo.
Se isso vier em volume — e vem, porque é assim que raspadores e testes automatizados se comportam —, você tem uma rota que ignora completamente o cache.
Guarde o negativo também, com TTL curto. E se o volume for realmente grande, um Bloom filter na frente responde "definitivamente não existe" sem nem tocar no cache.
O cache como dependência crítica
Cache-aside bem implementado degrada: sem cache, tudo fica mais lento, e continua funcionando. Isso exige que o caminho para o banco continue existindo e que o seu banco aguente a carga sem o cache — nem que seja com descarte de carga.
O número que importa
Monitore a taxa de acerto por chave ou por prefixo, não só a global.
Abaixo de oitenta por cento em cache de leitura, uma das duas coisas está errada: a chave está granular demais, ou o TTL está curto demais para o padrão de acesso. Nos dois casos, você está pagando a complexidade e a inconsistência sem receber o benefício.
E monitore também a taxa de invalidação. Se ela for alta, você está gastando mais tempo invalidando do que servindo.
Resumo prático
Cache troca frescor por velocidade. O trabalho de engenharia não é ligar o cache; é decidir explicitamente quanto de frescor você topa perder, por chave, e depois defender o sistema contra as quatro formas conhecidas de isso dar errado.
Um e-mail por semana, sem enrolação
O que eu aprendi construindo software e IA na semana — em texto curto, direto, sem newsletter de 3 mil palavras. Cancele quando quiser.