Когда стоит использовать Caffeine?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Когда использовать 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
| Параметр | Caffeine | Redis |
|---|---|---|
| Размещение | 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, не везде подряд.