Técnicas avançadas de caching para aplicações web
1. Fundamentos e Estratégias de Cache Distribuído
O cache distribuído é a espinha dorsal de aplicações web escaláveis. Ao migrar de um cache local para uma arquitetura em cluster, surgem desafios como consistência de dados e particionamento eficiente.
Redis Cluster vs. Apache Ignite
O Redis Cluster oferece sharding automático com 16384 slots de hash, distribuindo dados entre nós. Cada nó gerencia um subconjunto de slots e replica dados para alta disponibilidade. Já o Apache Ignite utiliza uma arquitetura baseada em memória com suporte a SQL distribuído e computação paralela.
# Exemplo de configuração de Redis Cluster com 3 nós
redis-cli --cluster create 192.168.1.10:6379 192.168.1.11:6379 192.168.1.12:6379 \
--cluster-replicas 1
Técnicas de Particionamento e Consistência
O particionamento de dados (sharding) pode ser feito por:
- Hash consistente: minimiza remapeamento quando nós são adicionados/removidos
- Range-based: divide dados por faixas de chaves
- Geo-localização: mantém dados próximos aos usuários
A consistência eventual é gerenciada com vetores de relógio (vector clocks) e estratégias de resolução de conflitos.
Gerenciamento de TTL Adaptativo
Políticas de expiração adaptativas ajustam dinamicamente o TTL com base na frequência de acesso:
# Pseudocódigo para TTL adaptativo
def calcular_ttl_adaptativo(frequencia_acesso, ttl_base):
if frequencia_acesso > 100:
return ttl_base * 2
elif frequencia_acesso > 50:
return ttl_base * 1.5
else:
return ttl_base * 0.5
2. Cache em Múltiplas Camadas
Cache no Navegador
Headers HTTP como Cache-Control e ETag controlam o cache do lado do cliente:
# Configuração de cache no servidor (Node.js/Express)
res.setHeader('Cache-Control', 'public, max-age=3600, s-maxage=86400');
res.setHeader('ETag', 'hash-do-conteudo-12345');
Service Workers permitem caching programático offline:
// Service Worker: estratégia Cache First
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then((response) => {
return response || fetch(event.request);
})
);
});
Edge Caching com CDNs
CDNs como Cloudflare e Akamai utilizam cache em pontos de presença (PoPs) globais. Estratégias de purga incluem:
- Purga por URL: invalidação seletiva
- Purga por tag: agrupa recursos relacionados
- Cache warming: pré-carregamento de conteúdo popular
Cache em Camadas L1 e L2
Aplicações modernas implementam cache hierárquico:
# Estrutura de cache em duas camadas
class CacheManager:
def __init__(self):
self.l1_cache = {} # Cache local em memória
self.l2_cache = RedisClient() # Cache distribuído
def get(self, key):
# Tenta L1 primeiro
data = self.l1_cache.get(key)
if data:
return data
# Fallback para L2
data = self.l2_cache.get(key)
if data:
self.l1_cache[key] = data # Popula L1
return data
return None
3. Padrões Avançados de Cache
Write-Through
Garante consistência imediata entre banco e cache:
def write_through(usuario):
# Escreve no banco primeiro
db.salvar(usuario)
# Atualiza cache simultaneamente
cache.set(f"usuario:{usuario.id}", usuario)
Write-Behind (Write-Back)
Otimiza gravações com processamento assíncrono:
def write_behind(usuario):
# Escreve apenas no cache
cache.set(f"usuario:{usuario.id}", usuario)
# Enfileira para gravação no banco
fila_assincrona.enfileirar(usuario)
Refresh-Ahead
Pré-carrega dados proativamente baseado em padrões de acesso:
def refresh_ahead(key, ttl, threshold):
# Se o dado está próximo da expiração, atualiza antes
if cache.ttl(key) < threshold:
dados = buscar_dados_recentes(key)
cache.set(key, dados, ttl)
4. Cache de Consultas e Resultados de Banco de Dados
Cache de Queries SQL
Invalidar cache baseado em dependências de tabelas:
# Cache com invalidação por tabela
def cache_query(sql, tabelas_dependentes):
cache_key = hashlib.md5(sql.encode()).hexdigest()
resultado = cache.get(cache_key)
if resultado:
# Verifica se tabelas foram modificadas
for tabela in tabelas_dependentes:
if cache.get(f"ultima_atualizacao:{tabela}") > resultado.timestamp:
return None # Cache inválido
return resultado
return None
Cache de Objetos ORM
Gerenciamento de identidade com lazy loading:
# Cache de objetos no Hibernate/JPA
@Entity
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Usuario {
@Id
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Departamento departamento;
}
Cache de Agregações Complexas
Materialização de resultados:
# Cache de relatório financeiro agregado
def get_relatorio_vendas(data_inicio, data_fim):
cache_key = f"relatorio_vendas:{data_inicio}:{data_fim}"
resultado = cache.get(cache_key)
if not resultado:
resultado = db.query("""
SELECT SUM(valor), COUNT(*)
FROM vendas
WHERE data BETWEEN :inicio AND :fim
""")
cache.set(cache_key, resultado, ttl=3600)
return resultado
5. Técnicas de Invalidação e Consistência
Invalidação por Evento (Pub/Sub)
Padrão Cache-Aside com notificações:
# Publica evento de invalidação
def atualizar_usuario(usuario_id, dados):
db.atualizar(usuario_id, dados)
redis.publish("cache_invalidation", f"usuario:{usuario_id}")
Invalidação Baseada em Versão
Uso de timestamps e vetores de relógio:
def get_com_versao(key):
versao = cache.get(f"versao:{key}")
dados = cache.get(key)
if dados and dados.versao == versao:
return dados
return None
Cache Tolerante a Falhas
Degradação graciosa quando cache falha:
def get_com_fallback(key):
try:
return cache.get(key)
except ConnectionError:
# Fallback para banco de dados
return db.get(key)
except Exception:
# Fallback para cache local
return local_cache.get(key)
6. Cache de Sessão e Autenticação Distribuída
Sessões Centralizadas vs. Stateless
Sessões em Redis:
# Armazenamento de sessão em Redis
redis.setex(f"sessao:{session_id}", 3600, {
"usuario_id": 123,
"permissoes": ["admin", "editor"]
})
JWT para sessões stateless:
# Token JWT com claims de autorização
{
"sub": "123",
"exp": 1700000000,
"permissoes": ["admin", "editor"],
"iat": 1699996400
}
Rate Limiting com Contadores Atômicos
def rate_limit(usuario_id, limite=100, janela=60):
chave = f"rate_limit:{usuario_id}:{int(time.time()/janela)}"
contagem = redis.incr(chave)
if contagem == 1:
redis.expire(chave, janela)
return contagem <= limite
7. Monitoramento, Métricas e Otimização
Métricas-Chave
- Hit ratio: percentual de acessos bem-sucedidos ao cache
- Miss ratio: acessos que falharam e foram ao backend
- Latência: tempo de resposta do cache
- Taxa de expiração: itens expirados vs. removidos manualmente
Ferramentas de Monitoramento
# Configuração de métricas com Prometheus
redis_hits_total = Counter('redis_hits_total', 'Total de hits no cache')
redis_misses_total = Counter('redis_misses_total', 'Total de misses')
cache_latency = Histogram('cache_latency_seconds', 'Latência do cache')
Técnicas de Tuning
Políticas de evicção:
- LRU (Least Recently Used): remove itens menos acessados recentemente
- LFU (Least Frequently Used): remove itens menos acessados no total
- TTL: remove itens expirados
# Ajuste de política de evicção no Redis
CONFIG SET maxmemory-policy allkeys-lru
CONFIG SET maxmemory 2gb
Referências
- Redis Documentation: Cluster Tutorial — Guia completo sobre configuração e gerenciamento de clusters Redis, incluindo sharding e replicação
- Apache Ignite Documentation: Distributed Caching — Documentação oficial sobre cache distribuído com Apache Ignite, incluindo SQL e computação paralela
- MDN Web Docs: HTTP Caching — Referência completa sobre headers de cache HTTP, ETags e estratégias de cache no navegador
- Cloudflare: CDN Caching Best Practices — Guia prático sobre edge caching, purga de cache e cache warming com Cloudflare
- Martin Fowler: Patterns of Distributed Systems — Artigo técnico sobre padrões de cache distribuído, incluindo Write-Through e Write-Behind
- Prometheus Documentation: Monitoring Redis — Tutorial sobre monitoramento de métricas de cache Redis com Prometheus e dashboards
- Hibernate ORM: Caching Guide — Documentação sobre cache de segundo nível, lazy loading e gerenciamento de objetos ORM