Кеширование
Разбираемся как работает кеш на разных уровнях, какие заголовки им управляют, как проверяется актуальность данных и почему инвалидация — одна из сложнейших задач в вебе.
Зачем нужно кеширование
Каждый HTTP-запрос — это сетевой обмен. Клиент отправляет запрос, ждёт пока он дойдёт до сервера, сервер обрабатывает, формирует ответ и отправляет обратно. Даже если всё работает быстро — это время, трафик и нагрузка на сервер.
Кеширование — это сохранение ответа, чтобы при повторном запросе не ходить за ним заново.
↓ запрос
Сервер → обработка
↓ ответ
Клиент
↓ снова запрос
Сервер → обработка
↓ снова ответ
Клиент
↓ запрос
Кеш → сохраняет
↓ ответ
Клиент
↓ снова запрос
Кеш → ответ мгновенно
сервер не задействован
Выигрыш тройной:
- Быстрее для пользователя — ответ приходит без ожидания сервера
- Меньше нагрузка на сервер — не обрабатывает одни и те же запросы повторно
- Меньше трафика — данные не гоняются по сети лишний раз
Где живёт кеш
Кеш — не одно место, а несколько слоёв. Каждый работает на своём уровне. Запрос проходит сверху вниз. Если кеш на каком-то уровне отдаёт ответ — дальше запрос не идёт. Чем выше уровень отдал ответ — тем быстрее и дешевле.
(disk cache) или (memory cache) — запрос до сервера
не доходил. Хранит данные только для этого пользователя.
Заголовки управления кешем
Кеш не работает сам по себе — сервер говорит клиентам и промежуточным узлам что можно кешировать и на сколько.
Cache-Control
Основной заголовок. Мы познакомились с ним в главе 2 — здесь разберём подробнее как он управляет поведением кеша на разных уровнях.
max-age для общих кешей (CDN, прокси). Переопределяет max-age на промежуточных узлах. Браузер использует max-age, CDN — s-maxage.no-cache не запрещает кеш, а запрещает использовать без проверки.Практические примеры
# Статика — кешировать на год (URL сменится при изменении файла) Cache-Control: public, max-age=31536000 # API каталога — кешировать публично на час Cache-Control: public, max-age=3600 # Данные текущего пользователя — не кешировать вообще Cache-Control: private, no-store # HTML-страница — проверять при каждом запросе Cache-Control: no-cache
Expires и Pragma
Expires — предшественник Cache-Control: max-age. Указывает конкретную дату
после которой ответ считается устаревшим. Если присутствуют оба — max-age имеет приоритет.
Проблема: нужна синхронизация часов между клиентом и сервером — max-age надёжнее
(использует относительное время).
Pragma: no-cache — рудимент из HTTP/1.0. В современном HTTP не нужен —
Cache-Control полностью его заменяет. Встречается в старых системах.
Vary
Vary: Accept-Encoding
Говорит кешу: «ответ зависит от конкретного заголовка запроса». Кеш должен хранить разные копии для разных значений этого заголовка.
Vary: Accept-Encoding кеш может отдать сжатый gzip-ответ клиенту, который не умеет
его распаковать. Мы упоминали Vary в главе 2 в контексте Content-Encoding.
В nginx директива gzip_vary on добавляет этот заголовок автоматически.
Условные запросы: проверка актуальности
max-age истёк — значит ли это что данные устарели? Не обязательно.
Файл мог не измениться. Условные запросы позволяют проверить актуальность кеша
без повторной загрузки данных.
Механизм: клиент говорит серверу «у меня есть версия от такого-то момента, она ещё актуальна?»
Сервер отвечает: либо 304 Not Modified (тело не передаётся), либо 200 OK
с полным новым ответом.
Last-Modified: Wed, 18 Jun 2026 10:30:00 GMTIf-Modified-Since: Wed, 18 Jun 2026 10:30:00 GMTETag: "a3f9c12e"If-None-Match: "a3f9c12e"If-None-Match, и If-Modified-Since — сервер по
спецификации проверяет If-None-Match первым. nginx автоматически генерирует
ETag и Last-Modified для статических файлов.
Жизненный цикл кеширования
Когда браузер делает запрос к ресурсу который когда-то кешировал — он проходит через два вопроса: свежесть и валидация.
Два ключевых этапа:
- Свежесть — определяется
max-age: можно ли вообще не ходить к серверу - Валидация — определяется
ETag/Last-Modified: если кеш устарел, может всё ещё актуален
Инвалидация кеша
Инвалидация — это когда данные на сервере изменились и сохранённые копии стали неактуальными. Нужно чтобы клиенты получили новую версию.
Cache-Control: max-age=86400 —
браузер будет использовать кеш сутки, даже если данные на сервере изменились через минуту.
Сервер бессилен.«В программировании есть две по-настоящему сложные вещи — инвалидация кеша и именование переменных»
Стратегия 1 — Короткий max-age
Самый простой подход — не кешировать надолго. max-age=60 означает что кеш будет
актуален максимум минуту, после чего браузер сделает условный запрос. Подходит для данных
которые меняются часто, но повторные запросы в рамках минуты можно отдать из кеша.
Стратегия 2 — Версионирование URL (cache busting)
Самый надёжный подход для статических файлов. Вместо того чтобы менять содержимое файла по тому же URL — меняется сам URL. Хеш в имени файла — это хеш его содержимого. Изменился файл → изменился хеш → изменился URL → браузер запрашивает новый файл, старый кеш не мешает.
no-cache чтобы браузер всегда
получал свежую версию с актуальными ссылками на хешированные файлы.
Стратегия 3 — no-cache + ETag
Для API-ответов. Кеш хранит копию, но при каждом запросе делает условный запрос к серверу.
Если данные не изменились — 304, трафик сэкономлен.
Если изменились — 200 с новыми данными.
Стратегия 4 — Очистка кеша на прокси/CDN
Администратор или деплой-скрипт вызывает API CDN для сброса кеша определённых URL. Это не влияет на кеш браузера, но обновляет промежуточный слой немедленно.
Что нельзя кешировать
Не всё подлежит кешированию — некоторые ответы кешировать опасно или бессмысленно.
| Что | Почему нельзя | Заголовок |
|---|---|---|
| Персональные данные | Профиль, корзина, дашборд. Прокси-кеш сохранит ответ /api/me одного пользователя и отдаст другому — тот увидит чужие данные |
private, no-store |
| Ответы с Set-Cookie | Один пользователь может получить куку другого. Большинство CDN и прокси по умолчанию не кешируют ответы с Set-Cookie |
private |
| POST, PUT, DELETE, PATCH | Изменяющие операции. Кеширование их результатов бессмысленно — каждый вызов производит разный эффект | не кешируются |
| Конфиденциальные данные | Токены, балансы, медицинские данные — любые данные которые не должны попасть к другим лицам | no-store |
Кеширование в контексте проксирования
В главе 4 мы разобрали цепочку HAProxy → nginx → бэкенд. Где в этой цепочке место кешу и кто принимает решение что кешировать?
Кто решает что кешировать
Бэкенд. Именно бэкенд ставит Cache-Control, ETag,
Last-Modified в ответ — потому что только он знает бизнес-логику: какие данные
статичны, какие персонализированы, как часто меняются.
Реверс-прокси (nginx) и CDN подчиняются этим заголовкам. Они не принимают
решения о кешировании самостоятельно — они читают заголовки и действуют по инструкции.
Если бэкенд сказал no-store — прокси не кеширует.
Если public, max-age=3600 — прокси может сохранить на час.
Cache-Control бэкенда и навязать свои правила
через proxy_cache_valid и proxy_ignore_headers. Но это означает что
прокси берёт на себя бизнес-решение которого не понимает — он не знает какие данные
персонализированы, а какие нет. Опасная практика.
Итог
Кеширование работает на нескольких уровнях: браузер (приватный), реверс-прокси и CDN (общий). Бэкенд принимает решение о кешировании — через заголовки ответа.
Два этапа
| Этап | Механизм | Что делает |
|---|---|---|
| Свежесть | Cache-Control: max-age |
Можно ли использовать кеш без обращения к серверу |
| Валидация | ETag / Last-Modified |
Проверяет актуальность устаревшего кеша условным запросом (304 или 200) |
Основные заголовки
| Заголовок | Роль |
|---|---|
| Cache-Control | Главный — указывает правила кеширования |
| ETag | Идентификатор версии содержимого |
| Last-Modified | Дата последнего изменения |
| If-None-Match | Условный запрос по ETag |
| If-Modified-Since | Условный запрос по дате |
| Vary | От какого заголовка запроса зависит ответ |
Главная сложность — инвалидация: сервер не может удалить устаревший кеш из браузера.
Решение для статики — версионирование URL (хеш в имени файла).
Для API — короткий max-age или no-cache с условными запросами.