Repository navigation
De-skew query_stats.creation_time before comparing it to the analysis window (Part of #2991) - #2992
Merged
Conversation
… window (Part of #2991) creation_time is the monitored server's local wall clock — QueryStatsCollector ships the sys.dm_exec_query_stats value verbatim — while the bound it was compared against is naive UTC off DateTime.UtcNow. Six analysis reads across the two stores compared them untranslated, so the PARAMETER_SENSITIVITY detector's compiled-before-the-window guard asked a different question on every server that is not UTC. At a negative offset the predicate admits every plan compiled inside the window, which is exactly the population it exists to exclude, and a young plan's partial-life worker-time spread is then scored as full-life variance. At a positive offset it discards plans that legitimately predate the window. Every production SQL Server target reports utc_offset_minutes = -240, so the fleet has been running the false-positive direction. Each site now resolves the collected server_properties.utc_offset_minutes in a single-row CTE and compares creation_time_utc, so the test spans one frame. The dialects diverge because they must: DuckDB has no make_interval, and AT TIME ZONE would pull in ICU, so Lite multiplies INTERVAL '1' MINUTE the way it already does against the sibling column in this same table. A server with no collected offset falls back to 0 and the read proceeds, since server_properties is an on-load collector and an absent offset is the state every server passes through on its first cycle. The predicate itself is unchanged in meaning and no parameter was added.
ReviewVerified the fix against the codebase's existing de-skew precedent (
Also checked the live-Postgres and real-DuckDB test twins ( No correctness, security, or parity issues found. Nothing to flag inline. |
erikdarlingdata
deleted the
fix/2991-creation-time-server-local-bound
branch
September 5, 2026 05:14
This was referenced Sep 5, 2026
Closed
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.
Part of #2991.
query_stats.creation_timeis the monitored server's local wall clock —QueryStatsCollectorships thesys.dm_exec_query_statsvalue verbatim, and says so about this very column. The bound it was compared against is naive UTC offDateTime.UtcNow. Six analysis reads across the two stores compared them untranslated, so thePARAMETER_SENSITIVITYdetector's compiled-before-the-window guard asked a different question on every server that is not UTC.The direction, re-derived
utc_offset_minutesisDATEDIFF(MINUTE, GETUTCDATE(), GETDATE()), i.e.local - utc, so a plan compiled at UTC instantTis stored ascreation_time = T + offset. The old predicatecreation_time <= Wtherefore admittedT <= W - offset, where the correct bound isT <= W.Measured against real PostgreSQL 17 and real DuckDB 1.5.5, on a five-plan fixture straddling the window bound, default four-hour window:
So a negative offset is the false-positive direction: it admits every plan compiled inside the window, which is exactly the population the predicate exists to exclude, and a young plan's partial-life min/max worker-time spread is then scored as full-life variance. A positive offset is the suppression direction. One correction to the issue's wording: at a positive offset the finding class is sharply thinned, not silenced — a plan cached longer than the offset still qualifies, which is why
old_3dsurvives at UTC+10 above. Zero was the only offset that was ever right, which is why every store anyone develops or tests against agreed with the bug.The fix
Each site resolves the collected
server_properties.utc_offset_minutesin a single-rowCOALESCECTE and compares a de-skewedcreation_time_utc. Subtracting the offset is the direction already established at the three existing de-skew sites (ViewerDataService.SystemEvents,ViewerDataService.JobHistory, andPgFindingStore's- context.ServerUtcOffset).No parameter was added:
server_idis already$1, so the ordinals and the C# bind order are unchanged.The two dialects diverge, and have to. PostgreSQL gets
make_interval(mins => svr.offset_minutes); DuckDB has nomake_interval, andAT TIME ZONEwould pull in ICU, so Lite multipliesINTERVAL '1' MINUTE— the form it already uses againstlast_execution_timein this same table. Each expression was verified independently against its own engine rather than assumed portable.server_propertiesis an on-load collector, so "no offset yet" is the state every server passes through on its first cycle. Both dialects fall back to0and the read proceeds, rather than refusing and pre-empting the answers that outrank any window. A pre-migration snapshot whoseutc_offset_minutesis NULL behaves identically to an absent row, and a NULLcreation_timeis still excluded exactly as before —NULL - intervalis still NULL.compile_age_secondswould have been a frame-free alternative, andQueryStatsCollectorcomputes it for exactly this reason, but it is deliberately not stored (PayloadColumnsis unchanged; it exists only to inform the delta), so it is not reachable from either store.AnalysisContext.ServerUtcOffsetis deliberately untouched. It isTimeSpan.Zeroin both live SKUs, the only non-zero assignments are in the retired Dashboard, and its documented meaning is the inverse — a server-local window converted back to UTC for persistence. Setting it here would silently move the windowPgFindingStorepersists, which is a different change.What pins it now
Two behavioural twins, one per store, plus a source-level guard.
ParameterSensitivityClockFrameLiveTests(live Postgres) andParameterSensitivityClockFrameTests(real DuckDB) assert an invariance rather than a timestamp: the same five compile instants, re-expressed in each server's local clock the way the collector would really have stored them, must yield the identical offender set. Membership is asserted alongside invariance on purpose — a constant empty result is also invariant, so invariance alone is not a test. Three offsets plus the no-offset fallback: UTC-4, UTC, UTC+10. Each covers the detector and the drill-down, which carries its own copy of the same predicate.CreationTimeClockFrameDisciplineTestsscans both analysis trees for a barecreation_timecompared against a window bound and censuses the de-skew sites per file. It exists because the inventory found nothing in either suite that compares Darling's analysis SQL against Lite's — no parity guard, no shared constant, not even a count — so the realistic regression is a read ported to one store and not the other. It also reaches the twopsp_signaturesites, which need a whole Query Store fixture before they return a row. Its discriminator is pinned in both directions.No existing test asserted the buggy behaviour. Every seeded
creation_timein the suite isTestPeriodStart.AddDays(-3)with no collected offset, so those fixtures are offset-tolerant with a three-day margin and pass unchanged.Verified
Eight one-at-a-time mutations, each anchor-count-asserted and hashed before and after, each watched red and restored:
psp_signaturepsp_signatureThe two
psp_signaturerows are the honest gap: only the source guard sees them, which is why it is here.The three shipped Darling SQL constants were also executed against a real migrated store schema rather than a retyped copy, and the DuckDB expression against DuckDB 1.5.5 in-process.
Not measured: whether stored
analysis_findingscounts actually look inflated on the fleet. That needs a store query, and it is the comparison that would confirm the over-reporting empirically.