Gymterview
middle

Какие стратегии инвалидации кэша существуют?

Инвалидация кэша — это процесс удаления или обновления устаревших данных в кэше, являющийся самой сложной частью кэширования.

Аналогия из жизни: инвалидация кэша — как обновление расписания на информационном стенде. Если расписание изменилось, но стенд не обновили, люди будут приходить в неправильное время. Вопрос в том, как и когда обновлять стенд.

1. TTL (Time-To-Live)

Самая простая стратегия — данные автоматически удаляются через заданное время.

Пример
@Cacheable(value = "users", key = "#id") // TTL задаётся в конфигурации CacheManager
public User findById(Long id) { ... }
Пример
SET user:1 '{"name":"John"}' EX 600  # удалится через 10 минут

Плюсы: простота; гарантия, что данные не старше TTL. Минусы: в пределах TTL данные могут быть устаревшими; после TTL — cache miss.

2. Явная инвалидация (Event-Driven)

При обновлении данных явно удаляем/обновляем кэш.

Пример
@CacheEvict(value = "users", key = "#id")
public void updateUser(Long id, UpdateRequest request) {
    userRepository.save(toEntity(id, request));
}

// Или через события (в микросервисах)
@TransactionalEventListener
public void onUserUpdated(UserUpdatedEvent event) {
    cacheManager.getCache("users").evict(event.getUserId());
}

В микросервисах сервис публикует событие в Kafka, потребители инвалидируют свои кэши:

Пример кода
// Сервис A: обновил пользователя → опубликовал событие
@Transactional
public void updateUser(Long id, UpdateRequest request) {
    userRepository.save(toEntity(id, request));
    kafkaTemplate.send("user-events", new UserUpdatedEvent(id));
}

// Сервис B: получил событие → инвалидировал свой кэш
@KafkaListener(topics = "user-events")
public void onUserEvent(UserUpdatedEvent event) {
    cacheManager.getCache("users").evict(event.getUserId());
}

3. Write-Through / Write-Behind

Кэш обновляется одновременно с БД (Write-Through) или асинхронно (Write-Behind). Подробнее см. раздел “Паттерны кэширования”.

4. Version-based (ETag)

Каждая запись имеет версию. При чтении из кэша проверяется, не изменилась ли версия в БД.

Пример
// Быстрая проверка версии (один лёгкий запрос к БД)
Long cachedVersion = cache.get("user:" + id + ":version");
Long currentVersion = userRepository.getVersion(id);
if (!currentVersion.equals(cachedVersion)) {
    // Версия изменилась — обновить кэш
    cache.put("user:" + id, userRepository.findById(id));
    cache.put("user:" + id + ":version", currentVersion);
}

5. Pub/Sub-инвалидация (для распределённых кэшей)

Redis Pub/Sub для уведомления всех экземпляров приложения:

Пример
// При обновлении — публикуем в канал
redisTemplate.convertAndSend("cache-invalidation", "users:" + id);

// Все экземпляры слушают канал
@Component
public class CacheInvalidationListener implements MessageListener {
    @Override
    public void onMessage(Message message, byte[] pattern) {
        String key = new String(message.getBody());
        caffeineCache.invalidate(key); // инвалидируем L1 (in-memory)
    }
}

Сравнение стратегий

Стратегия Согласованность Сложность Применение
TTL Eventual (в пределах TTL) Низкая Справочники, каталоги
Явная инвалидация Высокая Средняя CRUD-операции
Event-driven (Kafka) Eventual Высокая Микросервисы
Write-Through Strong Средняя Критичные данные
Version-based Strong Средняя Когда TTL неприемлем

Выводы

  • TTL + явная инвалидация — наиболее распространённая комбинация: TTL как страховка, @CacheEvict при обновлении
  • В микросервисах: событийная инвалидация через Kafka/Redis Pub/Sub
  • Стратегия “cache aside + TTL + evict on update” покрывает 90% сценариев

Частые ошибки

  • Обновить БД, забыть инвалидировать кэш — пользователи видят старые данные
  • Инвалидировать кэш ДО записи в БД — при ошибке записи кэш пуст, следующий запрос загрузит старые данные из БД
  • Правильный порядок: сначала запись в БД, потом инвалидация кэша
  • TTL слишком большой — 24 часа для профиля пользователя = показывать вчерашнее имя
  • TTL слишком маленький — 1 секунда для справочника = нет смысла кэшировать

Как используется в 2026

  • TTL + @CacheEvict — стандарт в Spring-приложениях
  • Kafka-based инвалидация — стандарт в микросервисах
  • Redis keyspace notifications — автоматическое уведомление об истечении TTL

На собеседовании: интервьюер хочет услышать про комбинацию TTL + явная инвалидация и правильный порядок операций (сначала БД, потом кэш). Частая ошибка — не знать про event-driven инвалидацию в микросервисах.