"Cache é a diferença entre uma API lenta e uma API instantânea. A estratégia certa reduz latência em 90%." — #30DiasJava Performance Notes

🎯 Objetivo do Day 15

Com o gateway protegendo a entrada, era hora de otimizar o desempenho. O Day 15 do #30DiasJava entregou um sistema de cache completo com múltiplas estratégias, invalidação inteligente e observabilidade de hit rates.

🛠️ O que foi implementado

✅ Múltiplas Estratégias de Cache

  • Cache-aside (Lazy Loading): Aplicação gerencia cache manualmente
  • Write-through: Escrita simultânea em cache e banco
  • Write-behind (Write-back): Escrita assíncrona em batch
  • Refresh-ahead: Pré-carregamento antes de expirar
  • Cache invalidation: Invalidação por eventos ou TTL

✅ Stack de Cache Multi-Camada

  • L1 Cache (Caffeine): Cache in-memory local (ultra-rápido)
  • L2 Cache (Redis): Cache distribuído compartilhado
  • L3 Cache (Database): Queries otimizadas com índices
  • Cache warming: Pré-carregamento em startup

✅ TTL Inteligente

  • Static TTL: Tempo fixo para dados estáticos
  • Dynamic TTL: TTL baseado em volatilidade dos dados
  • Sliding TTL: Renovação automática em acesso
  • TTL por tipo: Diferentes TTLs para diferentes domínios

✅ Cache Invalidation

  • Event-driven invalidation: Invalidação por eventos de domínio
  • Pattern-based invalidation: Invalidação por padrões (ex.: user:*)
  • Distributed invalidation: Pub/Sub Redis para invalidar em todos os pods
  • Stale-while-revalidate: Serve stale data enquanto revalida em background

✅ Observabilidade

  • Hit rate metrics: cache.hits.total, cache.misses.total
  • Cache size metrics: cache.size{name}
  • Eviction metrics: cache.evictions.total
  • Latency metrics: cache.latency.seconds

📊 Arquitetura

┌─────────────┐
│   Request   │
└──────┬──────┘
       │
       ▼
┌─────────────────┐
│  @Cacheable     │
│  (Caffeine L1)  │
└──────┬──────────┘
       │ Miss
       ▼
┌─────────────────┐
│  Redis Cache    │
│  (L2)           │
└──────┬──────────┘
       │ Miss
       ▼
┌─────────────────┐
│   Database      │
│   (L3)          │
└─────────────────┘

🔧 Implementação Técnica

1. Dependências (pom.xml)

<!-- Spring Cache -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

<!-- Caffeine (L1 Cache) -->
<dependency>
  <groupId>com.github.ben-manes.caffeine</groupId>
  <artifactId>caffeine</artifactId>
</dependency>

<!-- Redis (L2 Cache) -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- Cache Metrics -->
<dependency>
  <groupId>io.micrometer</groupId>
  <artifactId>micrometer-core</artifactId>
</dependency>

2. Cache Configuration

@Configuration
@EnableCaching
public class CacheConfig {
  
  @Bean
  public CacheManager cacheManager(RedisConnectionFactory factory) {
    RedisCacheConfiguration defaultConfig = RedisCacheConfiguration
      .defaultCacheConfig()
      .entryTtl(Duration.ofMinutes(10))
      .serializeValuesWith(RedisSerializationContext.SerializationPair
        .fromSerializer(new GenericJackson2JsonRedisSerializer()));
    
    return RedisCacheManager.builder(factory)
      .cacheDefaults(defaultConfig)
      .withCacheConfiguration("users", 
        defaultConfig.entryTtl(Duration.ofHours(1)))
      .withCacheConfiguration("families",
        defaultConfig.entryTtl(Duration.ofMinutes(30)))
      .build();
  }
  
  @Bean
  public CaffeineCache localCache() {
    return Caffeine.newBuilder()
      .maximumSize(1000)
      .expireAfterWrite(5, TimeUnit.MINUTES)
      .recordStats()
      .build();
  }
}

3. Cache Usage

@Service
public class FamiliaService {
  
  @Cacheable(value = "families", key = "#id")
  public FamiliaDTO findById(Long id) {
    return familiaRepository.findById(id)
      .map(this::toDTO)
      .orElseThrow();
  }
  
  @CacheEvict(value = "families", key = "#id")
  public FamiliaDTO update(Long id, FamiliaDTO dto) {
    // Update logic
    return updated;
  }
  
  @CacheEvict(value = "families", allEntries = true)
  public void evictAll() {
    // Clear all cache
  }
}

4. Cache Warming

@Component
@Slf4j
public class CacheWarmer implements ApplicationListener<ContextRefreshedEvent> {
  
  @Autowired
  private FamiliaService familiaService;
  
  @Override
  public void onApplicationEvent(ContextRefreshedEvent event) {
    log.info("Warming cache...");
    // Pre-load frequently accessed data
    familiaService.findAll().forEach(f -> {
      familiaService.findById(f.getId());
    });
    log.info("Cache warmed successfully");
  }
}

💡 Padrões de Cache

Cache-Aside (Lazy Loading)

public FamiliaDTO findById(Long id) {
  // 1. Check cache
  FamiliaDTO cached = cache.get(id);
  if (cached != null) {
    return cached;
  }
  
  // 2. Load from database
  FamiliaDTO dto = loadFromDatabase(id);
  
  // 3. Store in cache
  cache.put(id, dto);
  
  return dto;
}

Write-Through

@CachePut(value = "families", key = "#dto.id")
public FamiliaDTO save(FamiliaDTO dto) {
  // Write to database and cache simultaneously
  return familiaRepository.save(toEntity(dto));
}

Write-Behind (Async)

@Async
@CachePut(value = "families", key = "#dto.id")
public CompletableFuture<FamiliaDTO> saveAsync(FamiliaDTO dto) {
  // Write to cache immediately
  cache.put(dto.getId(), dto);
  
  // Write to database asynchronously
  return CompletableFuture.supplyAsync(() -> 
    familiaRepository.save(toEntity(dto))
  );
}

💡 Lições do dia

  • Multi-layer cache maximiza performance: L1 para ultra-rápido, L2 para compartilhado
  • TTL é uma arte: Muito curto = muitos misses, muito longo = dados stale
  • Invalidation é crítico: Cache stale pode causar bugs graves
  • Hit rate é a métrica principal: 80%+ hit rate é excelente
  • Cache warming reduz cold starts: Pré-carregamento melhora primeira resposta

📈 Métricas Importantes

  • Hit Rate: cache.hits / (cache.hits + cache.misses)
  • Cache Size: Tamanho atual do cache
  • Eviction Rate: Taxa de remoção por falta de espaço
  • Latency: Redução de latência com cache vs sem cache

🔗 Recursos


Next episodeDay 16/30 — Config Service & Centralized Configuration