Balancing pm.max_children for variable concurrent request patterns
0 reputation · 29 Apr 2021, 06:07 UTC
0 reputation · 29 Apr 2021, 06:07 UTC
PHP‑FPM’s fixed worker pool causes request queuing when concurrent requests exceed available workers, and the pm.max_requests limit triggers worker termination pauses that manifest as latency spikes under load.
The appropriate configuration of pm.max_children remains unclear for workloads with variable concurrency, while the default file‑based session locking can further serialize access and increase response times.
How should pm.max_children be set to accommodate bursty traffic without exhausting memory or hitting OS process limits?
Is there a practical way to monitor queue length and worker lifecycle events to adjust workers dynamically?
Can session locking be reduced through configuration changes without switching save handlers?
35775 reputation · 29 Apr 2021, 16:58 UTC
Set pm.max_children from measured worker memory, then cap it below the OS process limit. For bursty traffic, use pm = dynamic so idle capacity absorbs spikes without reserving every worker at all times.
When concurrent requests exceed available children, PHP-FPM queues requests in the listen socket. A low pm.max_requests recycles workers frequently, which can appear as latency spikes under load. File-based sessions add serialization because PHP locks the session file for the duration of each request.
pm.max_children is a hard ceiling on simultaneous workers per pool.pm = dynamic maintains spare workers between pm.min_spare_servers and pm.max_spare_servers, creating new workers only when demand rises.listen.queue grows; sustained depth means requests are waiting.session.save_handler = files locks session files per request and cannot be unlocked by configuration alone.ulimit -u) can prevent PHP-FPM from reaching pm.max_children.ps -o pid,rss,comm -C php-fpm
Use the median RSS as the average worker size.
ulimit -u minus reserved system processes./etc/php/8.2/fpm/pool.d/www.conf:pm = dynamic pm.max_children = [calculated value] pm.start_servers = [max_children / 2] pm.min_spare_servers = [max_children / 4] pm.max_spare_servers = [max_children * 3 / 4] pm.max_requests = 2000
pm.status_path = /status.json request_slowlog_timeout = 1s
curl -s http://localhost/status.json | jq '.listen.queue, .processes.active, .processes.idle'
A listen.queue consistently above zero during bursts means requests are waiting. Sustained depth above 5 suggests more workers or more server resources, provided memory and OS limits allow.
File-based session locking cannot be disabled while keeping session.save_handler = files. Lowering session.gc_maxlifetime affects garbage collection frequency, not the per-request lock duration. To remove file-lock serialization, switch to Redis or Memcached.
pm.max_children is below ulimit -u with headroom for other processes.free -m during load to ensure available memory stays positive.listen.queue during a burst; near-zero queue depth with stable memory indicates appropriate sizing.This assumes PHP-FPM 7.4 or later on Linux with one pool. Worker memory varies by application, so theoretical estimates are unreliable. The 20-30% buffer is a starting point, not a guarantee.
What is your measured peak concurrent request rate during the busiest hour? This determines whether the memory-based calculation needs adjustment or additional server capacity.
Use comments to ask for clarification. Post a solution as an answer.
7,130 reputation · 29 Apr 2021, 13:20 UTC
When sizing pm.max_children for variable concurrency, a practical starting point is to divide the server’s free RAM (after other services) by the typical PHP‑FPM worker memory footprint, which includes opcode cache and request overhead, then cap the result at the OS process limit (ulimit -u). This guards against memory exhaustion while respecting process‑count boundaries. The php‑fpm status endpoint’s ‘queue’ and ‘idle process’ metrics can then be monitored to confirm the pool isn’t backing up, and pm.max_requests set in the 500–1000 range helps cycle workers before accumulated drift causes latency spikes under bursty load.