Материал подготовлен в рамках курса «Сетевой инженер. Продвинутый уровень».
Привет, Хабр!
Знакомая картина: канал гигабитный, загружен наполовину, а стоит начать качать большой файл — и пинг под нагрузкой подскакивает с пяти миллисекунд до пятисот, видеозвонок рассыпается, игра лагает. Пропускной способности вагон, потерь нет, а задержка взлетает. И первое, что приходит в голову — «мало буфера, надо больше», — ровно неправильное.
Потому что причина в обратном. Задержка растёт именно из‑за того, что буфера слишком много. Это bufferbloat — раздутые буферы, которые копят пакеты вместо того, чтобы вовремя дать понять отправителю «притормози». Явление контринтуитивное: инженеры десятилетиями увеличивали буферы, считая, что больше памяти под пакеты — всегда лучше, и получили сеть, где под нагрузкой всё тонет в задержке.
Разберём, почему так вышло, как это диагностировать, и что реально помогает — от алгоритмов активного управления очередью до нового L4S, ради которого операторы уже гоняют полевые испытания.
Откуда берётся задержка при свободном канале
Начнём с механики, потому что без неё «слишком много буфера» звучит как бессмыслица.
TCP устроен так, что разгоняется, пока не упрётся. Он наращивает скорость отправки, пока не увидит сигнал перегрузки, а классический сигнал перегрузки — потеря пакета. Пока пакеты доходят, TCP считает, что можно быстрее, и ускоряется.
Теперь представьте на пути большой буфер. TCP разгоняется, пакеты начинают приходить быстрее, чем узкое место успевает их отправлять, и лишние складываются в буфер. Буфер большой, поэтому он долго не переполняется — он просто накапливает пакеты. Ни один не теряется, сигнала перегрузки нет, и TCP продолжает разгоняться, набивая буфер всё сильнее.
И вот результат: буфер забит тысячами пакетов, каждый новый пакет — ваш пинг, ваш DNS‑запрос, ваш игровой трафик — встаёт в конец этой очереди и ждёт, пока перед ним разгребут всё остальное. Отсюда и полсекунды задержки на свободном по пропускной способности канале. Пакеты не теряются, они стоят в очереди.
Большой буфер, задуманный как защита от потерь, превратился в накопитель задержки. Чем он больше, тем дольше пакеты в нём маринуются, прежде чем TCP наконец получит сигнал притормозить, — а сигнал этот приходит только когда буфер переполнится целиком.
Почему увеличение буфера делает только хуже
Отсюда сразу видна ловушка, в которую попадали производители оборудования. Память дешевела, буферы наращивали, борясь с потерями пакетов, — и каждое увеличение буфера удлиняло очередь, в которой пакеты ждут.
Получилась вилка.
Маленький буфер теряет пакеты на всплесках, но держит низкую задержку.
Большой буфер не теряет пакеты, но копит огромную задержку под нагрузкой.
Долго считалось, что потеря пакетов — зло, а задержка — терпимо, и выбирали большой буфер. Для интерактивного трафика — звонки, игры, любой запрос‑ответ — это ровно неправильный выбор: им задержка критична, а редкая потеря переживаема.
Ключевое, что тут нужно понять: сама по себе величина буфера — не решение ни в одну, ни в другую сторону. Проблема не в размере, а в том, что буфер — тупой. Он либо копит всё подряд, либо теряет всё подряд при переполнении, и у него нет способа сказать отправителю «притормози» раньше, чем станет поздно. Решение не в подборе размера, а в том, чтобы научить очередь управлять собой активно.
Активное управление очередью: CoDel
Идея активного управления очередью (AQM) в том, чтобы не ждать переполнения буфера, а вмешиваться раньше — начинать сигналить о перегрузке, пока очередь ещё не разрослась.
Первые попытки — вроде RED — управляли по длине очереди: очередь выросла до порога, начинаем ронять пакеты. Проблема в том, что длину очереди в пакетах трудно перевести в задержку, настройка требовала ручного подбора под конкретный канал, и работало это капризно.
Прорыв случился, когда стали управлять не длиной очереди, а временем, которое пакет в ней проводит. Это алгоритм CoDel, и его логика элегантна. Он смотрит на минимальную задержку в очереди за скользящее окно. Если пакеты стабильно проводят в очереди дольше целевого значения — обычно порядка пяти миллисекунд — CoDel начинает ронять отдельные пакеты, давая TCP сигнал притормозить. Если задержка ниже цели — не трогает ничего.
Гениальность в том, что CoDel отличает хорошую очередь от плохой. Короткий всплеск трафика создаёт временную очередь, которая тут же рассасывается, — это нормально, её CoDel не трогает. А вот постоянная, стоячая очередь, которая держится и держится, — это bufferbloat, и по ней CoDel бьёт. Он реагирует на устойчивую задержку, а не на всплески, и потому почти не требует настройки: целевые пять миллисекунд работают на канале любой скорости, потому что это время, а не размер.
FQ‑CoDel: очередь на каждый поток
Следующий шаг — совместить CoDel с честным разделением по потокам. Это FQ‑CoDel, и на сегодня это фактический стандарт для домашних роутеров и не только.
Идея простая и мощная. Вместо одной общей очереди трафик раскладывается по множеству очередей — грубо говоря, по одной на поток, — и они обслуживаются по очереди честно, круговым перебором. К каждой применяется CoDel отдельно.
Что это даёт, видно на классическом сценарии. Идёт большая закачка, которая набивает свою очередь, и параллельно — ваш видеозвонок с редкими маленькими пакетами. В общей очереди пакеты звонка встали бы в хвост за закачкой и ждали. В FQ‑CoDel у звонка своя очередь, короткая, и она обслуживается наравне с очередью закачки — маленький поток не страдает от тяжёлого. Тяжёлая закачка сама себе копит задержку в своей очереди, а интерактивный трафик её не разделяет.
Это решает главную боль bufferbloat для смешанного трафика: тяжёлые потоки перестают топить лёгкие. Ценой того, что нужно классифицировать пакеты по потокам и держать много очередей, — это требует памяти и просмотра заголовков, что не везде доступно, но на домашнем и корпоративном оборудовании давно норма.
Для каналов с асимметрией и NAT есть родственник — CAKE, который поверх FQ‑CoDel добавляет ещё и шейпинг полосы, справедливость по хостам, учёт DiffServ и фильтрацию ACK. Для домашнего шлюза, стоящего перед провайдерским каналом, это обычно и есть готовый ответ.
Где именно копится очередь: не только буфер устройства
Прежде чем идти дальше, стоит уточнить, что очередь, дающая задержку, живёт не в одном месте, и это влияет на диагностику.
Самый очевидный источник — буфер интерфейса на устройстве, где быстрый трафик упирается в медленный канал. Но есть и менее заметные. У беспроводных сетей своя большая беда: Wi‑Fi агрегирует кадры и имеет собственные очереди на уровне радио, и bufferbloat там возникает даже при формально свободном эфире, потому что радиодоступ делится между устройствами и кадры ждут своего окна передачи. Для Wi‑Fi придумали отдельную дисциплину — учёт эфирного времени, airtime fairness, которая делит не полосу, а время в эфире, иначе одно медленное устройство на краю зоны утаскивает непропорционально много эфира и создаёт очередь для всех.
Ещё одно место — драйвер и сетевой стек самой операционной системы. Между приложением и проводом пакет проходит через несколько внутренних очередей, и они тоже умеют раздуваться. В Linux с этим борются механизмы вроде BQL, которые ограничивают, сколько данных стек отдаёт в очередь драйвера, не давая ей копить лишнее. Это тот же bufferbloat, только внутри хоста, а не на канале.
Практический смысл: когда меряете задержку под нагрузкой и она большая, узкое место может быть не там, где вы думаете. Оно на самом медленном участке пути — но этим участком бывает и радио, и внутренняя очередь хоста, а не только стык с провайдером. Поэтому диагностику ведут от точки, где скорость падает, а не от первого попавшегося устройства.
ECN: сигналить о перегрузке, не роняя пакеты
Пока всё лечение перегрузки строилось на том, чтобы ронять пакеты — это единственный сигнал, который понимает классический TCP. Но ронять пакет расточительно: его придётся переслать, а это ещё задержка. Хочется сказать «притормози», не выбрасывая пакет.
Ровно это делает ECN — explicit congestion notification. Вместо того чтобы дропнуть пакет, устройство ставит в его заголовке метку «я испытываю перегрузку», и пакет доходит до получателя. Получатель видит метку и сообщает отправителю, тот снижает скорость. Пакет не потерян, пересылать нечего, а сигнал перегрузки доставлен.
Работает это только когда все на пути умеют ECN — и отправитель с получателем договорились его использовать, и промежуточные устройства ставят метку вместо дропа. Если кто‑то в цепочке ECN не понимает, всё откатывается к обычным потерям.
Но у классического ECN есть ограничение, которое долго мешало ему раскрыться. Метка перегрузки в нём означает ровно то же, что потеря пакета, — «снизь скорость вдвое». Это грубый сигнал: TCP резко сбрасывает скорость, потом снова разгоняется, снова получает сигнал, снова сбрасывает — пилообразный график, при котором очередь то раздувается, то опустошается. ECN избавил от потерь, но не от колебаний задержки.
L4S: сверхнизкая задержка через частые тонкие сигналы
Здесь появляется самое интересное из свежего — L4S, ради которого операторы уже проводят полевые испытания, а производители встраивают поддержку в оборудование.
Идея L4S — переопределить смысл метки ECN, сделав сигнал перегрузки частым и тонким вместо редкого и грубого. Вместо «переполнилось, режь вдвое» устройство начинает помечать пакеты, едва очередь чуть подросла — при задержке в доли миллисекунды. А отправитель на такой сигнал реагирует не резким сбросом, а маленькой плавной подстройкой скорости.
Результат: вместо пилы, где задержка скачет от нуля до сотен миллисекунд, получается почти ровная линия с задержкой в очереди меньше миллисекунды. Отправитель постоянно получает частые лёгкие сигналы и постоянно чуть корректирует темп, держа очередь почти пустой, но канал при этом загруженным. Сверхнизкая задержка и полная утилизация одновременно — то, что раньше считалось взаимоисключающим.
Корни этого — в дата‑центрах. Там уже давно работал DCTCP, который использовал ровно такую логику частых тонких ECN‑сигналов, но только в замкнутой среде, потому что он несовместим с классическим TCP: агрессивный масштабируемый контроль в общей очереди задавил бы обычные потоки. L4S — это способ вынести эту дата‑центровую технику в общий интернет.
Почему L4S нельзя просто включить
И вот тут главная сложность, из‑за которой L4S — не «включил и работает».
Проблема в сосуществовании. Масштабируемый трафик L4S и классический трафик нельзя держать в одной очереди. L4S реагирует на сигналы тонко и потому наращивает скорость агрессивнее — в общей очереди он вытеснил бы классические потоки, загнав их в мизерную долю канала. Их сигналы перегрузки означают разное, а очередь одна.
Решается это разделением.
Один подход — Dual‑Queue Coupled AQM: две отдельные очереди на узком месте, одна для L4S, другая для классического трафика, а поле ECN служит признаком, по какому пакету в какую. Очередь L4S держат мелкой и метят пакеты рано, при задержке в доли миллисекунды, — потому она и даёт сверхнизкую задержку. Классическая очередь живёт по‑старому. Специальная связка между очередями следит, чтобы обе получали справедливую долю канала.
Второй подход — те же честные очереди на поток, что в FQ‑CoDel, только умеющие L4S: раз у каждого потока своя очередь, конфликт семантик снимается сам собой, потоки просто изолированы. Минус тот же — нужен просмотр заголовков и память под очереди, что не везде есть.
И над всем этим висит требование, чтобы поддержка была по всему пути. Отправитель должен использовать масштабируемый контроль перегрузки — вроде TCP Prague, — получатель должен уметь отражать сигналы, а узкое место на пути должно иметь L4S‑совместимый AQM. Выпадет любое звено — и L4S либо не заработает, либо, хуже, L4S‑трафик задавит соседей, потому что попадёт в классическую очередь со своим агрессивным поведением. Именно поэтому это раскатывают осторожно и через полевые испытания, а не флажком в конфиге.
Incast: bufferbloat наоборот, в дата‑центре
Стоит отдельно сказать про дата‑центры, потому что там очереди ведут себя иначе, и наивное «поставим AQM» не всегда работает.
Классический сценарий дата‑центра — incast. Один узел запрашивает данные сразу у множества серверов — так устроены распределённые хранилища, MapReduce, шардированные базы. Все серверы отвечают почти одновременно, и их ответы сходятся в одну точку — на порт коммутатора перед запрашивающим узлом. Десятки потоков утыкаются в один буфер разом.
Здесь проблема противоположна домашней. Дома буфер большой и копит задержку. В дата‑центре буферы коммутаторов маленькие и быстрые, а трафик приходит синхронным залпом — буфер мгновенно переполняется, пакеты теряются пачками, TCP на потерях резко тормозит, и пропускная способность обрушивается, хотя канал быстрый. Задержки тут микросекундные, и классический TCP с его реакцией на потерю в такой среде ведёт себя слишком грубо.
Именно под это и придумали DCTCP — тот самый предок L4S. В замкнутой среде дата‑центра, где всё оборудование ваше и настроено единообразно, можно включить частые тонкие ECN‑сигналы и настроить контроль перегрузки так, чтобы он реагировал плавно и держал буферы почти пустыми. Это снимает и потери от incast, и микровсплески задержки. Работает именно потому, что среда контролируемая — все узлы говорят на одном языке контроля перегрузки, чего в общем интернете нет и ради чего пришлось изобретать L4S с разделением очередей.
Практический вывод для тех, кто строит дата‑центр: bufferbloat и incast — разные болезни, лечатся по‑разному. Домашний bufferbloat — это большой буфер, который надо научить активно управлять очередью. Incast — это маленький буфер и синхронный залп, где помогает DCTCP с тонкими ECN‑сигналами и иногда осознанное увеличение буфера ровно под размер залпа. Перепутать их и применить домашний рецепт к дата‑центру — значит сделать хуже.
Как это диагностировать у себя
Теперь практика — как понять, что у вас именно bufferbloat, а не что‑то другое.
Главный тест — измерять задержку под нагрузкой, а не в покое.
Пинг на простое ничего не покажет — он будет прекрасным.
Нужно нагрузить канал закачкой или заливкой на полную и одновременно мерить пинг.
Если в покое пять миллисекунд, а под нагрузкой сотни — это bufferbloat, классический симптом.
Специальные тесты вроде тех, что меряют задержку под нагрузкой отдельно на закачке и на отдаче, дают ту же картину наглядно и сразу говорят, в какую сторону канала раздут буфер. Часто оказывается, что проблема на отдаче — исходящий буфер на стороне абонента или в первом же устройстве провайдера.
Важно понимать, где узкое место, потому что лечить bufferbloat можно только там, где очередь реально образуется. Обычно это самая медленная точка пути — стык вашего канала с провайдером. Ставить AQM в середине быстрой сети бессмысленно, там очередь не копится. Он нужен ровно на узком месте, где быстрый трафик упирается в медленный канал и начинает складываться в буфер.
Проверить, насколько уверенно вы разбираетесь в таких задачах, можно, пройдя вступительный тест по сетевым технологиям. Он поможет оценить текущий уровень и увидеть темы, которые стоит повторить.
BBR: другой подход со стороны отправителя
Всё, о чём шла речь, — управление очередью на узком месте, то есть в сети. Но есть и второй фронт — сам отправитель, и там за последние годы случилось своё изменение, о котором полезно знать.
Классический TCP разгоняется, пока не увидит потерю, и именно поэтому набивает буфер: он специально доводит очередь до переполнения, чтобы получить сигнал. BBR заходит с другой стороны — он пытается оценить реальную пропускную способность канала и его задержку напрямую, и держать темп отправки ровно на уровне, при котором канал загружен, но очередь не растёт. То есть он старается не наполнять буфер вообще, а не ждать его переполнения.
Это делает BBR устойчивее к bufferbloat со стороны отправителя: даже если на пути раздутый буфер без всякого AQM, BBR старается его не набивать, потому что ориентируется не на потери, а на измеренные задержку и полосу. Для отдачи с сервера — раздача контента, стриминг — это заметно помогает там, где управлять очередью в сети вы не можете, а отправителя контролируете.
Полной заменой AQM это не служит. BBR помогает, когда трафик исходит от вас и вы поставили этот контроль перегрузки; но чужие потоки на классическом TCP по‑прежнему набьют общий буфер, и от них BBR ваши интерактивные пакеты не защитит. Плюс у BBR своя история сосуществования с классическим TCP, которую дорабатывали не одну версию. Так что BBR и AQM — не альтернативы, а два конца одной проблемы: один управляет очередью в сети, другой старается её не создавать со стороны источника. В идеале работают оба.
Что со всем этим делать на практике
Если у вас домашний или офисный шлюз перед провайдерским каналом — самый прямой способ убрать bufferbloat — включить на нём FQ‑CoDel или CAKE и задать шейпинг чуть ниже реальной скорости канала. Смысл шейпинга в том, чтобы узкое место переехало с неуправляемого буфера провайдера на ваш управляемый AQM: если вы отдаёте чуть медленнее, чем канал физически может, очередь копится у вас, где ей есть кому управлять, а не в раздутом буфере на той стороне.
Если вы оператор или строите сеть — AQM ставится на границах, где сходятся разные скорости, и особенно на абонентских стыках. А дальше вопрос про L4S: он даёт качественно лучший результат для интерактива, но требует поддержки по всему пути и аккуратного развёртывания с разделением очередей, иначе можно навредить соседнему трафику. Полевые испытания у операторов идут как раз для того, чтобы отладить это до массового включения.
И общий принцип, который стоит унести: задержка под нагрузкой — это отдельная характеристика канала, не менее важная, чем пропускная способность, и она не видна, пока не начнёшь мерить под нагрузкой. Гигабитный канал с раздутым буфером для видеозвонка хуже, чем стомегабитный с нормальным AQM. Пропускную способность продают и меряют все, а задержку под нагрузкой — почти никто, и именно она определяет, будет ли сеть комфортной для всего интерактивного.

Как и в случае с bufferbloat, привычный механизм может формально работать, но уже не решать задачу сети. На бесплатном демо-уроке 24 августа в 20:00 разберем альтернативные способы защиты от петель и критерии выбора для разных топологий. Это поможет проектировать L2-сегменты так, чтобы локальный сбой не превращался в аварию всей сети. Присоединяйтесь.
Больше бесплатных уроков смотрите в дайджесте.

