CDN и edge-вычисления: как отдавать контент из точки рядом с пользователем

Девятая статья серии «Архитектура и производительность». Мы уже научились раскладывать нагрузку по серверам (балансировка нагрузки), ускорять ответы из кэша (кеширование) и видеть, где именно всё тормозит, по логам и трассировке (логирование и трассировка). В прошлой серии мы довели клиент до автономности: приложение умело работать без сети благодаря сервис-воркерам и офлайн-режиму.

Теперь посмотрим на то, что стоит между вашим сервером и браузером пользователя. Когда проект выходит за пределы одного города, одного языка и одного провайдера, выясняется простая вещь: скорость ответа зависит не только от того, как быстро работает ваш код, но и от того, как далеко летит пакет. Один и тот же файл, отданный с сервера в Москве пользователю во Владивостоке, в Бразилии или в Южной Африке, придёт с разницей в секунды. CDN и edge-вычисления — это способ сократить этот полёт.

Разберём по-простому: что такое CDN и точки присутствия, как устроены слои кэширования и origin shield, что означают по-русски заголовки Cache-Control, такое edge-логика и Varnish, как работает geo-routing, чем CDN прикрывается от DDoS и как встроить это всё в сервис-воркер, который вы уже написали. Плюс готовые фрагменты на nginx, HTML и JavaScript.


Часть I. Что такое CDN и как устроены его слои

1. Скорость — это расстояние, а не только мощь

Начнём с физики. Скорость света в оптоволокне — примерно двести тысяч километров в секунду. Между Москвой и Владивостоком около девяти тысяч километров кабеля, и туда-обратно пакет идёт хотя бы 90 миллисекунд чистой задержки, а с учётом маршрутизации на роутерах — 120–150. Между Москвой и Сан-Паулу — 180–230 миллисекунд. Это на один запрос. А страница делает их десятки.

Ваш сервер один, он физически стоит в одном месте. Как бы вы его ни масштабировали (см. балансировку нагрузки), пользователи на другом континенте всё равно платят за расстояние. CDN решает это не «ускорением сервера», а приближением контента.

CDN (Content Delivery Network, сеть доставки контента) — это сеть серверов, расставленных по разным странам и городам, которые держат копии ваших файлов и отдают их оттуда, где пользователь находится рядом. Пользователь из Берлина качает картинку не из вашего дата-центра в Москве, а из сервера в Франкфурте в десяти миллисекундах от себя.

PoP (Point of Presence, точка присутствия) — это один такой узел в конкретном городе. Обычно это не одна машина, а небольшая группа серверов в одном дата-центре: балансировщик разливает входящие по ним, чтобы один узел тянул толпу. Когда говорят «у CDN двести PoP», имеют в виду двести городов, где стоят такие группы.

Простая ментальная картинка: ваш origin-сервер — это оптовый склад. PoP — это магазины у дома в каждом городе. Пользователь идёт в ближайший магазин, а не едет на склад.

2. Откуда берётся копия файла в PoP

Важно сразу понять: CDN не копирует всё заранее. Он работает по принципу «ленивой доставки».

Первый пользователь в Берлине просит файл app.css. Берлинский PoP смотрит в свой кэш — пусто. Тогда он сам идёт на ваш origin-сервер, качает файл, отдаёт его пользователю и заодно кладёт в свой кэш. Все следующие пользователи Берлина получают файл уже из PoP, до origin не доходя.

Отсюда два важных вывода:

  • Первый запрос всегда медленнее — он идёт на origin. Это называется «холодный PoP», «miss» (промах) в терминах кэша.
  • CDN — это кэш, а не база данных. Он хранит ответ ровно столько, сколько вы ему скажете заголовками. Скажете max-age=0 — он будет каждый раз лететь к origin, и толку от него почти не будет.

Здесь CDN смыкается с тем, что вы уже читали в кешировании: те же понятия hit (попадание) и miss (промах) работают и на сетевом уровне.

3. Слои кэширования: почему их несколько

