Що таке кеш другого рівня і коли його використовувати
Кеш другого рівня (L2 cache) — це кеш на рівні EntityManagerFactory, який розділяється між усіма сесіями. На відміну від L1 cache, він переживає закриття сесій і може використов...
Огляд
Кеш другого рівня (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]]