ESPN DeportesCalificaciones de los mexicanos en el extranjeroInquirerDILG chief Remulla visits QC jail ahead of looming Romualdez transferThe Jerusalem PostA good deal in bad neighborhoods: Why the Gulf’s best bet remains in Jerusalem - opinionESPNPower Rankings: Everything we learned from the top 25 in Week 2RTP DesportoEuroVolley 2026. Portugal soma quarta derrota consecutiva frente à UcrâniaBBC NewsI had 11 years of chemotherapy for a cancer I didn't have20 MinutenUrsache unklar: Fischsterben im MühlebachComplete SportsBlackburn Give Injury Update On Super Eagles StarESPN CricinfoFleming to link up with T20I squad in preparation for Test coaching stintBBC عربيجماعة أنصار الله تعلن استهداف قاعدة ثانية في السعودية، ومحمد بن سلمان يلتقي قائد القيادة المركزية الأمريكيةIl Fatto QuotidianoKimi Antonelli ora ha in mano il titolo di Formula 1: quel vantaggio su Russell e il sogno di una passerella trionfaleABC News4 people hospitalized after a crane collapsed at a Miami construction site: Officials
The Daily Newsstand · Free, Always
Monday, September 14, 2026

Где HTTP/2 и HTTP/3 реально быстрее, а где не дают ничего

Translate

Про HTTP/2 и HTTP/3 написано столько, что вопрос кажется закрытым. Включаешь — и всё летает. Так, во всяком случае, рассказывают. Мультиплексирование убирает очередь из запросов, QUIC держится, когда пакеты теряются, рукопожатие укладывается в один круг вместо двух. Звучит складно. И, наверное, где-то так и есть.

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

Поэтому я собрал стенд и померил сам. Caddy раздаёт одни и те же данные по всем трём протоколам сразу, tc netem изображает шесть сетей — от дата-центра до края соты. Дальше — что из этого вышло. Включая те случаи, где новые протоколы проигрывают старому. И три ловушки в методике, каждая из которых едва не увела меня в публикацию красивой неправды.

Поэтому я собрал стенд и померил сам. Caddy раздаёт одни и те же данные по всем трём протоколам сразу, tc netem изображает шесть сетей — от дата-центра до края соты. Дальше — что из этого вышло. Включая те случаи, где новые протоколы проигрывают старому. И три ловушки в методике, каждая из которых едва не увела меня в публикацию красивой неправды.

❯ Что именно мы проверяем

HTTP/1.1 — это один запрос за раз в одном соединении. RFC 9113 прямо называет это причиной, по которой клиенты открывают к серверу сразу несколько соединений. Сколько именно — спецификация не фиксирует, браузеры сошлись на шести на хост. Не больше, не меньше.

HTTP/2 делает иначе: много запросов едут одновременно в одном TCP-соединении. Очередь на уровне HTTP исчезает. Но остаётся уровнем ниже — и это важно. TCP обязан отдать приложению байты строго по порядку. Поэтому, как только теряется сегмент, стек придерживает в приёмном буфере всё, что пришло после, и ждёт, пока потерянное не восстановят. Это и называют head-of-line blocking: встают все потоки соединения разом, даже те, чьи сегменты дошли целыми.

HTTP/3 переносит всё на QUIC поверх UDP. Порядок доставки гарантируется внутри каждого потока по отдельности — потерянный пакет блокирует только свой поток, соседей не трогает. Заодно QUIC складывает транспортное рукопожатие и TLS 1.3 в одну процедуру. Там, где TCP тратит круг на SYN и SYN-ACK, а TLS следом ещё один, QUIC укладывается в один.

Три обещания. Все три можно проверить измерением, а не верить слайдам.

❯ Стенд

Компонент

Что именно

Сервер

Caddy 2.11.4, один порт, протоколы h1 h2 h3 одновременно

Клиент

curl 8.2.1-DEV: BoringSSL, nghttp2 1.52.0, quiche 0.18.0

ОС

Ubuntu 24.04, ядро 6.8.0

Сеть

loopback с MTU 1500, эмуляция tc netem, полоса 100 Мбит/с

Один и тот же бинарник curl обслуживает все три протокола — меняется только флаг. Это не мелочь. Сравнивать HTTP/2 из одной сборки с HTTP/3 из другой бессмысленно: разница между сборками перекроет разницу между протоколами, и вы будете измерять чужой код, а не транспорт.

Второй вопрос снимаю сразу — управление перегрузкой одинаково по обе стороны. В ядре net.ipv4.tcp_congestion_control = cubic, у quiche алгоритм по умолчанию тоже CUBIC. Значит, всё, что я увижу ниже, — это различия транспорта, а не выбора алгоритма.

И одна настройка, без которой замер вообще бессмыслен. QUIC работает поверх UDP, а стандартный приёмный буфер сокета его душит — Caddy при старте прямо жалуется в лог. Документация quic-go рекомендует поднять буфер до 7 МБ:

sudo sysctl -w net.core.rmem_max=7500000
sudo sysctl -w net.core.wmem_max=7500000

Если вам попадётся бенчмарк, где HTTP/3 проигрывает, первым делом спросите — поднимали ли там буфер. Обычно нет.

Профили сети. netem на loopback применяется к обоим направлениям, поэтому RTT равен удвоенной задержке — проверено замером времени TCP-рукопожатия. По той же причине удваиваются потери: в колонке «потери 1%» читайте «1% на каждое направление».

Сценарий

Настройка netem

Измеренный RTT

Дата-центр

Нет

0,15 мс

Проводной

delay 20ms rate 100mbit

40,16 мс

Другой континент

delay 75ms rate 100mbit

150,24 мс

Мобильный

delay 30ms loss 1%

60,22 мс

Плохой мобильный

delay 30ms loss 3%

60,25 мс

Край соты

delay 50ms loss 5%

100,42 мс

Четыре теста. Десять файлов по 8 КБ. Пятьдесят таких же. Один файл 3 МБ. И холодное соединение. HTTP/1.1 получает шесть параллельных соединений, HTTP/2 и HTTP/3 мультиплексируют потоки в одном — проверено счётчиком num_connects. Медиана из пяти прогонов, для крупного файла — из трёх, таймаут две минуты.

❯ Десять файлов: шесть окон перегрузки против одного

Сценарий

HTTP/1.1

HTTP/2

HTTP/3

Лучший

HTTP/3 к HTTP/2

Дата-центр, RTT 0

7 мс

9 мс

6

HTTP/3

-33%

Проводной, RTT 40 мс

170 мс

210 мс

146

HTTP/3

-30%

Другой континент, RTT 150 мс

615 мс

767 мс

519

HTTP/3

-32%

Мобильный, RTT 60 мс, потери 1%

255 мс

348 мс

214

HTTP/3

-39%

Плохой мобильный, RTT 60 мс, потери 3%

569 мс

368 мс

270

HTTP/3

-27%

Край соты, RTT 100 мс, потери 5%

846 мс

708 мс

466

HTTP/3

-34%

Первое, что бросается в глаза: на каналах без потерь HTTP/2 проигрывает HTTP/1.1. На проводном — 210 мс против 170, на межконтинентальном — 767 против 615. Ровно противоположно тому, что обещают слайды.

Объяснение простое, даже обидно простое. Браузер открывает шесть соединений, и десять файлов укладываются в два RTT. У каждого соединения своё окно перегрузки, все шесть проходят slow start параллельно — за тот же круг в сеть уходит вшестеро больше сегментов. А HTTP/2 кладёт всё в одно соединение и разгоняет одно-единственное окно. Мультиплексирование тут не отыгрывается: очереди из запросов и так нет.

HTTP/3 выигрывает у HTTP/2 стабильно — от 27 до 39 процентов. Я сначала списал это на рукопожатие, потом посчитал. На проводном канале отрыв 64 мс при экономии на рукопожатии 38, на межконтинентальном — 248 против 149. Рукопожатие объясняет от 41 до 60 процентов, остальное без объяснения. Скорее всего, начальное окно перегрузки у QUIC своё и разгоняется иначе — но чтобы утверждать это, нужна трассировка, а у меня её нет.

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

Сценарий

HTTP/1.1

HTTP/2

HTTP/3

Лушчий

HTTP/3 к HTTP/2

Дата-центр, RTT 0

14 мс

12 мс

15 мс

HTTP/2

+25%

Проводной, RTT 40 мс

461 мс

306 мс

227 мс

HTTP/3

-26%

Другой континент, RTT 150 мс

1,67 с

1,07 с

827 мс

HTTP/3

-23%

Мобильный, RTT 60 мс, потери 1%

805 мс

492 мс

355 мс

HTTP/3

-28%

Плохой мобильный, RTT 60 мс, потери 3%

1,12 с

1,16 мс

520 мс

HTTP/3

-55%

Край соты, RTT 100 мс, потери 5%

2,07 с

1,92 мс

1,12 с ⚠ 4/5

HTTP/3

-41%

Пятьдесят файлов по 8 КБ — здесь мультиплексирование наконец окупается

Пятьдесят файлов по 8 КБ — здесь мультиплексирование наконец окупается

Картина переворачивается. Пятьдесят файлов на шести соединениях — это девять кругов, и HTTP/1.1 платит за каждый. На межконтинентальном канале 1,67 секунды против 827 миллисекунд у HTTP/3. Разница вдвое, и она видна невооружённым глазом.

И тут же главная оговорка. Посмотрите на первую строку: в дата-центре, где RTT практически ноль, все три протокола укладываются в 12–15 миллисекунд. Разница лежит в пределах шума. Мультиплексирование окупается только тогда, когда круговая задержка заметна. Нет задержки — нет выигрыша. Вот, собственно, и всё.

❯ Один поток на 3 МБ: упираемся в пропускную способность

Сценарий

HTTP/1.1

HTTP/2

HTTP/3

Лучший

HTTP/3 к HTTP/2

Дата-центр, RTT 0

5 мс

8 мс

22 мс

HTTP/1.1

+186%

Проводной, RTT 40 мс

631 мс

632 см

654 мс

HTTP/1.1

+4%

Другой континент, RTT 150 мс

1,62 с

1,62 с

2,10 с

HTTP/2

+30%

Мобильный, RTT 60 мс, потери 1%

4,59 с

1,22 с

6,66 с

HTTP/2

+446%

Плохой мобильный, RTT 60 мс, потери 3%

16,90 с

18,19 с

10,92 с

HTTP/3

-40%

Край соты, RTT 100 мс, потери 5%

32,10 с

40,31 с

19,42 с ⚠ 1/3

HTTP/3

-52%

Один файл 3 МБ, шкала логарифмическая. На проводном канале все три протокола показывают одно и то же

Один файл 3 МБ, шкала логарифмическая. На проводном канале все три протокола показывают одно и то же

Строка «Проводной» — это квинтэссенция всей статьи. 631, 632 и 654 миллисекунды. Три протокола, разных на десять лет разработки, показывают одно и то же время. Потому что все трое упираются в пропускную способность. Узкое место лежит вне протокола.

Прикиньте порядок величин. При 100 Мбит/с и RTT 40 мс произведение полосы на задержку — около 500 КБ. Файл 3 МБ — это шесть таких окон, и заметная часть передачи уходит на разгон.

Строка «Дата-центр» интереснее. На идеальном локальном канале HTTP/1.1 отдаёт файл за 5 мс, HTTP/2 — за 8, HTTP/3 — за 22. Новые протоколы проигрывают старому вчетверо. Когда сеть перестаёт быть узким местом, им становится процессор: HTTP/2 тратит такты на фрейминг и сжатие заголовков HPACK, а у QUIC весь транспорт живёт в пространстве пользователя — каждая датаграмма проходит через системный вызов и шифрование, тогда как TCP-стек работает в ядре.

❯ Рукопожатие: экономия ровно в один RTT

Сценарий

HTTP/1.1

HTTP/2

HTTP/3

Лучший

HTTP/3 к HTTP/2

Дата-центр, RTT 0

1 мс

2 мс

2 мс

HTTP/1.1

-2%

Проводной, RTT 40 мс

122 мс

124 мс

86 мс

HTTP/3

-30%

Другой континент, RTT 150 мс

452 мс

452 мс

303 мс

HTTP/3

-33%

Мобильный, RTT 60 мс, потери 1%

182 мс

182 мс

123 мс

HTTP/3

-33%

Плохой мобильный, RTT 60 мс, потери 3%

182 мс

182 мс

125 мс ⚠ 3/5

HTTP/3

-32%

Край соты, RTT 100 мс, потери 5%

349 мс

303 мс

203 мс

HTTP/3

-33%

Гипотеза «QUIC экономит ровно один круг». Замеры ложатся на неё сами

Гипотеза «QUIC экономит ровно один круг». Замеры ложатся на неё сами

Здесь HTTP/3 выигрывает предсказуемо: примерно на треть во всех сценариях с задержкой. TCP тратит круг на SYN и SYN-ACK, и только по установленному соединению идёт TLS 1.3, забирая ещё один. А у QUIC криптография едет прямо в первых пакетах транспорта — отдельного TLS-рукопожатия после установки соединения просто нет.

Арифметика сходится до миллисекунд. На межконтинентальном канале с RTT 150 мс: 452 мс у TCP против 303 у QUIC. Разница — 149 мс, ровно один круговой обмен.

Одна оговорка. В начале рукопожатия сервер не имеет права отправить на неподтверждённый адрес больше чем втрое от полученного объёма — RFC 9000 называет это anti-amplification limit. В этот лимит входит цепочка сертификатов. Если она объёмная, ответ не помещается, и рукопожатие съедает лишний круг. У меня сертификат локального центра сертификации маленький, поэтому экономия и легла точно в один RTT.

❯ Потери пакетов: откуда берётся разброс в разы

Девять прогонов файла 3 МБ подряд в одних и тех же условиях.

Девять загрузок одного файла подряд. На чистом канале точки сливаются, под потерями — разлетаются

Девять загрузок одного файла подряд. На чистом канале точки сливаются, под потерями — разлетаются

При потерях 1% худший прогон HTTP/1.1 хуже лучшего в 13,5 раза. У HTTP/2 — в 11,3. У HTTP/3 — только в 2,4. На чистом канале у всех троих разброс — единица.

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

$ cat /sys/module/tcp_cubic/parameters/beta 717 

QUIC устроен иначе. Номера пакетов у него монотонно растут и не повторяются — повторная передача едет под новым номером. Исчезает та самая неоднозначность, из-за которой TCP не может понять, какому из двух одинаковых сегментов пришло подтверждение, и портит себе оценку RTT. Вместо таймаута повторной передачи работает probe timeout по RFC 9002.

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

❯ Пять процентов потерь: HTTP/3 встаёт намертво

Последний сценарий — RTT 100 мс и 5% потерь. Обычная жизнь на границе покрытия.

По медианам выходила красивая картинка: 32,1 секунды у HTTP/1.1, 40,3 у HTTP/2 и всего 19,4 у HTTP/3. Отличный финал для статьи. Потом я посмотрел, сколько прогонов вообще дошло до конца. У HTTP/3 — один из трёх.

Ни один прогон HTTP/1.1 и HTTP/2 не сорвался. Все обрывы у HTTP/3

