Skip to content

Bound the dependents/suggesters listing queries with a statement timeout - #1842

Open
Seldaek wants to merge 1 commit into
mainfrom
perf/dependents-statement-timeout
Open

Bound the dependents/suggesters listing queries with a statement timeout#1842
Seldaek wants to merge 1 commit into
mainfrom
perf/dependents-statement-timeout

Conversation

@Seldaek

@Seldaek Seldaek commented Sep 7, 2026

Copy link
Copy Markdown
Member

Both listings materialise every row for the required package name in a derived table with no LIMIT, join package, then sort the whole joined set before applying LIMIT/OFFSET — so the cost is proportional to the total number of dependents regardless of which page is asked for. There was no statement timeout anywhere (the only timeout configured for Doctrine is PDO::ATTR_TIMEOUT, which is the connect timeout), so a pathological sort held a PHP-FPM worker until the gateway killed it — up to 37s observed.

On whether this causes problems

Worth being straight about the size of the win: this is a stability guard, not a throughput win. Per the percentile data (396k requests: p50 9ms, avg 26ms, p95 84ms, p99 321ms, p99.9 1.29s, max 37.1s), the band above 5s is a handful of requests, so the aggregate CPU saving is negligible. What it buys is bounding worker occupancy, which matters for surviving the loss of one node at peak rather than for average CPU.

5s sits far above p99.9, so the requests it cuts short are precisely the ones already holding workers longest. A request that would have completed in 6s now errors — that's the trade.

Also correcting my earlier read of this endpoint: the failures on it are 403s from the page > 3 anonymous gate, i.e. that guard working as designed on crawlers. They were never timeouts.

Degradation

MySQL reports the abort as error 3024, which Doctrine surfaces as a DriverException. Both actions now return a 503 (JSON for the .json format, plain text otherwise) instead of letting it become a 500.

Verification

composer phpstan clean (phpstan-dba validates the hinted SQL), full suite green. New repository tests execute the hinted queries against real MySQL 8.4 — an invalid hint would be a syntax error, not a silently ignored comment — and cover ordering, the type filter and pagination. New controller tests cover the 503 degradation across both actions and both formats; confirmed they fail without the catch.

Both listings materialise every row for the required package name in a derived table
with no LIMIT, join package, and sort the whole joined set before applying
LIMIT/OFFSET, so the cost is proportional to the total number of dependents regardless
of which page is asked for. There was no statement timeout anywhere - the only timeout
configured for Doctrine is PDO::ATTR_TIMEOUT, which is the connect timeout - so a
pathological sort held a PHP-FPM worker until the gateway killed it, up to 37s observed.

This is a stability guard rather than a throughput win: 99.9% of these requests finish
within 1.3s, so the aggregate saving is small. What it buys is bounding worker
occupancy, which matters for surviving the loss of one node at peak.

5s sits far above the p99.9, so the requests it cuts short are the ones already holding
workers longest. MySQL reports the abort as error 3024, which Doctrine surfaces as a
DriverException, so both actions now degrade to a 503 instead of a 500.
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