
System Design: Estratégias de Cache Distribuído à prova de balas

"Existem apenas duas coisas difíceis em Ciência da Computação: invalidação de cache e nomear as coisas." — Phil Karlton
Todo engenheiro sênior sabe colocar um Redis na frente do banco de dados para "ficar rápido". Mas poucos sabem o que acontece quando esse Redis cai, ou quando uma chave expira sob carga extrema.
Neste deep dive de System Design, vamos além do básico. Vamos explorar padrões de invalidação, políticas de eviction e como grandes empresas como o Facebook resolveram o problema do Thundering Herd.
1. Padrões de Acesso ao Cache

Antes de falar de problemas, precisamos definir como sua aplicação fala com o cache.
Cache-Aside (Lazy Loading)
É o padrão mais comum.
- A aplicação pede a chave
user:123ao Cache. - Miss: O Cache não tem. A aplicação vai ao Banco de Dados.
- A aplicação grava o resultado no Cache e devolve ao usuário.
- Pró: Resiliente. Se o Cache cair, o sistema continua (embora lento).
- Contra: Inconsistência. Se alguém atualizar o banco, o cache fica velho até expirar (TTL).
Write-Through
A aplicação grava no Cache e no Banco simultaneamente.
- Pró: O cache nunca está velho (Stale).
- Contra: Escrita lenta (2 round-trips).
Write-Back (Write-Behind)
A aplicação grava apenas no Cache. O Cache grava no Banco assincronamente depois.
- Pró: Escrita ultra-rápida.
- Contra: Perigo de perda de dados se o Cache cair antes de persistir no disco.
2. O Pesadelo: Thundering Herd (A Manada Estourada)

Imagine que você tem uma chave muito popular, digamos homepage_news, que expira a cada 5 minutos.
Às 14:00:00, essa chave expira.
Neste exato milissegundo, 10.000 usuários acessam a home.
- Usuário 1 vê Cache Miss -> Vai ao Banco.
- Usuário 2 vê Cache Miss -> Vai ao Banco.
- ...
- Usuário 10.000 vê Cache Miss -> Vai ao Banco.

Seu banco de dados recebe 10.000 queries idênticas e pesadas no mesmo segundo. A CPU do banco vai a 100%, as conexões saturam, e o site cai. Isso é o Thundering Herd.
Solução 1: Request Coalescing (Singleflight)
No código da aplicação, você usa um padrão de "Singleflight". Se 10.000 requests pedem a mesma coisa, o primeiro "ganha" o direito de ir ao banco. Os outros 9.999 ficam pausados esperando o retorno desse único request.
- Pró: Resolve o problema na máquina local.
- Contra: Em sistemas distribuídos com 50 servidores web, ainda teremos 50 queries ao banco.
Solução 2: Probabilistic Early Expiration (Jitter)
Se o TTL é 60 segundos, não espere dar 60s para recalcular. A partir dos 50s, faça um cálculo probabilístico: "Tenho 10% de chance de revalidar esse cache agora, mesmo que ele ainda seja válido". Isso espalha a revalidação no tempo, evitando que todos expirem juntos.
Solução 3: O Padrão "Lease" do Facebook (GIGANTESCO)
O Facebook modificou o Memcached para resolver isso. Quando ocorre um Miss, o Memcached retorna um token (Lease) para apenas um cliente.
- Cliente A: Recebe o Lease. Vai ao banco.
- Cliente B: Recebe um aviso "Alguém já está calculando. Espere um pouco e use o dado velho por enquanto (Stale)".
Isso garante que apenas uma query bata no banco, não importa o tamanho do cluster.
3. Políticas de Eviction: O que jogar fora?
O cache encheu. Quem sai?
- LRU (Least Recently Used): Remove o que foi acessado há mais tempo. Padrão de ouro.
- LFU (Least Frequently Used): Remove o que é pouco popular. Bom para padrões estáveis, ruim para tendências passageiras.
- TTL (Time To Live): Tudo morre após X segundos. Simples e eficaz.
Sempre configure o maxmemory e o maxmemory-policy no Redis. Se você não fizer isso, quando a RAM acabar, o Redis para de aceitar escritas e seu app quebra.
4. Invalidação: O Problema Hardcore
"Existem apenas duas coisas difíceis..." lembra? Como garantir que o cache seja limpo quando o dado muda?
- TTL Curto: A abordagem preguiçosa. "Aceito dados velhos por 60 segundos".
- Event-Driven Invalidation: O serviço de atualização emite um evento (Kafka/RabbitMQ) "User 123 mudou". O consumidor do evento vai no Redis e dá
DEL user:123. - Versionamento de Chaves: Em vez de
user:123, useuser:123:v1. Quando mudar, escrevauser:123:v2. O v1 fica lá até o LRU limpar. Útil para CDN e arquivos estáticos.
Conclusão
Cache não é apenas "armazenamento rápido". É um componente complexo de estado distribuído. Se você desenhar sua estratégia de cache errado, você não terá apenas dados velhos; você terá um sistema que cai sob carga (Thundering Herd) e que corrompe dados silenciosamente (Invalidação Falha).
Glossário Técnico
- Cache Hit: O dado estava na memória.
- Cache Miss: O dado não estava, precisou ir à fonte (lento).
- Eviction: O ato de remover um item do cache para liberar espaço.
- Hot Key: Uma chave acessada desproporcionalmente mais que as outras (ex: perfil do Neymar).
- TTL (Time-To-Live): Tempo de vida de um registro antes de ser considerado inválido.
Referências
- Facebook Engineering. Scaling Memcache at Facebook. O paper lendário.
- Redis. Using Redis as an LRU cache.
- Amazon Builders' Library. Caching challenges and strategies.
