CDN и статика
Интуиция: пункт выдачи вместо центрального склада
Представь маркетплейс с одним огромным складом под Москвой. Если каждый заказ из Владивостока везти оттуда, посылка идёт неделю, а склад захлёбывается. Поэтому популярные товары заранее развозят по пунктам выдачи рядом с покупателями. Ты забираешь посылку за углом, а центральный склад занят редкими заказами.
CDN (Content Delivery Network) устроена так же. Это сеть краевых серверов (edge, PoP) по всему миру. На них лежат копии твоих файлов: JS, CSS, картинок, видео. Пользователь получает файл с ближайшего узла, а твой основной сервер (origin) отвечает только тогда, когда на краю копии нет.
Почему статику отдают именно с края
Статика — это файлы, одинаковые для всех: logo.png, app.js, превью видео. Её идеально кэшировать, и пользы от этого три:
- Задержка. Скорость света не ускоришь. RTT из Москвы до сервера в Вирджинии около 120 мс, до узла CDN в Москве около 10 мс. TCP + TLS — это 2–3 RTT ещё до первого байта. Значит, разница в сотни миллисекунд на каждом соединении.
- Нагрузка на origin. Большую часть запросов закрывает край, и до тебя доходит малая доля.
- Трафик и устойчивость. Пиковые всплески (релиз, вирусная картинка) и даже DDoS ложатся на огромную распределённую сеть, а не на твои три сервера.
Схема
Как работает кэш на краю
- Пользователь запрашивает
cdn.site.com/app.3f9a2c.js. DNS или anycast приводит его к ближайшему узлу. - Cache hit: файл есть и не протух, узел сразу его отдаёт.
- Cache miss: узел идёт на origin (например, в S3), сохраняет копию и отдаёт её. Следующие пользователи в этом регионе уже получают hit.
Сколько хранить копию, решает TTL. Обычно его задаёт origin заголовками:
HTTP/1.1 200 OK
Content-Type: application/javascript
Cache-Control: public, max-age=31536000, immutable
ETag: "3f9a2c"max-age=31536000 означает «храни год», immutable — «даже не проверяй». Для HTML ставят короткий TTL или no-cache, чтобы пользователи быстро видели новую версию страницы.
Инвалидация: версия в имени файла
Главная боль кэша — как обновить файл, который уже разложен по сотне узлов с TTL в год. Есть два пути:
| Способ | Как работает | Минусы |
|---|---|---|
| Purge через API CDN | «Удали /app.js на всех узлах» |
Медленно (секунды–минуты), часто платно, браузеры всё равно держат свою копию |
| Версия в имени | app.3f9a2c.js, где хэш берётся из содержимого |
Нужна сборка, которая переписывает ссылки в HTML |
Второй способ — стандарт индустрии. Файл с конкретным хэшем никогда не меняется, поэтому его можно кэшировать навечно. Новый релиз даёт новое имя (app.8b1d07.js), HTML начинает ссылаться на него, и старая копия просто перестаёт запрашиваться. Инвалидировать ничего не нужно: короткий TTL нужен только у HTML.
Совет: на собесе формулируй так: «Статику версионируем хэшем в имени и кэшируем с
immutableна год, а HTML отдаём с коротким TTL. Purge оставляем на экстренные случаи».
Посчитаем выгоду
Допустим, 10 млн просмотров страниц в день по 2 МБ статики, hit ratio на CDN 95%.
| Величина | Расчёт | Итог |
|---|---|---|
| Трафик в день | 10 млн × 2 МБ | 20 ТБ |
| Средняя полоса | 20 ТБ / 86 400 с | ≈ 230 МБ/с ≈ 1,9 Гбит/с |
| Пик (×3) | 1,9 × 3 | ≈ 5,6 Гбит/с |
| До origin (5% miss) | 5,6 × 0,05 | ≈ 0,28 Гбит/с |
Без CDN тебе пришлось бы держать почти 6 Гбит/с на своих серверах. С CDN хватит одного скромного бакета в S3.
CDN и динамический контент
Даже некэшируемые запросы выигрывают от CDN:
- TLS terminates на краю. Дорогое рукопожатие идёт с близким узлом, а к origin у CDN уже открыты тёплые keep-alive соединения по быстрой магистрали.
- Microcaching. Ответы вроде «топ товаров» или «ленты для гостей» можно кэшировать на 1–5 секунд. При 10 000 RPS на такой эндпоинт до origin дойдёт около одного запроса в секунду с каждого узла.
- Кэш по ключу. Варьирование по
Accept-Languageили устройству черезVaryили настраиваемый cache key. - Edge compute (Cloudflare Workers, Lambda@Edge): A/B-редиректы, проверка JWT, геоблок прямо на краю.
- Защита: WAF, rate limiting и поглощение DDoS до того, как трафик дойдёт до тебя.
Персональные данные (корзина, профиль) помечай Cache-Control: private или no-store, чтобы они не попали в общий кэш.
Компромиссы, которые стоит назвать
- Устаревание: пока не истёк TTL, пользователь может видеть старую версию. Это лечится версионированием.
- Стоимость: платишь за исходящий трафик и запросы. На больших объёмах выгоднее мульти-CDN или собственные узлы, как у Netflix Open Connect.
- Холодный кэш: редкие файлы (длинный хвост) часто дают miss. Помогает origin shield — промежуточный слой кэша, который схлопывает промахи с разных узлов в один запрос к origin.
- Сложность отладки: «у меня работает, у пользователя в Токио нет» — потому что у них разные узлы.
Как это звучит на собесе
«Картинки и видео кладём в S3, перед ним ставим CDN. Статика версионирована хэшем и кэшируется на год, HTML живёт с TTL в минуту. Ожидаем hit ratio 95%+, так что origin получает около 5% трафика. Для API тоже используем CDN: TLS на краю, microcaching публичных эндпоинтов на пару секунд, WAF. Приватные ответы помечаем
private, чтобы не утекли в общий кэш».
Частые ошибки
- Обновлять файл под тем же именем и надеяться на purge.
- Длинный TTL у
index.html: после релиза пользователи сидят на старой версии. - Кэшировать ответ с cookie или персональными данными как
public. - Забыть про origin shield при большом каталоге медиа: тысячи промахов бьют прямо в хранилище.
- Говорить «CDN только для картинок» и упускать выгоду для динамики.
Если ты можешь нарисовать схему выше и объяснить, почему app.3f9a2c.js можно кэшировать навечно, тему CDN на собесе ты уже закрываешь уверенно.