Skip to content

sweep_pressure reports OK while the body overruns: an infrequent 37s collector is invisible to an amortized model #2446

Closed

Description

@erikdarlingdata

Found soaking dev on prod-sql-use2-monitor-01. The worker is logging real skipped relaunches while sweep_pressure reports the server healthy, and both are correct about what they measure.

The disagreement

Six overruns in three hours on one server:

12:58  shard-A         collection body has not completed after 66s of execution - skipping relaunch
13:29  shard-A         62s
14:01  shard-A         69s
14:23  single-C  62s
15:02  shard-A         60s
15:34  shard-A         61s

get_collection_health for that server, at the same moment:

busy_ms_per_minute: 12250    busy_percent: 20.4    verdict: OK

Every collector reads HEALTHY. Nothing in the tool says this server is collecting at a multiple of its configured interval, which is the exact condition #2296 was built to surface.

Why the model misses it, and why the model is not wrong

SweepPressureClassifier amortizes each collector's average duration by its own cadence. Reconciled against the reported number:

collector avg duration cadence amortized
procedure_stats 3,205 ms 1 min 3,205 ms/min
query_store 13,834 ms 5 min 2,767 ms/min
query_stats 2,674 ms 1 min 2,674 ms/min
plan_correction 11,246 ms 5 min 2,249 ms/min
index_object_stats 37,207 ms 1440 min 26 ms/min

The total lands on the reported 12,250 ms/min, so the arithmetic is doing what it says. But index_object_stats takes 37 seconds in a body that has 60, and contributes 26 ms/min to the verdict. On the cycle where it coincides with query_store (13.8 s) and plan_correction (11.2 s), the serialized body is over 60 s — which is precisely what the log shows, roughly every half hour.

So the metric answers "can this server's total collection demand fit inside its cadence on average", and the operator's question is "does any single sweep overrun". Those diverge exactly when one collector's single-run cost approaches the interval while its amortized cost is negligible — which is the signature of an infrequent heavy collector, not of a saturated server.

#2296's model choice was right and should not be reverted. Its own note explains that gap-based detection flags every server at fleet scale because delivered cadence stretches benignly from bounded sweep concurrency. Sustained demand is the correct primary signal. What is missing is a second dimension, not a different one.

What would close it

The data is already collected — avg_duration_ms per collector is right there in the same payload, and the interval is known. Two candidates:

A burst term in the verdict. Any collector whose single-run average exceeds some fraction of the fastest cadence is a body-overrun risk regardless of amortized cost. On this server index_object_stats at 37 s against a 60 s minute is 62% of the budget in one collector.

Say it in heaviest_collectors. That list currently ranks by amortized contribution, so the collector actually causing the overruns is not in it — index_object_stats is absent while three collectors that fit comfortably are named. Ranking or annotating by single-run cost would put the cause in front of whoever is looking.

Worth deciding whether the verdict should change at all, or whether this is a note rather than an AT_RISK. An operator told "SATURATED" for a once-daily 37-second collector would reasonably ignore the next one; a note saying "index_object_stats takes 37 s of a 60 s budget when it runs" is actionable and does not cry wolf.

Also worth a look while in here

query_store on this server reports enumeration yielded 0 items - nothing to collect this cycle on 958 of 1155 runs, and still costs 13,834 ms average. That is 83% of its executions doing nothing at a cost that dominates its amortized share. Possibly correct (the collector is on a use2 box whose Query Store went quiet by design after the #2296 split) — but if an enumeration that yields nothing still pays 13.8 s, that is worth understanding separately.


Edited: server and database identifiers replaced with role placeholders. This is a public repository and the originals named tenants and customer databases; the measurements, ratios and reasoning are unchanged, because those are what the issue is about.

Activity

  1. erikdarlingdata commented on Aug 21, 2026

    @erikdarlingdata
    OwnerAuthor

    Fixed on dev by #2459 — and the mechanism I gave in this issue was wrong, in a way I should have caught before filing.

    The correction

    I wrote that index_object_stats (37,207 ms) coinciding with query_store and plan_correction explains the overruns, "roughly every half hour". That is arithmetically impossible. Its cadence is 1440 minutes and it had 6 runs in 7 days — it can account for one guaranteed overrun per day, not six in three hours. I checked that its amortized cost was negligible and never checked that its run frequency was compatible with the frequency of the symptom. Same family as the other measurement errors this week: I confirmed the number and not what it could explain.

    What is actually causing them

    query_store's avg_duration_ms is one mean over a bimodal population, and the split is stark. On single-A the enumeration yields nothing on all 1,555 runs and averages 36 ms — that is the empty-run cost. Backing it out of shard-A:

    1,155 runs × 13,834 ms   = 15,978,270 ms total
      958 empty ×     36 ms  =     34,488 ms
                               ──────────────
       15,943,782 ms ÷ 197 productive runs = 80,933 ms per productive run
    

    ~81 seconds for one productive query_store run, into a 60-second body. single-C — the other server that logged a skip — works out to ~65,500 ms on the same arithmetic. single-A, which logged none, is empty on every run. The discriminator lines up with the symptom exactly, and it is not the collector I named.

    That is #2460, and it is the more important half of what this issue turned up.

    What shipped here anyway, and why it still matters

    sweep_pressure now answers two questions in two fields. verdict is untouched and still means sustained demand. A new peak_cycle_risk (FITS/BODY_OVERRUN) answers "does one scheduled body fit at all", from peak_cycle_ms — the collectors' costs added without amortizing.

    Keeping it a separate field rather than a fourth band is the right call for the reason the issue guessed: a once-daily 37-second collector is not saturation, and SATURATED recommends capacity when the fix is schedule shape. Making it a field rather than a note is the better call, and mine was worse — a fleet scan has to filter on it. BODY_OVERRUN reuses the watchdog's own wording so the field and the log line agree.

    Validated live: shard-A 122% BODY_OVERRUN, single-C 109% BODY_OVERRUN (both logged skips), single-A 19% FITS (logged none).

    One honest limitation, flagged in the PR rather than discovered later: peak_cycle_ms is built from means, so it inherits the bimodal problem — it understates shard-A's worst cycle by roughly 67,000 ms. Once #2460 serves max_duration_ms, the peak cycle should use it.

    Edited: server and database identifiers replaced with role placeholders — this is a public repository. Measurements and reasoning unchanged.

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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions