Кэширование ускоряет ответы и снижает нагрузку на БД. Эффективность зависит от стратегии работы с кэшем и от того, какие данные вытеснять при нехватке места.
Зачем нужно кэширование
- Сокращение времени ответа (кэш быстрее БД).
- Снижение нагрузки на сторонние сервисы и БД.
- Стабилизация при кратковременных сбоях (отдача устаревших данных из кэша).
- Экономия ресурсов за счёт кэширования результатов тяжёлых вычислений.
Термины: 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) удобен для масштабирования и сохранения данных между рестартами; внутренний кэш в памяти процесса — быстрее, но теряется при рестарте и не общий для инстансов.