Диагностика падения производительности сервера: как найти узкое место в Linux-системе | AdminWiki

Диагностика падения производительности сервера: как найти узкое место в Linux-системе

22 сентября 2026 14 мин. чтения
Содержание статьи

Деградация производительности сервера почти всегда локализуется за 15-20 минут, если идти от симптома к метрике в фиксированном порядке, а не перебирать гипотезы наугад. Практический маршрут такой: сначала dmesg, потому что ядро часто уже записало причину, затем load average и распределение времени CPU в top или vmstat, затем диски через iostat -x 1, затем сетевые счётчики и логи приложения.

Такой порядок отсекает четыре частые причины: нехватку памяти с работой OOM killer, насыщение диска, борьбу за блокировки в ядре и троттлинг CPU. Каждая оставляет след в конкретной метрике, и по этому следу виновник находится без перезагрузок и случайных проверок.

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

Почему сервер тормозит: первые шаги диагностики

Формулировка «всё тормозит» не годится как отправная точка. Превратите её в набор фактов: когда началось, как проявляется (растёт время ответа, падает пропускная способность, рвутся соединения), касается ли проблема одного сервиса или всей машины, повторяется ли по расписанию. Ответы на эти вопросы сразу сокращают область поиска.

Второй элемент - базовая линия. Если раньше 1000 запросов в секунду держались при 20% CPU, а теперь те же 1000 дают 80%, это сигнал о деградации конфигурации, кода или окружения, а не о нормальном росте трафика. Без сохранённых нормальных значений сравнивать не с чем. Как собрать такой набор и не утонуть в метриках, разобрано в статье про метрики производительности: что отслеживать DevOps-инженеру и администратору.

Сбор первичных метрик: top, vmstat, iostat, dmesg

Пять команд дают основную часть картины и выполняются за пару минут:

  • dmesg -T | tail -n 50 - сообщения ядра: ошибки дисков (I/O error, medium error), срабатывания OOM killer со строкой Killed process, сообщения о троттлинге и проблемах драйверов.
  • top или htop - load average, распределение %us, %sy, %wa, %id, столбец состояния процессов.
  • vmstat 1 - очереди r (ждут CPU) и b (в непрерываемом сне), обмен с диском si и so, доля ожидания ввода-вывода wa.
  • iostat -x 1 - по каждому устройству await, %util и число операций в секунду.
  • ss -s, netstat -s, ip -s link - потери пакетов, ошибки интерфейсов, повторы TCP.

Что искать: load average, устойчиво превышающий число логических ядер; wa выше 10-20% без всплеска записи; растущие si и so, то есть активный своп; строки про OOM или thermal в dmesg. Учтите ограничение top: он усредняет значения за интервал обновления, поэтому короткие пики в нём теряются. Для точности берите pidstat с интервалом в одну секунду.

Как не ошибиться с интерпретацией: типичные ловушки

  • Высокий load average при свободном CPU обычно указывает на процессы в состоянии D (непрерываемый сон) или на iowait, а не на перегрузку процессора.
  • Высокий %sy не доказывает проблему ядра: так выглядит contention на блокировках, шторм системных вызовов и обработка сети в softirq.
  • Один замер вместо динамики. Метрику нужно смотреть несколько минут и желательно в момент инцидента, а не после перезапуска сервиса.
  • Игнорирование dmesg. События вроде срабатывания OOM killer или сброса диска закрывают вопрос о причине сразу.
  • Отсутствие базовой линии: без сохранённых норм «подозрительное» число не отличить от рабочего.

Порядок проверки слоёв важен не меньше самих команд. Готовый маршрут по CPU, памяти, swap, дискам, очередям, блокировкам и сети приведён в руководстве про причины снижения производительности и порядок диагностики.

Высокий iowait в Linux: причины и поиск виновного процесса

iowait (%wa) показывает долю времени, когда CPU простаивал в ожидании завершения операций ввода-вывода. Рост этой доли означает, что работа упирается в диск или в сетевое хранилище. Источники: медленный накопитель, избыточная запись, деградация массива RAID, проблемы драйвера или контроллера, сетевые файловые системы вроде NFS.

Сам по себе высокий %wa не доказывает проблему. Если диск занят полезной работой, а время обслуживания остаётся в норме, это признак загрузки, а не узкого места. Опасен рост очереди и времени ответа накопителя.

Чтение iostat: await, %util, svctm

  • await - среднее время от постановки запроса в очередь до его завершения, в миллисекундах. Включает и ожидание в очереди, и обслуживание. В новых версиях разделён на r_await и w_await, что точнее для смешанной нагрузки.
  • %util - доля времени, когда у устройства была хотя бы одна незавершённая операция. Для HDD 90-100% означает насыщение. Для SSD и NVMe со глубокой очередью 100% может держаться и при здоровом времени ответа, потому что устройство обрабатывает запросы параллельно.
  • svctm - недостоверный показатель. Man-страница iostat прямо предупреждает: «Do not trust this field any more. This field will be removed in a future sysstat version». Поле было удалено из вывода sar и iostat в sysstat версии 12.1.2 (2018/12/14). Причина в том, что расчёт svctm фундаментально сломан: внутри iostat за интервал он вычисляется как время, когда устройство выполняло работу, делённое на объём выполненной работы, а статистика I/O теперь считается на блочном уровне, и неизвестно, когда драйвер диска начинает обработку запроса. На современных устройствах с параллельной обработкой очереди значения svctm недостоверны, поэтому опирайтесь на await. Для NVMe и многодисковых массивов %util тоже теряет смысл: устройство обрабатывает команды параллельно, и 100% загрузки не означают предела.

Ориентиры для быстрой реакции: для HDD 7200 rpm при случайном чтении 4K типичны 80-120 IOPS и latency 8-15 мс; для SATA SSD - 50-90 тыс. IOPS и latency 0,1-0,3 мс; для потребительского NVMe - 200-500 тыс. IOPS; для серверного NVMe с PCIe 4.0 - от 1 млн IOPS и latency ниже 0,1 мс. Порог тревоги по await: выше 20 мс для SSD и выше 50 мс для NVMe. %util выше 90% на всех дисках массива означает насыщение. Порог всегда привязан к модели накопителя и к тому, читает он последовательно или случайно.

Пример чтения вывода: HDD загружен полностью (%util 99), очередь накопилась, await 18 мс при потоке 340 записей в секунду - диск работает на пределе. NVMe при 8200 операций чтения имеет await 0,08 мс и загрузку 12% - у него большой запас.

Команда iostat -x 1 раз в секунду показывает, какое именно устройство попало в проблему. Связка top, iostat и ss с типовыми порогами разобрана в статье про мониторинг производительности сервера: метрики CPU, памяти, диска и сети.

Поиск процесса-виновника через pidstat и iotop

  • pidstat -d 1 - по каждому процессу kB_rd/s и kB_wr/s, в свежих версиях также iodelay. Показывает, кто создаёт нагрузку на диск.
  • iotop -oPa - в выводе только процессы с активным вводом-выводом, суммарные значения обновляются на месте.
  • ps -eo state,pid,cmd с фильтром по состоянию D - список процессов, застрявших в непрерываемом сне. Если такие процессы держатся минутами, диск или сетевая ФС стали узким местом.

Типовой сценарий: время ответа базы данных растёт, iostat показывает %util 95% на единственном SSD, а pidstat -d 1 выводит на первое место процесс СУБД с сотнями МБ/с записи в журнал. Виновник - режим синхронизации WAL или избыточный уровень логирования, а не «медленный сервер» целиком. Похожая картина возникает при резервном копировании, rsync-репликации и записи логов контейнеров через json-file драйвер.

Утечка памяти в Linux: диагностика и предотвращение OOM

Самый неприятный сценарий выглядит так: free показывает гигабайты свободной памяти, а через минуту в dmesg появляется строка Killed process. Разбор этого поведения и его причин приведён в материале о том, как Linux выдаёт обещания памяти.

