HTTP/3 и QUIC — мультиплексирование без TCP HoL blocking
В предыдущем уроке мы обсудили, что HTTP/2 устранил HoL blocking на уровне HTTP, но оставил его на уровне TCP — если один пакет потерян, все streams в этом TCP-соединении ждут retransmit. На стабильных сетях это незаметно. На мобильных и WiFi с потерями — ощутимо. HTTP/3 решает эту проблему радикально: уходит с TCP на UDP.
Звучит парадоксально: UDP — ненадёжный протокол, без гарантий доставки, без подтверждений. Как поверх него построить ровно тот же HTTP, который требует надёжной доставки? Ответ — QUIC. QUIC — это новый транспортный протокол, который повторяет в user-space всё, что делает TCP (надёжность, ordering, congestion control), но проектируется с учётом современных реалий: мобильные сети, мультиплексирование, TLS как первоклассная фича.
В этом уроке разберём, как устроен QUIC, почему его handshake быстрее TCP+TLS, что такое connection migration и почему это меняет правила игры для мобильных приложений.
Откуда взялся QUIC
Google в 2012 году столкнулся с проблемой: их сервисы (Search, YouTube, Maps) на мобильных пользователях работают плохо. Не из-за CPU или backend’а — из-за сети. Высокая latency, частые потери пакетов, переключения между WiFi/LTE. TCP, разработанный в 1970-х, плохо приспособлен к этому миру.
Google запустил эксперимент: gQUIC — свой proprietary протокол поверх UDP. Через несколько лет, поняв, что работает, передал спецификацию в IETF. В 2021 году IETF опубликовал RFC 9000 — стандартизированный QUIC. На его основе сделали HTTP/3 (RFC 9114).
К 2026 году ситуация такая: Google, Cloudflare, Facebook, YouTube, Akamai — все поддерживают HTTP/3. Браузеры (Chrome, Firefox, Edge, Safari) автоматически переключаются на HTTP/3, если сервер его поддерживает. На стороне сервера это всё ещё немного экзотика: nginx поддерживает HTTP/3 экспериментально, Caddy — из коробки, специализированные серверы (Cloudflare’s quiche) — проще всего.
Стек: HTTP/3 -> QUIC -> UDP
Чтобы понять, почему HTTP/3 такой как есть, надо помнить весь стек:
Ключевая мысль: QUIC — это полная замена TCP+TLS, реализованная в user-space. Это означает:
- Обновления QUIC не требуют изменений ядра ОС — библиотеку обновили, и всё.
- TLS встроен — невозможно сделать unencrypted QUIC (в отличие от plain TCP).
- Метаданные QUIC шифрованы — middlebox’ы не могут вмешиваться (это и плюс, и минус для firewall’ов).
Решение HoL blocking: streams на уровне транспорта
Главная мотивация: в HTTP/2 мультиплексирование streams делается на уровне HTTP, поверх TCP. TCP не знает про streams — для него это один поток байт. Если пакет потерян, TCP не может отдать «соседние» байты приложению, потому что они могут быть из другого stream, но TCP их не различает.
QUIC переносит концепцию streams в сам транспорт. Каждый пакет QUIC может содержать данные нескольких streams. Если пакет потерян — QUIC может продолжить доставлять данные других streams, только не доставит данные потерянного stream до retransmit.
Это не значит, что потеря пакета не влияет вообще. Stream 1 ВСЁ РАВНО будет ждать retransmit потерянного фрагмента. Но другие streams (CSS, картинки, JSON-ответы другого API-запроса) продолжают идти. На странице с 50 ресурсами это сильно меняет visible performance.
0-RTT: handshake почти за ноль времени
В TCP+TLS 1.2 классически handshake занимал 2 RTT (TCP 3-way + TLS обмен ключами). TLS 1.3 уменьшил до 1 RTT (TCP + TLS overlapping). QUIC встроил TLS в свой handshake — получается 1 RTT для нового соединения и 0 RTT для повторного.
0-RTT звучит магически, но имеет нюансы:
- Replay attacks. 0-RTT данные не имеют защиты от повтора — атакующий может перезаписать их позже и сервер выполнит снова. Поэтому 0-RTT можно использовать только для безопасных, идемпотентных запросов — GET / HEAD. POST через 0-RTT — опасно.
- Forward secrecy не полная. Если у атакующего есть session ticket — он может расшифровать 0-RTT данные. Поэтому tickets должны ротироваться часто.
В реальности 0-RTT даёт огромный выигрыш для повторных запросов на тот же сервер (открыли страничку, через минуту вернулись — мгновенный handshake).
Connection migration
Сценарий: вы смотрите видео на YouTube через WiFi. Выходите из дома, телефон переключается на LTE. Ваш IP-адрес меняется. С TCP — катастрофа: соединение разорвано, нужно установить новое (3-way handshake, TLS, заново). Видео паузится, буферизация.
QUIC решает это через connection migration. Соединение идентифицируется не парой (src_ip:port, dst_ip:port), а специальным Connection ID — 8-байтное случайное число, встроенное в каждый QUIC-пакет.
Это фундаментально лучший UX для мобильных приложений. Сейчас приложения вроде Snapchat, TikTok используют это для бесшовного перехода между сетями.
QPACK: header compression без HoL blocking
Помните HPACK в HTTP/2? Там dynamic table обновляется последовательно: каждый header может ссылаться на индексы, которые были раньше. Это работает в TCP (порядок гарантирован), но в QUIC streams могут приходить в любом порядке — HPACK на этом сломался бы.
QPACK — модификация HPACK для work без strict ordering. Использует двунаправленный обмен через два специальных streams (encoder и decoder streams). Чуть сложнее по логике, но работает корректно с QUIC.
В практическом плане для разработчика разницы между HPACK и QPACK нет — библиотека всё делает сама. Знать, что это разные алгоритмы, полезно для отладки и для понимания archive структуры.
Alt-Svc: как сервер сообщает «у меня есть HTTP/3»
Браузер не знает заранее, поддерживает ли сервер HTTP/3. Первое подключение идёт по HTTP/2 (через TLS+TCP). В ответе сервер шлёт заголовок Alt-Svc:
HTTP/1.1 200 OK
Alt-Svc: h3=":443"; ma=86400, h3-29=":443"; ma=86400
Content-Type: text/html
Это значит: «я также доступен по HTTP/3 на порту 443; можешь использовать его следующие 86400 секунд». Браузер на следующем запросе попробует HTTP/3. Если соединение не установится за разумное время — упадёт обратно на HTTP/2.
Alt-Svc — стандартный механизм discovery. Поэтому в production вы видите такой паттерн: первый запрос всегда по HTTP/2, последующие — по HTTP/3.
Минусы и нюансы HTTP/3
Не всё так розово.
-
UDP заблокирован в некоторых сетях. Корпоративные firewall’ы традиционно блокируют UDP-трафик на всё, кроме DNS (53) и нескольких других портов. QUIC на 443 может не пройти. Клиенты падают на HTTP/2 — но это лишний round-trip на проверку.
-
Middleboxes ничего не видят. Метаданные QUIC зашифрованы. Network admins не могут сделать deep packet inspection, traffic shaping, content filtering. Это плюс для privacy, минус для контроля. Корпоративные политики могут запрещать QUIC по этой причине.
-
Сложнее в debugging. Нет
tcpdump | grep HTTP— всё в TLS внутри UDP. Нужен Wireshark + SSLKEYLOGFILE для расшифровки. Инструменты ловят up. -
CPU нагрузка выше. QUIC реализован в user-space, не в ядре. Каждый пакет проходит через TLS-decrypt в user-space, обработка фрагментации тоже. Бенчмарки показывают, что сервер на HTTP/3 потребляет на 20-30% больше CPU при той же нагрузке, чем HTTP/2 (Linux kernel TCP оптимизирован сильнее, чем user-space QUIC). Поэтому большие сайты часто терминируют QUIC на edge (Cloudflare), а внутри инфраструктуры используют HTTP/1.1 или HTTP/2.
-
Не все библиотеки поддерживают. На Python:
requests— нет.httpx— пока нет HTTP/3.aioquic— есть, но low-level. Это меняется, но в 2026 году большинство API-клиентов в скриптах работают на HTTP/1.1 или HTTP/2.
Если вы пишете backend на Python и думаете, что HTTP/3 даст вам выигрыш — скорее всего нет. Backend-to-backend в data center с low latency, no packet loss — HTTP/1.1 keep-alive и HTTP/2 идеально. HTTP/3 имеет смысл на user-facing edge, между мобильным клиентом и CDN.
Реальный пример: проверка HTTP/3 на популярном сайте
# 1. Запросить YouTube -- посмотрите Alt-Svc
curl -sI https://www.youtube.com | grep -i alt-svc
# Что-то вроде: alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
# 2. Заставить curl использовать HTTP/3 (если установлен curl с http3-поддержкой)
# Обычный curl на macOS/Linux не имеет HTTP/3 -- нужен сборка с quiche или ngtcp2
# Проверить: curl --version | grep -o 'HTTP3'
# 3. Если есть curl с HTTP/3:
curl --http3 -v https://www.cloudflare.com 2>&1 | grep -i 'http/3\|http3'
# 4. Альтернатива: онлайн-чекер
# https://http3check.net/
# Введите домен -- сайт скажет, поддерживает ли HTTP/3
# 5. В Chrome DevTools (F12) -> Network -> Protocol column
# Зайдите на cloudflare.com, youtube.com, twitter.com
# Большинство запросов будут h3
# На первом запросе -- h2, на следующих -- h3 (Alt-Svc сработал)
Что в реальной жизни делать с HTTP/3
HTTP/2 multiplexing и переход на HTTP/3 с точки зрения APIДля junior data engineer советы такие:
-
На user-facing front-end / CDN. Если ваш сайт публичный, на мобильную аудиторию — включите HTTP/3 на CDN (Cloudflare, Fastly, CloudFront). Бесплатное улучшение latency на мобильных и flaky-сетях.
-
Backend-to-backend (microservices). НЕ надо. HTTP/1.1 + keep-alive или HTTP/2 в data center — идеально. QUIC overhead тут только мешает.
-
API-клиенты в скриптах. Не парьтесь.
requests/httpxидут на HTTP/1.1 или HTTP/2 — этого достаточно. -
Мобильное приложение. Если у вас native app и пользователи на мобильных — HTTP/3 серьёзный буст. Используйте библиотеки (Cronet для Android, NSURLSession с HTTP/3 поддержкой на iOS).
Что вы должны вынести
- HTTP/3 = HTTP-семантика, но транспорт — QUIC поверх UDP.
- QUIC — замена TCP+TLS в user-space: reliability, streams, encryption — всё в одном слое.
- Решает TCP HoL blocking — streams независимы на транспорте.
- 0-RTT для повторных соединений (только для safe методов).
- Connection migration — соединение переживает смену IP.
- Альт-сервер discovery через
Alt-Svcheader. - Минусы: UDP может быть заблокирован, выше CPU нагрузка, инструменты дебага хуже.
- Backend-to-backend HTTP/3 обычно не нужен — польза на user-facing edge.