Суть Читай реальные заголовки Cache-Control, trace-заголовки ответа и слоистый путь hit/miss, затем выбирай поведение и фикс с наибольшим рычагом.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min
Баги кэша диагностируются в заголовках ответа и пер-слойном трейсе, а не на диаграмме. Прочитай заголовок, предскажи, что делает каждый слой, и выбери фикс, который senior делает первым.
Цель
Отработай цикл, который ты гоняешь на каждом инциденте с кэшем: прочитай директивы Cache-Control и trace-заголовки, спустись по стеку слоёв за запросом и найди, какой слой лжёт, прежде чем трогать TTL.
Инженер добавляет этот заголовок к персонализированному эндпоинту /me, затем спрашивает, почему hit ratio у CDN равен 0% и нагрузка на origin не упала. Что верно?
Heads-up no-store побеждает — он запрещает хранение полностью, поэтому max-age лишний. А кэширование персонализированного ответа на общем CDN утекло бы данными одного пользователя другому; 0% hit ratio корректен, это не баг для починки здесь.
Heads-up private значит, что хранить может лишь приватный для пользователя кэш (браузер); общие кэши вроде CDN не должны. Это противоположность «только origin» — а no-store запрещает даже браузеру.
Heads-up Директивы явно отказываются от общего кэширования. Персонализированный эндпоинт не должен кэшироваться на CDN; 0% hit ratio — это заголовок, делающий свою работу.
Сниппет 2 — trace-заголовки
HTTP/1.1 200 OKAge: 240Cache-Control: public, max-age=300CF-Cache-Status: HITX-Cache: MISS from reverse-proxy
Викторина
Completed
Пользователь сообщает об устаревших данных через 4 минуты после записи. Читая эти заголовки из одного ответа, что заключаешь?
Heads-up MISS у proxy значит, что proxy пошёл вверх — но HIT у CDN перед ним замкнул запрос первым, поэтому ответ origin вообще не использовался. Устаревшая копия — CDN-ская, а не origin.
Heads-up Нахождение внутри max-age — именно поэтому CDN всё ещё их отдаёт, и это и есть проблема. Запись «свежа» по TTL, но устарела по данным, потому что запись её не очистила. Свежесть по TTL — не свежесть данных.
Heads-up Они описывают два разных слоя — HIT у CDN и MISS у reverse-proxy — и согласованы: запрос попал в CDN и не дошёл до upstream-а proxy. Чтение их пер-слойно — именно так находят лгущую копию.
Сниппет 3 — слоистый путь hit/miss
client → CDN edge → reverse proxy → app (Redis) → Postgres miss miss HIT (Redis) —
Викторина
Completed
Запрос промахивается мимо CDN и reverse proxy, но попадает в Redis на app-слое. Для in-region схемы какова грубая картина задержек и о чём она говорит?
Heads-up Трейс показывает обратное: CDN и proxy оба промахнулись и переслали. Попадание глубоко в стеке значит, что каждый слой над ним добавил miss-хоп. Слои не делят состояние — каждый проверяется и пересылает дальше при miss.
Heads-up Miss — нормальное поведение кэша, а не поломка: просто эти верхние слои не держат этот ключ. Сигнал в том, окупает ли hit ratio каждого слоя его существование для этого трафика, а не что кэширование провалилось.
Heads-up Трейс заканчивается на HIT у Redis — Postgres (—) не запрашивался. Redis сэкономил round trip к БД на этом запросе; вопрос в том, тянут ли CDN и proxy над ним свой вес.
Высоконагруженная лента, терпимая к лёгкому устареванию, переходит на эту директиву, и p99 резко падает без роста всплесков нагрузки на origin. Почему это работает?
Heads-up Наоборот — stale-while-revalidate продлевает пригодность на 600с сверх max-age, отдавая устаревшую копию при фоновом обновлении. Внутри этого окна ничто не вынуждено ждать свежую выборку.
Heads-up Она не ревалидирует на каждом запросе — она отдаёт устаревшую копию мгновенно и ревалидирует асинхронно. Данные намеренно слегка устаревшие; именно эта терпимость к устареванию и покупает выигрыш в задержке.
Heads-up Один max-age=60 вызывал бы синхронную выборку из origin (и возможный herd) на каждом истечении. Выигрыш — это окно stale-while-revalidate, отвязывающее задержку пользователя от ревалидации, а не значение max-age.
Итог
Поведение кэша читается в заголовках и трейсах. Директивы Cache-Control решают, какие слои могут хранить ответ — no-store запрещает всем, private запрещает общим кэшам вроде CDN — поэтому персонализированный эндпоинт корректно показывает 0% hit ratio у CDN. Пер-слойные trace-заголовки (CF-Cache-Status, X-Cache, Age) позволяют пройти стек и найти устаревшую копию: HIT у CDN внутри max-age, переживший запись, значит, что CDN не был очищен. Попадание глубоко в стеке значит, что каждый слой над ним добавил miss-хоп, поэтому проверь, окупает ли себя каждый слой. А stale-while-revalidate отдаёт устаревшую копию мгновенно при фоновом обновлении, срезая p99 и сглаживая нагрузку на origin для терпимых к устареванию чтений.