Em arquiteturas monolíticas e aplicações de pequeno porte, o acesso direto ao banco de dados relacional para cada requisição costuma ser suficiente. Conforme o volume de leitura cresce, a latência de I/O de disco e a contenção de conexões no banco tornam-se gargalos críticos de infraestrutura. Para mitigar a alta latência de leitura, o uso de mecanismos de cache tornou-se indispensável. Contudo, a introdução de uma camada temporária de memória traz um dos problemas mais complexos da engenharia de software: manter a consistência entre o dado armazenado em memória e a fonte primária da verdade (o banco de dados), evitando a entrega de dados obsoletos (stale data) aos usuários.
A Abordagem Tradicional: Cache Local em Memória Historicamente, a solução mais simples para acelerar consultas foi o uso de Caches Locais na aplicação (In-Memory Application Cache), mantidos no próprio processo do serviço (como mapas em memória, Guava Cache ou Ehcache). O funcionamento baseia-se na retenção local do dado após a primeira consulta, associada a um tempo de expiração fixo (Time-To-Live – TTL).
Limitações do Cache Local Inconsistência entre réplicas: em ambientes com múltiplos nós, o nó A pode atualizar o banco de dados enquanto os nós B e C continuam servindo dados antigos em memória; Consumo excessivo de memória da aplicação: o heap da aplicação compete por memória RAM entre o processamento de regras de negócio e a retenção de dados temporários; Riscos de Cache Stampede: quando o TTL de um item muito acessado expira, múltiplas requisições simultâneas atingem o banco de dados ao mesmo tempo para reconstruir o cache; Acoplamento ao ciclo de vida da instância: a reinicialização de um nó apaga todo o cache local, gerando um efeito de “cache frio” (cold cache). Essas limitações tornam o cache local pouco eficiente para aplicações altamente distribuídas e elásticas.
A abordagem Moderna: Cache Distribuído O modelo moderno separa a camada de cache da execução da aplicação, utilizando um cluster de armazenamento em memória dedicado e compartilhado, como Redis ou KeyDB. Todos os nós da aplicação passam a consultar e invalidar uma única fonte centralizada de cache em memória de altíssima velocidade.
Mecanismos de Atualização de Dados Para gerenciar o ciclo de vida do dado armazenado no cache distribuído, adotam-se estratégias específicas de leitura e escrita.
Estratégias de Implementação de Cache Existem duas abordagens predominantes para gerenciar a persistência e a invalidação do cache distribuído.
Cache-Aside (Lazy Loading) Na estratégia Cache-Aside, a aplicação é responsável por gerenciar explicitamente a leitura e a escrita tanto no cache quanto no banco de dados. Fluxo:
- A aplicação consulta o Cache Distribuído.
- Se houver Cache Hit, o dado é retornado imediatamente.
- Se houver Cache Miss, a aplicação consulta o Banco de Dados, grava o resultado no Cache para futuras requisições e retorna a resposta.
- Quando ocorre uma escrita, a aplicação atualiza o Banco de Dados e remove (evict) o item correspondente no Cache.
Vantagens
- O cache contém apenas dados que realmente foram solicitados;
- Falhas na camada de cache não derrubam a aplicação (o sistema faz fallback para o banco).
Desvantagens
- Risco de pequenas janelas de inconsistência se a invalidação falhar;
- Latência maior na primeira consulta do dado (Cache Miss).
Write-Through / Write-Behind Nas estratégias Write-Through e Write-Behind, a aplicação trata o cache como a camada primária de persistência, e o próprio sistema de cache encarrega-se de atualizar o banco de dados.
- Write-Through: O cache atualiza o banco de dados de forma síncrona antes de confirmar a escrita para a aplicação.
- Write-Behind (Write-Back): O cache confirma a escrita imediatamente para a aplicação e atualiza o banco de dados de forma assíncrona, em lote.
Vantagens
- Altíssima performance de escrita no modelo Write-Behind;
- O dado no cache está sempre atualizado em relação às novas solicitações.
Desvantagens
- Risco de perda de dados no modelo Write-Behind caso o nó de cache falhe antes da persistência no banco;
- Maior complexidade de infraestrutura.
Quando Utilizar Cada Abordagem? Utilize Cache Local (In-Memory) quando:
- O volume de dados for pequeno, estático e de pouca variação (ex.: tabelas de domínio, países, configurações);
- Inconsistências temporárias entre instâncias forem irrelevantes para o negócio;
- A aplicação rodar em uma única instância ou não exigir escalabilidade horizontal agressiva.
Utilize Cache Distribuído com Cache-Aside quando:
- A aplicação for baseada em microsserviços ou rodar em múltiplas instâncias elásticas;
- A taxa de leitura for significativamente maior que a taxa de escrita (Read-Heavy Workloads);
- O sistema precisar de alta disponibilidade e tolerância a falhas na camada de cache.
Utilize Cache Distribuído com Write-Through / Write-Behind quando:
- O sistema exigir baixíssima latência também nas operações de escrita;
- A ordem das atualizações for crítica e precisar ser coordenada centralmente na camada de memória.
Na prática, a combinação de Cache Distribuído (Redis) com a estratégia Cache-Aside e invalidação explícita orientada a eventos tornou-se o padrão mais adotado pela indústria para acelerar sistemas distribuídos Cloud Native de alta escala.