Skip to content

feat(performer): run WP-CLI via php with an opcache file cache - #72

Open
luismulinari wants to merge 1 commit into
trunkfrom
opcache-file-cache-cli
Open

feat(performer): run WP-CLI via php with an opcache file cache#72
luismulinari wants to merge 1 commit into
trunkfrom
opcache-file-cache-cli

Conversation

@luismulinari

@luismulinari luismulinari commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Goal

The main idea behind this PR is to remove the complexity of the PHP-FPM path. Today the runner invokes WP-CLI through a FastCGI shim (processCommandWithFPM/var/wpvip/fpm-cron-runner.php) that fakes a CLI environment inside an FPM worker. That shim exists for one reason: it was the only way to get opcache for WP-CLI, since a fresh php process recompiles WordPress and every plugin on each call (Automattic/Cron-Control#208).

It keeps causing trouble, because customer code runs inside the response path: long headers wedging the response read (PLTFRM-2424, #62), output escaping ob_start() (Cron-Control#211), FPM worker limits and timeouts applying to cron, and exit codes turned into HTTP statuses. None of that is fixable while cron runs through FPM.

This PR makes the plain exec path fast enough that the shim is no longer needed, so it can be turned off (-fpm-url empty) and later deleted along with the gofast dependency, the two FPM flags, the FPM metric, and the devcontainer shim.

What changes

Without -fpm-url, processCommand now always runs WP-CLI as:

php -d opcache.enable_cli=1 -d opcache.file_cache_only=1 \
    -d opcache.file_cache=<tmpdir>/cron-control-runner-opcache \
    <wp-cli-path> <args...>

Each short-lived process loads precompiled opcodes from disk instead of compiling from source. Timestamp validation is left at its default, so a changed file is recompiled and the cache never serves stale code. The cache directory is created under the OS temp dir at startup; if that fails, the error is logged and php runs uncached.

  • php is resolved from PATH, the same way the wp shebang already does it.
  • Because php runs the script explicitly, -wp-cli-path may also point at an extracted wp-cli tree's php/boot-fs.php, which skips the per-call phar open and signature check (~10 ms).
  • The FPM path and Remote WP-CLI (remote.go) are untouched.
  • No new flags. The behaviour is unconditional for the non-FPM path.

Measurements

n=40 min=301ms p50=357ms max=448ms mean=358ms // php-fpm shim
n=40 min=846ms p50=864ms max=910ms mean=864ms // wp-cli no opcache
n=40 min=455ms p50=466ms max=557ms mean=468ms // wp-cli (phar) with opcache enabled
n=40 min=436ms p50=445ms max=456ms mean=445ms // wp-cli (raw PHP files) with opcache enabled

Testing

  • go test ./performer/ ./orchestrator/ (new tests cover the exact argument list, the PATH default, cache dir creation, and a processCommand round trip through a fake php shim, so php is not required on CI).
  • Built for linux and ran the binary against a dev-env site with php.ini untouched: the cache dir was created and populated by the runner alone, events were fetched and executed, no stderr output.
  • go build ./... needs GOOS=linux; the darwin failure in remote.go is pre-existing.

To try it locally: run the runner without -fpm-url, then check that $TMPDIR/cron-control-runner-opcache fills with .bin files after the first list-due-batch.

Follow-ups

  1. Roll out with -fpm-url empty on one deployment and compare the existing get_site_events / run_event latency histograms.
  2. Remove processCommandWithFPM, the FPM flags and metric, gofast, and the devcontainer shim once the soak looks good.

Without -fpm-url, every WP-CLI invocation spawned a fresh php process that
recompiled WordPress and every plugin from source. On a large customer
codebase that compile step was ~0.7s of a 1.4s list-due-batch call, which
is the reason the php-fpm shim was introduced in the first place.

processCommand now always executes:

  php -d opcache.enable_cli=1 -d opcache.file_cache_only=1 \
      -d opcache.file_cache=<tmpdir>/cron-control-runner-opcache \
      <wp-cli-path> <args...>

so each process loads precompiled opcodes from disk. Timestamp validation
is left at its default so a changed file is always recompiled. The cache
dir lives under the OS temp dir and is created at startup.

Measured on a dev-env site (p50 per list-due-batch): plain wp-cli 221ms,
php-fpm shim ~130ms, wp-cli with file cache ~93ms. On a large codebase
the plain call dropped from 1.39s to 0.71s.

The FPM path and remote WP-CLI are unchanged. Running php explicitly also
lets -wp-cli-path point at an extracted wp-cli boot-fs.php, which avoids
the per-call phar open and signature check.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant