O que é caching e por que ele existe
Armazenar resultados para não recalcular o que já foi calculado
Cache é uma camada de armazenamento temporário que guarda resultados de operações custosas — consultas ao banco de dados, chamadas a APIs externas, cálculos complexos ou renderizações de HTML — para servir requisições futuras sem repetir o trabalho. O princípio é simples: se a mesma informação será requisitada várias vezes e não muda com frequência, é mais eficiente calculá-la uma vez e guardar o resultado do que recalcular a cada requisição. Um banco de dados que processa 1000 consultas por segundo pode processar 50 requisições por segundo se cada consulta levar 20ms; com cache, essas mesmas 1000 requisições retornam em microssegundos sem tocar no banco.
Cache-Aside — o padrão mais comum
A aplicação gerencia o cache diretamente
No padrão Cache-Aside (também chamado de Lazy Loading), a aplicação é responsável por consultar o cache antes do banco, popular o cache quando há miss, e invalidar o cache quando os dados mudam. O fluxo é: verificar se existe no cache — se sim, retornar (cache hit); se não, buscar no banco de dados, salvar no cache com TTL, e retornar (cache miss). Vantagem: dados são carregados sob demanda, cache só contém o que foi realmente requisitado. Desvantagem: cada cache miss resulta em 3 operações (cache + banco + cache), e em um restart do cache, todas as requisições iniciais causam miss simultâneos — fenômeno chamado de thundering herd.
Write-Through — manter cache e banco sempre sincronizados
Toda escrita vai para o cache e para o banco ao mesmo tempo
No padrão Write-Through, cada operação de escrita atualiza tanto o cache quanto o banco de dados de forma síncrona. A vantagem é que o cache nunca fica desatualizado em relação ao banco — qualquer leitura subsequente encontra o dado correto no cache. A desvantagem é latência adicional nas escritas (esperar confirmação de dois destinos) e armazenamento de dados que talvez nunca sejam lidos novamente. Write-Through é ideal quando leitura de dado stale é inaceitável, quando o sistema tem mais leituras do que escritas, e quando o cache deve funcionar como fonte de verdade para operações de leitura críticas.
TTL — o tempo de vida dos dados em cache
Todo dado em cache deve ter uma data de expiração
TTL (Time-To-Live) é o tempo máximo que um dado pode permanecer em cache antes de ser automaticamente descartado. Definir TTL correto é mais arte do que ciência: TTL muito curto aumenta a taxa de miss e coloca pressão no banco de dados; TTL muito longo aumenta o risco de servir dado desatualizado. A regra prática é: dados que raramente mudam (configurações do sistema, listas de países) podem ter TTL de horas ou dias; dados que mudam com frequência moderada (perfis de usuário, catálogos de produto) ficam bem com TTL de minutos a uma hora; dados em tempo real (preços ao vivo, estoque) não deveriam ser cacheados sem invalidação explícita ou TTL de segundos.
Cache Invalidation — o problema mais difícil do caching
Invalidar no momento certo sem perder performance
Phil Karlton disse que há apenas duas coisas difíceis em ciência da computação: invalidação de cache e nomear variáveis. A invalidação de cache é o problema mais crítico: quando os dados no banco mudam, o cache precisa ser atualizado ou descartado. As estratégias são: TTL-based (dados expiram automaticamente — simples mas pode servir dados stale durante a janela do TTL), event-driven invalidation (na escrita, o sistema invalida explicitamente as chaves de cache afetadas — mais complexo mas imediato), e write-through com atualização simultânea. O erro mais comum é invalidar de forma ampla demais (limpar todo o cache) ou de forma imprecisa (não invalidar chaves dependentes de um dado que mudou).
Thundering Herd — quando o cache vazio derruba o banco
Centenas de requisições batendo no banco ao mesmo tempo após cache miss
Thundering herd acontece quando o cache expira ou é reiniciado e múltiplas requisições simultâneas chegam ao mesmo tempo sem encontrar o dado em cache — todas vão para o banco simultaneamente. Para um banco que aguenta 100 queries/segundo em condições normais, 500 requisições simultâneas de cache miss podem ser devastadoras. As soluções são: cache warm-up (popular o cache antes de abrir tráfego), mutex/lock por chave (só uma requisição vai ao banco para popular a chave; as outras aguardam), probabilistic early expiration (expirar o cache um pouco antes do TTL de forma aleatória para evitar todos expirarem juntos), e shadow caching com TTL estendido (servir dado expirado enquanto a atualização acontece em background).
O que não deve ser cacheado
Dados únicos por usuário, sensíveis ou altamente voláteis exigem cuidado
Nem tudo deve ir para o cache. Dados personalizados por usuário (como saldo de conta, carrinho de compras ou notificações não lidas) nunca devem ir para cache compartilhado sem incluir o userId na chave — caso contrário, um usuário pode receber os dados de outro. Dados sensíveis como tokens de autenticação, números de cartão ou informações médicas requerem cuidados extras com encryption em cache. Dados que mudam com cada requisição ou que são únicos (resultado de uma busca com filtros únicos) têm hit rate tão baixo que o overhead de cachear supera o benefício. Dados com estado de transação em andamento (pedido sendo processado) podem ser problemáticos se o cache não for invalidado corretamente ao finalizar.
Ferramentas de cache — Redis, Memcached e CDN
Cada ferramenta tem seu caso de uso ideal
Redis é a escolha mais versátil: suporta estruturas de dados ricas (strings, hashes, sorted sets, listas), tem persistência opcional, suporta pub/sub, scripting Lua e transactions atômicas. Memcached é mais simples e mais rápido para casos de chave-valor puro com alto volume de reads. CDNs (Cloudflare, CloudFront, Fastly) cacheiam conteúdo estático e semi-estático na borda, próximo ao usuário, reduzindo latência geograficamente. Para cache em memória na própria aplicação, bibliotecas como IMemoryCache (.NET), Guava Cache (Java) e node-cache (Node.js) funcionam bem para dados usados frequentemente por uma única instância.
Estratégias de cache para banco de dados
O maior ganho de performance geralmente vem aqui
Queries ao banco de dados são o principal candidato para caching: uma query que agrega dados de múltiplas tabelas, aplica filtros complexos e retorna 1000 linhas pode levar 500ms. Com cache, a segunda requisição idêntica retorna em menos de 1ms. A chave de cache deve ser determinística: incluir todos os parâmetros da query (incluindo paginação, filtros e ordenação) para garantir que variações diferentes da query não retornem resultado incorreto. Ferramentas como Hibernate (Java) e Entity Framework (.NET) têm cache de segundo nível integrado. Para MongoDB, Redis como cache de consultas é o padrão mais comum em produção.
Conclusão — cache bem feito é fundamento de escala
Performance sem caching geralmente não sobrevive ao crescimento
Caching é um dos poucos mecanismos que oferece ganho de performance de ordem de grandeza — de segundos para milissegundos — com investimento relativamente baixo. O desafio não é implementar o cache em si, mas fazer a invalidação correta e escolher o TTL adequado para cada tipo de dado. Um cache mal implementado cria bugs sutis de consistência que aparecem em produção sob condições específicas e são difíceis de reproduzir. Comece pelos endpoints mais lentos da aplicação, meça o hit rate e o ganho de latência, e expanda para outros dados com base em evidência. Continue em: Fundamentos obrigatórios antes de produção.
Caching e Redis — Vídeos Essenciais
Caching Strategies With Redis — Software With Shawn
Top Redis Caching Strategies You Need To Know — SWE Vivek
Redis Caching — Speed Up Your Application With Redis — Josh tried coding
Using Redis for Caching in 2023 — Aseem Wangoo
Redis in 100 Seconds — Fireship
Idempotency in APIs — princípio similar ao cache para consistência
Caching e Performance no Instagram
Cache-Aside
Padrão onde a aplicação consulta o cache, vai ao banco em caso de miss, e popula o cache. O mais comum.
Write-Through
Padrão onde cada escrita atualiza cache e banco simultaneamente. Garante consistência mas adiciona latência nas escritas.
TTL (Time-To-Live)
Tempo máximo que um dado permanece em cache antes de expirar automaticamente.
Cache Hit
Quando o dado requisitado é encontrado no cache — a resposta mais rápida possível.
Cache Miss
Quando o dado não está no cache — exige busca no banco e popularização do cache.
Thundering Herd
Fenômeno onde muitas requisições simultâneas chegam ao banco após expiração de cache — pode derrubar o banco.
Caching e Performance no X
@bytebytego
Reels — Sistemas e Arquitetura
@bytebytego
ByteByteGo no Facebook
Referências e Leituras Recomendadas
Como testar que sua API é resiliente e segura para produção real
Ver post completo no X →Implementando padrões de resiliência em .NET Core com exemplos reais
Ver post completo no X →Vertical Slice Architecture — organizando sistemas para escala
Ver post completo no X →5 anos com Clean Architecture — lições de sistemas em produção
Ver post completo no X →Design de APIs resilientes — retry, backoff e idempotência juntos
Ver post completo no X →Monolito vs Microsserviços — como escolher para cada contexto
Ver post completo no X →O que dizem