Картина знакома многим администраторам: мониторинг присылает тревогу по средней нагрузке, 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 растёт, ошибок нет. Проблема лечится расписаниями и разносом по устройствам, а не заменой железа.
Четвёртый источник это драйверы и прошивки. Контроллер хранилища, редкая ошибка в драйвере, устаревшая прошивка диска способны отправить в непрерываемое ожидание целую очередь запросов. Журнал ядра здесь главный свидетель, и читать его стоит до всяких экспериментов.
Пошаговый план поиска ресурса с зависшим ожиданием
Все элементы картины собраны по отдельности, остаётся соединить их в короткий порядок действий. Он умещается в пять шагов:
- зафиксировать общую картину через uptime и vmstat с колонками b и wa;
- собрать статистику устройств через iostat -x 1 и наметить кандидата по высокой очереди и await;
- вывести процессы в состоянии D с именами, аргументами и колонкой wchan;
- определить ресурс под каждым ожиданием, от функции ожидания до точки монтирования;
- устранить причину на уровне ресурса и убедиться в возврате метрик к норме.
Определение ресурса под конкретным процессом сводится к нескольким командам:
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 засор находится за один спокойный вечер.