Сетевые очереди и TCP-дисциплины в Linux

8 октября 2023

  • В данном случае мы публикуем очень старую заметку, которую написали несколько лет назад, но по ряду причин решили тогда не выкладывать.
  • Несмотря на устаревание в частностях, некие общие сведения и приёмы в ней остались неизменными, поэтому надеемся, что даже сейчас она окажется полезной как минимум новичкам в сетевом Linux-администрировании.
  • Мы не собираемся приводить её в актуальный вид, потому что по мере роста в нашем коллективе произошло неизбежное разделение таланта — технические писатели уже не разбираются в сетях достаточно глубоко, а сетевые инженеры больше не занимаются приведением накопленных наработок в популярную форму. Nuff said.

Очень кратко про дисциплины сетевых очередей:

  • Queue disciplines (далее qdisc) — это часть ядра, через которую исходящие сетевые пакеты проходят непосредственно перед передачей в драйвер сетевой карты для отправки во внешний мир.
  • Задача дициплин — переупорядочивать, задерживать или блокировать пакеты таким образом, чтобы никакие сетевые соединения не работали слишком медленно из-за того, что пропускная полоса потребляется другими соединениями (или слишком быстро, если речь идёт о шлюзе интернет-провайдера, отвечающем за ограничение скоростей до тарифных).
  • Ядро Linux содержит несколько qdiscs и позволяет самостоятельно выбирать наиболее подходящую.
  • Ключевой характеристикой qdisc является классовость или бесклассовость:
    • бесклассовая принимает все решения самостоятельно,
    • классовая предназначена для вызова классов — т.е. других дисциплин, выбираемых ею по настраиваемым критериям.
  • Корневая дисциплина (root qdisc) назначается на сетевой интерфейс.
  • Иерархию из дисциплин называют деревом, а бесклассовые дисциплины в дереве называют листьями (leaf).
  • Также дисциплины разделяют на предназначенные для исходящего (egress) и входящего (ingress) трафика:
    • здесь и далее речь идёт только об исходящем!
    • ingress-дисциплин значительно меньше, и их возможности минимальны, т.к. они обрабатывают пакеты, уже поступившие в ядро, т.е. никак не могут повлиять на скорость и порядок их поступления в ядро.

Как управлять дисциплинами:

  • Назначаем дисциплину по умолчанию для новых интерфейсов (для существующих не меняется):
    sysctl net.core.default_qdisc=...
  • Назначаем или меняем дисциплину для существующего интерфейса:
    QDISC=sfq
    tc qdisc add dev eth0 root $QDISC
    tc qdisc replace dev eth0 root $QDISC
  • Полный список в вашем ядре (не считая pfifo):
    ls -1 /lib/modules/`uname -r`/kernel/net/sched/sch_*.ko
  • Смотрим корневые дисциплины интерфейсов:
    ip link list
  • Смотрим полные деревья дисциплин:
    tc qdisc show

Наиболее распространённые дисциплины:

  • pfifo_fast:
    • Долгое время являлась в ядре Linux дисциплиной по умолчанию.
    • Ничего не делает, поэтому создаёт минимальную нагрузку на систему.
    • Прекрасно работает, пока каналы быстрые, или соединений мало, исключительно по TCP, и все вопросы с задержками внутри них решает TCP congestion control (о котором речь ниже).
    • Практически не работает, как только одно из этих условий нарушается (т.е. в современных реалиях — практически всегда).
  • sfq, fq и fq_codel:
    • Бесклассовые дисциплины, которые поровну (fair = честный) делят пропускную способность между всеми соединениями.
    • SFQ является среди них самой старой и известной, FQ_CoDel во многих современных дистрибутивах назначается по умолчанию.
  • mq:
    • Данная дисциплина автоматически назначается сетевым картам с несколькими аппаратными очередями.
    • mq является классовой, количество вызываемых из неё дисциплин (классов) равно количеству аппаратных очередей в сетевой карте.
    • Если qdisc по умолчанию задана через sysctl net.core.default_qdisc, то при инициализации сетевого интерфейса Linux назначит её не интерфейсу в качестве корневой, а классам mq в качестве листьев.
    • Назначить mq сетевым картам, не имеющим поддержки аппаратных очередей, невозможно:
      # ethtool -i enp6s0 | grep ^driver:
      driver: e1000e
      # tc qdisc add dev enp6s0 root mq
      RTNETLINK answers: Operation not supported
    • Можно вручную заменить mq на любую другую, но делать этого не следует — многопоточная обработка превратится в однопоточную и деградация производительности в этом случае обеспечена.

TCP congestion control:

  • Протокол TCP управляет на стороне отправителя (а) размером отправляемых пакетов и (б) паузами между отправкой пакетов.
  • Делается это с целью повысить скорость доставки данных и при этом уменьшить нагрузку на сеть.
  • Задача осложняется тем, что разные участки маршрута между отправителем и получателем не только имеют разную пропускную способность, но и меняют её непосредственно во время передачи.
  • Для решения этой задачи разработано некоторое количество алгоритмов.
  • Выбрать алгоритм в Linux'e можно командой "sysctl net.ipv4.tcp_congestion_control=название"
  • Большинство алгоритмов собрано в виде модулей и нужный модуль должен быть предварительно загружен.
  • Список загруженных алгоритмов:
    • sysctl net.ipv4.tcp_available_congestion_control — доступные для выбора через setsockopt() из процессов суперпользователя,
    • sysctl net.ipv4.tcp_allowed_congestion_control — доступные для выбора через setsockopt() из непривилегированных процессов (подмножество предыдущего списка),
    • net.ipv4.tcp_congestion_control — назначаемый новым соединениям по умолчанию (один из первого списка).
  • Полный список модулей с алгоритмами:
    ls -l /lib/modules/`uname -r`/kernel/net/ipv4/tcp_*.ko

Что мы меняли?

  • Дисциплины сетевых очередей: pfifo, sfq, fq, fq_codel.
  • Алгоритмы управления TCP-соединениями: Reno, Cubic, Hybla, BBR.
  • Скорость физического соединения у клиента-получателя: медленную (100 mbps) и быструю (40 gbps, как у сервера-отправителя).

Наша задача заключалась в том, чтобы найти оптимальное сочетание qdisc и tcp_congestion_control, обеспечивающее:

  • максимальную скорость
  • минимальную избыточность

Как мы оценивали избыточность?

  • Создавали на сервере большой файл, добавляли в файрволл правило-счётчик "iptables -I OUTPUT -d ip-адрес-тестового-клиента" и запускали nginx.
  • Скачивали файл на тестовую клиентскую машину.
  • Сравнивали на сервере значение в счётчике файрволла с фактическим размером файла.
  • Кроме тестовой копии Nginx, сервер продолжал обслуживать большое количество соединений в production-режиме. Это позволило оценить эффективность разных qdisc в реальной обстановке.

На быстрых каналах получилось так:

  • Для всех алгоритмов доля ретрансмитов оказалась в пределах 1%.
  • Самую высокую скорость показали BBR + FQ (в нашем случае 56 мегабайт/сек) и CUBIC + FQ (48 мегабайт/сек, т.е. на 1/7 медленнее).
  • Замена дисциплины с FQ на pfifo вызвала падение скорости для всех алгоритмов: пару мегабайт для BBR и Reno, 20% для CUBIC и Hybla.

Полная таблица результатов для быстрого канала (без SFQ и FQ_Codel):

TCP congestion control Queue discipline Speed, megs/sec
bbr pfifo 54
bbr fq 56
reno pfifo 38
reno fq 35
cubic pfifo 38
cubic fq 48
hybla pfifo 19
hybla fq 25

На медленном канале (у сервера линк 40 гигабит, у клиента 100 мегабит):

  • BBR показал 8-10% ретрансмитов с fq и 30-40% с pfifo, sfq и fq_codel.
  • Cubic, Reno и Hybla показали менее 2% ретрансмитов со всеми qdisc.
  • Скорость передачи данных во всех случаях составила 100 mbps, т.е. ограничивалась только пропускной способностью клиентского канала.

Выводы про BBR:

  • BBR добивается немного более высокой скорости за счёт флуда (до 35%), в этом он отчасти схож со знаменитым uTP.
  • Предполагаемая цель флуда — слать данные с запасом в расчёте на то, что на медленных участках пути вдруг вырастет пропускная способность.
  • Большая доля ретрансмитов не приводит к уменьшению скорости. Наоборот, даже при 35% ретрансмитов скорость по сравнению со всеми другими алгоритмами оказывается выше хотя бы на доли процента.

Выводы про оптимальный вариант:

  • Используйте BBR + FQ и получайте максимальную скорость, если у вас широкий канал или бесплатный трафик, при котором 10% дополнительной полосы не играют роли.
  • В остальных случаях используйте CUBIC + FQ как оптимальный компромисс.


← Назад в Блог

Подпишитесь на новые статьи: