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

Средняя нагрузка объединяет очередь на процессор и непрерываемое ожидание

Три числа после слов load average в выводе uptime это усреднённые значения за минуту, пять минут и пятнадцать. Усреднение устроено с убыванием веса старых событий: короткий всплеск поднимает первую цифру и почти не трогает третью, затяжная проблема тянет вверх весь набор. В классических системах счётчик означал длину очереди к процессору, то есть количество задач, претендующих на вычислительное время прямо сейчас. В Linux к этой группе добавлена вторая, задачи в непрерываемом ожидании, которые стоят в очереди к диску, сетевому хранилищу или иному блочному ресурсу. Решение давнее и осознанное: сервер, на котором тридцать процессов застряли на неработающем хранилище, обязан выглядеть нагруженным, а не безмятежным.

Посмотреть значения можно двумя командами:

uptime
cat /proc/loadavg

Первая утилита печатает три средних рядом с аптаймом и числом подключённых пользователей. Вторая выдаёт те же средние и два дополнительных поля. Четвёртое поле вида "2/1043" показывает, сколько задач выполняется в текущее мгновение и сколько всего задач живёт в системе, пятое содержит номер последней созданной задачи. Именно здесь прячется ключ к разгадке: load average равен сорока, а мгновенных готовых задач всего две. Нагрузку создают не очереди на процессор, а ожидания ввода-вывода, которые мгновенная выборка готовых задач не учитывает вовсе.

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

Состояние D и отличие непрерываемого ожидания от обычного сна

У каждой задачи в системе есть текущее состояние, и ps выводит его однобуквенным кодом в колонке STAT или state. Буква R описывает задачу, которая выполняется прямо сейчас или ждёт своей очереди на процессор. Буква S означает обычный сон: процесс ждёт таймера, ответа сокета, освобождения блокировки, и его можно разбудить сигналом в любой момент. Получив сигнал, спящая задача прерывает ожидание, отрабатывает его и продолжает жить дальше. Так устроено подавляющее большинство процессов, и здоровый сервер почти целиком состоит из записей R и S.

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

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

Состояние конкретного процесса читается прямо из каталога сведений ядра:

grep State /proc/1234/status

Строка "State: D (disk sleep)" подтверждает непрерываемое ожидание диска. Рядом лежат другие подсказки: файл wchan содержит имя функции ядра, в которой задача спит, файл io копит счётчики прочитанного и записанного, файл stack показывает цепочку вызовов ядра и требует прав суперпользователя. Пара минут в этом каталоге нередко даёт больше, чем час блужданий по графикам.

Быстрая проверка процессов и их состояний через ps

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

ps -eo state,pid,user,wchan:32,cmd --sort=state | awk '$1=="D"'

Колонка state несёт однобуквенный код, сортировка сгребает все D в одну группу, awk оставляет только их. Колонка wchan превращает абстрактное "ждёт" в конкретное имя функции ядра. Примерный вид вывода на нездоровом сервере:

D   4321 app    io_schedule        tar cf backup.tar data
D   4432 app    io_schedule        python3 /opt/app/import.py
D   4465 root   wait_on_page_bit   [kworker/u8:2]

Значения wchan читаются без словаря: io_schedule указывает на ожидание в очереди ввода-вывода, wait_on_page_bit сообщает об ожидании страницы в кэше, а имена с nfs внутри прямо называют сетевое хранилище виновником. Если в повторных выборках регулярно мелькает один и тот же PID, сомнений не остаётся: он застрял всерьёз и надолго.

У привычной команды ps aux та же буква прячется в колонке STAT, и часто её не замечают, глядя только на %CPU и %MEM. Один взгляд на STAT мгновенно разделяет процессы на честно вычисляющие и молчащие в ожидании. Для наблюдения в динамике выборку повторяют в цикле:

while sleep 2; do ps -eo state,pid,wchan:24,cmd | awk '$1=="D"'; done

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

Колонки ожидания в статистике vmstat и iostat

Состояния процессов отвечают на вопрос "кто ждёт". Статистика системы отвечает на вопрос "чего ждут и в каком объёме". Обе половины закрывают две утилиты, и обе обычно уже установлены на сервере.

vmstat печатает компактную таблицу через заданный интервал, и две её колонки прямо относятся к теме. В блоке procs колонка r содержит готовые к выполнению задачи, а колонка b это процессы в непрерываемом ожидании, живой аналог количества записей с буквой D. В блоке cpu колонка wa показывает долю времени, которую процессоры простаивали при наличии незавершённого ввода-вывода. Примерный расклад на проблемном узле:

 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 1 36      0 120400 214720 998304    0    0  8412    35 1204 3210  3  1 88  8  0

Первая колонка держит единицу, вторая три десятка с лишним, а процессоры бездействуют почти всё время. Пара r и b читается как готовый вывод: перед нами очередь к диску, а не к процессору. Побочно стоит взглянуть на si и so: интенсивный обмен со свопом тоже оборачивается высоким wa и общей вялостью, и лечится он добавлением памяти. Отдельная тонкость про wa: колонка растёт при любом незавершённом вводе-выводе на фоне бездействующих процессоров, поэтому одиночное высокое значение мало о чём говорит. Ориентироваться стоит на связку b, await и длину очереди устройств.

Вторая утилита показывает то же самое в разрезе устройств. Команда iostat -x 1 печатает таблицу по каждому диску раз в секунду, примерный фрагмент:

Device      r/s     w/s   rkB/s   wkB/s   await  aqu-sz  %util
sda       102.20   14.60   408.80  116.80   95.40    8.21  99.60
sdb         2.10    0.30     8.40    1.20    12.30   0.04   0.40

Колонка await содержит среднюю длительность обслуживания запроса в миллисекундах с учётом простоя в очереди. Колонка aqu-sz показывает среднюю длину этой очереди, в старых выпусках утилиты она зовётся avgqu-sz. Колонка %util отражает занятость устройства. Чтение фрагмента не требует опыта: один диск прогрет до предела и держит очередь, второй почти спит. Такой расклад сразу указывает на конкретное устройство, и искать остаётся его очередь и его здоровье.

Утилита iostat входит в пакет sysstat, и если сбор статистики включён, история устройств доступна задним числом командой sar -d. Атрибуцию к процессам даёт iotop с флагом -o: утилита показывает, кто именно читает и пишет в текущий момент, и требует прав суперпользователя. В упрощённых окружениях те же сведения дают счётчики из файла io каталога сведений процесса: прирост read_bytes и write_bytes между двумя замерами вычисляется простым вычитанием.

Типовые источники непрерываемых ожиданий на сервере

За высокой средней нагрузкой при низком потреблении процессора почти всегда стоит одна из четырёх причин, и полезно знать их в лицо.

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

Второй источник это деградирующий диск. Нестабильные секторы заставляют контроллер перечитывать данные многократно, отсюда редкие, но огромные скачки await и подвисания ровного на вид узла. Журнал ядра обычно содержит сообщения об ошибках чтения, а отчёт smartctl показывает рост счётчиков переназначенных секторов. Диск формально работает, но рывками, и каждая попытка чтения стоит дорого.

Третий источник это перегруженные очереди ввода-вывода. Железо исправно, но нагрузка превысила пропускную способность: ночная резервная копия пишется на тот же диск, где живёт база, и %util упирается в потолок, await растёт, ошибок нет. Проблема лечится расписаниями и разносом по устройствам, а не заменой железа.

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

Пошаговый план поиска ресурса с зависшим ожиданием

Все элементы картины собраны по отдельности, остаётся соединить их в короткий порядок действий. Он умещается в пять шагов:

  1. зафиксировать общую картину через uptime и vmstat с колонками b и wa;
  2. собрать статистику устройств через iostat -x 1 и наметить кандидата по высокой очереди и await;
  3. вывести процессы в состоянии D с именами, аргументами и колонкой wchan;
  4. определить ресурс под каждым ожиданием, от функции ожидания до точки монтирования;
  5. устранить причину на уровне ресурса и убедиться в возврате метрик к норме.

Определение ресурса под конкретным процессом сводится к нескольким командам:

cat /proc/4321/wchan
lsof -p 4321
findmnt -T /srv/import
lsblk -o NAME,SIZE,TYPE,MOUNTPOINT

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

dmesg | tail -30

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

Устранение причины и возврат метрик к норме

Честно говоря, у администратора здесь меньше власти, чем подсказывает опыт с обычными зависшими программами. Принудительное завершение процесса в состоянии D не работает по определению: сигнал ляжет в очередь доставки и сработает лишь после возврата операции. Задачу нельзя ни завершить, ни сдвинуть, пока ресурс молчит. Отсюда главное правило: лечить нужно ресурс, а не процесс.

Для сетевого хранилища первым делом проверяют досягаемость сервера и его загрузку. Если узел вернулся в строй, зависшие операции чаще всего завершаются сами, а отложенные сигналы наконец доставляются. Если хранилище ушло надолго, точку монтирования снимают принудительно: флаг -f у umount рассчитан именно на недоступные сетевые хранилища, флаг -l открепляет каталог от дерева и освобождает остальные задачи от зависшей точки. Иногда помогает перезапуск клиентской службы NFS, в самом тяжёлом случае остаётся перезагрузка: остановка системы тоже может ждать зависшую точку монтирования. Число и длительность повторов запросов задаются параметрами монтирования, конкретные значения подбираются под окружение.

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

Для перегруженных очередей лечат расписание: резервное копирование смещают в окно минимальной нагрузки, базу и копии разносят по разным устройствам, фоновым задачам назначают низкий приоритет через ionice. Журналы транзакций на отдельном носителе снижают пики очереди, и база перестаёт конкурировать с копированием за один накопитель.

Для драйверов и прошивок работает плановый путь: обновление ядра и прошивки контроллера сначала на стенде, затем на рабочем узле, с контрольным замером статистики до и после.

Финальная проверка проста: три средних спадают постепенно, потому что несут память о прошлых минутах, а мгновенные показатели b и wa очищаются быстрее. Полезно держать в мониторинге отдельную метрику числа задач в состоянии D: она растёт раньше, чем пользователи начнут замечать вялость, и превращает разбор из загадки в короткий список проверок. Средняя нагрузка работает как манометр в водопроводе: она не покажет, где засор, но честно сообщит, что давление выросло. А уже по колонкам b, await и выборке ps засор находится за один спокойный вечер.