Estratégias de cache em múltiplas camadas
1. Fundamentos do Cache Multicamadas
1.1. Definição e objetivos do cache em múltiplas camadas
Cache em múltiplas camadas é uma arquitetura que armazena dados temporários em diferentes níveis hierárquicos, desde o cliente até o banco de dados. O objetivo principal é reduzir a latência de acesso a dados, diminuir a carga em servidores de origem e melhorar a experiência do usuário. Cada camada opera com diferentes capacidades de armazenamento, velocidades de acesso e políticas de expiração.
1.2. Latência e gargalos: do cliente ao banco de dados
A latência em sistemas web varia drasticamente entre camadas:
- Cache L1 (navegador): 0-5ms
- Cache L2 (CDN): 10-50ms
- Cache L3 (aplicação): 1-5ms (em memória)
- Cache L4 (banco): 5-20ms (buffer pool)
- Banco de dados sem cache: 50-500ms
Gargalos típicos incluem consultas repetitivas ao banco, assets não cacheados e TTLs mal configurados.
1.3. Hierarquia de cache: L1, L2, L3, L4
A hierarquia segue o princípio de localidade temporal e espacial:
- L1 – Navegador: cache local do usuário (Cache Storage, HTTP cache)
- L2 – CDN: cache em servidores edge geograficamente distribuídos
- L3 – Aplicação: cache em memória (Redis, Memcached) no servidor de aplicação
- L4 – Banco: cache interno do SGBD (buffer pool, query cache)
2. Cache na Camada do Cliente (L1)
2.1. Cache de navegador: cabeçalhos HTTP
Cache-Control: public, max-age=3600, immutable
ETag: "abc123"
Expires: Wed, 21 Oct 2025 07:28:00 GMT
Exemplo de resposta HTTP com cache otimizado:
HTTP/1.1 200 OK
Content-Type: text/css
Cache-Control: public, max-age=31536000, immutable
ETag: "v2.1.3-style-min"
Last-Modified: Tue, 15 Mar 2024 10:30:00 GMT
2.2. Service Workers e Cache API para aplicações PWA
Registro de Service Worker com cache de assets:
// service-worker.js
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('app-v2').then((cache) => {
return cache.addAll([
'/',
'/index.html',
'/styles/main.css',
'/scripts/app.js',
'/images/logo.png'
]);
})
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});
2.3. Estratégias de invalidação: stale-while-revalidate e versionamento
# Estratégia stale-while-revalidate para assets dinâmicos
Cache-Control: public, max-age=600, stale-while-revalidate=86400
# Versionamento de assets via hash no nome do arquivo
<link rel="stylesheet" href="/styles/main.a1b2c3d4.css">
<script src="/scripts/app.e5f6g7h8.js"></script>
3. Cache na Camada de CDN (L2)
3.1. Funcionamento de CDNs: edge caching e distribuição geográfica
CDNs armazenam conteúdo em servidores edge próximos aos usuários. Exemplo de configuração com Cloudflare:
# Regras de cache para conteúdo estático
# Arquivos com extensão .css, .js, .png, .jpg
# Cache TTL: 30 dias (2592000 segundos)
# Edge Cache TTL: 30 dias
# Browser Cache TTL: 7 dias
# Regra para conteúdo dinâmico com cache controlado
# URL: /api/*
# Edge Cache TTL: 60 segundos
# Bypass on cookie: session_id
3.2. Purga e invalidação seletiva de cache em CDN
# Purga seletiva via API (exemplo Cloudflare)
curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/purge_cache" \
-H "Authorization: Bearer API_TOKEN" \
-H "Content-Type: application/json" \
--data '{"files":["https://exemplo.com/produtos/novo-lancamento.html"]}'
# Purga por tag (se suportado)
--data '{"tags":["produto:12345","categoria:10"]}'
3.3. Cache de conteúdo dinâmico vs. estático: TTLs e regras
# Conteúdo estático: TTL longo (30 dias)
Cache-Control: public, max-age=2592000, immutable
# Conteúdo dinâmico com variação por usuário: sem cache CDN
Cache-Control: private, no-cache, no-store
# Conteúdo dinâmico semi-estático: TTL curto (5 minutos)
Cache-Control: public, max-age=300, s-maxage=300
4. Cache na Camada de Aplicação (L3)
4.1. Cache em memória: Redis e Memcached
Exemplo de configuração Redis para cache de sessão e consultas:
# Configuração Redis para cache
maxmemory 512mb
maxmemory-policy allkeys-lru
save 900 1
save 300 10
save 60 10000
# Exemplo de cache de consulta frequente
# SETEX user:profile:12345 3600 '{"nome":"João","email":"joao@exemplo.com"}'
# GET user:profile:12345
4.2. Padrões de cache: Cache-Aside, Read-Through, Write-Through
Cache-Aside (Lazy Loading):
1. Tentar ler do cache
2. Se cache miss, ler do banco
3. Armazenar resultado no cache
4. Retornar dados
funcao buscarUsuario(id):
usuario = redis.get("usuario:" + id)
se usuario == null:
usuario = banco.buscarUsuario(id)
redis.setex("usuario:" + id, 3600, usuario)
retornar usuario
Write-Through:
1. Escrever no cache
2. Escrever no banco simultaneamente
3. Confirmar escrita
funcao atualizarUsuario(id, dados):
redis.set("usuario:" + id, dados)
banco.atualizarUsuario(id, dados)
retornar sucesso
4.3. Estratégias de expiração: TTL, LRU, LFU e cache warming
# TTL por tipo de dado
# Dados de perfil de usuário: 1 hora (3600s)
# Lista de produtos em destaque: 5 minutos (300s)
# Configurações do sistema: 24 horas (86400s)
# LRU (Least Recently Used) - Redis padrão
maxmemory-policy allkeys-lru
# LFU (Least Frequently Used) - Redis 4.0+
maxmemory-policy allkeys-lfu
# Cache warming em startup
funcao aquecerCache():
produtos_populares = banco.buscarProdutosPopulares(100)
para cada produto em produtos_populares:
redis.setex("produto:" + produto.id, 300, produto)
5. Cache na Camada de Banco de Dados (L4)
5.1. Query cache nativo do banco (MySQL, PostgreSQL) e suas limitações
# MySQL Query Cache (depreciado no MySQL 8.0)
query_cache_type = 1
query_cache_size = 64M
query_cache_limit = 2M
# Limitações:
# - Invalidado a cada modificação na tabela
# - Não funciona com consultas não-determinísticas (NOW(), RAND())
# - Overhead de manutenção para tabelas muito voláteis
# PostgreSQL: shared_buffers e effective_cache_size
shared_buffers = 4GB
effective_cache_size = 12GB
5.2. Buffer pool e índices em memória (InnoDB, Buffer Cache)
# MySQL InnoDB Buffer Pool
innodb_buffer_pool_size = 70% da RAM disponível
innodb_buffer_pool_instances = 4
innodb_old_blocks_time = 1000
innodb_old_blocks_pct = 37
# PostgreSQL Buffer Cache
# shared_buffers: 25% da RAM
# effective_cache_size: 50-75% da RAM
# work_mem: 4-16MB por operação de sort
5.3. Cache de resultados agregados e materialized views
-- Materialized View para relatórios de vendas
CREATE MATERIALIZED VIEW vendas_diarias AS
SELECT
DATE(data_venda) as dia,
COUNT(*) as total_vendas,
SUM(valor) as receita_total
FROM vendas
GROUP BY DATE(data_venda)
WITH DATA;
-- Refresh periódico
REFRESH MATERIALIZED VIEW CONCURRENTLY vendas_diarias;
-- Índice na materialized view
CREATE INDEX idx_vendas_diarias_dia ON vendas_diarias(dia);
6. Consistência e Coerência entre Camadas
6.1. Problema de coerência de cache: dados obsoletos em múltiplos níveis
Quando um dado é atualizado no banco, as cópias em todas as camadas de cache (L1, L2, L3, L4) podem ficar obsoletas. O desafio é invalidar ou atualizar todas essas cópias de forma coordenada.
6.2. Estratégias de invalidação em cascata: pub/sub com Redis ou mensageria
# Publicador (serviço de atualização)
redis.publish("cache:invalidate", "usuario:12345")
# Assinante (serviço de cache)
redis.subscribe("cache:invalidate", funcao(canal, mensagem):
chave = mensagem
redis.del(chave)
# Opcional: notificar CDN para purgar
cdn.purge("/api/usuarios/" + extrairId(chave))
)
6.3. Cache stampede: prevenção com mutex locks e cache warming
# Prevenção de cache stampede com mutex lock
funcao buscarDadosCaros(chave):
dados = redis.get(chave)
se dados != null:
retornar dados
# Tentar adquirir lock
lock = redis.setnx("lock:" + chave, "1", 10) # TTL 10s
se lock:
# Apenas um processo busca do banco
dados = banco.buscarDadosCaros()
redis.setex(chave, 300, dados)
redis.del("lock:" + chave)
retornar dados
senao:
# Outros processos esperam e tentam novamente
sleep(50ms)
retornar buscarDadosCaros(chave) # Recursão controlada
7. Monitoramento e Otimização do Cache Multicamadas
7.1. Métricas essenciais: hit rate, miss rate, latência e estouro de memória
# Métricas críticas para cada camada:
# L1 (Navegador): cache hit rate > 80%, transfer size
# L2 (CDN): cache hit rate > 90%, bandwidth savings
# L3 (Redis): hit rate > 95%, evicted keys, memory usage
# L4 (Banco): buffer pool hit rate > 99%, query cache hit rate
# Alertas:
# Redis: used_memory > 80% maxmemory
# CDN: cache hit rate < 70%
# Banco: buffer pool hit rate < 95%
7.2. Ferramentas de monitoramento: Prometheus, Grafana e dashboards de cache
# Exemplo de métricas Redis para Prometheus
# redis_info{db="0",role="master"} 1
# redis_keys_total{db="0"} 15000
# redis_hit_rate{db="0"} 0.97
# redis_memory_used_bytes{db="0"} 4294967296
# Dashboard Grafana:
# Painel 1: Hit rate por camada (L1, L2, L3, L4)
# Painel 2: Latência média por camada
# Painel 3: Memória utilizada vs. limite
# Painel 4: Top 10 chaves mais acessadas
7.3. Ajuste dinâmico de TTL e tamanho de cache baseado em padrões de acesso
# Algoritmo de ajuste dinâmico de TTL
funcao ajustarTTL(chave, frequenciaAcesso):
se frequenciaAcesso > 1000/minuto:
# Dados muito acessados: TTL longo
novoTTL = 3600 # 1 hora
senao se frequenciaAcesso > 100/minuto:
# Acesso moderado: TTL médio
novoTTL = 600 # 10 minutos
senao:
# Acesso baixo: TTL curto
novoTTL = 60 # 1 minuto
redis.expire(chave, novoTTL)
# Ajuste de tamanho de cache baseado em eviction rate
se evicted_keys_por_segundo > 100:
# Muitas evicções: aumentar memória
redis.config_set("maxmemory", tamanhoAtual * 1.2)
senao se evicted_keys_por_segundo < 10:
# Poucas evicções: reduzir memória
redis.config_set("maxmemory", tamanhoAtual * 0.9)
Referências
- Redis Documentation: Cache Patterns — Documentação oficial sobre padrões de cache com Redis, incluindo Cache-Aside, Read-Through e Write-Through.
- Cloudflare Caching Documentation — Guia completo sobre configuração de cache em CDN, purga seletiva e edge caching.
- MDN Web Docs: HTTP Caching — Referência detalhada sobre cabeçalhos HTTP de cache, ETags e estratégias de validação.
- PostgreSQL Documentation: Routine Vacuuming and Analysis — Documentação oficial sobre gerenciamento de buffer cache e materialized views no PostgreSQL.
- AWS ElastiCache Best Practices — Guia de melhores práticas para cache em memória com Redis e Memcached, incluindo prevenção de cache stampede.