Почему сервер с 90% простоя всё равно не успевает обрабатывать запросы
Открываете top, видите %Cpu(s): 8.0 us, 2.0 sy, 0.0 ni, 90.0 id — и первая мысль «процессора полно, дело не в нём». А сайт при этом отвечает медленно, очередь запросов растёт, клиенты жалуются на таймауты. Это одна из самых частых ошибок диагностики: низкая загрузка CPU не означает запас мощности, она означает, что процессор большую часть времени ждёт чего-то другого. Разберём, чего именно он может ждать и как это найти, не гадая.
Содержание
- Простой процессора — это ожидание, а не свободная мощность
- Что на самом деле означают user, system и iowait
- Диск: узкое место, которое прячется за низкой загрузкой
- Сеть: время, которое съедают не байты, а обмен сообщениями
- Блокировки и однопоточная модель обработки
- Ограниченный пул соединений: когда очередь растёт из-за лимита, а не из-за нагрузки
Простой процессора — это ожидание, а не свободная мощность
CPU в состоянии idle не «отдыхает про запас» — он просто не занят работой прямо сейчас, потому что ему нечего исполнять. Причина может быть в двух принципиально разных ситуациях, и их легко перепутать:
- Действительно избыток мощности — задач мало, все они быстро выполняются, очереди нет.
- Задачи есть, но процессор не может их исполнять — потому что они заблокированы: ждут ответа от диска, от сети, от другого потока, освобождения блокировки или свободного слота в пуле соединений.
Во втором случае ядро планировщика просто не находит готового к исполнению процесса — не потому что работы нет, а потому что вся работа сейчас числится в состоянии ожидания (в Linux это, например, состояния D — uninterruptible sleep, или S — interruptible sleep). Именно поэтому у сервера бывает одновременно низкий %CPU и высокий load average: load average в Linux учитывает не только процессы, готовые к исполнению на CPU, но и процессы в состоянии D — ожидающие ввода-вывода. Число задач в очереди растёт, а колонка «свободный процессор» в мониторинге продолжает показывать зелёный цвет. Подробнее о том, что это число означает на практике, — в статье про load average и что оно на самом деле показывает.
Вывод для диагностики простой: если CPU idle высокий, а отклик медленный — не останавливайтесь на этой метрике. Она отвечает только на вопрос «занят ли процессор счётом», а не на вопрос «успевает ли сервер обрабатывать запросы».
Что на самом деле означают user, system и iowait
Разбивка в top/vmstat даёт первую подсказку, но её часто читают неправильно.
- us (user) — время, потраченное на код приложения в пользовательском пространстве.
- sy (system) — время в системных вызовах ядра: чтение файлов, работа с сетью, память, планировщик.
- wa (iowait) — процессор простаивает, потому что *все* процессы, которые он мог бы исполнять, ждут завершения операций ввода-вывода. Это не «диск тормозит вообще», это конкретно означает: у ядра прямо сейчас нет готовой к исполнению работы, а есть процессы, зависшие на I/O.
- st (steal) — на виртуальных серверах: время, которое гипервизор отдал другим виртуальным машинам на том же физическом хосте вместо вас. Высокий steal при низкой собственной загрузке — это чужая нагрузка, съедающая ваш тайм-слот.
Ключевая ловушка: iowait может быть *низким* даже когда диск — реальное узкое место. Если на сервере несколько ядер, а тормозящий процесс — однопоточный и просто ждёт диск, остальные ядра будут показывать честный idle, а не iowait: iowait считается по всей системе усреднённо и легко «размазывается» на многоядерных машинах. Поэтому смотреть нужно не только на агрегированный %iowait, но и на состояние конкретных процессов:
ps -eo pid,stat,comm | grep ' D'
Если видите процессы в состоянии D, они прямо сейчас ждут завершения операции ввода-вывода, которую нельзя прервать сигналом — и пока она не завершится, этот процесс не сдвинется с места, сколько бы свободных ядер рядом ни стояло.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверДиск: узкое место, которое прячется за низкой загрузкой
Даже одно быстрое устройство хранения не гарантирует быструю обработку запросов, если операции идут последовательно, а не параллельно. У NVMe и SSD огромная глубина очереди — они рассчитаны на десятки параллельных запросов одновременно, а не на один поток, который читает по одной странице и ждёт ответа. Если приложение (или конкретный воркер) обращается к диску синхронно и по одному запросу за раз, оно не использует и десятой доли возможностей накопителя — и при этом сам диск в iostat может показывать умеренную утилизацию, а очередь запросов к приложению всё равно растёт. Разбор этого эффекта — в статье про очереди NVMe и почему один поток не выжимает из накопителя всё, на что он способен.
Что смотреть:
iostat -x 1
Обращайте внимание на %util (доля времени, когда устройство было занято обслуживанием запросов), avgqu-sz/aqu-sz (средняя глубина очереди) и await (среднее время ожидания запроса, включая время в очереди). Высокий await при невысоком %util — характерный признак того, что запросы идут не параллельно, а последовательно, упираясь в латентность каждой отдельной операции, а не в пропускную способность устройства.
Практическое следствие: если узкое место — диск, поднятие числа ядер CPU или увеличение оперативной памяти под кэш процессов ничего не даст. Помогает либо параллелизация обращений к диску (несколько воркеров вместо одного), либо снижение числа синхронных операций (батчинг записи, кэш перед диском, вынос логов на отдельный том), либо переход на накопитель с более низкой латентностью операции.
Сеть: время, которое съедают не байты, а обмен сообщениями
Второе частое узкое место — не пропускная способность канала, а число round-trip’ов, которые нужно сделать, прежде чем запрос будет обработан до конца. Каждое обращение к базе данных, к внешнему API, к кэшу по сети — это минимум один цикл «отправить — дождаться ответа». Если для одного пользовательского запроса приложение делает десятки последовательных обращений к базе (классическая проблема N+1-запросов), то итоговое время ответа складывается не из вычислительной сложности, а из суммы задержек сети между приложением и базой, даже если каждая задержка сама по себе небольшая.
Это тот случай, когда CPU обоих серверов — и приложения, и базы — может простаивать: оба ждут пакетов друг от друга. top покажет низкую загрузку на обеих машинах, а пользователь всё равно получит медленный ответ.
Что смотреть:
ss -s
ss -tn state established
Большое количество TCP-соединений в состояниях, отличных от established, разрастающийся backlog на прослушивающем сокете, или рост числа коротких соединений вместо переиспользования — всё это признаки того, что накладные расходы на установление и разрыв соединений (TCP-хендшейк, TLS-хендшейк) начинают доминировать над временем полезной работы. Если сервис общается с базой или кэшем через одно и то же соединение на каждый запрос вместо постоянного пула — на этом можно потерять заметную часть времени ответа просто на рукопожатиях, ещё до того как выполнится сам запрос.
Блокировки и однопоточная модель обработки
Даже если и диск, и сеть в порядке, узким местом может быть сама модель обработки внутри приложения. Два частых сценария:
Однопоточный обработчик событий. Модели вроде event loop в Node.js или worker-процесса, который обрабатывает запросы по одному, отлично справляются с вводом-выводом (пока идёт ожидание сети или диска, поток свободен для другой работы), но если в обработчике встречается тяжёлая синхронная CPU-операция — парсинг большого JSON, хеширование, регулярное выражение с катастрофическим бэктрекингом — она блокирует *весь* event loop целиком, пока не завершится. В этот момент остальные ядра сервера могут быть полностью свободны: проблема не в нехватке CPU-мощности, а в том, что доступная мощность физически не может быть распараллелена между запросами в рамках одного потока.
Блокировки в базе данных или в самом приложении. Транзакция, держащая блокировку на строке или таблице дольше необходимого, заставляет остальные запросы, которым нужна та же блокировка, просто стоять и ждать — независимо от того, сколько свободных ядер на сервере базы. Здесь диагностика уже не про CPU и не про диск, а про то, кто кого блокирует и как долго. Разбор конкретного случая с очередью на блокировках — в статье блокировки в базе: кто кого ждёт.
Общий признак обоих сценариев: растущее время ответа и очередь запросов при спокойных графиках CPU, диска и сети. Если ни диск, ни сеть не показывают аномалий, а задержка всё равно растёт — вероятная причина в сериализации работы внутри самого приложения, а не в инфраструктуре под ним.
Ограниченный пул соединений: когда очередь растёт из-за лимита, а не из-за нагрузки
Отдельный и очень частый случай — пул соединений к базе данных, к очереди сообщений или к внешнему сервису настроен на фиксированное число одновременных соединений (например, 10 или 20). Пока запросов немного, всё работает быстро: свободное соединение находится сразу. Как только число одновременных запросов превышает размер пула, новые запросы начинают вставать в очередь *на получение соединения* — ещё до того, как они реально начнут выполняться. С точки зрения CPU в этот момент ничего не происходит: приложение просто ждёт свободный слот. top покажет простаивающий процессор, а метрика времени ответа приложения — растущие цифры, потому что часть этого времени — не выполнение запроса, а стояние в очереди перед ним.
Отличить эту ситуацию от «сервер не справляется по мощности» помогает конкретная метрика — время ожидания соединения из пула (большинство ORM и пул-менеджеров её экспортируют отдельно от времени выполнения запроса). Если оно растёт вместе с общим временем ответа, а исполнение самих запросов остаётся стабильным — дело в размере пула, а не в производительности базы или сервера. Что здесь устроено и как правильно выставлять лимиты, разобрано в статье что такое connection pool и зачем он нужен.
Важный нюанс: бездумное увеличение размера пула не всегда помогает и иногда вредит. Если узкое место — сама база (её собственный CPU, диск или блокировки), то увеличение числа одновременных соединений от приложения просто переносит очередь внутрь базы, где конкуренция за ресурсы может обойтись дороже, чем ожидание снаружи. Поэтому размер пула стоит подбирать, отталкиваясь от реальной пропускной способности того, к чему вы подключаетесь, а не только от нагрузки со стороны приложения.
Чек-лист, с которого стоит начинать диагностику «сервер простаивает, но не успевает», прежде чем добавлять ядра или память:
| Что проверить | Команда | На что смотреть | |
|---|---|---|---|
| Процессы в ожидании I/O | `ps -eo pid,stat,comm \ | grep ' D'` | Есть ли процессы в состоянии D |
| Очередь и задержка диска | iostat -x 1 | await, aqu-sz при умеренном %util | |
| Состояние сетевых соединений | ss -s, ss -tn | Много соединений не в established, рост коротких сессий | |
| Load average против числа ядер | uptime, nproc | Load average заметно выше числа ядер при низком %CPU | |
| Блокировки в базе | системные представления БД (например, pg_locks в PostgreSQL) | Кто держит блокировку и как долго | |
| Ожидание в пуле соединений | метрики пула на стороне приложения | Время ожидания слота отдельно от времени выполнения запроса |
Ни один из этих пунктов не заменяет остальные — узкое место редко бывает единственным и постоянным, оно может смещаться: сегодня это диск под ночным бэкапом, завтра — исчерпанный пул при всплеске трафика. Смысл чек-листа не в том, чтобы найти один универсальный виновник, а в том, чтобы не останавливаться на CPU, если он не даёт ответа.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Если добавить ядра CPU, поможет ли это, когда сервер и так простаивает?
Как правило нет. Если процессор не является узким местом, дополнительные ядра не ускорят операции, на которые он и так не тратит время — диск, сеть, блокировки или ожидание в пуле соединений останутся такими же. Смысл добавлять CPU есть только тогда, когда диагностика показала, что именно вычисления — реальный ограничитель.
Почему load average может быть выше числа ядер, если CPU не загружен на 100%?
Потому что в Linux load average учитывает не только процессы, готовые исполняться на процессоре, но и процессы в состоянии непрерываемого ожидания (обычно ввода-вывода). Высокий load average при низком %CPU — это почти всегда сигнал ждать не вычислений, а диска или другого ресурса, а не признак перегруженного процессора.
Как быстро понять, диск это или сеть, не разворачивая полноценный мониторинг?
Начните с ps -eo pid,stat,comm | grep ' D' — если там есть процессы, вероятно, дело в диске или в файловой системе. Дальше iostat -x 1 покажет, растёт ли await при обращениях к конкретному устройству. Если процессов в D нет, а задержка всё равно есть — переходите к ss -s и логам приложения на предмет медленных сетевых обращений.
Может ли проблема быть одновременно и в CPU, и в чём-то ещё?
Да, и это частый случай под смешанной нагрузкой: например, ночной бэкап забирает пропускную способность диска и одновременно поднимает нагрузку на CPU из-за сжатия. В таких ситуациях полезно смотреть на приоритеты фоновых задач по вводу-выводу отдельно от приоритетов CPU — это разные механизмы планирования, и настраивать их нужно по отдельности.
Стоит ли доверять только графикам мониторинга или проверять руками через SSH?
Готовые дашборды хороши для отслеживания трендов, но при разборе конкретного зависания полезно зайти на сервер и посмотреть состояние процессов и очередей в реальном времени — агрегированные по времени графики иногда сглаживают короткие, но критичные всплески ожидания, которые и создают ощущение «сервер не успевает» при формально низкой средней загрузке.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →