user, system, iowait: куда на самом деле уходит время процессора
Открываете top, видите четыре строчки процента — us, sy, id, wa — и интуитивно читаете их как «занят / свободен». Это неправильное чтение почти гарантированно приводит к неправильному диагнозу: вы добавляете ядра там, где нужен более быстрый диск, или оптимизируете код там, где всё дело в сетевом хранилище. Разница между user, system и iowait — это разница между «процессор работает», «работает по чужому поручению» и «ничего не делает, но не может переключиться на что-то полезное». Разберём, что каждая цифра означает буквально и почему высокий iowait — почти всегда хорошая новость для CPU и плохая для диска.
Содержание
- Откуда берутся эти цифры: /proc/stat, top, vmstat
- User: время, потраченное на ваш код
- System: за что отвечает ядро в этот момент
- Iowait: почему это не «процессор занят»
- Почему высокий iowait — это сигнал о диске или сети, а не о процессоре
- Как это меняет диагностику узкого места сервера
- Практические примеры чтения показаний
Откуда берутся эти цифры: /proc/stat, top, vmstat
Все утилиты мониторинга — top, vmstat, mpstat, sar — не измеряют ничего сами. Они читают файл /proc/stat, который ядро обновляет на каждом тике планировщика, и на каждой итерации вычисляют разницу между текущими и предыдущими счётчиками, деля на прошедшее время.
Первая строка /proc/stat выглядит примерно так:
cpu 123456 45 34567 987654 5432 0 234 0 0 0
Числа — это накопленные с момента загрузки системы «тики» (в единицах USER_HZ, обычно 1/100 секунды), проведённые в каждом из состояний. Порядок полей фиксированный:
user nice system idle iowait irq softirq steal guest guest_nice
vmstat 1 печатает почти те же самые категории под колонками us, sy, id, wa, st — просто уже в процентах за интервал, а не в абсолютных тиках. Это важно понимать: iowait — не отдельный «режим работы процессора» вроде user или system, а разновидность простоя (idle), которую ядро решило учесть отдельно, потому что в этот момент у CPU было незавершённое дисковое I/O. Отсюда и вырастают все нюансы, о которых дальше.
User: время, потраченное на ваш код
user (и родственный ему nice — то же самое, но для процессов с пониженным приоритетом) — это время, которое CPU провёл, выполняя инструкции процесса в непривилегированном режиме: ваш Python-скрипт, обработчик Nginx, JVM, PHP-FPM воркер. Сюда попадает всё, что процессор исполняет от имени приложения между системными вызовами — арифметика, работа со строками, сериализация JSON, рендеринг шаблона.
Высокий user при в целом высокой утилизации CPU — самый «честный» сценарий: процессор занят полезной работой приложения. Диагностика идёт в сторону профилирования кода (perf top, профайлеры языка), поиска неэффективных алгоритмов, лишних сериализаций. Горизонтальное масштабирование (больше ядер, больше инстансов) почти всегда помогает именно здесь — вы упираетесь в реальные вычисления.
Стоит смотреть на user, разложенный по ядрам через mpstat -P ALL 1: если общий user умеренный, но одно конкретное ядро стабильно близко к 100% usr, это почти всегда однопоточное приложение, упирающееся в потолок одного ядра — Node.js без кластеризации, GIL-ограниченный Python-процесс. Общая картина по top это скрывает, потому что усредняет по всем ядрам.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверSystem: за что отвечает ядро в этот момент
system — время, проведённое в режиме ядра (kernel mode) по инициативе процесса: обработка системных вызовов (read, write, accept, mmap, epoll_wait), управление памятью (page fault, выделение и освобождение страниц), работа с файловой системой на уровне VFS, планирование потоков, копирование данных между пространством пользователя и ядра.
Ключевое отличие от user: system — это не «ваш код», а код ядра, выполняемый *от лица* вашего процесса. Приложение просит ядро сделать что-то — открыть сокет, прочитать файл, поставить mutex — и пока ядро это делает, время засчитывается в system.
Практически высокий system — это почти всегда сигнал о частоте или дороговизне системных вызовов, а не о «медленном ядре» как таковом. Типичные причины:
- Слишком мелкая гранулярность I/O — приложение делает
write()на каждый лог-байт вместо буферизации, и каждый вызов — это переход в ядро и обратно. - Частые
mmap/munmapвместо переиспользования буферов — характерно для наивных аллокаторов памяти или JIT-компиляторов, агрессивно выделяющих код-кэш. - Большое число page fault — процесс с растущим резидентным набором данных постоянно трогает новые страницы памяти; помогает предварительное резервирование памяти или huge pages.
- Виртуализация: под KVM/Xen часть операций (таймер, прерывания сетевой карты) требует выхода в гипервизор (VM exit), который тоже засчитывается как
systemгостевой ОС. - Слишком много контекстных переключений — тысячи короткоживущих потоков или неверно настроенный пул соединений постоянно передают CPU туда-сюда, и накладные расходы на переключение (сохранение регистров, TLB-flush) съедают время именно как
system.
Разложить system на конкретные вызовы можно через strace -c -p <pid> или perf trace для профилирования на проде. Если доминируют read/write на дескрипторах сети или диска — это уже мостик к следующей категории, iowait: сами вызовы быстрые, а вот ожидание запрошенных данных — нет.
Iowait: почему это не «процессор занят»
Вот здесь начинается путаница, которая ломает диагностику чаще всего. iowait — это простой CPU, а не работа. Формально: время, которое CPU провёл ничего не делая, при условии, что в этот момент у системы было хотя бы одно незавершённое дисковое (блочное) I/O-обращение, инициированное процессом, который *мог бы* сейчас выполняться на этом CPU, если бы не ждал данные.
Механика простая. Процесс делает read() с диска. Ядро видит, что данных нет в page cache, отправляет запрос блочному устройству и переводит процесс в состояние D (uninterruptible sleep — тот самый непрерываемый сон, который не убивается через kill -9, потому что процесс не может безопасно среагировать на сигнал посреди операции с ядром). Планировщику нечего выполнять на этом CPU прямо сейчас — если только в очереди не найдётся другой готовый процесс. Если такого процесса нет, CPU простаивает, и это время учитывается не как обычный idle, а как iowait.
Отсюда нюанс, который редко объясняют, но который меняет интерпретацию цифры на многоядерных машинах: iowait — это не «доля времени, когда система ждёт диск», а «доля времени, когда конкретному CPU было совсем нечем заняться, а где-то в системе висело диск-I/O». На 32-ядерной машине, где один процесс ждёт медленный диск, а остальные 31 ядро заняты полезной работой, общий iowait по системе окажется исчезающе малым — простаивающих циклов почти нет, хотя тот единственный процесс реально стоит и ждёт. И наоборот: на слабо загруженной машине даже одно медленное обращение к диску может дать заметный процент iowait, потому что процессору больше нечем заняться.
Поэтому iowait нельзя читать в отрыве от общей загрузки: если id низкий, а wa высокий — CPU реально упирается в диск/сеть. Если и id, и wa заметны одновременно — это, скорее, малозагруженная машина, где операции ввода-вывода просто видны на фоне общего затишья.
Почему высокий iowait — это сигнал о диске или сети, а не о процессоре
Главный практический вывод: если top показывает низкий us, низкий sy и высокий wa, то процессор почти свободен. Ядра CPU физически способны выполнить в разы больше полезной работы прямо сейчас — они просто ждут, пока вернутся данные с диска, из сетевого хранилища (NFS, Ceph, iSCSI) или, реже, из свопа. Добавление ядер, апгрейд CPU, увеличение числа воркеров приложения — не решит проблему и часто её усугубит: больше параллельных воркеров означает больше параллельных запросов к тому же медленному диску, то есть более длинную очередь на устройстве.
Правильная диагностика при высоком wa идёт в сторону блочного устройства и сети:
iostat -x 1 5
pidstat -d 1
iotop -o
Здесь важны колонки %util (насколько устройство занято обслуживанием запросов — приближение к 100% означает, что диск физически не успевает) и await (среднее время ожидания запроса, включая очередь). Если %util у конкретного диска стабильно высокий одновременно с ростом wa в vmstat, узкое место найдено — и это диск, а не процессор. pidstat -d покажет, какой процесс генерирует I/O.
Если диск не перегружен (%util умеренный, await в разумных пределах), а wa всё равно заметен, стоит проверить сетевые хранилища отдельно — NFS-монтирования, удалённые базы через медленный канал, S3-совместимые бэкенды с высокой задержкой. Симптомы похожи (процесс уходит в D), но диагностика уже сетевая: sar -n DEV, задержки до эндпоинта.
Отдельно стоит упомянуть колонку steal (st) — она не относится напрямую к iowait, но часто путается с ним: steal означает, что гипервизор отобрал у вашей виртуальной машины физическое время CPU в пользу соседей на том же хосте. Симптоматика похожая («вроде не занят, а тормозит»), но причина другая — не диск, а перепроданность CPU у провайдера. Если st регулярно заметен, это отдельная история про шумного соседа, а не про I/O.
Как это меняет диагностику узкого места сервера
На практике связка user/system/iowait — это таблица маршрутизации для дальнейшего расследования. Ниже — обобщённая схема, куда смотреть в зависимости от того, какая колонка доминирует (без привязки к конкретным цифрам — соотношения у каждой системы свои):
| Картина в top/vmstat | Что это означает | Куда смотреть дальше |
|---|---|---|
Высокий us, низкий sy/wa | CPU занят полезными вычислениями приложения | Профилирование кода, алгоритмическая оптимизация, горизонтальное масштабирование |
Высокий sy, умеренный us | Ядро тратит время на syscalls, page fault, переключения контекста | strace -c, проверка буферизации I/O, число потоков, huge pages |
Высокий wa, низкие us/sy | CPU простаивает в ожидании диска/сети, само по себе не перегружен | iostat -x, pidstat -d, проверка сетевых хранилищ |
Заметный st | Гипервизор забирает CPU-время у виртуалки | Смена тарифа/провайдера, проверка соседей по хосту |
Высокий id при жалобах на «тормозит» | Возможно, узкое место не в CPU вовсе | Задержки сети, блокировки в приложении (не I/O, а мьютексы/локи), внешние API |
Практическое следствие для планирования мощностей: если нагрузка систематически даёт высокий wa, покупка более мощного CPU в новом тарифе — деньги на ветер. Нужен более быстрый накопитель (переход с сетевого диска на локальный NVMe, увеличение IOPS-лимита у виртуального диска), больше RAM под page cache, либо архитектурное изменение — асинхронный I/O, батчинг записи, вынос тяжёлых чтений в реплику. Если картина обратная — CPU забит us/sy, а диск почти простаивает, — апгрейд диска не даст ничего, нужны более быстрые или более многочисленные ядра, либо оптимизация кода.
Ещё одно следствие — для алертинга. Правило «алерт, если загрузка CPU выше 90%» без разбивки по компонентам почти бесполезно: 90% из wa и 90% из us требуют совершенно разных действий дежурного инженера, а метрика «CPU load» в упрощённых дашбордах их не различает. Стоит завести отдельные пороги для us+sy и для wa. Для более широкого разбора перегрузки именно вычислительной части — отдельная тема, как найти причину высокой нагрузки на процессор, а чтобы проверить, действительно ли диск является узким местом, полезно предварительно проверить скорость диска на VPS честным замером, а не полагаться на паспортные цифры провайдера.
Стоит держать в голове и ограничение самой метрики: iowait — агрегированный показатель, усреднённый по всем ядрам и процессам. Он не покажет, *какой* именно процесс или диск виноват — для этого нужны pidstat и iostat по конкретным устройствам. Это индикатор «стоит копать в сторону I/O», а не диагноз сам по себе.
Практические примеры чтения показаний
Разберём несколько типичных сочетаний, которые реально встречаются на серверах, без привязки к конкретным цифрам замеров — просто как логика рассуждения.
База данных с медленными запросами и растущей очередью соединений. Важно не путать «CPU занят выполнением запроса» (us, парсинг SQL, построение плана, сортировка в памяти) с «CPU ждёт данные с диска» (wa, чтение страниц таблиц/индексов, не поместившихся в буферный пул). Если увеличение shared_buffers/innodb_buffer_pool_size заметно снижает wa при той же нагрузке — узкое место действительно было в дисковом I/O, а не в самих запросах.
Контейнеризированное приложение «тормозит», хотя docker stats показывает низкий CPU. docker stats по умолчанию показывает us+sy, но не разбивает iowait отдельно — контейнер может простаивать в ожидании I/O от смонтированного тома или сетевого хранилища, и это не видно на уровне cgroup-метрики CPU. Надёжнее смотреть iowait на уровне хоста и I/O-метрики конкретного блочного устройства, к которому примонтирован том контейнера.
Резервное копирование по ночам роняет отклик сайта. Классический сценарий: rsync/tar/pg_dump создают устойчивую нагрузку на диск, %util устройства приближается к максимуму, обычные запросы приложения начинают ждать своей очереди на тот же диск — и это проявляется как рост wa именно в окна бэкапа. Решение — не в CPU, а в приоритизации ввода-вывода: перенос бэкапа на реплику, ограничение скорости через --bwlimit у rsync, или ionice для снижения приоритета I/O у фонового процесса.
«CPU почти не загружен, но запросы всё равно копятся». Узкое место может быть не в ресурсах CPU вообще, а во внешней зависимости — медленном ответе стороннего API, блокировке на уровне приложения (глобальный лок вокруг критической секции), исчерпанном пуле соединений к базе. Здесь iowait ничего не покажет, потому что ожидание идёт не на блочное устройство, а на сеть или на внутреннюю блокировку — это уже область strace и профилирования самого приложения, а не системных счётчиков CPU.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Если iowait равен нулю, значит ли это, что диск не является проблемой?
Не обязательно. На загруженной многоядерной машине процесс может стоять в очереди на диск, но пока на CPU есть другая полезная работа для планировщика, время не засчитывается как iowait. Проверяйте iostat -x и await/%util напрямую, не полагаясь только на iowait.
Можно ли понизить iowait настройками ядра, не трогая диск?
Отчасти — увеличением page cache (больше RAM), тюнингом vm.dirty_ratio/vm.dirty_background_ratio, использованием readahead. Но это смягчает симптом за счёт памяти, а не устраняет причину — если рабочий набор данных заметно больше доступной RAM, вы всё равно упрётесь в скорость накопителя.
Почему top и vmstat иногда показывают разные цифры iowait в один момент?
Обе читают один /proc/stat, но с разной частотой обновления и усреднением — при коротких всплесках I/O это даёт расхождения. Для устойчивой картины смотрите несколько последовательных интервалов (vmstat 1 10), а не одно значение.
Steal и iowait — это одно и то же на VPS с перепроданным CPU?
Нет. Iowait — простой из-за ожидания вашего же дискового/сетевого I/O. Steal — время, которое гипервизор отдал другим виртуалкам на том же хосте, независимо от того, ждёте вы диск или нет. Симптомы похожи, но лечатся по-разному.
Стоит ли переживать из-за высокого iowait ночью, во время бэкапа?
Как правило, нет — если бэкап укладывается в отведённое окно и не мешает дневной нагрузке. Проблема начинается, когда окна бэкапа пересекаются с рабочей нагрузкой — тогда стоит смотреть в сторону ionice или переноса задачи на реплику.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →