На прод-серверах «внезапные» пики softirq, рост ksoftirqd/N и деградация сетевой задержки (CPU jitter) почти всегда упираются в то, как ядро распределяет обработку пакетов между CPU. Особенно заметно на VPN/туннелях, прокси, балансировщиках, busy веб-узлах и везде, где высокий PPS (много маленьких пакетов).
Ниже — практический разбор: как быстро подтвердить, что проблема именно в сети, где искать перекос, и что настраивать в правильном порядке: irqbalance, IRQ affinity, RSS, RPS/RFS/XPS, offload’ы (GRO/GSO/TSO) и параметры NAPI (netdev_budget).
Короткая теория: IRQ, softirq и ksoftirqd
IRQ — аппаратные прерывания: устройство (например, NIC) сигналит CPU «пришли пакеты/есть работа». В Linux обработка IRQ обычно делится на две стадии:
- top-half (жёсткий IRQ): минимальная работа, чтобы быстро «снять событие» и не блокировать прерывания;
- bottom-half: основная обработка уходит в контекст
softirq(для сети —NET_RXиNET_TX).
Если работы слишком много и ядро не успевает отработать softirq «сразу» в текущем контексте, оно переносит догоняющую обработку в поток ksoftirqd/N (на каждом CPU свой). Это не «плохой процесс», а симптом: сеть попадает в режим, где latency обычно хуже и появляется разброс задержек.
Высокий CPU у
ksoftirqdчаще означает перекос: обработка пакетов и прерывания «прилипли» к одному ядру или к узкой группе ядер.
Как выглядит проблема: симптомы и быстрые маркеры
Типичные признаки:
- в
top/htopрастётksoftirqdна одном или нескольких CPU; - в
mpstatувеличивается%soft(иногда вместе с%sys); - p50 «нормальный», но p99/p999 заметно хуже (jitter);
- одно ядро перегружено (hot CPU), остальные простаивают;
- под нагрузкой растут drops/overruns по драйверу NIC или в сетевом стеке.
Полезная привычка: прежде чем тюнить sysctl, убедитесь, что у вас нет явного перекоса IRQ/очередей. Иначе вы будете «лечить следствие».

Быстрая диагностика: подтверждаем сетевой softirq и находим перекос
1) Смотрим распределение softirq по CPU
cat /proc/softirqs
Смотрите в первую очередь NET_RX и NET_TX. Если один CPU сильно впереди остальных — это почти всегда источник jitter.
2) Смотрим динамику загрузки по CPU
mpstat -P ALL 1
Ищите CPU, где стабильно высокие %soft и/или %sys. Дальше обычно вы идёте в сторону IRQ affinity, RSS и очередей.
3) Находим «кто генерирует IRQ»
cat /proc/interrupts
Это основной файл для темы. Для сетевых устройств строки обычно содержат имя интерфейса/очереди/драйвера: eth0, ens3, rx-0, TxRx, mlx5_comp и т.п.
Что искать в выводе:
- одна IRQ-строка «убегает» в один CPU;
- у NIC много MSI-X векторов, но активны 1–2 (часто RSS выключен или не даёт нужной параллельности);
- IRQ сидит на том же CPU, где работает ваше приложение (конкуренция за CPU time и кэш).
4) Проверяем дропы и статистику драйвера
ip -s link show dev eth0
ethtool -S eth0
Набор счётчиков зависит от драйвера, но полезно отслеживать рост rx_dropped, rx_missed_errors, rx_no_buffer, а также пер-очередную статистику (если есть).
irqbalance: когда включать, а когда лучше руками
irqbalance пытается распределять IRQ по CPU автоматически. На «типовой» машине это часто быстро лечит ситуацию «всё на CPU0».
Нюансы, из-за которых иногда лучше ручной режим:
- если вы делаете pinning/изоляцию CPU под приложение, автоматическая миграция IRQ может ухудшить предсказуемость;
- irqbalance может периодически перекладывать IRQ, а это иногда даёт jitter;
- в виртуализации поведение зависит от виртуального NIC и политики хоста.
Практический алгоритм:
- Если видите явный перекос — проверьте, что
irqbalanceзапущен и не отключён политиками. - Если после включения/перезапуска стало ровнее — фиксируйте результат и переходите к RSS/очередям.
- Если требования по стабильной latency жёсткие — обычно выигрывает ручная фиксация IRQ affinity и настройка очередей.
Если вы параллельно настраиваете ограничение ресурсов для сервисов (чтобы соседние процессы не «отъедали» CPU во время пиков softirq), посмотрите материал про лимиты CPU и памяти в systemd.
RSS, очереди NIC и MSI-X: база, без которой RPS/RFS не дадут максимум
RSS (Receive Side Scaling) — аппаратное распределение входящих потоков по RX-очередям NIC на основе хэша (обычно 5-tuple). Каждая очередь обычно соответствует MSI-X IRQ вектору. Если RSS работает правильно, нагрузка по IRQ и NET_RX становится заметно ровнее.
Проверяем количество каналов (очередей)
ethtool -l eth0
Если драйвер позволяет увеличить число очередей под число CPU/vCPU (и под реальную нагрузку), это может снизить давление на ksoftirqd за счёт параллелизма:
ethtool -L eth0 combined 4
Важно: «больше очередей» не всегда лучше. На малом трафике это может поднять overhead, на большом — улучшить p99. Меряйте: PPS, %soft, drops, latency.
Проверяем RSS хэш/таблицу (если драйвер поддерживает)
ethtool -x eth0
Если очередей мало или распределение выглядит странно, вернитесь к каналам (ethtool -L) и дальше проверьте IRQ affinity по векторным IRQ.
RPS/RFS/XPS: когда железо не распределяет как надо (или вы в VM)
Если RSS недоступен, не помогает или вы хотите лучше «приклеить» обработку к CPU приложения, используйте программные механизмы:
- RPS (Receive Packet Steering) — распределяет обработку RX по CPU на уровне ядра;
- RFS (Receive Flow Steering) — старается направить пакет на CPU, где выполняется поток, читающий сокет (лучше кэш-локальность);
- XPS (Transmit Packet Steering) — распределяет TX по CPU.
Все настройки находятся в /sys/class/net/eth0/queues/ (очереди rx-N и tx-N).
Включаем RPS на RX-очередях
Для каждой RX-очереди задаётся маска CPU в rps_cpus. Пример для CPU0–CPU3 (hex-маска f):
for f in /sys/class/net/eth0/queues/rx-*/rps_cpus; do echo f > "$f"; done
На системах с большим числом CPU маска будет длиннее. В качестве стартовой точки часто работает «разрешить все CPU», но для минимального jitter обычно лучше выделить CPU-набор под сеть и не смешивать с тяжёлым user-space.
Включаем RFS (таблица потоков)
Чтобы RFS работал, нужны записи flow table: на очередях rps_flow_cnt и глобальный лимит net.core.rps_sock_flow_entries.
sysctl -w net.core.rps_sock_flow_entries=32768
for f in /sys/class/net/eth0/queues/rx-*/rps_flow_cnt; do echo 2048 > "$f"; done
Размеры зависят от числа очередей и активных соединений. Слишком маленькие значения не дадут эффекта, слишком большие добавят память и overhead.
Включаем XPS для TX
for f in /sys/class/net/eth0/queues/tx-*/xps_cpus; do echo f > "$f"; done
XPS чаще заметен при высоком исходящем трафике и нескольких TX-очередях: помогает снизить конкуренцию в одном CPU и сделать задержку ровнее.
IRQ affinity вручную: когда нужно «прибить гвоздями»
Если вы видите в /proc/interrupts, что конкретные IRQ сидят на одном CPU, можно задать affinity вручную через /proc/irq/IRQNUM/smp_affinity_list (удобный list-формат) или smp_affinity (hex-маска).
Схема такая: находите номер IRQ для нужной очереди NIC (первый столбец в /proc/interrupts), затем задаёте список CPU:
cat /proc/irq/123/smp_affinity_list
echo 1-3 > /proc/irq/123/smp_affinity_list
Если вы используете CPU isolation/pinning под приложение, не отдавайте IRQ на изолированные CPU: это почти всегда ухудшает предсказуемость latency.
Если вы подбираете конфигурацию виртуалки под PPS-нагрузку (прокси, VPN, балансировщик) — пригодится шпаргалка по выбору тарифа: как подобрать VDS по CPU и RAM.

