Event MPM thread pool limits and keep-alive latency under concurrent load
0 reputation · 18 Sept 2020, 02:06 UTC
0 reputation · 18 Sept 2020, 02:06 UTC
Apache HTTP Server running the Event MPM exposes a fixed worker thread pool governed by MaxRequestWorkers and ThreadsPerChild. When simultaneous requests exceed that pool, additional connections wait in the operating system backlog, producing latency that is absent at low concurrency. Keep-alive handling adds a second constraint: KeepAliveTimeout holds worker threads and sockets open, so under concurrent clients the effective capacity for new request processing can diminish before the numeric thread ceiling is reached.
The unresolved decision is how to balance these documented limits for a specific workload. Reducing KeepAliveTimeout frees threads sooner but may increase connection churn; raising MaxRequestWorkers expands concurrency but requires matching operating system file-descriptor and thread limits to avoid failures. Because the optimal mix depends on traffic patterns, client behavior, and hardware, Apache does not prescribe a single configuration.
Under the general Event MPM model, where distribution defaults and compiled limits vary by version, what observable metrics distinguish thread-pool queuing from keep-alive hold time? Which configuration boundary should be evaluated first when tail latency appears only under concurrency? How can the interaction between MaxRequestWorkers and KeepAliveTimeout be assessed safely before production changes?
34775 reputation · 18 Sept 2020, 10:30 UTC
To distinguish between thread-pool queuing and keep-alive hold time, monitor the mod_status page (server-status) and the OS-level TCP stack. The key difference lies in whether the connection is actively occupying a worker thread or merely occupying a socket.
BusyWorkers reaches the MaxRequestWorkers limit. At this point, new connections cannot be accepted by Apache and accumulate in the operating system's TCP listen queue. You can verify this using ss -ln or netstat -ln; a growing Recv-Q on the HTTP port confirms that the application is unable to accept connections fast enough.Keep-Alive state (visible in mod_status) does not directly cause thread exhaustion, but it does consume file descriptors and memory.When tail latency appears only under concurrency, evaluate MaxRequestWorkers first. This is the hard ceiling for concurrent request processing. If this limit is hit, requests queue at the OS level, creating the latency spikes described.
Only after ensuring MaxRequestWorkers is sufficient for your peak concurrent request volume should you tune KeepAliveTimeout. Reducing this timeout is a secondary optimization to reclaim memory and file descriptors, not a primary fix for thread-pool queuing.
To assess the interaction between these settings without risking production stability, use a staged load test with the following steps:
BusyWorkers hits MaxRequestWorkers. Observe the point where tail latency spikes and check the OS Recv-Q.MaxRequestWorkers, verify the OS file descriptor limit using ulimit -n. The limit must be higher than MaxRequestWorkers plus other system overhead to avoid "Too many open files" errors.MaxRequestWorkers, vary KeepAliveTimeout (e.g., from 5s down to 2s) and monitor the rate of new TCP handshakes via netstat to ensure connection churn does not introduce new latency.Missing Diagnostic Detail: To refine this recommendation, please provide the current ulimit -n value and the average memory footprint per Apache child process.
Use comments to ask for clarification. Post a solution as an answer.
34,775 reputation · 18 Sept 2020, 07:03 UTC
Apache Event MPM (2.4+) under concurrent load: when MaxRequestWorkers * ThreadsPerChild nears the OS file-descriptor limit, keep-alive connections can exhaust available threads before the thread-pool ceiling is reached. The earliest indicator is the idle-worker-to-keep-alive ratio visible in server-status: a falling idle count while keep-alive connections rise signals hold-time pressure, not yet queuing. Before adjusting MaxRequestWorkers, verify ulimit -n and consider lowering KeepAliveTimeout to reclaim threads, then re-run a wrk load test to observe 95th-percentile latency shifts.