Skip to content

get_fleet_overview's collection-health rollup times out at :35 on the largest store — phase-grid signature, discriminate compression-band contention vs plan before fixing #3735

Closed

Description

@erikdarlingdata

get_fleet_overview times out at :35 on the largest store — three for three, retry always succeeds

Observed on the largest production store over the first day of the 3.8.0-nightly.20260919.450 build: the fleet-overview tool's collection-health rollup was cancelled by the server's statement_timeout at 06:35, 11:35 and 12:35Z — the store log carries the cancellation with the statement (v_collection_log, GROUP BY server_id, collector_name). Every retry succeeded. Three occurrences at the same minute is a phase-grid signature, not a slow-query one.

What is VERIFIED at source (dev at f5cc82e)

HYPOTHESIS to discriminate before any fix

The :35 minute is where the once-a-day aggregate-compression band lands, and that band compresses the heaviest materializations first. A 20-second rollup over collection_log across 43 servers that normally finishes in a few seconds gets pushed past its budget by the compression job's I/O and lock-manager load, not by its own plan. Two cheap discriminators from the store side:

  1. Does the timeout recur only in hours when a compression job was running at :35 (timescaledb_information.job_history — read as the owner role, per get_store_metrics: the job_history block said 'recording' and never said whose eyes the rows are visible to — it now evaluates the view's ownership filter for its own reader and proves rows exist where it can (#3574) #3585 — or pg_stat_activity showing compress_chunk at that minute)? A 13:35 / 14:35 recurrence with a job running, and a quiet 21:35–00:35 (band idle), settles it.
  2. EXPLAIN (ANALYZE, BUFFERS) of the rollup with the tool's actual $1 on a quiet minute vs at :35 — if the plan is the same and only the time differs, it is contention, not the query.

If the hypothesis holds, the fix is scheduling, not SQL: either the rollup phase moves off :35 (the tool is on-demand, so the practical arm is the fleet cards' background refresh cadence), or the band moves off the minute the dashboard's own reads use — the same class as #3678 (three heaviest hypertables on consecutive minutes), and should be decided WITH it, after #3678's own re-measure tonight. If the hypothesis fails (timeouts at :35 with no job running), then the rollup deserves the #3647 treatment — measure, then a covering index on collection_log (collection_time, server_id, collector_name) if the plan shows a heap scan over the window's chunks.

Not a lane until discriminated. Requested of the monitor seat: the two reads above from the store, scrubbed, on the bus.

Activity

  1. erikdarlingdata commented on Sep 19, 2026

    @erikdarlingdata
    OwnerAuthor

    Solved at source: self-amplified fan-out of a window-independent rollup

    Live catch at 13:36:03Z on the largest store (monitor seat, bus 20260919T133817Z): the collection-health rollup was running three times concurrently — identical statement, same start, one per get_fleet_overview call the caller had raced in parallel with different hours_back values — each with two parallel workers, nine backends on collection_log in pure I/O waits while the collectors' flush band was mid-COPY. The slowest copy crossed the mcp role's server-side statement_timeout = 15 s; the retry landed after the flush drained and succeeded. Hit-vs-miss hours track write-band intensity, which explains 3 of 9.

    Cleared by the evidence: the plan (680 ms solo, healthy parallel aggregate); the #3611 compression band (wrong tables, ~0 s runs); the 15 s cap (doing its job — raising it would hide the fan-out).

    What is VERIFIED at source

    • GetFleetOverview(hours_back) (DarlingMcpFleetTools.cs:64) → GetFleetOverviewAsync → ReadFailingCollectorCountsAsync (DarlingFleetReader.cs:1266–1272), which runs FleetCollectionHealthSql over a fixed 7-day window (now.AddDays(-7)). The hours_back argument bounds only blocking/deadlock; the rollup's result is identical for every window the caller asks for. Three calls with three windows = three identical 7-day scans.
    • The web dashboard's fleet page calls the same read (DarlingWebEndpoints.cs:263), so browser auto-refresh + an agent's tick can stack the same way.

    Fix shape (lane's choice within this)

    Single-flight + short memo on the 7-day collection-health rollup inside DarlingFleetReader: concurrent callers share one in-flight scan; a result younger than the collection cadence (60 s is the floor of what could have changed — the rollup counts per-collector runs at 1–5 min cadences) is served from memory. The other reads in the overview stay as they are; the memo is process-local, so the MCP host and the web host each hold their own. EXPLAIN from the quiet arm is the acceptance: one scan per minute per host regardless of how many overview calls race.

    Caller-side hygiene, independent of the fix: an agent tick that wants three windows should call once with the widest and derive, or sequence the three — the rollup's cost is the same either way and the write band does not care about the caller's reasons.

  2. erikdarlingdata commented on Sep 19, 2026

    @erikdarlingdata
    OwnerAuthor

    Closed by the merge of #3738 (its body names this issue with a closing keyword; keywords do not fire against dev, so the watcher closes it).

  3. erikdarlingdata commented on Sep 19, 2026

    @erikdarlingdata
    OwnerAuthor

    Live verification on the .453 fleet (17:1xZ): three consecutive get_fleet_overview calls on one store read collection_health_age_seconds = 0 → 4 → 7 — one fresh scan, then two memo-served reads ageing honestly. The single-flight holds.

    Reader note, not a defect: on the largest store the overview payload is ~1.3 MB and collection_health_age_seconds serializes LAST, so an MCP wrapper with an output guard truncates before reaching it. The field was placed trailing for wire compatibility; the next lane that touches FleetOverviewResult's serialization order should move the stamp near the head (additive fields are order-free for JSON consumers, so this costs nothing on the wire) — noted here so it isn't rediscovered.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions