O problema do cache local em ambiente escalado

Cada instância com seu próprio cache cria inconsistências inevitáveis

Em uma aplicação com uma única instância, cache em memória local funciona bem: todos os dados estão no mesmo processo, não há inconsistência entre instâncias. Mas ao escalar horizontalmente para três, dez ou cem instâncias, cada servidor mantém seu próprio cache independente. O usuário que atualizou o perfil pode ver o dado antigo na próxima requisição se ela for roteada para uma instância com cache desatualizado. Um produto com estoque atualizado pode aparecer como disponível para usuários em uma instância enquanto aparece como esgotado em outra. Cache local em ambiente com múltiplas instâncias é uma fonte constante de inconsistência difícil de debugar em produção.

Cache distribuído — uma camada compartilhada entre instâncias

Redis ou Memcached como cache externo acessível por todas as instâncias

A solução é um cache externo compartilhado — tipicamente Redis ou Memcached — que todas as instâncias da aplicação consultam e atualizam. Em vez de cada servidor manter sua própria cópia, todos leem e escrevem no mesmo cache central. A vantagem é consistência: quando uma instância invalida ou atualiza uma chave, todas as outras instâncias automaticamente passam a ver o dado atualizado na próxima consulta. O trade-off é latência de rede (consultar um Redis externo leva mais tempo que acessar memória local) e dependência de disponibilidade (se o Redis cair, o cache cai com ele).

Particionamento e consistência em hash

Como os dados são distribuídos em um cluster Redis

Em um Redis Cluster, as chaves são distribuídas entre nós usando consistent hashing — um algoritmo que mapeia cada chave para um "slot" entre 0 e 16383, e cada nó é responsável por um intervalo de slots. Isso permite que o cluster escale horizontalmente: ao adicionar um novo nó, apenas parte das chaves precisa ser redistribuída, sem mover tudo. A implicação prática para o desenvolvedor é que operações que envolvem múltiplas chaves (como transações Redis, pipelines ou comandos MULTI/EXEC) só funcionam se todas as chaves estiverem no mesmo slot — o que exige uso de hash tags na chave ({userId}:profile e {userId}:settings ficam no mesmo slot).

Cache coerência — o desafio central do cache distribuído

Manter o cache sincronizado com a fonte de verdade

Em cache distribuído, a coerência é mais crítica do que em cache local porque há mais caminhos de escrita. Quando uma das dez instâncias da aplicação atualiza um dado no banco, ela deve também invalidar ou atualizar a chave correspondente no cache compartilhado. Se falhar nisso, todas as instâncias continuam servindo o dado antigo até o TTL expirar. As estratégias incluem: invalidação imediata na escrita (mais simples, pode gerar thundering herd), atualização do cache no mesmo request que atualiza o banco (write-through), e event-driven invalidation via pub/sub do Redis (publicar evento de invalidação que todas as instâncias ouvem).

Redis como cache distribuído em produção

Configurações críticas que afetam performance e confiabilidade

Para Redis como cache distribuído, as configurações mais importantes são: maxmemory e maxmemory-policy (o que acontece quando o Redis fica cheio — allkeys-lru descarta as chaves menos usadas recentemente, que é o comportamento correto para cache), persistência (RDB ou AOF para não perder o cache em reinício — opcional para cache, mas útil para evitar thundering herd após restart), replicação (replica para leitura e failover automático via Sentinel ou Cluster), e timeout de conexão (conexões que ficam abertas mas não usadas consomem recursos). Connection pooling é essencial: não abrir uma nova conexão TCP por requisição.

Cache local como camada adicional

L1 (local) e L2 (distribuído) — dois níveis de cache

Para aplicações com requisições muito intensas a dados que mudam raramente (configurações do sistema, feature flags, tabelas de referência), um cache em dois níveis é eficiente: L1 em memória local (nanossegundos) para as buscas mais frequentes com TTL muito curto (segundos), e L2 distribuído no Redis (milissegundos) como fallback. A instância consulta primeiro o L1; se miss, consulta o L2; se miss no L2, busca no banco. O TTL do L1 deve ser suficientemente curto para que uma mudança no Redis se propague para todas as instâncias em tempo aceitável. Essa estratégia é usada em sistemas como os da Netflix e LinkedIn para configurações globais que mudam raramente.

Cache vs banco de dados: qual é a fonte de verdade

