Кэш — это быстрая копия данных, положенная ближе к потребителю, чтобы не пересчитывать их и не ходить в базу при каждом запросе. Нужен ради двух вещей: скорости (ответ из памяти приходит в разы быстрее) и снятия нагрузки (база и сервисы не надрываются). Кэшируют везде: в браузере, на CDN, внутри приложения, в отдельном хранилище вроде Redis. Если данные есть в кэше — это cache hit, если нет и пришлось идти в базу — cache miss. Главная боль — инвалидация: как понять, что копия устарела, и вовремя её обновить. Отсюда классическое «показывает старое». Для аналитика ключевой вопрос — не технический, а бизнесовый: насколько свежими должны быть эти данные.
Однажды я полдня доказывал команде, что баланс в личном кабинете «врёт». Пользователь пополнял счёт, видел старую сумму, психовал, пополнял ещё раз. Баг искали в платёжке, в базе, в логах списаний — всё было верно. Виноват оказался кэш: баланс кэшировался на минуту, и эта минута между пополнением и обновлением экрана рождала жалобы. Чинили не код, а решение: «баланс кэшируем ноль секунд, а вот курс валют — пять минут, и это нормально». Тогда я понял, что кэш — это не настройка бэкенда, а требование, которое должен поставить аналитик.
Что такое кэш и зачем он нужен
Кэш — это место, где лежит готовый ответ, чтобы не считать его заново. Представьте, что вас каждый день спрашивают, сколько будет 347 × 829. Можно умножать в столбик каждый раз, а можно один раз посчитать, записать ответ на стикер и приклеить к монитору. Стикер — это и есть кэш: копия результата, лежащая под рукой.
В системах считать заново дорого по двум причинам. Во-первых, скорость: достать данные из быстрой памяти — это микросекунды, а сходить в базу через сеть, выполнить запрос и вернуть результат — это миллисекунды, иногда сотни. Разница в тысячи раз. Во-вторых, нагрузка: если десять тысяч пользователей одновременно просят одну и ту же главную страницу, без кэша база получит десять тысяч одинаковых запросов и захлебнётся. С кэшем она ответит один раз, остальные получат копию.
Скорость и устойчивость под нагрузкой — это нефункциональные требования. Кэш — один из главных инструментов, которым их закрывают. Когда в спеке написано «страница должна открываться быстрее секунды при пиковой нагрузке», кэширование почти всегда окажется частью ответа.
Cache hit и cache miss на пальцах
Логика любого кэша одинакова: сначала загляни в кэш, и только если там пусто — иди за данными по-настоящему.
flowchart TD
A["Запрос: дай данные"] --> B{"Есть в кэше?"}
B -->|"да — cache hit"| C["Вернуть копию из кэша, быстро"]
B -->|"нет — cache miss"| D["Сходить в БД, медленно"]
D --> E["Положить ответ в кэш"]
E --> F["Вернуть данные"]
Схема показывает путь запроса. Сначала система спрашивает кэш: «есть ли тут готовый ответ?». Если есть — это cache hit, копия возвращается сразу, быстро и без нагрузки на базу. Если нет — это cache miss: приходится идти в базу по-настоящему (медленно), а полученный ответ по дороге кладут в кэш, чтобы следующий такой же запрос стал уже hit. Чем больше доля hit'ов, тем быстрее и дешевле работает система. Метрика «процент попаданий в кэш» (hit rate) — то, за чем следят в проде; подробнее про метрики — в записи про наблюдаемость.
Где кэшируют
Кэш стоит не в одном месте, а слоями — от пользователя до базы. Каждый слой ловит часть запросов, чтобы они не доходили до следующего.
- Браузер (HTTP-кэш) — браузер сам хранит у себя картинки, стили, скрипты, чтобы не качать их при каждом заходе. Управляется HTTP-заголовками (Cache-Control и компания). Самый ближний к пользователю кэш.
- CDN — сеть серверов по всему миру, которые держат копии статики ближе к пользователю географически. Человек из Новосибирска получает картинку с сервера в Новосибирске, а не из Москвы. Снимает нагрузку и сокращает задержку.
- Кэш приложения — сам сервис держит часто нужные данные у себя в памяти, чтобы не дёргать базу.
- Отдельное хранилище (Redis) — выделенный сверхбыстрый сервер-кэш, общий для всех экземпляров приложения. Redis — это in-memory хранилище ключ-значение; в роли кэша он один из самых частых. Подробнее про такие хранилища — в записи про SQL и NoSQL.
Кэш — это про чтение, не про запись
Кэш ускоряет получение данных, которые читают часто, а меняют редко: каталог товаров, профиль, курс валют. Для данных, которые постоянно меняются и должны быть точны до копейки (остаток на складе в момент покупки, текущий баланс при списании), кэш либо не ставят, либо ставят с нулевым временем жизни. Понимать, что кэшируется, а что нет, — это и есть требование к свежести данных, и ставить его — работа аналитика.
Инвалидация: главная боль
Положить данные в кэш легко. Сложно — вовремя понять, что копия устарела, и убрать её. Это называется инвалидация кэша, и именно отсюда растут почти все жалобы вида «показывает старое».
Самый частый способ — TTL (time to live), время жизни копии. Кладём данные в кэш и говорим: «живи пять минут». Пять минут все получают копию (быстро, но, возможно, чуть устаревшую), через пять минут копия «протухает», следующий запрос идёт в базу за свежими данными и кладёт новую копию. TTL — это компромисс: чем он больше, тем быстрее и дешевле система, но тем дольше она может показывать устаревшее.
Вот почему пользователь «уже оплатил, а баланс старый»: данные закэшированы на минуту, оплата прошла, но копия ещё живёт свой TTL и отдаёт старое число. Система не сломана — она работает ровно как настроена. Проблема в том, что для баланса минута устаревания недопустима, а для курса валют — нормальна. Это решение, и принять его должен не разработчик наугад, а аналитик осознанно.
«В IT две сложные вещи»
Есть известная шутка: «В программировании всего две по-настоящему сложные задачи — инвалидация кэша и придумывание имён». Звучит как анекдот, но это правда из окопов. Положить в кэш умеет каждый. А вот гарантировать, что после изменения данных все копии везде — в браузере, на CDN, в Redis — обновятся вовремя и согласованно, — это честно трудно. Чем больше слоёв кэша, тем больше мест, где может застрять старое.
Кэш и асинхронность
Кэш — близкий родственник асинхронного мышления (см. синхронную и асинхронную интеграцию). Отдавая данные из кэша, система сознательно говорит: «вот ответ, возможно, чуть устаревший, зато сразу». Это та же логика, что и «приняли, посчитаем потом»: мы меняем абсолютную свежесть на скорость. Иногда обновление кэша вообще запускают фоновым процессом — данные пересчитываются в стороне, а пользователь всё это время получает последнюю готовую копию и никогда не ждёт. Главное — понимать, на какой именно компромисс мы пошли и где он допустим.
Что меняется для аналитика
Аналитик не настраивает Redis и не пишет заголовки Cache-Control. Но именно аналитик отвечает на вопрос, без которого настройка невозможна: насколько свежими должны быть эти данные? И это бизнес-вопрос, а не технический.
Спрашивайте по каждому экрану и каждой цифре: можно ли показать баланс минутной давности? А цену товара? А число лайков? А остаток на складе в момент оформления заказа? Ответы будут разными — и именно из них рождается настройка кэша.
| Данные | Допустимая устарелость | Решение по кэшу |
|---|---|---|
| Список категорий каталога | Часы | Кэшируем долго, TTL в часах |
| Цена товара | Минуты | Короткий TTL, инвалидация при смене цены |
| Курс валют | Минуты | TTL 1–5 минут — приемлемо |
| Баланс счёта | Ноль (деньги) | Не кэшируем или TTL = 0 |
| Остаток на складе при покупке | Ноль (продадим лишнее) | Читаем напрямую из базы |
Эта таблица — не теория, а готовый артефакт для спеки. Если вы принесёте разработчику такую табличку вместо общего «сделайте побыстрее», вопрос «а что можно кэшировать?» снимется сам собой, а пользователь не увидит чужой устаревший баланс.
Откуда это взялось
Идея кэша стара как сама архитектура компьютеров. Ещё в фон-неймановской модели память выстроена иерархией: крошечная сверхбыстрая память процессора — побольше и помедленнее — и огромная медленная. Каждый верхний уровень кэширует нижний, потому что быстрая память дорогая, а медленная — большая. Веб подхватил тот же принцип: HTTP-кэширование описали в стандартах протокола ещё в 1990-х, а компания Akamai около 1998 года построила первую большую CDN, раскидав копии контента по миру, чтобы он грузился ближе к пользователю. А крылатую фразу про «две сложные вещи в IT — инвалидацию кэша и именование» приписывают Филу Карлтону, инженеру Netscape.
Частые вопросы
Что такое инвалидация кэша простыми словами?
Это процесс, когда устаревшую копию данных в кэше помечают негодной или удаляют, чтобы система пошла за свежими данными. Без инвалидации кэш отдаёт старое: пользователь оплатил, а баланс прежний. Самый частый способ — TTL, время жизни копии: через заданный срок копия «протухает» сама и обновляется при следующем запросе. Инвалидацию считают одной из самых сложных задач в IT, потому что копий бывает много и обновить их все вовремя и согласованно непросто.
Чем cache hit отличается от cache miss?
Cache hit — это попадание: запрошенные данные нашлись в кэше, и их вернули сразу, быстро и без нагрузки на базу. Cache miss — промах: в кэше данных не оказалось, пришлось идти в базу (медленно), а полученный ответ положили в кэш на будущее. Чем выше доля попаданий (hit rate), тем быстрее и дешевле работает система. За этим показателем следят в проде как за метрикой здоровья кэша.
Какие данные нельзя кэшировать?
Те, где устарелость недопустима, — обычно связанные с деньгами и количеством в момент операции: баланс счёта при списании, остаток на складе в момент покупки, результат платежа. Если показать старую копию, можно дважды продать последний товар или ввести пользователя в заблуждение по деньгам. Такие данные либо не кэшируют вовсе, либо ставят им нулевое время жизни. Решение «можно ли это кэшировать и насколько» — это требование к свежести данных, и формулирует его аналитик.