Как работает overcommit и demand paging

Ядро Linux выдаёт процессу обещание памяти, которое не всегда способно подкрепить физическими страницами: физическая страница встаёт за виртуальным адресом только при первом реальном обращении, при записи или чтении. Это отложенное выделение, demand paging. При обращении к ещё не подкреплённому адресу процессор генерирует page fault, ядро перехватывает его, находит свободную физическую страницу, отображает её на виртуальный адрес и возвращает управление процессу.

Политику задаёт параметр vm.overcommit_memory: 0 - эвристика ядра, 1 - разрешать любые запросы, 2 - отклонять запросы, превышающие лимит CommitLimit. Режим 2 делает поведение предсказуемым на сервере с фиксированным набором процессов, но может ломать приложения, которые резервируют большие объёмы адресного пространства заранее.

Практический эффект: приложение запрашивает 10 ГБ, реально использует 1 ГБ, и вызов завершается успешно. Пока суммарное потребление остаётся ниже физической памяти и места в swap, всё работает. Когда несколько процессов одновременно начинают использовать обещанное, памяти не хватает, и OOM killer выбирает жертву по счёту oom_score, завершая процесс. Именно этот механизм и даёт строку Killed process в логе ядра при формально свободной памяти, о чём подробно рассказано в разборе overcommit и работы OOM.

Инструменты поиска утечки: smaps, valgrind, bpftrace

  • ps -o pid,rss,vsz,cmd -p PID несколько раз с интервалом. Линейный рост RSS без роста нагрузки - повод копать дальше.
  • /proc/PID/smaps и smaps_rollup дают разбивку по регионам: Private_Dirty, Pss, Anonymous. Растущий Anonymous при стабильном размере кучи указывает на утечку или на растущий внутренний кэш приложения.
  • valgrind --leak-check=full --show-leak-kinds=all находит точное место утечки, но замедляет процесс в разы, поэтому применяется на стенде или на копии нагрузки.
  • bpftrace позволяет трассировать malloc и free прямо на боевом сервере и видеть, где выделенная память не возвращается, при умеренных накладных расходах.

Важное различие: рост buff/cache в выводе free - это кэш страниц, который ядро освободит под нужды процессов. Рост RSS конкретного процесса кэшем не объясняется, и путать эти два показателя нельзя.

Профилактика: лимиты памяти в cgroup или systemd (MemoryMax, MemoryHigh), алерт на появление строк oom-killer в dmesg, отдельный тест на потребление памяти после крупных релизов.

Contention на блокировках ядра: как обнаружить и что делать

Contention возникает, когда несколько потоков или процессов конкурируют за один ресурс: мьютекс, спинлок, futex, запись в общую таблицу ядра. Пропускная способность падает, хотя CPU и диск загружены не полностью. Характерная картина: высокий %sy при низком %us, растущий load average, ступенчатый рост времени ответа.

Использование perf и bpftrace для поиска блокировок

  • perf top -g - живой профиль с деревом вызовов. Высокая доля _raw_spin_lock, _raw_spin_lock_irqsave, mutex_lock или futex_wait указывает на борьбу за блокировки.
  • perf record -g -a -- sleep 10 с последующим perf report - запись профиля за окно инцидента, разбирать удобнее на спокойной системе.
  • bpftrace с точкой на _raw_spin_lock и агрегацией по kstack показывает, из какого кода приходит основная часть захватов. Трассировка стартует и останавливается на живой системе без перезагрузки.
  • /proc/lock_stat даёт счётчики по блокировкам ядра, если ядро собрано с поддержкой lockdep. Статистика блокировок включается через CONFIG_LOCK_STAT; сбор включается командой echo 1 > /proc/sys/kernel/lock_stat, отключается echo 0 > /proc/sys/kernel/lock_stat, а текущие данные смотрят в /proc/lock_stat. Статистика отслеживает 4 точки конкуренции на класс, где точка конкуренции - это место вызова, которому пришлось ждать захвата блокировки.

