O post-it na geladeira
Você anota o PIN do Wi-Fi num post-it para não abrir o roteador toda vez. Funciona — até trocar a senha e esquecer de rasgar o post-it. Os convidados continuam digitando a senha velha. O post-it é o cache; rasgar (ou datar e trocar) é a invalidação.
Cache-aside: a aplicação manda
No cache-aside (lazy loading), o cache não sabe da fonte: a aplicação pergunta ao cache, e só no miss vai ao banco e grava a cópia. É o modelo do Redis na maioria dos serviços.
A simetria que falta na leitura aparece na escrita: depois de mudar a fonte, a chave precisa morrer (ou ser atualizada). Senão o próximo hit serve o passado.
Há variantes: write-through grava cache e fonte juntos; write-behind atrasa a fonte. Em API de produto, cache-aside domina porque o banco continua sendo a verdade e o cache pode sumir sem corrupção.
TTL sozinho não é estratégia — é cinto de segurança. Com TTL de uma hora e sem DEL na escrita, você aceita até uma hora de mentira. Em catálogo de preço, isso é incidente; em contagem de views, talvez não.
- App
- 1. get keyCache
- 2. miss → queryBanco
Stampede e a chave que expira junto
Quando uma chave popular expira, N requisições descobrem o miss ao mesmo tempo e N queries batem no banco. O cache que deveria proteger vira amplificador.
Mitigações: lock curto / singleflight (só um preenche), TTL com jitter (não expirar tudo no mesmo segundo), stale-while-revalidate (servir velho enquanto um revalida).
E se o Redis cair?
Mexa nos controles: a teoria acima vira comportamento observável.
Simulador de falha
Derruba uma peça e compara com o hit normal.
Injetar falha
Tudo no ar: hit no Redis, Postgres nem é chamado.
Latência sentida
5,5ms
- 1Usuário · abre a tela: GET /produtos0ns
- 2API / Gateway · Nginx termina TLS e balanceia → encaminha2ms
- 3Aplicação (casos de uso) · caso de uso orquestra a operação2ms
- 4Domínio · regras conferem o que pode ser exibido1ms
- 5Leitura · Redis: HIT, resposta direto da memória. O banco nem fica sabendo.0,5ms
- 6Resposta · o usuário recebe em ~5.5ms0ns
O jeito errado
Compila, roda, passa no code review. E quebra depois.
Cache que só sabe ler: a escrita atualiza o banco e esquece de invalidar a chave — o hit passa a mentir até o TTL
// Cache-aside "certo" na leitura — e errado na escrita:
async function buscarProduto(id: string) {
const hit = await redis.get(`produto:${id}`);
if (hit) return JSON.parse(hit);
const row = await db.produto.findUnique({ where: { id } });
await redis.set(`produto:${id}`, JSON.stringify(row), "EX", 3600);
return row;
}
async function atualizarPreco(id: string, preco: number) {
await db.produto.update({ where: { id }, data: { preco } });
// Esqueceu de invalidar. O cache serve o preco velho por ate 1h.
// Em pico de trafego, milhares de leituras veem o valor antigo —
// e o TTL "resolve" so quando o dano ja foi feito.
}O que quebra
- o preço muda no bancoa API continua devolvendo o valor cacheado até o TTL
- suporte e cliente divergemum bate no primário, o outro no hit — ninguém desconfia do Redis
A saída: Toda escrita que altera o valor cacheado apaga (ou atualiza) a chave no mesmo fluxo — e o TTL fica como rede de segurança, não como única política.
Não é tudo a mesma coisa
CDN cacheia HTTP na borda; Redis cacheia o que a aplicação decide; réplica de leitura escala SELECT no banco; índice acelera achar a linha. Confundir os quatro gera a peça errada no canvas.
Flyweight também 'reusa' — mas dentro do processo, compartilhando estado imutável entre objetos. Cache de aplicação vive fora, com TTL e rede.
Como isso aparece na prática
O preço que o cliente ainda via velho
Cenário
A página de produto era servida de um cache Redis com TTL de uma hora. O time de pricing atualizava o banco e abria chamado: 'o site não refletiu o desconto'.
Como o padrão entra
A escrita do admin passou a fazer DEL da chave no mesmo request. O TTL ficou só como rede de segurança para chaves órfãs.
Trade-off: A primeira leitura após o publish paga o miss — irrelevante frente a milhares de hits corretos por minuto.
Sessão e rate limit no Redis
Cenário
Precisava de store compartilhado entre instâncias para sessão e contador de rate limit, sem persistir no Postgres a cada request.
Como o padrão entra
Redis como store volátil: queda do cache desloga ou afrouxa o limite — trade-off consciente, documentado no runbook.
Trade-off: Disponibilidade do Redis vira parte do caminho crítico. Mitigado com fail-open no rate limit e re-login barato na sessão.
Asset versionado na CDN
Cenário
JS/CSS mudavam a cada deploy e purgar a CDN chave a chave era lento e falhava em PoPs.
Como o padrão entra
app.[hash].js com cache imutável longo: a invalidação é trocar o nome no HTML do deploy.
Trade-off: Build e pipeline precisam emitir o hash; em troca, zero purge manual e hit ratio alto na borda.
Onde isto dá errado
TTL como única invalidação
Funciona até o dia em que 'no máximo N minutos de atraso' vira bug de negócio. Invalidação na escrita é o padrão; TTL é backup.
Cache sem plano de miss total
Se o Redis cai e a app não aguenta o thundering herd no banco, o cache virou single point of failure disfarçado de otimização.
Chave sem namespace / sem versão
Um deploy muda o shape do JSON e o hit serve o formato antigo. Versionar a chave (
user:v2:id) ou invalidar em massa no release.
Testar o que ficou
3 armadilhas de Cache e invalidação viram perguntas, com o nome do padrão escondido, misturadas com padrões parecidos.
Onde isto aparece de verdade
Você já usa isto, talvez sem saber o nome.
Redis / MemcachedO cache de aplicação clássico: chave-valor em memória, TTL nativo, e a aplicação decide o que guardar e quando apagar.Cache-Control / ETagO cache HTTP: navegador e CDN guardam a resposta e perguntam ao servidor se ainda vale — invalidação pelo protocolo.React Query / SWRCache de dados no cliente com stale-while-revalidate: mostra o velho na hora e revalida em segundo plano.
O padrão em uma linha
Sem nome de domínio, sem cerimônia. Só a ideia.
// Miss → fonte; hit → cópia. Escrita invalida a chave.
const v = (await redis.get(k)) ?? (await fillAndSet(k));O que ele cobra
1 salto a mais entre a chamada e o efeito.
- Toda escrita precisa de uma política de invalidação — sem ela o cache serve mentira até o TTL
- Mais uma peça para operar, monitorar hit ratio e planejar o que acontece quando o cache cai
- Stampede no miss: mil clientes batem no banco juntos quando a chave expira
Não vale se: a leitura já é barata, o dado muda a cada request ou você não tem disciplina para invalidar — aí o cache só adiciona bugs de consistência.
Onde isto aparece
Continue por aqui
- Réplica de leituraQuando as leituras afogam o banco antes das escritas, a saída barata é copiar: o primário recebe todas as escritas e as replica para nós somente-leitura que absorvem as consultas. Escala leitura sem tocar no modelo — ao preço de um atraso de replicação que faz as réplicas verem o passado por um instante.
- Índice de bancoA diferença entre achar uma linha em milhões e varrer todas elas. Um índice é uma cópia ordenada de uma coluna que troca busca linear por busca binária — a estrutura que faz a query voar. O preço vem em cada escrita, que agora precisa manter o índice também, e na sutileza de que o índice só serve quando a consulta o consulta do jeito certo.
- CQRS (Command Query Responsibility Segregation)Separa o modelo de escrita (commands) do modelo de leitura (queries), podendo usar stores e serviços distintos otimizados para cada lado.
- FlyweightReduz o consumo de memória compartilhando entre muitos objetos a parte do estado que é igual em todos, mantendo fora apenas o que varia de instância para instância.
- Consistência eventualQuando o dado vive em cópias, mantê-las idênticas a cada instante custa latência e disponibilidade. A consistência eventual troca o 'sempre igual' por 'igual daqui a pouco': as réplicas convergem, e o sistema fica mais rápido e mais disponível — ao preço de janelas em que a leitura vê o passado.