Тот самый график, ради которого всё затевалось. Зелёный — HTTP/3, и он не там, где я ожидал его увидеть. Источник: собственные замеры, стенд Caddy 2.8.4 + Chromium 141
Тот самый график, ради которого всё затевалось. Зелёный — HTTP/3, и он не там, где я ожидал его увидеть. Источник: собственные замеры, стенд Caddy 2.8.4 + Chromium 141

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

Внятного ответа я не нашёл. Измерения в интернете противоречат друг другу: одни показывают выигрыш HTTP/3 везде и всегда, другие — что разницы нет, а на потерях выгода не подтверждается вовсе. Так что я решил померить сам.

Собрал стенд: сервер с HTTP/1.1, HTTP/2 и HTTP/3 одновременно, эмулятор канала на уровне IP-пакетов и настоящий Chromium в роли клиента. Прогнал шесть профилей сети — от датацентра до края соты с пятью процентами потерь — на трёх типах нагрузки. Получилось 54 серии замеров.

Короткий ответ: переход с HTTP/1.1 на HTTP/2 дал двукратный выигрыш на странице с полусотней файлов и ровно ноль на скачивании одного большого. HTTP/3 на этом стенде выиграл 12% на десяти файлах при большом RTT — и проиграл HTTP/2 втрое на пятидесяти. А на трёх мегабайтах с однопроцентными потерями отстал в девять раз.

Дальше — как я это мерил, почему получилось именно так, и в каком месте я сам чуть не опубликовал ерунду.

❯ Почему нельзя просто взять и померить

Первый подход был лобовым: curl --http1.1 / --http2 в цикле, сравнить time_total, взять среднее. Такой замер собирается за пять минут, и на нём построена половина сравнений в интернете. Он меряет установку соединения плюс передачу одного объекта по петлевому интерфейсу — то есть величину, которая с временем загрузки страницы связана слабо. Отсюда и расхождение выводов: при RTT около нуля разница между протоколами лежит внутри погрешности, и знак результата определяется тем, какой прогон попал в выборку.

Проблем три.

Клиент должен быть браузером. Различие не в удобстве, а в модели загрузки.

На HTTP/1.1 браузер держит до шести TCP-соединений на источник и раскладывает по ним очередь запросов; curl по умолчанию работает в одно соединение и последовательно. Уже здесь замеры расходятся в разы, причём в пользу curl — он не показывает того параллелизма, ради которого HTTP/1.1 вообще живёт.

Дальше — приоритизация. Браузер строит дерево зависимостей: блокирующий рендер CSS идёт раньше картинок ниже сгиба, preload scanner вытаскивает ссылки из HTML до того, как парсер до них дошёл. В HTTP/2 это выражается в дереве приоритетов, в HTTP/3 — в схеме Extensible Priorities. curl не приоритизирует ничего, потому что ему нечего приоритизировать: у него нет DOM, нет критического пути рендеринга и нет понятия «страница отрисовалась».

И техническое ограничение: HTTP/3 в стандартной сборке curl недоступен — нужна сборка с ngtcp2, quiche или msh3. На большинстве машин curl --http3 просто вернёт ошибку.

Отсюда выбор клиента: Chromium под управлением Playwright, а метрика — loadEventEnd из Navigation Timing, то есть момент, когда страница действительно загрузилась.

Сеть должна быть плохой. Вся разница между протоколами измеряется в сэкономленных раундтрипах, а на петлевом интерфейсе раундтрип почти ничего не стоит.

Считаем, что вообще экономится. Установка соединения по TCP с TLS 1.3 — это один раунд на SYN/SYN-ACK плюс один на ClientHello/ServerHello, итого два до первого байта запроса. QUIC совмещает транспортное и криптографическое рукопожатие и укладывается в один; при возобновлении сессии с 0-RTT данные уходят вместе с первым пакетом. Экономия — от одного до двух RTT на соединение.

Теперь подставим числа. На lo время оборота порядка 0,05 мс, значит вся экономия составляет около 0,1 мс — она тонет в разбросе между прогонами. При RTT 150 мс та же экономия превращается в 150–300 мс, то есть в заметную долю загрузки. Плюс на петлевом интерфейсе MTU равен 65536 байт против типичных 1500: сегментация, а вместе с ней и вся механика окна перегрузки ведут себя не так, как в реальной сети.

Поэтому «у меня на ноутбуке HTTP/3 быстрее» — не результат. Это замер величины, которая на локальной машине физически не может проявиться.

Потери должны быть настоящими. Здесь и оказалась главная сложность.

Прокси на сокетах не годится принципиально. Он терминирует соединение: со стороны клиента одно TCP-соединение, со стороны сервера другое, между ними — копирование байтов в пространстве пользователя. Отбросить в такой схеме «пакет» нельзя — можно только не переслать кусок потока, а это для TCP не потеря, а разрыв.

Важнее другое: блокировка головы очереди в HTTP/2 происходит не в HTTP/2. Она происходит в ядре, на уровне TCP. Потерянный сегмент останавливает выдачу в сокет всех последующих сегментов, которые уже пришли и лежат в буфере приёма, — потому что TCP обязан отдать приложению непрерывный поток. Мультиплексированные поверх него стримы встают все разом, хотя данные девяти из десяти уже физически на машине. Прокси, работающий с сокетом, этого не воспроизведёт: он видит поток уже после того, как ядро его собрало.

QUIC устроен иначе: у каждого потока своя нумерация, и потерянный пакет блокирует только те потоки, чьи данные в нём ехали. Остальные продолжают доставляться.

Ровно эту разницу и надо было измерить — а значит, работать пришлось на уровне IP-пакетов, до того как ядро соберёт из них поток.

Так что стенд получился злее, чем я рассчитывал.

❯ Стенд

Сервер — Caddy 2.8.4, он отдаёт все три протокола на одном порту сразу. Клиент — Chromium 141 под управлением Playwright, протокол форсируется флагами запуска.

const EXPECT = { h1: ['http/1.1'], h2: ['h2'], h3: ['h3', 'h3-29'] };
// ...
const seen = [...new Set([d.navProto, ...Object.keys(d.protos)])].filter(Boolean);
const ok = seen.length > 0 && seen.every((p) => EXPECT[proto].includes(p));

Каждый ресурс отдаёт свой nextHopProtocol через Resource Timing API. Если браузер молча съехал на другой протокол — а он это делает охотно, — прогон помечается негодным и не попадает в статистику. Без этой проверки очень легко получить сравнение HTTP/2 с HTTP/2 под видом сравнения с HTTP/3.

Эмулятор канала пришлось писать самому. В контейнере не оказалось tc, а sch_netem не вкомпилирован в ядро — модулей там нет вообще. Зато нашёлся /dev/net/tun, и это открывало дорогу к настоящим IP-пакетам.

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

if dst == b_vsrv:
    # клиент → сервер: прячем клиента за VIRT_CLI, целимся в реальный адрес
    to_srv.push(rewrite(pkt, VIRT_CLI, REAL))
elif dst == b_vcli:
    # сервер → клиент: возвращаем адреса на место
    to_cli.push(rewrite(pkt, VIRT_SRV, REAL))

Задержка и потери применяются к каждому пакету в обе стороны одинаково — TCP и QUIC оказываются в равных условиях. Именно этого не даёт прокси на сокетах.

Схема стенда: пакет уходит на адрес, которого ни у кого нет, попадает в TUN, проходит через эмулятор и возвращается в ядро уже с другими адресами. Источник: схема автора
Схема стенда: пакет уходит на адрес, которого ни у кого нет, попадает в TUN, проходит через эмулятор и возвращается в ядро уже с другими адресами. Источник: схема автора

Шесть профилей сети, от честного датацентра до сотового края:

Профиль

RTT

Джиттер

Потери

Датацентр

0 мс

0

0%

Проводной Интернет

40 мс

5 мс

0%

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

150 мс

10 мс

0%

Мобильный

60 мс

15 мс

1%

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

60 мс

20 мс

3%

Край соты

100 мс

25 мс

5%

Три сценария нагрузки: страница с 10 файлами (80 КБ), страница с 50 файлами (421 КБ) и скачивание одного файла на 3 МБ.

❯ Как стенд едва не наврал

Первая версия эмулятора спала по паре миллисекунд и выпускала накопившиеся пакеты пачками. Логично и просто. И — неверно.

Я это заметил не сразу, а когда полез считать байты. На одной и той же странице HTTP/3 прогонял через провод вдвое больше данных, чем HTTP/2: 908 КБ против 448 КБ при полезной нагрузке в 421 КБ. Столько ретрансмитов на канале без потерь взяться было неоткуда.

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

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

gap = due - time.perf_counter()
if gap > 0.0004:
    time.sleep(gap - 0.0003)
while time.perf_counter() < due:
    pass

Стало точнее. А разрыв между протоколами почти не изменился — и вот это уже было интересно, потому что означало: дело не в стенде.

Мораль, которую я вынес: если вы меряете сеть, сначала померьте свой измеритель. Я поймал это только потому, что считал байты, а не только секунды.

❯ HTTP/1.1 → HTTP/2: выигрыш есть, и он большой

50 файлов, 421 КБ: оранжевый столбик вдвое ниже синего почти везде — это и есть выигрыш от HTTP/2. Медиана из 5 прогонов. Источник: собственные замеры
50 файлов, 421 КБ: оранжевый столбик вдвое ниже синего почти везде — это и есть выигрыш от HTTP/2. Медиана из 5 прогонов. Источник: собственные замеры

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

Профиль

HTTP/1.1

HTTP/2

Выигрыш

Датацентр

199 мс

161 мс

19%

Проводной

589 мс

321 мс

45%

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

2018 мс

1034 мс

49%

Мобильный

1027 мс

541 мс

47%

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

1349 мс

679 мс

50%

Край соты

2289 мс

1209 мс

47%

Двукратная разница на всех профилях, кроме локального. Причина известна: HTTP/1.1 открывает шесть соединений на домен и гоняет по ним запросы по очереди. Пятьдесят файлов в шесть очередей — это девять последовательных раундтрипов только на ожидание. При RTT 150 мс это полторы секунды чистого простоя.

HTTP/2 мультиплексирует всё в одно соединение: запросы уходят разом, ответы возвращаются вперемешку. Пятьдесят файлов стоят почти столько же, сколько один.

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

❯ Когда HTTP/2 не даёт ничего

Один файл 3 МБ: шкала логарифмическая, иначе столбик HTTP/3 на плохой сети раздавил бы весь остальной график. Медиана из 3 прогонов. Источник: собственные замеры
Один файл 3 МБ: шкала логарифмическая, иначе столбик HTTP/3 на плохой сети раздавил бы весь остальной график. Медиана из 3 прогонов. Источник: собственные замеры

Теперь тот же тест, но вместо пятидесяти файлов — один на 3 МБ:

Профиль

HTTP/1.1

HTTP/2

Разница

Проводной

378 мс

378 мс

0%

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

1296 мс

1272 мс

2%

Мобильный

841 мс

820 мс

2%

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

1059 мс

1161 мс

-10%

Ноль. На плохом мобильном HTTP/2 даже чуть медленнее — в пределах разброса, но выигрыша нет точно.

Это не дефект, это арифметика. Мультиплексирование ускоряет параллельные запросы. Когда запрос один, мультиплексировать нечего: и там и там — одно TCP-соединение, один TLS, один поток байтов. HTTP/2 добавляет к нему обрамление кадрами и накладные расходы, и ровно ничего не ускоряет.

Отсюда практическое следствие, которое противоречит привычному «оптимизируем всё»:

  • API, отдающий один JSON на запрос, от HTTP/2 не выиграет ничего. Выиграет от переиспользования соединений, но это и в HTTP/1.1 работает через keep-alive.

  • Раздача видео, архивов, крупных бандлов — тот же ноль.

  • Страница с десятками мелких ресурсов — вот тут выигрыш вдвое.

И зеркальное следствие: шардинг по доменам, который в эпоху HTTP/1.1 был обязательной оптимизацией, при HTTP/2 работает против вас. Каждый лишний домен — это отдельное TCP-соединение, отдельное TLS-рукопожатие и потерянное мультиплексирование. Если у вас в проекте до сих пор живёт static1.example.com и static2.example.com — их пора схлопнуть.

❯ HTTP/3: где обещанное сбывается

10 файлов, 80 КБ: здесь HTTP/3 отыгрывает своё — чем дальше сервер, тем заметнее. Медиана из 5 прогонов. Источник: собственные замеры
10 файлов, 80 КБ: здесь HTTP/3 отыгрывает своё — чем дальше сервер, тем заметнее. Медиана из 5 прогонов. Источник: собственные замеры

Теория говорит: QUIC даёт рукопожатие за один раунд вместо трёх, а потеря пакета в одном потоке не тормозит остальные. Значит, выигрыш должен расти с задержкой и с потерями.

На странице из десяти файлов первая половина обещания сбывается ровно:

Профиль

HTTP/1.1

HTTP/2

Разница

Датацентр

54 мс

70 мс

+30 (хуже)

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

192 мс

168 мс

-12%

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

643 мс

562 мс

-13%

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

294 мс

296 мс

+1%

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

330 мс

339 мс

+2%

Чем дальше сервер, тем лучше HTTP/3: минус 12–13% на дистанции. Это честный выигрыш, и он ровно там, где обещан.

А вот в датацентре HTTP/3 проигрывает на треть. Это тоже логично: при нулевом RTT экономить нечего, а QUIC живёт в пространстве пользователя и платит за это процессором. TCP обрабатывается ядром с аппаратными разгрузками, которых у UDP нет. Исследование на десятигигабитных каналах даёт ту же картину в цифрах: TCP с TLS выдаёт 8010 Мбит/с, лучшие реализации QUIC — около 3000 Мбит/с.

Если ваши клиенты — это соседние сервисы внутри одного датацентра, HTTP/3 вам не нужен. Он будет медленнее.

❯ Где обещанное не сбылось

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

Теперь неприятная часть. Та же самая разница протоколов, но на странице с пятьюдесятью файлами:

Профиль

HTTP/2

HTTP/3

Разница

Проводной

321 мс

500 мс

+56%

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

1034 мс

1633 мс

+58%

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

541 мс

1145 мс

+112%

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

679 мс

1869 мс

+175%

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

1209 мс

3914 мс

+224%

Те же протоколы, тот же стенд, тот же объём данных. Разница только в числе файлов — и HTTP/3 из победителя превратился в аутсайдера, отстающего втрое.

Решил, что стенд врёт. Полез разбираться и вытащил из Resource Timing разбивку по фазам. Вот она, и она всё объясняет:

Метрика Resource Timing

HTTP/2

HTTP/3

responseStartrequestStart, медиана — время ответа сервера

162 мс

41 мс

requestStartstartTime, медиана — простой в очереди до отправки

17 мс

181 мс

requestStartstartTime, максимум

20 мс

364 мс

Активных запросов в середине окна загрузки

44

29

Последняя строка — конкурентность: сколько ресурсов имели незакрытый интервал [startTime, responseEnd] в момент, делящий окно загрузки пополам. Для HTTP/2 это 44 запроса из 50, то есть почти вся страница качается одновременно. Для HTTP/3 — 29, и остальные ждут своей очереди.

Вторая строка в терминах Chrome DevTools называется Stalled или Queueing: интервал между тем, как загрузчик поставил запрос в очередь, и тем, как первый байт запроса ушёл в сокет. Сетевого времени там нет вообще — это чистое ожидание внутри клиента.

Складываем картину. QUIC отвечает вчетверо быстрее: 41 мс против 162, то есть один раунд вместо четырёх — транспорт отрабатывает ровно так, как обещано в спецификации.

Но до транспорта запросы не доходят. Они простаивают в очереди по 181 мс, в десять раз дольше, чем на HTTP/2, а в хвосте распределения — 364 мс. Выигрыш в один раунд оплачен пятью раундами ожидания перед отправкой. Похоже на исчерпание лимита одновременных потоков: браузер упирается в разрешённое сервером число и ждёт кадра MAX_STREAMS, чтобы открыть следующий.

Проверил на страницах разного размера, чтобы поймать порог:

Файлов на странице

HTTP/2, ожидание отправки

HTTP/3, ожидание отправки

10

8 мс

9 мс

20

13 мс

71 мс

30

16 мс

115 мс

50

16 мс

186 мс

У HTTP/2 очередь не растёт вообще. У HTTP/3 растёт линейно с числом файлов.

Дальше я проверил, дело в объёме данных или в числе потоков: сделал страницу из тех же пятидесяти файлов, но по 900 байт каждый — 45 КБ вместо 421. Задержка осталась на месте, 191 мс. Значит, упирается не в байты, а в количество одновременных потоков.

И главное: этот эффект воспроизвёлся при замере в обход эмулятора, напрямую через ядро. Стенд его усиливает пропорционально RTT — что физически правильно, — но не создаёт.

Итого: на странице с большим числом мелких ресурсов HTTP/3 в связке Chromium с Caddy упирается в лимит одновременных потоков там, где HTTP/2 не упирается. Выигрыш от быстрого транспорта съедается очередью на входе.

❯ Три мегабайта: полный провал

Самое резкое расхождение — на большом файле в плохой сети:

Профиль

HTTP/2

HTTP/3

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

820 мс

7517 мс

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

1161 мс

14923 мс

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

1840 мс

не дошёл за 2 минуты

Разница почти в девять раз, а на пяти процентах потерь HTTP/3 не доехал вовсе.

Я был уверен, что это мой стенд захлебнулся на UDP. Посчитал пакеты — и получил обратное:

h2: 4514 пакетов, 6.4 МБ передано, 1068 мс
h3: 2417 пакетов, 3.3 МБ передано, 7561 мс

HTTP/3 передал ровно объём файла, почти без ретрансмитов. HTTP/2 прогнал вдвое больше байтов — то есть переотправлял потерянное куда активнее — и всё равно закончил в семь раз быстрее.

Значит, дело не в потерях как таковых. 3.3 МБ за 7.5 секунды при RTT 60 мс — это окно перегрузки размером примерно в 26 КБ, которое так и не выросло. Ядерный TCP на тех же потерях разгоняется и держит скорость, а реализация QUIC зажимает окно и не восстанавливается.

Здесь важна оговорка, и я на ней настаиваю: это поведение конкретной библиотеки — quic-go, на которой работает Caddy. У Cloudflare своя реализация, у Google своя, у nginx третья. Мой результат говорит «QUIC-стек в этом сервере плохо разгоняется на потерях», а не «QUIC плохой».

❯ Почему в интернете такой разброс данных

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

Академическая работа, где прогнали тысячи сайтов, даёт осторожную картину: в обычных условиях разницы нет, примерно половина сайтов быстрее на HTTP/3, половина на HTTP/2. При добавленной задержке в 200 мс быстрее на HTTP/3 уже 81% сайтов. А про потери там прямая формулировка: выигрыш HTTP/3 в сценариях с высокими потерями не подтверждается.

Замеры Request Metrics дают противоположное: HTTP/3 быстрее во всех точках, и чем дальше сервер, тем сильнее — до 1200 мс выигрыша при загрузке из Лондона.

Оба измерения, скорее всего, корректны. Просто они меряли разные вещи: разный сервер, разные реализации QUIC, разное число ресурсов на странице. Мои данные говорят, что число ресурсов и тип нагрузки меняют знак результата — не величину, а именно знак.

Вывод, которым я закончу техническую часть, такой: вопрос «что быстрее, HTTP/2 или HTTP/3» не имеет ответа в отрыве от вашей нагрузки и вашего сервера. Любая статья, которая отвечает на него одним числом, включая эту, описывает свой стенд, а не вашу систему.

❯ Что с этим делать на практике

Включите HTTP/2, если ещё нет. Это главный выигрыш: до двух раз на странице с множеством ресурсов. Всё остальное в статье — борьба за проценты поверх него.

Уберите шардинг по доменам. При HTTP/2 он вредит: лишние соединения и рукопожатия вместо мультиплексирования.

Не ждите чуда от HTTP/2 на одиночных больших ответах. API с одним JSON, отдача видео, скачивание архивов — там выигрыша нет и не будет.

HTTP/3 включайте, если ваши пользователи далеко. Минус 12–13% на дистанции — это реальные цифры, и они стоят одной строчки в конфиге. Если вся аудитория в соседнем датацентре, не включайте: будет медленнее.

Померьте сами, если у вас много мелких ресурсов. Именно тут мой стенд показал, что HTTP/3 может проиграть втрое. Ваша связка сервера и клиента может вести себя иначе — но это надо проверить, а не предположить.

Не оставляйте HTTP/3 без присмотра на раздаче крупных файлов. Проверьте на плохой сети, прежде чем катить.

Сначала посмотрите, где вы реально теряете время. Если у вас 3 МБ несжатого JavaScript и картинки без ленивой загрузки, переход на HTTP/3 не спасёт: он экономит десятки миллисекунд там, где вы теряете тысячи.

❯ Выводы

Начинал я с простого вопроса: сколько даёт HTTP/3 на обычной странице. Закончил пониманием, почему на многих страницах он не даёт ничего — и почему это нормально.

Основную выгоду забирает HTTP/2, и забирает он её этажом ниже. Мультиплексирование сводит девять последовательных раундтрипов к одному — вот они, те самые 50%. Всё, что QUIC может добавить сверху, — это сэкономленное рукопожатие: один раунд там, где их было три. При RTT в 20–30 миллисекунд такая экономия тонет в шуме измерений. Она становится заметной там, где раунд стоит дорого: 150 миллисекунд до другого континента превращают её в 13% времени загрузки.

Что удивило по-настоящему — это не отсутствие выигрыша, а его знак. Я ждал, что HTTP/3 будет либо лучше, либо примерно так же. А он на пятидесяти мелких файлах оказался втрое хуже, и не из-за транспорта: транспорт как раз отвечал вчетверо быстрее. Его задушила очередь на открытие потоков — то есть настройка, а не протокол.

И отдельно про самопроверку. Я дважды получал красивый результат, который оказывался артефактом: сначала два экземпляра сервера дрались за порт и отдавали пустые ответы, потом мой собственный эмулятор гнал пакеты пачками и наказывал за это QUIC. Оба раза спасло то, что я не поверил цифрам и полез считать байты и пакеты. Если бы поверил — статья была бы уверенной, красивой и неправильной.

И отдельно про самопроверку. Я дважды получал красивый результат, который оказывался артефактом: сначала два экземпляра сервера дрались за порт и отдавали пустые ответы, потом мой собственный эмулятор гнал пакеты пачками и наказывал за это QUIC. Оба раза спасло то, что я не поверил цифрам и полез считать байты и пакеты. Если бы поверил — статья была бы уверенной, красивой и неправильной.

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

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

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