Для осмысленных стеков нужны debug symbols для ядра и приложения. Без них вы увидите адреса вместо имён функций и не сможете связать захват блокировки с конкретным кодом.

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

Анализ вывода strace при futex-контеншене

Команда strace -f -e trace=futex -p PID выводит все futex-вызовы потоков процесса, а ключ -c даёт сводку по времени и числу вызовов. Много FUTEX_WAIT с заметным временем ожидания и малым числом успешных пробуждений означает, что потоки стоят в очереди к одному объекту синхронизации.

Типовой пример: пул из 32 рабочих потоков обновляет общий счётчик под глобальным мьютексом. С ростом числа потоков производительность падает, а %sy растёт. Решение лежит в коде или архитектуре: разбить блокировку, перейти на атомарные операции, шардировать состояние по потокам.

Ограничение strace: он останавливает процесс на каждом вызове и заметно замедляет работу. Применяйте его короткими окнами, а на высоконагруженном продакшене предпочитайте perf или bpftrace.

Переполнение сетевых буферов: диагностика и настройка

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

Как читать статистику сетевых интерфейсов

  • ip -s link - счётчики RX и TX по каждому интерфейсу: errors, dropped, overrun, missed.
  • ethtool -S eth0 - детальные счётчики драйвера: rx_dropped, rx_fifo_errors, rx_no_buffer_count, tx_dropped. Здесь видно, теряются ли пакеты в кольцевом буфере адаптера.
  • netstat -su - статистика UDP: receive buffer errors и packet receive errors. Для UDP потери заметны сразу, повторов у протокола нет.
  • ss -ti - по каждому TCP-соединению retrans, rtt и cwnd. Рост повторов вместе с падением cwnd указывает на потери в пути или на переполнение на приёмной стороне.

Различайте два уровня потерь. overrun и fifo errors означают переполнение кольцевого буфера драйвера: пакеты отбрасываются до того, как ядро успело их забрать. dropped на уровне стека чаще связан с нехваткой буферов ядра или с недостаточным backlog при всплеске. Лечатся эти случаи разными параметрами.

Настройка буферов через sysctl

ПараметрЧто задаётКогда увеличивать
net.core.rmem_maxМаксимальный приёмный буфер сокетаПри rx_dropped и на каналах 10 Гбит/с и выше
net.core.wmem_maxМаксимальный буфер отправкиПри tx_dropped и ошибках ENOBUFS у приложения
net.core.netdev_max_backlogОчередь пакетов между драйвером и стекомПри всплесках трафика и rx_no_buffer_count
net.ipv4.tcp_rmem, net.ipv4.tcp_wmemГраницы автотюнинга TCP: min, default, maxНа быстрых каналах и при больших RTT
net.core.somaxconn, net.ipv4.tcp_max_syn_backlogОчередь accept и очередь SYNПри отказах в установке соединений под пиком

Значение по умолчанию для rmem_max и wmem_max составляет около 208 КБ в большинстве дистрибутивов Linux, чего достаточно для сетевой среды общего назначения с низкой задержкой или для таких приложений, как DNS и веб-сервер, но мало для каналов 10 Гбит/с. Меняйте по одному параметру и замеряйте потери и повторы до и после, иначе источник улучшения не определить.

Кольцевой буфер адаптера расширяется отдельно: ethtool -g eth0 показывает текущий размер, ethtool -G eth0 rx 4096 увеличивает приёмное кольцо, если драйвер и карта это поддерживают. Типовой сценарий: при всплесках трафика каждые несколько минут растёт rx_no_buffer_count; увеличение net.core.netdev_max_backlog до 5000 убирает потери, если процессор успевает обрабатывать пакеты. Этот параметр задаёт максимальное количество пакетов, помещаемых в очередь на стороне INPUT, когда интерфейс получает пакеты быстрее, чем ядро может их обработать.

Троттлинг CPU: причины и методы обнаружения

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

Виртуализация добавляет свои источники: квота CPU в гипервизоре, ограничение числа vCPU, недополученные такты процессора. Последние видны в top как %st и в mpstat как steal time.

Мониторинг температуры и частоты

  • sensors (пакет lm-sensors) - температура пакета процессора и отдельных ядер, обороты вентиляторов.
  • turbostat - отчёт о топологии процессора, частоте, статистике idle-состояний, температуре и мощности на процессорах X86. Периодический вывод по умолчанию идёт с 5-секундным интервалом, который можно изменить. Среди полей: Bzy_MHz (фактическая частота), TSC_MHz (номинальная), PkgWatt (ватты, потребляемые всей платформой, RAPL), PkgTmp. Bzy_MHz вычисляется как TSC_delta*APERF_delta/MPERF_delta/measurement_interval, а TSC_MHz = TSC_delta/measurement_interval. Учтите: расчёты Bzy_MHz зависят от TSC_delta и ненадёжны в интервалах, когда TSC_MHz не работает на базовой частоте, а сбор данных turbostat не атомарен. Поэтому сравнивайте PkgWatt и PkgTmp с системными лимитами и оценивайте частоту в динамике, а не по одному замеру.
  • cpupower frequency-info или cpufreq-info - текущий governor, доступные частоты, лимит scaling_max_freq.
  • cat /proc/cpuinfo с полем MHz - быстрая проверка текущей частоты по ядрам.
  • dmesg с поиском по throttl и thermal, а также mcelog или ras-mc-ctl - аппаратные ошибки и события machine check.

Типовая картина: частота держится на 800 МГц при полной загрузке, а turbostat одновременно показывает рост PkgTmp до 95 градусов. Причина в охлаждении, а не в настройках приложения.

Что делать при троттлинге: проверка охлаждения и лимитов

  • Охлаждение: запылённые радиаторы, высохшая термопаста, отказ вентилятора, слабый поток воздуха в стойке.
  • BIOS и UEFI: лимиты PL1 и PL2, длительность Turbo Boost, настройки C-states, политика вентиляторов.
  • Governor: профиль powersave на сервере с постоянной нагрузкой удерживает частоту ниже доступной, performance снимает это ограничение.
  • Виртуализация: проверьте %steal, квоты CPU и лимиты гипервизора, число выделенных vCPU.

Правки в BIOS и профилях питания проверяйте под той же нагрузкой, что и раньше. Сравнение в одинаковых условиях покажет, ушёл троттлинг или просто сместился порог срабатывания.

Шпаргалка: какой инструмент выбрать для каждой проблемы

СимптомПервая командаЧто смотретьСледующий шаг
Высокий load average, CPU свободенvmstat 1, topКолонка b, wa, процессы в состоянии Diostat -x 1, pidstat -d 1
Высокий %waiostat -x 1await и %util по конкретному устройствуpidstat -d 1, iotop -oPa
RSS процесса растётps -o pid,rss,cmd, /proc/PID/smaps_rollupPrivate_Dirty, Anonymousbpftrace по malloc и free, valgrind на стенде
Высокий %sy при низком %usperf top -g_raw_spin_lock, mutex_lock, futex_waitbpftrace по стеку, strace -e trace=futex
Потери пакетовip -s link, ethtool -Srx_dropped, overrun, retransБуферы через sysctl, ethtool -G
Частота CPU ниже номиналаturbostatBzy_MHz против TSC_MHz, PkgTmpОхлаждение, настройки BIOS, governor
Неожиданное убийство процессаdmesg -TСтроки oom-killer и Killed processЛимиты cgroup, поиск утечки памяти

Рабочий порядок на инциденте выглядит так: зафиксируйте время и симптом, снимите dmesg, оцените load average и wa, проверьте iostat по устройствам, найдите процесс через pidstat, и только затем переходите к приложению. Такой маршрут доводит до конкретного виновника за один проход и убирает соблазн покупать железо до подтверждения причины. Как подтвердить деградацию метриками и не потратить бюджет зря, разобрано в материале про поиск узкого места в системе без лишних затрат.

Поделиться:
Сохранить гайд? В закладки браузера