Skip to content

perf: cut nightly load and expose pool observability - #228

Merged
Tbaile merged 4 commits into
mainfrom
optimization
Aug 5, 2026
Merged

perf: cut nightly load and expose pool observability#228
Tbaile merged 4 commits into
mainfrom
optimization

Conversation

@gsanchietti

@gsanchietti gsanchietti commented Aug 5, 2026

Copy link
Copy Markdown
Member

Summary

  • nginx now answers basic-auth challenges directly for repository downloads, so credential-less requests from opkg/wget never boot PHP.
  • The php-fpm worker pool is sized for the nightly traffic peak, baked into the image instead of a manual patch that didn't survive rebuilds.
  • Added per-request timing to the php logs and a pool status page (loopback only), so pool saturation is visible instead of guessed at.

opkg/wget clients send no credentials on their first request and wait
for a WWW-Authenticate challenge before retrying. In the nightly
update window that made ~44k requests/hour boot the full framework
just to emit one header, roughly a third of all peak traffic doing no
useful work.

nginx now returns the challenge directly for
/repository/{community,enterprise}/* when no Authorization header is
present. Credentialed requests fall through to Laravel unchanged, so
ForceBasicAuth and the licence middleware still run.

Assisted-by: Claude Code:claude-sonnet-5
The image shipped the upstream default of pm.max_children = 5, which ceilings
throughput near 16 rps for this workload. The nightly update window peaks at
37 rps, so the pool was oversubscribed for hours every night and clients
abandoned ~13k connections an hour waiting.

32 workers at ~50MB each is ~1.6GB on a 7.7GB host, and the workload is I/O
bound on S3 and the licence endpoint, so exceeding the 4-core count is correct
here. Replaces a transient in-container sed patch that would not have survived
this image rebuild.

Assisted-by: Claude Code:claude-sonnet-5
Neither the nginx access log nor the fpm access log carried a timing field, so
production request latency was not measurable from logs - diagnosing the
nightly slowdown required reconstructing it from request rates and status
codes. %{milli}d in access.format fixes that, request_slowlog_timeout adds
backtraces for outliers, and pm.status_path exposes the pool's own counters.

Assisted-by: Claude Code:claude-sonnet-5
The pool's own counters - listen queue depth, active processes, and especially
"max children reached" - are the direct measurement of saturation, but nothing
routed to pm.status_path. Restricted to loopback so it is readable via
podman exec and denied through the published port.

conf.d/status.conf's "listen localhost" server (added for the container
HEALTHCHECK's /status endpoint) resolves to both 127.0.0.1 and ::1 at nginx
startup, so it - not default.conf's catch-all - actually answers all loopback
traffic on port 80 regardless of path or Host header. The /fpm-status location
therefore has to live there to be reachable at all; default.conf's copy still
matters for requests arriving via the published port, which land on the
catch-all and must be denied rather than 404ing.

Assisted-by: Claude Code:claude-sonnet-5
@gsanchietti gsanchietti changed the title perf: cut nightly PHP boot load and expose pool observability perf: cut nightly load and expose pool observability Aug 5, 2026
@gsanchietti
gsanchietti requested a review from Tbaile August 5, 2026 07:19
@Tbaile
Tbaile merged commit 8e8b037 into main Aug 5, 2026
11 checks passed
@Tbaile
Tbaile deleted the optimization branch August 5, 2026 07:33
@Tbaile Tbaile mentioned this pull request Aug 5, 2026
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