Какие паттерны кэширования существуют?
Паттерны кэширования — это устоявшиеся стратегии взаимодействия приложения, кэша и источника данных, определяющие кто, когда и как читает и записывает данные.
Cache-Aside (Lazy Loading)
Самый распространённый паттерн. Приложение управляет кэшем явно: сначала проверяет кэш, при промахе загружает из БД и кладёт в кэш.
Пример кода
public User getUser(Long id) {
// 1. Проверить кэш
User cached = cache.get("user:" + id);
if (cached != null) return cached;
// 2. Cache miss — загрузить из БД
User user = userRepository.findById(id).orElseThrow();
// 3. Положить в кэш
cache.put("user:" + id, user, Duration.ofMinutes(10));
return user;
}
// Spring Cache реализует Cache-Aside автоматически
@Cacheable(value = "users", key = "#id")
public User getUser(Long id) {
return userRepository.findById(id).orElseThrow();
}
Плюсы: кэшируются только запрошенные данные; промах заполняет кэш. Минусы: первый запрос всегда медленный (cold start); возможна несогласованность при обновлении БД без инвалидации кэша.
Read-Through
Кэш сам загружает данные из источника при промахе. Приложение всегда обращается к кэшу, не к БД напрямую.
Пример кода
// Caffeine с CacheLoader — Read-Through
LoadingCache<Long, User> cache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(Duration.ofMinutes(10))
.build(id -> userRepository.findById(id).orElseThrow()); // CacheLoader
// Использование — кэш сам вызовет loader при промахе
User user = cache.get(userId);
Отличие от Cache-Aside: логика загрузки инкапсулирована в кэше, а не в сервисе.
Write-Through
При записи данные одновременно пишутся и в кэш, и в БД. Кэш всегда содержит актуальные данные.
Пример кода
public User updateUser(Long id, UpdateRequest request) {
User user = userRepository.save(toEntity(request)); // запись в БД
cache.put("user:" + id, user); // запись в кэш
return user;
}
// Spring Cache — @CachePut реализует Write-Through
@CachePut(value = "users", key = "#result.id")
public User updateUser(UpdateRequest request) {
return userRepository.save(toEntity(request));
}
Плюсы: кэш всегда актуален. Минусы: каждая запись медленнее (двойная запись); кэшируются данные, которые могут никогда не быть прочитаны.
Write-Behind (Write-Back)
Данные сначала пишутся в кэш, а запись в БД происходит асинхронно (с задержкой или батчами).
Пример
Приложение → Кэш (мгновенно) → [асинхронно, батчами] → БД
Плюсы: максимальная скорость записи; можно агрегировать множество записей в один batch. Минусы: риск потери данных при сбое кэша до синхронизации с БД. Применение: счётчики, метрики, аналитика, write-heavy нагрузка.
Refresh-Ahead
Кэш проактивно обновляет данные до истечения TTL для горячих ключей.
Пример
// Caffeine с refreshAfterWrite — автоматическое фоновое обновление
LoadingCache<Long, User> cache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(Duration.ofMinutes(30)) // жёсткое удаление через 30 мин
.refreshAfterWrite(Duration.ofMinutes(10)) // фоновое обновление через 10 мин
.build(id -> userRepository.findById(id).orElseThrow());
// Через 10 мин: возвращает старое значение + запускает фоновую загрузку нового
// Через 30 мин: ключ удаляется, следующий запрос — полный cache miss
Плюсы: нет холодных промахов для горячих данных. Минусы: сложнее в реализации; нагрузка на БД для обновления даже незапрашиваемых данных.
Сравнение паттернов
| Паттерн | Кто читает из БД | Кто пишет в БД | Consistency | Latency чтения | Latency записи |
|---|---|---|---|---|---|
| Cache-Aside | Приложение | Приложение | Eventual | Miss — медленно | Без кэша |
| Read-Through | Кэш | Приложение | Eventual | Miss — медленно | Без кэша |
| Write-Through | Кэш | Кэш + БД синхронно | Strong | Hit — быстро | Медленнее (2 записи) |
| Write-Behind | Кэш | Кэш, затем БД асинхронно | Eventual | Быстро | Быстро |
| Refresh-Ahead | Кэш (проактивно) | Приложение | Eventual | Всегда быстро | Без кэша |
Выводы
- Cache-Aside + TTL — самый распространённый и простой паттерн; Spring
@Cacheableреализует его - Write-Through — когда нужна согласованность кэша с БД; Spring
@CachePut - Write-Behind — для write-heavy нагрузок (счётчики, логи)
- Refresh-Ahead — для данных, которые должны быть всегда теплыми (каталог товаров, курсы валют)
Частые ошибки
- Cache-Aside без инвалидации — обновили БД, забыли обновить/удалить кэш, получили stale data
- Write-Through для редко читаемых данных — кэш заполняется данными, которые никто не читает
- Write-Behind без обработки ошибок — если асинхронная запись в БД упала, данные потеряны
- Путать паттерны — Cache-Aside +
@CachePut= рабочая комбинация; Read-Through в Spring — через CaffeineLoadingCache
Как используется в 2026
- Cache-Aside — 80%+ всех кэширований в Spring-приложениях
- Write-Behind — используется в Redis Streams и Apache Kafka для буферизации записей
- Caffeine
refreshAfterWrite— стандарт для проактивного обновления справочных данных
На собеседовании: интервьюер проверяет, понимаете ли вы разницу между паттернами и умеете ли выбрать нужный под задачу. Частая ошибка — описать только Cache-Aside и не знать Write-Behind и Refresh-Ahead.