MAATRIX / Блог / Что происходит на CDN, когда кеш промахнулся, и почему промахов больше, чем кажется

Что происходит на CDN, когда кеш промахнулся, и почему промахов больше, чем кажется

MAATRIX

Когда речь заходит о CDN, обычно говорят о хит-рейте: чем он выше, тем лучше работает кеш. Но почти никто не объясняет, что конкретно происходит в момент, когда кеш промахивается — а ведь именно в эти секунды формируется задержка, которую видит пользователь, и нагрузка, которую получает ваш origin-сервер. И тут есть контринтуитивный момент: промахов на CDN объективно больше, чем кажется на первый взгляд, потому что у сети точек присутствия нет единого общего кеша — у каждой точки свой, и она ничего не знает о соседях.

Что вообще значит "промах" на конкретном узле CDN

CDN — это не один большой кеш, размазанный по планете, а множество независимых узлов (их называют точками присутствия, PoP — point of presence), каждый из которых обслуживает запросы пользователей, оказавшихся к нему ближе всего по сети. У каждого такого узла — свой локальный кеш: свой диск или память, своя таблица объектов, свой TTL-счётчик на каждый объект.

Когда запрос долетает до конкретного узла, происходит простая проверка: есть ли у меня валидная копия этого объекта прямо сейчас. Если есть — это хит (cache hit), узел отдаёт данные из локального хранилища, и происходит это быстро, потому что запрос не покидает точку присутствия. Если копии нет или она признана невалидной — это промах (cache miss), и узел обязан получить актуальные данные откуда-то ещё, прежде чем ответить пользователю.

Промах — это не поломка и не редкое исключение. Это штатное состояние узла в трёх типовых ситуациях: объект запрашивают на этом узле впервые, TTL закешированной копии истёк, или кеш был явно инвалидирован (например, вы вызвали purge после деплоя). Разница между этими тремя причинами важна: она определяет, сколько раз подряд один и тот же объект придётся тянуть с origin, и об этом дальше.

Путь запроса при промахе: от edge-узла до origin и обратно

Разберём по шагам, что делает узел CDN, столкнувшись с промахом:

  1. Проверка локального кеша. Узел вычисляет ключ кеша (обычно это комбинация домена, пути, иногда — части query-строки и заголовков) и ищет по нему запись. Записи нет или она просрочена — фиксируется промах.
  2. Формирование запроса к origin. Узел собирает исходящий HTTP-запрос к вашему серверу, добавляя служебные заголовки — как минимум X-Forwarded-For с реальным IP клиента, часто X-Forwarded-Proto, заголовок с идентификатором самого CDN-узла или региона. Ваш origin в этот момент видит запрос не от пользователя, а от инфраструктуры CDN.
  3. Ожидание ответа origin. Здесь появляется задержка, которую вы могли замечать в мониторинге — TTFB на промахе всегда выше, чем на хите, потому что добавляется полное сетевое плечо до вашего сервера и обратно, плюс время генерации ответа на стороне origin.
  4. Проверка кешируемости ответа. Узел смотрит на заголовки ответа — прежде всего Cache-Control и Vary — и решает, можно ли вообще сохранить этот ответ, и если да, то на сколько и с каким ключом.
  5. Запись в локальный кеш и отдача клиенту. Только после этого объект появляется в кеше именно этого узла — и именно на нём, а не на всех остальных точках присутствия сети.

Отдельно стоит упомянуть механизм, который многие CDN включают по умолчанию — коалесцирование запросов (request collapsing / cache lock). Если на один и тот же промахнувшийся объект в узел одновременно прилетело множество запросов, узел не пошлёт на origin столько же параллельных обращений. Он пошлёт одно, остальные поставит в очередь ожидания и раздаст им общий результат, как только он придёт. Без этого механизма всплеск интереса к новому объекту на одном узле мог бы устроить origin небольшую атаку "спасибо себе самому".

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Почему точек присутствия много, и у каждой свой кеш — это и есть источник лишних промахов

Вот ключевая вещь, которая обычно остаётся за кадром. У CDN не два-три узла, а десятки или сотни точек присутствия по миру — и это архитектурно правильно: чем больше точек, тем ближе к пользователю оказывается ближайшая из них, тем ниже сетевая задержка. Но у этой близости есть цена — она напрямую связана с частотой промахов.

Каждая точка присутствия хранит свою копию кеша независимо от остальных. Между узлами нет автоматической синхронизации содержимого кеша (в отличие от инвалидации — команды purge обычно распространяются на всю сеть). Это значит: если объект уже давно и стабильно закеширован на узле во Франкфурте, для узла в Сингапуре, Сан-Паулу или Йоханнесбурге это ничего не меняет. Первый пользователь, чей запрос попадёт именно на этот узел, всё равно получит промах — узел прежде не видел этот объект и обязан пойти за ним на origin.

Отсюда и контринтуитивный вывод: чем больше у CDN точек присутствия (а это то, за что вы, по сути, платите — за близость к пользователям по всему миру), тем больше независимых "первых промахов" произойдёт для одного и того же объекта, просто потому что у каждой точки свой изолированный кеш, который нужно прогреть с нуля. Плюс к этому добавляется распределение трафика: узлы с низкой посещаемостью прогреваются медленнее и куда чаще теряют объекты по TTL раньше, чем к ним придёт следующий запрос на этот же объект — кеш попросту не успевает "устояться".

Практическое следствие: если вы смотрите только на общий хит-рейт сети (агрегированный по всем узлам), картина выглядит благополучно. Но если разбить метрику по отдельным точкам присутствия, разброс может быть заметным — у популярных региональных узлов хит-рейт заметно выше, чем у узлов с редким трафиком, и это нормальная особенность архитектуры, а не повод считать конфигурацию сломанной.

Подробнее о том, как устроена CDN изнутри и где физически хранятся кешированные файлы, можно почитать в статье что такое CDN и где лежит картинка.

Shield-слой: как сократить число прямых обращений к origin

Раз каждая точка присутствия промахивается независимо, при достаточно широкой сети точек и не самом горячем контенте origin рискует получать заметно больше прямых запросов, чем "один на объект". Для этого у большинства крупных CDN есть механизм промежуточного кеширования — origin shield (иногда называется просто shield или tiered caching).

Идея простая: вместо того чтобы каждая точка присутствия при промахе шла напрямую на origin, назначается один или несколько промежуточных узлов (обычно географически ближе к вашему origin-серверу), через которые обязаны проходить все запросы-промахи с остальных точек. Тогда картина меняется:

  • Первый промах любой точки присутствия по-прежнему уходит дальше — но не сразу на origin, а на shield-узел.
  • Shield-узел проверяет свой собственный кеш. Если объект там уже есть (потому что его запросил другой edge-узел раньше) — shield отдаёт его сам, и origin вообще не узнаёт об этом запросе.
  • Только если объекта нет и на shield-узле, запрос наконец доходит до origin.

Получается двухуровневая иерархия кеша: локальный кеш на каждой точке присутствия и общий "зонтичный" кеш на shield-слое, который агрегирует промахи со многих точек в один поток к origin.

СхемаКто обращается к origin при промахеЧто это значит для вашего сервера
Без shieldКаждая точка присутствия — независимо и напрямуюOrigin видит отдельный запрос от каждой первой точки, которая столкнулась с промахом
С shieldТолько shield-узел(-ы)Origin получает уже агрегированный поток: один промах на shield закрывает промахи сразу нескольких edge-узлов

Важная оговорка: shield не устраняет промахи вообще — первый запрос к самому shield-узлу всё равно будет промахом. Он снижает количество *прямых* обращений к origin и делает прогрев кеша более предсказуемым: вместо N независимых "первых запросов" (по числу точек присутствия) origin в норме увидит существенно меньше — обычно единицы обращений на объект, а не десятки. Но это качественная тенденция, а не гарантированная цифра — конкретное соотношение зависит от того, как CDN балансирует точки присутствия за конкретным shield-узлом, и это стоит смотреть в документации именно вашего провайдера, а не считать универсальной константой.

Если у вас нет доступа к shield-функции CDN (она не всегда есть в бюджетных тарифах) или вы хотите похожий эффект перед собственным origin, аналогичную роль частично может сыграть кеширующий reverse-proxy прямо на границе вашей инфраструктуры — например, nginx с proxy_cache перед приложением. Как его настроить, разобрано в статье настройка кеширования nginx на VPS.

Что реально управляет частотой промахов

Промахи — не константа, они управляемы, и вот основные рычаги, которые на них влияют:

TTL и заголовки Cache-Control. Чем короче время жизни объекта в кеше, тем чаще он будет "протухать" и требовать повторного похода на origin — даже на узлах, где трафик стабильный. Разумный s-maxage (директива, которую понимают именно кеширующие прокси, в отличие от max-age, ориентированного на браузер) напрямую снижает частоту промахов по TTL:

Cache-Control: public, max-age=300, s-maxage=86400, stale-while-revalidate=60

Здесь браузер держит копию 5 минут, а CDN — сутки, и stale-while-revalidate позволяет узлу отдать чуть устаревшую версию, пока в фоне идёт обновление, вместо того чтобы заставлять пользователя ждать полного цикла похода на origin.

Состав ключа кеша. Если в ключ кеша попадают заголовки или параметры, которые варьируются от запроса к запросу (например, весь Cookie, часть User-Agent, случайные query-параметры аналитики вроде utm_*), формально одинаковый контент превращается в множество разных "объектов" с точки зрения кеша — и каждый из них промахивается по отдельности. Заголовок Vary работает похожим образом: Vary: Accept-Encoding, Cookie расширяет пространство возможных ключей кеша для одного и того же URL.

Инвалидация и деплои. Каждый вызов purge (явный или через смену версии в пути файла) обнуляет накопленный прогрев — все последующие запросы к этому объекту на всех узлах снова становятся промахами, пока кеш не прогреется заново. Если вы деплоите часто и делаете полный purge при каждом релизе, вы фактически держите сеть в состоянии постоянного холодного старта для затронутых объектов.

Форма распределения трафика (long tail). Небольшое число очень популярных объектов (главная страница, ключевые статичные ассеты) кешируется почти везде и почти всегда остаётся хитом. Но у большинства сайтов основная масса контента — это длинный хвост из редко запрашиваемых страниц и файлов, для которых TTL нередко истекает раньше, чем придёт второй запрос на том же узле. Такой контент структурно обречён давать больше промахов независимо от настроек — это не проблема конфигурации, а свойство распределения спроса.

Как подготовить origin-сервер к промахам

Раз промахи неизбежны и их частота зависит от количества точек присутствия, TTL и структуры трафика, разумнее готовить сервер к ним заранее, а не удивляться постфактум всплескам нагрузки после деплоя.

Считайте реальную нагрузку от промахов, а не только общий трафик сайта. Ориентируйтесь не на общий объём посещений, а на комбинацию: число уникальных объектов × число точек присутствия/shield-узлов, которые их реально запрашивают, деленное на длительность TTL. Точную цифру заранее не предскажете, но порядок величины прикинуть можно — и он почти всегда выше, чем "просто разделить трафик на хит-рейт".

Держите origin готовым к параллельным запросам. Коалесцирование на CDN спасает от дублей *в рамках одного узла*, но не от параллельных промахов *с разных* узлов или shield-серверов одновременно — особенно сразу после деплоя или массового purge. Настройте разумные лимиты на количество одновременных соединений и keep-alive к апстриму, чтобы всплеск не укладывал сервер.

Используйте stale-if-error и негативное кеширование осознанно. Если origin временно недоступен или отвечает ошибкой, разумно настроенный CDN может отдать устаревшую, но валидную копию вместо ошибки — директива stale-if-error в Cache-Control для этого и существует. А вот с кешированием самих ошибок (404, 5xx) нужно быть аккуратнее: слишком долгий TTL на "отрицательный" ответ иногда держит недоступным давно восстановившийся ресурс дольше, чем длилась сама проблема — эта ловушка разобрана в статье кеш отрицательных ответов держал сайт мёртвым час.

Проверьте, что заголовки кеширования вообще присутствуют и осмысленны. Частая причина завышенной частоты промахов — не архитектура CDN, а то, что origin вовсе не отдаёт Cache-Control для кешируемого контента, и CDN честно перепроверяет объект на каждый чих. Если у вас ещё не настроен CDN перед сервером в принципе, порядок настройки — от выбора зоны до заголовков — описан в статье настройка CDN перед сервером.

Мониторьте хит-рейт по точкам присутствия, а не только агрегированно. Как уже говорилось выше, общий хит-рейт сглаживает картину. Если панель вашего CDN позволяет смотреть метрики по регионам или отдельным PoP — используйте это, чтобы отличить "у нас в принципе мало трафика в этом регионе, промахи там — норма" от "у нас проблема с конфигурацией кеша конкретно для этой географии".

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Если объект давно закеширован на одной точке присутствия, почему в другом регионе он снова "промахивается"?

Потому что у каждой точки присутствия собственный локальный кеш, изолированный от остальных. Синхронизации содержимого кеша между точками нет (кроме команд инвалидации, которые распространяются на всю сеть) — прогрев на одном узле никак не ускоряет прогрев на другом.

Означает ли высокая частота промахов, что CDN настроен неправильно?

Не обязательно. Часть промахов — структурная особенность архитектуры: много точек присутствия и длинный хвост редко запрашиваемого контента дают промахи даже при идеальных заголовках кеширования. Стоит разбираться, если промахи растут именно там, где ожидается стабильно горячий контент, или если хит-рейт заметно хуже, чем у сравнимых по трафику проектов.

Shield-слой полностью убирает нагрузку промахов с origin?

Нет, он её сокращает, агрегируя промахи с нескольких точек присутствия в меньшее число обращений к shield-узлу, а тот уже реже ходит на origin. Первый запрос к самому shield-узлу всё равно останется промахом и дойдёт до вашего сервера.

Как отличить промах из-за первого обращения от промаха из-за истёкшего TTL?

Напрямую по одному ответу это не всегда видно, но большинство CDN добавляют служебный заголовок вроде X-Cache: MISS или аналог с указанием причины (в зависимости от провайдера — cf-cache-status, x-cache-status и т.п.), а в детальных логах можно сопоставить время предыдущего хита по этому же ключу с текущим TTL.

Стоит ли вручную "прогревать" кеш после деплоя вместо того, чтобы ждать органических промахов?

Для критичных страниц и ассетов — да, это разумно: сделать несколько запросов к ключевым URL сразу после purge, желательно с разных географий или через API прогрева, если провайдер его поддерживает, чтобы не отдавать первым живым пользователям заведомо самый медленный путь через origin.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →