[Перевод] Какую нагрузку на серверы создают сетевые механизмы?
Когда анализируют работу сетевой подсистемы серверов, внимание обычно обращают на такие показатели, как время задержки (latency), пропускная способность системы (throughput), количество пакетов, которое можно обработать за секунду (PPS, Packets Per Second). Эти показатели применяют для того чтобы понять то, под какой максимальной нагрузкой сможет работать исследуемый компьютер. И, хотя эти метрики важны и часто способны многое сказать о системе, они не дают сведений о том, какое воздействие обработка сетевых пакетов оказывает на программы, выполняющиеся на сервере.
Этот материал направлен на исследование нагрузки, создаваемой сетевыми механизмами на серверы. В частности, речь пойдёт о том, сколько процессорного времени решение сетевых задач может «украсть» у различных процессов, выполняющихся в Linux-системах.
Обработка сетевых пакетов в Linux
Linux обрабатывает значительное количество пакетов в контексте любого процесса, выполняемого процессором в момент обработки соответствующего IRQ. Механизм System Accounting назначит используемые для этого такты процессора любому процессу, выполняемому в соответствующий момент. Это будет сделано даже в том случае, если этот процесс не имеет никакого отношения к обработке сетевых пакетов. Например, команда top
может указать на то, что процесс, по видимому, использует более 99% ресурсов процессора, но на самом деле 60% процессорного времени будет уходить на обработку пакетов. А это значит, что сам процесс, решая собственные задачи, использует лишь 40% ресурсов CPU.
Обработчик входящих пакетов net_rx_action
обычно выполняется очень и очень быстро. Например, менее чем за 25 мкс. (Эти данные получены по результатам измерений с использованием eBPF. Если вам интересны подробности — поищите здесь net_rx_action
.) Обработчик за один раз, до откладывания задачи на другой цикл SoftIRQ, может обработать до 64 пакетов на экземпляр NAPI (NIC или RPS). Друг за другом, без перерыва, могут следовать до 10 циклов SoftIRQ, на что уходит около 2 мс (подробности об этом можно узнать, почитав о __do_softirq
). Если у вектора SoftIRQ, после того, как прошло максимальное количество циклов, или вышло время, есть ещё нерешённые задачи, то решение этих задач откладывается для выполнения в потоке ksoftirqd
конкретного CPU. Когда это происходит, система оказывается немного прозрачнее в смысле получения сведений о нагрузке на процессор, создаваемой сетевыми операциями (хотя такой анализ выполняют исходя из предположения о том, что исследуются именно SoftIRQ, имеющие отношение к обработке пакетов, а не к чему-то другому).
Один из способов получения вышеописанных показателей заключается в использовании perf
:
sudo perf record -a \
-e irq:irq_handler_entry,irq:irq_handler_exit
-e irq:softirq_entry --filter="vec == 3" \
-e irq:softirq_exit --filter="vec == 3" \
-e napi:napi_poll \
-- sleep 1
sudo perf script
Вот что может получиться в результате:
swapper 0 [005] 176146.491879: irq:irq_handler_entry: irq=152 name=mlx5_comp2@pci:0000:d8:00.0
swapper 0 [005] 176146.491880: irq:irq_handler_exit: irq=152 ret=handled
swapper 0 [005] 176146.491880: irq:softirq_entry: vec=3 [action=NET_RX]
swapper 0 [005] 176146.491942: napi:napi_poll: napi poll on napi struct 0xffff9d3d53863e88 for device eth0 work 64 budget 64
swapper 0 [005] 176146.491943: irq:softirq_exit: vec=3 [action=NET_RX]
swapper 0 [005] 176146.491943: irq:softirq_entry: vec=3 [action=NET_RX]
swapper 0 [005] 176146.491971: napi:napi_poll: napi poll on napi struct 0xffff9d3d53863e88 for device eth0 work 27 budget 64
swapper 0 [005] 176146.491971: irq:softirq_exit: vec=3 [action=NET_RX]
swapper 0 [005] 176146.492200: irq:irq_handler_entry: irq=152 name=mlx5_comp2@pci:0000:d8:00.0
В данном случае процессор бездействует (отсюда и появление записей swapper
для процесса), IRQ вызывается для очереди Rx на CPU 5, обработка SoftIRQ вызывается дважды, сначала обрабатываются 64 пакета, потом — 27. Следующий IRQ вызывается через 229 мкс и снова запускает цикл.
Эти данные получены на простаивающей системе. Но на процессоре может выполняться любая задача. В таком случае вышеприведённая последовательность событий происходит, прерывая эту задачу и выполняя задачи IRQ/SoftIRQ. При этом System Accounting приписывает прерванному процессу нагрузку, создаваемую на процессор. В результате задачи по обработке сетевых пакетов обычно скрыты от обычных средств мониторинга нагрузки на процессор. Они выполняются в контексте некоего случайным образом выбранного процесса, в контексте «процесса-жертвы». Это приводит нас к некоторым вопросам. Как оценить время, на которое выполнение процесса прерывается ради обработки пакетов? Как сравнить 2 различных сетевых решения для того чтобы понять, какое из них оказывает меньшее влияние на различные задачи, решаемые на компьютере?
При использовании механизмов RSS, RPS, RFS обработка пакетов обычно распределена между ядрами процессора. Поэтому вышеописанная последовательность обработки пакетов имеет отношение к каждому конкретному CPU. По мере увеличения скорости поступления пакетов (полагаю, тут можно говорить о скоростях 100000 пакетов в секунду и выше), каждому CPU приходится обрабатывать тысячи или десятки тысяч пакетов в секунду. Обработка такого количества пакетов неизбежно окажет влияние на другие задачи, выполняемые на сервере.
Рассмотрим один из способов оценки этого влияния.
Отключение распределённой обработки пакетов
Для начала давайте остановим распределённую обработку пакетов, отключив RPS и настроив правила управления потоком, направленные на то, чтобы организовать обработку всех пакетов, имеющих отношение к конкретному MAC-адресу, на единственном, известном нам CPU. В моей системе имеются 2 NIC, агрегированных в конфигурации 802.3ad. Решение сетевых задач возложено на единственную виртуальную машину, выполняемую на компьютере.
RPS на сетевых адаптерах отключается так:
for d in eth0 eth1; do
find /sys/class/net/${d}/queues -name rps_cpus |
while read f; do
echo 0 | sudo tee ${f}
done
done
Далее, настраиваем правила управления потоком, направленные на то, чтобы пакеты попадали бы в тестовую виртуальную машину, использующую единственный CPU:
DMAC=12:34:de:ad:ca:fe
sudo ethtool -N eth0 flow-type ether dst ${DMAC} action 2
sudo ethtool -N eth1 flow-type ether dst ${DMAC} action 2
Отключение RPS и использование правил управления потоком позволяет обеспечить то, что все пакеты, предназначенные для нашей виртуальной машины, обрабатываются на одном и том же CPU. Для того чтобы удостовериться в том, что пакеты направляются в ту очередь, в которую должны направляться, можно воспользоваться командой наподобие ethq. Потом можно выяснить то, к какому CPU относится эта очередь, воспользовавшись /proc/interrupts
. В моём случае очередь 2 обрабатывается средствами CPU 5.
Команда openssl speed
Я мог бы, для анализа времени выполнения SoftIRQ, ответственных за обработку входящего трафика, воспользоваться утилитами perf
или bpf
, но такой подход достаточно сложен. К тому же, сам процесс наблюдения, определённо, влияет на результаты. Гораздо более простым и понятным решением является выявление нагрузки, создаваемой на систему сетевыми операциями, с использованием какой-нибудь задачи, такой, которая создаёт на систему заранее известную нагрузку. Например, это команда openssl speed
, используемая для тестирования производительности OpenSSL. Это позволит узнать о том, какой объём процессорных ресурсов достаётся программе в реальности, и сравнить его с тем объёмом ресурсов, который, как предполагается, она должна получить (это поможет узнать о том, какой объём ресурсов уходит на решение сетевых задач).
Команда openssl speed
практически на 100% является командой пространства пользователя. Если привязать её к некоему CPU, то она, во время выполнения тестов, использует все его доступные ресурсы. Команда работает, устанавливая таймер на заданный интервал (здесь, например, чтобы было легче выполнять расчёты, используется 10 секунд), запуская тест, а затем, при срабатывании таймера, используя times
()
для того чтобы узнать о том, сколько процессорного времени в реальности досталось программе. С точки зрения syscall
это выглядит так:
alarm(10) = 0
times({tms_utime=0, tms_stime=0, tms_cutime=0, tms_cstime=0}) = 1726601344
--- SIGALRM {si_signo=SIGALRM, si_code=SI_KERNEL} ---
rt_sigaction(SIGALRM, ...) = 0
rt_sigreturn({mask=[]}) = 2782545353
times({tms_utime=1000, tms_stime=0, tms_cutime=0, tms_cstime=0}) = 1726602344
То есть, оказывается, что между вызовом alarm()
и проверкой результатов было сделано очень мало системных вызовов. Если программу не прерывали, или прерывали очень редко, время tms_utime
будет совпадать с временем теста (в данном случае это 10 секунд).
Так как это — тест, выполняемый исключительно в пользовательском пространстве, любое системное время, появившееся в times()
, будет означать некую дополнительную нагрузку на систему. Получится, что хотя openssl
— это процесс, который выполняется на CPU, сам CPU может быть при этом занят чем-то ещё. Например — обработкой сетевых пакетов:
alarm(10) = 0
times({tms_utime=0, tms_stime=0, tms_cutime=0, tms_cstime=0}) = 1726617896
--- SIGALRM {si_signo=SIGALRM, si_code=SI_KERNEL} ---
rt_sigaction(SIGALRM, ...) = 0
rt_sigreturn({mask=[]}) = 4079301579
times({tms_utime=178, tms_stime=571, tms_cutime=0, tms_cstime=0}) = 1726618896
Тут видно, что openssl
удалось поработать на процессоре 7.49 секунд (178 + 571 в единицах измерения, соответствующих 0.01 с.). Но при этом 5.71 с. этого интервала представлено системным временем. Так как openssl
не занят никакими делами в пространстве ядра, это значит, что 5.71 с. — это результат некоей дополнительной нагрузки на систему. То есть — это время, которое у процесса «украли» ради обеспечения нужд системы.
Использование команды openssl speed для выявления нагрузки на систему, создаваемой сетевыми механизмами
Теперь, когда мы разобрались с тем, как работает команда openssl speed
, посмотрим на результаты, которые она выдаёт на практически бездействующем сервере:
$ taskset -c 5 openssl speed -seconds 10 aes-256-cbc >/dev/null
Doing aes-256 cbc for 10s on 16 size blocks: 66675623 aes-256 cbc's in 9.99s
Doing aes-256 cbc for 10s on 64 size blocks: 18096647 aes-256 cbc's in 10.00s
Doing aes-256 cbc for 10s on 256 size blocks: 4607752 aes-256 cbc's in 10.00s
Doing aes-256 cbc for 10s on 1024 size blocks: 1162429 aes-256 cbc's in 10.00s
Doing aes-256 cbc for 10s on 8192 size blocks: 145251 aes-256 cbc's in 10.00s
Doing aes-256 cbc for 10s on 16384 size blocks: 72831 aes-256 cbc's in 10.00s
Как видно, нам сообщают о том, что на обработку блоков разных размеров программа тратит от 9.99 до 10 секунд. Это подтверждает то, что системные механизмы не отнимают процессорное время у программы. Теперь давайте, используя netperf
, нагрузим сервер обработкой пакетов, поступающих из двух источников. Выполним тест снова:
$ taskset -c 5 openssl speed -seconds 10 aes-256-cbc >/dev/null
Doing aes-256 cbc for 10s on 16 size blocks: 12061658 aes-256 cbc's in 1.96s
Doing aes-256 cbc for 10s on 64 size blocks: 3457491 aes-256 cbc's in 2.10s
Doing aes-256 cbc for 10s on 256 size blocks: 893939 aes-256 cbc's in 2.01s
Doing aes-256 cbc for 10s on 1024 size blocks: 201756 aes-256 cbc's in 1.86s
Doing aes-256 cbc for 10s on 8192 size blocks: 25117 aes-256 cbc's in 1.78s
Doing aes-256 cbc for 10s on 16384 size blocks: 13859 aes-256 cbc's in 1.89s
Результаты разительно отличаются от тех, что были получены на простаивающем сервере. Ожидается, что каждый из тестов будет выполняться в течение 10 секунд, но times()
сообщает о том, что реальное время выполнения составляет от 1.78 до 2.1 секунд. А это значит, что оставшееся время, варьирующееся в диапазоне от 7.9 до 8.22 секунд, было потрачено на обработку пакетов, выполняемую либо в контексте процесса openssl
, либо в ksoftirqd
.
Взглянем на то, что выдаст команда top
при анализе выполненного только что запуска openssl speed
.
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND P
8180 libvirt+ 20 0 33.269g 1.649g 1.565g S 279.9 0.9 18:57.81 qemu-system-x86 75
8374 root 20 0 0 0 0 R 99.4 0.0 2:57.97 vhost-8180 89
1684 dahern 20 0 17112 4400 3892 R 73.6 0.0 0:09.91 openssl 5
38 root 20 0 0 0 0 R 26.2 0.0 0:31.86 ksoftirqd/5 5
Тут можно подумать, что openssl
использует примерно 73% ресурсов CPU 5, а ksoftirqd
достаются оставшиеся ресурсы. А в реальности в контексте openssl
выполняется обработка такого большого количества пакетов, что самой программе на решение её задач достаётся лишь 18-21% процессорного времени.
Если снизить сетевую нагрузку до 1 потока, то возникнет такое ощущение, что openssl
достаётся 99% системных ресурсов.
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND P
8180 libvirt+ 20 0 33.269g 1.722g 1.637g S 325.1 0.9 166:38.12 qemu-system-x86 29
44218 dahern 20 0 17112 4488 3996 R 99.2 0.0 0:28.55 openssl 5
8374 root 20 0 0 0 0 R 64.7 0.0 60:40.50 vhost-8180 55
38 root 20 0 0 0 0 S 1.0 0.0 4:51.98 ksoftirqd/5 5
Но в реальности оказывается, что программе, работающей в пользовательском пространстве, достаётся, из ожидаемых 10 секунд, всего примерно 4 секунды:
Doing aes-256 cbc for 10s on 16 size blocks: 26596388 aes-256 cbc's in 4.01s
Doing aes-256 cbc for 10s on 64 size blocks: 7137481 aes-256 cbc's in 4.14s
Doing aes-256 cbc for 10s on 256 size blocks: 1844565 aes-256 cbc's in 4.31s
Doing aes-256 cbc for 10s on 1024 size blocks: 472687 aes-256 cbc's in 4.28s
Doing aes-256 cbc for 10s on 8192 size blocks: 59001 aes-256 cbc's in 4.46s
Doing aes-256 cbc for 10s on 16384 size blocks: 28569 aes-256 cbc's in 4.16s
Обычные средства мониторинга процессов указывают на то, что программа пользуется практически всеми ресурсами процессора, но в реальности оказывается, что 55-80% ресурсов CPU уходит на обработку сетевых пакетов. Пропускная способность системы при этом выглядит великолепно (более 22 Гб/с на 25 Гб/с-линии), но это оказывает колоссальное воздействие на процессы, работающие в этой системе.
Итоги
Здесь мы рассмотрели пример того, как механизмы обработки пакетов «воруют» такты процессора у простого и не особенно важного бенчмарка. Но на реальном сервере процессы, на которые оказывается подобное воздействие, могут быть чем угодно. Это могут быть виртуальные процессоры, потоки эмуляторов, потоки vhost виртуальных машин. Это могут быть разные системные процессы, воздействие на которые способно оказывать различное влияние на производительность этих процессов и всей системы.
А вы учитываете, анализируя свои серверы, влияние на их реальную производительность нагрузки, связанной с выполнением сетевых операций?