Repository navigation
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
Merged
Merged
Conversation
…WLock, IO, IPC) and named standouts (Lock:relation, LWLock:WALWrite, IO:DataFileRead, IO:WALSync) as fractions of the wait source's own observed time — Aurora's measured pg_wait_stats deltas under the three-state interval (stored → NULLIF, NULL → LAG, a restart collection is not a sample), or stock's pg_wait_sampling estimate marked is_sampled with its resolution and read only when the Aurora table is empty; unmeasured fraction bars with threshold_lineage = 0, a rollup graded only as the unnamed story, co-fires into the write, memory and saturation chains, and advice that says 'estimated from sampling', never 'spent', and names PostgreSQL's lock levers (#3542 lane 5)
Owner
Author
|
Pushed cb643cc: the first CI run was 10,863/10,864 — the one red was lane 3's |
This was referenced Sep 19, 2026
erikdarlingdata
added a commit
that referenced
this pull request
Sep 19, 2026
… PRs across the #3653, #3542 and #3691 waves, #3704, #3710, #3712 and #3735, spliced once (#3751) Sixty-five PRs merged to dev since #3672 (the first splice) across two seats - the coordinator's #3653 brains-review residue lanes and the wave-2 worker's #3542 / #3691 PostgreSQL-target engine lanes - and by rule none touched this file; each returned its entry to a buffer. This is the one splice. Text-only; one file; nothing outside ## [Unreleased] and the definition block moves. [Unreleased] gains a ### Changed between ### Added and ### Fixed (Keep-a-Changelog order) for the nine entries whose subject is a designed behaviour that now behaves differently - routing (#3712 confidence chooses the channel), gating (#3692 tempdb persistence, #3734 blocking/LRQ gates, #3724 peak-and-mean), the wire shape (#3719 one error envelope), the review guard's drift arm (#3673), lineage metadata (#3723) and two grading changes (#3693, #3747). Twenty-two new capabilities append to ### Added (the sixteen PostgreSQL-target fact families and rungs, CONFIG_CHANGED, the recurrence label, the V134/V135 columns, the review traps file, the measurement census); thirty-two defects append to ### Fixed. Order within each section is ascending PR number, as #3672 did. Every entry is one line and verbatim from its buffer (trailing whitespace only). Four PR pairs fold into one entry as the buffers wrote them (#3738+#3746, #3683+#3686, #3681+#3685, #3726+#3729); #3695 carries three entries. #3673 has no issue and cites none, like the file's ninety-six other ref-less entries. Five definitions added ([#3691], [#3704], [#3710], [#3712], [#3735]) at the end of the [Unreleased] run, which stays one ascending run (3517..3735). No archive touched; the split was not run. tools/changelog/changelog_archive.py verify: all PASS (1,483 bold entries, floor 1,329; 1,363 distinct refs resolve; 1,372 definitions; CRLF throughout; 409,263 bytes under the ceiling). Client-hygiene grep over the file: zero hits.
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 has its wait profile — the
pg_waitsfamily (#3542 lane 5, design §2a / §5): four type rollups (Lock,LWLock,IO,IPC) and four named standouts (Lock:relation,LWLock:WALWrite,IO:DataFileRead,IO:WALSync), each keyed throughPgTargetFactKeys.WaitKey(case-insensitive, punctuation-collapsed — Aurora re-cases event names across majors and a key that differed by case would split one wait's history), each withValue= wait time as a fraction of the wait source's own observed time: backend-seconds waiting per wall second, time summed over tasks, the axis the SQL Server wait facts grade on (it exceeds 1.0 with concurrent waiters, as theirs does).Two evidence grades under one key, told apart by metadata, never blended. The design's hardest point (adversarial item A): Aurora's
pg_wait_statsis the engine's MEASURED cumulative wait time differenced at collect (thedm_os_wait_statsmodel); stock'spg_wait_samplingis SAMPLED sightings —Δsample_count × profile_period_ms, an estimate that cannot see a wait shorter than the period, quantises a continuous wait to ±1 period per backend-sample, and counts per backend-sample (ten backends waiting one second is "10 s of waiting"). So:PgWaitStatsSql(Aurora) sumsGREATEST(delta_wait_time_us, 0)per (type, event) over the collections the window can honestly count, with the three-state interval — the storedsample_interval_secondsV128 gave the table →NULLIF(…, 0); a pre-V128 NULL →LAG(collection_time); neverELSE 0— so a restart collection (every row's interval 0) contributes no time and no deltas (its rows are excluded by the JOIN, not just its seconds), exactlyPgAnomalyDetector.WaitRateWindowSql's shape, not re-derived. The header aggregate isLEFT JOINed so the read always returns one row: an EMPTY window (collection_count = 0) is distinguishable from an UNCOUNTABLE one (rows, but every collection a restart) — the former falls back, the latter emits nothing and does not.PgWaitSamplingSql(stock) differences each (event_type, event, query_id) series per consecutive collection with the reader's reset rule (DarlingPgWaitSamplingReader: a count that went backwards is taken whole — everything since the reset — neverGREATEST's silent zero), makes the estimate once beside the count (Σ Δsamples × profile_period_ms, per row, so a period that changed mid-window is honoured per interval), and derives its own observed time fromLAGover distinct collection times (the table is not a delta family and stores no interval). A series' first in-window sighting contributes nothing — the count may predate the window, or the key may have fallen out of the collector's top-500 and returned with history the window did not see.is_sampled = 1andestimate_resolution_ms = profile_period_ms(plusdelta_samples,peak_backends,counter_resets); no fact from the Aurora read carries either (it carriesrestart_collections). The scorer grades both on the same fraction bars with the estimate stated; a sampled fact under three Δsamples scores 0 (below that the estimate has no resolution). The advice's evidence sentence differs by grade: "the engine measured N s of Lock:relation waiting across M completed waits" vs "seen in N backend-samples at a 10 ms sampling period — about T of waiting, estimated from sampling" plus the resolution and the per-backend-sample caveat. No wait block of either grade says the server "spent" time — the fraction is time-over-tasks and can exceed one.Routing is READ-then-fallback, on the data, never on the registry kind.
pg_wait_statsis read first; only when it has no rows in the window ispg_wait_samplingread. The two collectors are platform-exclusive by construction, so the data says which flavour this is;servers.engine_kindis NULL until a connect stamps it and would blind an Aurora target's waits on a stale row (adversarial item B).AnalysisContextis shared with Lite and did not grow an engine-kind field. A source pin holds that the wait partial's code names neitherengine_kind,IsAuroranorEngineKind, and reads the Aurora const before the sampling const.The denominator is the wait source's OWN observed time, and both are stated. The pass's coverage witness is
pg_database_stats(D3, one-minute); on stock the wait series is five-minute and a gap in one is not a gap in the other. Every fact carrieswait_source_observed_ms(the divisor),observed_ms(the witness's),witness_fraction(the same wait over the witness),coverage_fraction,sample_count(countable collections) andcollection_count; the advice states which the fraction is over and, when the two differ by more than 5 %, says so in a sentence.One wait is graded once — a rollup is the UNNAMED story. Executing the first draft's chain pins showed the shape the design did not: a standout's seconds are also inside its type's rollup and a standout's bar is tighter, so a rollup graded on its whole fraction rooted a second story about the same seconds right after the standout rooted the first (the engine walks roots in severity order along one linear path; a rollup cannot lead to a consumed standout). So the collector stamps
max_standout_fractionon each rollup and the scorer yields the rollup (0, lineage still stated) whenever a named standout of its type is itself a finding; the rollup grades on its whole fraction only when the type's waiting is in events the vocabulary does not name (Lock:transactionid,LWLock:BufferMapping, …) — exactly when "the Lock class" is the most specific true thing to say.Valuestays the whole fraction; the profile is not edited, only the grade.Lineage of every bar (
PgTargetThresholdLineageTestsexecuted green)Lock/LWLock/IPC: 0.20 → 1.0 (WaitRollupConcerning/Critical)IO: 0.40 → 2.0 (WaitIoConcerning/Critical)WaitStandoutConcerning/Critical)WaitSampledMinimumSamples = 3WaitCoFireBoost = 0.3(every amplifier)Every graded fact carries
threshold_lineage = 0. The calibrating read is named once in the file: the per-server per-type fraction distribution overpg_wait_stats, 4-hour buckets — the pass #3616 ran over SQL Serverwait_stats. No SQL Server wait constant reused by value: the SQL Server table's pairs are (0.01, 0.05, 0.10, 0.25, 0.30, 0.50, 0.75) and a pin asserts none of the PG bars is one of them. Gates are fractions of observed time; no absolute-ms total anywhere. "Fired" = at or past concerning (0 below it, the convention lane 2 set), so every edge and amplifier readingBaseSeverity > 0on a wait means "a finding".Co-fire gating and the chains
Amplifiers (
WaitAmplifiers, all reading the sibling'sBaseSeverity > 0):LockandLock:relation←PG_CONNECTION_SATURATION(lane 3, landed) andPG_IDLE_IN_TRANSACTION(v2, declared, inert);IO:WALSyncandLWLock:WALWrite←PG_CHECKPOINT_PRESSURE;IO:DataFileRead←PG_BUFFER_CACHE_PRESSURE. Each lifts a concerning wait from 0.5 to 0.65 — above the story line, well short of the notify line; a wait becomes a page on its own fraction, never on a co-fire.Edges,
AddEdgecalls added to the three chain files (every predicate reads the destination's verdict):.Write.cs:IO:WALSync → PG_CHECKPOINT_PRESSURE,LWLock:WALWrite → PG_CHECKPOINT_PRESSURE→ (lane 2's)CONFIG_PG_MAX_WAL_SIZE. Symptom → cause only, and the split when the CAUSE outranks (near-total requested share ×1.3) is pinned as the honest shape: a reverse pressure → wait edge would make the walk prefer the higher-severity wait over the knob and leave the knob to root a third card..Memory.cs:IO:DataFileRead → PG_BUFFER_CACHE_PRESSURE→CONFIG_PG_SHARED_BUFFERS..Saturation.cs(lane 3 landed while this was building; its commented-out lane-5 edge is now live code): a mesh, both directions,Lock:relation ↔ PG_CONNECTION_SATURATIONandLock ↔ PG_CONNECTION_SATURATION— safe here because saturation has no other leaf in v1, so whichever fires harder roots and the other is consumed; pinned wait-led AND saturation-led.Advice (OtterTune doctrine)
Value-stated from the fact: the wait seconds, the observed seconds and collection count, the backend-equivalents, the share of all waiting (CPU excluded) and of its own type, the peak backends and resets on a sampled fact, the restart count on an Aurora one. Each key's block says what the wait IS in PostgreSQL and names its levers with counter-objectives. The SQL Server lock advice does not transfer: the
Lock/Lock:relationblocks say MVCC means readers and writers never queue on each other, that a relation queue is DDL /LOCK TABLE/TRUNCATE/ a conflicting mode held by a parked transaction, and nameget_pg_blockingfor the holder,idle_in_transaction_session_timeout,lock_timeout,CONCURRENTLY— "RCSI" appears nowhere (pinned). Durability settings are not a lever (D6): the WAL-wait blocks name the WAL device, commit batching,max_wal_size,wal_compression,wal_buffersand say in as many words that a commit waiting for its WAL is doing what it promised;synchronous_commit/fsyncare not named in any wait block (pinned). NoCREATE INDEX(D8; pinned). Static (no-fact) blocks exist for all eight keys and point atget_pg_wait_stats/get_pg_wait_sampling.What it does NOT do
pg_stat_activityat 1 s for 30 s of every 5-minute cycle and storesprofile_period_ms = 1000;Δsamples × 1000 msthen covers only the sampled 30 s, so the fraction over the interval is a ~10× floor. The arm cannot be told apart honestly from this read: the instrument token lives incollector_state, which is outside the analysis read's table whitelist, and the reader's own note says guessing from the period is wrong the day an operator sets the extension to a second. The facts stateestimate_resolution_ms = 1000and the advice calls the estimate a floor; the fix is an out-of-lane item below.query_iddimension is a later slice); noANOMALY_PG_WAIT_PROFILE(lane 9); no wait-profile drill-down tool; no new key, source, schema or knob. No IPC standout — the design names none.Blast radius
Four lane-5 partials filled (
PgTargetFactCollector.Waits.cs,PgTargetScorer.Waits.cs,PgTargetAdvice.Wait.cs), additiveAddEdgecalls in the three chain files (lane 3's saturation file: its commented edge made live, its header brought current), two new test files, and one deliberate pin move inPgTargetSharedSwitchRoutingTests(the wait representative flips fromNulltoNotNull; a key outside the vocabulary still answers null). Zero shared switches, keys or the census touched. Every other family's facts are unaffected; a store with no wait rows emits nothing (the plumbing e2e'sAssert.Single(facts)still holds — executed).Test plan
New (compile-verified AND executed here through a net10.0 xunit.v3 harness,
AssemblyName=Darling.Tests, against a throwawaytimescale/timescaledb:2.28.1-pg18ondesktop-linuxmigrated to V131 — 177/178 green across this lane's two files plusPgTargetFactCollectorTests,PgTargetSharedSwitchRoutingTests,PgTargetThresholdLineageTests,PgTargetPostureIsolationTests,PgTargetPostureTests,StoreSqlClockDisciplineTests,LivePostgresCollectionHygieneTests,LiveCleanupConversionRatchetTests,AnalysisPassCommandTimeoutTests,AnalysisPassTokenThreadingTests,RepoFileAdoptionTests,FactSourceRegistryTests,CommentFilterAdoptionTests,AsOfWindowAnchorTests; the one red isLivePostgresCollectionHygieneTestsflaggingLivePostgresStoreFixture, a harness artifact — that fixture is not compiled into the harness so its type-name exemption cannot resolve; this lane's live class carries[Collection("live-postgres")]. First CI execution is the Windows job.)PgTargetWaitTests(14): both reads' shape (three-state interval,NULLIF,LAG, noELSE 0, restart exclusion, the reader's reset rule, the estimate beside the count,server_id = $1, membership inAllSql, data-routing source pin); the grouping and estimate arithmetic executed on arranged rows through the collector's internal seam (rollups, standouts, shares with CPU excluded and BufferPin included, both denominators,max_standout_fraction,is_sampled+ resolution on EVERY sampled fact and on NO Aurora fact,peak_backendsper key); empty / uncountable / unknown-type emission; the bar table (every v1 key, unknown → null, re-cased key → same entry, no SQL Server value); the ramp (0 below, 0.5 at, 0.75 mid, 1.0 at critical and past it, lineage stamped, unobserved → 0); sampled = measured on the same bars and the three-sample floor; the rollup yield rule; every amplifier fires only on the sibling's verdict; the saturation mesh both ways; the write chain wait-led and the pinned split when the cause leads; the memory chain; advice pins over all 8 keys × 3 shapes (no "spent" / "RCSI" /CREATE INDEX/synchronous_commit; "estimated from sampling" + resolution + per-backend-sample caveat on sampled; "the engine measured" on Aurora; value-stated numbers; static blocks name the PG reads); the relation-lock block's levers and co-fire sentence; the witness-divergence sentence only when the denominators differ; the shared entry points answer this family's block.PgTargetWaitLiveTests(gated,[Collection("live-postgres")],LiveStoreCleanup): plants an Aurora-stamped server with 241 one-minutepg_wait_statscollections carrying all three interval states (10 pre-V128 NULL rows → LAG, a restart collection at minute 120, stored 60 elsewhere) and six events incl. CPU, and a stock server with 49 five-minutepg_wait_samplingcollections (10 ms period, cumulative, a secondLock:relationseries that resets mid-window, a CPU/Running series), plus the 25 h witness series for both; asserts the collector alone — AuroraLock:relation= 0.20 over 14,340 s (239 countable of 241; 1 restart), no sampled keys, shares 12/57 and 12/15, rollups and the other standouts; stockLock:relation= 302,200 Δsamples × 10 ms over 14,400 s through the fallback,is_sampled = 1,estimate_resolution_ms = 10, 1 reset taken whole,peak_backends = 3per key — then drives the REALanalyze_serveron both: Aurora rootsIO:DataFileReadandLock:relation(the rollups, whose standouts fired, root nothing;IO:WALSyncat 0.10 under its bar roots nothing), the relation card's prose says "the engine measured … 0.2 of one backend … 80 % of its own wait type … restart or first sighting", never "estimated" / "spent" / "RCSI",next_tools=get_pg_wait*; stock rootsLock:relationwith "estimated from sampling … backend-samples at a 10 ms sampling period … cannot see a wait shorter than 10 ms … reset 1 time(s)";get_analysis_facts source=pg_waitsshows 7 Aurora facts all withthreshold_lineage = 0and none withis_sampled, 4 stock facts all withis_sampled = 1and resolution 10.Two defects the execution caught that compile-only would have shipped: (1) a rollup and its standout rooting two stories about the same seconds — now the yield rule; (2)
peak_backendstaken as the window-wide max, so the CPU/Running series' backend count leaked into every wait fact — now per key.Builds, zero warnings of ours:
PerformanceMonitor.Analysis,PerformanceMonitor.Darling.Analysis,Darling.Tests,Lite.Tests(the one xUnit2000 inLogTailOverlapThresholdPinTests.cs:95is pre-existing on dev),deprecated/Dashboard.Tests, with-p:EnableWindowsTargeting=truewhere windows-targeted.Which component(s) does this affect?
How was this tested?
Above. Throwaway TimescaleDB 2.28.1 / PostgreSQL 18 container on Docker context
desktop-linux, torn down. No production store or fleet server was read.Checklist
dotnet build -c Debug)pg_vocabulary the shared switches route by prefix)Partial for #3542 (lane 5, the wait profile). CHANGELOG deliberately untouched — the coordinator splices this wave's entries.