DadosIntermediário8 min de leitura7 seçõesanos 1960, Data aproximada

Cache e invalidação

Uma cópia rápida de um dado caro de obter. Acelera leitura e alivia o banco — em troca de uma pergunta permanente: como a cópia para de mentir quando a fonte muda? TTL, invalidação e stampede são o conteúdo; a ferramenta (Redis, Memcached, CDN) é só o endereço.

Em 10 segundos

Cache é uma cópia rápida de um dado caro. O ganho é latência e alívio na fonte; o preço é consistência. Sem invalidação (ou TTL consciente), o sistema fica rápido e mente. O padrão de aplicação mais comum é cache-aside: a app lê o cache, no miss vai à fonte e preenche; na escrita, atualiza a fonte e apaga a chave.

Antes de ter nome

Bibliotecas, catálogos e mesas de referência: qualquer sistema que evita refazer um trabalho caro mantendo uma cópia local.

  • #performance
  • #dados
  • #redis
  • #ttl
  • #invalidacao
Analogia

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.

01Conceito

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.

Como se encaixa
  1. App
  2. 1. get keyCache
  3. 2. miss → queryBanco
Hit devolve no passo 1. Miss paga a fonte, a app preenche o cache com TTL, e a próxima leitura economiza.
02Conceito

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).

03Interativo

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

ok
  1. 1Usuário · abre a tela: GET /produtos0ns
  2. 2API / Gateway · Nginx termina TLS e balanceia → encaminha2ms
  3. 3Aplicação (casos de uso) · caso de uso orquestra a operação2ms
  4. 4Domínio · regras conferem o que pode ser exibido1ms
  5. 5Leitura · Redis: HIT, resposta direto da memória. O banco nem fica sabendo.0,5ms
  6. 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.

05Conceito

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.

06No mundo real

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.

07Cuidado

Onde isto dá errado

  1. 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.

  2. 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.

  3. 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