CPU Idle While PHP Requests Wait: How to View the PHP-FPM Queue
We examine a specific PHP-FPM pool during slowdowns: which metrics to compare, why an idle CPU doesn't rule out waiting, and what to do if the queue is empty. Includes a reproducible educational calculation.

In this article
Карточка товара открывается несколько секунд, а средняя загрузка процессора остаётся небольшой. Это ещё не доказательство, что серверу хватает ресурсов. Динамический запрос может ждать свободный процесс PHP-FPM; уже занятые процессы в это время могут ожидать базу или внешний сервис.
Я бы начала с более узкого вопроса: стояли ли запросы в очереди именно того пула PHP, который обслуживает магазин, в момент задержки? Ни процент CPU, ни одно старое максимальное значение сами по себе на него не отвечают.
Сначала отделите ожидание процесса от выполнения кода
Пул — группа процессов PHP-FPM. Свободный процесс принимает запрос, занятый обслуживает предыдущий. Если свободных процессов нет, запросы могут ждать перед пулом. Такой участок задержки отличается от времени, которое уже получивший запрос процесс тратит на работу приложения.

Среднее значение CPU по серверу способно скрыть нагрузку отдельного ядра. Обратная ситуация тоже возможна: процесс PHP занят запросом, но большую часть времени ждёт ответ другой системы. Поэтому свободный процессор не равен свободному пулу.
Настройка pm.max_children ограничивает число одновременно обслуживаемых запросов. Для статического режима она задаёт число процессов, для динамического и запуска по требованию — верхнюю границу. Это описано в документации конфигурации PHP-FPM. Из этих правил не следует, что лимит нужно сразу увеличивать.
Что попросить у администратора во время замедления
Если статус FPM уже собирается внутренним мониторингом, возьмите данные оттуда. Иначе попросите администратора подготовить доступ только для разрешённых внутренних запросов. Статус содержит сведения о ресурсах и запросах: открывать его всем посетителям сайта ради проверки не нужно.
Смысл полей можно сверить с официальным описанием страницы статуса FPM. Для начала достаточно следующих значений:
Поле | Как использовать |
|---|---|
| Проверить, что выбран пул магазина. |
| Текущее ожидание свободного процесса. |
| Сколько процессов свободно и занято в этом снимке. |
| Исторический максимум; не текущая очередь. |
| Был ли достигнут предел; старое значение не датирует нынешний сбой. |
| Обнаружить перезапуск между измерениями. |
Сохраните несколько снимков в течение самого замедления и один после него. Рядом запишите время с часовым поясом, затронутый маршрут магазина, время ответа и ошибки за тот же интервал. Эти данные относятся к одному пулу; после перезапуска FPM его значения сбрасываются.
Для запроса администратору достаточно такой формулировки: «Во время медленного открытия карточки проверьте пул этого сайта: есть ли текущая очередь, остаются ли свободные процессы и достигается ли настроенный предел. Сохраните показатели вместе со временем ответа, чтобы сравнить их на одной шкале». Это запрос на наблюдение, а не на изменение конфигурации.
Учебная проверка: почему четыре процесса не всегда успевают
Чтобы проверить арифметику, при подготовке статьи мы выполнили отдельный расчёт непрерывного потока. В нём четыре процесса, равномерный вход 10 запросов в секунду, начальная очередь нулевая. Один запрос занимает процесс 0,5 секунды. Идеальная пропускная способность тогда равна 4 / 0,5 = 8 запросам в секунду, а очередь растёт на 2 запроса в секунду.
Расчёт дал 10 ожидающих запросов через 5 секунд и 20 через 10. Во втором варианте мы сохранили четыре процесса и тот же вход, но сократили занятость на запрос до 0,25 секунды. Расчётная способность стала 16 запросов в секунду, и очередь в этой модели не росла.

Проверку можно повторить в Python 3. Следующие строки выводят две расчётные длины очереди для каждого момента:
workers, incoming = 4, 10
for seconds in (0, 5, 10):
queues = [max(0, (incoming - workers / hold) * seconds) for hold in (0.5, 0.25)]
print(seconds, queues)
Вывод расчёта: для 0 секунд — [0, 0], для 5 — [10, 0], для 10 — [20, 0]. Модель намеренно не учитывает дискретные завершения, всплески, ошибки и изменение длительности запросов. Она показывает влияние времени занятости процесса. Чтобы прогнозировать реальный магазин, нужны его собственные измерения.
Как выбрать следующий шаг по наблюдениям
Наблюдение в момент задержки | Следующий шаг |
|---|---|
Очередь есть, свободных процессов нет, пул достигает своего предела | Разобрать длительность запросов и причины занятости процессов. Оценку лимита проводить вместе с памятью и базой. |
Очередь есть, число процессов ниже верхней границы | Проверить режим пула и появление новых процессов; не считать это доказательством слишком малого лимита. |
Очередь пула не видна, ответ всё равно медленный | Проверить выполнение приложения, базу, внешние вызовы и другие участки маршрута. |
Высок только старый максимум очереди | Собрать данные текущего эпизода; прошлый пик не объясняет нынешний. |
Нулевая очередь в одном снимке не исключает короткий пик между измерениями. И наличие очереди не сообщает, что именно задержало занятые процессы. Полезный результат проверки — временная связь между ожиданием и замедлением, а затем установление причины длительных запросов.
Увеличение числа процессов без расчёта может перенести ограничение на память или базу. Если диагностика действительно указывает на нехватку ресурсов, следующий этап — оценка ресурсов VPS по сценариям нагрузки. Там разобраны условия нагрузочной проверки и критерии масштабирования.
Передайте разработчику время эпизода, маршрут, снимки пула и связанные задержки базы или внешних вызовов. Если сервер и код обслуживают разные команды, заранее определите, кто продолжит диагностику: этот вопрос разобран в статье о границах администрирования и поддержке магазина. Тогда задача заканчивается понятным следующим действием, а не сообщением «CPU свободен».
A product card takes several seconds to open, yet average CPU usage remains low. This alone does not prove the server has sufficient resources. A dynamic request may be waiting for a free PHP-FPM process; meanwhile, already occupied processes might be waiting for the database or an external service.
I would start with a more specific question: were requests in the queue for the exact PHP pool serving the store at the moment of the delay? Neither CPU percentage nor a single historical maximum value answers this on their own.
First, separate process waiting from code execution
A pool is a group of PHP-FPM processes. A free process accepts a request; a busy one handles the previous request. If no free processes exist, requests may wait before the pool. This waiting period differs from the time a process already holding a request spends executing application code.

Average CPU usage across the server can mask the load on a specific core. The reverse situation is also possible: a PHP process is busy handling a request but spends most of its time waiting for a response from another system. Therefore, an idle processor does not equal an available pool.
The pm.max_children setting limits the number of requests served simultaneously. In static mode, it defines the number of processes; in dynamic mode and on-demand mode, it sets the upper limit. This is described in the PHP-FPM configuration documentation. These rules do not imply that the limit must be increased immediately.
What to ask the administrator during slowdowns
If the FPM status is already being collected by internal monitoring, retrieve the data from there. Otherwise, ask the administrator to prepare access restricted only to authorized internal requests. The status contains information about resources and requests; there is no need to expose it to all site visitors for verification purposes.
You can verify the meaning of the fields against the official FPM status page description. For a start, the following values are sufficient:
Field | How to use |
|---|---|
| Check that the store pool is selected. |
| Current wait time for a free process. |
| How many processes are free and busy in this snapshot. |
| Historical maximum; not the current queue. |
| Has the limit been reached? The old value does not date the current failure. |
| Detect a restart between measurements. |
Save several snapshots during the slowdown and one after it. Record the time with the time zone, the affected store route, the response time, and any errors for that same interval. These data points belong to a single pool; after an FPM restart, its values are reset.
For a request to the administrator, the following phrasing is sufficient: 'During the slow loading of the product card, check the pool for this site: is there a current queue, are there still idle processes, and is the configured limit being reached? Save the metrics along with the response time so they can be compared on the same scale.' This is a request for observation, not for configuration changes.
Training check: why four processes do not always keep up
To verify the arithmetic, we performed a separate continuous flow calculation while preparing the article. It involves four processes, a uniform input of 10 requests per second, and an initial queue of zero. One request occupies a process for 0.5 seconds. The ideal throughput is then 4 / 0.5 = 8 requests per second, and the queue grows by 2 requests per second.
The calculation yielded 10 pending requests after 5 seconds and 20 after 10. In the second variant, we kept four processes and the same input but reduced the request processing time to 0.25 seconds. The calculated capacity became 16 requests per second, and the queue in this model did not grow.

The check can be repeated in Python 3. The following lines output two calculated queue lengths for each moment:
workers, incoming = 4, 10
for seconds in (0, 5, 10):
queues = [max(0, (incoming - workers / hold) * seconds) for hold in (0.5, 0.25)]
print(seconds, queues)
Calculation output: for 0 seconds — [0, 0], for 5 — [10, 0], for 10 — [20, 0]. The model intentionally does not account for discrete completions, spikes, errors, or changes in request duration. It shows the impact of process busy time. To forecast a real store, its own measurements are needed.
How to choose the next step based on observations
Observation at the moment of delay | Next step |
|---|---|
There is a queue, no free processes, the pool has reached its limit | Analyze request duration and reasons for process occupancy. Evaluate the limit together with memory and the database. |
There is a queue, the number of processes is below the upper limit | Check the pool mode and the appearance of new processes; do not treat this as proof of an insufficient limit. |
The pool queue is not visible, yet the response remains slow. | Check the application execution, database, external calls, and other segments of the route. |
Only the old maximum queue value is high. | Collect data from the current episode; the previous peak does not explain the current one. |
A zero queue in a single snapshot does not rule out a short spike between measurements. The presence of a queue does not indicate what specifically delayed the busy processes. The useful result of the check is establishing a temporal link between waiting and slowdown, followed by identifying the cause of long-running requests.
Increasing the number of processes without calculation can shift the bottleneck to memory or the database. If diagnostics truly indicate a lack of resources, the next step is assessing VPS resources under load scenarios. That article covers the conditions for load testing and scaling criteria.
Provide the developer with the episode timestamp, route, pool snapshots, and associated database or external call delays. If the server and code are managed by different teams, determine in advance who will continue the diagnostics: this issue is covered in the article about administration boundaries and store support. This ensures the task concludes with a clear next action rather than a message stating 'CPU is free'.




Discussion 0
Share your experience and ask questions. Comments without links appear after editorial review.
No comments yet. Start the discussion.