Включили HTTP/3 — стало хуже: когда QUIC проигрывает обычному TCP на практике
Команда включает HTTP/3 на сервере, ожидая ускорения по всем канонам — быстрее рукопожатие, нет head-of-line blocking на транспортном уровне, миграция соединения при смене сети. Через неделю метрики показывают обратное: у заметной части пользователей время до первого байта выросло, а не упало, а в логах видна возня с повторными попытками соединения. Это не баг конкретной сборки Nginx и не разовое совпадение — это системное свойство UDP как транспорта: там, где сеть между клиентом и сервером не готова пропускать QUIC, HTTP/3 не ускоряет, а платит цену за саму попытку.
Содержание
Чем QUIC теоретически лучше TCP — и почему это работает не всегда
Три обещания QUIC звучат убедительно, и в правильных условиях он их выполняет. Первое — устранение head-of-line blocking на транспортном уровне. В HTTP/2 поверх TCP все потоки внутри одного соединения мультиплексируются, но само TCP-соединение остаётся одной последовательной трубой: если теряется один сегмент, ядро ОС держит все данные после него в буфере, пока потерянный сегмент не будет передан повторно, — и замирают все запросы разом, даже те, чьи данные уже физически лежат на сервере. QUIC решает это иначе: он реализует независимую доставку по потокам внутри одного UDP-канала, поэтому потеря пакета в одном потоке не блокирует остальные. Подробнее это устройство разобрано в статье про мультиплексирование и голову очереди в HTTP/2, а сама смена транспорта — в материале «Почему HTTP/3 выбросил TCP».
Второе преимущество — более быстрое установление соединения. TCP отдельно выполняет трёхстороннее рукопожатие, а затем поверх него ещё TLS-рукопожатие — это минимум один-два лишних RTT до первого байта данных. QUIC интегрирует транспортное и криптографическое рукопожатие в один обмен и поддерживает 0-RTT для повторных подключений к уже известному серверу, когда клиент может отправить данные в первом же пакете. Третье — миграция соединения: TCP-сессия жёстко привязана к четвёрке (IP-адрес источника, порт источника, IP-адрес назначения, порт назначения), и смена сети (Wi-Fi → мобильный интернет) рвёт соединение. QUIC идентифицирует сессию по Connection ID, независимому от IP и порта, поэтому смена сети не обязана рвать соединение — клиент присылает пакеты с новым адресом, но тем же Connection ID, и сервер продолжает сессию.
Все три пункта — реальные архитектурные преимущества QUIC перед TCP, и в благоприятных условиях (открытый UDP, хороший приём в вышестоящих сетях, современный клиент и сервер) они дают измеримый выигрыш. Проблема в слове «в благоприятных условиях» — оно выполняется далеко не всегда, и дальше речь именно о тех случаях, где оно не выполняется.
UDP не так прозрачен для сети, как кажется
TCP-порт 443 — это, по сути, самый открытый порт в интернете: через него ходит HTTPS-трафик десятилетиями, и любой файрвол, корпоративный прокси или провайдерское оборудование, которое блокирует TCP:443, немедленно рвёт доступ к половине веба — поэтому его практически никогда не трогают. С UDP:443 ситуация другая. UDP исторически ассоциируется с DNS-запросами, VoIP, играми и — что немаловажно для сетевых администраторов — с DDoS-амплификацией: множество атак типа UDP flood и amplification attack используют именно этот протокол, потому что он не требует установления соединения и позволяет подделывать адрес отправителя. В результате часть корпоративных файрволов, часть провайдеров и почти все публичные Wi-Fi-сети (отели, аэропорты, конференц-центры) по умолчанию либо блокируют исходящий UDP на нестандартные порты, либо жёстко троттлят его, оставляя открытыми только известные протоколы вроде DNS на 53-м порту.
Когда браузер пытается установить QUIC-соединение в такой сети, происходит не мгновенный отказ, а тишина: UDP не имеет механизма явного отказа в духе TCP RST — пакеты просто теряются. Клиент отправляет initial-пакет QUIC, не получает ответа, повторяет попытку по таймауту, и только после нескольких неудачных попыток решает, что QUIC недоступен, и откатывается на HTTP/2 поверх TCP. Каждая такая неудачная попытка — это не бесплатная страховка, а реальная задержка до первого байта, которой не было бы, если бы сайт вообще не предлагал HTTP/3 этому клиенту. Именно поэтому массовое включение HTTP/3 без учёта аудитории иногда ухудшает медианные и особенно хвостовые (p95, p99) метрики: медиана может почти не измениться, а именно "плохие" 5–10% сессий из корпоративных сетей и общественного Wi-Fi становятся заметно хуже, чем были на чистом HTTP/2.
Механизм, которым сервер вообще сообщает клиенту о поддержке HTTP/3, — заголовок Alt-Svc:
Alt-Svc: h3=":443"; ma=86400
Он отдаётся по HTTP/2 или HTTP/1.1 и говорит браузеру: «в следующий раз попробуй h3 на этом порту, кэшируй эту информацию на 86400 секунд». Это важная деталь: первое соединение почти всегда идёт по TCP (либо клиент уже знает про h3 из предыдущего визита и пробует QUIC параллельно, по схеме, похожей на Happy Eyeballs). Если Alt-Svc настроен некорректно — например, TTL завышен, а UDP у части аудитории на самом деле недоступен, — браузер будет упорно пытаться h3 при каждом визите в течение суток, добавляя задержку на каждый повторный неудачный хендшейк. О частых ошибках такой настройки на стороне сервера подробно написано в материале «HTTP/3 и QUIC на сервере: частые ошибки и решения».
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверСпутниковые и некоторые мобильные сети: QoS не любит UDP
Отдельная категория проблем — сети, где оборудование на пути пакета применяет активный шейпинг (traffic shaping) или приоритизацию трафика по типу протокола, а не просто пропускает всё подряд. Спутниковый интернет — характерный пример: у геостационарных спутниковых систем исторически большая задержка и специфичная схема управления очередями, заточенная под типичный веб-трафик прошлых десятилетий (то есть в основном TCP), а UDP-потоки могут обрабатываться по остаточному принципу или подвергаться более агрессивному rate limiting при перегрузке канала. LEO-системы спутникового интернета устроены иначе и в целом лучше справляются с задержкой, но конкретное поведение QoS для UDP всё равно зависит от оператора и текущей загрузки спутника, и заранее гарантировать одинаковое качество для TCP и UDP нельзя.
У части мобильных операторов встречается похожая картина: сети 3G/4G/5G используют собственные механизмы управления радиоресурсом и очередями на уровне базовой станции, и в отдельных конфигурациях UDP-трафик получает более низкий приоритет, чем TCP, особенно под нагрузкой на соту в час пик. Это не универсальное правило — многие операторы прекрасно пропускают QUIC, и именно поэтому крупные CDN массово используют HTTP/3 без катастроф. Но для конкретного проекта с конкретной аудиторией (мобильные пользователи в регионе с определённым оператором, B2B-аудитория за корпоративными VPN) поведение может отличаться от усреднённой статистики крупных игроков, и полагаться на чужие благополучные цифры без собственных замеров — рискованно.
Важно отделять две разные проблемы, которые внешне выглядят похоже, но лечатся по-разному: полную недоступность UDP (файрвол блокирует наглухо, откат на HTTP/2 неизбежен и его нужно просто делать быстрым) и деградацию UDP под нагрузкой (пакеты не блокируются, но чаще теряются или задерживаются, чем TCP-пакеты в той же сети). Во втором случае QUIC технически работает, но из-за встроенного механизма пересборки потоков и контроля перегрузки более высокая потеря пакетов может обернуться не преимуществом, а дополнительными накладными расходами по сравнению с TCP, где стек десятилетиями оттюнингован именно под подобные условия. Смежная тема — как маршрут пакета вообще формируется в сети провайдера — разобрана в статье «Пиринг и транзит простыми словами»: именно то, через какие сети реально идёт трафик, определяет, как поведёт себя UDP на конкретном участке.
Серверная сторона: молодой стек стоит дороже по CPU
Вторая практическая проблема — не в сети между клиентом и сервером, а на самом сервере. TCP-стек в ядре Linux десятилетиями оптимизировался: обработка сегментов происходит в ядре, широко используется аппаратное ускорение сетевой карты — сегментация (TSO, GSO), сборка (GRO) и разгрузка контрольных сумм снимают значительную часть нагрузки с CPU. QUIC в подавляющем большинстве текущих реализаций работает в пространстве пользователя (userspace), поверх обычных UDP-сокетов, и не может полноценно опереться на тот же набор аппаратных офлоадов, что закреплён за TCP годами использования в NIC-драйверах и оборудовании. Плюс к этому QUIC сам шифрует заголовки пакетов и делает больше криптографической работы на пакет, чем TLS поверх TCP, где шифруется только полезная нагрузка после установления сессии.
Практическое следствие — при высокой конкурентной нагрузке (много одновременных соединений, интенсивная раздача мелких объектов) обработка QUIC-трафика может потреблять заметно больше CPU на то же количество запросов в секунду, чем HTTP/2 поверх TCP на том же сервере. Насколько именно больше — зависит от конкретной реализации на сервере, версии ядра, наличия аппаратного ускорения на сетевом адаптере и профиля нагрузки, поэтому точные цифры прироста CPU приводить бессмысленно — их нужно снимать на своём железе и своём трафике, а не переносить из чужих бенчмарков. Практический шаг перед включением HTTP/3 на проде — нагрузочный тест именно с h3-трафиком, а не только с h2, и сравнение CPU-профиля при сопоставимом RPS. Базовые шаги настройки самого QUIC-модуля и типичные грабли на этом пути описаны в статье «Как установить и настроить HTTP/3 и QUIC на VPS».
Есть и обратная сторона: UDP экономит на рукопожатии и контроле доставки по сравнению с TCP именно потому, что не тащит за собой встроенную гарантию доставки и порядка на уровне ядра — но у QUIC вся эта гарантия появляется снова, просто реализованная в userspace поверх «голого» UDP. Иными словами, QUIC не «дешевле» TCP по вычислениям — он платит похожую цену за надёжность, просто в другом месте стека, и в текущий момент это место менее отточено индустрией, чем TCP-стек ядра.
Откат на HTTP/2 — это не бесплатная страховка
Одна из самых частых ошибок в проектировании перехода на HTTP/3 — рассуждение вида «ничего страшного, если QUIC не пройдёт, браузер просто откатится на HTTP/2, а мы ничего не теряем». На практике откат почти никогда не бывает мгновенным и незаметным:
- Если браузер уже закешировал заголовок
Alt-Svcс прошлого визита, он попытается открыть QUIC-соединение параллельно с TCP или до него — и если UDP заблокирован, добавит к общей задержке время ожидания таймаута до того, как решит, что нужно использовать альтернативный путь. - Если Alt-Svc ещё не закеширован (первый визит), обычно используется обычный HTTP/2 без риска, но тогда весь смысл включения HTTP/3 для этого визита теряется — выигрыша просто не будет, что тоже стоит закладывать в ожидания от rollout.
- Разные браузеры и клиентские стеки по-разному агрессивно пробуют QUIC и по-разному быстро сдаются — унифицированного поведения «одна попытка, короткий таймаут, чистый откат» across всех браузеров и мобильных приложений на HTTP-клиентах ожидать не стоит.
Итог: механизм отката на HTTP/2 обязателен и должен работать корректно — но сам факт его наличия не делает включение HTTP/3 нейтральным решением с точки зрения задержки для той части аудитории, у которой QUIC не проходит. Отключать HTTP/2 «раз мы всё равно перешли на HTTP/3» — грубая ошибка: без работающего фолбэка часть пользователей вообще потеряет доступ к сайту, а не просто получит его чуть медленнее.
Что делать: измерять, а не гадать
Прежде чем включать HTTP/3 на весь прод и объявлять это готовым решением, разумно пройти несколько практических шагов.
- Не отключайте HTTP/2 при включении HTTP/3. Alt-Svc должен корректно объявлять поддержку h3, но сервер обязан продолжать штатно обслуживать HTTP/2 по TCP:443 — это единственная защита от полного отказа у тех, кому UDP недоступен.
- Мониторьте долю успешных QUIC-соединений отдельно от общего трафика. В логах Nginx (и аналогичных серверов) протокол соединения обычно доступен как переменная (
$http3в самом простом случае, либо разбор поля протокола в структурированных логах) — стоит логировать его отдельным полем и считать долю h3 от всех соединений, а не полагаться на предположение, что раз фича включена, значит все её используют.
log_format main_ext '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'proto=$http3 rtt=$request_time';
- Сравнивайте задержку до первого байта отдельно для h3- и h2-сессий, а не только средние по всему трафику — именно так вылезает картина, где медиана почти не меняется, а хвост распределения (p95/p99) заметно ухудшается у части клиентов.
- Раскатывайте постепенно, на реальной аудитории, а не сразу на 100% — например, через постепенное увеличение TTL у Alt-Svc или через canary-раскатку на часть трафика, если фронт это позволяет, с оглядкой на собственные метрики, а не на чужие отчёты о том, что «HTTP/3 быстрее» у крупных CDN с совершенно другим профилем аудитории.
- Проверяйте нагрузочными тестами именно h3-трафик, если у сервиса высокая конкурентная нагрузка — CPU-профиль стоит сверять до, а не после массового включения.
- Держите под рукой быстрый способ отката — например, снятие заголовка Alt-Svc или полное отключение слушателя QUIC в конфиге — на случай, если метрики после включения покажут ухудшение, а не улучшение.
Ни один из этих шагов не отменяет реальных преимуществ QUIC там, где сеть и сервер к нему готовы — включение HTTP/3 просто не должно быть решением по умолчанию «потому что новее — значит лучше».
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Стоит ли вообще включать HTTP/3, если есть риск ухудшения для части пользователей?
Да, но не вслепую: HTTP/3 остаётся штатно доступным как опция поверх работающего HTTP/2, а не заменой ему, поэтому риск управляем — плохо не наличие HTTP/3, а его включение без мониторинга и без фолбэка.
Как быстро понять, есть ли проблема с QUIC у моей аудитории, не разворачивая сложный мониторинг?
Достаточно на старте логировать долю соединений по протоколу (h3 против h2/h1) и грубо сравнивать $request_time между ними за одинаковый период — заметная разница в пользу h2 или большая доля неудачных попыток h3 обычно видна уже на этом уровне.
Значит ли высокая доля отказов от QUIC, что нужно совсем его выключить?
Не обязательно — иногда это признак конкретного сегмента аудитории (например, корпоративная VPN-сеть у части B2B-клиентов), и решение может быть точечным: например, не форсировать h3 агрессивно через большой TTL у Alt-Svc, а не отказываться от HTTP/3 целиком.
Правда ли, что QUIC всегда сильнее грузит CPU сервера, чем HTTP/2?
Как правило да, при сопоставимой нагрузке, из-за меньшего использования аппаратных офлоадов и дополнительной криптографии на пакет, но точный размер разницы зависит от конкретного стека и профиля трафика — его нужно измерять на своём сервере, а не считать универсальной константой.
Есть ли смысл включать HTTP/3 только для статики, а API оставить на HTTP/2?
Разумный промежуточный вариант: статика чаще выигрывает от параллельной загрузки многих мелких объектов без head-of-line blocking, а API-трафик с меньшим числом более тяжёлых запросов выигрывает не так предсказуемо — стоит тестировать сценарии раздельно.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →