MAATRIX / Блог / Что происходит за те 300 мс, пока открывается ваш сайт

Что происходит за те 300 мс, пока открывается ваш сайт

MAATRIX

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

Таймлайн одной загрузки

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

Грубая раскладка, куда уходит время до появления первого пикселя на экране (это ориентир, а не измерение — у вас цифры будут другие в зависимости от расстояния до сервера, состояния кэшей и загрузки канала):

ЭтапЧто происходитПорядок величины
DNS-резолвингдомен превращается в IP0 мс (в кэше) — сотни мс (холодный, через несколько NS)
TCP-хендшейкустанавливается транспортное соединение1 RTT до сервера
TLS-хендшейксогласуется шифрование1 RTT (TLS 1.3) или 2 RTT (TLS 1.2 full handshake)
Запрос + TTFBсервер получает запрос и формирует первый байт ответаRTT + время обработки на сервере
Загрузка контентабраузер докачивает HTML, CSS, JS, картинкизависит от размера и числа ресурсов
Рендербраузер строит DOM, CSSOM, дерево рендера и красит пикселизависит от сложности страницы

Ключевая вещь: RTT (round-trip time, время туда-обратно до сервера) — не разовая плата, а списывается на каждом этапе с обменом пакетами: раз на TCP, ещё раз (или два) на TLS, ещё раз на сам HTTP-запрос. При ping до сервера 40 мс только на соединение и первый запрос уйдёт 80-160 мс — задолго до того, как сервер начнёт готовить ответ. Отсюда и эффект от выбора ближайшей локации ещё до всякой оптимизации бэкенда.

DNS: от домена до IP-адреса

Первое, что делает браузер, — не открывает соединение, а спрашивает: «а по какому адресу вообще стучаться». Цепочка резолвинга выглядит так: браузер проверяет свой DNS-кэш (у Chrome отдельный, смотреть на chrome://net-internals/#dns) → если промах, идёт в кэш ОС (systemd-resolved/nscd в Linux, свой резолвер в Windows) → если и там пусто, запрос уходит к рекурсивному резолверу — это либо DNS провайдера, либо публичный (1.1.1.1, 8.8.8.8), если вы его прописали руками → резолвер, если у него самого нет кэша, обходит корневые серверы → серверы зоны .io/.com → авторитативные NS вашего домена, которые и отдают финальный A- или AAAA-запись.

Каждый из этих прыжков — это тоже round-trip, просто не до вашего сервера, а до чужой инфраструктуры. Если авторитативные NS вашего домена перегружены или физически далеко (частая ситуация с DNS «в комплекте» у дешёвых регистраторов), холодный резолвинг может съесть заметную часть бюджета — притом что вы это время физически не видите: браузер просто «думает», прежде чем в сетевой панели вообще появится первая строка.

Отсюда два практических вывода. Во-первых, TTL записи — это компромисс: низкий TTL (60-300 секунд) даёт гибкость при миграции между серверами, но заставляет резолверы чаще ходить за свежим ответом; высокий TTL (3600+ секунд) экономит резолвинги, но вы дольше катите изменения. Во-вторых, если DNS для домена держит тот же провайдер, что и хостинг сайта, стоит убедиться, что это не единая точка отказа и не узкое место — многие переносят зону на отдельный сервер имён, когда нужен контроль над временем ответа. Сюда же относится классическая головная боль при переезде сервера: пока не истёк TTL старой записи, часть посетителей продолжает стучаться по старому IP, и это выглядит как «сайт то работает, то нет», хотя на самом деле работает исправно — просто не для всех одновременно.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать VPS

TCP и TLS: два рукопожатия до первого байта

Адрес получен, дальше — установление соединения. TCP делает это в три шага: SYN, SYN-ACK, ACK. Полезные данные пойдут только после этого — TCP-хендшейк уже стоит один RTT, прежде чем передан хотя бы один байт запроса.

Дальше — TLS, если сайт на HTTPS (а он почти наверняка на HTTPS). Здесь разница между версиями протокола ощутима: TLS 1.2 с полным хендшейком (client hello, server hello с сертификатом, обмен ключами, finished) стоит два дополнительных round-trip'а; TLS 1.3 ужимает согласование до одного RTT, потому что клиент сразу присылает предполагаемые параметры шифрования вместе с client hello. При возобновлении сессии (session resumption) с сохранённым session ticket TLS 1.3 может обойтись и вовсе без дополнительного RTT (0-RTT) — с оговоркой, что такие данные потенциально уязвимы к replay-атакам, поэтому серверы разрешают в этом режиме только идемпотентные запросы.

Итого: на «холодном» соединении по HTTPS вы платите минимум 2 RTT (TCP + TLS 1.3) до того, как браузер вообще отправит HTTP-запрос — и это без учёта проверки отзыва сертификата (OCSP), которая при отсутствии OCSP stapling добавляет ещё один поход, уже к серверу удостоверяющего центра.

Здесь же стоит вспомнить про QUIC и HTTP/3: транспорт поверх UDP объединяет установление соединения и согласование шифрования в один обмен и устраняет head-of-line blocking на уровне TCP — потеря одного пакета не тормозит остальные потоки. Выигрыш заметнее всего на нестабильных мобильных сетях с потерями пакетов, а не в идеальных лабораторных условиях; как включить — есть отдельный разбор настройки HTTP/3 и QUIC на VPS.

Запрос к серверу и TTFB

TTFB (time to first byte) — это RTT до сервера плюс время, которое сервер тратит на подготовку ответа. Именно этот второй компонент чаще всего путают с «скоростью сервера» — а внутри него может происходить что угодно.

Если это статика — картинка, CSS, шрифт — веб-сервер (Nginx, Caddy) отдаёт файл из page cache операционной системы за доли миллисекунды. Если запрос идёт в приложение через reverse proxy, картина другая: Nginx принимает соединение, матчит его на нужный server блок по имени хоста и SNI, проксирует запрос дальше — в PHP-FPM, Node-процесс, Gunicorn/uWSGI. Backend может обратиться к базе данных, к кэшу (Redis/Memcached), к внешнему API — и вот тут легко потерять не миллисекунды, а десятки и сотни: запрос без индекса, N+1 к базе, холодный воркер, который только что заспавнился и ещё не прогрел JIT/opcache. Ответ идёт обратно тем же путём.

Пара вещей, которые здесь реально экономят время:

  • Кэширование на уровне Nginx (proxy_cache или fastcgi_cache) — если контент не персонализирован, можно вообще не доходить до backend, отдавая готовый ответ из памяти или с диска. Разбор конфигурации — в статье про кэширование Nginx на VPS.
  • Keep-Alive соединения — без него каждый запрос (в том числе за каждым отдельным CSS-файлом или картинкой на странице) заново проходит TCP- и TLS-хендшейк. На HTTP/1.1 это было особенно больно; HTTP/2 решает проблему мультиплексированием — несколько запросов через одно уже установленное соединение, без повторных рукопожатий на каждый ресурс.

Рендер в браузере: быстрый сервер — не то же самое, что быстрый сайт

Тут важно разделить: TTFB — это про сеть и сервер, а всё, что после получения HTML, — работа браузера, и она может съесть больше времени, чем всё предыдущее вместе взятое.

Грубо: браузер парсит HTML и строит DOM, параллельно парсит CSS и строит CSSOM, объединяет их в дерево рендера, считает layout (геометрию каждого элемента), красит пиксели. Проблема в том, что этот процесс не свободен от сети — он то и дело в неё упирается:

  • <script> без defer или async в <head> блокирует парсинг HTML, пока файл не скачается и не выполнится — браузер буквально останавливается и ждёт.
  • Цепочки @import в CSS работают последовательно, а не параллельно — каждый импорт добавляет round-trip.
  • Шрифты, аналитика, виджеты с сторонних доменов — это отдельные DNS-резолвинги и отдельные TLS-хендшейки для каждого нового origin, и они происходят по ходу рендера, а не заранее.

Отсюда практика с <link rel="preconnect"> и dns-prefetch: это подсказка браузеру заранее (пока грузится и рендерится основной документ) выполнить DNS-резолвинг и, для preconnect, ещё и TCP+TLS хендшейк к домену, который понадобится через секунду — например, к CDN шрифтов или к домену аналитики. Так к моменту, когда браузер реально обратится за ресурсом, соединение уже готово и не нужно ждать очередной RTT.

Браузер также ограничивает число параллельных соединений к одному origin (исторически — около шести на HTTP/1.1), поэтому раньше практиковали шардинг ресурсов по поддоменам; с HTTP/2 это в основном не нужно и даже вредно — лишние соединения означают лишние хендшейки.

Как искать потерянные миллисекунды на практике

Все предыдущие разделы — не теория ради теории: именно это разбиение на этапы и есть инструмент диагностики. Когда «сайт долго грузится» — вопрос не «почему», а «на каком именно этапе».

Самый быстрый способ измерить это без браузера — curl с шаблоном таймингов:

curl -o /dev/null -s -w "dns: %{time_namelookup}\ntcp: %{time_connect}\ntls: %{time_appconnect}\nttfb: %{time_starttransfer}\ntotal: %{time_total}\n" https://example.com

Важный нюанс: значения здесь — это накопленное время от начала запроса, а не длительность отдельного этапа. Чтобы получить именно длительность TLS-хендшейка, нужно вычесть time_connect из time_appconnect, а не смотреть на time_appconnect как есть.

В браузере то же самое видно в DevTools → Network → клик по запросу → вкладка Timing: там явно подписаны DNS Lookup, Initial connection, SSL, Waiting (TTFB), Content Download — тот же таймлайн, что мы разбирали выше, но с конкретными числами для конкретного запроса на конкретной сети.

Типичная ошибка, растущая именно из непонимания этой механики: команда полгода оптимизирует код бэкенда ради условных 10-20 мс, пока TTFB и так уже неплохой, — но не замечает, что keep-alive выключен в конфиге прокси, DNS TTL стоит 60 секунд без причины, HTTP/2 не включён, а сервер физически стоит в другом полушарии относительно основной аудитории. Начинать разбор стоит не с кода, а с транспортного уровня: RTT до сервера, версия TLS, включён ли keep-alive и HTTP/2/3, есть ли кэш перед бэкендом. Если аудитория географически распределена, CDN снимает часть проблемы, приближая статику к пользователю — это отдельная тема настройки кэширующей сети перед сервером.

Отдельно стоит проверять сам RTT: методика с ping/traceroute/mtr, без веры в маркетинговые таблицы провайдеров, описана в статье как измерить реальный пинг до сервера — она пригодится и здесь, потому что RTT — это множитель для каждого из этапов выше, а не разовая константа.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать VPS

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Пинг до сервера 20 мс, а сайт всё равно грузится секунду — почему?

Потому что пинг — это один ICMP round-trip, а полная загрузка страницы — это несколько последовательных RTT (TCP, TLS, HTTP-запрос) плюс TTFB на сервере плюс загрузка десятков ресурсов плюс рендер в браузере. Низкий пинг снижает цену каждого отдельного шага, но не убирает сами шаги.

HTTP/3 действительно ускоряет загрузку?

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

CDN нужен, даже если сервер и так рядом с большинством пользователей?

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

Можно ли назвать точную цифру, сколько миллисекунд сэкономит keep-alive или HTTP/2?

Нет — зависит от числа ресурсов на странице, версии протокола до этого и характеристик сети. Надёжный способ узнать цифру для вашего случая — измерить до и после через curl -w или DevTools, а не полагаться на общие оценки.

Обсудить статью, задать вопрос или начать новую тему

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

Перейти в сообщество →