Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

Cache Invalidation no Node.js: evite dados obsoletos

Atualizado em: 27 de outubro de 2026

Rack de servidores processando fluxos de dados no Node.js

Cache Invalidation no Node.js é o processo de remover ou atualizar dados armazenados em cache quando a fonte de verdade muda. É uma das partes mais difíceis de uma arquitetura de cache, porque o ganho de desempenho só é válido enquanto o conteúdo entregue continua correto.

Uma aplicação pode usar cache em memória, Redis, CDN e navegador ao mesmo tempo. Cada camada possui seu próprio tempo de vida, chave e mecanismo de purga. Sem uma estratégia explícita, dados antigos permanecem ativos, tenants se misturam e atualizações parecem desaparecer.

Fonte de verdade e cache

O banco ou serviço principal continua sendo a fonte de verdade. O cache é uma cópia descartável. A aplicação deve conseguir reconstruí-lo e operar, ainda que mais lentamente, quando ele estiver vazio ou indisponível.

TTL como primeira defesa

await redis.set(
  `product:${productId}`,
  JSON.stringify(product),
  { EX: 300 }
);

O TTL limita por quanto tempo um erro de invalidação permanece visível. Ele não garante consistência imediata, mas evita valores eternamente obsoletos.

Invalidar após a gravação

await database.updateProduct(productId, changes);
await redis.del(`product:${productId}`);

Excluir costuma ser mais seguro que tentar reconstruir o valor dentro da mesma operação. A próxima leitura buscará a versão atual no banco.

Falha entre banco e cache

Se o banco confirmar e a exclusão do cache falhar, o valor antigo continua disponível. Trate a invalidação como uma operação que pode falhar. Estratégias possíveis incluem retry, Outbox Pattern, TTL curto e reconciliação periódica.

Outbox para invalidação confiável

Grave a alteração e um evento na mesma transação:

BEGIN;
UPDATE products SET name = $2, version = version + 1 WHERE id = $1;
INSERT INTO outbox (type, aggregate_id)
VALUES ('product.changed.v1', $1);
COMMIT;

Um worker publica o evento depois. Veja Outbox Pattern no Node.js.

Invalidação entre instâncias

Quando cada processo mantém cache local, uma instância não consegue apagar diretamente a memória das demais. Publique um sinal:

await publisher.publish(
  'cache.products.invalidated.v1',
  JSON.stringify({ productId })
);

Cada instância remove sua chave local. Consulte Redis Pub/Sub no Node.js. Como Pub/Sub pode perder mensagens durante desconexões, combine com TTL.

Chaves versionadas

Uma versão no namespace permite invalidar um conjunto sem listar todas as chaves:

const namespaceVersion = await redis.get('products:namespace-version');
const key = `products:v${namespaceVersion}:item:${productId}`;

Para uma purga global, incremente a versão. As chaves antigas expiram naturalmente.

Versão por entidade

const key = `product:${productId}:v${product.version}`;

Essa técnica evita sobrescrever valores de versões diferentes. Ainda é necessário limpar ou expirar chaves antigas.

Tags de cache

Uma página pode depender de produto, categoria e preço. Registre quais chaves pertencem a cada tag:

SADD cache-tag:product:123 page:/products/123
SADD cache-tag:category:9 page:/category/9

Ao alterar o produto, leia os membros e apague as páginas. Limite o tamanho dessas estruturas e remova membros expirados.

Invalidação por evento

Eventos devem ser versionados e conter somente os identificadores necessários:

{
  "id": "evt-123",
  "type": "product.changed.v1",
  "data": {
    "productId": "p-10",
    "version": 8
  }
}

O consumidor pode ignorar um evento antigo se já processou versão maior.

Ordenação e corrida

Imagine duas atualizações rápidas. Uma invalidação atrasada não deve restaurar conteúdo antigo. Prefira excluir a chave, ou compare versões antes de gravar um novo valor.

if (incomingVersion > cachedVersion) {
  await refreshCache();
}

Double delete

Em alguns sistemas, a aplicação apaga o cache, atualiza o banco e apaga novamente depois de um pequeno intervalo. A técnica tenta reduzir uma corrida com leitores concorrentes, mas adiciona complexidade e não substitui versionamento ou eventos confiáveis.

Write-through

No write-through, a aplicação atualiza cache e fonte de verdade no mesmo fluxo. É útil quando existe uma camada responsável pela escrita, mas continua sujeito a falhas parciais. Defina qual sistema confirma primeiro e como reconciliar divergências.

Write-behind

No write-behind, a atualização entra no cache e é persistida depois. O desempenho pode melhorar, mas há risco de perda e ordenação. Use somente com fila durável, idempotência e requisitos que aceitem consistência eventual.

Negative caching

Resultados ausentes também podem ser armazenados por pouco tempo:

await redis.set(`product:${id}`, '__NOT_FOUND__', { EX: 30 });

Quando o recurso é criado, invalide a chave negativa. Use TTL menor para não esconder criações recentes.

Cache por tenant

tenant:${tenantId}:product:${productId}

O tenant deve fazer parte de toda chave relevante. Uma invalidação sem tenant pode apagar dados de outros clientes ou, pior, reutilizar conteúdo entre eles.

CDN e navegador

Respostas públicas podem usar Cache-Control, ETag e CDN. Para purgas, prefira URLs versionadas em assets e mecanismos do provedor para conteúdo dinâmico.

Cache-Control: public, max-age=60, stale-while-revalidate=300

Veja Cache HTTP no Node.js.

Stale-while-revalidate

A aplicação pode servir temporariamente um valor antigo enquanto uma instância atualiza o cache. Isso reduz latência e stampede, mas deve ser usado apenas quando alguns segundos de atraso são aceitáveis.

Evite cache stampede

Quando uma chave popular expira, centenas de requisições podem consultar o banco. Use single-flight, lock curto ou TTL com jitter:

const ttl = 300 + Math.floor(Math.random() * 30);

Consulte Cache Stampede no Node.js.

Reconciliação

Uma tarefa periódica pode comparar versões, apagar chaves antigas ou reconstruir índices de tags. Reconciliação é uma defesa contra eventos perdidos, mas deve ser paginada e limitada.

Observabilidade

Monitore:

  • hit e miss rate;
  • idade do valor;
  • invalidações solicitadas e concluídas;
  • falhas e retries;
  • latência de reconstrução;
  • chaves por namespace;
  • eventos atrasados;
  • divergências detectadas.

Não coloque IDs individuais em labels de métricas.

Testes de concorrência

  • leitura durante atualização;
  • duas atualizações fora de ordem;
  • falha do Redis depois do commit;
  • evento duplicado;
  • evento perdido e recuperação por TTL;
  • tenant diferente com mesmo ID;
  • cache local em múltiplas instâncias;
  • negative cache seguido de criação.

Erros comuns

  • cache sem TTL;
  • apagar antes do commit e não depois;
  • ignorar falha da invalidação;
  • chave sem tenant;
  • reconstruir com versão antiga;
  • depender apenas de Pub/Sub;
  • não invalidar resultados negativos;
  • purga global frequente;
  • não medir idade do conteúdo.

Conclusão

Cache Invalidation no Node.js exige mais que chamar DEL. É necessário considerar falhas parciais, concorrência, múltiplas camadas e mensagens perdidas.

Combine TTL, eventos confiáveis, chaves versionadas e reconciliação. Mantenha o cache descartável e preserve o banco como fonte de verdade. A estratégia correta depende do atraso aceitável para cada dado.

Consulte a documentação do Redis sobre client-side caching e a documentação sobre keyspace notifications.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita