thecodingmachine/php v5 documentation GitHub

Tuning simulator: mod_php, PHP-FPM or FrankenPHP

Pick the situation closest to yours: each way to serve PHP shows what its default settings give, and the settings optimized for your situation. It is a model calibrated on our benchmarks, not a load test: use it to choose and to start, then measure.

1Your situation

Application
Users at the busiest moment
Simultaneous users of the application, all servers together.
Average response time
Of a PHP request, as measured in production (APM, access log) or by your profiler in prod mode.
Memory per request
Average peak of a request (memory_get_peak_usage(), profiler).
In front of the containers
DesktopMobile
Mobile round trips are longer (≈ 50 ms against 20 ms): they hold the connections longer. European mix (50%)
Production servers
CPUs per container
Memory per container
Containers
Application details: waiting time, heavy requests, framework boot, pages
Database, cache, HTTP APIs: time without CPU.
An export, a report, an import: counted once in the worst-case memory.
Kernel, container, service providers: what FrankenPHP's worker mode saves.
The page and its XHR / API calls.
Not in the browser cache yet. 0 with a CDN.

2Settings and impact

What the numbers mean
Max users
With a fixed number of containers: the simultaneous users they serve together, with pages in under 1 s (or twice the response time, for slow requests) and the CPU below 90%. The white mark is your number of users.
Containers needed
With an automatic number of containers: how many are needed for your users, with 25% of headroom, and the users they serve at most.
Page time
What one of your users waits for a page: PHP, the wait for a free PHP worker, and the network round trips to open connections.
Twice the users
A rush of 5 minutes at twice your users (a newsletter, Monday 9 am): the page time at its end. When the containers cannot follow, the requests queue up and the time grows; above 60 s, the proxies and the browsers give up. Out of memory: the kernel kills processes.
Memory, worst case
Every PHP worker busy with an average request, plus one heaviest request.
Default / Optimized / Custom
The default settings of the v5 images, the settings optimized for your situation, and your own (“Custom”, from the optimized ones).

3What to choose

Two ways to run PHP

mod_php (apache variant) runs PHP inside each Apache process (mpm_prefork): a process is held for the whole life of a connection, PHP or not: static files, TLS handshakes, idle keep-alive connections. Each process costs the memory of a PHP request.

PHP-FPM (fpm variant, PHP_FPM_WEB_SERVER=apache) keeps the connections in Apache (mpm_event), with cheap threads; a PHP worker is only busy while PHP runs, and the number of workers is set for the CPU and the memory, not for the connections.

Behind a reverse proxy

The proxy terminates HTTPS and keeps the connections of the browsers: the container only receives short requests. Well tuned (keep-alive off, enough processes for the memory), mod_php serves about as many users as PHP-FPM, as measured in our benchmark. The difference is in the margins: the memory of the processes, and the behavior when twice the users come. Keep-alive must stay off for mod_php: the proxy parks its idle connections on the processes (p99 of 14 s measured).

HTTPS served by the container

Every connection of a browser holds a mod_php process: with keep-alive, the idle connections (up to 6 per browser) hold processes for seconds; without it, each request pays a TCP and TLS handshake, in the heavy process and in the page time of the users, more on mobile networks. mod_php cannot serve HTTP/2. PHP-FPM keeps one HTTP/2 connection per browser in Apache: +63% of pages in our benchmark with light pages, less with heavy pages where PHP dominates.

Memory and rushes

The v5 default of the apache variant allows 150 processes: with requests of 100 MB, a rush starts them all and exhausts the memory (208 processes killed in our benchmark with 48 MB pages). PHP-FPM caps the PHP workers independently of the connections: in a rush, the extra requests wait in a queue instead of starting processes, and fewer processes compete for the CPU (+16% to +29% of pages served in our benchmark).

nginx instead of the built-in Apache

The fpm variant can also run without its Apache, behind your own nginx container: PHP-FPM is configured the same way, nginx serves the static files for a bit less CPU. It is worth it with many static files and no CDN; otherwise the built-in Apache keeps a single container.

FrankenPHP (v6)

Planned as a variant of the v6 images. Its worker mode keeps the application booted between requests: it saves the boot of the framework, a few percent of a heavy request, far from the ×3 of “hello world” benchmarks. PHP runs in threads (ZTS build), a few percent slower. WordPress does not run in worker mode. Not measured yet: estimate only.

Page time when the number of users per container grows, with the settings selected in each card. Dashed: FrankenPHP preview. The vertical line is your users per container, the dotted one the page time target.

4Configuration


  
  
Model
  • CPU per page = PHP requests × CPU time (response time minus waiting) + static files × 0.3-0.4 ms + new connections × (0.1 ms, + 0.9 ms for a TLS handshake); costs measured on a 2.6 GHz core, close to a cloud vCPU.
  • CPU sharing stretches the CPU time by 1 / (1 − CPU usage). A PHP request holds a worker for its waiting time plus its stretched CPU time; the wait for a free worker uses the Erlang C formula.
  • mod_php processes are also held by static files and, with HTTPS, by the handshakes (2 round trips) and, with keep-alive, by the idle connections of the browsers (up to 6, for KeepAliveTimeout).
  • Memory: base + per worker 6 MB plus the request memory, plus one heaviest request. Rush: the CPU capacity reduced by the contention of the PHP processes (calibrated on the measured peaks); the requests above it queue up.
  • Optimized settings: the most users within 90% of the container memory, then the best rush, then the fewest processes. In Custom, the settings you change are kept and the other ones are optimized around them.