Repository navigation
PostgreSQL-target analysis engine: design and v1 build #3542
Description
PostgreSQL targets are skipped by the analysis pipeline outright (DarlingWorker's scheduled arm and analyze_now both write the PostgresAnalysisNotApplicable tombstone), so PG servers get alerts but no scored, baseline-aware, story-built, advice-bearing analysis — the surface the product treats as THE thing an operator's agent talks to. A full design doc was produced during the 2026-09 brains-review campaign; this issue tracks the build.
Shape (from the design doc):
- The 27-collector PG surface already feeds everything a v1 needs; the only structural blockers are the worker skip and the wait_stats-based 24h data-span gate.
- Machinery reuses whole: pipeline, scorer formulas, 168-bucket robust baselines, AnomalyGate, inference/clustering, persist/mute/notify, the existing MCP tools (zero new tools). Net-new is CONTENT: fact collectors, scorer arms, thresholds, advice blocks, PG graph edges — in a separate
PgTargetFactCollectorfamily (NOT extending PgFactCollector, which analyzes SQL Server targets and is pinned by the Lite-parity census; the PG family gets a self-pin census since Lite doesn't monitor PG). - ~10 of the 31 SQL Server fact families map directly (with named traps: PG waits/blocking are SAMPLED sightings, not engine events; Aurora vs stock wait sources are platform-exclusive and carry different evidence grades; deadlock RATE comes from pg_database_stats counters, not the lossy log parser). SQL-Server idioms (tempdb, memory clerks, scheduler, trace flags) are replaced by 11 PG-only detectors: autovacuum backlog/slope, wraparound, xmin horizon, connection saturation, checkpoint pressure, buffer-cache composite, temp-file spills, replication lag + slots, bloat trend, idle-in-transaction.
- Config surface follows the field-study's two-knob result (75-95% of tuning value from shared_buffers + max_wal_size set per docs): those two amplified only by their workload co-fires, a short convention-check tier, composition checks, and fsync / synchronous_commit / full_page_writes isolated in a pg_posture source no amplifier can touch (durability is policy, never a performance win).
- Known gap: host RAM is not collected for PG targets, which blocks the memory-overcommit composition checks — the same hardware-profile dimension the campaign flagged as the cross-server transfer blocker. Same collector fix serves both.
Ten maintainer decisions are enumerated in the design doc; the expensive-to-reverse one is pg_-prefixed fact keys/sources (persisted into findings and mutes from day one) and should be settled before the build starts.
Ranked build order: v1 (~11 facts/detectors: the two knobs + connection saturation + the vacuum family + wait profile + temp spills + bad-actor + posture) -> v2 (replication, bloat trend, blocking chains, maintenance-shape) -> v3 (plan regression via pg_statement_stats + captured plans, parameter sensitivity, evidence-gated index candidates).
Build venue: crew-scale feature work — plan validation against the design doc, adversarial review before implementation, then staged build.
Activity
erikdarlingdata commentedon Sep 18, 2026
Design input from the competitive gap analysis (#3605, filed today): the three
PostgresAnalysisNotApplicablecall sites in DarlingWorker are the exact seams the v1 build replaces; #3605 proposes fact-family-list-first slicing and asks that PG detector thresholds inherit #3538's measured-lineage discipline rather than constants. Also related from the same analysis: #3601 (target-PG log ingestion umbrella), #3602/#3603 (parser families on it), #3604 (service-side wait sampler fallback for stock PG), #3607 (logging-settings audit, sequenced behind #3601). These are data sources the PG detectors could eventually consume; v1 as designed does not depend on them.erikdarlingdata commentedon Sep 18, 2026
Validated v1 build plan (plan-validation pass against dev
950fad3a→2df8fde0, 2026-09-18)Full document (537 lines, every claim path:line-cited) is filed on the design bus as
pm-issue-queue-inbox/design/3542-V1-BUILD-PLAN.md; this comment carries the parts a lane worker or reviewer needs first. Decisions D1–D10 are adopted as the design doc recommends (D2pg_-prefixed sources/keys included). #3605's three contributions are folded in (§7).0. Headline corrections to the design doc (read these first)
D4 as literally worded cannot be built. The doc says "one
DarlingAnalysisServicetaking an engine(constructor composes collector/detector/graph — swap those three by engine)". But the service is
constructed at THREE sites and two of them are per-process singletons shared across every server:
Darling/PerformanceMonitor.Darling.Service/DarlingWorker.cs:5671— fresh per pass (the worker knows theengine here);
Darling/PerformanceMonitor.Darling.Service/Mcp/DarlingMcpHostService.cs:471—AddSingleton(new DarlingAnalysisService(postgres, planFetcher, _logger)), one instance for all servers;Darling/PerformanceMonitor.Darling.Service/DarlingWebEndpoints.cs:245—new DarlingAnalysisService(postgres),"shared across requests, like the MCP host's singleton".
The MCP tools resolve a server by NAME (
DarlingServerResolver.ResolveOrErrorAsyncreturns(ServerId, ServerName)only —Mcp/DarlingServerResolver.cs:61) and never see an engine. So D4 must berealized as per-CALL engine resolution inside the service: the one service holds BOTH component sets and
picks per
serverIdby readingservers.engine_kind(Mcp/DarlingEngineCapability.cs:49-52ServerEngineSql; token vocabularyPerformanceMonitor.Collectors/MonitoredEngineKind.cs:45-62).PerformanceMonitor.Darling.Analysisalready referencesPerformanceMonitor.Collectors(
PerformanceMonitor.Darling.Analysis.csproj:26), soMonitoredEngineKindis reachable without a newproject reference. The spirit of D4 (one service, one pipeline body, one persist/mute/notify path) is
preserved exactly; only the selection point moves from constructor to call.
Analysis facts divide by the time the collector actually observed, and a window with a hole in it says so, so collector downtime no longer reads as improvement (#3538 A2) #3592 (MERGED 16:19Z as
2df8fde0, i.e. AFTER the950fad3aworktree was cut) introduces a NEWSQL-Server-ism the doc could not know about, and it is load-bearing. On dev now:
CollectFactsAsyncstep 0 is acoverage witness read from
v_wait_stats(PgFactCollector.Waits.cs:46-49CoverageSql,:93CollectObservedCoverageAsync;PgFactCollector.cs:177call site), it stampsAnalysisContext.Coverage/ObservedDurationMs, and — decisively —the service's empty-window branch is now
if (facts.Count == 0 || context.ObservedDurationMs <= 0)(
DarlingAnalysisService.cs:280on dev). A PG-target collector that does not stamp its own coverage witness makesEVERY PG pass return
unavailable("the collector observed none of the window"). The entry points are nowCollectAndScoreFactsAsync→(Facts, Coverage)(:479) andComparePeriodsAsync→ 4-tuple (:515); ctor movedto
:168;TotalDataSpanSqlto:607. The plumbing lane must ship aPgTargetCoverageSqloverpg_database_stats(DISTINCT
collection_time, same LAG/gap-policy rules) as its own step 0. The C# edge-finishingBuildCoverageisprivate staticinPgFactCollector.Waits.cs:139-160— the plumbing lane makes itinternal static(same assembly)or hoists it to
FactCollectorHelpers; either way it is not copied. The census inPgFactCollectorTestsis now 32names (
:72CollectObservedCoverageAsync) — untouched by D1. The doc's D3 choice ofpg_database_statsas thedata-span source is the right witness for the same reason (universal, 1-minute, no extension) — one table answers
both gates, mirroring how
v_wait_statsanswers both on the SQL Server side.Extreme, corroborated anomaly findings can now cross the notify floor, so the baseline engine is no longer notification-inert at shipped settings (Fixes #3526) #3584 (MERGED today) added two more SQL-Server-keyed registries the doc's §5b does not list, and one of
them is load-bearing for whether PG anomalies can ever notify:
FactScorer.IsDeviationScoredAnomalyKey(PerformanceMonitor.Analysis/FactScorer.cs:664-671) is a literallist of
ANOMALY_CPU_SPIKE / READ_LATENCY / WRITE_LATENCY / BATCH_REQUESTS / SESSION_SPIKE / QUERY_DURATION / MEMORY_PRESSURE.IsExtremeAnomaly(:706-735) andScoreAnomalyFact(:737-836) both route through it.A PG anomaly keyed
ANOMALY_PG_*(D2) falls to the ratio arms, scores 0 unless it carriesratiometadata,and is NEVER released from the tuning-class cap (
IsTuningClassKey:654-656catches everyANOMALY_prefix, including PG's) — i.e. the exact notification-inertness Extreme, corroborated anomaly findings can now cross the notify floor, so the baseline engine is no longer notification-inert at shipped settings (Fixes #3526) #3584 just fixed for SQL Server would ship
for PG on day one.
FactScorer.AnomalyAmplifiers/LoadAnomalyAmplifiers(:921-989): the load-family co-fire arm's measuredconfirmer is
CPU_SQL_PERCENT >= 80(:985-989). PG's CPU fact isPG_CPU_PERCENT(instance CPU, Auroraonly) — the arm is inert for PG and needs a twin (
PG_CPU_PERCENT >= 80, Aurora; absent on stock).PerformanceMonitor.Analysis/AnomalyIncidentReconciler.cs:70-78AnomalyToFamiliesmaps anomaly keys toSQL Server parent keys (
CPU_SQL_PERCENT,IO_READ_LATENCY_MS,BLOCKING_EVENTS,DEADLOCKS…). Not in thedoc. PG anomalies with no entry stay solo cards (tolerable in v1) but
ANOMALY_PG_DEADLOCK_RATE → PG_DEADLOCKSetc. should be registered when those keys exist.
D7 needs no code.
RunAnalysisPassAsyncalready writesanalysis_statecleared (insufficientData:false, null) after any real pass (DarlingWorker.cs:5820-5823) and the honest gate/empty messages otherwise(
:5808-5818). Deleting the skip arms + tombstone IS the lazy overwrite; the old row stands until the firstPG pass runs, which stays truthful.
The doc's
PgMigrations.csline citations are all shifted ≈ +94 (e.g.pg_wait_statsDDL :1806 → :1900,pg_database_stats:2265 → :2359,pg_server_config:3492 → :3586,pg_wait_sampling:3759 → :3853).Content unchanged — V127 (The four naked delta families store the interval their deltas accrued over, so a restart's fabricated zero reads as unknowable instead of 0.00 ms/sec (#3540, V127 / Lite v60) #3595) added interval columns to the four SQL Server delta tables only.
audit_configlies for a PG target today and will still lie after plumbing unless given an arm(
Mcp/DarlingMcpTools.cs:409-606): it looks upCONFIG_CTFP / CONFIG_MAXDOP / CONFIG_MAX_MEMORY_MBand, findingnone, returns
status = "no_config_data", "The config collector may not have run yet."— false for a PGtarget whose
pg_server_configcollector runs hourly. Not in the doc's MCP inventory (§5a lists fourtools; there are five analysis-service consumers).
5. PR-sized lanes
*Lane 3 depends on lane 2's config KEYS (pre-declared in P) not its file; lane 5's graph edges touch.Write.cs/.Memory.cs/.Saturation.cschain files owned by lanes 2/3 → land 5 after 2 and 3 or accept a three-file rebase. Lane 6 depends on lane 2'sDatabase.csread and lane 7'sQueries.csdrill-down.analyze_server→emptyw/ coverage ≈ 1;analysis_statecleared after first pass; SQL Server byte-identicalConfig/Write/Buffer/Databasecollector partials; scorer/advice/graph partials;audit_configprojectionScoreConfigFact,ComposeConfigMaxdop,PgDatabaseSql,PgWriteStatsSql,PgIoSql,CurrentConfigSqlPgSettingValueTests; co-fire gating pins; Aurora hit-ratio suppression pin; PG<16 eviction-absent pinSessionspartialsPgSessionStatesSqlsessions/(max−reserved)Vacuumpartials;PostgresOutagePredictorThresholds(Common);PostgresAlertEvaluatoraliasingPgAutovacuumSql,PgWraparoundSql,PgXminHorizonSql,PostgresAlertInfoper-counter gradingWaitspartials; edges into 3 chain filesPgWaitStatsSql,PgWaitSamplingSql,WaitRateWindowSqlLAG fallbackis_sampledpresence/absence; restart-collection-not-a-sample; per-source denominatorTempscorer/advice/graph partials;Queries.cstemp drill-downDatabase.csfact;PgTopQueriesSqlPG_TEMP_SPILL → CONFIG_PG_WORK_MEMstory w/ offending statementsQueriespartials;PgTargetDrillDownCollector.Queries.cs; tool recsScoreBadActorFact,PgDrillDownCollector.Queries.cs,PgTopQueriesSqlpg_statement_text; queryid-instability prose pinPosturepartialsScoreConfigFact0.9 bandfsync=off→ CRITICAL posture card, zero perf wordsPgTargetBaselineProvider,PgTargetAnomalyDetector,Anomalyscorer/advice,AnomalyThresholds,MetricNames, keys'AnomalyToFamiliesPgBaselineProvider.cs:288,340-365;PgAnomalyDetectorper-detector try/catch;AnomalyGateNine PRs; four can open the same day P merges. Every lane: base
dev, arm squash auto-merge at open, disposition bot findings,no CHANGELOG edits, PARTIAL for #3542 until lane 9 (the last) — lane 9's PR title carries "Fixes #3542" only if every checklist item is
ticked; otherwise the coordinator closes.
6. Adversarial pass — where the doc is most likely wrong, and the pin that holds each
A. Units: sampled PG waits presented on the SQL Server "fraction of period" axis. The doc's §2a-1 mapping puts Aurora
Σ delta_wait_time_us / observedand stockΔsample_count × profile_period_ms / observedunder ONE fact key withis_sampledmetadata.Two ways this embarrasses the product: (i) the stock estimate has a floor — a wait shorter than
profile_period(10 ms default) isinvisible, and a continuous wait quantizes to ±1 period per backend-sample — so the SAME threshold produces different false-negative
rates on the two flavors, and OtterTune's authors would ask "what is your measurement error?"; (ii)
pg_wait_samplingisper-BACKEND-sample — 10 backends in
Lock:relationfor one second = 1,000 samples = "10 s of waiting" — which is correct astime-summed-over-tasks (the SQL Server fraction has the same >1.0 property) but the advice must never say "the server spent X% of the
window waiting". Hold: (1) metadata
estimate_resolution_ms = profile_period_msandis_sampled = 1on every stock fact — pinned by atest asserting BOTH keys present on stock facts and ABSENT on Aurora facts; (2) an advice-text pin that any composed PG wait advice for
an
is_sampledfact contains "estimated from" and never "spent"; (3) aScoreWaitFact-style guard that a sampled fact whosedelta_samples < 3scores 0 (below three samples the estimate has no resolution). Also the NEW trap from step 5:pg_wait_statshas nostored interval (pre-V127 shape) — pin that
PgWaitStatsSqlcomputes intervals viaLAG(collection_time)and excludes a collectionwhose every delta is a first sighting from the sample count (copy the
NULLIF(... , 0)fallback shape fromWaitRateWindowSql).B. The Aurora-vs-stock evidence-grade split is platform-EXCLUSIVE, and the registry may not know which one you are. Aurora waits
come only from
pg_wait_stats(AppliesTo => target.IsAurora), stock only frompg_wait_sampling(extension; "impossible on Aurora").The doc routes on flavor. But
servers.engine_kindis NULL until a connect stamps it (DarlingEngineCapability.cs:90-93), and theMCP path resolves engine from the registry, not the live target. A NULL-kind PG server would take the SQL Server set (correct fallback
for the ENGINE decision — it is today's behaviour), but inside the PG collector routing waits by kind would silently pick the wrong
table on a stale row. Hold: the wait partial reads BOTH sources and emits from whichever has rows in the window (they are mutually
exclusive by construction, so "both non-empty" is itself a finding worth an ERROR log); the engine resolution itself falls back to the
data: if
engine_kindis NULL butpg_database_statshas rows for the server_id andwait_statshas none, take the PG set (pin:ResolveEngineAsync_NullKind_WithPgRowsOnly_TakesPgSet). Coverage honesty (D6) then reports "CPU: not collected on this flavor" fromCollectorEngineCapability.IsCollectedOnEngineKind(PerformanceMonitor.Collectors/CollectorEngineCapability.cs:509) — and for aNULL kind says "flavor unknown" rather than guessing, per that class's own rule (
:135-146).C. Deadlock RATE from
pg_database_stats.deadlocks, exemplars from the log parser — and the two can disagree in public. The doc isright that the counter is complete and the log tail is lossy (
CollectorScheduleDefaults.cs~14 KB/s note). The embarrassment is theconverse: the counter counts every deadlock the engine detected, including ones inside
pg_catalog-only or system sessions the parsernever sees, and is per-DATABASE while
pg_deadlocksrows are cluster-wide; a card saying "12 deadlocks/hour" with 3 exemplars looks likea bug to a reader unless it says why. Also the PG deadlock ALERT counts
pg_deadlocksrows with threshold 1 (PostgresAlertEvaluator.cs:182)— alert and analysis would count different things. Hold: (1) fact metadata
exemplar_countandcounter_countboth present; advice-textpin that the prose contains "the engine counted N; M were captured from the log"; (2) a source pin that
PgTargetFactCollector.Database.csnever reads
pg_deadlocksfor the RATE (DoesNotContain("FROM pg_deadlocks")in the rate SQL) and that the drill-down reads onlypg_deadlocks(never the counter) for exemplars; (3) rate derived with the reset-aware differencing (stats_reset IS DISTINCT FROM LAG),pinned by text — a
pg_stat_reset()mid-window otherwise reads as a negative delta clamped to 0, i.e. "no deadlocks" during exactly thereset-heavy hours a DBA is debugging. Note for D9: the alert's
pg_count_thresholdand the analysis rate bar are DIFFERENT instrumentsby design (the evaluator's own comment
:170-182argues the surfaces differ); do not "share" that constant — share only the threeoutage-predictor constants the doc names.
D (bonus). Composition without RAM. §4b's memory-overcommit check is the doctrine's headline [D4] example and host RAM is not
collected. The doc offers two framings; both can be wrong in a way OtterTune's authors would notice: treating
effective_cache_sizeas"available memory" (it is a planner HINT, routinely 50–75% of RAM and often left at the 4 GB default — the check would fire on every
default install for the wrong reason). v1 should ship framing (b) only — the RAM-free ratio
max_connections × work_mem > k × shared_buffers— with
kstated as a judgment, and NOT ship (a). Hold: a pin thatPgTargetAdvicenever mentionseffective_cache_sizeas a memoryfigure (the word may appear only in
CONFIG_PG_EFFECTIVE_CACHE_SIZE's own convention card). This is v2 content; the pin belongs with it.7. Relationship to #3605 (competitive gap analysis, filed 2026-09-18)
#3605 ("
analyze_serverfor PostgreSQL targets — the inference engine answers 'what is wrong, ranked' for one engine only")is the same problem seen from the competitive side; Erik's cross-link comment proposes it becomes a design input / checklist
item on #3542. Its three contributions and how this plan absorbs them:
PostgresAnalysisNotApplicablecall sites inDarlingWorker" as the exact seams. Confirmed by grep on dev950fad3a:DarlingWorker.cs:2500(scheduled arm),:5936(analyze_nowarm),:8206(theinternal constdeclaration). The design doc's "two sites" counted the two WRITE arms; analyze_server for PostgreSQL targets — the inference engine answers 'what is wrong, ranked' for one engine only #3605's three counts the declaration. Both are
right; the plan deletes all three plus the
PostgresAnalysisStateWrittenlatch (:696, set at:2493-2495and:5930)— so the true seam count the plumbing PR touches in
DarlingWorker.csis six edit sites in four regions (two arms,one latch property, one constant, two rationale comments). The seams analyze_server for PostgreSQL targets — the inference engine answers 'what is wrong, ranked' for one engine only #3605 cannot see from the worker are the ones that
actually decide whether a PG pass produces anything: the
wait_statsspan gate (DarlingAnalysisService.cs:607), thev_wait_statscoverage witness (Analysis facts divide by the time the collector actually observed, and a window with a hole in it says so, so collector downtime no longer reads as improvement (#3538 A2) #3592, §0.2), andFactScorer.ScoreAll's_ => 0.0source fallthrough.through the SAME store and alert path, ship two or three families end-to-end rather than all shallowly"). Adopted as the
plan's structure: lane P declares the WHOLE v1 vocabulary up front (
PgTargetFactKeys— every source, key, advisory root,anomaly parent; the D2 vocabulary made concrete before a row persists) and the whole collect census as stubs; content lanes
then fill one family each end-to-end (collector → scorer → advice → graph → tests). analyze_server for PostgreSQL targets — the inference engine answers 'what is wrong, ranked' for one engine only #3605's "two or three families first"
maps to wave A = lanes 2 (knobs), 4 (vacuum), 8 (posture) — the three with the fewest cross-lane edges and the highest
doctrine weight ([D1], [D3], [D6]) — with 3/5/7 following and 9 last. Persistence/read/notify/MCP reuse is exactly what
D4 (one service) and "zero new tools" already require; analyze_server for PostgreSQL targets — the inference engine answers 'what is wrong, ranked' for one engine only #3605 asks for nothing there the doc does not.
as the threshold-lineage rule in §1 step 1 and its source pin — every PG bar is measured, engine-defined, or marked
unmeasured in code AND in fact metadata. This goes further than Analysis-engine campaign: coverage honesty, measured thresholds, config attribution (adversarial review, 11 findings) #3538 A5 (which re-derives existing bars) because PG has
no bars yet: the rule is that none is born without lineage.
#3605 points D1–D10 already answer: "SAME findings store and alert path" → D4 + §5a reuse (no new tables, no new tools);
"PG engine needs its own fact families" → D1 (separate
PgTargetFactCollectorwith self-pin census) + D2 (pg_/PG_*namespacing so families cannot collide); "statement regressions, autovacuum debt, bloat trajectory, wait fractions, connection
saturation, replication risk" → all six are in the doc's §2/§3 catalogue: autovacuum debt = §3.1 (v1), wait fractions = §2a
(v1, with the sampled/measured split #3605 does not mention), connection saturation = §3.6 (v1), bloat trajectory = §3.4 (v2),
replication risk = §3.5 (v2), statement regressions = §2b plan regression (v3 — needs
pg_plan_capturecorroboration, D8sign-off for anything index-shaped); "measured thresholds" → D5 (shared bands, PG calibrated INTO them) + D9 (alert constants
shared for the outage predictors) + the lineage rule above; "runbook honesty" → the plumbing lane's doc edits
(
docs/postgres-first-target-runbook.md:650-652,Darling/README.md:733,735) — the section keeps saying analysis does notexist until lane P lands, then stops, in the same PR. Nothing in #3605 contradicts D1–D10. Disposition: checklist item on
#3542; close #3605 when lane P merges (the seam-and-plumbing half) with a comment pointing at the lane ledger for the families.
v2+/data-source dependencies (v1 does NOT depend on any of these)
PG_CONNECTION_CHURN/FATAL: too many clientsevent fact — the event-grain evidence for §3.6 saturation's cliff, which v1 can only infer from the session ratio (v2)log_temp_filesparserPG_TEMP_SPILL's drill-down from "statement shapes that spill in general" (temp_blks_written) to "this execution spilled 4.2 GB at 03:07"; v1 keeps the counter-based fact (v2 drill-down)log_autovacuum_min_durationparserPG_MAINTENANCE_SHAPE_SHIFTproperly (run duration vs its own hour×dow baseline — the [D3] "overrunning" arm), which v1/v2 otherwise approximate fromautovacuum workerpresence inpg_session_states(v2)pg_stat_activitypolling into the wait tables whenpg_wait_samplingis absentis_sampled+estimate_resolution_msdesign already has the slot for it (v2, no key change)log_lock_waits,log_temp_files,log_autovacuum_min_duration,log_checkpoints,log_connections,log_min_duration_statementCONFIG_PG_TRACK_IO_TIMING/CONFIG_PG_STAT_STATEMENTS_MISSINGcome from the config snapshot directly; #3607 would let aPG_MONITORING_COVERAGEfinding say WHICH log family is dark and why the matching detector (#3602/#3603) is blind — the D6 coverage-honesty story extended from collectors to target logging (v2)8. The three open questions, called (veto window open)
audit_configfor a PG target → the plumbing lane ships the honestnot_collectedenvelope pointing at theCONFIG_PG_*facts ("refuse what you cannot honor", the payload contract's rule 8). A PG projection inside the same tool changes its contract for every consumer and is a v2 checklist item with its own description change.servers.engine_kindNULL with PG rows present → no inference from data presence (PostgreSQL targets have no UI on either SKU, and both UIs render SQL Server tabs at them #2530: a NULL makes no claim). NULL keeps today's SQL Server component set; the pass's honest message names the missing engine stamp so an operator can see why, instead of "still collecting".pg_wait_statsinterval column → it is already one of Measurement-layer campaign: the delta honesty contract (11 findings, one keystone) #3540's four still-naked families; it is the next rung CANDIDATE, not filed as a build dependency. Lane 5 ships the LAG fallback in the V127COALESCE(stored, LAG)-ready shape so the rung, when it lands, is a one-line change to the read.PostgreSQL targets enter the analysis pipeline: the pass routes by re…
A PostgreSQL target's durability posture is stated, not inferred: fsy…
PG_BAD_ACTOR: one statement's share of the window's execution time be…
The two PostgreSQL knobs learn to speak: shared_buffers and max_wal_s…
The vacuum family of a PostgreSQL analysis pass: a table persistently…
PG_CONNECTION_SATURATION: the harshest PostgreSQL cliff is graded aga…
The temp-spill family of a PostgreSQL analysis pass: spilled bytes pe…
The wait profile of a PostgreSQL analysis pass: type rollups (Lock, L…
erikdarlingdata commentedon Sep 19, 2026
v2 residue (from lane 3 via the between-waves batch, #3688):
pg_database_statshas nonumbackends— a RUNG decision (a universal one-minute client-only session numerator per database, instead of the exception-capture peakspg_session_statesgives the saturation fact). Recorded here, not built; the v1 saturation ratio stays onpg_session_statestotals.The PostgreSQL analysis vocabulary absorbs what six content lanes rep…
PostgreSQL targets get their own baselines and anomalies: five clean …
The Aurora-only-surface census accounts for the wait-profile anomaly …
erikdarlingdata commentedon Sep 19, 2026
Closing — v1 landed and the exit criterion is met, demonstrated on a planted store against the real tool methods; v2/v3 and the lanes' residue move to the follow-on issue #3691.
What shipped (2026-09-18/19, all merged to
dev):servers.engine_kind, thepg_database_statscoverage witness and 24 h gate, thepg_-prefixed vocabulary declared once, one delegating arm per shared switch, tombstone removed,audit_confighonest for PostgreSQL — PostgreSQL targets enter the analysis pipeline: the pass routes by registry engine_kind inside the service, a pg_database_stats coverage witness replaces the tombstone, and every shared switch gains its one pg_ arm so the content lanes can build in parallel (#3542 v1 plumbing) #3665shared_buffers,max_wal_size+ checkpoint / buffer-cache pressure co-fires,PgSettingValue) — The two PostgreSQL knobs learn to speak: shared_buffers and max_wal_size at their shipped defaults root advisory cards and reach the incident line only when requested-dominant checkpoints or a measurably short cache co-fire, with the measured share, WAL bytes per interval and per-arm evidence in the prose (#3542 lane 2) #3671PG_MONITORING_PERMISSIONS) — 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) #3677is_sampled, rollups + standouts, chain edges) — The wait profile of a PostgreSQL analysis pass: Lock, LWLock, IO and IPC rollups with their named standouts as fractions of the wait source's own observed time — Aurora's measured deltas under the three-state interval, or stock's sampling estimate marked is_sampled with its resolution and read only when the Aurora table is empty — so a PostgreSQL operator learns which wait class a backend-equivalent of time is going to, at which evidence grade, and what PostgreSQL's own levers for it are (#3542 lane 5) #3683, The saturation-chain pin moves with the wait family: #3683 made lane 3's commented saturation ↔ Lock-wait edges live and merged before its pin move landed, so dev's PgTargetSessionsTests asserts an edge-less chain that no longer exists — the pin now names the mesh (#3542 lane 5 follow-up) #3686work_memco-fire, thepg_database_statsread (PG_TPS,PG_DEADLOCK_RATE) — The temp-spill family of a PostgreSQL analysis pass: spilled bytes per second of observed time from the reset-aware pg_database_stats difference, work_mem graded only on that evidence and lifted to the incident line only when the spill fires, the offending statements in the drill-down; the same read emits PG_TPS with a half-window trend and the counter-derived PG_DEADLOCK_RATE on the alert band's measured tiers (#3542 lane 6) #3681, PgTargetTempTests: the temp-spill e2e is anchored at the planted window's end, so analyze_server measures the series that was planted rather than one that UtcNow slid past; and the observed-time pin names the one nominal-span use (the trend midpoint) instead of forbidding the expression (#3542 lane 6 follow-up to #3681) #3685fsync,full_page_writes,synchronous_commit; D6 isolation) — A PostgreSQL target's durability posture is stated, not inferred: fsync and full_page_writes off are CRITICAL policy cards, synchronous_commit off names the commits a crash discards, Aurora's managed pair is present and ungraded, and no tuning path can reach any of them (#3542 lane 8) #3669reserved_connections,CONFIG_PG_AUTOVACUUM_OFF/MAINT_WORK_MEMarms,collection_logadmitted to the census, tool recs) — The PostgreSQL analysis vocabulary absorbs what six content lanes reported back: a stable PG_BAD_ACTOR alias the graph resolves to the window's heaviest statement, the two vacuum-side config facts that were inert because no arm ever gave them a base, PostgreSQL 16's reserved_connections in the connection ceiling, and the bad actor's first next-read (#3542, between waves) #3688Exit check (read-only, throwaway PostgreSQL 18 + TimescaleDB, dev @ f887409, real
DarlingMcpTools.*methods):status=findings, fiveCONFIG_PG_*advisories at 0.4 / confidence 0.20, coverage 1.0 over 241 samples,next_tools→get_pg_server_config.PG_CHECKPOINT_PRESSURE → CONFIG_PG_MAX_WAL_SIZE1.30;ANOMALY_PG_TPS1.30 (60/s, 25σ over 295 samples);ANOMALY_PG_SESSION_SPIKE1.30;PG_CONNECTION_SATURATION → PG_WAIT_LOCK_RELATION1.25 (93/97 = 0.959);PG_BAD_ACTOR_70011.25 (60% share, drill-down text);PG_AUTOVACUUM_BACKLOG → CONFIG_PG_MAINT_WORK_MEM0.94;PG_TEMP_SPILL → CONFIG_PG_WORK_MEM0.89 (drill-down names the spilling statement);PG_WAIT_IO_DATAFILEREAD0.71 ("estimated from sampling"); every finding carries advice in PostgreSQL nouns and PostgreSQL-onlynext_tools(14 distinctget_pg_*, zero SQL Server tools); anomaly and saturation share one incident.get_analysis_facts: 28 facts,threshold_lineage = 0on every scored PostgreSQL bar and absent on the engine-defined ones; no fabricated zeros.analysis_findingsrows, allPG_*/ANOMALY_PG_*/CONFIG_PG_*,confidence_basispopulated), mutes (matched_now = 1, the story absent on re-run), occurs (13 → 25),compare_analysisandaudit_configanswer honestly.1187a36e) on the same planted store —analyze_server,get_analysis_facts,compare_analysis,cmpequal after timestamp normalisation.What v1 does not claim: every PostgreSQL bar is unmeasured tonight and says so on its face — the calibration pass over the dogfood PostgreSQL fleet is the first v2 item;
compare_analysisdoes not yet sigma-bandPG_TPS(exit check W1); stock sampled waits have no anomaly baseline; the notify floor on a stock target needs the self-hosted CPU proxy. All on #3691 with path:line.CHANGELOG: second splice - sixty-three entries from sixty-five merged…
audit_config answers a PostgreSQL target with its own settings: the C…