ethtool и offload’ы: GRO/GSO/TSO и полезные проверки
Смотрим текущие offload-настройки
ethtool -k eth0
Ключевые механизмы, которые часто снижают PPS-нагрузку на сетевой стек (и, как следствие, давление на softirq и ksoftirqd):
- GRO — склейка входящих пакетов, меньше обработок на уровне ядра;
- GSO — отложенная сегментация на отправке;
- TSO — сегментация TCP на стороне драйвера/железа.
Для большинства веб-нагрузок и типовых сервисов включённые offload’ы помогают. Но есть сценарии, где они могут мешать (специфические туннели, фильтры, отдельные требования к latency). Поэтому правильный путь — менять по одному параметру и мерить p99/p999, drops и %soft.
Пример (меняйте осознанно и тестируйте):
ethtool -K eth0 gro on gso on tso on
NAPI и netdev_budget: когда softirq «не успевает выгрести»
NAPI снижает число IRQ под нагрузкой и переводит обработку в poll-режим. Это полезно для throughput, но при неудачных бюджетах может добавлять jitter.
Параметры, которые обычно трогают:
net.core.netdev_budget— сколько пакетов можно обработать за один проход;net.core.netdev_budget_usecs— ограничение по времени (мкс) на один проход.
Посмотреть текущие значения:
sysctl net.core.netdev_budget net.core.netdev_budget_usecs
Пример осторожного тюнинга (нет «магических чисел», только стартовая точка):
sysctl -w net.core.netdev_budget=600
sysctl -w net.core.netdev_budget_usecs=8000
Логика: если бюджет слишком мал — backlog не разгребается и работа уходит в ksoftirqd. Если бюджет слишком велик — можно «залипать» в сети и ухудшать latency других задач. Меняйте по одному параметру и сравнивайте метрики.
Пошаговый план (runbook), чтобы не тюнить вслепую
Шаг 1. Зафиксируйте исходные метрики
mpstat -P ALL 1(5–10 минут под реальной нагрузкой);- снимки
/proc/interruptsи/proc/softirqsдо/после; ip -s linkиethtool -S(drops/ошибки);- latency на уровне приложения (p95/p99/p999).
Шаг 2. Уберите явный перекос IRQ
- проверьте
irqbalance; - если нужно — задайте IRQ affinity для MSI-X векторов NIC вручную.
Шаг 3. Приведите в порядок очереди и RSS
- проверьте каналы через
ethtool -l; - при необходимости настройте
ethtool -L; - снова проверьте распределение в
/proc/interrupts.
Шаг 4. Настройте RPS/RFS/XPS (если нужно)
- RPS — чтобы разнести обработку RX по CPU;
- RFS — чтобы приблизить обработку к CPU приложения;
- XPS — чтобы разнести TX и сгладить пики.
Шаг 5. Проверьте offload’ы через ethtool
- если offload’ы выключены — включите и сравните;
- если включены, но jitter высокий — тестируйте по одному параметру.
Шаг 6. Только потом трогайте netdev_budget
Иначе можно «замазать» перекос IRQ/очередей, а проблема останется и вернётся при следующем изменении нагрузки.
Частые ошибки
- «Отключим все offload’ы — будет быстрее». Часто становится хуже: PPS растёт,
softirqиksoftirqdулетают. - Слишком много очередей при маленьком трафике: overhead, иногда хуже стабильность.
- IRQ на изолированных CPU: приложение и сеть начинают конкурировать за одно ядро.
- Тюнить только sysctl без работы с IRQ/RSS/RPS: эффект нестабилен.
Мини-чеклист «после»
- в
/proc/interruptsIRQ NIC распределены по нескольким CPU без явного hot-ядра; - в
/proc/softirqsNET_RX/NET_TXрастут ровнее; mpstat:%softне упирается в один CPU, пики ниже;- drops/ошибки на интерфейсе не растут (или растут заметно медленнее);
- p99/p999 latency улучшаются, jitter уменьшается.
Оговорки для виртуалок
На VM/VDS поведение зависит от виртуального NIC (например, virtio-net) и политики хоста. Но логика диагностики та же: /proc/interrupts, очереди, распределение по CPU, RPS/RFS, offload’ы. Часто помогает увеличить число vCPU (чтобы было куда распределять очереди) и затем правильно разложить IRQ/RPS.
Если вы подбираете площадку под сетевой PPS, обычно разумная отправная точка — VDS, где у вас есть контроль над ядрами и системными настройками.
Итог
ksoftirqd — индикатор того, что сетевой стек не успевает обрабатывать пакеты «в моменте» и догоняет их планово. В большинстве случаев лечится не «убийством процесса», а выравниванием источника нагрузки: IRQ affinity и очередей NIC, включением/настройкой RSS и (при необходимости) RPS/RFS/XPS, плюс аккуратным тюнингом offload’ов через ethtool и параметров NAPI вроде net.core.netdev_budget.


