Суть Читай реальные заголовки Cache-Control и ответ CDN, предсказывай поведение браузера и CDN и выбирай верную директиву для сценария.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min
Баги кэширования читаются прямо на проводе — в заголовках ответа и в строке cache-status CDN. Читай каждый набор заголовков, предсказывай, что делает с ним каждый уровень цепочки, затем выбирай исправление, которое отправит senior-инженер.
Цель
Отработай цикл, который запускаешь в каждом инциденте кэширования: читай директивы, рассуждай о браузере и общем кэше раздельно и выбирай директиву под нужды данных по разделению и свежести.
Сниппет 1 — «защищённый» эндпоинт
GET /api/account/balance HTTP/1.1200 OKCache-Control: no-cacheContent-Type: application/json
Викторина
Completed
Этот эндпоинт баланса на пользователя отправляет no-cache и без ETag. Каково реальное поведение и что надо отправлять вместо этого?
Heads-up no-cache разрешает хранение и лишь форсирует ревалидацию; он никогда не предотвращает хранение. Финансовым данным на пользователя нужен no-store, запрещающий хранение везде.
Heads-up ETag чинит эффективность ревалидации, но ответ всё равно хранится — в браузере и bfcache. Данные не должны храниться вообще, поэтому no-store нужен независимо от ETag.
Heads-up s-maxage=0 всё ещё разрешает хранение и лишь форсирует ревалидацию общего кэша; о браузере он тоже молчит. Верное исключение для данных на пользователя — private, no-store.
Этот заголовок стоит на index.html сайта, отдаваемом через CDN. Что идёт не так и какой верный заголовок для HTML, ссылающегося на ассеты с хешем содержимого?
Heads-up Очистка CDN не достанет браузер, держащий HTML год; этот клиент держит устаревший документ. HTML должен ревалидироваться браузером, поэтому не должен нести max-age на год.
Heads-up Браузеры соблюдают max-age для HTML, как для любого ресурса; max-age=31536000 заставляет браузер держать его тоже год. Нет автоматической ревалидации HTML, когда max-age большой.
Heads-up immutable делает хуже — он говорит браузеру никогда не ревалидировать даже при перезагрузке, замораживая устаревший HTML сильнее. Правило: хешировать ассеты, никогда HTML.
Сниппет 3 — no-cache против no-store, бок о бок
# Ответ ACache-Control: no-cache, privateETag: "v9"# Ответ BCache-Control: no-store
Викторина
Completed
Страница результатов поиска, дешёвая в ревалидации и нечувствительная, использует Ответ A; страница сброса пароля использует Ответ B. Каждый выбор верен?
Heads-up no-cache на странице сброса разрешает хранение и лишь требует ревалидации — чувствительная страница всё равно хранилась бы и лежала в bfcache. Странице сброса нужен no-store; ревалидация A ровно подходит дешёвому нечувствительному поиску.
Heads-up no-cache означает ревалидировать перед повторным использованием, а не никогда не использовать. 304 позволяет кэшу отдать сохранённое тело бесплатно, поэтому стабильный результат переиспользуется эффективно — в этом смысл пары no-cache с ETag.
Heads-up no-store на странице поиска выбрасывает всё кэширование нечувствительного контента, дёшево ревалидируемого, вредя производительности без выигрыша в безопасности. Подбирай директиву под данные: no-store лишь там, где риск — само хранение.
Сниппет 4 — строка ответа CDN
200 OKCache-Control: max-age=60, s-maxage=300, stale-while-revalidate=3600Age: 420CF-Cache-Status: HIT
Викторина
Completed
CDN возвращает это для JSON-API. Age=420 превышает s-maxage=300, но это HIT. Это корректно и что получает клиент?
Heads-up stale-while-revalidate явно разрешает отдавать устаревшее в своём окне и считать это HIT, пока в фоне идёт ревалидация. Это фича, а не баг.
Heads-up max-age=60 — свежесть браузера; копия CDN управляется s-maxage. Будет ли браузер ревалидировать, зависит от того, что CDN вернёт сейчас, но HIT CDN тут валиден по stale-while-revalidate.
Heads-up HIT означает отдано из кэша, а не что оно свежее. По stale-while-revalidate устаревшая копия отдаётся как HIT; свежесть определяется Age против s-maxage, что здесь говорит устаревшее-но-отдаваемое.
Итог
Каждый баг кэширования читается из заголовков. no-cache без ETag на данных на пользователя — это хранимая, неэффективно ревалидируемая утечка — отправляй private, no-store. max-age на год на HTML замораживает деплои, потому что правило — хешировать ассеты и держать HTML ревалидируемым. no-cache плюс ETag — верный выбор для дешёвого нечувствительного контента, переиспользуемого через 304; no-store — для данных, которые нельзя хранить вообще. А HIT CDN с Age за s-maxage корректен по stale-while-revalidate — устаревшее-но-мгновенное по замыслу, с фоновым обновлением. Читай директивы по уровням, подбирай под разделение и чувствительность данных, затем проверяй на проводе.