Repository navigation
A fresh store's fleet overview answers instead of failing with 'Out of range of DateTime' before collection_health_hourly materializes (#3973) - #3981
Merged
Conversation
…f range of DateTime' before collection_health_hourly materializes (#3973) CollectionHealthWatermarkSql guarded the aggregate's watermark with isfinite(), expecting "nothing materialized" to read -infinity. TimescaleDB (2.28.1, 2.30.1) reports the minimum finite timestamp, 4714-11-24 BC, both before the first refresh and after a refresh over an empty log. Npgsql cannot convert that to a DateTime, and the InvalidCastException escaped the guard's catch (PostgresException). So get_fleet_overview and /api/fleet failed on every fresh store until rows had landed and a refresh had covered them. - A watermark before MaterializedWatermarkFloor (2000-01-01) now reads as nothing materialized: NULL, the all-real-time path, which is exact. - The guard also treats a value the client cannot convert as a failed guard read (the exact raw scan), as its contract already said. - A TimescaleSupport doc that repeated the -infinity assumption is corrected. Live tests build a scratch store the product's way with a never-refreshed aggregate. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Z4T51J4E6L8fjriPHpsif
…#3973) The empty-log refresh was measured on 2.28.1 only; the never-refreshed sentinel on 2.28.1 and 2.30.1. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Z4T51J4E6L8fjriPHpsif
erikdarlingdata
enabled auto-merge (squash)
September 23, 2026 02:35
erikdarlingdata
added a commit
that referenced
this pull request
Sep 23, 2026
…3920, #3927, #3931, #3932, #3940, #3942, #3946, #3947, #3950, #3952, #3955, #3956, #3957, #3964, #3965, #3966, #3968, #3972, #3975, #3979, #3980, #3981, #3983, #3984, #3985) (#3989) The wave's fix PRs deliberately carried no CHANGELOG edits (parallel-agent hot-spot protocol); each agent reported its entry and this commit lands them together, byte-verified against origin/dev. Claude-Session: https://claude.ai/code/session_01GdmA4ND1wLSqA91ax1m4xv Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.
Closes #3973.
Why
On every fresh TimescaleDB store,
get_fleet_overviewansweredOut of range of DateTime (year must be between 1 and 9999)and/api/fleetreturned a bare 500. The web dashboard's Fleet Overview and sidebar were broken untilcollect.collection_health_hourlyhad materialized something.DarlingFleetReader.CollectionHealthWatermarkSql(#3893, #3911) guarded the aggregate's watermark withisfinite(), assuming "nothing materialized" reads-infinity. It doesn't: TimescaleDB reports the minimum finite timestamp. Npgsql cannot convert that to aDateTime, so the value throwsInvalidCastException. The guard'scatch (PostgresException)did not cover that, and the whole overview failed.Measured on the rig (TimescaleDB 2.28.1, a throwaway hypertable plus an aggregate created
WITH NO DATA):cagg_watermarkisfiniteThe middle row answers the issue's unmeasured scope: a refresh over an empty log does not move the watermark. So a new install stayed broken until collection had rows AND a refresh (end offset one hour, hourly) had covered them, not just until the first refresh.
What changes
CollectionHealthWatermarkSql:CASE WHEN isfinite(w) AND w >= '2000-01-01'::timestamp THEN w END. A newMaterializedWatermarkFloor(2000-01-01) names the floor, and a test holds the SQL literal to the constant. No store holds a collection from before 2000, so a watermark below that is the extension's "nothing materialized" sentinel, whichever one it uses. It now reads NULL, which the guard already treats as all real-time from raw. That is exact: the aggregate ismaterialized_only = false, so with nothing materialized every bucket is computed from raw.CollectionHealthRollupUsableAsync: it catchesInvalidCastExceptionas well asPostgresException. Its own contract already said a failed guard read falls back to the exact raw scan, and a value the client cannot convert is a failed guard read. It used to fail the whole overview instead.TimescaleSupport.BaselineBackfillProbeSql: it said aWITH NO DATAaggregate's watermark is-infinity. That remark carried the same assumption, though no code acted on it.cagg_watermarkandisfinite. This is the only CAGG watermark read in product code, in either app. Lite has no TimescaleDB, so there is nothing to mirror.Test plan
System.InvalidCastException: Out of range of DateTime (year must be between 1 and 9999). That includes the empty-log case, which confirms the finding above.FreshStoreWatermarkLiveTests. Each builds its own scratch store the product's way: migrations, TimescaleDB, thecollection_loghypertable,EnsureContinuousAggregatesAsynccreating the aggregateWITH NO DATA, and the policy switched off.CollectionHealthRollupUsableAsyncreturns true.GetFleetOverviewAsyncanswers, and both carded servers carry their three collectors through the unmaterialized aggregate.FreshStoreWatermarkTests(pure): the SQL carries the floor, and the guard's catch coversInvalidCastException.2026-09-23 01:00:00before and after). All 26 aggregates there are materialized.Darling.TestswithDARLING_TEST_PGon the rig: 12,730 total, 1 failed, 24 skipped. The failure isForcePlanFailuresAccessPathTests.TheShippedRead_PlansAsAnIndexOnlyScanOnTheCoveringIndex_AgainstDevPostgres: a plan-shape assertion that got a Merge Join on the shared store's statistics mid-run, in a read this PR does not touch. Re-run alone, the class passes 4/4. Every skip is environment-gated.0 Warning(s).🤖 Generated with Claude Code
https://claude.ai/code/session_018Z4T51J4E6L8fjriPHpsif