Repository navigation
PG_CONNECTION_SATURATION: a PostgreSQL pool near its ceiling is graded as the outage it is — the window's peak session count over max_connections − superuser_reserved_connections (the engine's own line, read off lane 2's context facts), zero below the unmeasured 0.8 band and 1.0 at 0.9, the peak capture's active / idle-in-transaction / other breakdown and both levers with their costs in the prose, and PG_MONITORING_PERMISSIONS instead of a ratio when the monitoring login could not see session state (#3542 lane 3) - #3677
Merged
Merged
Conversation
…inst the engine's own ceiling — the window's peak session count from pg_session_states over max_connections − superuser_reserved_connections read off lane 2's context facts, zero below the unmeasured 0.8 band and 1.0 at 0.9 with threshold_lineage = 0, the peak capture's state breakdown and both levers with their counter-objectives in the prose, and PG_MONITORING_PERMISSIONS in its place when the monitoring login could not see session state (#3542 lane 3)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Headline mechanism. A PostgreSQL target's analysis pass now grades the one cliff PostgreSQL has that SQL Server does not: connection refusal.
PgTargetFactCollector.Sessions.csreads ONE query overpg_session_states— the window's PEAK capture by the denormalisedtotal_sessions, the newest capture, and how many stored rows werestate_is_redacted— and composesPG_CONNECTION_SATURATIONaspeak_sessions / (max_connections − superuser_reserved_connections), reading the ceiling off the two context facts lane 2'sConfig.csemitted a moment earlier in the same pass (CONFIG_PG_MAX_CONNECTIONS,CONFIG_PG_SUPERUSER_RESERVED; emission order Config → Sessions, the same in-memory seam Buffer and Write use forshared_buffers/max_wal_size).PgTargetScorer.Sessions.csscores the ratio 0 below 0.8, 0.5 at 0.8, 1.0 at 0.9.PgTargetAdvice.Sessions.csstates the division with all three numbers —90 / (100 − 3) = 93%— and the state breakdown of the peak capture (active / idle in transaction / other), so the operator sees WHICH population fills the pool, then names both levers with their counter-objectives. When the redacted share of the stored rows reaches one half, the collector emitsPG_MONITORING_PERMISSIONSINSTEAD — the login's blindness is the finding, and no ratio is built on blank rows. Partial for #3542 (v1 step 3, lane 3); do not close.The lie it stops telling
Nothing. Until tonight a PostgreSQL target's pass had no session fact at all: a pool at 96 of 97 usable slots — one deploy, one retry storm from
FATAL: too many clients alreadyfor every application on the box — produced anemptyverdict with coverage 1.0. That is not a slowdown the pass under-reported; it is an outage the pass could not see. The SQL Server sibling concept is THREADPOOL, a WAIT: SQL Server queues the request. PostgreSQL does not queue — it refuses, and the design (§3.6) calls it "the harshest PG cliff" for that reason. The advice says so in those words.Why this shape
max_connectionsoff step 2's context facts rather than the config snapshot. The plumbing's base-severity seam isScoreSessionsFact(Fact)— one fact, no lookup — so the ratio has to be ON the fact before scoring; the amplifier predicates are the only scorer arm that seesfactsByKey, and a ratio is not an amplifier.facts.Find(f => f.Key == PgTargetFactKeys.ConfigMaxConnections)is the whole dependency;PgTargetConfigSnapshotSqlis never named here (pinned). The three numbers ride the fact as metadata soget_analysis_factsreaders can redo the division.ApplyThresholdFormulagrades any positive value belowconcerningas0.5 × value / concerning, so a pool at 40% would score 0.25 — and be "fired" to every sibling predicate that asksBaseSeverity > 0(lane 5's Lock-wait confirmer, lane 9's session-spike fold) on every server that ever accepted a connection. Saturation scores 0 below 0.8 and 0.5 AT it; a fired saturation fact always means the pool is near its cliff. Pinned at 0.79 → 0, 0.80 → 0.5, 0.85 → 0.75, 0.90 → 1.0.pg_read_all_stats, PostgreSQL returns every other role's backend withstate,xact_start,backend_typeand the text blanked — the privileged role saw four idle-in-transaction sessions, the unprivileged one zero of the same nine. Under redaction the collector's own duration floors are NULL for every backend the login does not own, so WHICH captures stored rows and what they say about the pool is the login's blindness, not the server. The advisory states the peak COUNT it saw (a barecount(*)survives redaction) and explicitly not its share of the ceiling; the saturation key is not emitted at all (pinned both at the collector and throughget_analysis_facts).PG_MONITORING_PERMISSIONSroots at the story line (0.5), not the D5 advisory 0.4. It is not a config convention check and is not inPgTargetFactKeys.ConfigAdvisoryRoots(a shared file this lane may not edit); at 0.4 it would never root a card, which is the silence the V86 comment says this finding exists to replace. At 0.5 it is a WARNING-band card with no amplifier arm — it cannot climb, never pages, and surfaces on the quietest server. The constant is one edit if the coordinator prefers 0.4 + root membership; the report says so.idle_in_transaction_sessionsshare as an amplifier ("parked connections consuming slots"). ThePG_IDLE_IN_TRANSACTIONfact is v2, so the arm readspeak_idle_in_transaction_shareoff the saturation fact itself (bar 0.25, unmeasured); under partial redaction the idle-in-transaction count under-reports, so this can only fail to fire, never fire falsely. The second arm is the AuroraPG_CPU_PERCENT-fired co-fire (lane 9's key; inert until its bar lands — proven inert through the realScoreAll). The offered-vs-delivered co-fire (sessions climbing whilePG_TPSis flat) and thePG_IDLE_IN_TRANSACTIONco-fire are written out as commented hooks with the metadata they would need.PG_WAIT_LOCK*— lane 5;PG_IDLE_IN_TRANSACTION— v2); an edge whose predicate asksBaseSeverity > 0of a fact nobody emits is inert by construction but reads as a finished chain. The two hooks are in the file for the lanes that own their destinations; pinned thatAddEdge(does not appear in code and the built graph has no edge from either key.work_memin the advice is THIS server's (D4, composition without RAM): when lane 2'sCONFIG_PG_WORK_MEMfact is in the set, the memory cost of raisingmax_connectionsreads "work_mem is 4 MB here, so 97 connections each running one such node could claim 388 MB"; when it is not, the cost is stated as a shape and no figure is assumed (both pinned).Lineage of every bar (#3538 A5/A7 via #3605)
max_connections − superuser_reserved_connectionsmax_connections; the reserved slots are the engine's own carve-out for superusersConnectionSaturationWarning/Criticalpg_session_states(per-servermax(total_sessions)per window over that server's usable ceiling, p50 / p95 across the dogfood PostgreSQL fleet); every saturation fact carriesthreshold_lineage = 0, including the ones graded to 0IdleInTransactionShareBarpg_session_states(per-captureidle_in_transaction_sessions / total_sessions)ConnectionSaturationCoFireBoostanalysis_findingsco-fire ratesRedactedShareMajorityMonitoringPermissionsBaseInferenceEngine.MinimumSeverityThresholdis private); not a measurement barNo absolute-ms gate; nothing here divides by observed time (the fact is a peak, not a rate) — the family still returns early on an unobserved window for consistency with its siblings.
PgTargetThresholdLineageTestsgreen on the file.What the family says about its own evidence (in the prose, every time)
total_sessionscounts every backendpg_stat_activityreports, PostgreSQL's own background processes included (the collector could not filter toclient backendbecausebackend_typeredacts). Those hold no connection slot, so the ratio reads a few points HIGH of the true client share — the safe direction for a cliff detector, and the advice says so beside the number, with the "other" bucket labelled as "idle client sessions and PostgreSQL's own background processes, which this series cannot tell apart".pg_session_statesis an exception table: a capture with no session past the collector's floors stores nothing, so the peak is over the captures that had something to report and a pool of short OLTP sessions may leave no capture at all.captures_with_rowsrides the fact; the prose says "seen over N captures that stored session rows … quiet minutes are absent from this series".collection_log(the reader's honest-empty denominator) is not an analysis table and is not read (pinned).reserved_connections(a second carve-out, default 0) is not in lane 2's snapshot name list; on a target that set it the ceiling reads that many slots high — noted in the collector's doc, not compensated.What it does NOT do
No schema (no rung, no
StorageVersionbump). No edit to any shared file —FactScorer,FactAdvice,InferenceEngine,PgTargetFactKeys, the three root partials,Config.cs, the census test — all untouched (git diff --stat origin/devis the five files below). No edge. NoPG_IDLE_IN_TRANSACTION(v2). No offered-vs-delivered co-fire (needs a TPS trend the database family does not yet carry). No change toPgSessionStatesCollector,DarlingPgSessionStatesReaderor theget_pg_session_statestool. NoAnomalyThresholdsPG session floors (step 9). No Lite twin (D1 — Lite has no PostgreSQL target). No DDL, no GRANT statement — the permissions remediation names the role to grant in prose.Blast radius
A PostgreSQL target with
pg_session_statesrows in the window and apg_server_configsnapshot at or before its end gains at most onepg_sessionsfact per pass. A target with no session rows (the plumbing e2e's fixture) is byte-identical: theCROSS JOINagainst twoLIMIT 1CTEs yields no row, the family returns,emptystaysempty— executed here on the live rig. SQL Server passes read none of the changed files.FactScorer.KnownSourcesunchanged (the source was registered by plumbing).Test plan
New —
Darling/Darling.Tests/PgTargetSessionsTests.cs(17 ungated + 1 gated), every one EXECUTED on this machine through a net10.0 harness (AssemblyName=Darling.Tests) before push, alongside the wave's rosters —PgTargetFactCollectorTests(census,AllSqlclosure, dialect, FROM/JOIN targets),PgTargetThresholdLineageTests,PgTargetSharedSwitchRoutingTests(delegation equality — the static blocks are non-null and equalCompose(key, ∅)),PgTargetPostureIsolationTests,PgTargetMcpSurfaceTests,FactCollectorCommandTimeoutTests,FactCollectorFailureReportingTests,StoreSqlClockDisciplineTests,FactSourceRegistryTests,RepoFileAdoptionTests,CommentFilterAdoptionTests,DocCommentHygieneTests,LiveCleanupConversionRatchetTests,LivePostgresCollectionHygieneTests: 250 / 250 green withDARLING_TEST_PGset against a throwawaytimescale/timescaledb:2.28.1-pg18migrated to V131 (the three gated e2es in the harness all ran), torn down after. First CI execution is the Windows job.FactScorer+InferenceEngine(PgTargetRelationshipGraph)with no amplifier results and is not an advisory root by registration.ScoreAllthe parked arm lifts 1.0 → 1.25 while the CPU stub stays inert; below the warning band nothing fires and no sibling lifts it.90 / (100 − 3) = 93%, the headline, the breakdown40 active, 30 idle in transaction, 20 other, the PARKED sentence past the bar and its absence below it, the exception-table and background-process caveats, both levers with their counter-objectives,work_memstated from lane 2's fact (388 MB) vs stated as a shape without it, the pending-restart sentence, noCREATE INDEX; the permissions block's redacted share, the measured four-vs-zero, the peak COUNT and nousable/max_connectionsanywhere in it; the static blocks claim no figure and every other key handed to the partial is null.NpgsqlCommand, one const inAllSql,FROM pg_session_statesonly, nocollection_log, nopg_server_config, theMAX()picks, the peak ordering, the row-level redacted share, both config keys read viafacts.Find, the redaction gate textually before the ceiling lookup, the shared deadline and degrade, noDateTime.UtcNow,filled by lane 3.AddEdge(absent from code; both hooks named in the file.[Collection("live-postgres")],LiveStoreCleanup.RunAsync): two servers, each with 25 h ofpg_database_stats, a REALpg_server_configsnapshot 30 min before the window's end (max_connections 100,superuser_reserved_connections 3,work_mem 4096 kB— lane 2's emitter, not hand-built facts) and 48 five-minutepg_session_statescaptures. Busy: totals 60 with one peak of 90 (40 / 30 / 20) two hours before the end → the collector alone yields 90/97 with every metadata figure pinned (peak_age_s 7200,config_snapshot_age_s 1800,captures_with_rows 48, …),ScoreAllgives base 1.0 / severity 1.25 / lineage 0;analyze_server(anchoredas_ofthe planted end) returns the card at severity 1.25, categorypg_sessions, the exact headline, the division, the breakdown, "2 h before the window's end", the 388 MBwork_memline,get_pg_session_statesamongnext_tools, and no permissions card. Blind: 3 rows per capture, 2 redacted → the collector yields ONLYPG_MONITORING_PERMISSIONS(2/3, 96 of 144, peak count 95, nosaturation_ratio/usable_connectionskey),analyze_serverreturns its card at 0.5 with the headline and prose and no saturation card, andget_analysis_facts source=pg_sessionsshows one fact per server with the lineage stamp on the ratio and none on the advisory.Builds (zero warnings of ours):
PerformanceMonitor.Analysis,PerformanceMonitor.Darling.Analysis,PerformanceMonitor.Darling.Service,Darling.Tests,Lite.Tests,deprecated/Dashboard.Tests(-p:EnableWindowsTargeting=truefor the test projects). The one Lite.Tests warning (LogTailOverlapThresholdPinTests.cs:95xUnit2000) is pre-existing on dev from #3646.Which component(s) does this affect?
How was this tested?
Above. Live store: throwaway
timescale/timescaledb:2.28.1-pg18on Docker contextdesktop-linux, migrated to the current ladder (V131), torn down after. No production store or fleet server was read. No SQL Server surface is touched.Checklist
dotnet build -c Debug)Partial for #3542 (v1 step 3, lane 3). CHANGELOG deliberately untouched — the coordinator splices this wave's entries.