Skip to content

fix(query): honor counter-function semantics in ExactAgg dispatch (closes #301, #300) - #304

Merged
zzylol merged 2 commits into
mainfrom
fix/counter-fn-semantics-301
May 20, 2026
Merged

zzylol merged 2 commits into
mainfrom
fix/counter-fn-semantics-301

Conversation

@zzylol

@zzylol zzylol commented May 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #301 (subsumes #300). The asap engine's ExactAgg(Sum) dispatch collapsed sum / sum_over_time / increase / rate into the same code path, ignoring PromQL function semantics. Measured rel-err vs VictoriaMetrics baseline was sum 35%, increase 35%, sum_over_time 99%, rate 64%.

The 4 layered bugs (now fixed)

  1. Dispatch collapse — only evaluate_exact_agg_rate (if rate) vs evaluate_exact_agg (everything else); function name never consulted beyond rate-vs-not.
  2. Lookback collapse — instant-sum used DEFAULT 5min lookback; [5m] queries used range=5min; producer ran <5min so all captured the same windows → identical values.
  3. Instant .last()asap_tier_result_to_query_result took the last window's per-window delta for instant queries instead of accumulating to a cumulative.
  4. Rate divisor — divided by nominal range_seconds (300s) regardless of actual data coverage.

Fix

  • Analyzer (asap_tier_analysis.rs, capability.rs): type-track the counter-function semantic distinctly (extends refactor(query): preserve rate/sum_over_time distinction in analyzer (kill PromQL string re-parse from PR #292) #295's OuterFn).
  • Engine dispatch (engine.rs): route each counter-fn to the correct reducer path.
  • Reducer (sketch_reducer.rs, index/mod.rs): InstantSum accumulates windows into cumulative; Increase clips to [t-r,t]; Rate divides by actual coverage; sum_over_time(counter[r]) → CapabilityMiss → archive (honest refusal rather than fabricating a wrong number).

Multinode validation (4 zones, 10000 series @ 100Hz, asap node2:9091 vs VictoriaMetrics node2:8428)

Query Before After
rate 64% rel-err 0.26%
topk(rate) 64% tracks rate ✅
sum_over_time 99% (fabricated) capability-miss → archive (honest)
sum/increase/rate all identical (collapse) now distinct values ✅

Working queries (quantile_over_time, max by (zone) (quantile_over_time)) unaffected. Instant sum is cumulative-since-storage (residual gap is producer-runtime/flush-lag, not a dispatch bug).

Tests

  • cargo test -p control_plane --lib: 757 passed
  • cargo test -p data_plane --lib: 755 passed
  • New per-counter-fn regression tests with canned per-window-delta fixtures

Note

Rebased onto post-#302 (PromQL-only) main; removed an obsolete query_string_sql_populates_workload test + de-SQL'd two doc comments that #302 left behind (separate cleanup commit).

Closes #301
Closes #300

zzylol and others added 2 commits May 20, 2026 07:56
…oses #301, #300)

Post-#299 the agent streams per-window DELTAS for counters, but the asap
engine collapsed `sum` / `sum_over_time` / `increase` / `rate` — four
semantically-distinct PromQL counter idioms — onto the same two reducer
paths, so all instant counter sums returned the same wrong number and
`rate` was systematically under-reported. Four layered bugs:

  Layer 1 (engine dispatch): only `rate` vs everything-else branched;
    sum / sum_over_time / increase / instant-sum all hit one path.
  Layer 2 (lookback): instant sum used a 5-min default == the [5m]
    range, so on a <5min producer every idiom captured the same horizon.
  Layer 3 (instant projection): the instant branch took
    `samples.last()` — the MOST RECENT window's delta — instead of the
    cumulative-since-storage value PromQL `sum(counter)` requires.
  Layer 4 (rate divisor): `evaluate_exact_agg_rate` divided by the
    NOMINAL range (300 for [5m]) regardless of how much data actually
    covered the window, halving the rate when the producer ran < range.

Fix:
- control_plane: extend `OuterFn` from {Plain, Rate} to the full
  counter-fn taxonomy {Plain, Rate, Increase, SumOverTime}, populated by
  the analyzer's `trace_from_promql` walker (inner-counter-idiom wins for
  composed shapes like `sum by (..) (rate(..))`; precedence enforced by a
  new `set_counter_fn` helper). Carried on `ASAPTierCandidate.outer_fn`.
- engine dispatch (instant + range surfaces): branch on the typed
  counter-fn — `Rate` → rate reducer; `Increase` → accumulate windows
  over the [t-r,t] clip into one cumulative number; `Plain` instant sum →
  accumulate ALL windows over the full storage horizon (t0=0) →
  cumulative-since-start; `SumOverTime` over a counter → capability-miss →
  archive (asap stores deltas and cannot reconstruct the
  Σ-of-cumulative-samples sum_over_time wants — issue #301 decision (a),
  subsumes #300).
- reducer: `evaluate_exact_agg` gains an `accumulate_windows` flag that
  collapses each group's per-window deltas into ONE cumulative sample
  (Layer 3); the matrix/range surface keeps `accumulate_windows=false`.
  `evaluate_exact_agg_rate` now divides by `min(range_seconds,
  actual_coverage_span_seconds)` via a new
  `SketchStore::exact_agg_coverage_bounds(sid, t0, t1)` that reports the
  true `(min_window_start, max_window_end)` span (Layer 4).

Tests: control_plane 783, data_plane 755 green. New unit + integration
coverage pins each counter-fn semantic (instant-sum accumulates all
windows not last; increase accumulates without divisor; sum_over_time
capability-misses; rate divisor uses actual coverage).

Multinode validation (4 zones, 10000 series @ 100 Hz; asap node2:9091
vs VictoriaMetrics baseline node2:8428), steady state:
  rate  : asap 997,413  b0 999,987  rel-err  0.26%  (was 64%)
  topk(rate): asap 997,413 (tracks rate; unchanged semantic)
  sum_over_time: asap capability-miss → archive (no longer fabricates)
The four idioms now return distinct values (sum != increase != rate),
confirming the dispatch no longer collapses them. Working queries
(`quantile_over_time(0.99, ...)`, `max by (zone) (quantile_over_time)`)
unaffected. Instant `sum` is cumulative-since-storage (runtime-dependent;
residual gap is producer-runtime / flush-lag, not a dispatch bug).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…omQL-only)

#302 removed the SQL/ElasticDSL front-ends but left `query_string_sql_populates_workload`
(asserts `SELECT ... GROUP BY` parses into a workload) plus two "PromQL or SQL"
doc comments in pipeline.rs. With SQL parsing gone the test fails. Removed the
stale test and de-SQL'd the QuerySpec docs. Surfaced when rebasing the #301
counter-fn fix onto post-#302 main.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zzylol
zzylol merged commit 0756e13 into main May 20, 2026
@zzylol
zzylol deleted the fix/counter-fn-semantics-301 branch July 17, 2026 20:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant