Ошибка в quiche у Cloudflare проявлялась не где-нибудь на краю стенда, а в критическом сетевом коде, через который проходит заметная часть реального трафика компании. При 30% случайных потерь пакетов в первые две секунды соединения алгоритм CUBIC в Rust-реализации QUIC мог так и не восстановиться, а для разработчиков это хороший повод еще раз проверить, как их транспорт ведет себя не в идеальной сети, а в той, что обычно достается продакшену.
О проблеме сообщает InfoQ со ссылкой на рассказ инженеров Cloudflare Эстебана Карисимо и Антонио Висенте. Сбой всплыл не в пользовательских жалобах и не в красивом постмортеме задним числом, а в пайплайне интеграционных тестов ingress-прокси: часть тестов неожиданно падала, если проверялась работа CUBIC в сценарии с сильными потерями пакетов на самом старте соединения. Общий знаменатель у этих падений был один: алгоритм управления перегрузкой после шумной фазы не возвращался к нормальному росту окна перегрузки.
Чтобы отделить случайный шум от системной ошибки, команда собрала локальный воспроизводимый стенд. На localhost запускались HTTP/3-клиент и сервер на quiche, контроллером перегрузки был CUBIC, RTT выставили в 10 мс. Клиент скачивал файл объемом 10 МБ, а в первые две секунды в канал искусственно вносили 30% случайной потери пакетов. В норме такой тест должен был завершаться примерно за 4-5 секунд, так что таймаут в 10 секунд выглядел более чем щедрым. На практике выяснилось, что примерно 60% из серий по 100 запусков не укладывались даже в этот лимит. Для транспортного кода это уже не «редкий крайний кейс», а вполне воспроизводимая поломка поведения.
Дальше началась та часть работы, которую разработчики обычно любят меньше красивых архитектурных схем, но именно она спасает релизы: инструментирование и разбор внутреннего состояния. Cloudflare добавила подробную телеметрию и увидела, что после завершения фазы потерь окно перегрузки не растет как должно. Вместо восстановления CUBIC метался между двумя состояниями: congestion avoidance и recovery. Причем метался почти с механической регулярностью: 999 переходов примерно за 6,7 секунды, то есть по одному переходу примерно каждые 14 мс. Это подозрительно близко к настроенному RTT в 10 мс, а такие совпадения в низкоуровневой сетевой логике редко бывают невинными.
Чтобы не списать все слишком рано на QUIC, Rust или особенности тестового окружения, инженеры заменили CUBIC на Reno, другой loss-based алгоритм контроля перегрузки. С Reno тот же симуляционный тест проходил в 100% случаев. Это сузило зону поиска до конкретной реализации CUBIC в quiche. И здесь история становится особенно полезной для тех, кто пишет сетевые сервисы: проблема оказалась не в «большой концепции» и не в дефекте самого QUIC, а в тонкости того, как считается idle time. Иными словами, код делал вроде бы понятную оптимизацию, но в связке с noisy slow start и минимальным congestion window в два пакета эта оптимизация превращалась в ловушку.
Механика бага выглядела так: когда входящие ACK-пакеты в шумной стартовой фазе доводили объем in-flight bytes до нуля, логика расчета простоя заставляла реализацию входить в бесконечный recovery loop. Дальше получался почти комичный по форме, но болезненный по эффекту замкнутый круг. Приложение оставалось в состоянии recovery, момент окончания recovery уезжал все дальше в будущее, а окно перегрузки фактически блокировалось и не могло снова расти. Для бизнеса это значит довольно приземленную вещь: соединение не падает красиво и сразу, а может вязнуть в деградированном состоянии, где пользователь просто получает медленный или нестабильный сервис, а команда потом долго спорит, у кого именно проблема — у сети, приложения или инфраструктуры.
Отдельно здесь интересен контекст. Команда Cloudflare связывает происхождение дефекта с изменением в ядре Linux, которое изначально вносили для исправления реальной TCP-проблемы. Это типичная история для сетевого стека: паттерн, который разумно работает в одном протоколе или в одной фазе соединения, при переносе в соседний стек может дать очень неприятный побочный эффект. QUIC давно живет не как лабораторная игрушка, а как продакшен-транспорт для HTTP/3 и высоконагруженных сервисов, поэтому такие баги уже нельзя считать экзотикой уровня «интересно почитать на выходных». Для русскоязычных команд, которые строят CDN, edge-сервисы, видеостриминг, игровые бэкенды или просто активно гоняют мобильный трафик через QUIC, вывод неприятный, но полезный: проверка happy path вообще ничего не гарантирует, если старт соединения проходит через потерю пакетов, джиттер и агрессивные ретраи.
Решение при этом оказалось почти издевательски простым на фоне объема расследования. Вместо того чтобы измерять idle time только от момента отправки последних данных, Cloudflare стала учитывать и момент получения последнего ACK. Этого почти однострочного исправления хватило, чтобы разорвать цикл, вернуть нормальное восстановление окна перегрузки и снова получить 100% успешных прогонов теста. В статье на Reddit, которую цитирует InfoQ, один из комментаторов отметил, что сталкивался с пугающе похожими симптомами в таймер-зависимых high-frequency workload, где переходы C-state добавляли непредсказуемые задержки. Параллель не идеальная, но мысль здравая: когда проблема выглядит как «что-то иногда лагает под нагрузкой», искать стоит не только в приложении, но и в стыке таймингов, сетевой логики и системных оптимизаций.
Ошибка в quiche здесь важна не только как частный баг Cloudflare, а как напоминание о цене маленьких допущений в инфраструктурном коде. Чем шире компании переходят на QUIC и HTTP/3, тем чаще выигрыш в задержках и устойчивости будет зависеть не от громких анонсов, а от таких почти невидимых деталей: как считается idle time, когда именно меняется состояние recovery и что происходит, если сеть на первых секундах соединения ведет себя не по презентации, а по жизни.