From bb7ec495d2a48d0e7ef8d8e42c3117b10d5cdab3 Mon Sep 17 00:00:00 2001 From: Julien Pivotto <291750+roidelapluie@users.noreply.github.com> Date: Wed, 3 Jun 2026 15:23:58 +0200 Subject: [PATCH] promql: fix stale matchedSigs maps breaking fill_left in range queries resetMatchedSigs used clear() to recycle inner maps, but the fill-LHS loop checked matchedSigs[sigOrd] \!= nil to decide whether an RHS sample was already matched. A cleared but non-nil map from a previous timestep caused a false positive, skipping the fill and producing missing samples in range queries using group_left fill_left or group_right fill_right. Fix by restoring clear() for map reuse and changing the check to len(matchedSigs[sigOrd]) > 0, which correctly treats an empty map as unmatched while preserving the allocation-reuse across timesteps. Signed-off-by: Julien Pivotto <291750+roidelapluie@users.noreply.github.com> --- promql/engine.go | 2 +- promql/promqltest/testdata/fill-modifier.test | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/promql/engine.go b/promql/engine.go index 4b8e12a10b..9b7f0fba3e 100644 --- a/promql/engine.go +++ b/promql/engine.go @@ -3221,7 +3221,7 @@ func (ev *evaluator) VectorBinop(op parser.ItemType, lhs, rhs Vector, matching * sigOrd := rhsh[i].sigOrdinal if (matching.Card == parser.CardOneToOne && matchedSigsPresent[sigOrd]) || - (matching.Card != parser.CardOneToOne && matchedSigs[sigOrd] != nil) { + (matching.Card != parser.CardOneToOne && len(matchedSigs[sigOrd]) > 0) { continue // Already matched. } ls := Sample{ diff --git a/promql/promqltest/testdata/fill-modifier.test b/promql/promqltest/testdata/fill-modifier.test index 079a48cc99..8061147a12 100644 --- a/promql/promqltest/testdata/fill-modifier.test +++ b/promql/promqltest/testdata/fill-modifier.test @@ -309,6 +309,21 @@ eval range from 0 to 20m step 5m intermittent_left + fill_left(0) intermittent_r {label="b"} 100 _ 300 _ 500 {label="c"} 1000 _ _ 4000 5000 +# Regression test: group_left fill_left in a range query must apply fill values +# at every timestep, even when the RHS sigOrd was matched at a previous timestep. +# resetMatchedSigs previously used clear() which left non-nil empty maps, causing +# the fill-LHS path to incorrectly skip unmatched RHS samples on subsequent timesteps. +clear + +load 5m + matched_lhs{key="a"} 1 _ 3 + matched_rhs{key="a"} 10 20 30 + matched_rhs{key="b"} 100 200 300 + +eval range from 0 to 10m step 5m matched_lhs + on(key) group_left fill_left(0) matched_rhs + {key="a"} 11 20 33 + {key="b"} 100 200 300 + # ---------- fill with vectors where one side is empty ---------- clear