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

Как долго действует кэш первого уровня

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

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

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

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

# Как долго действует кэш первого уровня (L1 Cache)

Кэш первого уровня (L1 Cache) в контексте ORM (например, Hibernate) и JVM - это очень быстрая, но очень короткоживущая память. Рассмотрим оба контекста.

1. L1 Cache в Hibernate (Session/Persistence Context)

Время жизни: Весь lifecycle Session

@Autowired
private SessionFactory sessionFactory;

public void demonstrateL1Cache() {
    Session session = sessionFactory.openSession();
    
    try {
        // Кэш L1 существует с момента открытия Session
        User user1 = session.get(User.class, 1L);
        System.out.println("Первый запрос - SELECT из БД");
        
        // Этот запрос не пойдет в БД - вернет из L1
        User user2 = session.get(User.class, 1L);
        System.out.println("Второй запрос - из L1 Cache (нет SQL)");
        
        // Проверка: это один объект?
        System.out.println(user1 == user2);  // true - один объект!
        
        user1.setName("Updated");
        session.flush();  // Обновление пойдет в БД
        
    } finally {
        session.close();  // L1 Cache УНИЧТОЖАЕТСЯ здесь
    }
    
    // L1 Cache более не существует
}

Время жизни L1 Cache в Spring:

@Transactional
public void transactionLevel() {
    User user1 = userRepository.findById(1L);   // SELECT
    User user2 = userRepository.findById(1L);   // ИЗ КЭША
}  // transaction.commit() -> Session закрывается -> L1 Cache очищается

User user3 = userRepository.findById(1L);      // Новая транзакция, новая Session
                                                // SELECT снова

2. L1 Cache в JVM (CPU уровне): Микросекунды

Это совсем другое понимание L1 Cache - это аппаратный кэш.

Типичные латенси:
├── L1 Cache (CPU)       4 цикла (~ 1-4 наносекунды)
├── L2 Cache (CPU)       10 цикла (~ 3-10 наносекунд)
├── L3 Cache (CPU)       40 цикла (~ 10-40 наносекунд)
├── RAM                  200 цикла (~ 60-100 наносекунд)
├── SSD                  150,000 цикла (~ 50 микросекунд)
├── HDD                  10,000,000 цикла (~ 5 миллисекунд)
└── Network              100,000,000+ цикла (~ 100+ миллисекунд)

Этот кэш НЕ контролируется программистом.

3. Сравнение разных типов кэшей

// ❌ Неправильно смешивать понятия
// L1 Cache может означать:
// 1. Hibernate L1 Cache (Session) — ВРЕМЯ ЖИЗНИ: одна транзакция
// 2. JVM L1 Cache (CPU) — ВРЕМЯ ЖИЗНИ: наносекунды (не контролируется)

public class CacheComparison {
    // Вариант 1: Hibernate L1 (Session level)
    @Transactional
    public void hibernateL1() {
        User u1 = userRepository.findById(1L);  // SELECT
        User u2 = userRepository.findById(1L);  // Cache hit (одна Session)
    }  // Session close -> Cache clear
    
    // Вариант 2: Spring Cache (L2 level)
    @Cacheable("users")
    public User getUserWithCache(Long id) {
        return userRepository.findById(id).orElse(null);  // SELECT только первый раз
        // Потом возвращает из Redis/Caffeine
    }
    
    // Вариант 3: CPU Cache (микросекунды - не контролируем)
    public int sumArray(int[] arr) {
        int sum = 0;
        for (int num : arr) {
            sum += num;  // num может быть в L1 CPU Cache
        }
        return sum;
    }
}

4. Практический пример: L1 Cache в Hibernate

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    
    @Transactional
    public void demonstrateL1() {
        // Вся транзакция = одна Session = один L1 Cache
        
        User user1 = userRepository.findById(1L);
        user1.setActive(true);
        
        // Один и тот же объект
        User user2 = userRepository.findById(1L);
        System.out.println(user1 == user2);  // true
        System.out.println(user2.isActive()); // true (видит изменение от user1)
        
        // user2 изменится, как и user1 - это один объект
        user2.setEmail("new@example.com");
        
    } // flush() -> commit() -> Session close -> L1 clear
}

