Skip to content

Make the concurrent worker count configurable - #83

Open
joehoyle wants to merge 1 commit into
masterfrom
configurable-max-workers
Open

Make the concurrent worker count configurable#83
joehoyle wants to merge 1 commit into
masterfrom
configurable-max-workers

Conversation

@joehoyle

Copy link
Copy Markdown
Member

Problem

The Runner's worker count is hardcoded to 4 and there is no way to change it. bin/cavalcade accepts no options beyond a WordPress path, Runner::instance() calls new static() with no arguments, and nothing in the codebase reads the environment. The only way to run fewer workers today is to patch the file or bypass the singleton with a custom entry script.

Each worker spawns a full WordPress process, so this value is what effectively sets the Runner's peak memory use. On a memory-constrained host that matters a lot: 4 concurrent workers can exceed available RAM, at which point the host starts swapping.

We hit this on a small ECS container instance. The Cavalcade task was driving ~2,000 page-sized writes per second to a swapfile on the instance's EBS volume — effectively all of that instance's write IOPS, enough to exhaust its EBS I/O credit balance and leave the CPU sitting at 80% iowait. Cron throughput from 4 workers rather than 2 was worth far less than the I/O it was costing, but there was no way to make that trade.

Proposed solution

Read the default for max_workers from a CAVALCADE_MAX_WORKERS environment variable so a deployment can match the worker count to the host it runs on.

Design decisions worth reviewing:

  • Environment variable rather than a CLI flag. The motivating case is containers, where the command is frequently fixed by the orchestrator (or a symlink, as in our PHP images) while environment variables are ordinary per-deployment config. It also avoids changing bin/cavalcade's argument handling or the Runner::instance() signature, both of which are public surface.
  • Default unchanged at 4, extracted to a DEFAULT_MAX_WORKERS constant. Deployments that set nothing behave exactly as before.
  • Explicit constructor options still win over the environment, so new Runner( [ 'max_workers' => N ] ) keeps working and the env var acts only as the default.
  • Invalid values fall back to the default, with a warning on STDERR. Being strict here is deliberate rather than defensive padding: an unvalidated 0, -1 or abc would leave max_workers at a value the limit check never matches, and the Runner would spawn workers without bound. Silently ignoring a bad setting is much safer than that.
  • Limit check relaxed from === to >=. Now that the value can come from outside, exact equality is fragile — a numeric string would never match count() and the limit would never fire. >= is equivalent when the type is right and safe when it isn't.

Testing

Verified by constructing the Runner directly across a range of values:

CAVALCADE_MAX_WORKERS Result
unset 4
"" 4
1, 2, 8 1, 2, 8
0, -3, abc, 3.5 4, warning on STDERR
2 + new Runner( [ 'max_workers' => 7 ] ) 7 (explicit option wins)

Unrelated, but worth flagging

I could not run vendor/bin/phpcs against this change, because the repo's dev toolchain no longer installs:

  • composer.json requires humanmade/coding-standards: 1.0.0 but composer.lock pins v0.7.0, so composer install fails outright.
  • Resolving that pulls dealerdirect/phpcodesniffer-composer-installer ^0.6.0, which requires composer-plugin-api ^1.0 and PHP ^5.3|^7 — i.e. Composer 1 and PHP ≤ 7.

CI is also Travis (travis-ci.org, shut down in 2021) pinned to composer self-update --1, so nothing is currently linting this repo. All pre-existing and out of scope here, but probably wants its own issue. Style in this change was matched to the surrounding file by hand.

Each worker spawns a full WordPress process, so the worker count sets the
Runner's peak memory use — but it has been hardcoded to 4 with no way to
change it. On memory-constrained hosts that overcommit pushes the host into
swap, and the resulting swap I/O costs far more than the extra cron
throughput is worth.

Read the default from CAVALCADE_MAX_WORKERS so a deployment can match the
worker count to its host. The default stays at 4 and explicitly-passed
constructor options still take precedence, so existing behaviour is
unchanged. Invalid values fall back to the default with a warning rather
than leaving the limit check unmatchable.

Also relax the worker limit check from === to >=, since an exact-equality
check against an externally-supplied value would silently never fire if
that value arrived as a numeric string.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G3gUDee9ohEwvEty2eE14M

@rmccue rmccue left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Impl needs to move, but conceptually makes sense.

In re where the config is passed, I would say whatever works best across containers + systemd, the two places that I suspect are the common deployment option. For systemd, best practice is env vars which are set via systemctl edit cavalcade + Environment=... lines - so I think env vars are the way to go here.

(Also, your PR description could use clean-up here as it seems to be AI generated, with questions more for you as the prompter; see https://journal.rmccue.io/498/pull-requests-are-for-humans/ :) )

Comment thread inc/class-runner.php
public function __construct( $options = [] ) {
$defaults = [
'max_workers' => 4,
'max_workers' => static::get_max_workers_from_env(),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The design of the constructor here is that options can be fed in intentionally; this should be connected to either env vars or CLI params in bin/cavalcade instead.

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.

2 participants