Repository navigation
Drop an empty superseded baseline aggregate on sight (#4289) - #4292
Conversation
JudgeSupersededBaselineRelationAsync's coverage rule can never resolve against an empty successor (a NULL oldest bucket never compares <= any horizon), so a legacy perfmon_baseline/wait_stats_baseline that holds no rows itself stayed forever on a store nothing fed. Probe the legacy CAGG's own row count through its view before the coverage read: empty drops on sight, whatever the successor holds. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
Focused review at 0793909: the empty-legacy dropScope: FindingsHighNone. MediumNone. Low
Q1. After the drop, does any code path still name the legacy relation?Nothing found. I read these:
Q2. Can the probe say "empty" while the legacy holds history that no other relation has?Nothing found. The reason:
Q3. Can the probe run before a legacy aggregate is first refreshed, on a store with source rows?It can run then, but a drop at that time loses nothing.
Q4. Log text, the neutral
|
| Arm | Pure test | Live test |
|---|---|---|
| Empty legacy, empty successor: drop | RetirementCondition_EmptyLegacyAggregateDropsOnSight_RegardlessOfSuccessor (BaselineSupplyTests.cs) |
EmptySupersededBaselineLiveTests, on its own scratch database |
| Empty legacy, short successor: drop | The same pure test, with successorOldest 30 days back |
None. The judge skips the successor read after it finds an empty legacy, so the live empty/empty test runs the same path. |
| Legacy with rows, short successor: no drop | RetirementCondition_HoldsOnlyOnceTheSuccessorCoversTheTier |
Day-one assertions, RetiredBaselineAggregateTests.cs:301-310, now with LegacyHoldsRows true |
| Probe error: no drop | None | None (Low 1) |
Clears for the build: yes. The probe cannot report "empty" while the legacy holds history that raw and the successor lack. No reader breaks after the drop. The two Lows change no behavior today.
…le comments Low 1: TimescaleSupport.cs:1331 read the has-rows probe answer with `is true`, so any non-true answer (including a future null from a swallowed error) reads as "empty" and drops a legacy that holds rows. Changed to `is not false`, so only a definite false can mean empty. Added an own-store live test, EmptySupersededBaselineLiveTests.Legacy ProbeFails_LegacySurvives_AgainstDevPostgres: a second connection holds ACCESS EXCLUSIVE on the legacy relation while the judged connection carries a 1-second lock_timeout, so the has-rows probe times out. The lock releases on its own 1.5s timer, independent of the drop pass, so a regression that swallows the probe error and still attempts the drop gets a real chance to complete once unblocked -- proven by temporarily adding such a catch to JudgeSupersededBaselineRelationAsync: the test failed (dropped=1). Removed the catch: it passes (dropped=0, legacy survives). Low 2: corrected three TimescaleSupport.cs comments (the registry summary's first sentence, the "the same pass also walks" paragraph, and the SuccessorShort/Drop enum member docs) that still described the drop as coverage-only, and fixed RetiredBaselineAggregateTests.cs's comment naming the wrong test class for the #4289 empty-legacy test (it is in EmptySupersededBaselineLiveTests, not RetiredBaselineAggregateTests). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ
|
Pushed commit 92bcaa1 on Low 1: probe answer must mean "holds rows" only when definitely false
Low 2: stale "coverage only" commentsAdded "or it holds no rows (#4289)" (in each location's own words) to:
Build and test (rig: PostgreSQL 18.6 + TimescaleDB, port 55993, UTC)
Ran as MTP executables with
No local full suite (CI runs it after this push, per the brief). One process noteThe brief's commit trailer named a different Rig stopped ( |
…successor is short (#4441) Adds a live pin that a superseded baseline aggregate which still holds rows is kept while its successor is short. - EmptySupersededBaselineLiveTests.NonEmptyLegacyAggregate_SuccessorShort_IsKept seeds a legacy wait-stats baseline continuous aggregate with rows, and a successor whose oldest bucket is recent. - JudgeSupersededBaselineRelationAsync returns SuccessorShort, with the legacy recognised as a continuous aggregate that holds rows. - DropRetiredBaselineAggregatesAsync drops nothing: the legacy aggregate still exists and still returns rows. - The existing tests covered only the empty-legacy case. Forcing legacyHoldsRows to false in JudgeSupersededBaselineRelationAsync, which treats every legacy as empty, left them green. This pin fails under it: Expected SuccessorShort, Actual Drop. - Test-only. Refs #4292
Closes #4289.
Why
JudgeSupersededBaselineRelationAsyncdropped a legacyperfmon_baselineorwait_stats_baselinecontinuous aggregate only once its successor's oldest bucket reached the tier horizon. On a store where nothing ever fedwait_statsorperfmon_stats, the successor held no rows, so its oldest bucket wasNULL.NULLnever compares<=any horizon. So the verdict wasSuccessorShorton every start, and the empty legacy aggregate never dropped, nor did its refresh, retention and compression jobs. #4289's store had 26 continuous aggregates where every other store has 24.The wait protects history that a legacy aggregate holds and its successor has not backfilled yet. An empty legacy aggregate holds no history, so the wait does not apply to it.
What changes
In
Darling/PerformanceMonitor.Darling.Storage/TimescaleSupport.cs:JudgeSupersededBaselineRelationAsyncnow asks whether the legacy aggregate holds any rows, through its own view:SELECT EXISTS (SELECT 1 FROM collect.<legacy>)(BaselineRelationHasRowsSql). It asks right after it confirms the legacy is a continuous aggregate, before it reads the successor's coverage. An empty legacy aggregate goes straight toDrop, and the coverage read never runs for it.materialized_only = false, so on a store with source rows the probe sees them and nothing drops.SupersededBaselineRelationDropsAt, the pure rule, takes a newlegacyHoldsRowsargument. A continuous aggregate with no rows drops, whatever its successor holds. One with rows keeps the tier-coverage rule. A plain view is not affected.SupersededBaselineVerdictcarriesLegacyHoldsRows, so the live tests can check it and the drop's log line gives the real reason ("it held no rows through its own view"). Before, that line was built from the successor'sNULLoldest bucket.LegacyAbsent,SuccessorAbsent, or the Retire the orphaned cpu_utilization_baseline / file_io_baseline continuous aggregates (#1995 cleanup) #2007RetiredBaselineRelationslist. The affected store cleans up on its next start, with no manual step.Lite has no continuous aggregates (
Lite/Analysis/BaselineProvider.cs:93), so it has nothing to mirror.Review fixes
A focused review cleared the PR and found two Lows. Commit
92bcaa19fixes both:is not false, notis true. Only a definitefalsefrom theEXISTSprobe means empty. Any other answer, such asnull, counts as "holds rows", so it can never drop an aggregate.TimescaleSupport.csstill said that a legacy aggregate drops only when its successor covers the tier. They now state the Darling: an empty superseded baseline aggregate is never dropped when its successor is empty too #4289 rule as well. A comment inRetiredBaselineAggregateTests.csnamed the wrong class for the new test, and now names the right one.Tests
BaselineSupplyTests.RetirementCondition_EmptyLegacyAggregateDropsOnSight_RegardlessOfSuccessor(no database): an empty legacy aggregate drops whether its successor is empty, short or covering.RetirementCondition_HoldsOnlyOnceTheSuccessorCoversTheTiernow passeslegacyHoldsRows: true.EmptySupersededBaselineLiveTests.EmptyLegacyAggregate_DropsOnSight_EvenWithAnEmptySuccessor_AgainstDevPostgres(inRetiredBaselineAggregateTests.cs): an empty legacy aggregate with an empty successor drops. It makes its own scratch database. The probe reads every server's rows, and other tests leavewait_statsrows on the shareddarlingtestdatabase, so the legacy aggregate there is not empty. With the probe forced tolegacyHoldsRows = true, as before this PR, it failed with "Expected: Drop, Actual: SuccessorShort". With the fix restored, it passed.EmptySupersededBaselineLiveTests.LegacyProbeFails_LegacySurvives_AgainstDevPostgres: a second connection holds anACCESS EXCLUSIVElock on the legacy aggregate, and the judged connection haslock_timeout = '1s'. So the probe fails, and the test checks that the legacy aggregate survives and nothing drops. A timer releases the lock after 1.5 seconds. That way, a drop that the code tries by mistake succeeds, and the test sees it. With a temporary catch that swallowed the probe error and answered "empty", the test failed (1 dropped). With the catch removed, it passed.RetiredBaselineAggregateLiveTests.Supersession_RestartZeroInLegacyNotSuccessor_ProviderFollowsCoverage_SweepWaitsForTheTier_AgainstDevPostgresnow also checksLegacyHoldsRowson day one (rows and a short successor: no drop) and on day forty (rows and a covering successor: drop).Test plan
dotnet buildofPerformanceMonitor.Darling.StorageandDarling.Tests: 0 warnings, 0 errors.EmptySupersededBaselineLiveTests,RetiredBaselineAggregateLiveTests,RetiredBaselineAggregateTestsandBaselineSupplyTestsagainst a local PostgreSQL rig (TimescaleDB 2.28.1): 30 tests, 0 failed.Darling.Testsonce on that rig, on a newdarlingtestdatabase: the runner exited 0, with no[FAIL]line. The lane ran out of context before it copied the totals, and its log is gone.92bcaa19), on a new rig (PostgreSQL 18.6 with TimescaleDB):RetiredBaselineAggregateTests,EmptySupersededBaselineLiveTests,BaselineSupplyTestsandDocCommentHygieneTestsran 106 tests, 0 failed.RetiredBaselineAggregateLiveTestsran 2 tests, 0 failed.92bcaa19: build, Darling PostgreSQL tests and Lite tests all pass. The firstbuildattempt failed on one unrelated test,AlertReadFailureSurfaceTests.TheNewestFailuresFactsAreNeverABlendOfTwo(a race in the test itself, filed as AlertReadFailureSurfaceTests.TheNewestFailuresFactsAreNeverABlendOfTwo fails when the writers finish before the reader's first check #4312); the re-run passed.CHANGELOG entry
SECTION: Fixed
ENTRY:
perfmon_baselineorwait_stats_baselineaggregate, both it and its replacement stayed empty. The empty replacement has no oldest bucket, so the old aggregate's cleanup rule never passed. Now an old aggregate that holds no rows drops on the next start, with its refresh, retention and compression jobs.REF:
[Drop an empty superseded baseline aggregate on sight (#4289) #4292]: Drop an empty superseded baseline aggregate on sight (#4289) #4292
🤖 Generated with Claude Code
https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