CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-github-ben-manes-caffeine--caffeine

A high performance caching library for Java providing Google Guava-inspired API with advanced eviction policies and comprehensive features

Pending
Overview
Eval results
Files

statistics.mddocs/

Statistics

Caffeine provides comprehensive cache performance statistics through the CacheStats class and configurable StatsCounter implementations. Statistics enable monitoring cache effectiveness, identifying performance bottlenecks, and making informed tuning decisions.

CacheStats Class

The CacheStats class provides an immutable snapshot of cache performance metrics.

public final class CacheStats {
    // Request metrics
    public long requestCount();
    public long hitCount();
    public double hitRate();
    public long missCount();
    public double missRate();
    
    // Load metrics
    public long loadCount();
    public long loadSuccessCount();
    public long loadFailureCount();
    public double loadFailureRate();
    public double averageLoadPenalty();
    public long totalLoadTime();
    
    // Eviction metrics
    public long evictionCount();
    public long evictionWeight();
    
    // Statistics operations
    public CacheStats minus(CacheStats other);
    public CacheStats plus(CacheStats other);
    
    // Factory methods
    public static CacheStats empty();
    public static CacheStats of(long hitCount, long missCount, long loadSuccessCount, 
                               long loadFailureCount, long totalLoadTime, 
                               long evictionCount, long evictionWeight);
}

Basic Statistics Usage

Cache<String, String> statsCache = Caffeine.newBuilder()
    .maximumSize(1000)
    .recordStats()
    .build();

// Perform cache operations
statsCache.put("key1", "value1");
statsCache.put("key2", "value2");
statsCache.getIfPresent("key1");  // hit
statsCache.getIfPresent("key3");  // miss

// Get statistics snapshot
CacheStats stats = statsCache.stats();

// Request statistics
System.out.println("Total requests: " + stats.requestCount());      // 2
System.out.println("Hits: " + stats.hitCount());                    // 1
System.out.println("Hit rate: " + stats.hitRate());                 // 0.5
System.out.println("Misses: " + stats.missCount());                 // 1
System.out.println("Miss rate: " + stats.missRate());               // 0.5

Load Statistics

LoadingCache<String, String> loadingCache = Caffeine.newBuilder()
    .maximumSize(1000)
    .recordStats()
    .build(key -> {
        if (key.equals("error")) {
            throw new RuntimeException("Load error");
        }
        Thread.sleep(100); // Simulate load time
        return "loaded_" + key;
    });

// Perform operations that trigger loading
loadingCache.get("key1");           // successful load
loadingCache.get("key2");           // successful load
try {
    loadingCache.get("error");      // failed load
} catch (Exception e) {
    // Handle load failure
}

CacheStats loadStats = loadingCache.stats();

// Load statistics
System.out.println("Load count: " + loadStats.loadCount());                    // 3
System.out.println("Load successes: " + loadStats.loadSuccessCount());         // 2
System.out.println("Load failures: " + loadStats.loadFailureCount());          // 1 
System.out.println("Load failure rate: " + loadStats.loadFailureRate());       // 0.33
System.out.println("Average load time (ns): " + loadStats.averageLoadPenalty()); // ~100ms in ns
System.out.println("Total load time (ns): " + loadStats.totalLoadTime());

Eviction Statistics

Cache<String, String> evictionCache = Caffeine.newBuilder()
    .maximumSize(3)
    .recordStats()
    .build();

// Fill cache beyond capacity to trigger evictions
evictionCache.put("key1", "value1");
evictionCache.put("key2", "value2");  
evictionCache.put("key3", "value3");
evictionCache.put("key4", "value4");  // Triggers eviction
evictionCache.put("key5", "value5");  // Triggers eviction

CacheStats evictionStats = evictionCache.stats();

System.out.println("Evictions: " + evictionStats.evictionCount());        // 2
System.out.println("Eviction weight: " + evictionStats.evictionWeight()); // 2 (default weight 1 per entry)

Weight-Based Eviction Statistics

Cache<String, String> weightedCache = Caffeine.newBuilder()
    .maximumWeight(10)
    .weigher((key, value) -> key.length() + value.length())
    .recordStats()
    .build();

// Add entries with different weights
weightedCache.put("a", "1");         // weight: 2
weightedCache.put("bb", "22");       // weight: 4  
weightedCache.put("ccc", "333");     // weight: 6
weightedCache.put("dddd", "4444");   // weight: 8, may trigger eviction

CacheStats weightStats = weightedCache.stats();

System.out.println("Evictions: " + weightStats.evictionCount());
System.out.println("Total evicted weight: " + weightStats.evictionWeight());

Statistics Operations

Combining Statistics

Cache<String, String> cache1 = Caffeine.newBuilder()
    .maximumSize(100)
    .recordStats()
    .build();

Cache<String, String> cache2 = Caffeine.newBuilder()
    .maximumSize(100)
    .recordStats()  
    .build();

// Perform operations on both caches
cache1.put("key1", "value1");
cache1.getIfPresent("key1");  // hit

cache2.put("key2", "value2");
cache2.getIfPresent("key3");  // miss

// Combine statistics
CacheStats stats1 = cache1.stats();
CacheStats stats2 = cache2.stats();
CacheStats combined = stats1.plus(stats2);

System.out.println("Combined hits: " + combined.hitCount());      // 1
System.out.println("Combined misses: " + combined.missCount());   // 1
System.out.println("Combined requests: " + combined.requestCount()); // 2

Statistics Differences

Cache<String, String> monitoredCache = Caffeine.newBuilder()
    .maximumSize(1000)
    .recordStats()
    .build();

// Take baseline snapshot
CacheStats baseline = monitoredCache.stats();

// Perform operations
monitoredCache.put("key1", "value1");
monitoredCache.put("key2", "value2");
monitoredCache.getIfPresent("key1");  // hit
monitoredCache.getIfPresent("key3");  // miss

// Calculate delta
CacheStats current = monitoredCache.stats();
CacheStats delta = current.minus(baseline);

System.out.println("New hits: " + delta.hitCount());           // 1
System.out.println("New misses: " + delta.missCount());        // 1
System.out.println("New requests: " + delta.requestCount());   // 2

StatsCounter Interface

The StatsCounter interface allows custom statistics collection and reporting.

public interface StatsCounter {
    // Recording methods
    void recordHits(int count);
    void recordMisses(int count);
    void recordLoadSuccess(long loadTime);
    void recordLoadFailure(long loadTime);
    void recordEviction(int weight, RemovalCause cause);
    
    // Snapshot method
    CacheStats snapshot();
}

Built-in StatsCounter Implementations

ConcurrentStatsCounter

// Default thread-safe implementation using LongAdder
Cache<String, String> concurrentStatsCache = Caffeine.newBuilder()
    .maximumSize(1000)
    .recordStats(ConcurrentStatsCounter::new)
    .build();

// High-concurrency statistics collection
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
    final int threadId = i;
    executor.submit(() -> {
        concurrentStatsCache.put("key_" + threadId, "value_" + threadId);
        concurrentStatsCache.getIfPresent("key_" + threadId);
    });
}

// Statistics remain accurate under high concurrency
CacheStats concurrentStats = concurrentStatsCache.stats();
System.out.println("Concurrent hit rate: " + concurrentStats.hitRate());

Custom StatsCounter Implementation

public class CustomStatsCounter implements StatsCounter {
    private final AtomicLong hits = new AtomicLong();
    private final AtomicLong misses = new AtomicLong();
    private final AtomicLong loadSuccesses = new AtomicLong();
    private final AtomicLong loadFailures = new AtomicLong();
    private final AtomicLong totalLoadTime = new AtomicLong();
    private final AtomicLong evictions = new AtomicLong();
    private final AtomicLong evictionWeight = new AtomicLong();
    
    @Override
    public void recordHits(int count) {
        hits.addAndGet(count);
        // Custom logging or external metrics reporting
        logger.info("Cache hits recorded: {}", count);
    }
    
    @Override
    public void recordMisses(int count) {
        misses.addAndGet(count);
        // Alert on high miss rates
        long totalRequests = hits.get() + misses.get();
        if (totalRequests > 100 && (double) misses.get() / totalRequests > 0.9) {
            alertingService.sendAlert("High cache miss rate detected");
        }
    }
    
    @Override
    public void recordLoadSuccess(long loadTime) {
        loadSuccesses.incrementAndGet();
        totalLoadTime.addAndGet(loadTime);
        // Track slow loads
        if (loadTime > TimeUnit.SECONDS.toNanos(1)) {
            logger.warn("Slow cache load detected: {}ms", TimeUnit.NANOSECONDS.toMillis(loadTime));
        }
    }
    
    @Override
    public void recordLoadFailure(long loadTime) {
        loadFailures.incrementAndGet();
        totalLoadTime.addAndGet(loadTime);
        logger.error("Cache load failure after {}ms", TimeUnit.NANOSECONDS.toMillis(loadTime));
    }
    
    @Override
    public void recordEviction(int weight, RemovalCause cause) {
        evictions.incrementAndGet();
        evictionWeight.addAndGet(weight);
        metricsRegistry.counter("cache.evictions", "cause", cause.name()).increment();
    }
    
    @Override
    public CacheStats snapshot() {
        return CacheStats.of(
            hits.get(),
            misses.get(), 
            loadSuccesses.get(),
            loadFailures.get(),
            totalLoadTime.get(),
            evictions.get(),
            evictionWeight.get()
        );
    }
}

// Use custom stats counter
Cache<String, String> customStatsCache = Caffeine.newBuilder()
    .maximumSize(1000)
    .recordStats(CustomStatsCounter::new)
    .build();

Statistics Monitoring and Alerting

Periodic Statistics Collection

Cache<String, String> monitoredCache = Caffeine.newBuilder()
    .maximumSize(10000)
    .expireAfterAccess(Duration.ofMinutes(30))
    .recordStats()
    .build();

ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

scheduler.scheduleAtFixedRate(() -> {
    CacheStats stats = monitoredCache.stats();
    
    // Log key metrics
    System.out.printf("Cache Stats - Requests: %d, Hit Rate: %.2f%%, " +
                     "Avg Load Time: %.2fms, Evictions: %d%n",
                     stats.requestCount(),
                     stats.hitRate() * 100,
                     stats.averageLoadPenalty() / 1_000_000.0,
                     stats.evictionCount());
    
    // Send metrics to monitoring system
    metricsCollector.gauge("cache.hit_rate", stats.hitRate());
    metricsCollector.gauge("cache.miss_rate", stats.missRate());
    metricsCollector.gauge("cache.avg_load_time_ms", stats.averageLoadPenalty() / 1_000_000.0);
    metricsCollector.counter("cache.evictions").increment(stats.evictionCount());
    
}, 0, 30, TimeUnit.SECONDS);

Performance Analysis

public class CachePerformanceAnalyzer {
    public void analyzePerformance(Cache<?, ?> cache) {
        CacheStats stats = cache.stats();
        
        // Hit rate analysis
        double hitRate = stats.hitRate();
        if (hitRate < 0.8) {
            System.out.println("WARNING: Low hit rate (" + (hitRate * 100) + "%)");
            System.out.println("Consider: increasing cache size, adjusting expiration, or reviewing access patterns");
        }
        
        // Load time analysis
        double avgLoadTimeMs = stats.averageLoadPenalty() / 1_000_000.0;
        if (avgLoadTimeMs > 100) {
            System.out.println("WARNING: High average load time (" + avgLoadTimeMs + "ms)");
            System.out.println("Consider: optimizing data loading, adding async loading, or caching strategies");
        }
        
        // Load failure analysis
        double loadFailureRate = stats.loadFailureRate();
        if (loadFailureRate > 0.05) {
            System.out.println("WARNING: High load failure rate (" + (loadFailureRate * 100) + "%)");
            System.out.println("Consider: improving error handling, adding retry logic, or fallback values");
        }
        
        // Eviction analysis
        long evictionCount = stats.evictionCount();
        long requestCount = stats.requestCount();
        if (requestCount > 0 && evictionCount > requestCount * 0.1) {
            System.out.println("WARNING: High eviction rate (" + evictionCount + " evictions)");
            System.out.println("Consider: increasing cache size, adjusting expiration, or using weight-based eviction");
        }
        
        // Overall health summary
        System.out.printf("Cache Health Summary:%n" +
                         "  Requests: %d%n" +
                         "  Hit Rate: %.1f%%%n" +
                         "  Avg Load Time: %.1fms%n" +
                         "  Load Failures: %d (%.1f%%)%n" +
                         "  Evictions: %d%n",
                         requestCount,
                         hitRate * 100,
                         avgLoadTimeMs,
                         stats.loadFailureCount(),
                         loadFailureRate * 100,
                         evictionCount);
    }
}

Statistics Best Practices

When to Enable Statistics

// Enable for production monitoring
Cache<String, String> productionCache = Caffeine.newBuilder()
    .maximumSize(10000)
    .recordStats()  // Small overhead, valuable insights
    .build();

// Disable for high-frequency, low-value caches
Cache<String, String> highFreqCache = Caffeine.newBuilder()
    .maximumSize(1000)
    // No recordStats() - avoid overhead for simple caches
    .build();

// Use custom counter for specialized monitoring
Cache<String, String> specializedCache = Caffeine.newBuilder()
    .maximumSize(5000)
    .recordStats(() -> new CustomMetricsStatsCounter(metricsRegistry))
    .build();

Performance Considerations

  • Overhead: Statistics collection adds minimal overhead (typically <2% performance impact)
  • Memory: Statistics counters use additional memory (typically 8-16 bytes per cache)
  • Concurrency: ConcurrentStatsCounter provides excellent concurrent performance using LongAdder
  • Custom Counters: Expensive operations in custom counters can impact cache performance

Install with Tessl CLI

npx tessl i tessl/maven-com-github-ben-manes-caffeine--caffeine

docs

asynchronous-caching.md

cache-construction.md

cache-policies.md

functional-interfaces.md

index.md

statistics.md

synchronous-caching.md

tile.json