issue-3653 A8 option B: PG-target I/O, replication and blocking anomaly detectors move to per-hour tiles (lane L3b) - #4170
Conversation
a6e3904 to
3a7e6cf
Compare
Lane T4170-4 handoff (tests-only, did not complete — stopped at context/time wall)Environment set up and confirmed working:
Failure details captured live:
Not done — the actual fixture rebuild per the TESTS-COMMON recipe (read the ACTUAL start-bucket σ_eff post-seed via Next lane should:
No code was pushed (nothing to push — investigation only, no edits made). This comment is the full handoff per the brief's reporting rule (no PR to open; commenting on existing #4170 instead per "an issue comment if there is no PR" analog since I made no commits to open a new PR from). |
… sessions move to per-hour tiles (lane L3b) Each hour of the analysis window is now scored against its own hour-of-week baseline through AnomalyGate.EvaluateTiles. When no tile clears MinTileSamples, the detector falls back to today's whole-window EvaluateZScore path against the start bucket (never-blind rule). Product code only in this commit; tests to follow.
…ation, blocking)
Step 0 check: rated_samples/sample_count ordinals in the tiled I/O,
replication and blocking window reads still land on the COUNT(*) column
(samplesOrdinal: 3 in all three ReadTile calls) — no fix needed.
Pin fixes:
- PgTargetBetweenWavesV2Tests: the I/O detector's window read now
legitimately contains date_trunc('hour', ...) via WindowTiles.LocalHourSql
(the tile GROUP BY key), so the DoesNotContain pin is now Contains, with
a comment explaining the quarter-hour quantity is unchanged.
- PgTargetAnomalyTests: the blocking/I/O/replication never-blind-fallback
arm now reads whole.Peak/whole.Mean directly (peakBlocked/peakMsPerRead/
peakBytes locals are assigned AFTER the arm choice), so the two source
pins are updated to match.
…l bug found live) WindowCommand only binds $1..$3; the tiled I/O, replication and blocking window reads now group by WindowTiles.LocalHourSql, which references $4..$6 (the analysis window's clock, from map.WindowClock). Neither PostgreSQL nor DuckDB rejects an under-bound statement referencing only $1..$3 while more parameters exist elsewhere in scope — it fails only when the statement ITSELF references a parameter that was never bound. Here the opposite: the statement references $4..$6 that were never bound at all, so every run failed at execution. Bound in each family file (not in PgTargetAnomalyDetector.cs, which is off-limits this lane) right after WindowCommand, mirroring PgTargetBaselineProvider's own bind order. Confirmed live on timescale/timescaledb:2.28.1-pg18: all three families' live tests (PgTargetIoTests, PgTargetReplicationTests, PgTargetBlockingTests) failed before this fix and pass after. One expectation changed (not a pin, a live e2e assertion): PgTargetBlockingTests.ThirtyOneDaysOfLightBlocking...: avg_blocked_sessions now comes from the worst-scoring TILE's own mean (one target-local hour of the plant's 4h chain) instead of the whole window's mean across all four hours, so the pinned range moved from 2.9-3.0 to 2.8333... (widened the assertion to 2.8-3.0 with a comment explaining why).
…sts (WIP, 3/6 green)
…detector's own frame (lane T4170-3)
- Io_TwoHourShift and Blocking_TwoHourShift: delete their dev-comparison in-test assertion (they fired; only that assertion was wrong). The mutation check (EvaluateTiles -> null) proves the same thing more strongly. - ReplayLag_TwoHourShift: raise mu/sigma clear of PgReplayLagBytesFloor (16 MiB), delete its dev-comparison block, and fix fire_threshold to the family's modified-z cutoff (it carries robust statistics like the I/O and blocking families, not the classical default). All 6 tests pass live against timescale/timescaledb:2.28.1-pg18.
df7beaf to
6b73eec
Compare
…s to 8==8 Mirrors #4169's conformity pass for the new batch/sessions/query/memory families, mechanical only, no behaviour change: - Lite's four new fallback EvaluateZScore calls used inline whole.Peak/ whole.Mean instead of the named peakBatch/avgBatch (etc.) locals that get assigned the SAME values right after -- reordered the assignment before the call and pointed the call at the named locals, matching PgAnomalyDetector's exact text. - Renamed Lite's query-duration detector's queryThreshold local to queryDurationThreshold (used only within that one method, 3 sites) to match pg's name. AnomalyGate_EvaluateTilesCalls_AreFourPerSku_AndMatchBetweenPgAndLite, per the coordinator's instruction now that both sides carry all eight families: tightened the total-count check from >= 4 back to == 8, and added the four missing byte-identical Assert.Matches pairs (batch, sessions, query duration, memory), mirroring the existing cpu/wait/io pattern in the same test. Verified: real dotnet build (Darling.Tests, Lite.Tests) clean; the two touched cross-file regex tests (this census test and the wait-rate LiteVerbatim test, which reads Lite's source text too) pass locally against the built Darling.Tests DLL. Full local Darling.Tests run: 13694 total, 214 failed (same known Windows-only/WPF baseline as #4170's independent run moments ago), 0 new failures, none in the tile/anomaly/baseline space. Lite.Tests can't run directly on this Mac (Microsoft.WindowsDesktop.App runtime missing) -- Windows CI is the arbiter for its behaviour suite, as for the rest of tonight's Lite work.
…es (#4176) * 3653 B L4b step 1: Lite batch requests and sessions detectors move onto per-hour tiles * 3653 B L4b step 2: Lite query duration and memory detectors move onto per-hour tiles * 3653 B T4176: behaviour tests for Lite's tiled anomaly gate (batch requests, sessions, query duration, memory) * 3653 B T4176-2: fix memory seed SQL param count; fix query-duration floor undershoot; drop batch dev-comparison per coordinator ruling * 3653 B T4176-2: drop memory test's dev-comparison assertion (fires correctly; z-check mis-sized), per coordinator ruling * 3653 B L4b: conform to pg's exact call/variable shapes; tighten census to 8==8 Mirrors #4169's conformity pass for the new batch/sessions/query/memory families, mechanical only, no behaviour change: - Lite's four new fallback EvaluateZScore calls used inline whole.Peak/ whole.Mean instead of the named peakBatch/avgBatch (etc.) locals that get assigned the SAME values right after -- reordered the assignment before the call and pointed the call at the named locals, matching PgAnomalyDetector's exact text. - Renamed Lite's query-duration detector's queryThreshold local to queryDurationThreshold (used only within that one method, 3 sites) to match pg's name. AnomalyGate_EvaluateTilesCalls_AreFourPerSku_AndMatchBetweenPgAndLite, per the coordinator's instruction now that both sides carry all eight families: tightened the total-count check from >= 4 back to == 8, and added the four missing byte-identical Assert.Matches pairs (batch, sessions, query duration, memory), mirroring the existing cpu/wait/io pattern in the same test. Verified: real dotnet build (Darling.Tests, Lite.Tests) clean; the two touched cross-file regex tests (this census test and the wait-rate LiteVerbatim test, which reads Lite's source text too) pass locally against the built Darling.Tests DLL. Full local Darling.Tests run: 13694 total, 214 failed (same known Windows-only/WPF baseline as #4170's independent run moments ago), 0 new failures, none in the tile/anomaly/baseline space. Lite.Tests can't run directly on this Mac (Microsoft.WindowsDesktop.App runtime missing) -- Windows CI is the arbiter for its behaviour suite, as for the rest of tonight's Lite work.
Part of #3653 (A8 option B, lane L3b-2)
What this pass did (finishing #4170)
Step 0 (check): all three tiled window reads'
rated_samples/sample_countCOUNT(*) column is still atsamplesOrdinal: 3in everyWindowTiles.ReadTile(...)call for I/O, replication and blocking — no fix needed.Step 1 (pins/census): fixed three pins the tile move falsified:
PgTargetBetweenWavesV2Tests.TheIoBaselineAndDetector_ShareTheQuarterHourGrainAndTheReadsFloor...: the detector's SQL now legitimately containsdate_trunc('hour', ...)(theWindowTiles.LocalHourSqltile key), so theDoesNotContainassertion is nowContains, with a comment explaining the underlying quantity is still the quarter-hour quotient.PgTargetAnomalyTests.TheDetector_Fences...: two source-pattern pins for the I/O and blocking never-blind-fallback arm expectedAnomalyGate.EvaluateZScore(baseline, peakMsPerRead, avgMsPerRead, ...)/(baseline, peakBlocked, avgBlocked, ...). Those locals are now assigned AFTER the arm choice, so the fallback arm readswhole.Peak, whole.Meandirectly — updated both pins accordingly (also covers replication'speakBytes/avgBytes).Darling.TestsforratedSamples/window_samplesand the const names; thePgTargetAnomalyTests.cs:291-315window-SQL const list and theLocalClockBucketKeyTestsunkeyed/keyed$1..$6/$1..$7census are both scoped to baseline-provider statements, not detector window reads — I found no existing census of detector-read parameter counts to extend, so I did not add one; noting this rather than inventing a new arm outside the brief's ordered steps.PgTargetBaselineProvider.Replication.cs's doc still names the replication window read correctly; unchanged.Step 2 (live PG) — found and fixed a real bug.
WindowCommand(in the off-limits root file) only binds$1..$3. All three tiled reads now group byWindowTiles.LocalHourSql, which references$4..$6(the analysis window's clock). Live ontimescale/timescaledb:2.28.1-pg18, all three families' e2e tests failed at execution before this fix (Npgsql: unbound parameter). Fixed by binding$4..$6frommap.WindowClock(mirroringPgTargetBaselineProvider's own bind order) directly in each family file —PgTargetAnomalyDetector.Io.cs,.Replication.cs,.Blocking.cs— since the root file is L3a-2's to edit, not mine.One live e2e expectation changed (not a pin — a planted-data assertion):
PgTargetBlockingTests.ThirtyOneDaysOfLightBlocking...:avg_blocked_sessionsnow comes from the worst-scoring TILE's own mean (one target-local hour of the plant's 4h chain) instead of the whole window's mean across all four hours. Was pinned 2.9–3.0; the worst tile's hour reads 2.8333... — widened to 2.8–3.0 with a comment explaining why. This is the tile design working as intended (§1: report the worst tile's own values), not a defect.Live run after both fixes:
PgTargetIoTests,PgTargetReplicationTests,PgTargetBlockingTests,PgTargetBetweenWavesV2Tests,LocalClockBucketKeyTests,PgTargetAnomalyTests— 168 total, 0 failed, 0 skipped (DARLING_TEST_PG set). Same six classes with DARLING_TEST_PG unset: 168 total, 0 failed, 6 skipped (the live-only tests skip cleanly, as expected).Step 3 (timing). Seeded 24h of one-per-minute
pg_io_statsrows (server_id 999001) on the same rig. Timed the tiledIoLatencyWindowSql(bound$1..$6) against dev's pre-tileIoLatencyWindowSql(git showad89ad83a^:...Io.cs, bound$1..$3only), 5 runs each viaPREPARE/EXECUTEwith\timing:The tile grouping adds roughly +0.2 ms at 1,440 rows/24h — the extra
date_truncGROUP BY key over an already-small per-server row count. Not a meaningful cost at this scale; no index or plan change needed.Step 4 (full suite): ran once with
DARLING_TEST_PGunset: 13660 total, 214 failed, 730 skipped. 214 matches the known dev Windows-only-failure baseline on this Mac exactly (all failures areViewerDrillDownTests/WPF-dependent, e.g.ComputeOverlayPoints_PicksTheFieldMatchingTheSlicerMetric_AndDropsZeroCyclesvariants andSortByProperty_UnknownProperty_LeavesOrderUnchanged) — no new regressions outside that set. Did not build/run Lite or Lite.Tests (out of this lane's scope — PG-target only).What I did NOT verify
$4..$6reference census extension mentioned in the brief forLocalClockBucketKeyTests— I found no existing detector-window-read arm to extend (see Step 1 notes above); a follow-up lane should confirm whether one is wanted.CHANGELOG entry
Behaviour tests (T4170-2, tests-only lane)
Cherry-picked ab24e14 (T4170's test file) from
t4170ontofeat/3653-b-l3b, then fixed compile/schema/gate bugs so the tests run live. Status: 3 of 6 green; 3 still failing on live PostgreSQL, unresolved (context-wall stop, not RED-pushed).What was wrong and fixed
pg_database_stats, the universal one-minute seriesPgTargetAnomalyDetector.HasBaselineDataSqlrequires 30 days of before ANY family fires. AddedPlantDatabaseStatsSpanAsync(hourly rows, baselineStart..windowEnd) and call it from every test; added the DELETE to each cleanup helper.collection_loguseslog_id, notcollection_id(blocking test's log insert).pg_blocking_edgeshas noblocked_query_start/blocked_wait_start/blocking_query_start/blocking_xact_start/lock_type/lock_mode/relation_namecolumns — trimmed the insert to the columns that exist.fire_threshold. I/O and blocked-sessions both carry robust statistics (RobustTierScaffold), so tiles grade onAnomalyThresholds.ModifiedZThresholdFor(metric)(3.5), notDefaultDeviationThreshold(2.0). Fixed the shift (4h), the 24h N-aware, and the blocking scenario's assertions to use the modified cutoff, and re-derived the "whole-window mean sits below k" comparisons off the same corrected k.Still failing, unresolved (stopped at the context wall)
Io_TwoHourShift_FiresOnTheTile_...: the whole-window-mean-below-k assertion still fails (4.86 vs k=3.5) — the μ+3σ arithmetic assumption in the test comment may not hold now that the reads floor / tick grain interacts with the modified-z baseline differently than assumed. Needs a fresh look at what the actual window mean and baseline dispersion are.ReplayLag_TwoHourShift_FiresOnTheTile_...: stillAssert.Singlefinds zero anomalies — the replay-lag family may have its own floor/precondition issue not yet diagnosed (possibly the reads-floor equivalent, or a scoring/robust-tier interaction).Blocking_TwoHourShift_FiresOnTheTile_...: same whole-window-mean-below-k class of failure as I/O, now that the cutoff is corrected to 3.5.Live run
Docker
timescale/timescaledb:2.28.1-pg18on port 55891, roledarling. Ran targeted class only (-class 'Darling.Tests.PgTargetTileBehaviourIoReplicationBlockingTests'); did NOT reach the mutation check (step 6) or the full suite (step 7) before the context wall. Container removed at stop.Not done (handoff to whoever continues this PR)
AnomalyGate.EvaluateTiles→ null, confirm scenario 1 fails and the lone-spike test still passes; revert and rebuild.*PgTargetIo*,*PgTargetReplication*,*PgTargetBlocking*live classes, then the FULL suite once withDARLING_TEST_PGunset (baseline: 214 Windows-only failures on this Mac).Branch
feat/3653-b-l3b(PR #4170), plain pushes only, no new branch/PR opened, per brief.Lane T4170-3 report (tests-only, unfinished — handoff below)
What I verified, with
docker run timescale/timescaledb:2.28.1-pg18on a free port andDARLING_TEST_PGset:Ran
PgTargetTileBehaviourIoReplicationBlockingTestslive before touching anything. Result: 3/6 pass, and the 3 shift tests(
Io_TwoHourShift…,ReplayLag_TwoHourShift…,Blocking_TwoHourShift…) all fail — but not the way the brief anticipated.Per shift test, does the shift FIRE?
Io_TwoHourShift_FiresOnTheTile_WhereTheWholeWindowMeanWouldNotHaveFired: FIRES.tile_local_hour,tiles_scored == 4,tiles_fired == 2, andfire_thresholdall pass. Only the LAST assertion (the dev-comparison) fails.ReplayLag_TwoHourShift_FiresOnTheTile_WhereTheWholeWindowMeanWouldNotHaveFired: DOES NOT FIRE.Assert.Single()onanomaliesfinds an empty collection — the detector emits noAnomalyReplicationLagfact at all. This is a (b)-stage failure,which per the brief's step 2 is evidence for the other lane's SQL-Server-harness diagnosis. I did not get to the
*TileWindowSql+AnomalyGate.EvaluateTilesrow dump the brief asks for at that point — ran out of budget first.Blocking_TwoHourShift_FiresOnTheTile_WhereTheWholeWindowMeanWouldNotHaveFired: FIRES (tile assertions (a) and (b) allpass,
fire_thresholdmatches). Only the dev-comparison (c) fails, and its z came back as 50 — the baseline dispersion isdegenerate (looks near-zero), which points at the rounded/GREATEST(0, ROUND(...)) synthesis collapsing the sine noise into too
few distinct integer values for a trustworthy MAD.
Fix applied (partial): I changed all three dev-comparison assertions to compute z in the DETECTOR'S OWN FRAME — robust
(
Median/EffectiveRobustSigma) when the bucket has one, classical (Mean/EffectiveStdDev) otherwise — instead of alwaysusing
Mean/EffectiveStdDev. This was necessary (the I/O and Blocking families grade tiles on the robust modified-z cutoff,so comparing the classical frame proves nothing about the frame the detector actually uses) but not sufficient: after
rebuilding and re-running, all three dev-comparison assertions STILL fail — the z values (50, 5.98, and the Io one) are still
above their cutoffs. That means the test's own Δ (a 2-hour shift at
μ + 6σinside a 4-hour window) genuinely produces awhole-window mean whose z clears k in the robust frame too — the brief's step 1 instruction ("if (c) shows dev WOULD have
fired, shrink the shift's share of the window... until it doesn't") still needs to be executed, and I ran out of budget before
doing it.
Not done (handoff to the next lane / coordinator):
Io_TwoHourShift…andBlocking_TwoHourShift…per the brief (e.g. 2 shiftedhours of a 6h window, or a smaller Δ that still clears k per tile) until the dev-comparison passes, then re-verify (a)/(b)
still hold with the new numbers.
Blocking_TwoHourShift…specifically: the rounded/floored synthesis (GREATEST(0, ROUND(μ + σ·sin(n)))) may need alarger σ or a different noise shape so the baseline bucket has a non-degenerate MAD — a z of 50 against the classical-or-
robust frame both being consulted suggests the dispersion is pinned near the
AbsStdDevFloor, not a real measurement.ReplayLag_TwoHourShift…not firing at all is the (b)-stage failure the brief said to treat as evidence for the otherlane's SQL-Server-harness diagnosis. The brief's step 2 asks for a
*TileWindowSql+AnomalyGate.EvaluateTilesrow dumpat this point — not done. Worth checking first:
ReplayLagWindowSql'sMAX(replay_bytes_behind)pick vs. the plantedmu + 6*sigma = 1,835,008bytes vs.PgReplayLagBytesFloor(16 MiB) — the shift value is well UNDER that floor, which byitself would suppress a fire on the peak-clears-floor gate. That may be the whole story for this one, independent of the
tiling bug the other lane is chasing.
*PgTargetIo*/*PgTargetReplication*/*PgTargetBlocking*live, then the fullsuite) not started.
Container:
t41703-pgwas stopped and removed before I finished; no live PG rig left running.Commit:
58d19e674onfeat/3653-b-l3b, plain push, no force-push, no new branch/PR.CHANGELOG entry
(none — tests only, not yet green; no user-facing change to log)
Behaviour tests
PgTargetTileBehaviourIoReplicationBlockingTests.cs(58d19e6 → df7beaf). All 6 tests pass live againsttimescale/timescaledb:2.28.1-pg18.Io_TwoHourShift_FiresOnTheTile_WhereTheWholeWindowMeanWouldNotHaveFiredIo_TwoHourShiftInATwentyFourHourWindow_Fires_WithTheNAwareRaisedCutoffIo_FallbackPath_FiresThroughTheWholeWindow_WithNoTileKeysAtAllReplayLag_TwoHourShift_FiresOnTheTile_WhereTheWholeWindowMeanWouldNotHaveFiredBlocking_TwoHourShift_FiresOnTheTile_WhereTheWholeWindowMeanWouldNotHaveFiredChanges:
Io_TwoHourShift…andBlocking_TwoHourShift…: both fired correctly; only their in-test dev-comparison assertion(recompute the whole-window mean's z and assert it's below k) was wrong. Deleted that block from each, replaced
with a one-line comment pointing at the mutation check as the real proof dev's whole-window gate would not have
fired.
ReplayLag_TwoHourShift…: one attempt, fixed on the first try. The fixture's μ (1 MiB) was underPgTargetScorer.PgReplayLagBytesFloor(16 MiB), so the shift never cleared the magnitude floor and the testdidn't fire. Raised μ to 20 MiB and σ to 2 MiB (μ + 6σ well clear of the floor, μ − shift still real spread).
Also deleted its dev-comparison block. It fired once the fixture cleared the floor; the ONLY other break was
fire_thresholdasserting the classical default (2.0) instead of the family's modified-z cutoff (3.5) — replaylag carries robust statistics like the I/O and blocking families, so fixed that assertion too. No test was
deleted; the ONE-attempt rule was satisfied on the first try.
Mutation check: made
AnomalyGate.EvaluateTilesreturnnullunconditionally (uncommitted, reverted beforethe final build). Result: all 4 shift tests (
Io_TwoHourShift…,Io_TwoHourShiftInATwentyFourHourWindow…,ReplayLag_TwoHourShift…,Blocking_TwoHourShift…) FAILED as expected — every family fell back to today'swhole-window path and none of them fired, proving the tile split (not the fallback) is what makes each shift
detectable.
Io_FallbackPath_FiresThroughTheWholeWindow_WithNoTileKeysAtAllstill PASSED (it already exercisesthe fallback path by design — under the minimum tile-sample floor, not through
EvaluateTiles). Reverted, rebuiltwith
--no-incremental, re-ran: all 6 green again.Live runs (
timescale/timescaledb:2.28.1-pg18,DARLING_TEST_PGset):PgTargetTileBehaviourIoReplicationBlockingTests: 6/6 passed.PgTargetIoTests,PgTargetReplicationTests,PgTargetBlockingTests: 104/104 passed.Full suite (
DARLING_TEST_PGunset): Total 13666, Failed 214, Skipped 736, Not Run 2 — the 214 count matchesdev's known Windows-only baseline on this Mac; no new failures, none in the touched file or its sibling classes.
Not verified: Windows CI itself (this Mac can't run the WPF/Windows-only classes); relies on the 214 baseline
holding.