Кеш — это не магия: разбираемся на пальцах

«Просто добавьте кеш» — одна из самых частых рекомендаций, которую дают, когда сервис начинает тормозить. Проблема в том, что кеш — это не переключатель «быстро/медленно», а отдельная область со своими собственными способами всё сломать. Разберём по порядку.
Зачем вообще нужен кеш
Кеш имеет смысл, когда вычисление или получение данных дороже, чем их повторное использование. Классический пример — результат тяжёлого запроса к базе, который не меняется каждую секунду: список категорий товаров, агрегированная статистика за день, результат вызова внешнего API с лимитом запросов.
Если данные меняются на каждый запрос или их вычисление и так дешёвое — кеш не ускорит систему, а добавит сложности без пользы.
Самая частая проблема: инвалидация
Есть известная шутка про то, что в программировании есть только две сложные вещи: именование переменных и инвалидация кеша. Она не преувеличивает. Данные в кеше устаревают, и вопрос «когда их обновить» решается тремя основными способами.
TTL — самый простой: данные считаются устаревшими через фиксированное время. Подходит, когда допустима небольшая задержка в актуальности данных.
Инвалидация по событию — кеш сбрасывается явно в момент, когда данные изменились. Точнее, но требует не забыть сбросить кеш во всех местах, где данные могут поменяться, — а это источник трудноуловимых багов, когда забыли один из путей изменения.
Write-through — запись одновременно идёт и в основное хранилище, и в кеш, так что рассинхронизации не возникает вовсе, но это усложняет логику записи.
Кеш-промах под нагрузкой
Отдельная ловушка — так называемый thundering herd: когда популярный ключ кеша истекает, и сразу множество запросов одновременно бросаются пересчитывать значение и бьют по источнику данных всей силой сразу. Я один раз словил это на проде: кеш главной страницы сбрасывался раз в минуту, и в момент сброса база данных получала кратковременный, но очень заметный всплеск нагрузки.
Решение — либо продлевать TTL для части запросов случайным образом (jitter), чтобы не все ключи истекали одновременно, либо использовать блокировку, при которой только один запрос пересчитывает значение, а остальные ждут результата или отдают немного устаревшие данные.
Когда кеш создаёт больше проблем, чем решает
Если после внедрения кеша в системе появляются баги вида «я обновил данные, а старые значения всё ещё показываются» чаще, чем раз в квартал — вероятно, стратегия инвалидации выбрана неправильно для этого конкретного случая использования. Иногда правильный ответ — не более хитрый кеш, а более простой и быстрый путь к исходным данным: индекс в базе, денормализация, материализованное представление.
Кеш — инструмент, а не обязательный этап взросления системы. Добавлять его стоит, когда измерения показали конкретную проблему производительности, а не «на всякий случай».