Кэширование: стратегии взаимодействия и алгоритмы вытеснения

Кэширование ускоряет ответы и снижает нагрузку на БД. Эффективность зависит от стратегии работы с кэшем и от того, какие данные вытеснять при нехватке места.

Зачем нужно кэширование

  • Сокращение времени ответа (кэш быстрее БД).
  • Снижение нагрузки на сторонние сервисы и БД.
  • Стабилизация при кратковременных сбоях (отдача устаревших данных из кэша).
  • Экономия ресурсов за счёт кэширования результатов тяжёлых вычислений.

Термины: Cache Hit — данные найдены в кэше; Cache Miss — обращение к БД; Hit Rate — доля попаданий; Hot Key — часто запрашиваемый ключ; Cache Warmup — прогрев после запуска; Cache Invalidation — удаление устаревших данных.

Важно: кэш должен ускорять ответы и поддерживать БД, а не заменять её. При высоком Miss Rate кэш может быть неэффективен (среднее время выше, чем при работе только с БД). Защита от атак на промахи: rate limiting, прогрев, проектирование БД с учётом части нагрузки.

Стратегии взаимодействия с кэшем

Cache-Aside (ленивое кэширование)

Сервис сначала смотрит в кэш; при промахе идёт в БД, сохраняет в кэш и возвращает данные.

  • Плюсы: простота, кэш заполняется по требованию.
  • Минусы: при промахе задержка выше, возможны гонки при одновременном заполнении.

Когда: read-heavy, данные редко меняются (каталоги, справочники).

Write-Through

Запись сначала в БД, затем синхронное обновление кэша. Кэш всегда актуален.

  • Плюсы: консистентность, простое чтение.
  • Минусы: большая задержка записи, избыточность, если данные редко читают.

Когда: высокая консистентность (финансы).

Cache-Through (Read-Through)

Все запросы идут в кэш; при промахе кэш сам запрашивает БД и сохраняет. Приложение с БД не общается.

  • Плюсы: простая логика приложения.
  • Минусы: при падении кэша теряется доступ к данным, нужен механизм обновления из БД.

Когда: важна простота кода приложения и есть механизмы восстановления кэша.

Write-Ahead / Read-Through (опережающее кэширование)

Чтение только из кэша; данные подгружаются в кэш по расписанию или по событиям.

  • Плюсы: минимальная задержка чтения.
  • Минусы: возможна неконсистентность до обновления кэша.

Когда: низкая задержка важнее строгой свежести (новости, курсы валют с обновлением раз в минуту).

Write-Back

Запись сначала в кэш, затем асинхронная (пакетная) запись в БД. Возможна компактизация изменений.

  • Плюсы: меньше нагрузка на БД, быстрые записи для клиента.
  • Минусы: риск потери данных при падении кэша до синхронизации, сложная логика.

Когда: write-heavy системы, где допустима кратковременная неконсистентность (логи, телеметрия).

Алгоритмы вытеснения

При заполнении кэша нужно решать, какой элемент удалить.

  • Random: удаление случайного элемента. Просто, но неэффективно.
  • FIFO: удаление самого «старого» по времени добавления. Не учитывает частоту обращений.
  • LRU (Least Recently Used): удаление элемента, к которому дольше всего не обращались. Хороший hit rate, подходит для большинства систем с горячими ключами.
  • LFU (Least Frequently Used): удаление реже всего запрашиваемого. Учитывает долгосрочную популярность; сложнее реализация.
  • Second Chance / Clock: улучшение FIFO с флагом «использован»; элемент с флагом получает второй шанс (перемещение в конец или сброс флага). Используется в виртуальной памяти (например, Linux).
  • 2Q: две FIFO-очереди (A1, A2) и LRU. Новые элементы в A1; при вытеснении из A1 попадают в A2; при обращении в A2 — в LRU. Баланс между прогревом и сохранением горячих ключей.
  • SLRU: сегменты cold/warm/hot LRU; элементы переходят между уровнями при обращениях. Комбинация идей LRU и LFU.
  • TTL-based: вытеснение по времени жизни (и по размеру). Подходит для сессий, временных токенов.
  • OPT (Беллади): теоретический «оптимальный» алгоритм (вытеснять то, к чему дольше всего не будет обращений). Нереализуем на практике без знания будущего; используется как эталон для сравнения.

Выбор: для большинства приложений — LRU; при устойчивых горячих ключах — LFU; при ограниченных ресурсах (ОС) — Clock/Second Chance; при необходимости баланса прогрева и популярности — 2Q или SLRU.

Внешний кэш (Redis, Memcached) удобен для масштабирования и сохранения данных между рестартами; внутренний кэш в памяти процесса — быстрее, но теряется при рестарте и не общий для инстансов.