Ни один прогон HTTP/1.1 и HTTP/2 не сорвался. Все обрывы у HTTP/3

За весь эксперимент таких набралось семь. И все семь — у HTTP/3. Отдельная серия с проверкой кода возврата показала CURLE_OPERATION_TIMEDOUT: не разрыв соединения и не ошибка QUIC, а обычный таймаут.

Дальше я снял трафик tshark'ом. И картина оказалась неожиданной. Зависание — это не медленная передача, а полная остановка:

run1  19,8 с  успех    пауз >300 мс: 0
run2 120,3 с  таймаут  одна пауза 108,4 с (встал на 11,6 с, отдав 1,77 МБ)
run4 120,2 с  таймаут  одна пауза 119,3 с (встал на 0,69 с, отдав 0,14 МБ)
run6  24,1 с  успех    пауз >300 мс: 0

Три зависания из шести устроены одинаково. Последний пакет перед тишиной — всегда от клиента, короткое подтверждение на 77 байт. После него сервер не присылает ничего, а клиент не отправляет ни одного зондирующего пакета, хотя RFC 9002 их предписывает. Обе стороны просто ждут друг друга — пока curl не закроет соединение по своему таймауту.

Точка срыва произвольная. Один раз — после 1,77 МБ. Два раза — после 0,15 МБ, на первой секунде. Версия про исчерпание окна на фиксированной границе отпала.

Назвать причину я не берусь. QUIC шифрует типы фреймов, и чтобы увидеть последний отправленный, нужен захват с ключами. Фиксирую то, что воспроизводится: связка curl 8.2.1-DEV с quiche 0.18.0 и Caddy на quic-go под потерями от 3% примерно в половине случаев встаёт намертво. Сборка клиента старая — на свежей стоит перепроверить.

❯ Три ловушки, в которые я попался

Все три раза ошибка выглядела как свойство протокола. И все три раза оказывалась свойством стенда.

MTU у loopback. Первый вариант стенда давал разброс в восемь раз между двумя запусками одной команды. Очередь netem была ни при чём — tc -s qdisc показывал dropped 0. Тогда я померил канал голым сокетом на Python, без всякого HTTP: чистый TCP выдал 34 МБ/с там, где curl еле выжимал 1 МБ/с. Тридцатикратный разрыв указывал на стенд. Причина — MTU интерфейса lo равен 65536, в сорок раз больше обычного Ethernet, и окно перегрузки, которое считается в сегментах, ведёт себя рвано. Лечится строчкой ip link set lo mtu 1500.

Медиана по уцелевшим прогонам. Те самые «19,4 секунды», посчитанные по одному успешному измерению из трёх. Формально не ложь: медиана успешных загрузок действительно такая. Просто читатель никогда бы не узнал, что каждая вторая попытка не заканчивается. Теперь генератор таблиц помечает такие клетки: 19,42 с ⚠ 1/3. Шесть символов — а смысл строки меняют.

Распределение потерь. Самую неприятную я нашёл, уже когда садился писать выводы. В захвате оказалось, что кадры QUIC на loopback доходят до 14 452 байт при MTU 1500: quic-go отдаёт ядру несколько пакетов одним системным вызовом. У TCP максимальный кадр — 2962 байта.

netem стоит до сегментации и роняет кадр целиком. Я решил, что это занижает HTTP/3, посчитал — оказалось, нет: доля потерянных пакетов совпадает, 124 против 125 из примерно 2500. Отличается распределение. Со склейкой потери приходят пачками — по два-одиннадцать подряд. Без склейки — поодиночке.

Я отключил склейку переменной QUIC_GO_DISABLE_GSO=true и перемерил всё заново.

Одна и та же доля потерь. Гладкие столбики — потери пачками, штрихованные — поодиночке

Одна и та же доля потерь. Гладкие столбики — потери пачками, штрихованные — поодиночке

50 файлов

HTTP/1.1

HTTP/2

HTTP/3

Мобильный, потери 1%

805 → 756 мс

492 → 645 мс

355 → 715 мс

Плохой мобильный, 3%

1,1 → 1,1 с

1,2 → 1,0 с

520 → 854 мс

Край соты, 5%

2,1 → 1,7 с

1,9 → 3,9 с

1,1 → 2,5 с

При одной и той же доле потерь HTTP/3 вдвое медленнее, когда потери рассеяны. У HTTP/1.1 время почти не меняется. У HTTP/2 скачет без системы. Распределение бьёт в основном по QUIC — и это, в общем, логично: у него пятьдесят потоков восстанавливаются независимо, поэтому рассеянные потери задевают много потоков сразу, а пачка — один.

Возражение про процессор снимается контролем. Если бы дело было в лишних системных вызовах без склейки, это проявилось бы и на чистых каналах, а там ничего не изменилось — 461 против 455 мс, 306 против 308, 227 против 229.

Зависания HTTP/3, кстати, никуда не делись и во второй конфигурации. Сместились в другой сценарий, но остались. И по-прежнему только у HTTP/3.

❯ Что из этого следует

Ситуация

HTTP/2 против HTTP/1.1

HTTP/3 против HTTP/2

Сервер и клиент в одном дата-центре

ничего, иногда хуже

хуже

Один крупный файл, любой канал

ничего

ничего или хуже

Десяток мелких файлов

часто хуже

лучше на треть

Полсотни мелких файлов, заметный RTT

вдвое лучше

лучше на четверть

Мелкие файлы плюс потери

по-разному

зависит от распределения потерь

Установка соединения, заметный RTT

ничего

лучше на треть

Включать HTTP/2 и HTTP/3 стоит. Ни в одном реалистичном сценарии они не сделали заметно хуже — проигрыши вылезли только на loopback, где сеть вырождена. Но чуда не ждите там, где узкое место в другом. Если у вас один большой файл, медленная база или тяжёлый бэкенд — смена протокола не изменит ничего.

Больше всего выигрывают те, у кого много мелких ресурсов и далёкие пользователи. Если весь трафик приходит из соседнего дата-центра, эффект будет околонулевым.

HTTP/3 нужен как раз там, где есть потери: мобильный интернет, публичный Wi-Fi, дальние маршруты. На стабильном проводном канале его преимущество сводится к одному сэкономленному кругу на рукопожатии. И проверьте UDP-буфер: полстроки в sysctl отделяют работающий HTTP/3 от протокола, который у вас «почему-то медленный».

❯ Границы применимости

Замеры сделаны на эмулированной сети, и характер потерь у netem зависит от сегментации — совпадение с реальной сетью случайное. Клиент curl, а не браузер: нет разбора HTML, приоритизации, кеша и предзагрузки. Сервер один, реализации QUIC заметно отличаются. 0-RTT не проверял.

Всё это воспроизводится: конфиг Caddy на десять строк, шелл-скрипт и tc netem.

❯ Итог

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

Выигрыш оказался узким и адресным. HTTP/2 окупается ровно в одном сценарии — много мелких файлов при заметной задержке — и там действительно даёт вдвое. Во всех остальных он либо не меняет ничего, либо слегка проигрывает старому доброму HTTP/1.1 с его шестью соединениями. HTTP/3 добавляет сэкономленный круг на рукопожатии и более предсказуемое поведение под потерями, но под равномерными потерями сам оказывается вдвое медленнее, а при 5% в половине прогонов вставал намертво.

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

Отсюда единственный вывод, который я готов защищать: правдоподобный результат опаснее странного. Странный вы пойдёте проверять сами, а «19,4 секунды вместо 40» выглядят совершенно нормально — и проходят в публикацию, если не пересчитать прогоны из чистого занудства.

❯ Ссылки

Может быть интересно:
Перейти ↩

Перейти

Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud — в нашем Telegram‑канале 

View the original on Хабр

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.