CDN в системном дизайне: edge, origin shield, Cache-Control и инвалидация

Как устроена CDN: edge, PoP, origin и origin shield, push и pull, заголовки Cache-Control и ETag, версионирование ассетов, динамический контент и оценка трафика на system design интервью.

14 мин чтениясистемный дизайн · cdn · кэширование · http

Коротко

  • CDN — это географически распределённый HTTP-кэш. Пользователь получает ответ от ближайшей точки присутствия (PoP), а до вашего сервера (origin) доходит только промах.
  • Pull — режим по умолчанию. Edge сам забирает файл с origin при первом запросе. Push нужен, когда контент крупный, заранее известный и его нельзя потерять, например видео.
  • Поведением CDN управляет origin через заголовки. Cache-Control, s-maxage, ETag, Vary решают, что кэшировать, сколько и для кого.
  • Статику не инвалидируют, а версионируют. Хеш содержимого в имени файла плюс max-age=31536000, immutable. Purge оставляют для HTML и аварий.
  • CDN не спасёт от медленного персонализированного API, записей и промахов по длинному хвосту. На интервью её ставят перед статикой, медиа и публичными страницами, а потом считают трафик и долю попаданий.

Общие стратегии кэша — cache-aside, write-through, stampede, горячие ключи — разобраны в статье о стратегиях кэширования. Здесь — только HTTP-кэш на краю сети.

Зачем вообще нужна CDN

Сервер стоит во Франкфурте, пользователь — в Новосибирске. Даже по идеальному кабелю свет идёт туда и обратно десятки миллисекунд, а реальный маршрут длиннее. Новое HTTPS-соединение требует нескольких таких кругов: TCP-рукопожатие, TLS, сам запрос. Страница с полусотней ресурсов превращается в секунды ожидания, которые не исправить ни быстрым кодом, ни мощным железом.

CDN решает три задачи одновременно:

  1. Задержка. Соединение заканчивается в городе пользователя, а не на другом континенте.
  2. Пропускная способность. Терабайты картинок и видео отдаёт сеть провайдера, а не ваши серверы и не ваш исходящий канал.
  3. Защита и сглаживание пиков. Сеть CDN гораздо шире любого одного дата-центра, поэтому она поглощает всплески и объёмные DDoS-атаки.

Побочные плюсы — терминация TLS ближе к клиенту, поддержка HTTP/2 и HTTP/3 без изменений на origin, сжатие и оптимизация изображений на краю.

Как устроена CDN

Edge, PoP и origin

  • Origin — источник истины: ваш веб-сервер, объектное хранилище вроде S3, сервер упаковки видео.
  • PoP (point of presence) — площадка CDN в конкретном городе или у конкретного оператора связи. Внутри — десятки или сотни серверов.
  • Edge-сервер — сервер в PoP, который принимает соединение пользователя и отдаёт ответ из своего кэша.

Путь запроса выглядит так:

Браузер → ближайший PoP (edge) → [origin shield] → origin hit: ответ сразу промах edge промах shield

Если в кэше edge есть свежая копия — это hit, ответ уходит сразу. Если нет — miss: edge идёт дальше, получает ответ, сохраняет и отдаёт клиенту.

Как пользователь попадает в ближайший PoP

Два основных механизма:

  • DNS-маршрутизация. Авторитетный DNS CDN смотрит, откуда пришёл запрос, и возвращает IP ближайшего или наименее загруженного PoP. Минус — он видит резолвер, а не пользователя. Расширение EDNS Client Subnet частично это исправляет.
  • Anycast. Один и тот же IP анонсируется по BGP из многих PoP, и сеть сама доставляет пакет в «ближайший» по маршрутизации. Переключение при отказе PoP происходит на уровне BGP, без ожидания TTL в DNS.

Многие крупные CDN сочетают оба подхода. На интервью достаточно назвать оба и сказать, чем они отличаются при отказе. Подробнее о распределении трафика внутри одного дата-центра — в статье о балансировке нагрузки.

Origin shield: ещё один уровень кэша

