Caching em Sistemas Distribuídos: Cache-Aside, Write-Through e Write-Behind
Cache é a solução mais barata para escalar leitura. Mas qual estratégia usar? Descubra as diferenças entre Cache-Aside, Write-Through, Write-Behind e quando cada uma se aplica em arquiteturas distribuídas.
Pontos principais do artigo:
Arquitetura e design patterns aplicados
Exemplos de código prontos para produção
Trade-offs e decisões de engenharia
Quando usar e quando evitar.
Em escala pequena, cache parece atalho.
Em produção, cache vira sistema distribuído.
Ele tem pressão de memória, regras de roteamento, dados stale, comportamento de cliente, falhas parciais, segurança, custo e operação.
O erro comum é tratar Redis ou Memcached como camada mágica de velocidade.
O modelo mental correto é mais rígido:
- banco de dados guarda a verdade durável,
- cache guarda aceleração temporária,
- clientes precisam de fallback cuidadoso,
- plataforma precisa controlar invalidação, capacidade e visibilidade.
Cache distribuído bom reduz latência e carga no banco sem virar fonte oculta de verdade.
Este artigo desenha esse sistema de ponta a ponta.
Redis e Memcached aparecem juntos porque ambos continuam relevantes.
Redis oferece estruturas ricas, replicação, persistência opcional, Lua/functions, streams, sorted sets e Redis Cluster.
Memcached oferece um cache key-value em memória simples, rápido e normalmente shardado no cliente.
Nenhum deles elimina design.
Sumário
- Análise de Requisitos
- Cálculos de Envelope
- Arquitetura de Alto Nível
- Design de API
- Modelagem de Dados
- Padrões Centrais de Cache
- Sharding e Roteamento
- Hash Slots no Redis Cluster
- Memcached em Escala
- Replicação e Failover
- Cache Multi-Região
- Invalidação e Estratégia de TTL
- Eviction e Gestão de Memória
- Semântica de Consistência
- Mitigação de Hot Keys
- Thundering Herd e Prevenção de Dogpile
- Negative Caching e Admission Control
- Confiabilidade e Degradação
- Segurança
- Observabilidade e SLOs
- Dicas de Entrevista
- Anti-Patterns
- Conclusão
- Referências
- Referência Rápida
Análise de Requisitos
A primeira decisão em entrevista é escopo.
Não comece por Redis.
Comece pelo workload.
Requisitos Funcionais
- Servir objetos lidos com frequência com latência menor que a fonte de verdade.
- Reduzir pressão de leitura em bancos primários e APIs downstream.
- Suportar leituras e escritas key-value para objetos serializados.
- Suportar expiração por TTL.
- Suportar invalidação explícita depois de writes.
- Suportar fallback seguro em cache miss ou falha do cache.
- Suportar escala horizontal entre nós de cache.
- Suportar chaves de alta cardinalidade sem posicionamento manual.
- Suportar mitigação de hot keys.
- Expor métricas de hit rate, latência, memória, evictions e erros.
- Suportar controle de acesso e transporte criptografado quando necessário.
- Manter comportamento previsível durante deploys, failovers e resharding.
Requisitos Não Funcionais
| Requisito | Meta | Por que importa |
|---|---|---|
| Latência de get | < 2ms p99 dentro da região | cache precisa ganhar do banco |
| Disponibilidade do cache | 99,99% no caminho de leitura | falha não pode derrubar app |
| Hit rate | 80-95% para objetos cacheáveis | abaixo disso custo pode não fechar |
| Tolerância a stale | explícita por objeto | cada dado envelhece de forma diferente |
| Eficiência de memória | medida por classe de objeto | memória é custo principal |
| Tempo de failover | segundos a poucos minutos | incidente longo sobrecarrega banco |
| Estabilidade de roteamento | pouco movimento em mudança de nó | resharding não deve esfriar tudo |
| Clareza operacional | alertas ligados a impacto | hit rate sozinho engana |
Perguntas de Clarificação
- Quais dados podem ser cacheados?
- Quanto stale cada objeto pode ficar?
- Cache é local a um serviço ou compartilhado por vários serviços?
- Read-through mora numa biblioteca, proxy ou aplicação?
- Qual distribuição de tamanho dos objetos?
- Qual QPS de pico e qual QPS de miss?
- O que acontece se o cache cair?
- Consistência cross-region é obrigatória?
- Dados exigem criptografia ou isolamento forte?
- Writes são frequentes o suficiente para complicar invalidação?
Premissas Deste Design
Este desenho assume:
- produto SaaS multi-tenant,
- workload read-heavy,
- deploy regional de aplicações,
- Redis Cluster para casos com recursos ricos,
- pool Memcached para cache simples de objetos,
- banco relacional como fonte de verdade,
- Kafka ou equivalente para eventos de invalidação,
- meta de p99 abaixo de 150ms em APIs de usuário,
- budget de staleness de 30 segundos para perfis e catálogo,
- política de não cachear decisões críticas de autorização sem versionamento explícito.
O Que Este Cache Não É
Cache não é banco primário.
Cache não é log de auditoria.
Cache não é ledger de pagamento.
Cache não é a única cópia de estado de usuário.
Cache pode esquecer.
Esquecer é recurso quando a fonte de verdade continua correta.
Esquecer é incidente quando o cache virou banco sem ninguém admitir.
Cálculos de Envelope
Declare premissas antes de números.
O número exato importa menos que o formato da pressão.
Escala Assumida
Usuários ativos mensais: 80 milhões
Usuários ativos diários: 20 milhões
Requests de API no pico: 1.200.000/s
Requests médios de API: 250.000/s
Percentual de leitura cacheável: 65%
Hit rate alvo: 90%
Tamanho médio de objeto cacheado: 2 KB
P95 de objeto cacheado: 12 KB
Objetos quentes possíveis: 250 milhões de chaves
Objetos alterados por dia: 40 milhões
Regiões: 3 ativas
Carga de Requests
Leituras cacheáveis no pico = 1.200.000 * 0,65
= 780.000 leituras/s
Com hit rate de 90%:
cache hits/s = 702.000
cache misses/s = 78.000
78.000 leituras por segundo no banco ainda é muito.
O caminho de miss precisa de proteção.
O cache deve reduzir carga média e limitar amplificação em miss.
Estimativa de Memória
Objetos quentes lógicos: 250.000.000
Payload médio: 2 KB
Overhead de metadados e allocator: 35%
Fator de replicação: 2
Payload bruto = 250M * 2KB
~= 500 GB
Com overhead = 500GB * 1,35
~= 675 GB
Com uma réplica = 675GB * 2
~= 1,35 TB
Essa é estimativa direcional.
Memória real no Redis muda com encoding, tamanho de chave, tipo de objeto, fragmentação, buffers de replicação e buffers de persistência.
Memória real no Memcached depende de slab classes, distribuição de tamanho e espaço desperdiçado em chunks.
Estimativa de Rede
Banda de cache hit no pico ~= 702.000 * 2KB
~= 1,4 GB/s de payload
Com overhead de protocolo, TLS e objetos p95:
planeje múltiplos GB/s dentro da região.
Rede não é detalhe.
Cache pode mover gargalo de CPU do banco para NICs, conexões de cliente ou custo cross-AZ.
Estimativa de Miss Storm
Se deploy limpa 30% das chaves quentes no pico:
novo miss rate = misses normais + chaves frias
miss QPS normal = 78.000
leitura fria extra = 780.000 * 0,30
= 234.000
miss QPS total = 312.000
Se cada miss abre 3 queries:
QPS no banco = 936.000
Isso pode derrubar a fonte de verdade.
Warming, request coalescing, TTL com jitter e admission control não são opcionais em escala alta.
Insight Central
Design de cache distribuído é design para limitar dano em miss.
O caminho feliz é simples.
O caminho de miss decide se o sistema sobrevive.
Artigo técnico focado em aplicação prática. Sem enrolação.
Leia completo:
https://lemon.dev.br/pt/blog/distributed-cache-system-design