Repository navigation
fix(common-utils): skip metric points that have no recorded value - #3583
BilalAtique wants to merge 3 commits into
Conversation
When a scraped series goes away or a scrape fails, the collector's prometheus receiver sends a point with FLAG_NO_RECORDED_VALUE (a Prometheus staleness marker). The ClickHouse exporter stores it as Value = 0, or as zero counts for histograms, with Flags = 1. The metric CTEs did not look at Flags, so charts and alerts read the marker as a real zero: a gauge's min and avg dropped and count included the dead series. A counter or histogram that came back after the marker was diffed against zero, so its whole total counted as new. A stale exponential histogram point also has scale 0, which made the quantile query downscale every series to scale 0. The gauge, sum, histogram and exponential histogram source CTEs now add bitAnd(Flags, 1) = 0 to their WHERE. HyperDX does not require a Flags column on metric tables, so the filter is added only when the table has a Flags column of a non-Nullable integer type.
🦋 Changeset detectedLatest commit: 271830e The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
@BilalAtique is attempting to deploy a commit to the HyperDX Team on Vercel. A member of the Team first needs to authorize it. |
PR Review✅ No issues found. |
The gauge and sum Bucketed CTEs already read Flags, so a gauge or sum table without it fails before and after this change. Only histogram tables can lack Flags. Say so in the renderMetricWhere comment and drop the gauge and sum rows from the missing-column test.
Deep Review✅ No critical issues found. The change is tightly scoped and well-tested.
🔵 P3 nitpicks (2)
Reviewers: correctness, testing, maintainability, project-standards, performance, kieran-typescript, adversarial, previous-comments, agent-native, learnings-researcher. Testing gaps: Behavioral coverage exists for real No finding blocks merge automatically. A maintainer will expect P0/P1 findings in code this PR changes to be fixed; P2/P3 are your call -- fix or reply. Never fix findings about surrounding code here; reply instead. Do not widen the PR. How to respond |
A counter that restarts behind a staleness marker is now compared with its last real point, so the Sum query clamps the restart minute to 0, as it does for every counter reset. On main the marker's zero was the baseline, so this one case read the restart value. Say so in the renderMetricWhere comment, and test that a restart behind a marker reads the same as one without. Also check the minute before the marker in the histogram count tests, and that Flags of other integer types gets the filter.
|
Re the Deep Review above (271830e):
That is two rounds of bot findings, so I'll stop here. Could a maintainer approve the workflows and look at the scope when you have time? |
Summary
When a scraped series goes away, or one scrape fails, the collector's
prometheusreceiver sends one more point withFLAG_NO_RECORDED_VALUEset. This is the OTLP form of a Prometheus staleness marker. The ClickHouse exporter stores it withFlags = 1and zeros:Value = 0for gauges and sums, zero bucket counts for histograms, andCount = 0,Scale = 0and no buckets for exponential histograms. The metric queries inrenderChartConfigdid not readFlags, so they used the marker as a real zero. Alerts use the same queries.Effects on
main, measured in the new integration test:minis 0 instead of 10,avgis 5 instead of 10, andcountis 2 instead of 1. This is the case from Metric charts read data points flagged "no recorded value" (Prometheus staleness markers) as zeros #3511.increaseshows 130 for the next minute instead of 10.countis 15 instead of 5, and p50 is 7.5 although all new observations are in (10, 30].Changes:
renderMetricWhereinrenderChartConfig.tsaddsbitAnd(Flags, 1) = 0to theWHEREof the source CTE. The gauge, sum, histogram and exponential histogram paths use it, so single and multi-series metric charts and metric alerts all get it. The filter applies before the window functions, so the next point is compared with the last real point.Flagscolumn of a non-Nullableinteger type (UInt32in the OTel schema). The gauge and sum queries already readFlags, butReqMetricTableColumnsdoes not require it on histogram tables, and no table needs it to be an integer. So a custom table without it, or with another type, gets the same query as before. A failed column lookup also keeps the old query. The lookup reads theDESCRIBEresult that the same query already caches, so it adds no ClickHouse query.Things to be aware of:
NullableFlagscolumn is skipped on purpose:bitAnd(NULL, 1) = 0isNULL, so the filter would drop rows whoseFlagsisNULL.SourceCTE). Onmain, the marker's 0 was the baseline, so this one case read 5. A counter that comes back without a restart (one failed scrape) no longer counts its whole total again. Using the Prometheus reset rule (Valuewhen it is below the previous point) would fix both, but it changes every counter reset, so I left it out of this PR.Tests
packages/common-utils/src/__tests__/metricNoRecordedValue.int.test.ts(ClickHouse): one table per metric type with the OTel columns, and marker rows shaped like the receiver output (checked againstreceiver/prometheusreceiver/internal/metricfamily.go). 10 tests: gaugemin,avgandcountat the marker minute, the minute before it (a control), sumincrease, a counter restart behind a marker against the same restart without one, histogramcount(with the minute before the marker) and p50, and exponential histogramcount(with the minute before the marker) and p50.renderChartConfig.test.ts: the filter is in the source scan once for each metric type and aggFn whenFlagsisUInt32, and it is there forUInt8,Int64andLowCardinality(UInt32). It is not there when a histogram table has noFlags, whenFlagsisString,Nullable(UInt32)orLowCardinality(Nullable(UInt32)), or when the lookup fails.With
renderChartConfig.tsfrommain, 9 of the 10 integration tests fail (the control passes), and 9 of the 17 new unit tests fail (the "same query as before" cases pass on both). Without the type check, the 3 type tests fail.renderChartConfig.test.tsandbuilderToRawSql.test.ts(290 tests, no snapshot changes) andqueryChartConfig.int.test.ts(81 tests) pass. ESLint (no new warnings) and Prettier pass. I ran the integration tests on ClickHouse 26.8.14.3 and 26.8.34.13; CI uses 26.8.36.3.I did not run the
apiintegration tests (alerts, MCP),tscfor the whole package, or the UI steps below. I ran the SQL below against the OTel gauge schema on ClickHouse 26.8.How to reproduce
Starting point:
yarn dev, which creates the OTel metric tables in thedefaultdatabase of the local ClickHouse.On
main:Insert two series of one gauge for the last 6 minutes. Series
bends with a marker in the last minute:Open Chart Explorer. Pick the metrics source and the gauge
repro.cpuwith the Min aggregation, over the last 15 minutes with a 1 minute granularity.The last minute drops to 0. With Avg it drops to 5.
On this branch:
The new integration test covers the other metric types:
make dev-int-common-utils FILE=metricNoRecordedValue.How to test on Vercel preview
N/A — non-UI change.
References
valueExpression: gauge and sum charts always aggregate the raw value #3499, Number tile on a gauge changes with the time range:sumadds up every sample in short ranges #3500 and Series limit isn't applied in SQL for metric charts: every series is fetched, then capped in the browser #3502 report other problems in the same metric queries. This PR does not change them.