У CDN могут быть сотни PoP. Если файл популярен везде, каждый PoP при первом промахе пойдёт в origin. Сто PoP — сто одинаковых запросов, а после истечения TTL — ещё сто.

Origin shield — выделенный промежуточный уровень (один PoP или региональный слой), через который проходят все промахи edge. Edge спрашивает shield, и только промах shield доходит до origin. Провайдеры называют это по-разному: shielding, tiered cache, regional edge cache — суть одна.

Небольшая симуляция показывает эффект. Тридцать PoP, 20 000 файлов с популярностью по закону Ципфа (несколько популярных, длинный хвост редких), TTL — час:

import random random.seed(42) N_OBJECTS = 20_000 # разных файлов N_POPS = 30 # точек присутствия N_REQUESTS = 300_000 TTL = 3_600 # секунд DURATION = 3_600 * 6 # шесть часов трафика

Популярность по Ципфу: k-й файл запрашивают пропорционально 1/k

weights = [1 / k for k in range(1, N_OBJECTS + 1)] objects = random.choices(range(N_OBJECTS), weights=weights, k=N_REQUESTS) times = sorted(random.uniform(0, DURATION) for _ in range(N_REQUESTS)) pops = [random.randrange(N_POPS) for _ in range(N_REQUESTS)]

def fresh(cache, key, now): """Есть ли в кэше свежая копия; если нет — кладём новую.""" expires = cache.get(key) if expires is not None and expires > now: return True cache[key] = now + TTL return False

def simulate(with_shield): edges = [dict() for _ in range(N_POPS)] shield = {} edge_hits = origin = 0 for obj, now, pop in zip(objects, times, pops): if fresh(edges[pop], obj, now): edge_hits += 1 elif not with_shield or not fresh(shield, obj, now): origin += 1 return edge_hits / N_REQUESTS, origin

for flag in (False, True): hit, origin = simulate(flag) name = "с shield " if flag else "без shield" print(f"{name}: hit ratio на edge {hit:.1%}, запросов в origin {origin}")


