Кэширование — скрытый двигатель, на котором держится веб. Именно оно делает сайты быстрыми, надежными и относительно недорогими в обслуживании. При правильной настройке кэширование снижает задержки, разгружает серверы и позволяет даже хрупкой инфраструктуре выдерживать резкие всплески трафика. При неправильной настройке или полном отсутствии кэширования сайты работают медленно, остаются уязвимыми для атак и обходятся очень дорого.
В основе кэширования лежит сокращение лишней работы. Каждый раз, когда браузер, CDN или прокси обращается к серверу за ресурсом, который не изменился, впустую тратятся время и трафик. Когда сервер заново формирует или повторно отдает идентичный контент, это лишь добавляет нагрузки и увеличивает затраты. А при пиковом трафике — например, в «черную пятницу», во время вирусной публикации или DDoS-атаки — такие ошибки стремительно накапливаются и приводят к сбоям всей системы.
Хотя кэширование и является фундаментальной частью веба, именно оно чаще всего остается недопонятым. Многие разработчики:
путают
no-cacheс «не кэшировать», хотя на самом деле это «сохранять, но каждый раз проверять»выбирают
no-storeкак «безопасное» значение по умолчанию и тем самым полностью отключают кэшированиенеправильно понимают взаимодействие заголовков
ExpiresиCache-Control: max-ageне понимают разницы между
publicиprivate, что приводит к угрозам безопасности и снижению производительностиигнорируют расширенные директивы, вроде
s-maxageилиstale-while-revalidateне учитывают, что браузеры, CDN, прокси и кэши приложений накладывают свои собственные правила поверх общих
И что в результате? Бесчисленное количество сайтов работают с ненадежными или некорректными политиками кэширования. Из-за этого инфраструктура обходится дороже, пользователи замечают медленную работу сайта, а под нагрузкой такие системы просто ломаются — тогда как грамотно настроенные продолжали бы работать без проблем.
Это руководство создано, чтобы детально разобраться в вопросе. В нем мы подробно рассмотрим, как устроено HTTP-кэширование:
как на самом деле работают заголовки
Cache-Control,Expires,ETagиAge, как по отдельности, так и вместекак браузеры, CDN и кэши на уровне приложений интерпретируют эти заголовки и применяют их на практике
распространенные ошибки и мифы, способные запутать даже опытных разработчиков
практические подходы к кэшированию статических файлов, HTML-документов, API и других видов контента
особенности работы современных браузеров: BFCache, правила предсказания загрузки (speculation rules) и подписанные пакеты (signed exchanges, SXG)
реалии работы CDN: подробный разбор стандартных настроек Cloudflare, его особенностей и расширенных возможностей
как отлаживать и проверять кэширование в реальных условиях
В итоге мы не только разберемся в нюансах работы заголовков HTTP-кэширования, но и научимся создавать и внедрять стратегию кэширования, которая ускорит сайты, снизит расходы и повысит их надежность.
❯ Обоснование кэширования с точки зрения бизнеса
Кэширование важно, потому что оно напрямую влияет на четыре ключевых аспекта работы и масштабируемости сайта:
Скорость
Кэширование сильно сокращает количество лишних сетевых запросов. Если ресурс уже есть в кэше браузера, он загружается практически мгновенно, тогда как обычное обращение к серверу занимает 100–300 мс только на завершение рукопожатия (handshake) и получение первого байта. Умножьте это на десятки ресурсов — и вы получите более плавную загрузку страниц, улучшенные показатели Core Web Vitals и довольных пользователей.
Надежность
При резком росте нагрузки кэш позволяет системе справляться с большим объемом запросов. Если 80% трафика обслуживается на CDN-узле, сервер обрабатывает лишь оставшиеся 20%. Именно это отличает спокойную работу в «черную пятницу» от сбоев при резком всплеске вирусного трафика.
Стоимость
Каждое попадание (hit) в кэш сокращает количество дорогостоящих запросов к серверу. Пропускная способность CDN недорогая, а необработанные кэшем запросы потребляют CPU, обращаются к базе данных и расходуют исходящий трафик — все это требует затрат. Даже небольшое улучшение коэффициента попаданий в кэш на 5–10% может сэкономить тысячи долларов на крупных проектах. И это не учитывая, что часть запросов кэшируется прямо в браузерах пользователей и вообще не достигает CDN.
SEO
Кэширование ускоряет работу сайта и повышает эффективность его индексации поисковыми системами. Боты реже повторно сканируют страницы с корректными кэш-заголовками, что экономит бюджет обхода (crawl budget) для свежего и более динамичного контента. А быстрая загрузка страниц положительно влияет на показатели производительности Google.
Реальные кейсы
Новостной сайт выдерживает всплеск трафика во время срочной новости, потому что 95% запросов обслуживаются из кэша CDN.
API под высокой нагрузкой продолжает стабильно отвечать благодаря использованию
stale-if-errorи валидации черезETag/Last-Modified.Платформа e‑commerce справляется с трафиком «черной пятницы», поскольку статические ресурсы и страницы категорий долго кэшируются на граничных (edge) узлах CDN.
Небольшое отступление: философия кэширования
Следует отметить, что вокруг кэширования существует своеобразная «антикультура». Некоторые разработчики воспринимают его как временную заплатку — способ скрыть медленные системы и замаскировать более серьезные ошибки в дизайне или архитектуре. В идеальном мире каждый запрос обходился бы минимальными ресурсами, каждый ответ был бы мгновенным, и кэширование не требовалось бы вовсе. В этой концепции есть смысл: проектирование систем, изначально работающих быстро, позволяет избежать сложности и уязвимости, которые вносит кэш.
На практике мы редко живем в таком идеальном мире. Реальные системы сталкиваются с непредсказуемыми всплесками нагрузки, большими географическими расстояниями и резкими колебаниями спроса. Даже хорошо спроектированные приложения выигрывают от кэширования, которое действует как усилитель производительности. Главное — соблюдать баланс: кэширование не должно оправдывать низкую базовую производительность, но всегда должно быть частью стратегии масштабирования и поддержания устойчивости при резких скачках трафика.
❯ Кэширование в деталях: кто отвечает за хранение данных
Прежде чем углубляться в тонкости заголовков и директив, стоит понять, кто реально кэширует контент. Кэширование — это не одно действие в одном месте, а целая экосистема слоев с собственными правилами, зонами действия и особенностями.
Браузеры
Каждый браузер использует два типа кэша: памяти и диска.
Кэш памяти (memory cache) очень быстрый, но кратковременный — он «живет» только пока открыта страница. Его цель — исключить повторные сетевые запросы в рамках одной сессии. При этом кэш памяти не управляется заголовками HTTP: даже ресурсы с no-store могут повторно извлекаться из памяти, если на той же странице происходит повторный запрос.
Дисковый кэш (disk cache), напротив, сохраняется между вкладками и сессиями, способен вмещать ресурсы большего объема и учитывает HTTP-заголовки кэширования. Однако при отсутствии метаданных, браузеры все равно могут применять собственные эвристики.
Прокси
Между браузером и интернетом запросы часто проходят через прокси (proxy) — особенно в корпоративных сетях или у провайдеров. Такие прокси могут выступать в роли общих кэшей, сохраняя ответы для экономии трафика или соблюдения корпоративных правил. В отличие от CDN, их обычно не настраивает сам сайт, и их поведение может быть непредсказуемым.
Например, корпоративный прокси может кэшировать загрузки программного обеспечения, чтобы избежать многократной передачи гигабайт данных по одной и той же офисной сети. Провайдер может кэшировать популярные новостные изображения, чтобы страницы загружались быстрее. Проблема в том, что прокси не всегда точно соблюдают HTTP-заголовки кэширования и могут применять собственные эвристики или переопределения. Это может приводить к несоответствиям, например, пользователь за прокси может увидеть устаревший или неполный ответ, даже если он уже должен был обновиться.
Несмотря на то, что прокси менее заметны, чем кэши браузеров и CDN, они остаются важной частью экосистемы. Они напоминают, что владелец сайта не всегда полностью управляет кэшированием, и что промежуточные узлы сети могут влиять на актуальность, повторное использование и корректность данных.
Небольшое отступление: прозрачные прокси провайдеров
В начале 2000-х многие интернет-провайдеры внедряли так называемые «прозрачные» (transparent) прокси, которые кэшировали популярные ресурсы без ведома пользователей и владельцев сайтов. В некоторых регионах такие прокси встречаются и сегодня. Они работают между браузером и сервером незаметно, кэшируя контент по мере возможности для экономии трафика. Минус в том, что они иногда полностью игнорируют заголовки кэширования, и могут отдавать устаревший или неполный контент. Если вы замечали, что сайт ведет себя иначе дома и при использовании мобильного интернета, причиной мог быть именно прозрачный прокси.
Общие кэши
Между пользователями и серверами-источниками существует множество общих кэшей: CDN, вроде Cloudflare или Akamai, прокси провайдеров, корпоративные шлюзы и обратные (reverse) прокси. Эти слои могут значительно снизить нагрузку на сервер, но у каждого своя логика работы, и они иногда переопределяют или по-своему интерпретируют инструкции сервера-источника.
Обратные прокси
Технологии, вроде Varnish или Nginx, выступают локальными ускорителями серверов приложений. Они перехватывают и кэшируют ответы близко к серверу-источнику, сглаживая всплески трафика и снижая нагрузку на приложение и базу данных.
Кэши приложений и баз данных
Внутри инфраструктуры системы, вроде Redis или Memcached, хранят фрагменты готовых страниц, предварительно вычисленные результаты запросов или данные сессии. Они не подчиняются HTTP-заголовкам — ключи и время жизни кэша задаются вручную — но при этом остаются важной частью общей системы кэширования.
❯ Ключи и варианты кэша
Любому кэшу нужен способ определения идентичности запросов. Для этого используется ключ кэша — уникальный идентификатор сохраненного ответа.
По умолчанию ключ формируется из схемы, хоста, пути и строки запроса (query) к ресурсу. Но на деле браузеры учитывают и дополнительные параметры. Большинство из них используют так называемое двойное «ключирование»: в состав ключа также входит контекст верхнего уровня (сайт, на котором вы находитесь). Именно поэтому браузер не может повторно использовать шрифт Google, загруженный на одном сайте, когда другой сайт запрашивает тот же шрифт — для каждого из них создается своя запись.
Современные браузеры постепенно переходят к тройному ключированию, где в ключ закладывается и контекст вложенного фрейма. Это означает, что ресурс, запрошенный внутри iframe, может иметь собственную запись в кэше, отдельную от того же ресурса, запрошенного основной страницей или другим iframe. Такой подход повышает приватность, ограничивая межсайтовое отслеживание через общий кэш, но при этом снижает возможность его повторного использования.
Дополнительную сложность вносит заголовок HTTP Vary. Он сообщает кэшу, что определенные заголовки запроса тоже должны учитываться при формировании ключа.
Примеры:
Vary: Accept-Encoding→ хранить одну копию, сжатую с помощью gzip, и другую — с помощью brotliVary: Accept-Language→ хранить отдельные версии дляen-USиde-DEVary: Cookie→ каждый уникальный куки создает отдельную запись в кэше (что нередко приводит к катастрофическим последствиям)Vary: *→ означает «нельзя безопасно использовать этот ответ для других пользователей», что фактически отключает кэширование
Это мощный инструмент и порой необходимый. Если сервер меняет формат изображения в зависимости от заголовков Accept или отдает AVIF для поддерживающих его браузеров, использование Vary: Accept обязательно — иначе есть риск, что клиенты без поддержки данного формата изображений получат некорректный ответ.
При этом заголовок Vary очень легко применить неправильно. Бездумное добавление Vary: User-Agent, Vary: Cookie или Vary: * способно раздуть кэш до тысяч почти идентичных записей. Ключевой принцип — учитывать только те заголовки, которые действительно меняют ответ.
И здесь выручает нормализация. Современные CDN и прокси могут упрощать ключи кэша, отбрасывая несущественные различия. Например:
игнорировать аналитические параметры в query (
?utm_source=...)объединять все iPhone в один «мобильный» вариант, вместо создания ключа для каждой модели устройства
Главное — варьировать только по тем параметрам, которые действительно влияют на ответ. Все остальное приводит лишь к избыточному дроблению и снижению коэффициента попаданий в кэш.
Небольшое отступление:
No-Vary-SearchНовый экспериментальный заголовок
No-Vary-Searchпозволяет серверам указывать кэшу, какие параметры query можно игнорировать при формировании ключей кэша. Например, можно считать?utm_source=или?fbclid=несущественными, чтобы избежать дробления кэша на тысячи вариантов.На данный момент поддержка ограничена: в Chrome он работает только вместе с правилами предсказания загрузки. Если же заголовок получит более широкое распространение, он может стать стандартным способом нормализации ключей кэша без необходимости полагаться на настройки CDN.
❯ Актуальность и валидации
Понимание того, кто кэширует контент и как определяется, считаются ли два запроса одинаковыми, решает лишь часть задачи. Вторая часть — это определение того, когда сохраненный ответ можно использовать повторно.
Любой кэш, будь то браузер или CDN, должен решить:
Достаточно ли актуальна эта копия, чтобы отдать ее без изменений?
Или она устарела, и нужно проверить актуальность у сервера-источника?
В этом и заключается главный компромисс кэширования: актуальность (отдать сразу, быстро, но с риском устаревших данных) против валидации (проверить на сервере, медленнее, но ответ гарантированно правильный).
Все следующие заголовки — такие как Cache-Control, Expires, ETag и Last-Modified — помогают управлять этим процессом и принимать правильные решения.
❯ Основные заголовки HTTP-кэширования
Теперь, когда мы знаем, кто кэширует контент и как принимаются решения, пора перейти к «основным инструментам» — заголовкам, управляющим кэшированием. Именно их мы используем, чтобы влиять на все уровни системы: браузеры, CDN, прокси и т.д.
В целом, заголовки можно разделить на три категории:
Управление актуальностью: указывают кэшу, как долго ответ можно отдавать без проверки актуальности.
Валидаторы: позволяют быстро проверить, изменился ли ресурс.
Метаданные: описывают, как хранить, индексировать или отслеживать ответ.
Давайте разберем каждую категорию подробнее.
Заголовок Date
Каждый ответ должен содержать заголовок Date. Он отражает время, когда сервер сгенерировал ответ, и служит отправной точкой для всех расчетов актуальности кэша. Если заголовок отсутствует или содержит некорректное время, кэши будут самостоятельно оценивать актуальность ответа.
Заголовок Cache-Control (ответ)
Это самый важный заголовок — своего рода «панель управления» кэшированием контента. Он включает несколько директив, которые можно разделить на две группы:
Директивы актуальности:
max-age— указывает, сколько секунд ответ считается актуальнымs-maxage— аналогичноmax-age, но применяется только к общим кэшам (например, CDN) и переопределяетmax-ageдля нихimmutable— сигнализирует, что ресурс никогда не изменится (идеально для версионированных статических файлов)stale-while-revalidate— позволяет отдавать устаревший ответ, пока параллельно запрашивается актуальныйstale-if-error— позволяет отдавать устаревший контент, если сервер недоступен или возвращает ошибку
Директивы хранения и использования:
public— ответ может храниться любым кэшем, включая общиеprivate— ответ может кэшироваться только браузером, общие кэши использовать его не могутno-cache— хранить, но проверять актуальность перед выдачейno-store— не хранить вообщеmust-revalidate— после устаревания ответ нужно обязательно проверить перед использованиемproxy-revalidate— то же самое, но для общих кэшей
Заголовок Cache-Control (запрос)
Браузеры и клиенты тоже могут отправлять директивы кэширования. Они не изменяют заголовки сервера, но влияют на поведение кэша по пути следования запроса.
no-cache— принудительная проверка актуальности (при этом можно использовать уже сохраненные записи)no-store— полностью игнорировать кэшonly-if-cached— вернуть ответ только из кэша, если он доступен; иначе — ошибка (полезно для работы офлайн)max-age,min-fresh,max-stale— позволяют гибко управлять допустимым временем устаревания ответа
Заголовок Expires
Более старый способ определения актуальности — с помощью абсолютной даты и времени:
пример:
Expires: Wed, 29 Aug 2025 12:00:00 GMTигнорируется, если присутствует
Cache-Control: max-ageподвержен влиянию рассинхронизации часов между сервером и клиентом
все еще широко используется, чаще всего для обратной совместимости
Заголовок Age
Заголовок Age показывает, сколько секунд прошло с момента создания ответа. Он должен устанавливаться общими кэшами, но не все промежуточные узлы делают это корректно. Браузеры его никогда не устанавливают. Можно воспринимать этот заголовок как ориентир, но не как абсолютную истину.
Небольшое отступление:
AgeЗаголовки
Ageвстречаются только у общих кэшей, таких как CDN или прокси. Почему? Браузеры свое внутреннее состояние кэша в сеть не показывают — они просто отдают ответ пользователю. Общие кэши, напротив, должны информировать нижележащие узлы (другие прокси или браузеры) об актуальности ответа, поэтому добавляютAge. Именно поэтому при свежем ответе из CDN мы часто видимAge: 0, а при использовании только кэша браузера — никогда.
Заголовки-валидаторы: ETag и Last-Modified
Когда срок актуальности ответа истекает, кэши используют валидаторы, чтобы не загружать весь ресурс заново.
-
ETag— уникальный идентификатор (непрозрачная строка) для конкретной версии ресурса:сильный
ETag("abc123") означает, что ресурс идентичен побайтовослабый
ETag(W/"abc123") означает, что ресурс семантически тот же, хотя байты могут отличаться (например, после повторного сжатия gzip)
-
Last-Modified— отметка времени последнего изменения ресурса:менее точный, но все равно полезный
поддерживает эвристическую проверку актуальности, когда отсутствуют
max-ageилиExpires
-
Условные запросы:
If-None-Match(сETag) → сервер отвечает304 Not Modified, если ресурс не изменилсяIf-Modified-Since(сLast-Modified) → то же самое, но на основе даты измененияоба способа экономят трафик и снижают нагрузку, так как передаются только заголовки, а не весь ресурс
Небольшое отступление: сильные и слабые
ETag
ETag— это идентификатор конкретной версии ресурса. СильныйETag("abc123") означает полное совпадение побайтово — если хотя бы один бит изменился (например, пробел),ETagтоже должен измениться. СлабыйETag(W/"abc123") означает «семантически аналогичный» — содержимое может незначительно отличаться (например, другое сжатие или порядок атрибутов), но все еще может использоваться повторно.Сильные
ETagдают больше точности, но могут приводить к промахам кэша, если инфраструктура (например, разные серверы за балансировщиком нагрузки) генерирует немного разные варианты ответа. СлабыеETagболее гибкие, но дают меньшую точность. Оба типа работают с условными запросами — выбор зависит от баланса между точностью и практичностью.
Небольшое отступление:
ETagиCache-ControlДирективы
Cache-Controlобрабатываются до проверкиETag. Если кэш определяет, что ресурс устарел, он используетETag(илиLast-Modified) для проверки актуальности у сервера. Представим это так:
пока ресурс актуальный: кэш отдает копию сразу, без проверки
когда ресурс устарел: кэш отправляет
If-None-Match: "etag-value"Если сервер отвечает
304 Not Modified, кэш может продолжать использовать сохраненную копию без повторного скачивания всего ресурса. БезCache-ControlETagможет применяться для эвристической проверки актуальности или для полной перепроверки, что обычно приводит к более частым обращениям к серверу. Эти два механизма работают вместе:Cache-Controlзадает срок жизни ресурса, аETagобеспечивает проверку актуальности.
Заголовок Vary
Заголовок Vary сообщает кэшам, какие заголовки запроса учитывать при формировании ключа кэша. Он позволяет одному URL хранить несколько корректных вариантов ответа.
Например, если сервер отвечает с Vary: Accept-Encoding, кэш хранит одну копию, сжатую gzip, и другую — brotli. Каждое сжатие рассматривается как отдельный объект, и при следующем запросе выбирается подходящая версия.
Такая гибкость полезна, но ее легко использовать неправильно. Например, Vary: * фактически означает «этот ответ нельзя безопасно использовать для других», что делает его некэшируемым в общих кэшах. Аналогично, Vary: Cookie часто снижает коэффициент попаданий в кэш, так как каждый уникальный куки создает отдельную запись.
Оптимальный подход — использовать Vary только там, где это действительно необходимо. Варьируйте только по заголовкам, которые реально меняют ответ. Все остальное лишь дробит кэш, снижает эффективность и усложняет систему.
Инструменты для анализа кэша
Современные кэши не просто принимают решения «за кулисами» — они часто добавляют собственные заголовки для отладки, чтобы было понятно, что произошло.
Самый важный из них — Cache-Status, новый стандарт, который показывает, был ли ответ HIT или MISS, как долго он находится в кэше и иногда даже почему он был повторно проверен. Многие CDN и прокси используют более старый заголовок X-Cache для той же цели, обычно с простым флагом HIT или MISS. Cloudflare двигается вперед с заголовком cf-cache-status, который различает HIT, MISS, EXPIRED, BYPASS и DYNAMIC (и другие значения).
Эти заголовки крайне полезны при настройке или отладке, так как показывают реальные решения кэша, а не только намерения сервера. Ответ может выглядеть кэшируемым на первый взгляд, но если наблюдается постоянный поток MISS или DYNAMIC, это, скорее всего, значит, что промежуточный кэш обрабатывает заголовки не так, как ожидается.
❯ Определение свежести и возраста ответа
Когда стало понятно, кто кэширует контент и какие заголовки влияют на их поведение, следующий шаг — понять, как это работает на практике. Каждый кэш — будь то браузер, CDN или обратный прокси — действует по одной и той же логике:
Определяет, сколько времени ответ считается свежим.
Вычисляет текущий возраст ответа.
Сравнивает эти значения и решает: отдать из кэша, проверить его актуальность или загрузить заново с сервера.
Это та скрытая «математика», которая лежит в основе каждого cache hit и cache miss, с которыми сталкивается сайт.
Период свежести
Период свежести (freshness lifetime) показывает кэшу, как долго можно отдавать ответ без повторной проверки на сервере. Для каждого запроса кэш определяет это, ориентируясь на HTTP-заголовки в следующем порядке:
Cache-Control: max-age(илиs-maxage) → имеет наивысший приоритетExpires→ абсолютная дата, учитывается только еслиmax-ageотсутствуетэвристическая свежесть → если нет ни одного из этих заголовков, кэш выполняет оценку самостоятельно
Пример 1: max-age
Date: Tue, 29 Aug 2025 12:00:00 GMT
Cache-Control: max-age=300
Здесь сервер явно указывает кэшам: «Этот ответ актуален в течение 300 секунд с момента, указанного в заголовке Date». То есть его можно считать свежим до 12:05:00 GMT. После этого он становится устаревшим и требует повторной проверки.
Пример 2: Expires
Date: Tue, 29 Aug 2025 12:00:00 GMT
Expires: Tue, 29 Aug 2025 12:10:00 GMT
Здесь max-age отсутствует, но заголовок Expires задает абсолютное время устаревания. Кэш сравнивает Date (12:00:00) с Expires (12:10:00). Получается окно свежести в 10 минут: ответ считается актуальным до 12:10:00, после чего становится устаревшим.
Пример 3: эвристическая свежесть
Date: Tue, 29 Aug 2025 12:00:00 GMT
Last-Modified: Mon, 28 Aug 2025 12:00:00 GMT
Если нет ни max-age, ни Expires, кэши используют эвристики. Браузеры применяют разные подходы; например, Chrome считает ресурс свежим в течение 10% времени, прошедшего с момента его последнего изменения. В нашем примере ресурс был обновлен 24 часа назад, значит, кэш можно считать свежим около 2,4 часов (примерно до 14:24:00 GMT), после чего потребуется повторная проверка.
Текущий возраст
Текущий возраст (current age) показывает, насколько «старым» кэш считает ответ на данный момент. Спецификация определяет специальную формулу, но ее можно разбить на шаги:
видимый возраст (apparent age) = текущее время −
Date(если результат положительный)скорректированный возраст (corrected age) = максимум из видимого возраста и значения заголовка
Ageвремя нахождения в кэше (resident time) = сколько времени ответ уже находится в кэше
текущий возраст = скорректированный возраст + время нахождения в кэше
Пример 4: простой случай
Date: Tue, 29 Aug 2025 12:00:00 GMT
Cache-Control: max-age=60
Ответ был сгенерирован в 12:00:00 и попал в кэш в 12:00:05, то есть его видимый возраст уже был 5 секунд. Так как заголовок Age отсутствует, кэш удержал его еще 15 секунд. В итоге текущий возраст составил 20 секунд. Поскольку max-age равен 60 секундам, ответ все еще считается свежим.
Пример 5: заголовок Age
Date: Tue, 29 Aug 2025 12:00:00 GMT
Age: 30
Cache-Control: max-age=60
Сервер отдал ответ с меткой времени Date: 12:00:00 и Age: 30, что означает: где-то выше по цепочке кэшей этот ответ уже пролежал 30 секунд. Когда следующий кэш получил его в 12:00:40, возраст ответа был равен 40 секундам. Кэш выбирает большее значение (40 против 30) и прибавляет еще 20 секунд локального хранения — до 12:01:00. В итоге текущий возраст ответа составил 60 секунд, то есть он достиг лимита max-age=60. С этого момента ответ больше не считается свежим и должен пройти повторную проверку.
Дерево решений
Когда кэш знает оба значения (текущий возраст ответа и период его свежести), он действует так:
если текущий возраст < периода свежести → сразу отдает ответ (свежий hit)
если текущий возраст >= периоду свежести →
если задано
stale-while-revalidate→ отдает устаревший ответ, параллельно обновляя его у сервераесли задано
stale-if-errorи сервер недоступен → отдает устаревший ответво всех остальных случаях → обращается к серверу для повторной проверки (условный запрос GET/HEAD)
Пример 6: stale-while-revalidate
Cache-Control: max-age=60, stale-while-revalidate=30
Ответ содержит заголовок Cache-Control: max-age=60, stale-while-revalidate=30.
В 12:01:10 копия находится в кэше уже 70 секунд — то есть она на 10 секунд старше допустимого периода свежести. Обычно в такой ситуации кэш обязан проверить ресурс у сервера, прежде чем отдать его, но благодаря директиве stale-while-revalidate он может сразу выдать устаревшую копию и параллельно запросить обновление. Поскольку копия «просрочена» всего на 10 секунд из разрешенных 30, кэш спокойно отдает имеющийся ответ и одновременно обновляет его.
Пример 7: stale-if-error
Cache-Control: max-age=60, stale-if-error=600
Ответ содержит заголовок Cache-Control: max-age=60, stale-if-error=600.
В 12:02:00 копия находится в кэше уже 120 секунд — период свежести в 60 секунд давно прошел. Кэш делает запрос к серверу за новой версией, но получает ошибку 500. В этом случае директива stale-if-error разрешает отдавать устаревшую копию еще в течение 600 секунд, пока сервер недоступен. Так пользователь получает ответ, несмотря на то, что сервер временно не работает.
Почему это важно
Понимание «математики» кэша объясняет многие странные и непонятные ситуации:
ресурс «просрочен слишком рано»? Часто причина состоит в коротком
max-ageили ненулевом заголовкеAgeответ выглядит устаревшим, но все равно отдан пользователю? Возможно, срабатывают
stale-while-revalidateилиstale-if-errorстатус
304 Not Modifiedвовсе не означает, что кэш не сработал — это значит, что кэш корректно проверил актуальность и сэкономил трафик
Кэши — не мистические «черные ящики». Они просто выполняют эти вычисления тысячи раз в секунду для миллионов ресурсов. Как только понимаешь логику, поведение становится предсказуемым и управляемым. На практике же разработчики часто спотыкаются о тонкие настройки и вводящие в заблуждение названия директив. Разберемся с этими заблуждениями прямо сейчас.
❯ Распространенные заблуждения и мифы
Даже опытные разработчики порой неверно настраивают кэширование. Директивы бывают тонкими, значения по умолчанию — странными, а их взаимодействие можно неправильно интерпретировать. Вот самые типичные ловушки.
no-cache ≠ «не кэшировать»
Название вводит в заблуждение. На самом деле no-cache означает «хранить этот ответ, но проверять его актуальность перед повторным использованием». Браузеры и CDN сохранят копию, но всегда будут сверяться с сервером перед отдачей. Для полного запрета хранения, применяется no-store.
no-store — ничего не сохраняется
no-store — радикальный вариант. Он запрещает любому кэшу — браузеру, прокси или CDN — сохранять копию ответа. Каждый запрос идет напрямую к серверу. Такой подход оправдан только для особо чувствительных данных (например, банковских), а в большинстве случаев избыточен. Многие сайты включают его по умолчанию, из-за чего страдает производительность.
max-age=0 и must-revalidate
На первый взгляд похожие директивы, но между ними есть разница. max-age=0 означает «ответ устарел сразу после получения». Без must-revalidate кэш технически может кратковременно использовать такой ответ в некоторых случаях (например, если сервер временно недоступен). Добавление must-revalidate убирает эту возможность, заставляя кэш всегда проверять актуальность у сервера после устаревания ресурса.
s-maxage и max-age
max-age применяется везде — и в браузерах, и в общих кэшах. s-maxage действует только для общих кэшей, таких как CDN или прокси, и при этом переопределяет для них max-age. Это позволяет установить короткое время свежести для браузеров (например, max-age=60), но более длительное для CDN (s-maxage=600). Многие разработчики даже не знают о существовании s-maxage.
Неправильное использование immutable
immutable сообщает браузерам: «Этот ресурс никогда не изменится, не нужно проверять его актуальность». Отлично подходит для версионированных файлов с отпечатком (fingerprint), например app.9f2d1.js. Но опасно для HTML или любых ресурсов, которые могут измениться по тому же URL. Применение immutable к таким ресурсам может привести к тому, что пользователи будут долго видеть устаревший контент.
Кэширование перенаправлений и ошибок
Кэши могут хранить редиректы и даже ответы с ошибками. Например, 301 кэшируется по умолчанию (часто навсегда). Даже 404 или 500 могут храниться в кэше короткое время в зависимости от заголовков и настроек CDN. Разработчиков нередко удивляет, что «временные» сбои продолжают отображаться, потому что ответ с ошибкой был закэширован.
Сбои из-за рассинхронизации часов и эвристик
Кэши используют заголовки Date, Expires и Age для оценки свежести. Если часы на серверах и клиентах не синхронизированы или явные заголовки отсутствуют, кэш полагается на эвристики. Это может приводить к неожиданным ситуациям с истечением периода свежести. Явные директивы свежести всегда надежнее.
Фрагментация кэша: устройства и география
Кэширование просто, когда один URL соответствует одному ответу. Сложности появляются, если ответы различаются в зависимости от устройства или региона пользователя.
Разделение по устройствам: сайты часто отдают разный HTML или JS для десктопной и мобильной версий. Если ключ кэша формируется на основе
User-Agent, каждая комбинация браузера и версии создает отдельную запись, что существенно снижает коэффициент попаданий. Более надежные варианты — нормализоватьUser-Agentна уровне CDN или использовать Client Hints (Sec-CH-UA,DPR) с контролируемыми заголовкамиVary.Разделение по географии: разный контент для регионов (например, Индия или Германия) часто определяется заголовком
Accept-Languageили GeoIP-правилами. Каждая языковая комбинация (en,en-US,en-GB) создает новый ключ кэша. Без нормализации по региону или правилу кэш фрагментируется на тысячи вариантов.
Вывод простой: чем выше уровень персонализации, тем ниже эффективность кэширования. Как только эти нюансы станут понятны, можно перейти от теории к практике.
На этом первая часть руководства завершена. До встречи в следующей части.
Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud — в нашем Telegram-канале ↩
puzo
Огонь! все что Вы хотели узнать но стеснялись спросить!