Pular para o conteúdo
Voltar para todos os posts

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.