execute_range_query_pipeline's dual-population handling (added in #582, fixing #580) fetches and merges keys_query once, anchored at the range's end, and reuses that single snapshot for every output timestamp in the range. If the key set changes partway through the queried interval — a label combination first appears, or stops appearing — every step still uses the final snapshot: keys get phantom samples before they existed, or keep appearing after they stopped, instead of reflecting the key set as it actually was at each step's time.
Please also check other parts of the codebase for the same class of bug: any code path that fetches/merges a value or key set once and then reuses it across multiple output points in a time series, where the underlying set can legitimately change over that span. handle_binary_expr_range_promql's per-arm range context (build_arm_range_context, which calls the same finish_range_context/keys-snapshot logic per arm) is one likely candidate worth checking specifically.
Related: keys_query window scoping is instant-anchored, not range-aware
finish_range_context widens values_query to [start-lookback, end] for a range query, but clones keys_query unchanged — its window is still computed from a single instant (create_keys_query_params, using only end_timestamp), which is wrong for both key aggregation types, in opposite directions:
SetAggregator ("latest window only"): keys window is [end-window_size, end]. Since the range loop iterates merged_keys (not all_data), a label with real values earlier in the range but absent from that final window's key snapshot never gets iterated at all.
DeltaSetAggregator ("all keys since start"): keys window is [0, end]. This is actually necessary because this aggregator is a "delta" version of SetAggregator. It requires replaying every delta bucket back to the initial full-snapshot window
Fixing this properly likely means fetching/merging keys per output step, scoped consistently with each step's own window, rather than once at the range's end — which would fix both this scoping mismatch and the snapshot-staleness problem above as the same fix.
execute_range_query_pipeline's dual-population handling (added in #582, fixing #580) fetches and mergeskeys_queryonce, anchored at the range'send, and reuses that single snapshot for every output timestamp in the range. If the key set changes partway through the queried interval — a label combination first appears, or stops appearing — every step still uses the final snapshot: keys get phantom samples before they existed, or keep appearing after they stopped, instead of reflecting the key set as it actually was at each step's time.Please also check other parts of the codebase for the same class of bug: any code path that fetches/merges a value or key set once and then reuses it across multiple output points in a time series, where the underlying set can legitimately change over that span.
handle_binary_expr_range_promql's per-arm range context (build_arm_range_context, which calls the samefinish_range_context/keys-snapshot logic per arm) is one likely candidate worth checking specifically.Related: keys_query window scoping is instant-anchored, not range-aware
finish_range_contextwidensvalues_queryto[start-lookback, end]for a range query, but cloneskeys_queryunchanged — its window is still computed from a single instant (create_keys_query_params, using onlyend_timestamp), which is wrong for both key aggregation types, in opposite directions:SetAggregator("latest window only"): keys window is[end-window_size, end]. Since the range loop iteratesmerged_keys(notall_data), a label with real values earlier in the range but absent from that final window's key snapshot never gets iterated at all.DeltaSetAggregator("all keys since start"): keys window is[0, end]. This is actually necessary because this aggregator is a "delta" version ofSetAggregator. It requires replaying every delta bucket back to the initial full-snapshot windowFixing this properly likely means fetching/merging keys per output step, scoped consistently with each step's own window, rather than once at the range's
end— which would fix both this scoping mismatch and the snapshot-staleness problem above as the same fix.