Питання 10 · Розділ 16

Що таке кеш другого рівня і коли його використовувати

Кеш другого рівня (L2 cache) — це кеш на рівні EntityManagerFactory, який розділяється між усіма сесіями. На відміну від L1 cache, він переживає закриття сесій і може використов...

Мовні версії: English Russian Ukrainian

Огляд

Кеш другого рівня (L2 cache) — це кеш на рівні EntityManagerFactory, який розділяється між усіма сесіями. На відміну від L1 cache, він переживає закриття сесій і може використовуватися кількома транзакціями.

Навіщо: коли 100 користувачів одночасно запитують один і той самий довідник (наприклад, список країн), L1 cache марний — у кожної сесії свій кеш. L2 cache завантажить країни ОДИН раз і поділить між усіма.


🟢 Junior Level

Що таке L2 cache

Кеш другого рівня — це кеш, який розділяється між сесіями. Якщо одна сесія завантажила сутність, інша сесія може взяти її з L2 cache без звернення до БД.

Сесія 1: завантажує User(1) → L1 cache + L2 cache
Сесія 1: закрита → L1 cache видалено, L2 cache залишився
Сесія 2: завантажує User(1) → L2 cache (БЕЗ БД!)

L1 vs L2 cache

Характеристика L1 Cache L2 Cache
Рівень EntityManager EntityManagerFactory
За замовчуванням ✅ Увімкнений ❌ Потрібно увімкнути
Життєвий цикл Одна сесія Весь додаток
Розподіл Ні Між сесіями
Провайдер Вбудований Ehcache, Hazelcast, Redis

Коли L2 cache корисний

✅ Підходить коли:

  • Дані рідко змінюються (довідники, країни, валюти)
  • Багато читань, мало записів
  • Кілька екземплярів додатку

❌ Не підходить коли:

  • Дані часто змінюються (замовлення, логи)
  • Потрібна абсолютна актуальність даних
  • Більше записів ніж читань

🟡 Middle Level

Коли НЕ використовувати L2 cache

L2 cache НЕ працює з:

  • Native SQL queries (Hibernate не знає які сутності зачеплені)
  • Query cache для пагінації (кешується окремо)
  • EntityManager.clear() не чистить L2 cache

Конфігурація

<!-- Залежність для JCache провайдера -->
<dependency>
    <groupId>org.hibernate.orm</groupId>
    <artifactId>hibernate-jcache</artifactId>
</dependency>
# 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

В Hibernate 5.x використовується javax.persistence. В Hibernate 6+ / Jakarta EE замініть на jakarta.persistence та уточніть шляхи до factory classes.

// Позначення сутності для кешування
@Entity
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class User {
    @Id
    private Long id;
    private String name;
}

Стратегії кешування

READ_ONLY:
- Тільки читання, дані ніколи не змінюються
- Найшвидша стратегія
- Для довідників, країн, валют

READ_WRITE:
- Читання і запис з синхронізацією
- Використовує soft locks
- Для помірно змінюваних даних

NONSTRICT_READ_WRITE:
- Читання і запис без строгої синхронізації
- Може повернути застарілі дані
- Для рідко змінюваних даних

TRANSACTIONAL:
- Повна транзакційна узгодженість
- Вимагає XA транзакцій
- Для критично важливих даних

Управління кешем

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

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

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

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

Типові помилки

// ❌ Кеш для даних що часто змінюються
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public class Order { }
// Orders часто змінюються → застарілі дані!

// ❌ Немає TTL
// Кеш росте нескінченно → memory leak
// Рішення: завжди задавайте TTL

🔴 Senior Level

Soft locks — м’яке блокування: Hibernate позначає запис в кеші як «оновлюється», інші потоки чекають. На відміну від жорсткого блокування БД, це не блокує рядок в таблиці.

XA транзакції — двофазні транзакції, що забезпечують атомарність операцій і в БД, і в кеші.

Eventual consistency — узгодженість в кінцевому підсумку: після оновлення дані в кеші на різних нодах стануть однаковими не миттєво, а через деякий час.

Invalidation механізм

L2 cache invalidation відбувається при:
- UPDATE/DELETE сутності → eviction з кешу
- Явний evict() → видалення з кешу
- TTL expiration → автоматичне видалення
- Cluster replication → реплікація між нодами

Кластерний кеш

# Hazelcast L2 cache для кластера
spring:
  jpa:
    properties:
      hibernate:
        cache:
          region:
            factory_class: com.hazelcast.hibernate.HazelcastCacheRegionFactory
Для кількох інстансів додатку:
- Реплікація кешу між нодами (Hazelcast, Redis)
- Консистентність eventual (не strong)
- Network overhead при реплікації
- Потрібно враховувати при проектуванні

При оновленні User на Node 1: (1) Hibernate оновлює БД;
(2) invalidates запис в L2 cache Node 1;
(3) Hazelcast розсилає invalidation message на Node 2, 3;
(4) Next запит на Node 2 промахується в кеш і завантажує з БД.

Production моніторинг

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

// L2 cache метрики
long hitCount = stats.getSecondLevelCacheHitCount();
long missCount = stats.getSecondLevelCacheMissCount();
double hitRatio = (double) hitCount / (hitCount + missCount);

// Для production моніторингу
// hitRatio > 0.8 — відмінно, кеш ефективно використовується
// hitRatio < 0.5 — перевірте: чи правильно обрані сутності для кешування, чи достатній розмір кешу, чи не занадто малий TTL

Query cache vs L2 cache

Query cache зберігає: «запит X з параметрами Y повернув сутності з ID [1,2,3]». L2 cache зберігає: «сутність User(1) = {name: John, …}». Вони працюють разом: Query cache дає ID, L2 cache дає дані. Query cache марний без L2 cache.

Best Practices

✅ READ_ONLY для довідників і констант
✅ READ_WRITE для помірно змінюваних даних
✅ Моніторте hit/miss ratio
✅ Задавайте TTL для кожного region
✅ Обмежуйте heap size
✅ Invalidacja при оновленнях
✅ Кластерний кеш для multi-node

❌ Для даних що часто змінюються
❌ Без моніторингу hit ratio
❌ Без TTL та лімітів пам'яті
❌ Кеш для сутностей з N+1 проблемою
❌ Використання як заміна нормальної архітектури

🎯 Шпаргалка для співбесіди

Обов’язково знати:

  • L2 cache — на рівні EntityManagerFactory, розділяється між сесіями
  • Потрібно увімкнути: обрати провайдера (Ehcache, Hazelcast, Redis), налаштувати конфігурацію
  • Стратегії: READ_ONLY (довідники), READ_WRITE (помірно змінювані), NONSTRICT_READ_WRITE, TRANSACTIONAL
  • Корисний для: рідко змінюваних даних, багато читань/мало записів, довідників
  • Query cache зберігає ID сутностей, самі дані — з L2 cache (марний без L2)
  • Моніторинг: hitRatio > 0.8 відмінно, < 0.5 — перевірте конфігурацію

Часті уточнюючі запитання:

  • L1 vs L2 cache? L1 — в рамках сесії (завжди увімкнений), L2 — між сесіями (потрібно увімкнути)
  • Коли L2 cache НЕ працює? Native SQL queries, query cache для пагінації, після EntityManager.clear()
  • Як працює кластерний кеш? Реплікація між нодами (Hazelcast, Redis), eventual consistency
  • Що таке soft locks? М’яке блокування — Hibernate позначає запис як «оновлюється», інші потоки чекають

Червоні прапорці (НЕ говорити):

  • «Кешую всі сутності подряд» — потрібно кешувати тільки read-heavy
  • «Без TTL — кеш росте нескінченно» — memory leak
  • «L2 cache для Order/Log» — дані що часто змінюються, кеш неефективний
  • «Query cache без L2 cache» — марний, зберігає тільки ID

Пов’язані теми:

  • [[9. Що таке кеш першого рівня в Hibernate]]
  • [[11. Як налаштувати кеш другого рівня]]
  • [[1. Що таке проблема N+1 і як її вирішити]]
  • [[13. Як працює механізм flush в Hibernate]]