Repository navigation
fix: sum, avg, max and min over time with unwrap answer the sample at end on query_range, like Loki - #727
Conversation
⚪ Performance A/B: no change beyond noise25 shape×range — 25 ⚪ same · results vs Loki: 24 same, 0 new difference(s), 1 pre-existing Every shape is within noise of the base build (±30% and 100 ms). ¹ re-measured with 7 runs, on windows the first pass did not use, after it moved beyond noise. 25 unchanged shape×range (within noise)
Cold (first-run) timings — what a user opening a panel sees
† Loki's own counters say its results cache answered this cold run: not a computed time. Why these shapes
base |
✅ Visual smoke: passed with warningsBase Fix proofCaptures of the exact cases this pull request adds or fixes (registry case, base | PR | Loki). The verdict compares the PR with Loki where the base did not match. The gate fails a case unless one capture is fixed or improved, or, beyond the Loki data window, the base failed where Loki and the PR both answered with data (the label does not excuse it; only
Regression check
Gate: a base-vs-PR data difference passes when the PR is closer to Loki, and fails when the PR diverges from Loki or Loki cannot decide it (at a range Loki does not hold, a difference passes when the same page proved it closer to Loki at a shorter range; otherwise label the pull request explore-unwrap-max-over-time-end-sample-6h: no Loki data at this range; the same differences match Loki at a shorter range (1): query ('max by (level) (max_over_tim
Core set montages (5): base | PR | Lokidd-fields-1h dd-landing-1h dd-service-logs-1h explore-logs-plain-1h explore-metric-plain-1h All montages of this run: |
PR Quality ReportCompared against base branch Coverage and tests
Compatibility
Performance smokeLower CPU cost (
State
|
1a34147 to
b8f4962
Compare
fa407ac to
810cc10
Compare
810cc10 to
d26f6a0
Compare
d26f6a0 to
f6f6e67
Compare
… end on query_range, like Loki Loki evaluates a range query at start, start+step, ... up to and including end, each over (t-range, t]. These functions were read from VictoriaLogs' step buckets as labelled, so the point at end was the bucket after the last line and was missing while the data stops at end. They are now fetched from start-step on anchored step buckets and relabelled onto the evaluation time each closes, as rate and count_over_time over a range equal to the step already are. For a range equal to the step the values are Loki's window; a longer or shorter range keeps the step-sized bucket value.
… literal-name detection cases meaningful
… and regenerated reports
…hich a parser's series identity cannot change
f6f6e67 to
de6d00d
Compare








Summary
Gap
semantics/unwrap-range-last-point. Over an hour at step 60s,sum by (level) (max_over_time({service_name="api-gateway"} | json | unwrap status | __error__="" [5m]))has 61 samples on Loki 3.7.7 (start..end inclusive,pkg/logql/range_vector.go) and 60 on the proxy. The same happens forsum_over_time,avg_over_timeandmin_over_time.Cause. These functions use VictoriaLogs
stats_query_range. Its bucket labelled T holds [T, T+step), and the request's start/end were passed through unchanged. So the point atendcame from the bucket afterend, which is empty when the data stops atend.Fix.
sum/avg/max/min_over_timewith an unwrap stage now take the shifted fetch thatcount_over_timewith range == step already uses: fetch from start−step, then relabel each bucket +step and keep [start, end]. There is no new dispatch path and no new VictoriaLogs feature (metric_binary.go,range_metric_compat.go).semantics/unwrap-over-time-range-windows.semantics/unwrap-over-time-range-windows.Tests
TestUnwrapOverTimeRangeEmitsEndSample: 3 VictoriaLogs versions × json/logfmt × 4 functions × 5 shapes; 96 subtests failed before the fix.TestUnwrapOverTimeTumblingMatchesLoki: values against a Loki-semantics reference; 24 subtests failed before the fix.TestStatsRateRangeEqualsStepShift_Detection: updated for the flipped cases.TestCompat_UnwrapRangeEndSample(e2e vs Loki).Rebased on main
Rebased onto main after the VictoriaLogs v1.49.0 floor (#770), the
sum withoutfold (#739) and #582. The pre-v1.45 epoch-grid case is gone (theoffsetargument exists on every supported version); the emulator variants are now unknown, v1.49.0 and v1.52.0. On main the new tests fail:TestUnwrapOverTimeRangeEmitsEndSample96 of 120 cases (the 24 that pass end between evaluation times),TestUnwrapOverTimeTumblingMatchesLoki24 of 24,TestStatsRateRangeEqualsStepShift_Detection7 of 33; on this branch they pass.Two tests on main pinned the end-sample bug itself, and their answers change. Loki evaluates start..end inclusive (start=base-120, end=base, step=60: three samples); main answered two, each labelled one step early.
TestWithoutFoldIsLimitedToTheOperatorsItProves(query_range columns only; instant and limit columns unchanged): the old values are now one step later, plus the sample atend, for examplemax without (pod)x=20,12 y=24,8 → x=36,20,12 y=32,24,8 andmax(sum without ...)164,68 → 260,164,68. The sliding[5m]values at step 60 still follow the open rowsemantics/unwrap-over-time-range-windows.TestExtractionListOutsideWithoutKeepsItsGrouping: 24 golden entries (tumbling and sliding unwrap sum, avg, min and max). The LogsQL sent to VictoriaLogs is identical in all 24; each new answer is the old one with every timestamp one step later and at most one extra sample; the tumbling[5m]cases at step 300 now match Loki exactly.stddev_over_timeandstdvar_over_timewith unwrap still drop the sample at end (not covered here). Capped unit run (the proxy package in batches on this host),-raceon the touched tests, vet with every tag, lint and the conformance and changelog gates pass.Proofs in progress
unwrap_range_end_sample_test.go).Rebased onto main (2026-10-22)
Base moved from
8d57747b(v2.19.0) to304b5549(v2.25.1 plus #827). The PR is not stacked on #728 or #730; it is rebased onorigin/mainalone. Headd26f6a07, four commits, all signed, subjects unchanged.CHANGELOG.md(main's[Unreleased]is empty, the entry sat next to a cut section)## [Unreleased]/### Fixed; no released section edited.bench/visual/spec.json(two entries appended at the same place: main'sexplore-unwrap-quantile-over-timefrom #731 and this PR'sexplore-unwrap-max-over-time-end-sample)conformance/registry/generated/wiring.json,conformance/reports/{gaps,parity-gaps,roadmap}.mdwire.py,perf_evidence.py,coverage_map.py,gaps.py,translation_map.py,roadmap.py,matrix_link.py,parity_gaps.py); folded into the "visual fix proof page ... regenerated reports" commit.metric_binary.go,range_metric_compat.go, tests)Main since the previous rebase changed nothing this fix assumes: the golden table of
TestExtractionListOutsideWithoutKeepsItsGroupingandTestWithoutFoldIsLimitedToTheOperatorsItProvesstill carry the 24 and thequery_rangecolumns changed by this PR, and they pass.Gates (phase 1, no Docker, capped
GOMEMLIMIT=3GiB GOMAXPROCS=4)gofmt -l,go build ./...,go vet ./...andgo vet ./test/e2e-compat/...andgo vet -tags=e2e ./test/e2e-compat/...(the e2e files carry//go:build e2e; compile only, nothing run): clean.go test -short -count=1 -p 1 ./...and the./internal/... -jsonrun that feeds the conformance gate: all 13 packages ok (internal/proxy120 s; peak RSS 1.33 GB with/usr/bin/time -l; not compared with main in this phase).go test -count=1 -run 'TestSlidingWindowStats_|TestOrderedJSONExtractedMetricDifferential' ./internal/proxywithout-short(the tests that skip under-shortand declare the registry items near this change; CI's evidence step runs without-short): ok.-raceon the touched tests:TestUnwrapOverTimeRangeEmitsEndSample,TestUnwrapOverTimeTumblingMatchesLoki,TestStatsRateRangeEqualsStepShift_Detection,TestWithoutFoldIsLimitedToTheOperatorsItProves,TestExtractionListOutsideWithoutKeepsItsGroupingok.python3 scripts/ci/check_conformance.py --go-results <unit json>: ok (414 tests declare 137 items, 133 with a passing run, 0 failing).python3 -m unittest discover -s scripts/ci/tests -p 'test_*.py': 181 tests OK. Harness tests (bench/ab,bench/visual,bench/parity): OK.python3 scripts/ci/check_changelog_pr.py --base origin/main --head HEAD: ok.go run ./cmd/configdoc -check: ok.Overlaps. With #728: only
CHANGELOG.md,bench/visual/spec.jsonand the generated files conflict textually;first_over_timeandlast_over_timeare not touched by this fix. With #730:internal/proxy/extraction_list_outside_without_test.go(this PR changes the answers of 24 goldens, #730 changes the gate text in the sent LogsQL of 50) conflicts, so whichever of the two lands second regenerates that table. Merged together in a scratch tree (727+728+730), the only failing test is that golden table.Proofs to be refreshed (phase 2, need the stack)
Supported VictoriaLogs for the e2e runs: the floor v1.49.0 and the newest v1.53.0, with the CI pin v1.52.0 (the PR text from before the floor moved named older lines). Commands run from the worktree
$SCRATCH/wt-727on the PR head; the Colima VMcolima-lvpis the Docker host (DOCKER_CONTEXT=colima-lvp), and the shared e2e stack must not be running while a fresh stack is started on the same ports.1. e2e vs Loki (one fresh stack per group and per VictoriaLogs version)
Then the two CI groups that cover the touched code, each on its own fresh stack on v1.52.0 and v1.49.0 (same up/down lines,
-runpattern copied from.github/workflows/ci.yaml):2. A/B proof (previous release / base vs PR vs Loki, own compose project and ports)
Plus
go test -run '^$' -bench <touched benchmarks> -benchmem -count=6 ./internal/translator ./internal/proxyonorigin/mainand the head, compared withbenchstat, and the backend-safety load test (many 24h/7d queries concurrently, VictoriaLogs RestartCount before and after,/labelsprobe answering 200).3. Visual proof (isolated stack, base | PR | Loki)
Plan:
explore-unwrap-max-over-time-end-sampleat 15m/1h/6h (the fix proof,fixes: semantics/unwrap-range-last-point) plus the core set (explore-logs-plain,explore-metric-plain,explore-A,explore-pattern-filter-anchored,explore-unwrap-quantile-over-time,dd-landing,dd-service-logs,dd-fields); 19 captures.4. Real Grafana range matrix: the shapes below through the Loki-type datasources of the proxy and of Loki direct, over 5m, 15m, 1h, 6h, 24h and 7d where the data exists, compared for status, errors, series counts and totals.
Matrix shapes:
sum by (level) (max_over_time({service_name="api-gateway"} | json | unwrap status | __error__="" [$__auto]))and the same withsum_over_time,avg_over_time,min_over_time, plus... unwrap duration_msforms; range == step and range != step.