Estratégias de cache em 2026: Redis vs Memcached vs CDN
Equipe Arvucore
September 22, 2025 · Atualizado em August 26, 2026
15 min read
Use cache-aside com TTL mais invalidação explícita como padrão, coloque uma CDN na frente de tudo que for público e cacheável, e prefira Redis a Memcached, a menos que sua única necessidade seja get/set de valores pequenos com concorrência muito alta. Todo o resto se resume a dois problemas: manter os dados atualizados o suficiente e garantir que um cache miss não derrube o banco de dados.
As quatro camadas de cache e para que serve cada uma
Uma requisição pode ser respondida em quatro pontos, cada um com seu próprio dono, horizonte de TTL e mecanismo de invalidação.
| Camada | Onde vive | TTL típico | Invalidação | Ideal para |
|---|---|---|---|---|
| Browser / HTTP | Dispositivo do usuário | De minutos a um ano | URLs versionadas, Cache-Control, ETag |
Assets estáticos, fontes, bundles imutáveis |
| CDN / edge | PoPs do provedor perto do usuário | De segundos a dias | API de purge, surrogate keys, s-maxage |
Páginas públicas, imagens, respostas de API cacheáveis |
| Aplicação | Redis, Memcached ou memória do processo | De segundos a horas | TTL, invalidação na escrita, chaves versionadas | Fragmentos renderizados, sessões, objetos computados, rate limits |
| Banco de dados / query | Buffer pool do banco, views materializadas, cache de query | Gerenciado pelo banco | Automática ou refresh agendado | Linhas quentes, agregações caras |
Projete de fora para dentro: empurre para a CDN tudo que puder ser público, mantenha dados por usuário ou de mudança rápida no cache da aplicação e deixe o cache do banco cuidar do resto. A memória do processo (um Map no Node, Caffeine na JVM) é uma quinta camada: de longe a mais rápida, mas por instância e perdida a cada deploy. Use-a para configuração, feature flags e objetos pequenos muito quentes na frente do Redis.
Padrões de cache: cache-aside, read-through, write-through, write-behind, refresh-ahead
O padrão define quem popula o cache e quando as escritas chegam até ele.
Cache-aside (lazy loading). A aplicação consulta o cache, lê o banco em caso de miss, grava o valor de volta e o retorna. Tolera queda do cache, funciona com qualquer store e só cacheia o que é lido. O custo: cada miss paga a query completa, e todo escritor precisa lembrar de invalidar.
Read-through. Mesmo comportamento de leitura, mas é a biblioteca de cache ou o proxy que carrega do banco no miss. Pontos de chamada mais limpos, menos flexibilidade.
Write-through. As escritas vão para o cache, que grava de forma síncrona no banco. Nunca fica desatualizado para os dados escritos por ele, ao custo de latência de escrita e de cachear dados que ninguém lê.
Write-behind (write-back). As escritas vão para o cache e são descarregadas de forma assíncrona, em lotes. Alta vazão de escrita, mas se o nó do cache morrer antes do flush, as escritas se perdem. Só para dados que você pode se dar ao luxo de perder (contadores, estatísticas de visualização, presença).
Refresh-ahead. O cache renova uma chave quente em segundo plano antes de ela expirar. Bom para poucas chaves caras e previsíveis; desperdício numa cauda longa.
| Padrão | Custo do miss na leitura | Latência de escrita | Desatualização | Risco de perda de dados | Padrão para |
|---|---|---|---|---|---|
| Cache-aside | Query no banco + escrita no cache | Nenhuma | Até o TTL ou a invalidação | Nenhum | A maioria das cargas com muita leitura |
| Read-through | O mesmo, dentro da camada de cache | Nenhuma | Até o TTL ou a invalidação | Nenhum | Times que padronizam em uma biblioteca |
| Write-through | Baixo (dado geralmente presente) | Cache + banco, síncrono | Mínima | Nenhum | Consistência read-after-write |
| Write-behind | Baixo | Só o cache | Mínima para leituras | Sim, até o flush | Contadores, telemetria, altas taxas de escrita |
| Refresh-ahead | Quase zero para chaves quentes | Nenhuma | Limitada pelo intervalo de refresh | Nenhum | Poucas chaves caras e previsíveis |
Se estiver em dúvida, comece com cache-aside atrás de uma interface de repositório, como na arquitetura hexagonal; depois dá para mover chaves individuais para write-through ou refresh-ahead sem tocar em quem chama.
Estratégia de invalidação e TTL
TTL e invalidação não são alternativas. O TTL limita o quanto os dados podem envelhecer quando a invalidação falha; a invalidação mantém os dados atualizados quando funciona. Use os dois.
Escolha TTLs por classe de dado, não por serviço. Um catálogo que muda algumas vezes por dia pode viver uma hora; um nível de estoque no checkout, alguns segundos, se tanto. Documente as classes; elas são o contrato que o time implementa.
Invalide na escrita, na fonte. O caminho de código que atualiza o banco apaga a chave do cache ou emite um evento que faz isso. Apagar é mais seguro que atualizar: uma atualização pode competir com uma leitura concorrente que grava de volta um valor mais antigo.
Versione chaves em vez de purgar um namespace. Coloque uma versão na chave (catalog:v42:product:123) e incremente-a em atualizações em massa. As entradas antigas ficam inalcançáveis e expiram sozinhas.
Adicione jitter. Chaves aquecidas juntas com o mesmo TTL expiram juntas. Randomize cada TTL em 10 a 20 por cento.
Use eventos para invalidação entre serviços. Numa arquitetura orientada a eventos, o serviço dono de uma tabela publica ProductUpdated; os consumidores que cacheiam dados de produto assinam o evento e descartam suas chaves. Pub/sub ou keyspace notifications do Redis servem para um único deployment de Redis; um message broker é a ferramenta certa quando vários serviços entram na história.
Proteção contra stampede: lock, expiração antecipada, coalescência de requisições
A queda clássica de cache não é o cache cair. É uma chave popular expirar sob carga: centenas de requisições dão miss ao mesmo tempo, todas rodam a mesma query cara, e o banco vai ao chão. Estas técnicas se combinam.
Lock por chave. No miss, a primeira requisição pega um lock curto (SET lock:key 1 NX PX 5000), reconstrói, grava e libera. As outras esperam um pouco e releem, ou entregam uma cópia desatualizada. O TTL do lock precisa ser maior que uma reconstrução normal.
Coalescência de requisições (single-flight). Dentro de um processo, deduplique carregamentos concorrentes da mesma chave para que só uma chamada ao banco esteja em andamento. O singleflight do Go é a referência; no Node, é um map de promises pendentes.
Expiração antecipada probabilística. Cada leitura decide, com uma probabilidade que cresce conforme a expiração se aproxima, renovar antes da hora. Chaves quentes são renovadas por um leitor antes de expirar; chaves frias ficam em paz. Sem scheduler.
Entregue o valor antigo enquanto reconstrói. Mantenha o TTL lógico menor que o TTL físico. No intervalo entre os dois, os leitores recebem o valor antigo na hora enquanto um deles reconstrói.
Aqui está o cache-aside com lock e coalescência no processo em TypeScript, usando o cliente ioredis:
import Redis from "ioredis";
const redis = new Redis();
const inflight = new Map<string, Promise<string>>();
export async function cached(
key: string,
ttlSec: number,
load: () => Promise<string>,
): Promise<string> {
const hit = await redis.get(key);
if (hit !== null) return hit;
// Coalesce os misses concorrentes dentro deste processo.
const pending = inflight.get(key);
if (pending) return pending;
const task = (async () => {
const lockKey = `lock:${key}`;
const gotLock = await redis.set(lockKey, "1", "PX", 5000, "NX");
if (!gotLock) {
// Outra instância está reconstruindo; espere um pouco e verifique de novo.
await new Promise((r) => setTimeout(r, 50));
const again = await redis.get(key);
if (again !== null) return again;
// Segue em frente e carrega mesmo assim, em vez de bloquear para sempre.
}
try {
const value = await load();
const jitter = Math.floor(ttlSec * (0.9 + Math.random() * 0.2));
await redis.set(key, value, "EX", jitter);
return value;
} finally {
if (gotLock) await redis.del(lockKey);
}
})();
inflight.set(key, task);
try {
return await task;
} finally {
inflight.delete(key);
}
}
O fallthrough depois de um lock que falhou é proposital: bloquear todos os que esperam transforma uma reconstrução lenta numa indisponibilidade, enquanto deixar alguns carregarem em paralelo limita o estrago.
Os trade-offs de consistência que você está realmente fazendo
Um cache é uma réplica com um modelo de consistência mais fraco que o do banco. Seja explícito sobre qual modelo cada classe de dado recebe.
- Eventual com limite. Cache-aside mais TTL. Os leitores podem ver dados com até TTL segundos de idade. Serve para catálogos, conteúdo, resultados de busca e a maioria dos dashboards.
- Read-your-writes. Depois que o usuário atualiza algo, ele precisa ver a mudança. Apague a chave na escrita e ignore o cache na próxima leitura daquele usuário, ou use write-through para essa chave. Sessões e edições de perfil precisam disso.
- Leituras monotônicas. O usuário nunca pode ver os dados voltarem no tempo. Quebra quando duas instâncias guardam versões diferentes em cache. Corrija com chaves versionadas ou roteando o usuário sempre para a mesma réplica.
- Forte. Não cacheie. Baixas de estoque, saldos e estado de pagamento vão para o banco.
O bug de produção mais comum é um descasamento: consistência eventual aplicada a um campo que o produto trata como forte. Classifique primeiro; escolha o padrão depois.
Redis vs Memcached: tabela comparativa
| Critério | Redis | Memcached |
|---|---|---|
| Estruturas de dados | Strings, hashes, listas, sets, sorted sets, streams, bitmaps, HyperLogLog, geoespacial, módulos de JSON e busca | Só strings (valores de bytes opacos) |
| Persistência | Opcional: snapshots RDB, log AOF ou ambos | Nenhuma; reiniciar significa cache vazio |
| Replicação e HA | Primário-réplica, Sentinel para failover, Redis Cluster para sharding com failover | Nada nativo; os clientes fazem sharding com consistent hashing |
| Cluster | Nativo (Redis Cluster, hash slots, resharding) | Só no cliente ou no proxy |
| Eficiência de memória | Overhead maior por chave; hashes e sets pequenos são codificados de forma compacta | Slab allocator, overhead muito baixo por chave; pode desperdiçar memória entre classes de slab |
| Threads | Execução de comandos single-thread com threads de I/O; escala por sharding | Totalmente multithread; um nó grande escala com os cores |
| Operações atômicas | Ricas: INCR, SETNX, MULTI/EXEC, scripts Lua, Functions | INCR/DECR, CAS, add/replace |
| Eviction | Configurável: LRU, LFU, por TTL, só voláteis ou todas as chaves | LRU por classe de slab |
| Pub/sub e streams | Sim | Não |
| Tamanho máximo de valor | 512 MB | 1 MB por padrão (configurável) |
| Superfície operacional | Maior: ajuste de persistência, lag de replicação, topologia do cluster | Mínima: memória e conexões |
| Nota sobre licença | Verifique a licença da distribuição específica; Redis, Valkey (fork da Linux Foundation) e as variantes gerenciadas em nuvem diferem | Open source, BSD |
| Casos de uso típicos | Sessões, rate limiting, rankings, filas, locks, feature flags, objetos computados, contadores em tempo real | Cache de objetos puro para fragmentos renderizados, resultados de ORM, respostas de API |
Escolha Memcached para um cache de objetos grande, plano e multithread, com valores simples abaixo de 1 MB, onde perder tudo num restart é aceitável. A simplicidade é a vantagem.
Escolha Redis quando precisar de qualquer estrutura de dados, operações atômicas, locks, pub/sub, persistência ou replicação nativa. Isso descreve a maioria dos caches de aplicação depois que passam do get/set, e é por isso que o Redis (ou o Valkey, seu fork comunitário) é o padrão hoje. Se você já roda Redis para sessões ou filas, um segundo sistema só para cache raramente compensa. Todas as grandes nuvens oferecem os dois como serviço gerenciado; verifique o atraso de versão, o comportamento de failover e o preço por nó antes de se comprometer com recursos como o Cluster.
Cache em CDN: Cache-Control, stale-while-revalidate e surrogate keys
A CDN é a camada mais barata por requisição e a mais fácil de errar, porque as regras vivem em headers HTTP que a maioria do código de aplicação nunca define de propósito.
Cache-Control é o contrato. Separe os tempos de vida do browser e do edge:
Cache-Control: public, max-age=60, s-maxage=3600, stale-while-revalidate=300, stale-if-error=86400
max-age governa o browser, s-maxage governa os caches compartilhados (a CDN), stale-while-revalidate deixa o edge entregar uma cópia expirada enquanto busca outra em segundo plano, e stale-if-error deixa que ele continue entregando se a origem responder 5xx. Para respostas por usuário, defina private ou no-store; um header Set-Cookie numa resposta cacheável é a forma clássica de vazar a página de um usuário para outro.
Assets imutáveis ganham vida longa. Coloque um fingerprint no nome do arquivo em tempo de build (app.3f9a1c.js) e envie Cache-Control: public, max-age=31536000, immutable. Invalide mudando a URL, nunca purgando; todo bundler moderno faz isso por padrão.
Surrogate keys tornam o purge preciso. Marque cada resposta com as entidades das quais ela depende (Surrogate-Key: product-123 category-9, ou o Cache-Tag do provedor). Quando o produto 123 muda, purgue essa tag e toda página que o renderizou sai do edge numa única chamada. Ligue isso ao mesmo evento de escrita que limpa o Redis: um evento, dois purges. Prefira um soft purge (marcar como desatualizado, entregar o antigo, buscar de novo) para que uma atualização em massa não mande uma parede de misses para a origem.
Origin shielding. Ative o shield ou o cache em camadas do provedor para que todos os PoPs busquem de um único cache regional; a maioria das CDNs também coalesce misses concorrentes para a mesma URL.
Para sites estáticos e com muito conteúdo, um build Jamstack empurra a decisão de cache até o momento do deploy, e a CDN vira a origem.
Observabilidade: taxa de acerto, p99 e as métricas que preveem quedas
Um cache sem métricas é um palpite. Instrumente estas desde o primeiro dia.
- Taxa de acerto por classe de chave, não só global. Uma taxa global alta pode esconder uma taxa ruim justamente na query que importa.
- Latência p99 da origem e carga do banco lado a lado com a taxa de acerto. Se o p99 dispara toda vez que a taxa cai alguns pontos, você tem risco de stampede.
- Evictions por segundo e fragmentação de memória. Evictions subindo com tráfego estável significam cache pequeno demais ou TTLs longos demais (
INFO memoryeINFO statsno Redis;stats slabsno Memcached). - Tempo de reconstrução das chaves mais caras. Ele define o TTL do lock e a janela de dado antigo.
- Lag de replicação, se as leituras vão para réplicas do Redis.
- Taxa de acerto da CDN por caminho e status, mais a latência de purge.
Alerte sobre tendências, não sobre limites fixos: queda da taxa de acerto ao longo de dez minutos, ou p99 acima do SLO por uma janela sustentada. Alimente tudo no mesmo pipeline de tratamento de erros e logging do resto da plataforma.
Checklist de decisão
Percorra esta lista antes de adicionar ou mudar um cache.
- Qual é o custo de um miss? Se for uma query indexada e barata, talvez você ainda não precise do cache. Meça primeiro.
- Que consistência esse dado exige? Eventual, read-your-writes, monotônica ou forte. Forte significa sem cache.
- Qual camada responde mais barato? Público e igual para todos: CDN. Por usuário ou de mudança rápida: cache da aplicação. Asset imutável: browser com URL fingerprintada.
- Qual padrão? Cache-aside por padrão. Write-through para read-after-write. Write-behind só para dados que toleram perda. Refresh-ahead para poucas chaves quentes e caras.
- Qual é o TTL, e o que invalida antes dele? Documente os dois. Adicione jitter.
- O que acontece quando a chave expira sob carga? Lock, coalescência, entrega do valor antigo ou expiração antecipada; pelo menos uma para cada chave cuja reconstrução seja mais lenta que uma query simples.
- Redis ou Memcached? Qualquer coisa além de get/set, ou persistência ou HA: Redis (ou Valkey). Cache de objetos plano, simplicidade máxima: Memcached.
- Como você vai saber que funciona? Taxa de acerto por classe, p99 da origem, evictions, tempo de reconstrução, latência de purge, tudo antes do rollout.
- Como o cache falha? O cache-aside precisa degradar para o banco atrás de um circuit breaker, para que uma queda do Redis signifique lentidão, não indisponibilidade. Teste isso.
- Quem é dono da invalidação quando um segundo serviço escreve na mesma tabela? Se a resposta for "ninguém", use eventos.
Recomendação
Comece com cache-aside no Redis (ou Valkey), TTLs por classe de dado com jitter, invalidação por exclusão na escrita e lock por chave com coalescência no processo para toda chave cuja reconstrução seja cara. Coloque uma CDN na frente de toda resposta pública com Cache-Control explícito, stale-while-revalidate e surrogate keys ligadas aos mesmos eventos de invalidação. Reserve o Memcached para o cache de objetos plano e multithread em que a simplicidade é o ponto. Instrumente a taxa de acerto por classe de chave contra o p99 da origem antes de entregar; esse par diz se o cache é uma conveniência ou uma parede estrutural. Na Arvucore costumamos recomendar classificar os dados por requisito de consistência primeiro e escolher stores e padrões depois; times que fazem o contrário acabam depurando dados desatualizados em produção.
Pronto para Transformar seu Negócio?
Vamos conversar sobre como nossas soluções podem ajudá-lo a alcançar seus objetivos. Entre em contato com nossos especialistas hoje mesmo.
Falar com um EspecialistaTags:
Equipe Arvucore
A equipe editorial da Arvucore é formada por profissionais experientes em desenvolvimento de software. Somos dedicados a produzir e manter conteúdo de alta qualidade que reflete as melhores práticas da indústria e insights confiáveis.
Perguntas frequentes
- Qual é a diferença entre Redis e Memcached?
- O Memcached é um cache chave-valor simples e multithread para strings. O Redis acrescenta estruturas de dados, persistência, replicação, cluster, scripts Lua e pub/sub. Escolha Memcached para cache de objetos simples com concorrência muito alta; escolha Redis quando precisar de qualquer coisa além de get/set.
- Qual padrão de cache devo usar por padrão?
- Cache-aside. A aplicação lê o cache, recorre ao banco de dados em caso de miss e grava o resultado de volta com um TTL. É simples, tolera quedas do cache e funciona com qualquer store.
- O que é um cache stampede e como evitar?
- Um stampede acontece quando uma chave popular expira e muitas requisições batem no banco ao mesmo tempo para reconstruí-la. Evite com um lock por chave, coalescência de requisições dentro do processo ou expiração antecipada probabilística, que renova a chave antes de ela realmente expirar.
- Qual deve ser a duração do TTL de um cache?
- O tempo que o negócio tolera dados desatualizados, e nem um segundo a mais. Combine o TTL com invalidação explícita nas escritas, para que o TTL seja só uma rede de segurança, não o mecanismo principal de atualização.
- O que o stale-while-revalidate faz no cache de CDN?
- Permite que a CDN entregue imediatamente uma resposta expirada enquanto busca uma cópia nova na origem em segundo plano. O usuário nunca espera pela origem, e a origem recebe uma única revalidação por edge em vez de uma rajada.
- Qual taxa de acerto (hit ratio) de cache é boa?
- Depende do custo de um miss, não de um número universal. Acompanhe a taxa de acerto junto com a latência p99 da origem e a carga do banco; se uma pequena queda na taxa faz o p99 disparar, o cache está fazendo um trabalho crítico e precisa de proteção contra stampede.
Artigos relacionados

GraphQL vs REST em 2026: quando usar cada estilo de API
GraphQL vs REST comparados em fetching, cache, versionamento, erros, N+1, segurança e ferramentas, com checklist de decisão por cenário e exemplo lado a lado.

Arquitetura Hexagonal em 2026: Portas, Adaptadores, Clean
Hexagonal vs Clean vs Onion explicadas com um exemplo real em TypeScript, estrutura de pastas, estratégia de testes e os erros que afundam a maioria dos times.

Jamstack em 2026: o que sobreviveu e o que o substituiu
O rótulo Jamstack sumiu, mas as práticas venceram. Estratégias de renderização comparadas, frameworks, hospedagem e um checklist para saber quando estático é a escolha certa.

Event-Driven Architecture: Resilient and Escalável Sistemas
Na Arvucore, exploramos como a arquitetura orientada a eventos transforma sistemas distribuídos modernos, possibilitando aplicações responsivas, resilientes e escaláveis. Este artigo examina princípios básicos, padrões de design práticos e considerações operacionais para a implementação de sistemas orientados a eventos em ambientes corporativos. Os leitores encontrarão orientações sobre escolhas de arquitetura, estratégias de integração e benefícios mensuráveis para apoiar a agilidade dos negócios e a robustez técnica em implantações de produção.