Компьютер, отправляющий данные через интернет, не знает о канале почти ничего. Он не знает его пропускной способности, не знает, сколько других машин делят этот же путь, не знает, где стоит самый узкий маршрутизатор на трассе. Сеть не присылает ему справку. И тем не менее отправитель должен решить самый практический вопрос из всех: с какой скоростью слать пакеты прямо сейчас. Ответ, выработанный за десятилетия, выглядит почти дерзко: угадывать. Слать чуть быстрее, пока всё доходит, и резко отступать, когда что-то теряется. Из этой простой петли проб и откатов выросла целая дисциплина, называемая контролем перегрузки. Она определяет поведение практически каждого потока данных на планете, объясняет, почему скорость закачки рисует на графике пилу, почему старые милосердные буферы иногда вредили больше, чем помогали, и почему ни один координатор не нужен, чтобы миллиарды незнакомых машин как-то поделили общие провода. Ниже разбирается эта механика шаг за шагом: от медленного старта до современных алгоритмов, от петли обратной связи до парадоксов, которые мешают сетям работать сносно.

Два окна, определяющие каждый байт

Внутри любого соединения TCP действует правило: отправитель не может держать в пути неподтверждённых данных больше, чем позволяет минимум из двух окон. Первое окно называется окном получателя. Его объявляет принимающая сторона и им она говорит: у меня столько-то свободного места в буфере, больше не присылайте. Это защита памяти клиента, простая и честная.

Второе окно - окно перегрузки. Его никто не объявляет, потому что объявлять некому. Сеть не имеет голоса. Отправитель сам вычисляет это окно, наблюдая за тем, как ведут себя его собственные пакеты: доходят ли подтверждения вовремя, теряются ли сегменты, как меняется задержка. Окно перегрузки - это гипотеза машины о том, сколько сеть способна переварить. Именно оно, а не окно получателя, почти всегда является настоящим тормозом в длинных каналах. Домашний компьютер с гигабайтом свободной памяти может рекламировать огромное приёмное окно, но если где-то между ним и сервером стоит загруженный линк на сто мегабит, потолком станет именно вычисленное из потерь и задержек окно перегрузки. Вся дальнейшая история - история того, как это второе, невидимое окно растёт и сжимается.

Медленный старт, который на самом деле экспонента

Свежесозданное соединение не может сразу гнать данные на полной скорости: оно не имеет ни малейшего представления, что это за скорость. Поэтому TCP начинает робко. Начальное окно перегрузки составляет несколько сегментов - в ранних версиях один, теперь обычно около десяти. Дальше происходит то, что вводит в заблуждение своим названием: медленный старт растёт быстро. На каждое полученное подтверждение окно увеличивается на сегмент, а это значит, что за один круговой обход пути окно примерно удваивается. Числа в пути выглядят так: одна порция, потом две, четыре, восемь, шестнадцать. Геометрическая прогрессия с основанием два, привязанная не к таймеру, а к ритму подтверждений.

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

Заканчивается разгон одним из двух событий. Либо окно достигает порога, выставленного заранее, и алгоритм переходит в режим осторожного линейного роста. Либо случается потеря пакета - и это трактуется как сигнал, что гипотеза оказалась слишком смелой. Тогда порог перезаписывается примерно половиной текущего окна и либо всё начинается заново, либо включается режим быстрого восстановления, сглаживающий провал. Каждая закачка в первые доли секунды проживает именно эту разведку боем.

AIMD как компромисс между жадностью и выживанием

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

Почему именно асимметрия, объясняет простая математика согласования. Представим множество потоков, делящих один узкий линк. Если каждый при избытке прибавляет константу, а при беде делит скорость на две, то те, кто шёл быстрее, уступают больше, а те, кто шёл медленнее, теряют меньше. Каждый цикл понемногу стягивает их скорости друг к другу и вместе - к честной доле канала. Доказано, что другие комбинации так не работают: оба аддитивных правила не дают стабильной точки, мультипликативный рост с аддитивным спадом разгоняет систему. Сочетание роста по константе и спада по пропорции - единственный вариант, в котором распределённые эгоистичные агенты без всякого центра сходятся к справедливому равновесию. Элегантность здесь не украшение, а рабочее свойство: четырёхстрочное правило, исполняемое на каждом хосте независимо, заменяет глобального диспетчера, которого в интернете нет и не будет.

CUBIC и BBR как два разных ответа на старую потерю

Классический закон хорошо служил медленным линкам, но на скоростных трассах с большим произведением полосы на задержку он оказался слишком робким. После каждого разреза вдвое восстановление занимало десятки секунд, и канал большую часть времени простаивал недогруженным. Ответом стал CUBIC: температура кривой окна сменилась с прямой на кубическую функцию. Окно быстро догоняет точку последней потери, замирает возле неё в осторожном плато, а затем, если потери не повторились, переходит к агрессивному исследованию новых высот. Форма выбрана так, чтобы время с предыдущего события, а не размер окна, управляло темпом роста - это даёт честность между потоками разной скорости. Сегодня CUBIC - рабочая лошадка большей части соединений в мире.

Совсем другой ответ дал BBR. Его авторы заметили слабость самой метафоры потери: пакет исчезает не когда канал заполнен, а когда переполнен буфер перед каналом, то есть уже слишком поздно. Вместо ожидания беды BBR непрерывно измеряет две величины: реальную полосу доставки внизу и минимальную круговую задержку в самое спокойное время. Произведение этих чисел называется скорость наполнения трубы. Алгоритм держит в пути ровно столько данных, сколько труба вмещает: работать на краю без очереди, не в очереди. Периодически он чуть поднимает темп, чтобы проверить, не выросла ли полоса, и чуть сбрасывает, чтобы слить накопившуюся очередь. Потеря пакета для BBR перестаёт быть командой - это просто шум. Где классика реагирует на симптом, здесь строится модель канала и отправитель ведёт себя как исследователь с прибором, а не как водитель, тормозящий после удара.

Почему домашний интернет кладёт не злоумышленник

Часто жалуются: стоит кому-то начать закачку, как у всей квартиры замирает видео. Легко подозревать чью-то жадность, но реальный механизм невиннее и интереснее. Вспомним, чем завязан контур: потеря пакета трактуется как приказ отступить, и все потоки, проходящие через общий узкие участок, получают такой приказ примерно одновременно, потому что переполняется у них один и тот же буфер. Все вместе режут окна пополам, линк мгновенно пустеет, затем все вместе начинают снова наращивать скорость, снова упираются в потолок и снова получают общий откат. Канал живёт в колебательном режиме: перегрузка, недогрузка, снова перегрузка. Это не сбой отдельной машины, а фаза синхронизации множества независимых петель обратной связи.

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

Буферблоат как мир, где добрая очередь ломает сигнал

Логика потери-сигнала держится на допущении: пакет исчезает, когда канал забит. Но долгие годы производители устройств милосердно увеличивали буферы - память дешевеет, жаль выбрасывать данные, пусть полежат. Получилась сеть, где пакеты почти не теряются: они просто очень, очень долго ждут своей очереди. Для алгоритма на потерях это наихудший мир. Сигнал отступить не приходит, окно растёт без остановки, очередь раздувается до секунд, интерактивный трафик тонет, а когда потеря наконец случается, ущерб уже катастрофический и её тоже неверно истолковывают.

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

Пила скорости, спидтест и параллельная история UDP

Кто смотрел живой график закачки, видел зубья: скорость карабкается вверх, срывается вниз, снова карабкается. Это и есть отпечаток цикла роста и спада, нанесённый на экран. Пила объясняет и вечный спор про скорость. Спидтест качает параллельными потоками в несколько секунд, суммирует их и ловит пик - он показывает потолок трассы. Обычная одиночная закачка живёт внутри пилы: после каждого зубца она какое-то время недогружает канал, и её средняя скорость всегда ниже пиковой. Интернет один и тот же, различается способ измерения.

Рядом с TCP всегда шла вторая история. UDP сам по себе не контролирует перегрузку вообще: протокол просто швыряет датаграммы, никаких окон, никаких откатов. Это не порок, а пустое место, которое приложение заполняет по своему вкусу. Медиатранспорт подстраивает битрейт, игры посылают мелкие пакеты с фиксированным темпом и терпят потери. Современный QUIC поверх UDP перенёс уроки TCP в пространство пользователя: там есть своё окно перегрузки, свои варианты CUBIC и BBR, и плюс к этому быстрая смена алгоритмов без обновления операционной системы. Деление труда таково: транспорт даёт среду, а разумную реакцию на неё прикладывает тот, кто знает, чего хочет приложение.

Возвращаясь к началу: никакого регулятора не существует. Есть правило прибавлять константу и делить пополам, исполняемое каждой машиной у себя дома, и из миллиардов таких локальных решений самопроизвольно складывается нечто похожее на справедливость. Сеть угадывает свою скорость, и, как ни странно, угадывает хорошо.

То, как была устроена эта конфигурация, определяет ещё один практический приём: несмотря на то, что потеря пакета означает простой спад окна, современные алгоритмы отличают потери канала от потери перегрузки и на подсказках отмечают линию одну. Отсюда же и возникновение BBR: вместо ожидания потери система измеряет доступную полосу и точечную задержку, формируя скорость, соответствующую истинной ёмкости пути. Администратор домой умеет ответить тут простой вещью - понижение буфера на внутренней стороне модема и строгие ограничения пропускной способности для фоновых служб; отчёт ведётся в тех же отметках времени отклика, где раньше жила обычная пропускная единица.

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