// Следующая транзакция = новая Session = новый L1 Cache
@Transactional
public void nextTransaction() {
    User user3 = userRepository.findById(1L);  // SELECT - L1 Cache новой Session
    // user3 == user1 ? FALSE - разные объекты в разных Sessions
}

5. Проблемы с L1 Cache

Проблема 1: LazyInitializationException

@Transactional
public void initializeOutside() {
    User user = userRepository.findById(1L);
    // user в L1 Cache этой Session
}
// Session close -> L1 очищен

// Попытка доступа к lazy-loaded полю
System.out.println(user.getProfile().getName());  // ❌ LazyInitializationException!
// Profile было lazy-loaded, но L1 Session уже закрыта

Решение:

@Transactional
public void initializeInside() {
    User user = userRepository.findById(1L);
    // Инициализируем ленивые поля ДО закрытия Session
    user.getProfile().getName();  // ✅ Работает
}

Проблема 2: Memory Leak

@Transactional
public void memoryLeak() {
    List<User> users = new ArrayList<>();
    for (int i = 0; i < 1000000; i++) {
        User user = userRepository.findById((long)i);
        users.add(user);  // ❌ Все объекты в L1 Cache + список
    }
    // L1 Cache содержит 1 миллион объектов в памяти!
}

Решение:

@Transactional
public void noClear() {
    for (int i = 0; i < 1000000; i++) {
        User user = userRepository.findById((long)i);
        processUser(user);
        
        // Очищаем L1 Cache каждые 100 объектов
        if (i % 100 == 0) {
            entityManager.clear();  // L1 Cache clear
        }
    }
}

6. Управление L1 Cache

Явное очищение L1 Cache

@Autowired
private EntityManager entityManager;

@Transactional
public void clearL1Cache() {
    User user1 = userRepository.findById(1L);
    user1.setName("Alice");
    
    // Очищаем L1 Cache
    entityManager.clear();  // Все объекты выгружены из кэша
    
    // Следующий findById пойдет в БД
    User user2 = userRepository.findById(1L);
    user1 == user2  // false - разные объекты
    user2.getName() // "John" - старое значение из БД
}

Detach объект из L1 Cache

@Transactional
public void detachObject() {
    User user = userRepository.findById(1L);
    user.setName("Alice");
    
    entityManager.detach(user);  // Удаляем только этот объект из L1
    
    user.setName("Bob");  // Изменение НЕ будет сохранено
}  // flush() не вызовет UPDATE для user

7. Время жизни L1 Cache по транзакциям

Трансакция 1:
├─ Session open
├─ L1 Cache create
├─ Запросы и операции (кэш активен)
└─ Session close -> L1 Cache destroy

Трансакция 2:  (новая Session!)
├─ Session open
├─ L1 Cache create (новый!)
├─ Запросы и операции
└─ Session close -> L1 Cache destroy

8. Сравнение L1 и L2 Cache

ПараметрL1 Cache (Session)L2 Cache (Second-level)
Время жизни1 транзакцияВесь ApplicationContext
Область видимости1 SessionНесколько Sessions
Размер~10-100 MB~100-500 MB (configurable)
ТипMap (в памяти)Redis/Memcached/EhCache
ИнвалидацияАвтоматическаяРучная или TTL
КонтрольАвтоматическийНужна конфигурация

9. Настройка L1 Cache

# application.yml
spring:
  jpa:
    properties:
      hibernate:
        jdbc:
          batch_size: 20
          fetch_size: 50
        order_inserts: true
        order_updates: true

Итоговый ответ

В контексте ORM (Hibernate): L1 Cache действует в течение всего жизненного цикла Session, которая обычно соответствует одной транзакции в Spring приложении. После закрытия транзакции L1 Cache полностью очищается.

В контексте JVM (CPU): L1 Cache действует в течение нескольких наносекунд и полностью контролируется процессором, не программистом.

Практически: L1 Cache вашего приложения = одна @Transactional метода.