Skip to content

PostgreSQL sessions: a window with no session capture still grades connection saturation from pg_stat_database.numbackends as a level-only card that says its states are unobserved, and the Aurora wait-profile advice names the window mean against its routine (partial #3691, lane 26) - #3791

Merged
erikdarlingdata merged 1 commit into
devfrom
fix/3691-saturation-level-card-and-mean-prose
Sep 20, 2026

Conversation

@erikdarlingdata

Copy link
Copy Markdown
Owner

Two small honesty items on the PostgreSQL-target analysis engine, file-disjoint from each other, one PR. Lane 26 of #3691.

PARTIAL for #3691 — do not close. Two checklist lines, quoted verbatim:

  • "A window with NO session capture still emits no saturation fact…" — the level-only half is done here. The redacted-majority ruling (withhold the ratio WITH captures although numbackends is unprivileged) is the other half of that line and stays OPEN; it is the coordinator's call, and this PR does not touch it.
  • "PgTargetAdvice.Anomaly.cs ComposeWaitProfileRatio (~:258) does not name the window mean…" — done.

Item 1 — the saturation card exists as a level-only card when no capture exists but numbackends does

The lie. #3782 moved PG_CONNECTION_SATURATION's numerator to pg_stat_database.numbackends — the universal one-minute level — but kept lane 3's gate: the session-capture read's empty was the family's empty. A quiet OLTP pool that tripped no capture rule in the window got no saturation fact at all, even at 96 % of its usable ceiling, because the card's state breakdown, redaction verdict and newest-capture figure all came from pg_session_states and emitting without them changed what the card said. #3782's body named this as the honest next step.

What changes, and from which table (Darling/PerformanceMonitor.Darling.Analysis/PgTargetFactCollector.Sessions.cs). The capture read's empty now only records "no capture" (hasCapture = rowsStored > 0 && peakTotal > 0; the locals are zeroed instead of returning). Without a capture: the redaction gate is not taken (the redacted-majority rule is over stored rows — zero rows are neither redacted nor visible), the idle-in-transaction holders read is not run (the holders ARE the captures' rows), the ceiling is still looked up off lane 2's context facts, and the level is still read (PgTargetNumbackendsPeakSql, unchanged). When the level decides (coverage ≥ NumbackendsCoverageFloor, #3782's rule) the fact is emitted as the level-only card: Value = the level's peak instant ÷ usable, numerator_source = 1, numbackends_partial = 0, the coverage counts, peak_numbackends + its age, the ceiling keys, and captures_with_rows = 0 — and NONE of the capture-derived keys: no peak_total_sessions, no state split, no peak_idle_in_transaction_share, no latest_*, no peak_age_s, no rows_redacted_share. Absence is the stamp: a share of 0 would claim "nothing parked" of states nobody observed. When the level does not decide and there is no capture — a pre-V133 store, or one mid-migration — the family is silent exactly as v1 was (logged at debug with the coverage): a part window's level is the capture-peak lie in a new coat, and there is no capture peak to fall back to. No SQL changed; still three commands, three deadlines, one degrade.

Every reader of the absent keys reads "not measured", and it is pinned, not assumed.

  • PgTargetScorer.Sessions.cs — the parked-share amplifier's predicate now asks TryGetValue("peak_idle_in_transaction_share", out var parkedShare) && parkedShare >= IdleInTransactionShareBar: the key first, then the bar. Behaviourally identical today (GetValueOrDefault read 0 < 0.25) — the change is that "not measured" is never compared as a 0 %, and the test source-pins the shape so GetValueOrDefault cannot creep back. No bar, no boost, no lineage moves; ScoreSessionsFact untouched (grades saturation_ratio the same from either shape).
  • PgTargetRelationshipGraph.Saturation.cs (not edited) — the saturation ↔ idle gate reads the share with GetValueOrDefault ≥ bar; absent → false → no edge in either direction beside a fired idle fact. Pinned.
  • ComparisonBanding.BaselinedValueFor (not edited) — returns null without peak_total_sessions, so compare_analysis sigma-bands the PostgreSQL baselined metrics, so a 6× TPS surge on a PostgreSQL target stops reading "stable" (#3691 W1/W2) #3713's compare banding takes the absolute rule for the level-only row rather than a sigma against a bucket built from capture counts. Pinned (null for the level-only card, 40 for the capture-composed one).
  • PgTargetAdvice.Sessions.cs — ComposeSaturation witnesses the level-only shape by the same absent key banding does. The headline and division sentence are the level's (lane 25's text); the breakdown sentence is replaced by: "No session capture in the window: the count is pg_stat_database.numbackends — the backends attached to a database, every minute, whether or not any session tripped a capture rule — and state, application and idle-in-transaction shares are unobserved (the collector stores a session capture only when some session had a transaction open past its floor, so a window with none is a pool that tripped no rule, not an empty one). Whether these slots are held by working sessions or parked ones cannot be said from this window."; the coverage sentence reads "numbackends carried a value at 241 of the window's 241 one-minute pg_database_stats samples; no capture stored session rows in the window."; the remediation's tool sentence says get_pg_session_states has no rows for the window instead of pointing at an empty page. The idle fact's cross-reference sentence (ComposeIdleInTransaction) no longer prints "with 0% of the peak capture idle in transaction" beside a level-only card — unreachable in one pass (the idle fact needs a capture) but honest if the pairing ever arrives. The static block's "The state breakdown always comes from the peak capture" is now "…comes from the peak capture when the window stored one; a window with no capture states the level alone and says its states are unobserved."

Byte-identity for a window WITH a capture, executed. The capture keys are stamped in lane 3's exact order (the if (hasCapture) blocks sit where the initializer entries were), and the gated e2e asserts the with-capture fact's whole ordered key list — 25 keys, lane 25's, before the scorer adds its own. The advice's new sentences are asserted absent for four with-capture fixtures (level decided / mid-migration / pre-V133 stamped / pre-lane unstamped), and lane 25's exact-string pins pass unchanged.

Item 2 — ComposeWaitProfileRatio names the window mean

Since #3780 the Aurora wait-profile detector fires the trusted arms only when the window MEAN clears the bar as well as the peak and stamps mean_ms_per_sec / mean_ratio / mean_modified_z; the composer still stated the peak alone, so a reader took one hot five-minute collection's number for the whole window's. The sampled composer (PgTargetAdvice.Wait.cs, #3765) already named its mean; #3773's SQL Server enrichment had no PG-target twin. PerformanceMonitor.Analysis/PgTargetAdvice.Anomaly.cs now adds ONE sentence on the trusted arms, in the brief's shape, after the contributors: "The window's mean rate was 1507.2 ms/sec against a routine of 200 — 8.8 robust sigmas above it — so the whole window sat high, not one delta; the detector fires only when the mean clears its bar as well as the peak." — the mean's robust sigmas on the gate arm (mean_modified_z > 0), else its multiple on the ratio arm (the stamped mean_ratio first, the quotient over baseline_mean as the belt). On is_new the bar is on the peak alone (#3741's ruling), so the mean is plain context inside the existing parenthesis — "(CPU excluded; the window's mean was about 1507.2 ms/sec)" — and no gate is claimed. A fact without the mean (pre-#3780) composes byte for byte as before (pinned on both arms). Still "the engine measured"; never "estimated from sampling", never "spent". No second aurora_stat_* mention — AuroraOnlySqlIsGatedTests' roster class for this file is unchanged.

Lineage

No bar, ramp, floor or boost is added or moved. ConnectionSaturationWarning / Critical 0.8 / 0.9 stay measured (2026-09-19 + 2026-09-20 C1, #3782); NumbackendsCoverageFloor 0.5 stays definitional; IdleInTransactionShareBar 0.25 stays measured and still gates only a capture's share. No PG-target anomaly bar is touched.

Co-fire gating

Unchanged in declaration: three saturation amplifiers, same boosts, same keys. The parked-share arm cannot match a level-only card (no share); the CPU and offered-vs-delivered arms read siblings and can. The graph's edges are untouched.

What it does NOT do

  • Does not build a ratio under a redacted majority WITH captures (PG_MONITORING_PERMISSIONS still replaces it). That is the other half of the checklist line and the coordinator's ruling — stated on the face, left open.
  • Does not emit a level-only card over a part window (coverage under the floor) — silence, as v1.
  • Does not add a key to PgTargetFactKeys, change any SQL, touch FactScorer / FactAdvice / a root partial / the graph file / ComparisonBanding / PgSessionStatesCollector / PgDatabaseStatsCollector, or a rung (V135 on dev). No production read.
  • A SQL Server target's pass is byte-identical by construction (both edits sit inside the pg_sessions family and the ANOMALY_PG_WAIT_PROFILE composer, behind the pg_ arms the routing pins re-prove).

Tests

Darling/Darling.Tests/PgTargetSessionsTests.cs (+2 unit facts, a LevelOnly fixture, one static-block pin moved, the gated e2e extended with a fifth server and a key-order pin):

  • TheLevelOnlyCard_CarriesNoCaptureKey_GradesTheSame_AndEveryReaderOfTheAbsentKeysReadsNotMeasured: the 11 capture keys absent by name; ScoreBase 1.0 / lineage 1 / severity 1.0 through the real ScoreAll with 3 arms declared and none matched; the parked predicate false on the card and true on a capture-composed 30/90 (reflection over the real Amplifiers); the scorer source pinned to TryGetValue(…parkedShare) and no GetValueOrDefault("peak_idle_in_transaction_share"); a fired idle fact beside the card opens no edge either way (two stories); BaselinedValueFor null vs 40; the idle fact's cross-reference sentence by exact string; the collector's gating in stripped source (hasCapture definition, the redaction gate under it, the holders read directly under an if (hasCapture), the no-numerator return before the fact, the reader's row optional).
  • ComposeSessions_LevelOnly_StatesTheLevelAndThatStatesAreUnobserved_AndAFactWithACaptureIsByteIdenticalToLane25: headline + first sentence (lane 25's), the no-capture sentence and coverage sentence by exact string, no "State breakdown" / "0 active" / "0 sessions" / "newest capture" / PARKED, the measured clauses stand, the remediation's tool sentence, pending-restart; four with-capture fixtures reach none of the new text and keep lane 25's tool sentence and breakdown.
  • Gated e2e: a fifth server (…-quiet) with the level server's series (30 every minute, one instant at 93) and ceiling and no pg_session_states row. Collector alone: ONE sessions-source fact, 93/97, source 1, partial 0, peak_numbackends 93 at 5,400 s, 241/241, captures_with_rows 0, the 11 keys absent, BaselinedValueFor null, scored 1.0/1.0/lineage 1 with no arm matched, no idle fact, no permissions advisory. Through the REAL analyze_server anchored at the window's end: the card at 1.0 with the exact level headline, the division sentence, the no-capture sentence, the coverage sentence, no breakdown, the remediation's no-rows sentence; get_analysis_facts shows source 1, 93, captures_with_rows 0 and no peak_total_sessions / share. Then the quiet store NULLed to 101/241 → no sessions fact; NULLed everywhere → no sessions fact (v1's silence). The with-capture level server additionally asserts its exact 25-key ordered metadata before scoring.

Darling/Darling.Tests/PgTargetAnomalyTests.cs (+1 unit fact; the gated wait-gate e2e gains two asserts): the gate-arm sentence by exact string in its position (after "led by Lock:relation.", before "This is a shift…"); the ratio arm's multiple from the stamped mean_ratio and from the quotient when unstamped; is_new's context clause with no σ / × / gate claim; a pre-#3780 fact's surrounding text by exact string with no "mean" anywhere, headline equal to the stamped fact's; never "estimated from sampling" / "spent". The live e2e composes the detector's REAL shifted fact and asserts the sentence from its own stamped mean, routine and sigmas.

Executed here, not only compiled: a net10.0 harness (AssemblyName=Darling.Tests, 34 PgTarget / census / hygiene classes incl. PgTargetFactCollectorTests, PgTargetSharedSwitchRoutingTests, PgTargetThresholdLineageTests, PgTargetMeasuredLineageTests, StoreSqlClockDisciplineTests, DocCommentHygieneTests, RepoFileAdoptionTests, FleetIdentifierScrubTests, CommentFilterAdoptionTests, AsOfWindowAnchorTests, FactCollectorCommandTimeoutTests, FactSourceRegistryTests, MeasurementContractCensusTests, StragglerCommandTimeoutTests, LiveCleanupConversionRatchetTests, LivePostgresCollectionHygieneTests, PgTargetBetweenWaves*, PgTargetWaitTests, PgTargetSampledWaitTests, PgTargetNamedHopsTests, CollectionCaveatsTests) under <worktree>/obj/mactest/ against a throwaway timescale/timescaledb:2.28.1-pg18 on Docker context desktop-linux (role and database darling, migrated by the fixture to V135), deleted before commit and torn down after: 427 / 427 green with DARLING_TEST_PG set — every gated e2e in the set ran, including both of this lane's and lanes 3 / 14 / 18 / 25's. First execution of every other census is CI's Windows job.

Builds (-c Debug, -p:EnableWindowsTargeting=true where net10.0-windows): PerformanceMonitor.Analysis, PerformanceMonitor.Darling.Analysis, PerformanceMonitor.Darling.Service, Darling.Tests, Lite.Tests, deprecated/Dashboard.Tests — 0 warnings, 0 errors on every one.

Which component(s) does this affect?

  • Lite
  • Darling
  • Lite Tests
  • Darling Tests
  • SQL collection scripts
  • Documentation
  • Full Dashboard (deprecated)
  • CLI Installer (deprecated)

How was this tested?

Above. Throwaway container only; no production store or fleet server was read.

Checklist

  • I have read the contributing guide
  • My code builds with zero warnings (dotnet build -c Debug)
  • I have tested my changes against at least one SQL Server version (PostgreSQL-target surface; the SQL Server pass is byte-identical by construction — both edits sit behind pg_ arms)
  • I have not introduced any hardcoded credentials or server names

Partial for #3691 (lane 26). CHANGELOG deliberately untouched — the coordinator splices this wave's entries. Not armed (review-dark window).

…nnection saturation from numbackends as a level-only card, and the Aurora wait-profile advice names the window mean (partial #3691, lane 26)
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