Спор о сети доставки контента вспыхивает в каждом втором обсуждении скорости сайта. Одна сторона уверяет, что без точек присутствия по всей стране проект обречён на медленные первые загрузки. Другая парирует: выставил Cache-Control с годовым сроком, раздал файлам неизменяемые имена, и кэш браузера сделает всё сам. Обе стороны по-своему правы, и в этом весь подвох. Кэш браузера и сеть доставки решают разные задачи, и подмена одной другой бьёт по конкретным людям в самый неудобный день, обычно в день запуска рекламы.

Путь первого запроса который не ускорить заголовками

Пока файл не оказался на машине человека, достать его из кэша невозможно. А путь этот длинный. Сначала браузер спрашивает адрес сервера у системы DNS, затем открывает TCP-соединение, затем договаривается о шифровании, и лишь после этого летит сам запрос. Каждый шаг стоит минимум один оборот пакета туда и обратно. Свет в оптоволокне движется примерно двести тысяч километров в секунду, но кабели почти никогда не идут по прямой: они огибают моря, заезжают через узлы обмена трафиком, провисают на дне проливов. От Москвы до Франкфурта в удачный час путь занимает 35-40 миллисекунд в один конец, до Владивостока с европейской площадки уходит больше сотни. Рукопожатие TLS версии 1.2 добавляет два полных оборота, версия 1.3 экономит один. Итого у посетителя из дальнего города время до первого байта складывается из пяти-шести оборотов пакета, и каждый оборот умножается на километры под кабелем. Заголовки кэширования вступают в работу только со следующего визита, когда тяжёлый путь уже пройден целиком.

Многопоточность протоколов HTTP/2 и HTTP/3 тут не спасает: она ужимает много файлов в одно соединение, но само соединение всё равно сначала нужно открыть и защитить. Квант ускорения первой загрузки ограничен физикой, а не настройкой веб-сервера.

Конкретная арифметика выглядит так. Файл весом 300 килобайт с ближней площадки при обороте пакета в пять миллисекунд проходит рукопожатия почти мгновенно и докачивается за пару оборотов окна перегрузки. Тот же файл с дальнего континента при обороте в сто двадцать миллисекунд сначала платит шесть сотен и более за установку соединения, а потом ещё несколько оборотов только на разгон: соединение не решает отправить всё сразу, оно стартует с порции примерно в десять-четырнадцать килобайт и удваивает её каждый оборот. Разгонная дорожка из шести ступеней на коротком пути незаметна, а на длинном превращается в лишнюю секунду чистого ожидания.

Что кэш браузера умеет блестяще и где его пределы

Директива max-age=31536000 с пометкой immutable превращает статический файл в вечную запись: браузер один раз скачал app.8f3c21.js и больше не переспрашивает сервер ни при каких условиях. Повторный визит открывается почти мгновенно, и для него сеть доставки действительно не нужна. Вопрос упирается в то, у кого этот повторный визит. Первый человек из рекламного объявления открывает пустой кэш. Пользователь режима инкогнито тоже. Второе устройство, свежеочищенный браузер, обновление приложения с новыми именами файлов, всё это снова первый визит. У посадочных страниц после запуска рекламы доля людей с пустым кэшем бывает половиной трафика и выше.

Есть и тонкий момент с ETag. Если файл не помечен неизменяемым, браузер при каждом визите переспрашивает сервер, получает короткий ответ 304 и тратит целый оборот пакета только ради подтверждения, что файл не менялся. На длинном маршруте такое подтверждение стоит дороже, чем половина самого файла. Ускоряется лишь вторая и последующая загрузка, а первая остаётся ровно такой же, как без всяких настроек.

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

Ближняя площадка сети доставки и доля попаданий

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

Второй эффект менее очевиден. У площадок крупного оператора сетевой стек обычно новее, чем в собственном парке приложений: ранний переход на TLS 1.3 и HTTP/3, возобновление соединений без полного рукопожатия, ядро, вылизанное под мобильные сети. Собственный сервер, обновляемый по остаточному принципу, редко может похвастаться тем же, и разница проявляется именно на первом визите, когда соединение открывается с нуля.

location /assets/ {
    add_header Cache-Control "public, max-age=31536000, immutable";
}

location /media/ {
    add_header Cache-Control "public, max-age=86400, stale-while-revalidate=600";
}

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

Проекты которым одной машины действительно достаточно

Есть большой класс проектов, где сеть доставки не окупает даже собственной оплаты трафика. Корпоративная админка для сотрудников одного города. Сервис бронирования для сети клиник в двух соседних областях. Внутренний инструмент склада, куда ходят двадцать человек с одного промышленного узла. Сервер стоит в дата-центре рядом с аудиторией, путь короткий и стабильный, повторные визиты обслуживает кэш браузера, а программные клиенты работают поверх долгоживущих соединений. Добавление площадок здесь приносит вторую точку отказа, лишний счёт и новую категорию инцидентов, когда случайно закэшировалась персональная страница. Честный ответ на вопрос о сети доставки для таких проектов звучит скучно: не нужна. Зато сами настройки кэширования нужны вдвойне, ведь при повторных визитах они дают эффект, сопоставимый с переносом сервера в соседний кабинет.

Отдельный случай, который часто путают с географией, это один сервер, задыхающийся не от расстояния, а от количества. Полоса сетевой карты, число рабочих процессов, ёмкость диска, всё имеет предел, и всплеск посещаемости в пять раз против обычного выкладывает источник ровно в час пик. Распределённые площадки снимают с источника львиную долю отдачи статики, и узким местом остаётся только приложение. Поэтому вопрос о сети доставки иногда звучит не про скорость для людей, а про выживание сервера в день распродажи, и ответ на него положительный даже для проекта из одного города.

Когда расстояние начинает стоить денег в день запуска

Как только аудитория перестаёт жить в одном городе, арифметика меняется. Типовой сценарий из практики. Магазин запускает рекламу по всей стране, посадочная страница тянет 1,8 мегабайта картинок из единственного дата-центра, и посетитель с Дальнего Востока ждёт пять-шесть секунд. Часть из них уходит до первого просмотра товара, и рекламный бюджет сгорает вместе с ними. На длинных маршрутах работают жёсткие правила: каждая потеря пакета оборачивается повторной передачей, а вероятность потери растёт с числом промежуточных узлов и с загрузкой радиоканалов в мобильных сетях. Окно перегрузки TCP разгоняется медленно при больших задержках, поэтому двухмегабайтный файл по пути со ста двадцатью миллисекундами едет дольше того же файла на коротком маршруте даже при равной полосе. Медиа-библиотека, дистрибутивы сборок, каталог с сотнями фотографий, всё это умножает расстояние на количество файлов. Перенос статики на площадки рядом с людьми срезает время первой загрузки в разы, и разницу видно невооружённым таймером.

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

Мобильные сети добавляют свою ложку в эту копилку. Радиомодуль телефона просыпается не мгновенно, каждый переход в активное состояние стоит сотни миллисекунд, а на слабом приёме потери пакетов доходят до процентов, и каждый процент на длинном маршруте умножается повторными передачами. Сложите пробуждение радиомодуля, полдюжины оборотов пакета до дальнего дата-центра и пару потерь на разгоне окна, и получится та самая вечность загрузки, на которую жалуется служба поддержки. С ближней площадкой у того же человека обороты укладываются в городскую сеть, радиомодуль не успевает заснуть между запросами, и страница открывается в разы быстрее на том же тарифе и том же телефоне.

Внедрение сети доставки без переделки приложения

Порядок действий в типовом проекте укладывается в один рабочий день и выглядит так:

  1. разложить файлы по адресам, отделив неизменяемую сборку с отпечатками в именах от медиа, которое обновляется;
  2. выставить заголовки по группам, дав вечный срок сборке, сутки медиа и осторожные настройки страницам, которые собираются под конкретного человека;
  3. подключить сеть доставки на поддомене статики, выпустить сертификат и проверить, что кэш источника не запирает обновления;
  4. договориться о процедуре принудительной очистки до первого инцидента, потому что кнопка сброса нужна заранее, а не после испорченной витрины;
  5. измерить время первого визита из нескольких городов до и после одной командой, чтобы решение опиралось на цифры.
curl -o /dev/null -s -w 'connect=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer}\n' https://site.example/assets/app.8f3c21.js

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

Проверять стоит не только секунды. Доля людей, ушедших до окончания загрузки, доля первых визитов из каждого региона и стоимость отдачи мегабайта с источника вместе складываются в картину, по которой решение принимается без совещаний. Разумное правило, переснимать эти цифры после каждого крупного редизайна, потому что вес страницы живёт своей жизнью и незаметно толстеет с каждой новой фичей.

Вывод который стоит принять до следующего запуска рекламы

Кэш браузера и сеть доставки не соперники, а два этажа одной системы. Кэш убирает повторную загрузку файла, сеть убирает расстояние первой загрузки, и ни одно из этих действий не заменяет другое. Один сервер с грамотными заголовками честно закрывает проект с локальной аудиторией и стабильным составом посетителей. Широкая география, мобильный трафик и тяжёлые медиа переводят расстояние в прямые убытки, которые не лечатся никакой директивой, потому что директива работает только со второго раза. Решение в обоих случаях одинаковое: снять цифры первых визитов из дальних городов, посчитать секунды и деньги, а уже потом решать, стоит ли расстояние своих килобайт.

И напоследок трезвая мысль. Распределённая доставка это не универсальная кнопка, а инженерное сооружение с собственной дисциплиной эксплуатации: процедурой очистки, наблюдением за долей попаданий, порядком в именах файлов. Проекты, подключившие площадки без этой дисциплины, меняют старые проблемы на новые: отдают закэшированную персональную страницу чужому человеку или не могут выкатить правку витрины два часа. Дисциплина обходится дешевле, чем кажется, а выигрыш расстояния достаётся каждому первому визиту без исключений.