Static, dynamic, ondemand
Static, dynamic, ondemand
PHP-FPM gives every pool a pm mode, and it changes what pm.max_children
means. fpm-tune sizes that number for all three, but the number lands
differently depending on the mode, so it's worth 60 seconds on what each one does.
- static: the pool runs exactly
pm.max_childrenworkers, always. No scaling.max_childrenisn't a ceiling here; it's the running count. - dynamic: PHP-FPM keeps a warm floor and scales between it and
max_childrenas load comes and goes, usingstart_serversand themin/max_spare_serversband. - ondemand: no workers until a request arrives; each is spawned on demand and
killed after it goes idle.
max_childrenis the ceiling it may spawn up to.
The one thing to hold onto: for a static pool the number is the memory it commits right now; for an ondemand pool it's only a limit it might reach. Sizing the same integer means two different things.
What fpm-tune writes, per mode
| mode | what it writes | notes |
|---|---|---|
| static | pm.max_children only |
changing it changes the resident worker count immediately |
| ondemand | pm.max_children only |
sizes the ceiling; PHP-FPM still spawns on demand under it |
| dynamic | pm.max_children plus start_servers, min_spare_servers, max_spare_servers |
the spare band is re-derived from the new ceiling (≈25% to start, a 10-50% spare band) so PHP-FPM's own scaling stays coherent |
Two details that matter:
- Spare settings are dynamic-only. Writing
pm.start_serversinto a static or ondemand pool is a config error PHP-FPM refuses, so fpm-tune writes them for dynamic pools and nothing else. (This is exactly the kind of thing its sandbox validation catches before a reload, but it's cheaper never to write it.) - The learner is ondemand-aware. An ondemand pool sitting visible-but-empty isn't evidence its workers are cheap. It's just quiet. fpm-tune won't under-size a pool from a scrape that happened to catch it idle. The details are in measuring workers.
Why it doesn't just pick the mode for you
Because the right mode isn't a memory question, and fpm-tune only measures memory.
static-vs-dynamic-vs-ondemand is a trade between predictable RAM, latency, and
cold-starts, and the tie-breakers (do you have a latency SLO? do you want
memory that never moves? is this one of forty pools that are each hit twice an
hour?) live in your head, not in the numbers on the host. So fpm-tune sizes
inside the mode you chose and never rewrites pm itself. Its whole stance is to
keep PHP-FPM's own worker management on and get the ceiling right underneath it.
Here's the shape of the trade, so the choice is yours to make well:
| static | dynamic | ondemand | |
|---|---|---|---|
| idle memory | highest: every worker resident always | low: scales down to the floor | lowest: nothing when quiet |
| latency under load | best: workers always warm | good: warm floor absorbs bursts | worst: each burst pays a cold start |
| fits | steady, latency-sensitive, memory to spare | most pools; a safe default | many pools each rarely hit |
The one suggestion it will make
fpm-tune stays quiet about mode unless the measured shape points somewhere
clearly. When it does, plan prints a line under the table (and serve logs it
once), a nudge, never a change:
- A static pool holding idle workers. If its busiest moment left a lot of workers unused, static is paying to keep memory resident that nothing touched. dynamic or ondemand would hand that back between requests, unless you're keeping them warm for latency on purpose, which is a fine reason to ignore it.
- An ondemand pool that's queuing. Requests waiting on an ondemand pool means bursts are paying cold-start latency. A dynamic pool with a warm floor absorbs the burst instead.
It won't push a busy dynamic pool toward static, even though that can be the right move. Telling "steadily maxed out" from "spiked once" needs a sustained-load signal fpm-tune doesn't keep, and guessing there would just be noise. dynamic is a safe default; if you want static's predictable memory, that's a deliberate call.
Mode is not a security boundary
Worth saying plainly, since it comes up: the pm mode has nothing to do with
isolating one pool from another. That's user/group/chroot/open_basedir,
and it's orthogonal to how workers are scaled. What fpm-tune does guard is covered
in the trust boundary; mode choice is a
performance-and-memory decision, not a security one.