← Назад к вопросам

Когда стоит использовать Caffeine?

2.0 Middle🔥 111 комментариев
#Кэширование и NoSQL

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Когда использовать Caffeine кэш

Caffeine — это быстрая, легковесная in-memory кэш библиотека для Java. Это замена устаревшей Guava Cache с лучшей производительностью и современным API.

Основные случаи использования

1. Кэширование результатов тяжёлых вычислений

Если операция дорогая по CPU — кэшируй результат:

@Component
public class AnalyticsService {
    private final Cache<String, AnalyticsResult> cache = 
        Caffeine.newBuilder()
            .maximumSize(1000)
            .expireAfterWrite(10, TimeUnit.MINUTES)
            .build();
    
    public AnalyticsResult getReport(String reportId) {
        // Если в кэше — вернёт сразу
        // Если нет — выполнит функцию один раз, кэширует
        return cache.get(reportId, key -> {
            System.out.println("Computing expensive report...");
            return expensiveComputation(key);
        });
    }
}

Пример: вычисление итоговой статистики за месяц. Вместо SQL запроса с GROUP BY в 5 секунд — кэш возвращает за 1ms.

2. Результаты запросов к БД (читаемые данные)

Для часто запрашиваемых данных, которые меняются редко:

@Repository
public class UserRepository {
    private final Cache<Long, User> userCache = 
        Caffeine.newBuilder()
            .maximumSize(10000)  // макс 10k пользователей
            .expireAfterAccess(30, TimeUnit.MINUTES) // если не обращались 30 мин — выкидываем
            .recordStats() // собирать статистику попаданий
            .build();
    
    public User findById(Long id) {
        return userCache.get(id, userId -> 
            sqlRepository.findById(userId).orElse(null)
        );
    }
}

Когда это работает:

  • 80% запросов — к одним и тем же пользователям
  • БД не меняется часто
  • Network latency > процессор

3. Throttling и Rate Limiting

Кэш можно использовать для подсчёта количества операций:

@Component
public class RateLimiter {
    private final Cache<String, AtomicInteger> requestCounts = 
        Caffeine.newBuilder()
            .expireAfterWrite(1, TimeUnit.MINUTES)
            .build();
    
    public boolean allowRequest(String userId) {
        AtomicInteger count = requestCounts.get(userId, key -> new AtomicInteger(0));
        count.incrementAndGet();
        return count.get() <= 100; // макс 100 запросов в минуту
    }
}

4. Кэширование конфигурации

Данные, которые загружаются редко и меняются редко:

@Component
public class ConfigService {
    private final Cache<String, SystemConfig> configCache = 
        Caffeine.newBuilder()
            .maximumSize(100)
            .expireAfterWrite(1, TimeUnit.HOURS)
            .build();
    
    public SystemConfig getConfig(String environment) {
        return configCache.get(environment, env -> loadFromDb(env));
    }
}

Когда НЕ использовать

Неправильно: огромное количество ключей

// ПЛОХО: если кэшировать миллион разных запросов
// Память переполнится
Cache<String, QueryResult> cache = Caffeine.newBuilder()
    .maximumSize(Long.MAX_VALUE) // опасно!
    .build();

for (int i = 0; i < 1_000_000; i++) {
    cache.get("query_" + i, key -> executeQuery(key));
}

Решение: используй Redis для распределённого кэша или ограничь размер.

Неправильно: часто меняющиеся данные

// ПЛОХО: кэш всегда будет невалидным
Cache<Long, User> cache = Caffeine.newBuilder()
    .expireAfterWrite(1, TimeUnit.SECONDS) // слишком коротко
    .build();

// Пользователь обновляется, но кэш ещё на 1 сек старый
user.setName("New Name");
userRepository.save(user); // БД обновлена

// А кэш вернёт старое имя
User cachedUser = cache.get(1L, ...);

Неправильно: критично-консистентные данные

// ПЛОХО: финансовые данные должны быть актуальны
Cache<Long, Account> cache = Caffeine.newBuilder()
    .expireAfterWrite(5, TimeUnit.MINUTES) // слишком долго
    .build();

