← Kursa Dön
📄 Text · 20 min

Cache Provider'lar: Caffeine, Redis

Spring Cache abstraction, farklı cache provider'lar üzerine çalışır. Doğru provider seçimi; uygulamanızın mimarisine, ölçeğine ve performans gereksinimlerine bağlıdır. Bu derste her provider'ı detaylı inceleyecek, konfigürasyon seçeneklerini, eviction politikalarını ve monitoring yaklaşımlarını öğreneceğiz.

ConcurrentMapCacheManager (Default)

// application.properties — hiçbir şey yapmazsanız bu kullanılır
spring.cache.type=simple

Basit ConcurrentHashMap tabanlıdır. Tek JVM için çalışır.

Neden production'da kullanılmaz:

  • TTL desteği yok — entry'ler sonsuza kadar kalır (memory leak!)

  • Eviction yok — bellek doluncaya kadar büyür

  • İstatistik yok — hit/miss oranı bilinmez

  • Size limit yok — bellek kontrolü yapılamaz

Sadece development ve unit test için uygundur.

Caffeine — Yüksek Performanslı Local Cache

Caffeine, Java dünyasının en hızlı local cache kütüphanesidir. Google Guava Cache'in yazarı tarafından geliştirilmiş, near-optimal hit rate sağlayan W-TinyLFU eviction algoritması kullanır.

<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
</dependency>

Temel Konfigürasyon — application.properties ile:

spring.cache.type=caffeine
spring.cache.caffeine.spec=maximumSize=10000,expireAfterWrite=30m,recordStats

Detaylı Konfigürasyon — Java Config ile:

@Configuration
@EnableCaching
public class CaffeineCacheConfig {

    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager manager = new CaffeineCacheManager();

        manager.setCaffeine(Caffeine.newBuilder()
            .maximumSize(10_000)                        // Max entry sayısı
            .expireAfterWrite(Duration.ofMinutes(30))   // Yazma sonrası expire
            .expireAfterAccess(Duration.ofMinutes(10))  // Son erişimden sonra expire
            .recordStats());                             // İstatistik toplama

        // Cache name'leri önceden tanımla (isteğe bağlı)
        manager.setCacheNames(List.of("products", "users", "categories"));
        manager.setAllowNullValues(false);  // null cache'lemeyi engelle

        return manager;
    }
}

Caffeine Konfigürasyon Parametreleri:

ParametreAçıklamaÖrnek
maximumSizeMaksimum entry sayısımaximumSize(10_000)
maximumWeightAğırlık bazlı limit (size yerine)maximumWeight(100_MB)
expireAfterWriteYazma sonrası expireexpireAfterWrite(30m)
expireAfterAccessSon erişimden sonra expireexpireAfterAccess(10m)
refreshAfterWriteArka planda yenilemerefreshAfterWrite(15m)
recordStatsHit/miss istatistiklerirecordStats()
weakKeysKey'ler GC'ye tabiweakKeys()
softValuesValue'lar soft referencesoftValues()

⚠️ Dikkat: expireAfterWrite ve expireAfterAccess birlikte kullanıldığında hangisi önce dolarsa o geçerlidir.

Cache Bazında Farklı Konfigürasyon:

@Configuration
@EnableCaching
public class MultiCaffeineCacheConfig {

    @Bean
    public CacheManager cacheManager() {
        // Her cache için farklı Caffeine instance
        SimpleCacheManager manager = new SimpleCacheManager();
        manager.setCaches(List.of(
            buildCache("products", 10_000, Duration.ofHours(2)),
            buildCache("users", 5_000, Duration.ofMinutes(15)),
            buildCache("categories", 500, Duration.ofHours(24)),
            buildCache("search", 20_000, Duration.ofMinutes(5))
        ));
        return manager;
    }

    private CaffeineCache buildCache(String name, long maxSize, Duration ttl) {
        return new CaffeineCache(name,
            Caffeine.newBuilder()
                .maximumSize(maxSize)
                .expireAfterWrite(ttl)
                .recordStats()
                .build());
    }
}

Caffeine Eviction Politikaları

W-TinyLFU (Window Tiny Least Frequently Used):

Caffeine'in varsayılan eviction algoritmasıdır. LRU ve LFU'nun en iyi özelliklerini birleştirir:

Yeni entry'ler → Window Cache (LRU, %1)
                      ↓
              Admission Filter (TinyLFU)
                      ↓
         Main Cache (Segmented LRU, %99)
  1. Window Cache (%1): Yeni entry'ler buraya girer (LRU)

  2. Admission Filter: TinyLFU count-min sketch ile frekans tahmin eder

  3. Main Cache (%99): Sık erişilen entry'ler burada kalır

Bu hibrit yaklaşım, hem recency hem frequency'yi dikkate alarak near-optimal hit rate sağlar.

Karşılaştırma:

PolitikaHit RateScan ResistanceBurst Resistance
LRUOrtaKötüİyi
LFUİyiİyiKötü
W-TinyLFUEn İyiİyiİyi

Scan resistance: Bir kerelik büyük veri taramasının (DB full scan gibi) cache'i "kirletmemesi".

EhCache 3 — Enterprise Cache

EhCache 3, tiered storage desteği ile hem heap hem off-heap hem disk kullanabilir:

<dependency>
    <groupId>org.ehcache</groupId>
    <artifactId>ehcache</artifactId>
</dependency>
@Configuration
@EnableCaching
public class EhCacheConfig {

    @Bean
    public CacheManager cacheManager() {
        org.ehcache.CacheManager ehCacheManager = CacheManagerBuilder.newCacheManagerBuilder()
            .withCache("products",
                CacheConfigurationBuilder
                    .newCacheConfigurationBuilder(Long.class, Product.class,
                        ResourcePoolsBuilder.newResourcePoolsBuilder()
                            .heap(1000, EntryUnit.ENTRIES)     // 1000 entry heap'te
                            .offheap(100, MemoryUnit.MB)        // 100MB off-heap
                            .disk(1, MemoryUnit.GB, true))      // 1GB disk (persistent)
                    .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(
                        Duration.ofMinutes(30))))
            .build(true);

        return new JCacheCacheManager(/* JCache wrapper */);
    }
}

Caffeine vs EhCache 3 Karşılaştırma

ÖzellikCaffeineEhCache 3
HızÇok hızlı (en hızlı JVM cache)Hızlı ama Caffeine'den yavaş
EvictionW-TinyLFU (near-optimal)LRU, LFU, FIFO
StorageSadece heapHeap + Off-heap + Disk
PersistenceYok (restart = veri kaybı)Disk persistence var
DistributedYok (sadece local)Terracotta ile cluster
KonfigürasyonJava API / propertiesXML, Java API
DependencyTek JAR (~1MB)Birden fazla JAR
MonitoringCacheStats APIJMX, Micrometer
KullanımWeb app, microserviceEnterprise, büyük veri setleri

Tavsiye: Çoğu Spring Boot uygulaması için Caffeine yeterlidir. EhCache 3, ancak off-heap/disk storage veya persistence gerekiyorsa tercih edin.

Cache Metrics ve Monitoring

Caffeine Stats:

@RestController
@RequiredArgsConstructor
public class CacheMetricsController {

    private final CacheManager cacheManager;

    @GetMapping("/admin/cache/stats")
    public Map<String, Object> getCacheStats() {
        Map<String, Object> stats = new LinkedHashMap<>();

        cacheManager.getCacheNames().forEach(name -> {
            Cache cache = cacheManager.getCache(name);
            if (cache instanceof CaffeineCache caffeineCache) {
                com.github.benmanes.caffeine.cache.stats.CacheStats s =
                    caffeineCache.getNativeCache().stats();

                stats.put(name, Map.of(
                    "hitCount", s.hitCount(),
                    "missCount", s.missCount(),
                    "hitRate", String.format("%.2f%%", s.hitRate() * 100),
                    "evictionCount", s.evictionCount(),
                    "estimatedSize", caffeineCache.getNativeCache().estimatedSize()
                ));
            }
        });
        return stats;
    }
}

Micrometer ile Spring Boot Actuator entegrasyonu:

# application.properties
management.endpoints.web.exposure.include=caches,metrics
// Caffeine recordStats() aktifse, Spring Boot otomatik olarak
// aşağıdaki metric'leri Micrometer'a expose eder:
// cache.gets{result=hit}
// cache.gets{result=miss}
// cache.evictions
// cache.size

// Prometheus'tan sorgulama:
// rate(cache_gets_total{cache="products",result="hit"}[5m])
// /
// rate(cache_gets_total{cache="products"}[5m])
// = hit rate

Provider Seçim Rehberi

SenaryoÖnerilen ProviderGerekçe
Development / TestConcurrentMapSıfır konfigürasyon
Single instance, web appCaffeineEn hızlı, basit
Single instance, büyük veriEhCache 3Off-heap + disk
Microservice, multi-instanceRedisDistributed, paylaşımlı
Microservice + localCaffeine (L1) + Redis (L2)Multi-level

Yaygın Hatalar

  • Production'da ConcurrentMap kullanmak: TTL ve eviction olmadığı için memory leak oluşur.

  • maximumSize koymamak: Cache sonsuza kadar büyür, OOM riski.

  • recordStats() aktif etmemek: Hit rate bilinmeden cache tuning yapılamaz.

  • Tüm cache'lere aynı TTL vermek: Sık değişen veri (kullanıcı sepeti) ile nadir değişen veri (kategori listesi) farklı TTL ister.

💡 Özet: Caffeine, single-instance için en iyi seçimdir (W-TinyLFU, en yüksek hit rate). EhCache 3, off-heap ve disk persistence gerektiğinde tercih edilir. Redis, distributed cache için zorunludur. Her zaman recordStats() aktif edin ve hit rate'i monitoring edin.