O cache nunca substitui o banco — ele o complementa

Um erro comum é tratar o cache distribuído como banco de dados primário. O cache deve ser sempre uma camada de leitura acelerada que complementa o banco de dados. A fonte de verdade é sempre o banco: se o Redis ficar indisponível, a aplicação deve conseguir operar (com degradação de performance) buscando diretamente no banco. Cache-aside garante isso naturalmente: na ausência do cache, a operação vai direto ao banco. O design da aplicação nunca deve depender de um dado estar no cache para funcionar corretamente — apenas para funcionar com performance aceitável.

Monitoramento de cache distribuído

Métricas que revelam se o cache está funcionando como esperado

As métricas essenciais para cache distribuído são: hit rate (porcentagem de requisições que encontram o dado no cache — abaixo de 70% indica problema de chave de cache ou TTL inadequado), latência média (tempo de resposta do Redis — picos indicam pressão de memória, network latency ou operações lentas), eviction rate (taxa de chaves sendo descartadas por limite de memória — alta eviction indica necessidade de mais memória ou chaves muito grandes), e número de conexões ativas (pool esgotado causa filas e timeout). Redis tem comandos nativos para monitoramento: INFO, MONITOR e o dashboard do Redis Insight.

Quando não usar cache distribuído

Nem toda aplicação se beneficia de uma camada extra de cache externo

Cache distribuído adiciona complexidade: nova dependência externa, configuração de cluster, gerenciamento de conexões, estratégia de invalidação e monitoramento adicional. Para aplicações pequenas com carga baixa, banco de dados bem indexado com query cache do próprio banco (como o query cache do PostgreSQL para prepared statements) pode ser suficiente sem precisar de Redis externo. O ponto de inflexão onde cache distribuído vale a pena geralmente é quando o banco começa a ser o bottleneck, quando a latência de leitura é crítica para a experiência do usuário, ou quando há necessidade de compartilhar estado entre múltiplas instâncias de serviço.

Conclusão — cache distribuído como pilar de escalabilidade

A diferença entre uma aplicação que escala e uma que quebra sob carga

Cache distribuído é um dos pilares de qualquer aplicação que precisa escalar horizontalmente sem sacrificar consistência. A diferença entre cache local (simples mas inconsistente em múltiplas instâncias) e cache distribuído (compartilhado, consistente, mais complexo) é a diferença entre uma arquitetura que funciona para um servidor e uma que funciona para cem. Implemente de forma progressiva: comece com cache-aside no endpoint mais caro, monitore o hit rate, e expanda baseado em evidência de onde o banco ainda é bottleneck. Continue em: Fundamentos obrigatórios antes de produção.

Cache Distribuído com Redis — Vídeos

Cache Distribuído e Escalabilidade no Instagram

Cache Distribuído

Camada de cache externa compartilhada entre múltiplas instâncias da aplicação — Redis ou Memcached.

Consistent Hashing

Algoritmo que distribui chaves entre nós de um cluster de forma eficiente, minimizando redistribuição ao escalar.

Cache L1 / L2

Dois níveis de cache: L1 em memória local (nanossegundos) e L2 distribuído no Redis (milissegundos).

maxmemory-policy

Política do Redis para lidar com memória cheia: allkeys-lru descarta as chaves menos usadas recentemente.

Hit Rate

Porcentagem de requisições que encontram o dado no cache. Abaixo de 70% indica problemas de design do cache.

Cache Coerência

Garantia de que todos os nós de cache e todas as instâncias da aplicação veem o mesmo valor para uma chave.

Cache Distribuído no X

@bytebytego

Reels — Sistemas e Arquitetura

@bytebytego

ByteByteGo no Facebook

Referências e Leituras Recomendadas

@mjovanovictech

Como testar que sua API é resiliente e segura para produção real

Ver post completo no X →
@mjovanovictech

Implementando padrões de resiliência em .NET Core com exemplos reais

Ver post completo no X →
@mjovanovictech

Vertical Slice Architecture — organizando sistemas para escala

Ver post completo no X →
@mjovanovictech

5 anos com Clean Architecture — lições de sistemas em produção

Ver post completo no X →
@mjovanovictech

Design de APIs resilientes — retry, backoff e idempotência juntos

Ver post completo no X →
@mjovanovictech

Monolito vs Microsserviços — como escolher para cada contexto

Ver post completo no X →

Links Úteis

O que dizem