Repository navigation
issue-3653 A6 lane LB: the 3 interval-honest successor dailies + backfill runbook - #4181
Conversation
…real TimescaleDB
- New SuccessorDailyLiveTests.cs: three live tests against
timescale/timescaledb:2.28.1-pg18.
1. The ensure sweep creates the three successor dailies
(query_stats_interval_daily, procedure_stats_interval_daily,
query_stats_db_interval_daily) with a refresh policy each and NO
compression policy, and a second sweep is idempotent (no error,
no duplicate policy).
2. Seeded ~4 days of raw query_stats/procedure_stats, refreshed the
successor hourlies then the successor dailies, and confirmed
each successor daily's per-day sums equal its legacy daily's.
3. The runbook's verification query holds after a backfill
restricted to the three successor dailies and their successor-
hourly sources, per the runbook's own dependency order.
- Product fix (live-proven bug): TimescaleAggregateCompressionTests'
end-to-end count assertion still read
AggregateCompressionTargets.Count + OffGridAggregates.Length as
the ensure sweep's total creation count. That formula stopped
covering every registered aggregate the moment lane LB added three
registered-but-compression-deferred successor dailies
(CompressionDeferredUntilFreeze) — the live run measured 27
created against an expected 24. Re-pinned to
HourlyAggregates.Length + DailyAggregates.Length +
BaselineAggregates.Length + OffGridAggregates.Length, which is what
the ensure sweep actually creates.
- Doc-comment hygiene fix (found by DocCommentHygieneTests, live
run): CompressionDeferredUntilFreeze's summary and
AggregateCompressionTargets' summary were stacked out of order in
TimescaleSupport.cs (the CompressionDeferredUntilFreeze doc block
landed ABOVE the field it was never attached to). Reordered so
each summary sits directly above its own field, matching the
'declared before' comment in AggregateCompressionTargets' summary,
which already assumed this ordering.
Live results: all 3 new tests pass, 0 skipped. Grid pins
(TimescaleSupportTests, RefreshCeilingProvenancePinTests,
TimescaleAggregateCompressionTests, MeasurementContractCensusTests,
IntervalHonestHourlyRollupTests, DocCommentHygieneTests) all green.
Full suite (DARLING_TEST_PG unset): 215 failed / 762 skipped vs a
216-failed / 759-skipped base measured on the same rig before this
commit (one fewer failure - the doc-hygiene fix; three more skips -
the three new live tests with no DARLING_TEST_PG). No new failures.
…re trusting its count
Reproduced 26/27 on the CI runtime (TimescaleDB 2.30.1 via timescale/timescaledb:2.30.1-pg18):
SuccessorDailyLiveTests's own ensure call still reports 27/27 ready and idempotent on a fresh
scratch database with no seeded raw data (confirmed with a capturing logger: zero Warning lines,
verified over 5 repeated runs). IntervalHonestHourlyRollupLiveTests reproduces reliably: after
seeding raw rows and running the SAME EnsureContinuousAggregatesAsync sweep (also 27/27, zero
Warning lines from a capturing logger passed into that call), a later same-connection
to_regclass('collect.query_stats_interval_daily') resolves the relation and a hand-run CREATE
MATERIALIZED VIEW IF NOT EXISTS of the same SQL text succeeds silently (already exists) -- yet
DetectRollupCoverageAsync's own SELECT, on a fresh NpgsqlDataSource opened moments later, gets
'42P01: relation "collect.query_stats_interval_daily" does not exist'. The failure is therefore
NOT one of the three successor-daily CREATE statements going wrong on 2.30.1 (every reproduction
attempt shows all 27 created, with a clean logger); it reads instead as a materialization/search-path
or connection-visibility difference on 2.30.1 that I could not pin to one exact SQL statement inside
the lane's time budget. Root cause not yet isolated to my satisfaction -- see PR body for the
handoff and the exact repro steps I found so far.
This commit's actual, landable content: EnsureContinuousAggregatesAsync's per-statement try/catch
logs a WARNING and keeps going, so a caller who only checks the returned count learns nothing about
WHICH create/policy statement failed or why (#4181's own gap). SuccessorDailyLiveTests now asserts
the capturing logger saw no Warning line from the first ensure sweep, before it even checks the
count, so a future swallowed CREATE failure fails HERE with the verbatim error in the assertion
message instead of surfacing 3 frames downstream as a bare relation-not-found.
Lane A6-LB-5 handoff (ran out of time budget mid-investigation; no code changes made)Status: investigation only, no commits. This lane found the Step-A answer with certainty, made progress on Step B, but hit the context/time wall before writing the fix. Filing this as a comment (branch (2) The 42P01 coverage-probe failure — test-connection findingThe coordinator's hypothesis is WRONG. I read The LB-4 diagnostics (already on the branch) prove it from the CI run itself (the Windows PG-tests shard-1 job on PR #4181's latest run): So: the relation is visible to Next step for whoever picks this up: add (1)
|
…res via the RIG RULE Failure 1 (SuccessorDailyLiveTests): all three setups skipped TimescaleSupport.EnsureCollectionLogHypertableAsync between ConvertToHypertablesAsync and EnsureContinuousAggregatesAsync. On a store started per the RIG RULE (template1 without timescaledb, mirroring Windows CI's bundled runtime), collection_log stays a plain table and collection_health_hourly's CREATE (FROM collect.collection_log) is rejected with 0A000. This is the same fix IntervalHonestHourlyRollupTests.cs:530 already carries. Test-setup bug, not a product bug. Failure 2 (IntervalHonestHourlyRollupLiveTests): the test's own upgraded-store simulation drops query_stats_interval_hourly with DropRetiredBaselineRelationSql, which issues DROP MATERIALIZED VIEW ... CASCADE. query_stats_interval_daily is hierarchical FROM query_stats_interval_hourly, so the CASCADE takes the daily down with it. The test recreated the hourly but never the daily, so the later coverage probe hit 42P01 on a relation the test itself destroyed. Fixed by recreating query_stats_interval_daily right after the hourly, matching what the product's own ensure sweep would do on the next start (it walks the whole registry, not just the relation that changed). Test-setup bug, not a product bug. Reproduced RED then GREEN on the RIG-RULE rig: timescale/timescaledb:2.30.1-pg18 with 'DROP EXTENSION IF EXISTS timescaledb' run against template1 before any test, per lane-3653-A6-COMMON.md's RIG RULE. All 81 tests across SuccessorDailyLiveTests, IntervalHonestHourlyRollupLiveTests, IntervalHonestHourlyRollupTests, CollectionHealthLiveTests, RollupCoverageTests and TimescaleSupportTests pass.
…real TimescaleDB
- New SuccessorDailyLiveTests.cs: three live tests against
timescale/timescaledb:2.28.1-pg18.
1. The ensure sweep creates the three successor dailies
(query_stats_interval_daily, procedure_stats_interval_daily,
query_stats_db_interval_daily) with a refresh policy each and NO
compression policy, and a second sweep is idempotent (no error,
no duplicate policy).
2. Seeded ~4 days of raw query_stats/procedure_stats, refreshed the
successor hourlies then the successor dailies, and confirmed
each successor daily's per-day sums equal its legacy daily's.
3. The runbook's verification query holds after a backfill
restricted to the three successor dailies and their successor-
hourly sources, per the runbook's own dependency order.
- Product fix (live-proven bug): TimescaleAggregateCompressionTests'
end-to-end count assertion still read
AggregateCompressionTargets.Count + OffGridAggregates.Length as
the ensure sweep's total creation count. That formula stopped
covering every registered aggregate the moment lane LB added three
registered-but-compression-deferred successor dailies
(CompressionDeferredUntilFreeze) — the live run measured 27
created against an expected 24. Re-pinned to
HourlyAggregates.Length + DailyAggregates.Length +
BaselineAggregates.Length + OffGridAggregates.Length, which is what
the ensure sweep actually creates.
- Doc-comment hygiene fix (found by DocCommentHygieneTests, live
run): CompressionDeferredUntilFreeze's summary and
AggregateCompressionTargets' summary were stacked out of order in
TimescaleSupport.cs (the CompressionDeferredUntilFreeze doc block
landed ABOVE the field it was never attached to). Reordered so
each summary sits directly above its own field, matching the
'declared before' comment in AggregateCompressionTargets' summary,
which already assumed this ordering.
Live results: all 3 new tests pass, 0 skipped. Grid pins
(TimescaleSupportTests, RefreshCeilingProvenancePinTests,
TimescaleAggregateCompressionTests, MeasurementContractCensusTests,
IntervalHonestHourlyRollupTests, DocCommentHygieneTests) all green.
Full suite (DARLING_TEST_PG unset): 215 failed / 762 skipped vs a
216-failed / 759-skipped base measured on the same rig before this
commit (one fewer failure - the doc-hygiene fix; three more skips -
the three new live tests with no DARLING_TEST_PG). No new failures.
…re trusting its count
Reproduced 26/27 on the CI runtime (TimescaleDB 2.30.1 via timescale/timescaledb:2.30.1-pg18):
SuccessorDailyLiveTests's own ensure call still reports 27/27 ready and idempotent on a fresh
scratch database with no seeded raw data (confirmed with a capturing logger: zero Warning lines,
verified over 5 repeated runs). IntervalHonestHourlyRollupLiveTests reproduces reliably: after
seeding raw rows and running the SAME EnsureContinuousAggregatesAsync sweep (also 27/27, zero
Warning lines from a capturing logger passed into that call), a later same-connection
to_regclass('collect.query_stats_interval_daily') resolves the relation and a hand-run CREATE
MATERIALIZED VIEW IF NOT EXISTS of the same SQL text succeeds silently (already exists) -- yet
DetectRollupCoverageAsync's own SELECT, on a fresh NpgsqlDataSource opened moments later, gets
'42P01: relation "collect.query_stats_interval_daily" does not exist'. The failure is therefore
NOT one of the three successor-daily CREATE statements going wrong on 2.30.1 (every reproduction
attempt shows all 27 created, with a clean logger); it reads instead as a materialization/search-path
or connection-visibility difference on 2.30.1 that I could not pin to one exact SQL statement inside
the lane's time budget. Root cause not yet isolated to my satisfaction -- see PR body for the
handoff and the exact repro steps I found so far.
This commit's actual, landable content: EnsureContinuousAggregatesAsync's per-statement try/catch
logs a WARNING and keeps going, so a caller who only checks the returned count learns nothing about
WHICH create/policy statement failed or why (#4181's own gap). SuccessorDailyLiveTests now asserts
the capturing logger saw no Warning line from the first ensure sweep, before it even checks the
count, so a future swallowed CREATE failure fails HERE with the verbatim error in the assertion
message instead of surfacing 3 frames downstream as a bare relation-not-found.
…res via the RIG RULE Failure 1 (SuccessorDailyLiveTests): all three setups skipped TimescaleSupport.EnsureCollectionLogHypertableAsync between ConvertToHypertablesAsync and EnsureContinuousAggregatesAsync. On a store started per the RIG RULE (template1 without timescaledb, mirroring Windows CI's bundled runtime), collection_log stays a plain table and collection_health_hourly's CREATE (FROM collect.collection_log) is rejected with 0A000. This is the same fix IntervalHonestHourlyRollupTests.cs:530 already carries. Test-setup bug, not a product bug. Failure 2 (IntervalHonestHourlyRollupLiveTests): the test's own upgraded-store simulation drops query_stats_interval_hourly with DropRetiredBaselineRelationSql, which issues DROP MATERIALIZED VIEW ... CASCADE. query_stats_interval_daily is hierarchical FROM query_stats_interval_hourly, so the CASCADE takes the daily down with it. The test recreated the hourly but never the daily, so the later coverage probe hit 42P01 on a relation the test itself destroyed. Fixed by recreating query_stats_interval_daily right after the hourly, matching what the product's own ensure sweep would do on the next start (it walks the whole registry, not just the relation that changed). Test-setup bug, not a product bug. Reproduced RED then GREEN on the RIG-RULE rig: timescale/timescaledb:2.30.1-pg18 with 'DROP EXTENSION IF EXISTS timescaledb' run against template1 before any test, per lane-3653-A6-COMMON.md's RIG RULE. All 81 tests across SuccessorDailyLiveTests, IntervalHonestHourlyRollupLiveTests, IntervalHonestHourlyRollupTests, CollectionHealthLiveTests, RollupCoverageTests and TimescaleSupportTests pass.
6191e36 to
993640b
Compare
Adds query_stats_interval_daily, procedure_stats_interval_daily and query_stats_db_interval_daily: hierarchical CREATEs sourced from the interval-honest successor hourlies (not the legacy trio), WITH NO DATA. Registered in DailyAggregates, RollupViews and the availability probe; SupersededDailyRollups records the eventual legacy/successor/hourly mapping for LA and LC. Held out of aggregate compression via a new CompressionDeferredUntilFreeze set so the daily compression band stays at 23 members until LC's freeze frees the legacy trio's slots.
…he backfill runbook
- IntervalHonestHourlyRollupTests: RollupViews.Length 16 -> 19 (the
three successor dailies added to that list).
- TimescaleAggregateCompressionTests: DailyAggregates 7 -> 10, and the
compression target count stays 23 by subtracting the new
CompressionDeferredUntilFreeze set (9 + 10 + 7 - 3 = 23).
- docs/runbooks/a6-successor-daily-backfill.md: the operator runbook
for backfilling the three successor dailies from
date_trunc('day', successorHourlyFloor).
…real TimescaleDB
- New SuccessorDailyLiveTests.cs: three live tests against
timescale/timescaledb:2.28.1-pg18.
1. The ensure sweep creates the three successor dailies
(query_stats_interval_daily, procedure_stats_interval_daily,
query_stats_db_interval_daily) with a refresh policy each and NO
compression policy, and a second sweep is idempotent (no error,
no duplicate policy).
2. Seeded ~4 days of raw query_stats/procedure_stats, refreshed the
successor hourlies then the successor dailies, and confirmed
each successor daily's per-day sums equal its legacy daily's.
3. The runbook's verification query holds after a backfill
restricted to the three successor dailies and their successor-
hourly sources, per the runbook's own dependency order.
- Product fix (live-proven bug): TimescaleAggregateCompressionTests'
end-to-end count assertion still read
AggregateCompressionTargets.Count + OffGridAggregates.Length as
the ensure sweep's total creation count. That formula stopped
covering every registered aggregate the moment lane LB added three
registered-but-compression-deferred successor dailies
(CompressionDeferredUntilFreeze) — the live run measured 27
created against an expected 24. Re-pinned to
HourlyAggregates.Length + DailyAggregates.Length +
BaselineAggregates.Length + OffGridAggregates.Length, which is what
the ensure sweep actually creates.
- Doc-comment hygiene fix (found by DocCommentHygieneTests, live
run): CompressionDeferredUntilFreeze's summary and
AggregateCompressionTargets' summary were stacked out of order in
TimescaleSupport.cs (the CompressionDeferredUntilFreeze doc block
landed ABOVE the field it was never attached to). Reordered so
each summary sits directly above its own field, matching the
'declared before' comment in AggregateCompressionTargets' summary,
which already assumed this ordering.
Live results: all 3 new tests pass, 0 skipped. Grid pins
(TimescaleSupportTests, RefreshCeilingProvenancePinTests,
TimescaleAggregateCompressionTests, MeasurementContractCensusTests,
IntervalHonestHourlyRollupTests, DocCommentHygieneTests) all green.
Full suite (DARLING_TEST_PG unset): 215 failed / 762 skipped vs a
216-failed / 759-skipped base measured on the same rig before this
commit (one fewer failure - the doc-hygiene fix; three more skips -
the three new live tests with no DARLING_TEST_PG). No new failures.
…ests Two mechanical fixes, no design change: 1. MaterializationHoleRepairTests.Targets_AreEveryRegisteredAggregate_ InDependencyOrder_WithTheirOwnCreate pinned 23 registered aggregates; this lane's three successor dailies appended to DailyAggregates legitimately bring the total to 26. Re-pinned by derivation, matching the same pattern as the other tests this lane's brief already called out for re-pinning. 2. LivePostgresCollectionHygieneTests flagged SuccessorDailyLiveTests as touching the shared DARLING_TEST_PG store without documenting why it isn't serialized. It mints its own database via ScratchPostgres and never touches the shared one, so this is the #1776 own-store case (same wording as CollectionHealthAggregateTests/DarlingAlertTuningKnobsTests). Documented, not serialized. NOT fixed here, reported separately: EnsureContinuousAggregatesAsync's own-sweep return count comes back 26 instead of 27 in both of SuccessorDailyLiveTests's tests, and IntervalHonestHourlyRollupLiveTests separately hits "relation collect.query_stats_interval_daily does not exist" reading a relation the sweep should have created. Traced the whole path (RollupAvailability.Has, RollupProbeSql's column order vs the record's constructor order, RollupViews' new entries, the sweep's Hourly-before-Daily ordering) and found no wiring/ordering bug -- the sweep's own try/catch silently swallows whichever CREATE or ADD POLICY statement is actually failing, and the CI transcript doesn't surface that logger text.
…re trusting its count
Reproduced 26/27 on the CI runtime (TimescaleDB 2.30.1 via timescale/timescaledb:2.30.1-pg18):
SuccessorDailyLiveTests's own ensure call still reports 27/27 ready and idempotent on a fresh
scratch database with no seeded raw data (confirmed with a capturing logger: zero Warning lines,
verified over 5 repeated runs). IntervalHonestHourlyRollupLiveTests reproduces reliably: after
seeding raw rows and running the SAME EnsureContinuousAggregatesAsync sweep (also 27/27, zero
Warning lines from a capturing logger passed into that call), a later same-connection
to_regclass('collect.query_stats_interval_daily') resolves the relation and a hand-run CREATE
MATERIALIZED VIEW IF NOT EXISTS of the same SQL text succeeds silently (already exists) -- yet
DetectRollupCoverageAsync's own SELECT, on a fresh NpgsqlDataSource opened moments later, gets
'42P01: relation "collect.query_stats_interval_daily" does not exist'. The failure is therefore
NOT one of the three successor-daily CREATE statements going wrong on 2.30.1 (every reproduction
attempt shows all 27 created, with a clean logger); it reads instead as a materialization/search-path
or connection-visibility difference on 2.30.1 that I could not pin to one exact SQL statement inside
the lane's time budget. Root cause not yet isolated to my satisfaction -- see PR body for the
handoff and the exact repro steps I found so far.
This commit's actual, landable content: EnsureContinuousAggregatesAsync's per-statement try/catch
logs a WARNING and keeps going, so a caller who only checks the returned count learns nothing about
WHICH create/policy statement failed or why (#4181's own gap). SuccessorDailyLiveTests now asserts
the capturing logger saw no Warning line from the first ensure sweep, before it even checks the
count, so a future swallowed CREATE failure fails HERE with the verbatim error in the assertion
message instead of surfacing 3 frames downstream as a bare relation-not-found.
…iagnostics Assert.DoesNotContain's message preview truncates the ensure sweep's warning to ~50 chars, hiding the actual Postgres/Timescale error under it. Switch to Assert.False(...Contains..., message) so the full log.Joined text (never truncated on a user message) reaches CI. RestartRow_..._AgainstDevPostgres: before the coverage probe can hit its 42P01, print the availability probe's own answer and the actual timescaledb_information.continuous_aggregates contents via ITestOutputHelper, so a missing-relation throw is diagnosed from this run's own evidence. Evidence-gathering step only; no behavior change. Part of #3653 (A6, lane LB-4).
…res via the RIG RULE Failure 1 (SuccessorDailyLiveTests): all three setups skipped TimescaleSupport.EnsureCollectionLogHypertableAsync between ConvertToHypertablesAsync and EnsureContinuousAggregatesAsync. On a store started per the RIG RULE (template1 without timescaledb, mirroring Windows CI's bundled runtime), collection_log stays a plain table and collection_health_hourly's CREATE (FROM collect.collection_log) is rejected with 0A000. This is the same fix IntervalHonestHourlyRollupTests.cs:530 already carries. Test-setup bug, not a product bug. Failure 2 (IntervalHonestHourlyRollupLiveTests): the test's own upgraded-store simulation drops query_stats_interval_hourly with DropRetiredBaselineRelationSql, which issues DROP MATERIALIZED VIEW ... CASCADE. query_stats_interval_daily is hierarchical FROM query_stats_interval_hourly, so the CASCADE takes the daily down with it. The test recreated the hourly but never the daily, so the later coverage probe hit 42P01 on a relation the test itself destroyed. Fixed by recreating query_stats_interval_daily right after the hourly, matching what the product's own ensure sweep would do on the next start (it walks the whole registry, not just the relation that changed). Test-setup bug, not a product bug. Reproduced RED then GREEN on the RIG-RULE rig: timescale/timescaledb:2.30.1-pg18 with 'DROP EXTENSION IF EXISTS timescaledb' run against template1 before any test, per lane-3653-A6-COMMON.md's RIG RULE. All 81 tests across SuccessorDailyLiveTests, IntervalHonestHourlyRollupLiveTests, IntervalHonestHourlyRollupTests, CollectionHealthLiveTests, RollupCoverageTests and TimescaleSupportTests pass.
LB-6 found the 42P01's cause (the test's own CASCADE drop of the hourly successor), so the ITestOutputHelper plumbing and the availability/catalog printout LB-4 added to chase it are no longer needed. The class is back to dev's shape plus LB-6's recreate of the successor daily. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
993640b to
c7a95a1
Compare
…he daily tier at F_d DailySummarySql.RangeSqlFor(tier, coverage, windowStartUtc) only ever stitched the hourly tier; the daily tier fell through to the plain, unstitched form even once a successor daily exists. Extend it to both tiers: StitchFloor is asked on the tier's own legacy/StitchTier pair, and a genuine stitch runs QueriesCteForStitchedCagg's not-carried probe twice, split at F_d, with each side's own MaterializationHoleTargets source (the successor daily's source is the successor hourly, per RollupViews/#4181). Also fixes a latent bug the extension surfaced: the legacy-only/ successor-only branch used to redirect through the two-argument RangeSqlFor(tier, relation) overload, whose OWN daily branch always names the legacy regardless of what is passed — so a "successor already covers the window" daily answer would have been silently downgraded back to the legacy. Route through QueriesCteForCagg directly with the name the builder actually chose, for both tiers. Replace the stale DailyTier_IgnoresTheStitch pin (no longer true) with five daily-tier tests mirroring the hourly ones: absent from availability, empty, successor-only, and the F_d stitch itself. All 8 DailySummaryStitchedRangeTests green, 14 more in the surrounding classes unaffected. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
…3653 A6) (#4184) * issue-3653 A6 lane LA-8a step 1: daily stitch reads the real registry, gated on availability StitchedRelationSql and StitchFloor's daily branches used LA's placeholder s_supersededDailyRollupsStub (name lookup only) instead of LB's real TimescaleSupport.SupersededDailyRollups (#4181), and never checked RollupAvailability.Has() for the successor daily the way the hourly branch already does. Delete the stub and route both methods through the real registry, gating on Has() as well as the floor so an absent successor daily is never named in SQL even when a stale floor happens to be cached for it. Add pure tests for the three daily states: absent from availability (even with both successor floors cached) gives legacy-only text byte-identical to today; present but empty gives legacy-only; present with a floor already gives the F_d stitch (existing test). All 12 StitchedRelationSqlTests green. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3 * issue-3653 A6 lane LA-8a step 2: route ComposeSourceRouter's daily arm through the stitch builder The daily tier's ResolveFamily arm only ever set ComposeRoute.CaggRelation (the by-name answer); CaggFromClause stayed null, so ComposeCompiler always fell back to "collect.<dailyView> AS f" even once a successor daily exists. Set CaggFromClause the same way the hourly arm already does, via RollupCoverage.StitchedRelationSql(dailyView, FactAlias, windowStartUtc, StitchTier.Daily). The tier CHOICE is untouched (still coverage.For on the legacy pair); only the relation within the chosen daily tier can stitch. With no successor daily this is byte-identical to today: 306 Compose tests green (ComposeSourceRouterTests, DarlingComposeTests). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3 * issue-3653 A6 lane LA-8a step 2: route FinOps workload's daily splices through the stitch builder DatabaseResourceUsageSqlFor/TopResourceConsumersByTotalSqlFor/ TopResourceConsumersByAvgSqlFor's coverage-aware overloads stitched the hourly branch but spliced the daily rollup by bare name ("collect.<dailyView> AS f"). Route the daily branch through RollupCoverage.StitchedRelationSql(..., StitchTier.Daily) the same way, so a successor daily gets read once LB backfills it. With no successor daily this is byte-identical to today: 31 tests green (ViewerFinOpsTests, RetentionTierRouterTests). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3 * issue-3653 A6 lane LA-8a step 2: daily-summary RangeSqlFor stitches the daily tier at F_d DailySummarySql.RangeSqlFor(tier, coverage, windowStartUtc) only ever stitched the hourly tier; the daily tier fell through to the plain, unstitched form even once a successor daily exists. Extend it to both tiers: StitchFloor is asked on the tier's own legacy/StitchTier pair, and a genuine stitch runs QueriesCteForStitchedCagg's not-carried probe twice, split at F_d, with each side's own MaterializationHoleTargets source (the successor daily's source is the successor hourly, per RollupViews/#4181). Also fixes a latent bug the extension surfaced: the legacy-only/ successor-only branch used to redirect through the two-argument RangeSqlFor(tier, relation) overload, whose OWN daily branch always names the legacy regardless of what is passed — so a "successor already covers the window" daily answer would have been silently downgraded back to the legacy. Route through QueriesCteForCagg directly with the name the builder actually chose, for both tiers. Replace the stale DailyTier_IgnoresTheStitch pin (no longer true) with five daily-tier tests mirroring the hourly ones: absent from availability, empty, successor-only, and the F_d stitch itself. All 8 DailySummaryStitchedRangeTests green, 14 more in the surrounding classes unaffected. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3 * issue-3653 A6 lane LA-8a step 3: scan test guards against a bare daily-view splice Add NoReaderOutsideTheBuilder_SplicesADailyViewNameBySubstitution: a source scan (same carve-out rule as NoReaderOutsideTheBuilder_CallsHourlyRelationForDirectly) that fails on a literal collect.{...DailyView} interpolation outside TimescaleSupport.cs — the exact shape the three FinOps splices and ComposeSourceRouter's daily arm were in before this lane routed them, and the shape that can never grow a stitch once a successor daily exists. Proved red by reverting ViewerDataService.FinOps.Workload.cs:139 to its old bare splice: the scan caught it at the exact line, then restored. 41 RollupCoverageRoutingTests green. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3 * issue-3653 A6 lane LA-8b1 step 1+2: close the scan's ternary blind spot, route the 3 FinOps daily splices through it The daily-splice scan (NoReaderOutsideTheBuilder_SplicesADailyViewNameBySubstitution) matched only a literal collect.{TimescaleSupport.XDailyView} substring, so it missed the three FinOps one-argument overloads, which splice the same constant from inside a ternary: collect.{(tier == RetentionTier.Hourly ? ...HourlyView : ...DailyView)} AS f. Replaced the literal patterns with three regexes over the same comment-stripped text: an interpolation hole naming the constant anywhere in its expression (catches the ternary), a "collect." + constant concatenation, and the bare view name after collect. Confirmed red on exactly the three FinOps sites (FinOps.Workload.cs:119, 148, 170) before routing them. Routed DatabaseResourceUsageSqlFor(tier), TopResourceConsumersByTotalSqlFor(tier) and TopResourceConsumersByAvgSqlFor(tier) through their three-argument stitch-builder overloads with RollupCoverage.Unknown (RollupAvailability.None), which makes StitchedRelationSql answer collect.<legacy> AS f for both tiers -- the same bytes the bare splice produced. Fixed their doc comments, which said the one-argument form "reads the legacy hourly" even on the daily tier. Scan now green; RetentionTierRouterTests pins (FinOpsReaders_RouteWithoutTrippingTheirDriftGuards) pass unchanged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3 * issue-3653 A6 lane LA-8b1 step 3: DailySummarySql doc/naming fixes RangeSqlFor(tier, hourlyRelation)'s doc comment said the three-argument overload "calls this one only with the legacy-only splice's name (never a daily successor)". Since LA-8a that's no longer true: the three-argument overload builds its own queries CTE directly off QueriesCteForCagg rather than calling this one, so a successor-only daily keeps its own name. Corrected the doc comment. Renamed the three-argument overload's local legacyOnlySplice to singleSplice: the surrounding comment already says the builder can answer either the legacy OR the successor here (a single relation, not always the legacy), so the old name was misleading. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3 * issue-3653 A6 lane LA-8b2: live proof of the daily-tier stitch across FinOps, Compose, daily summary and all six pairs New DailyStitchLiveTests.cs, own scratch TimescaleDB store. One seeded server over six fixed-anchor calendar days (D0..D5) with a per-day row the interval-honest successors exclude and the legacy counts, so legacy/successor totals and unique_queries differ every day. Legacy refreshed for all six days; successor refreshed from D3 (F_d), with D4's successor DAILY deliberately left unrefreshed (while its successor HOURLY is) to exercise the not-carried NULL path. Proves with real numbers: FinOps' DatabaseResourceUsageSqlFor at the daily tier (39.0 ms / 183 executions, neither all-legacy 60.0/186 nor all-successor 18.0/180); a Compose panel through ComposeSourceRouter + ComposeCompiler (one point per day the stitched relation holds, D4 absent); DailySummarySql's not-carried probe (3/3/3/2/NULL/2 across the six days, D4 NULL because its source is the refreshed successor hourly); and a re-probe with RollupAvailability.WithoutIntervalDailies showing the pre-A6-daily numbers (D3=3, D4=3) the real probe replaced. A second fact forces all six stitch pairs (three hourly, three daily) into their UNION ALL form with a hand-built RollupCoverage and confirms SELECT * LIMIT 0 succeeds on each. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3 --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Part of #3653 (A6, lane LB).
What this adds
This change adds three continuous aggregates that roll up by day:
query_stats_interval_daily, built fromquery_stats_interval_hourlyprocedure_stats_interval_daily, built fromprocedure_stats_interval_hourlyquery_stats_db_interval_daily, built fromquery_stats_db_interval_hourlyEach one reads an interval-honest hourly successor (#3653 Q12), not the legacy hourly rollup. A daily built from the legacy hourly inherits the restart-row contamination of the legacy at the day grain. These three let the daily tier stop inheriting it in a later lane. Each one is created
WITH NO DATA. Its columns are the SUM, MIN and MAX re-aggregation of the legacy daily, plussum(sample_interval_seconds_sum).This change does not touch legacy refresh or compression policies, readers, retention gates or raw-purge gates. Lanes LA and LC own those.
Registration
DailyAggregates, the list that the creation sweep walks, grows from 7 to 10.RollupViews, the list behind the coverage probe and the--backfill-rollupsplan, grows from 16 to 19. Each new row names the successor hourly asSource, usesbucketasSourceTimeColumn, and uses theDailyBucketwidth.RollupBackfill.Targetsderives from this list and orders by source depth, so it needed no change.RollupProbeSql,DetectRollupsAsyncandRollupAvailability) gets three new flags and aWithoutIntervalDailiesshorthand for tests. The probe checks existence only. There is no migration and no version gate.SupersededDailyRollupsis a new registry with one(LegacyDaily, SuccessorDaily, SuccessorHourly)row for each pair. No reader uses it yet. Lane A6-LA-8 replacess_supersededDailyRollupsStubfrom issue-3653 A6 lane LA: stitched rollup reads, every hourly reader routes legacy→successor at the successor floor #4182 with it.Compression
The three new dailies get a refresh policy but no compression policy.
CompressionDeferredUntilFreezeholds their names, andAggregateCompressionTargetsleaves them out. The daily compression band is full at 23 members, so the target count stays at 23 (9 hourly + 10 daily + 7 baseline - 3). The three dailies come after the other dailies in the list, so no existing aggregate changes its compression slot. The freeze in lane LC retires the legacy dailies from the band, and then LC removes this set.Runbook
docs/runbooks/a6-successor-daily-backfill.mdtells an operator how to backfill the three dailies. The backfill is restricted to these three views. Each daily starts atdate_trunc('day', successorHourlyFloor). The first day is partial by design, because a daily cannot hold more history than its hourly source. The runbook includes the verification query from the design.Tests
RollupViews.Lengthis 19,DailyAggregates.Lengthis 10, andMaterializationHoleRepairTestsexpects 26 targets.TimescaleAggregateCompressionTestsassertsCompressionDeferredUntilFreeze.Count == 3. Its end-to-end test expects the sweep to createHourlyAggregates + DailyAggregates + BaselineAggregates + OffGridAggregatesaggregates, which is 27 on a fresh store.SuccessorDailyLiveTestsis new. It has three live tests:Warning. A second sweep changes nothing.daily_floor <= date_trunc('day', hourly_floor)).Windows-only CI failures (fixed in the tests)
Two live tests failed only on Windows CI. Both causes were in the test setup. The product code did not change.
SuccessorDailyLiveTestsdid not callTimescaleSupport.EnsureCollectionLogHypertableAsyncbefore the ensure sweep. Socollect.collection_logstayed a plain table, and TimescaleDB rejectedcollection_health_hourlywith0A000: invalid continuous aggregate view. The sweep then built 26 of 27 aggregates. The worker callsConvertToHypertablesAsync, thenEnsureCollectionLogHypertableAsync, thenEnsureContinuousAggregatesAsync. The three test setups now make the same calls in the same order.IntervalHonestHourlyRollupLiveTestssimulates an upgraded store. It dropsquery_stats_interval_hourlywithDROP MATERIALIZED VIEW ... CASCADE. The CASCADE also dropsquery_stats_interval_daily, because that daily reads from the hourly. The test recreated only the hourly, so the coverage probe failed with42P01: relation "collect.query_stats_interval_daily" does not exist. The test now recreates the daily too. Product code never drops the successor hourlies.The earlier passes (LB-3, LB-4 and LB-5) did not reproduce these failures. The
timescale/timescaledbdocker image has thetimescaledbextension intemplate1, so every scratch database on that rig started with the extension. The bundled runtime that Windows CI uses does not do that. When LB-6 dropped the extension fromtemplate1, both failures reproduced on the first local run.The theories in the earlier notes and in the LB-5 comment are wrong and need no follow-up. They are a TimescaleDB 2.30.1 validation change, a catalog visibility race, a Windows-only continuous aggregate defect, and the
INTERVAL '1 hour'spelling incollection_health_hourly. The last commit removes the diagnostic output that LB-4 added toIntervalHonestHourlyRollupLiveTests. The lane-by-lane notes are in the edit history of this description.Verification
993640bcfbefore it armed this PR. It ran 294 live tests with 0 failures on a TimescaleDB 2.30.1 rig with the extension dropped fromtemplate1. On that rig, the full suite had 214 failures, the same set as thedevbase.993640bcfpassed every check exceptreview. The three WindowsDarling PG testsshards ran withDARLING_TEST_PGset: 13,705 tests, 0 failed.devafter issue-3653 A6 lane LA: stitched rollup reads, every hourly reader routes legacy→successor at the successor floor #4182 merged (b6bd7630), with no conflicts. On the rebased tree, the Darling test classes that issue-3653 A6 lane LB: the 3 interval-honest successor dailies + backfill runbook #4181 or issue-3653 A6 lane LA: stitched rollup reads, every hourly reader routes legacy→successor at the successor floor #4182 touch passed without a live store. The result was 316 tests, 0 failed and 24 live tests skipped.c7a95a142passed every check exceptreview. The three WindowsDarling PG testsshards ran withDARLING_TEST_PGset: 13,733 tests, 0 failed. The fourLite testsshards ran 5,311 tests, 0 failed.The
reviewcheck fails on every PR todev. It started to fail on 2026-09-19. Theclaude-review.ymlondevdiffers from the copy onmain, the default branch, so the review action does not run. The client-site loop seat ignores this check until a release syncsmain.Handoff note
The client-site agents stopped at 23:40Z on 2026-09-24. The client-site loop seat had armed this PR on three conditions: a clean rebase, green CI and no new red. Erik handed the PR to a local session, which did the rebase, the cleanup commit and this description. The cleanup commit is outside the arm of the loop seat, and Erik approved it before auto-merge was turned on.
CHANGELOG entry
Section: Added
query_stats_interval_daily,procedure_stats_interval_dailyandquery_stats_db_interval_daily. Each one reads its interval-honest hourly rollup, which does not count restart rows, instead of the legacy hourly rollup. They start empty, and no reader uses them yet. They have a refresh policy but no compression policy until a later change retires the legacy dailies.docs/runbooks/a6-successor-daily-backfill.mdtells an operator how to backfill them. A store now has three more continuous aggregates.