pm.max_children is how many requests the pool runs at once; the next one waits in the socket queue. Size it twice and take the smaller answer. Memory first: measure workers after they have served real pages, and use PSS, which splits shared pages (OPcache, the binary) between processes, not RSS, which counts them in full for every worker:
$ for p in $(pgrep -f 'php-fpm: pool shop'); do
> pss=$(awk '/^Pss:/{print $2}' /proc/$p/smaps_rollup)
> echo "pid $p rss $(ps -o rss= -p $p) kB pss $pss kB"
> done
pid 249847 rss 42972 kB pss 11037 kB
pid 249848 rss 42248 kB pss 10313 kBWith 2 GB for PHP, RSS suggests 48 workers and PSS 186, but one heavy report can take a worker to its memory_limit, so budget (RAM for PHP) / (PSS + typical request peak). Latency second. By Little's law, throughput = workers / time per request, so a page that waits 50 ms on MySQL 524 (usleep(50_000) here) serves 20 requests per second per worker. With 64 clients:
| pm.max_children | Requests/s | p50 | p99 |
|---|---|---|---|
| 4 | 71 | 810 ms | 1.53 s |
| 16 | 307 | 203 ms | 315 ms |
| 64 | 1,237 | 51 ms | 53 ms |
Waiting workers use no CPU, so throughput tracked the worker count. CPU-bound pages top out at the core count instead. Raise max_children until max children reached (FPM Timeouts and Status) stops rising.