Если у CDN двести PoP, а на origin нахлынет толпа, все двести узлов промахнутся одновременно и дружно застучат на ваш сервер. Это ровно та же «лавина одинаковых запросов» (cache stampede), которую мы разбирали в статье про кэширование. Чтобы этого не случилось, вводят слои.

Классическая раскладка — три слоя:

СлойГде живётЧто хранитКого бережёт
Edge PoP (ближний)город пользователягорячие файлы этого регионапользователя (скорость)
Региональный PoP (дальний)крупный хаб (Франкфурт, Сингапур)общие для региона файлыedge-слой
Origin shieldодин хаб перед originагрегированный кэшваш сервер

Edge-слой обслуживает людей. Региональный слой нужен, чтобы десятки edge-узлов одного региона не лезли на origin за одним и тем же файлом: они спрашивают у регионального. А origin shield — последняя преграда перед вашим настоящим сервером: он собирает все промахи всей сети в один вход и сам идёт к origin максимум один раз на файл.

4. Когда CDN нужен, а когда это лишняя сущность

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

  • Есть пользователи из разных регионов или стран → CDN даёт заметный выигрыш, особенно на картинках, шрифтах, CSS/JS.
  • Проект на одном VPS, аудитория из одного города → выигрыш меньше; CDN полезен хотя бы ради защиты и TLS-разгрузки, но не обязателен.
  • Контент почти не кэшируется (личная лента, персональные данные под каждый запрос) → CDN на такое почти не влияет; здесь помогают кэш приложения и оптимизация БД, а не сеть доставки.

CDN — это транспорт и кэш на транспортном уровне. Он не отменяет работу вашего кода: он лишь решает, сколько раз к origin вообще доедут запросы.


Часть II. Заголовки Cache-Control: срок годности ответа простыми словами

5. Cache-Control — это инструкция для кэша

Когда ваш сервер отдаёт файл, он отправляет вместе с ним HTTP-заголовок Cache-Control. Это не украшение: именно по нему и edge-PoP, и браузер решают, можно ли использовать сохранённый ответ и сколько он ещё годен.

Самые важные директивы (правила) переведём на человеческий русский:

ДирективаФормальноПо-русски
max-age=31536000ответ свеж 31 536 000 секунд«считай этот ответ свежим целый год, не ходи ко мне»
s-maxage=86400срок только для разделяемых кэшей (CDN)«для CDN — сутки; для браузера — смотри max-age»
no-storeне сохранять нигде«вообще не сохраняй этот ответ, он слишком секретный»
no-cacheможно хранить, но всегда проверяй«держи копию, но перед выдачей спроси у меня, не изменилась ли она»
public / privateможно ли кэшировать посредникамpublic — всем, private — только браузеру пользователя
must-revalidateпо истечении срока — обязательная проверка«просрочено — не отдавай по-тихому, иди проверяй»

6. immutable: «файл с хэшем в имени никогда не изменится»

Директива immutable — это обещание: содержимое этого URL не поменяется никогда, даже когда срок годности выйдет. Браузер с immutable не будет слать conditional-запрос (проверку «а точно не изменилось?») даже после перезагрузки страницы.

Это работает по одной простой причине: файл называют по содержимому. Например app.7f3a9c.css. Изменили стили — сборщик пересчитал хэш и отдал app.b12d4f.css. Старый URL app.7f3a9c.css больше никем не используется, но и не менялся — значит, его можно кэшировать на год с immutable.

# Для статики с хэшем в имени — максимум агрессивности:
Cache-Control: public, max-age=31536000, immutable

# Для HTML-страницы, которую надо отдавать свежей, но дёшево:
Cache-Control: no-cache

7. stale-while-revalidate: «отдай старьё, обнови в фоне»

Самая удобная директива для контента, где скорость важнее идеальной свежести: новостные ленты, цены, описания товаров.

stale-while-revalidate=60 означает: после того как срок max-age истёк, можно ещё до 60 секунд отдавать пользователю протухший (stale) ответ немедленно, а параллельно тихо сходить к origin, забрать свежий и заменить его в кэше. Первый в этом окне пользователь получит старый контент за нулевую задержку до origin; те, кто придёт следом, уже свежий.

Это сетевой аналог стратегии stale-while-revalidate, которую мы реализовывали в сервис-воркере прошлой серии (об этом в Части VII). Разница в том, что там это делает браузер, а здесь — CDN.

8. Мета-теги HTML: наследие и честная оговорка

Исторически кэш можно было намекать из HTML через <meta http-equiv="Cache-Control">. Важно понимать по-русски: для CDN этот способ не работает. Сеть доставки читает заголовки HTTP-ответа, а не содержимое тега body. Мета-тег видит только сам браузер (да и тот с оговорками), и то не везде. Ставить no-cache в мета-теге, ожидая, что «CDN не будет кэшировать», — типичная ошибка.

HTML
<!doctype html>
<html lang="ru">
<head>
  <meta charset="utf-8">
  <!-- Мета-подсказка: действует только на браузер, но НЕ заменяет
       HTTP-заголовок. CDN и прокси её не читают — для них нужен
       настоящий заголовок Cache-Control на сервере (см. Часть V). -->
  <meta http-equiv="Cache-Control" content="no-cache">
  <meta http-equiv="Pragma" content="no-cache">
  <title>Страница</title>
  <!-- Статика с хэшем в имени кэшируется долго, сам HTML — нет -->
  <link rel="stylesheet" href="/assets/app.7f3a9c.css">
</head>
<body>
  <p>Актуальный контент всегда отдаётся по-новому, а картинка — из кэша.</p>
  <img src="/assets/hero.3b9c11.webp" width="1200" height="600" alt="Обложка">
</body>
</html>

Правильный порядок: срок годности ответа живёт в HTTP-заголовке, который ставит сервер или nginx; мета-теги — это максимум подсказка браузеру, и полагаться на них нельзя.


Часть III. Origin shield: защита исходного сервера

9. Что такое origin и почему его надо беречь

Origin (исходный сервер) — это ваш настоящий сервер, тот, который генерирует контент: PHP, база данных, файловое хранилище. Всё, что отдаёт CDN, — это копия с origin. Origin дорог в обслуживании: он выполняет код, ходит в базу, тратит CPU на рендер.

Если CDN настроен плохо, origin превращается в единственное узкое горлышко. Худший сценарий: вышел срок годности у популярного файла, и в одну секунду двести PoP промахнулись — двести запросов ударили по origin. Это и есть cache stampede на сетевом уровне, только в десять раз крупнее.

10. Как origin shield собирает промахи в одну точку

Origin shield (щит источника) — дополнительный слой кэша: один выбранный PoP, перед которым стоит вся остальная сеть. Вместо того чтобы каждый из двухсот узлов шёл к origin, edge-узлы идут к shield. Shield один раз сходил на origin, получил файл и раздал всем.

Без origin shield:
  user -> [200 edge PoP] --200 промахов--> [origin]        origin тонет

С origin shield:
  user -> [edge PoP] -> [origin shield] --1 промах--> [origin]   origin спокоен

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

11. Правила хорошего origin shield

  • Белый список shield-IP на файрволе origin. Если к вашему серверу можно достучаться напрямую, shield не спасёт от атаки. Закрывайте origin так, чтобы его трогал только shield (и, при необходимости, мониторинг).
  • Управляйте TTL осознанно. Shield хранит ответ по s-maxage/max-age. Короткий срок = shield чаще ходит на origin. Длинный = реже, но контент может быть устаревшим.
  • Не кэшируйте на shield то, что нельзя в принципе — персональные ответы с no-store вообще не должны попадать ни в один слой.

Origin shield — это то же самое, что read-through и группировка ключей в обычном кэше, только в географическом масштабе.


Часть IV. Edge-вычисления: логика близко к пользователю

12. Что значит «выполнять логику близко к пользователю»

Классический CDN только копирует готовые ответы. Но современные сети позволяют исполнять код прямо на PoP — то есть в том самом городе, где сидит пользователь. Это и называют edge-вычислениями (edge — «край», периферия сети; логика живёт на краю, а не в центре).

Зачем делать на краю то, что можно сделать на origin? Потому что здесь выигрывают три вещи:

  1. Задержка. Миллисекунды рядом с пользователем вместо ста на межконтинентальный путь.
  2. Разгрузка origin. Если решение можно принять на PoP, origin вообще не просыпается.
  3. Локальная адаптация. По стране можно менять язык, валюту, цену на доставку, не трогая код приложения.

near-user execution (выполнение близко к пользователю) — это и есть весь смысл edge: не ждать ответа издалека, а сделать нужное действие там, где находится человек.

13. Varnish: HTTP-кэш и логика на границе

Varnish — это быстрый HTTP-кэш и веб-ускоритель на границе: он стоит перед вашим приложением, разбирает запросы, кэширует ответы и выполняет логику на языке VCL (конфигурационный язык Varnish). Часто Varnish ставят и как локальный shield-слой прямо в вашем дата-центре: nginx -> Varnish -> PHP-FPM.

Логика на Varnish умеет, например: убрать куку из кэшируемого ответа, склеить страницу из кусков, перенаправить мобильного пользователя, добавить нужный заголовок — и при этом не будить приложение.

# Заглянуть в состояние Varnish: попадания/промахи и сколько памяти занято
varnishstat -1

# Что Varnish вернул по конкретному объекту (ключ кэша)
varnishadm "ban.list"

# Принудительно выкинуть всё, что относится к домену (инвалидация)
varnishadm "ban req.url ~ /articles/"

14. Edge-функции: небольшой код на краю

У крупных CDN-провайдеров есть edge-функции — мини-программы (обычно на JavaScript в изолированном рантайме или на собственном языке конфигурации), которые исполняются на PoP по событиям «запрос пришёл», «ответ с origin получен», «ответ уходит пользователю».

Типичные задачи edge-функции:

  • переписать Cache-Control перед выдачей (например, добавить stale-while-revalidate там, где origin его не ставит);
  • по геолокации вернуть пользователя на локальный поддомен;
  • подменить не найденный ресурс (404) на кэшированную страницу-заглушку;
  • сжать картинку на лету или добавить водяной знак;
  • проверить токен и вернуть 401 до того, как запрос дойдёт до origin.

Важное ограничение: edge-функции не должны долго думать и ходить в базу — они живут на краю и работают с тем, что уже в кэше или в самом запросе. Это функция «транспортного» уровня, а не замена бизнес-логики приложения.


Часть V. Geo-routing: как трафик попадает к ближайшему серверу

15. Три способа направить пользователя

Geo-routing (географическая маршрутизация) — это набор приёмов, которые решают, какой PoP отдаст контент конкретному человеку. «Ближайший» — это не обязательно ближайший по карте, а ближайший по сетевой доступности и загрузке. Основные способы:

  • Anycast. Один и тот же IP-адрес объявлен с нескольких PoP сразу. Сеть сама ведёт пакет к ближайшему «объявлению» адреса — это работает на уровне маршрутизации и почти не требует настройки. Плюс: прозрачно и устойчиво к DDoS (трафик рассеивается по узлам). Минус: труднее отлаживать, «почему именно этот узел».
  • DNS-роутинг (GeoDNS). Авторитетный DNS CDN смотрит, откуда пришёл запрос, и отдаёт адрес ближайшего PoP. Плюс: гибко. Минус: результат зависит от кэша DNS и от того, какой резолвер использует пользователь (публичный DNS может увести в чужой город).
  • HTTP-редирект. Один входной узел, получив запрос, смотрит на X-Forwarded-For или данные геолокации и перенаправляет на нужный PoP. Плюс: полный контроль логики. Минус: лишние миллисекунды на редирект.

16. nginx и доверенные заголовки от CDN

Когда CDN стоит перед вашим nginx, клиентский IP в логах nginx — это IP узла CDN, а не пользователя. Чтобы геолокация, лимиты запросов и логи работали правильно, nginx должен уметь извлекать настоящий адрес из заголовков и доверять им. Ниже — прокси-конфигурация, совместимая с CDN: она передаёт нужные заголовки наверх и корректно разбирает протокол и клиентский адрес.

Nginx
server {
    listen 443 ssl http2;
    server_name example.com;

    # Диапазоны IP, которые принадлежат вашему CDN-провайдеру.
    # Доверять заголовкам можно только от них, иначе любой
    # посетитель подставит себе любой X-Forwarded-For.
    set_real_ip_from 203.0.113.0/24;   # условные подсети PoP
    set_real_ip_from 198.51.100.0/24;
    real_ip_header   X-Forwarded-For;
    real_ip_recursive on;              # идти по цепочке адресов

    location / {
        proxy_pass http://php_backend;

        # Заголовки, которые CDN и балансировщик передают выше
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Протокол origin и задержка — полезно для трассировки
        proxy_set_header X-Request-ID $request_id;
    }

    # Статика с хэшем в имени: год и immutable
    location /assets/ {
        add_header Cache-Control "public, max-age=31536000, immutable";
        # Разрешаем CDN хранить копию
        add_header X-CDN-Cacheable "yes";
    }

    # HTML отдаём с условным кэшированием через CDN
    location ~ \.php$ {
        # срок для CDN чуть мягче, чем для браузера
        add_header Cache-Control "s-maxage=300, stale-while-revalidate=60";
    }
}

Ключевая мысль: nginx передаёт наверх X-Forwarded-For (цепочку адресов), X-Forwarded-Proto (изначальный протокол http/https) и X-Request-ID (сквозной идентификатор запроса для трассировки — см. логирование и трассировку). Без корректных доверенных подсетей эта схема опасна: поддельный X-Forwarded-For обходит лимиты и врёт в логах.

17. Как проверить, что geo-routing работает

  • Посмотрите заголовок X-Cache или CF-Cache-Status (имя зависит от провайдера): HIT, MISS, BYPASS.
  • Сравните X-Response-POPCode или аналог — он покажет, какой узел отдал ответ.
  • Сделайте запрос из нескольких регионов (через внешний тест или VPN) и проверьте, меняется ли узел и падает ли задержка.
  • Посмотрите время отклика origin на shield-хабе — там оно должно быть стабильным, даже если на edge идёт толпа.

Geo-routing — это продолжение той же темы, что и балансировка: там вы раскладываете запросы по серверам одного дата-центра, а здесь — по дата-центрам мира.


Часть VI. Безопасность: базовая защита от DDoS и интеграция WAF

18. Почему CDN сам по себе немного защищает

DDoS (Distributed Denial of Service, распределённая атака отказа в обслуживании) — это попытка завалить сервер таким количеством запросов, что он перестаёт отвечать нормальным пользователям. Атакующий использует тысячи заражённых машин, откуда и «распределённость».

CDN прикрывает от DDoS по двум причинам:

  1. Масштаб. Поток атаки сначала упирается в сеть CDN, у которой сотни PoP и огромная полоса пропускания. Вам на origin надо «пробить» не всю атаку, а только то, что прошло сквозь shield и не удовлетворилось из кэша.
  2. Отделяемость. Origin физически скрыт за CDN: его реальный IP может вообще не светиться в DNS. Прямой удар по origin для атакующего — задача посложнее, чем удар по публичному домену.

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

19. WAF: файрвол веб-приложений на входе

WAF (Web Application Firewall, файрвол веб-приложений) — это фильтр, который смотрит не на IP и порты, а на содержимое HTTP-запроса и блокирует типовые приёмы атак: SQL-инъекции, межсайтовый скриптинг (XSS), обход авторизации, автоподбор паролей, вредоносные payload в форме. В отличие от сетевого файрвола, который решает «кому вообще можно стучаться», WAF решает «разрешён ли этот конкретный запрос и это конкретное содержимое».

Когда WAF стоит на edge, у него два преимущества:

  • Атака отсекается до origin. Плохой запрос не будит ни приложение, ни базу.
  • Единая политика на все PoP. Правила WAF раскатываются по сети разом, а не копипастятся на каждый сервер.
# Типовая проверка: что вернул WAF по блокам (пример с логом edge-фильтра)
# Смотрим, какие правила сработали за последний час и с какими IP.
grep -i 'blocked\|waf' /var/log/edge/firewall.log | tail -n 20

# Счётчик запросов, отбитых файрволом (для мониторинга)
awk '/blocked/ {count++} END {print "заблокировано:", count}' /var/log/edge/firewall.log

Интеграция WAF в edge — это логичное продолжение статьи про WAF и ModSecurity: там мы рассматривали его как часть вашего nginx, а здесь выносите правила на уровень сети, ближе к источнику трафика.

20. TLS и загрузка криптографии

Отдельная приятная сторона CDN: разгрузка TLS. Шифрование/расшифрование HTTPS — дорогая по CPU операция. Когда терминация TLS (точка, где пакет «распаковывается») происходит на edge PoP, ваш origin принимает уже обычный расшифрованный трафик и тратит процессорное время на приложение, а не на криптографию. Сертификат при этом живёт на edge, а origin можно держать на «внутреннем» канале.

Это важно и для безопасности: вы держите приватный ключ не на каждом сервере, а в управляемом edge-слое, и обновляете его в одном месте.


Часть VII. Синхронизация с сервис-воркерами: мост к офлайн-кэшу

21. Браузер и CDN — два кэша одного пути

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

  • CDN (edge PoP) — кэш между origin и всем миром.
  • Service Worker (Cache API) — кэш между CDN и конкретным человеком в его браузере.

Хорошая новость: они договороспособны через одни и те же заголовки Cache-Control. Статика с max-age=31536000, immutable одинаково дорого живёт и на PoP, и в Cache API воркера. А stale-while-revalidate — это ровно та же стратегия, которую вы реализовывали руками в воркере: теперь её частично берёт на себя CDN, а воркер подхватывает то, что сеть не отдала.

22. Сопоставление кэша с учётом версий и CDN

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

// sw.js — сопоставление кэша с учётом версии сборки и CDN
// Работает в связке с офлайн-кэшем прошлой серии.

// Собираем имя кэша из версии приложения (её инжектит сборщик).
const CACHE = `app-${APP_VERSION}`;
const STATIC = ['/index.html', '/assets/app.7f3a9c.css', '/assets/hero.3b9c11.webp'];

self.addEventListener('install', (event) => {
  // Прогреваем ближний кэш статикой; origin/CDN при этом почти не нагружается,
  // потому что заголовки immutable гарантируют долгий срок на edge.
  event.waitUntil(
    caches.open(CACHE).then((cache) => cache.addAll(STATIC))
  );
  self.skipWaiting();
});

// Убираем старые версии при активации — иначе кэш разрастается.
self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((keys) => Promise.all(
      keys.filter((k) => k !== CACHE).map((k) => caches.delete(k))
    ))
  );
  self.clients.claim();
});

// Сопоставляем запрос с кэшем, игнорируя лишние CDN-параметры,
// и используем stale-while-revalidate для публичного контента.
function normalize(req) {
  const url = new URL(req.url);
  // Удаляем технические параметры, которые подставляет CDN (?v=, ?sig=...)
  url.searchParams.delete('v');
  url.searchParams.delete('sig');
  return url.href;
}

self.addEventListener('fetch', (event) => {
  const req = event.request;
  if (req.method !== 'GET') return;          // мутации отправляем напрямую

  // Статика — cache-first: отдаём из воркера, сеть не трогаем.
  if (req.url.includes('/assets/')) {
    event.respondWith(
      caches.match(req).then((hit) => hit || fetch(req).then((fresh) => {
        const copy = fresh.clone();
        caches.open(CACHE).then((c) => c.put(req, copy));
        return fresh;
      }))
    );
    return;
  }

  // Публичные данные — stale-while-revalidate: сразу то, что есть,
  // и параллельно тихо обновляем через CDN/origin.
  event.respondWith(
    caches.match(normalize(req)).then((stale) => {
      const network = fetch(req).then((fresh) => {
        caches.open(CACHE).then((c) => c.put(normalize(req), fresh.clone()));
        return fresh;
      }).catch(() => stale);                 // сеть упала — держим старьё
      return stale || network;
    })
  );
});

Здесь важно: normalize чистит CDN-параметры, чтобы воркер не плодил дубликаты одного и того же файла. Статика идёт по cache-first (дорого живёт и на PoP, и в воркере), а данные — по stale-while-revalidate, как мы описали в Часте II. Точное поведение стратегий и фолбэка разобрано в статье про сервис-воркеры.

23. Что синхронизировать, а что нет

  • Версию сборки держите в одном месте: если имя файла поменялось (хэш в имени), и edge-кэш, и воркер автоматически возьмут новый URL — инвалидация по имени.
  • Инвалидацию контента делайте на edge (purge/бан по URL или тегу), но помните: воркер у тех, кто уже открыл сайт, обновится только при следующем визите или по skipWaiting. Для срочных изменений контента не рассчитывайте на офлайн-кэш.
  • Персональные ответы (профиль, корзина, оплата) не кэшируйте ни на edge (no-store/private), ни в воркере. Их путь — origin, и точка.
  • Фолбэк офлайна оставьте на воркере: CDN человеку в метро без сети не поможет, а воркер с закэшированной страницей — поможет.

CDN и сервис-воркер — не конкуренты, а два конца одной трубы: сеть доставки ускоряет всех, воркер страхует конкретного человека.


Чек-лист: CDN без самообмана

  • Заголовки решают всё. Проверьте Cache-Control на статике: public, max-age=31536000, immutable. На HTML — короткий срок или no-cache с stale-while-revalidate. Мета-теги в HTML CDN не читают.
  • Origin shield настроен, а origin закрыт белым списком по IP shield — прямой удар мимо CDN не проходит.
  • nginx доверяет X-Forwarded-For только подсетям CDN (set_real_ip_from), иначе лимиты и логи врут.
  • geo-routing проверен из нескольких регионов: смотрим X-Cache/CF-Cache-Status и код PoP, убеждаемся, что HIT растёт, а задержка падает.
  • WAF на edge активен, правила блокируют типовые приёмы атак до origin; логи блокировок отправлены в мониторинг (см. логирование и трассировку).
  • TLS терминируется на edge, приватный ключ не лежит на каждом сервере.
  • Edge-функции короткие и без базы: они читают кэш и запрос, а не выполняют бизнес-логику приложения.
  • Воркер синхронизирован по версии сборки: хэш в имени файла автоматически инвалидирует и edge, и Cache API; персональный контент не кэшируется нигде.
  • Инвалидация (purge) описана: что и как чистить на CDN при релизе, чтобы пользователи не сидели на вчерашнем JS.
  • Балансировка на origin остаётся нужной: CDN снимает сетевую задержку и статический флуд, а живые запросы всё равно принимают ваши серверы (см. балансировку нагрузки).

Итог. Сеть доставки как часть архитектуры

CDN превращает вопрос «как ускорить сервер» в вопрос «как приблизить ответ к человеку». Мы разобрали, как устроены точки присутствия и слои кэширования, что на самом деле значат по-русски директивы Cache-Control, зачем нужен origin shield, чтобы не топило ваш настоящий сервер, как edge-функции и Varnish выполняют логику близко к пользователю, как geo-routing выбирает ближайший узел и чем CDN прикрывает от DDoS и ставит WAF и TLS на входе. И главное — как это всё стыкуется с офлайн-кэшем сервис-воркера, который вы уже построили.

Материал написан простым языком без излишней терминологии. Все примеры основаны на технологиях из предыдущей серии «Веб-разработка без фреймворков» (HTML, CSS, JavaScript, Service Worker) и серии «Архитектура и производительность». Практические советы учитывают специфику проектов на PHP/WordPress и самостоятельного хостинга на VPS. Названия продуктов и заголовков CDN-провайдеров намеренно приведены обобщённо: конкретные имена полей (X-Cache, CF-Cache-Status и аналоги) различаются у вендоров, сверяйтесь с документацией своего провайдера.

👤 Зарегистрируйтесь, чтобы добавить материал в закладки.