// Баланс кэширован 5 минут назад
// А в БД уже три переводов
Account account = cache.get(accountId, ...);
BigDecimal balance = account.getBalance(); // может быть неправильно

Caffeine vs Redis

ПараметрCaffeineRedis
РазмещениеIn-memory в JVMОтдельный сервис
СкоростьНаибыстрее (нет network)Медленнее на ~1ms
МасштабОдин серверРаспределённо
ПерсистентностьНетОпционально
Использование памятиСокращает нагрузку на БДТребует отдельный сервер

Правило: Caffeine для локального кэша одного сервера, Redis если нужно между микросервисами.

Настройка Caffeine

Размер и вытеснение

Cache<String, String> cache = Caffeine.newBuilder()
    .maximumSize(1000)              // LRU вытеснение (макс 1000 элементов)
    .maximumWeight(10_000)          // или по весу
    .weigher((key, value) -> value.getBytes().length) // вес одного элемента
    .build();

TTL (Time To Live)

Cache<String, String> cache = Caffeine.newBuilder()
    .expireAfterWrite(10, TimeUnit.MINUTES)   // через 10 мин удалить
    .expireAfterAccess(5, TimeUnit.MINUTES)   // если 5 мин не обращались — удалить
    .refreshAfterWrite(1, TimeUnit.MINUTES)   // асинхронно обновлять каждую минуту
    .build();

Статистика и мониторинг

Cache<Long, User> cache = Caffeine.newBuilder()
    .recordStats() // включить сбор метрик
    .build();

// Позже
CacheStats stats = cache.stats();
System.out.println("Hit rate: " + stats.hitRate());      // вероятность попадания
System.out.println("Eviction: " + stats.evictionCount()); // сколько элементов выкинули
System.out.println("Load time: " + stats.averageLoadPenalty()); // среднее время загрузки

Интеграция со Spring

@Configuration
public class CacheConfig {
    @Bean
    public CacheManager cacheManager() {
        return new CaffeineCacheManager();
    }
}

@Service
public class UserService {
    @Cacheable(value = "users", key = "#id")
    public User getUser(Long id) {
        return repository.findById(id).orElse(null);
    }
    
    @CacheEvict(value = "users", key = "#id")
    public void updateUser(Long id, User user) {
        repository.save(user);
    }
}

Практический пример

@Component
public class ProductPriceCache {
    private final Cache<String, BigDecimal> priceCache;
    private final PricingService pricingService;
    
    public ProductPriceCache(PricingService pricingService) {
        this.pricingService = pricingService;
        this.priceCache = Caffeine.newBuilder()
            .maximumSize(5000)  // 5000 товаров
            .expireAfterWrite(15, TimeUnit.MINUTES) // цена актуальна 15 мин
            .recordStats()
            .build();
    }
    
    public BigDecimal getPrice(String productId) {
        return priceCache.get(productId, key -> 
            pricingService.fetchPrice(key)
        );
    }
    
    public void invalidatePrice(String productId) {
        priceCache.invalidate(productId);
    }
    
    public void printStats() {
        CacheStats stats = priceCache.stats();
        System.out.printf("Hits: %d, Misses: %d, Hit rate: %.2f%%",
            stats.hitCount(),
            stats.missCount(),
            stats.hitRate() * 100);
    }
}

Чеклист: использовать ли Caffeine

✅ Используй если:

  • Есть тяжёлые SQL запросы или вычисления
  • Данные не меняются часто (или изменяются предсказуемо)
  • Нужна низкая latency (< 1ms)
  • Размер кэша < несколько GB
  • Работаешь с одним сервером

❌ Не используй если:

  • Данные должны быть consistency критичны (финансы)
  • Много разных ключей (> 100k в час)
  • Нужен кэш между микросервисами (используй Redis)
  • Нет давления на производительность

Вывод: Caffeine — это фундамент оптимизации production систем. Используй её умело: там, где она даст +30% в throughput, не везде подряд.