Страница делает 180 запросов: почему это важнее скорости вашего канала
Вы апгрейдите канал, переезжаете на сервер с гигабитным портом — а страница как грузилась две-три секунды, так и грузится. Дело чаще всего не в мегабитах: дело в том, что браузер открывает вкладку разработчика и видит там сотню-другую отдельных запросов — на каждый скрипт, стиль, шрифт, картинку, счётчик аналитики. Пропускная способность канала почти никогда не является узким местом на обычной странице; узкое место — это сумма накладных расходов на каждый отдельный запрос, помноженная на их количество.
Содержание
- Что на самом деле измеряет "скорость канала" и почему это не то же самое, что скорость загрузки
- Анатомия одного запроса: из чего складываются накладные расходы
- HTTP/1.1, лимит параллельных соединений и очередь запросов
- HTTP/2 мультиплексирует, но не отменяет RTT
- Почему на мобильных и дальних соединениях эффект умножается
- Как сократить число запросов на практике
Что на самом деле измеряет "скорость канала" и почему это не то же самое, что скорость загрузки
Пропускная способность (megabit/sec, "сколько данных в секунду проходит по трубе") — это про объём. Скорость загрузки страницы — это про время, а время складывается не только из передачи байтов, но и из ожидания: пока установится соединение, пока сервер обработает запрос, пока придёт первый байт ответа. На современной странице объём данных редко превышает пару мегабайт — это меньше секунды передачи даже на скромном мобильном канале. А вот количество отдельных ресурсов на средней коммерческой странице — счётчик аналитики, виджет чата, несколько шрифтов, десяток скриптов трекеров, стили из CDN, иконки — легко доходит до полутора-двух сотен. Каждый из них не бесплатен сам по себе: это отдельный HTTP-запрос со своим циклом "спросить — подождать — получить".
Разница особенно хорошо видна на контрасте: удвойте канал — вторая половина страницы, ограниченная не объёмом, а числом round-trip'ов, ускорится в лучшем случае незначительно. Сократите число запросов вдвое — выигрыш почти всегда заметен на глаз, потому что вы убрали не байты, а сами циклы ожидания. Если вы уже разбирали, почему хороший пинг не гарантирует быструю загрузку, эта статья продолжает ту же мысль на уровне конкретного числа запросов: смотрите разбор в статье про пинг 20 мс и страницу, которая всё равно грузится 4 секунды.
Анатомия одного запроса: из чего складываются накладные расходы
Возьмём один "лишний" запрос — например, favicon, мелкую иконку или сторонний JS-сниппет — и разложим, что происходит, прежде чем браузер получит хоть один байт полезных данных:
- DNS-резолвинг — если ресурс на другом хосте (а сторонние скрипты почти всегда на другом хосте), нужен отдельный DNS-запрос, если ответ ещё не в кеше резолвера.
- TCP-рукопожатие — три пакета (SYN, SYN-ACK, ACK), прежде чем можно передавать данные. Это минимум один полный round-trip.
- TLS-рукопожатие — если это HTTPS (а он почти всегда HTTPS), добавляется ещё один-два round-trip'а на согласование шифрования и обмен сертификатами.
- Сам запрос и обработка на сервере — отправить заголовки, дождаться, пока сервер отдающей стороны сформирует ответ.
- Передача ответа — и только это зависит от объёма данных и пропускной способности канала.
Из пяти шагов только последний масштабируется с шириной канала. Первые четыре — это фиксированные накладные расходы, которые не становятся меньше, сколько бы гигабит вы ни докупили: они определяются задержкой (RTT) между клиентом и сервером, а не объёмом трафика. Если у вас уже настроен keep-alive и сервер держит соединение открытым, шаги 1-3 выполняются один раз на хост, а не на каждый запрос — но это работает только для запросов к одному и тому же хосту. Экономику повторного использования TCP-соединения подробно разбирали в статье про keep-alive и цену нового соединения: там же видно, во сколько раз дороже обходится каждый ресурс, если keep-alive выключен или соединение почему-то не переиспользуется.
Ключевой нюанс: сторонний ресурс (аналитика, виджет, шрифт из чужого CDN) почти никогда не выигрывает от вашего keep-alive — это новый хост, значит новый DNS, новое TCP, новый TLS, независимо от того, насколько хорошо настроен ваш собственный сервер.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверHTTP/1.1, лимит параллельных соединений и очередь запросов
В HTTP/1.1 браузер ограничивает себя примерно шестью параллельными TCP-соединениями на один домен (число варьируется по браузерам, но порядок именно такой). Если у страницы 180 запросов и все они идут к одному хосту без HTTP/2, браузер физически не может открыть 180 соединений одновременно — он выстраивает очередь и обрабатывает её волнами по шесть. Грубая иллюстрация, не измеренный факт, а просто чтобы показать порядок: 180 запросов при лимите в 6 параллельных соединений — это 30 последовательных "волн". Если на каждую волну уходит время, сравнимое с одним round-trip до сервера, то итоговая задержка — это не один RTT, а RTT, умноженный примерно на число волн. Даже если каждый отдельный запрос выглядит быстрым в изоляции, в сумме на очередь набегает заметная часть общего времени загрузки.
Именно поэтому раздача мелких ресурсов с разных поддоменов (расчёт на то, что лимит "на домен" даст больше параллелизма) в эпоху HTTP/1.1 была распространённой практикой — и одновременно источником новых накладных расходов: каждый новый поддомен — это ещё один DNS-резолвинг и ещё одно TCP+TLS рукопожатие, которые надо оплатить один раз, прежде чем параллелизм начнёт окупаться.
HTTP/2 мультиплексирует, но не отменяет RTT
HTTP/2 решает именно проблему лимита параллельных соединений: вместо N отдельных TCP-соединений на хост используется одно, а запросы внутри него мультиплексируются — идут "одновременно" в рамках одного канала, без очереди по шесть штук. Это меняет картину принципиально: 180 запросов к одному хосту по HTTP/2 не выстраиваются в 30 волн, они уходят почти одновременно после установки одного соединения. Если у вашего сервера ещё не включён HTTP/2 (или ALPN не настроен так, чтобы браузер его действительно согласовывал), это одна из самых доступных оптимизаций — она не требует переписывать код страницы, только конфиг веб-сервера.
Но здесь есть важная оговорка, которую часто упускают: мультиплексирование убирает штраф за *количество соединений*, а не штраф за *количество запросов как таковое*. Даже по одному соединению каждый запрос всё ещё требует раунда "спросить сервер — дождаться ответа" для получения самого ресурса (не путать с установкой соединения — она действительно одна на хост). Кроме того, у HTTP/2-мультиплексирования есть собственная изнанка: если на уровне TCP теряется один сегмент, это может застопорить все потоки в соединении разом, потому что TCP гарантирует порядок доставки на уровне всего соединения, а не отдельного потока внутри него. Этот эффект — "head-of-line blocking" на транспортном уровне — подробно разобран в статье про мультиплексирование HTTP/2 и почему один медленный сегмент тормозит все запросы сразу. Так что HTTP/2 — это способ параллелить неизбежные запросы эффективнее, а не оправдание не сокращать их число.
Отдельно стоит третий сторонний хост: если аналитика, шрифты и виджет чата размещены на трёх разных доменах, то даже идеально настроенный HTTP/2 на вашем сервере не поможет — браузеру всё равно придётся открыть три отдельных соединения (с DNS и TLS на каждое) к трём чужим хостам, которые вы не контролируете.
Почему на мобильных и дальних соединениях эффект умножается
Всё, что описано выше, — это разговор про RTT, умноженный на число запросов (или "волн" запросов). На быстром локальном или столичном канале RTT до сервера может быть небольшим, и умножение даже на десятки запросов даёт задержку, которую пользователь едва замечает. На мобильном соединении или при доступе с другого континента ситуация другая: и радиоканал сам по себе добавляет задержку (время на установление связи с базовой станцией, буферизацию, возможный хендовер между сотами), и физическое расстояние до сервера добавляет неустранимую задержку распространения сигнала. Если вы разбирали, чем мобильная сеть отличается от проводной с точки зрения соединений — CGNAT, хендоверы, засыпающий радиомодуль — это подробно в статье про мобильный интернет и то, что операторская сеть делает с вашими соединениями.
Механика проста: если условный RTT на хорошем локальном канале в несколько раз меньше, чем RTT на мобильной сети или при обращении к серверу в другом регионе, то и штраф "RTT × число волн запросов" вырастает в те же несколько раз. Причём это не линейная неприятность, а мультипликативная: та же страница с теми же 180 запросами, которая на локальном канале грузится приемлемо, на мобильном соединении с трёхкратно большим RTT может ощущаться загружающейся заметно дольше — просто потому что каждая единица задержки на каждом запросе (или волне запросов) домножилась на больший коэффициент. Именно поэтому оптимизация "просто добавить канала" почти никогда не помогает мобильным пользователям: у них не узкий канал, у них дорогой RTT, и с ним борются не мегабитами, а сокращением числа раундов обмена.
Как сократить число запросов на практике
Раз проблема — в количестве раундов, а не в объёме данных, решение почти всегда в том, чтобы физически уменьшить число отдельных запросов, а не в том, чтобы разогнать канал.
Бандлинг скриптов и стилей. Идея простая: вместо десятков мелких JS- и CSS-файлов, каждый из которых — отдельный HTTP-запрос, собрать их в один (или в несколько крупных) файл на этапе сборки проекта. Это классическая задача сборщиков фронтенда — общая идея важнее конкретного инструмента, но суть всегда одна: меньше файлов на выходе — меньше запросов у клиента. Обратная сторона — при любом изменении инвалидируется весь бандл целиком, так что баланс между "один большой файл" и "несколько логических бандлов" стоит подбирать под частоту деплоев конкретного проекта.
Спрайты и инлайнинг мелких изображений. Много маленьких иконок (меньше пары килобайт каждая) выгоднее либо собрать в один CSS-спрайт (одна картинка, к разным частям которой обращаются через background-position), либо закодировать прямо в CSS/HTML как data URI. В обоих случаях вместо N запросов на N иконок — ноль дополнительных запросов, потому что данные уже пришли вместе с текстом страницы или стилями. Тот же принцип работает для мелких SVG — их часто можно инлайнить прямо в разметку.
Сокращение числа сторонних скриптов. Это самая недооценённая статья расходов. Каждый виджет — счётчик аналитики, чат-виджет, кнопка соцсети, скрипт A/B-тестирования — это не просто один запрос на сам скрипт: это отдельный хост, значит отдельный DNS-резолвинг, отдельное TCP- и TLS-рукопожатие, а часто ещё и собственные дочерние запросы, которые инициирует уже сам загруженный скрипт. Пять сторонних виджетов на странице легко добавляют десятки скрытых запросов, которые не видны в коде страницы напрямую. Практический шаг — регулярно открывать вкладку "Сеть" в инструментах разработчика, сортировать по хосту и честно спрашивать по каждому стороннему домену: действительно ли он окупает свою цену в задержке. Часть виджетов можно грузить асинхронно и с задержкой (после основного контента), часть — вовсе убрать, если аналитика дублируется в нескольких системах одновременно.
Объединение и минимизация шрифтов. Отдельный частый источник лишних запросов — веб-шрифты: несколько начертаний одного семейства, каждое отдельным файлом, часто ещё и с чужого хоста. Где возможно — сократить число начертаний до реально используемых, разместить файлы шрифтов на своём сервере (совмещая с остальными статическими ресурсами по HTTP/2) вместо стороннего хоста.
HTTP/2 (или HTTP/3) там, где число запросов сократить физически нельзя. Если после бандлинга и удаления лишних виджетов у вас всё равно остаётся условная сотня необходимых ресурсов (например, для сайта с большим количеством уникальных изображений в каталоге товаров), включите HTTP/2 на сервере — это уберёт штраф за лимит параллельных соединений на хост, о котором шла речь выше. Проверить, что HTTP/2 действительно согласуется с браузером, можно так:
curl -I --http2 -v https://example.com/ 2>&1 | grep -i "http/2\|alpn"
Если в выводе не видно HTTP/2 — либо сервер не отдаёт ALPN-расширение с h2, либо конфиг веб-сервера собран без поддержки HTTP/2. Для nginx это чаще всего означает добавить http2 в директиву listen для TLS-порта и перезапустить сервис.
Итоговая последовательность действий обычно такая: сначала физически сократить число запросов (бандлинг, спрайты, минус сторонние скрипты), потом убедиться, что оставшиеся запросы идут через переиспользуемое соединение (keep-alive), и только на оставшемся множестве полагаться на HTTP/2 как на инструмент параллелизации того, что сократить уже нельзя.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Сколько запросов считается "много" для обычной страницы?
Точного порога нет — это зависит от RTT до аудитории и от того, идут ли запросы через одно HTTP/2-соединение или упираются в лимит параллельных подключений HTTP/1.1. Ориентируйтесь не на абсолютное число, а на то, есть ли явные "волны" в водопаде запросов в DevTools и сколько разных хостов участвует в загрузке.
Если у меня уже включён HTTP/2, имеет ли смысл ещё и бандлинг?
Да. HTTP/2 убирает штраф за лимит параллельных соединений, но не убирает сам факт, что каждый ресурс — это отдельный раунд обращения к серверу и отдельная единица обработки. Меньше файлов — меньше работы на обеих сторонах, независимо от версии протокола.
Разгон канала (переход на более быстрый тариф) вообще бесполезен?
Не бесполезен — он ускоряет передачу больших объёмов (видео, крупные изображения, скачивание файлов). Но для типичной страницы, где объём невелик, а число мелких ресурсов велико, выигрыш от более широкого канала почти незаметен по сравнению с сокращением числа запросов или самого RTT.
Сторонние скрипты аналитики — правда так дорого стоят?
Каждый добавляет минимум DNS-резолвинг и TCP+TLS рукопожатие к новому хосту, а часто ещё и собственные дочерние запросы. Если на странице стоят два-три разных счётчика аналитики "на всякий случай", это дублирующиеся накладные расходы, которые редко кто-то замечает при аудите скорости.
Как быстро проверить, что именно тормозит — канал или число запросов?
Откройте вкладку "Сеть" в DevTools, включите throttling с профилем медленной мобильной сети и посмотрите на водопад: если время растягивается пропорционально числу запросов и заметны "ступеньки" ожидания между ними — дело в запросах и RTT, а не в объёме данных.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →