Вопрос 11 · Раздел 16

Как настроить кэш второго уровня

Настройка кэша второго уровня включает выбор провайдера, конфигурацию Hibernate, настройку регионов кэша и определение стратегий кэширования для каждой сущности.

Версии по языкам: English Russian Ukrainian

Обзор

Настройка кэша второго уровня включает выбор провайдера, конфигурацию Hibernate, настройку регионов кэша и определение стратегий кэширования для каждой сущности.


🟢 Junior Level

Базовая настройка

Для настройки L2 cache нужно выполнить 3 шага:

  1. Включить кэш в конфигурации
  2. Выбрать провайдер (Ehcache, Hazelcast, Redis)
  3. Пометить сущности аннотацией @Cacheable
# application.yml
spring:
  jpa:
    properties:
      hibernate:
        cache:
          use_second_level_cache: true
          region:
            factory_class: org.hibernate.cache.jcache.JCacheRegionFactory
      javax.persistence.sharedCache.mode: ENABLE_SELECTIVE
@Entity
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class User {
    @Id
    private Long id;
    private String name;
}

Режимы кэширования

# Все сущности кэшируются по умолчанию
javax.persistence.sharedCache.mode: ALL

# Только помеченные @Cacheable (рекомендуется)
javax.persistence.sharedCache.mode: ENABLE_SELECTIVE

# Все кроме помеченных @Cacheable(false)
javax.persistence.sharedCache.mode: DISABLE_SELECTIVE

# Никакие (кэш отключён)
javax.persistence.sharedCache.mode: NONE

🟡 Middle Level

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

  1. Write-heavy workload — постоянная инвалидация кэша дороже чем SELECT из БД
  2. Высоко нормализованные данные с частыми JOIN — кэш неэффективен
  3. Многоузловой кластер с частыми обновлениями — invalidation traffic превышает пользу

Настройка Ehcache

<!-- ehcache.xml в src/main/resources -->
<config xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
        xmlns='http://www.ehcache.org/v3'>

    <cache alias="com.example.User">
        <heap unit="entries">1000</heap>
        <offheap unit="MB">50</offheap>
        <expiry>
            <ttl unit="minutes">30</ttl>
        </expiry>
    </cache>

    <cache alias="com.example.Country">
        <heap unit="entries">500</heap>
        <expiry>
            <ttl unit="hours">24</ttl>
        </expiry>
    </cache>
</config>

Управление кэшем программно

Cache cache = entityManager.getEntityManagerFactory().getCache();

// Evict конкретную сущность
cache.evict(User.class, userId);

// Evict все сущности типа
cache.evict(User.class);

// Evict всё
cache.evictAll();

Коллекции в кэше

@Entity
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class User {

    @OneToMany(mappedBy = "user")
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
    private List<Order> orders;  // кэширование коллекции
}

Типичные ошибки

// ❌ Нет TTL — кэш растёт бесконечно
// Решение: всегда задавайте TTL в конфигурации провайдера

// ❌ Кэш для всех сущностей
// Решение: кэшируйте только часто читаемые, редко изменяемые

// ❌ Неправильная стратегия
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public class Order { }  // ❌ Orders часто меняются

🔴 Senior Level

Кластерный кэш

# Hazelcast для кластера
spring:
  jpa:
    properties:
      hibernate:
        cache:
          region:
            factory_class: com.hazelcast.hibernate.HazelcastCacheRegionFactory

# Redis
spring:
  jpa:
    properties:
      hibernate:
        cache:
          region:
            factory_class: com.redislabs.hibernate.redisson.cache.impl.RedissonRegionFactory
Проблемы кластерного кэша:
- Eventual consistency (не strong) — данные в разных узлах кластера станут одинаковыми не мгновенно, а через некоторое время. В этот момент один узел может видеть старые данные, другой — новые.
- Network latency при репликации
- Конфликты при одновременном обновлении
- Необходимость стратегии инвалидации кэша

Настройка регионов

@Entity
@Cacheable
@org.hibernate.annotations.Cache(
    usage = CacheConcurrencyStrategy.READ_WRITE,
    region = "userCache"  // имя региона
)
public class User { }
<!-- Разные настройки для разных регионов -->
<cache alias="userCache">
    <heap>5000</heap>
    <expiry><ttl unit="minutes">15</ttl></expiry>
</cache>

<cache alias="countryCache">
    <heap>500</heap>
    <expiry><ttl unit="hours">48</ttl></expiry>
</cache>

Мониторинг в production

Statistics stats = entityManagerFactory
    .unwrap(SessionFactory.class)
    .getStatistics();

// Метрики L2 cache
long hitCount = stats.getSecondLevelCacheHitCount();
long missCount = stats.getSecondLevelCacheMissCount();
long putCount = stats.getSecondLevelCachePutCount();
long evictionCount = stats.getSecondLevelCacheEvictionCount();

double hitRatio = (double) hitCount / (hitCount + missCount);

// Логирование
log.info("L2 Cache: hits={}, misses={}, ratio={:.2f}",
    hitCount, missCount, hitRatio);

// hitRatio > 0.8 обычно хорошо для read-heavy нагрузки; для write-heavy workload даже 0.5 может быть нормой.

Query Cache

spring:
  jpa:
    properties:
      hibernate:
        cache:
          use_query_cache: true
@QueryHint(name = "org.hibernate.cacheable", value = "true")
@Query("SELECT u FROM User u WHERE u.status = :status")
List<User> findByStatus(@Param("status") String status);

// Важно: query cache хранит только ID сущностей
// Сами сущности берутся из L2 cache

Best Practices

✅ TTL для каждого region
✅ Heap limit для предотвращения memory leak
✅ Кластерный кэш для multi-node приложений
✅ Мониторинг hit ratio
✅ ENABLE_SELECTIVE mode
✅ READ_ONLY для справочников

❌ Без TTL и лимитов
❌ Кэш для часто меняющихся данных
❌ Без мониторинга hit ratio
❌ Query cache без L2 cache
❌ INVALIDATE_ALL при каждом изменении

🎯 Шпаргалка для интервью

Обязательно знать:

  • 3 шага настройки: включить кэш, выбрать провайдера, пометить сущности @Cacheable
  • Режимы: ENABLE_SELECTIVE (рекомендуется), ALL, DISABLE_SELECTIVE, NONE
  • Стратегии кэширования: READ_ONLY для справочников, READ_WRITE для изменяемых данных
  • Настройка Ehcache: heap size, offheap, TTL для каждого региона
  • Кластерный кэш: Hazelcast/Redis, eventual consistency, network overhead
  • Query cache бесполезен без L2 cache — хранит только ID сущностей

Частые уточняющие вопросы:

  • Какой режим кэширования выбрать? ENABLE_SELECTIVE — только помеченные @Cacheable, лучший контроль
  • Как настроить разные TTL для разных сущностей? Через регионы: region = “userCache” и разные настройки в ehcache.xml
  • Что происходит при обновлении сущности в кластере? Invalidation message рассылается на все ноды, next запрос — miss
  • Как мониторить в production? Statistics: hitCount, missCount, hitRatio, evictionCount

Красные флаги (НЕ говорить):

  • «Включаю кэш без TTL» — memory leak
  • «READ_ONLY для Order» — заказы часто меняются
  • «Query cache без L2 cache» — бесполезен
  • «ENABLE_SELECTIVE но не помечаю @Cacheable» — кэш не работает

Связанные темы:

  • [[10. Что такое кэш второго уровня и когда его использовать]]
  • [[9. Что такое кэш первого уровня в Hibernate]]
  • [[12. Что такое dirty checking в Hibernate]]
  • [[1. Что такое проблема N+1 и как её решить]]