```text
без shield: hit ratio на edge 53.0%, запросов в origin 140922
с shield : hit ratio на edge 53.0%, запросов в origin 48702

Hit ratio на edge не изменился — shield ему и не помогает. Зато нагрузка на origin упала почти втрое: промахи разных PoP по одному файлу сошлись в одном месте. Цена — лишний сетевой переход на промахе и плата за сам shield у некоторых провайдеров. Shield располагают ближе к origin, чтобы этот переход был коротким.

Второй приём того же рода — склеивание запросов (request coalescing, collapsed forwarding). Если в PoP одновременно пришла тысяча запросов за файл, которого нет в кэше, в origin уходит один, остальные ждут его ответа. Это та же защита от stampede, что и single-flight в прикладном кэше, только встроенная в CDN.

Pull и push

Pull (origin pull)Push (storage)
Как попадает на CDNEdge забирает с origin при первом промахеВы заранее загружаете файлы в хранилище CDN
Первый запросМедленный: идёт до originБыстрый, если файл уже разложен
ОбновлениеИстечение TTL или purgeПерезаливка файла
Нужен originДа, всегда доступныйНет, хранилище CDN и есть origin
Подходит дляСайты, API, статика с частыми изменениямиВидео, дистрибутивы, крупные редко меняющиеся файлы
РискХолодный кэш после деплоя, нагрузка на originНужно самим управлять загрузкой и удалением

На практике граница размыта: «push» часто означает, что вы кладёте файлы в объектное хранилище, а CDN тянет из него в режиме pull. Для интервью важнее мысль: pull прост и сам подстраивается под популярность, push даёт контроль и предсказуемость для тяжёлого контента.

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

Заголовки: как origin управляет CDN

CDN — это общий (shared) HTTP-кэш, и правила его работы описаны в HTTP Caching, RFC 9111. Origin почти всё сообщает заголовками ответа.

Cache-Control

ДирективаЧто значит
max-age=NОтвет свежий N секунд для любого кэша
s-maxage=NТо же, но только для общих кэшей (CDN, прокси); перекрывает max-age
publicМожно кэшировать в общих кэшах, даже если есть Authorization
privateТолько в браузере пользователя, CDN не хранит
no-cacheХранить можно, но перед каждой отдачей нужно перепроверить у origin
no-storeНе сохранять вообще
immutableСодержимое по этому URL никогда не изменится, перепроверять не нужно
stale-while-revalidate=NN секунд после истечения можно отдавать старое и обновлять в фоне
stale-if-error=NЕсли origin отвечает ошибкой, N секунд можно отдавать старое

Частая ошибка — путать no-cache и no-store. no-cache не запрещает кэш, а требует перепроверки. Для персональных данных нужен private или no-store.

Разделение max-age и s-maxage позволяет держать HTML в CDN минуту, а в браузере не держать вовсе:

Cache-Control: public, max-age=0, s-maxage=60, stale-while-revalidate=30

Браузер каждый раз спрашивает CDN, CDN раз в минуту спрашивает origin, а в окне stale-while-revalidate пользователь не ждёт обновления. Некоторые CDN дополнительно понимают отдельные заголовки только для себя: Surrogate-Control или стандартизованный CDN-Cache-Control (RFC 9213). Поддержку конкретной директивы у своего провайдера стоит проверить в документации.

ETag и условные запросы

Когда копия устарела, не обязательно скачивать файл заново. Origin при первой отдаче ставит валидатор:

HTTP/1.1 200 OK ETag: "a1b2c3" Last-Modified: Tue, 03 Nov 2026 10:00:00 GMT

Кэш перепроверяет копию условным запросом:

GET /catalog.json If-None-Match: "a1b2c3"

Если ничего не изменилось, origin отвечает 304 Not Modified без тела, и кэш продлевает старую копию. Экономится трафик, но не круг до origin. Поэтому ETag — дополнение к max-age, а не замена.

Подвох: если у вас несколько серверов origin и ETag строится из inode или времени изменения файла на конкретной машине, у одинакового файла будут разные ETag. Строй ETag из хеша содержимого.

Ключ кэша и Vary

Кэш хранит ответ под ключом. По умолчанию это метод, хост и URL со строкой запроса. Отсюда две классические проблемы:

  • Мусор в query string. ?utm_source=... делает каждый переход из рассылки уникальным ключом, и hit ratio падает. Лишние параметры исключают из ключа в настройках CDN.
  • Vary. Заголовок Vary: Accept-Encoding говорит, что gzip- и brotli-версии — разные ответы. Это нормально. А Vary: User-Agent или Vary: Cookie дробит кэш на тысячи вариантов и фактически его выключает.

Отдельная опасность — cache poisoning и утечки. Если ответ зависит от заголовка, которого нет в ключе, первый запрос с «плохим» значением отравит кэш для всех. Если персональную страницу случайно отдали с public, CDN раздаст чужой профиль. Правило простое: всё, от чего зависит ответ, должно быть в ключе, а всё персональное — private.

Инвалидация и версионирование

Версионирование статики

Лучшая инвалидация — та, которая не нужна. Сборщики фронтенда добавляют хеш содержимого в имя файла:

import hashlib from pathlib import Path def hashed_name(path: Path) -> str: digest = hashlib.sha256(path.read_bytes()).hexdigest()[:10] return f"{path.stem}.{digest}{path.suffix}" css = Path("app.css") css.write_text("body { color: #222 }") print(hashed_name(css)) css.write_text("body { color: #111 }") print(hashed_name(css))
app.2d29e699c0.css app.7ef9a23474.css

Изменился файл — изменился URL. Значит, старую версию можно кэшировать вечно:

Cache-Control: public, max-age=31536000, immutable

А HTML, который ссылается на эти файлы, кэшируют коротко или с перепроверкой. После деплоя новый HTML ссылается на новые имена, и браузеры с CDN подтягивают их как новые объекты. Старые файлы не удаляют сразу: пользователи с открытой вкладкой ещё могут их запросить.

Хеш лучше версии вида ?v=12: он меняется только при реальном изменении содержимого, и неизменившиеся файлы остаются в кэше после релиза.

Purge

Для того, что нельзя версионировать — HTML, картинки по постоянным URL, ответы API, — у CDN есть API очистки:

  • По URL или префиксу. Удалить /products/42 или всё под /products/.
  • По тегам (surrogate keys, cache tags). Origin помечает ответ заголовком со списком тегов, например product-42 category-7. Изменился товар — очищаем всё, что с ним связано, одним вызовом. Доступность функции зависит от провайдера и тарифа.
  • Полная очистка. Аварийный вариант: весь трафик разом пойдёт в origin.

Purge не мгновенный: распространение по всем PoP занимает от долей секунды до минут в зависимости от провайдера. Некоторые CDN ограничивают частоту вызовов или берут плату за пути сверх лимита. Поэтому строить консистентность на purge нельзя: TTL всё равно задаёт верхнюю границу устаревания.

Soft purge помечает объект устаревшим вместо удаления. Вместе с stale-while-revalidate это защищает origin от волны промахов сразу после очистки.

Динамический контент

«CDN только для статики» — устаревшее представление. Вот что она даёт для динамики:

  1. Микрокэширование. Публичная страница каталога или лента новостей с s-maxage=1–10. При тысяче запросов в секунду на страницу origin видит один запрос в несколько секунд — и пользователи получают данные возрастом в пару секунд.
  2. Ускорение некэшируемых запросов. Даже при no-store соединение с клиентом заканчивается в ближайшем PoP. До origin CDN держит заранее открытые и прогретые соединения по своей магистрали, поэтому TLS-рукопожатие и потери на «последней миле» не повторяются через весь мир.
  3. Edge compute. Небольшой код на краю сети: редиректы, A/B-разметка, проверка JWT, подстановка заголовков, сборка страницы из кэшируемых кусков. Такие платформы есть у большинства крупных CDN. Ограничения — время выполнения, память, доступ к данным: база всё равно далеко.
  4. Раздельный кэш для анонимов и залогиненных. Анонимы получают кэшированную страницу, а персональные блоки (корзина, имя) подгружаются отдельным запросом с private.

Для приватных файлов (купленные курсы, документы) используют подписанные URL или подписанные cookie: ссылка содержит срок жизни и подпись, edge проверяет её сам и отдаёт файл из кэша, не обращаясь к сервису авторизации.

Видео и крупные файлы

Видео — главный потребитель CDN. Современный стриминг (HLS, DASH) режет ролик на сегменты по несколько секунд в нескольких битрейтах. Каждый сегмент — обычный статический файл с неизменяемым URL, и кэшируется идеально. Манифест плейлиста для live-трансляции кэшируют на секунды.

Крупные файлы CDN обычно хранит и отдаёт кусками через Range-запросы, чтобы не тянуть весь гигабайт с origin ради первых мегабайт.

Когда CDN не поможет

  • Персонализированный API. Ответ для каждого пользователя свой, hit ratio около нуля. Остаётся только выигрыш от терминации соединения. Ускорять нужно сам сервис и его кэш данных.
  • Записи. POST и PUT проходят насквозь. Если узкое место — запись в базу, нужны шардирование и репликация или очередь.
  • Длинный хвост. Миллионы редко запрашиваемых объектов (старые фото в соцсети) вытесняются из edge раньше, чем их запросят повторно. Помогает shield и многоуровневый кэш, но origin всё равно должен держать заметную нагрузку.
  • Требование строгой свежести. Остаток на складе или баланс счёта нельзя показывать с задержкой TTL плюс время purge.
  • Пользователи рядом с origin. Если все клиенты в одном городе с дата-центром, выигрыш по задержке мал. Остаются разгрузка канала и защита.
  • Плохие заголовки. Set-Cookie в ответе статики, Vary: *, случайные параметры в URL — и CDN честно проксирует всё в origin.

Ещё один риск — сама CDN становится единой точкой отказа. Крупные сбои у провайдеров случались, и на время инцидента «ложились» тысячи сайтов. Крупные компании используют несколько CDN с переключением через DNS — это дорого и требует одинаковой конфигурации у всех провайдеров.

Как оценивать CDN на интервью

Где CDN появляется на схеме

В задачах вроде «спроектируй Instagram, YouTube, новостной сайт» CDN ставят перед объектным хранилищем с медиа и перед публичными страницами. В сокращателе ссылок её можно поставить перед редиректами — с оговоркой, что тогда клики будут теряться для аналитики, и это надо решить отдельно.

Хороший ответ звучит так: «Медиа загружается в объектное хранилище, имена файлов содержат хеш, CDN тянет их в режиме pull с immutable. Перед хранилищем — origin shield. HTML и публичные ленты кэшируем на десятки секунд с stale-while-revalidate, персональное идёт мимо кэша».

Считаем трафик

Пример: 10 млн пользователей в день, каждый открывает 5 страниц, на страницу приходится 1,5 МБ статики и картинок.

  • Объём в сутки: 107⋅5⋅1,5 МБ=75 ТБ10^7 \cdot 5 \cdot 1{,}5\ \text{МБ} = 75\ \text{ТБ}.
  • Средний поток: 75⋅1012⋅8/86 400≈7 Гбит/с75 \cdot 10^{12} \cdot 8 / 86\,400 \approx 7\ \text{Гбит/с}.
  • Пик при коэффициенте 3: около 21 Гбит/с. Такой канал на собственных серверах — дорогая инфраструктура.
  • При hit ratio 95% по байтам origin отдаёт 5%: 3,75 ТБ в сутки, примерно 0,35 Гбит/с в среднем и около 1 Гбит/с в пике. Это уже обычная задача.

Здесь важна оговорка: hit ratio по запросам и по байтам — разные метрики. Мелкие иконки дают много попаданий, а крупные редкие видео — основной трафик в origin. Считать нагрузку на канал нужно по байтам.

Какие вопросы зададут дальше

  • «Как обновить картинку, если пользователь сменил аватар?» Новый URL с версией или хешем, старый не трогаем. Purge — только если URL постоянный.
  • «Как не отдать чужие данные из кэша?» private для персонального, ключ кэша учитывает всё, от чего зависит ответ, авторизованные ответы по умолчанию не кэшируются.
  • «Что будет после деплоя с холодным кэшем?» Склеивание запросов, shield, прогрев популярных URL, stale-while-revalidate.
  • «Что если CDN упадёт?» Origin должен пережить хотя бы часть трафика или стоять за второй CDN. Как минимум — stale-if-error на случай обратной ситуации, когда падает origin.
  • «Как защитить origin от обхода CDN?» Разрешить входящий трафик только с адресов CDN или проверять секретный заголовок, который добавляет CDN.

Частые ошибки

  • Ставить max-age на HTML на сутки — после деплоя пользователи сутки видят старую версию со ссылками на удалённые файлы.
  • Версионировать через ?v= в URL при CDN, которая исключает query string из ключа.
  • Строить ETag из метаданных файла на конкретной машине.
  • Отдавать Set-Cookie вместе со статикой: многие CDN по умолчанию не кэшируют такие ответы.
  • Путать no-cache с no-store и max-age с s-maxage.
  • Считать, что purge мгновенный и бесплатный.
  • Оценивать выгоду по hit ratio запросов, а не байтов.

Что запомнить

  1. CDN — общий HTTP-кэш у пользователя: edge в PoP, за ним при необходимости shield, в конце origin.
  2. Pull — по умолчанию, push — для тяжёлого заранее известного контента.
  3. Свежесть задаёт Cache-Control, перепроверку — ETag, варианты — ключ кэша и Vary.
  4. Статику версионируют хешем и кэшируют навсегда, HTML — коротко, purge — для исключений.
  5. Для динамики работают микрокэш, stale-while-revalidate, ускорение соединений и edge compute, но не для персональных данных и записей.
  6. На интервью называй, что кэшируется и на сколько, как обновляется, и считай трафик в байтах.

Потренироваться в спокойной обстановке

Все задачи