Skip to content

feat(planner): declare counter metrics so CMS+heap can prove non-negative weights - #593

Draft
zzylol wants to merge 2 commits into
stack/509-w1-stage3-costfrom
stack/509-w6-counter-proof
Draft

zzylol wants to merge 2 commits into
stack/509-w1-stage3-costfrom
stack/509-w6-counter-proof

Conversation

@zzylol

@zzylol zzylol commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Rebased on main d4869a7 (DF 54).

Wave 1 chain: #594 → #593 → #592 → #591 → #595 → #596 → #597 → #598 → #599 (on #589)

Part of #509 (Example 1), decision X1 from #580's planning: a metric-type declaration plus a non-negativity proof, so Count-Min + heap is valid over raw counter samples.

Why

Example 1's Q2 is topk by (job) (10, sum_over_time(http_requests_total[1m])). Every Count-Min + heap candidate for it, including #589's whole-expression heap over raw samples, was rejected by Stage 3 and by the runtime, because nothing proved the update weights (raw sample values, or their sums) non-negative. Neither the workload nor the catalog carried a metric type, and the _total suffix is only a naming convention.

What

  • Declaration: DataWorkload.metric_types: BTreeMap<String, MetricType> with MetricType::{Counter, Gauge} (Prometheus # TYPE). It defaults to empty.
    • It goes in DataWorkload because that struct already holds the facts about the queried data, the PromQL frontend already reads its ingestion interval, and plan_stages already receives it, so Stage 1 can read it without a new input layer.
    • HistogramCatalog was not used: it is histogram-specific and lowering-only, and the IR has no place to carry the declaration on to Stage 1.
  • Proof: a new NonNegativeWeightProof::CounterSamples variant.
  • Stage 3 and runtime: unchanged. Both already accept Count-Min for any WeightDomain::NonNegative proof, so they apply the same rule.

How

  • plan_stages passes &data.metric_types to stage1_logical_candidates, which passes it to enumerate_local_logical_candidates. This is a plain parameter.
  • Pass 1 records counter_input on each target. It is true when the target's input is samples of a metric declared a counter, reached only through TimeRange and plain Sum aggregates (sum_over_time, sum by).
  • A keyed summary update weighted by SampleValue over such an input gets CounterSamples. This covers both the heap over per-series sums and the whole-expression heap over raw samples.
  • Arithmetic, any other operator, a gauge, or an undeclared metric (whatever its name) gets no proof.
  • Example 1's workload declares http_requests_total a counter in the stage_pipeline devtool and in the acceptance test. The fixture and acceptance doc are regenerated, and the workflow doc lists the field. The integration commit updates Stage 3 per-second cost model, memory pricing and calibration #594's per-second ranking test and the Stage 3 cost model's worked example to P60.

Before / After (Example 1, built-in models, no cost tuning)

Costs are per second (#594): per-evaluation CPU-ms × 0.1 evaluations/s.

Before (#594) After
Count-Min + heap candidates 24 invalid ("not proven non-negative") 24 valid, all compile in the runtime
Selected P58 (all exact, shared input), 5.220/s (52.201 per evaluation) P60 (Q1 exact, Rate and Sum acc; Q2 Count-Min + heap over the exact sum_over_time accumulator; shared input), 4.620/s (46.201 per evaluation)
Q2 cost in the winner, per evaluation 19.001 (Sum acc 5.0 + sort and limit 14.001) 13.001 (Sum acc 5.0 + CMS 1M sums × depth 8 = 8.0 + estimate 0.001)
P58 rank 1st 8th. Ranks 1–7 are shared-input Count-Min + heap plans over per-series sums, 4.620–5.220/s; P51 ties P58
Whole-expression Count-Min + heap invalid ranks 17th–20th; P63 is cheapest at 6.520/s (per evaluation, 4M raw samples × depth 8 = 32.0)
Whole-expression CountSketch + heap P64, 53.720/s unchanged

Without the declaration, or with http_requests_total declared a gauge, the 24 Count-Min + heap candidates stay invalid and the runtime still rejects them. This is today's behavior, and a test covers it.

Tests

  • Pass 1: a declared counter gives CounterSamples on all 6 heap updates. An undeclared m_total, a gauge, or a different declared counter gives none. counter_samples stops at arithmetic (- 100, unary minus).
  • Stage 3: Count-Min over declared counter samples is priced, not rejected.
  • Acceptance: with the declaration, all 64 candidates are valid and compile. With no declaration or a gauge, 24 are invalid and the runtime rejects the same ones.

Gate

Refs #509, #580.

🤖 Generated with Claude Code

@zzylol
zzylol force-pushed the stack/509-w6-counter-proof branch from f14bf7a to ee6f881 Compare October 4, 2026 18:25
zzylol added a commit that referenced this pull request Oct 4, 2026
Pass 1 takes the declared metric types since #593, and #594's year-long scan
test selects its variant by the Sharing enum instead of a shared flag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
zzylol added a commit that referenced this pull request Oct 4, 2026
Pass 1 takes the declared metric types since #593.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
zzylol added a commit that referenced this pull request Oct 4, 2026
plan_stages takes per-root demand since #594, and DataWorkload declares metric
types since #593; Example 2's pipeline test runs each query once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@zzylol
zzylol changed the base branch from stack/509-e2-keyed-additive-topk to stack/509-w1-stage3-cost October 4, 2026 18:25
zzylol and others added 2 commits October 5, 2026 04:48
…tive weights

DataWorkload gains `metric_types` (metric name -> Counter | Gauge, the
Prometheus TYPE metadata). plan_stages passes it to Stage 1, and Pass 1
attaches the new `CounterSamples` non-negativity proof to a keyed summary
update weighted by the sample value when the target's input is samples of a
declared counter, through a time range and plain sums (sum_over_time). Any
other operator, a gauge or an undeclared metric (whatever its suffix) gets
no proof. Stage 3 and the runtime already accept any NonNegative proof for
Count-Min, so their rule is unchanged.

Example 1 declares http_requests_total a counter: all 64 candidates are
valid and compile (24 Count-Min + heap were invalid). Stage 3 now selects
P60 (Count-Min + heap over the exact sum_over_time accumulator, shared
input) at 46.201 against P58 (all exact) at 52.201, now 8th.

Refs #509, #580.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Example 1 declares http_requests_total a counter, so Stage 3 selects P60 at
46.201 × 0.1 = 4.620 cost per second. Update the per-second ranking test and
the Stage 3 cost model's worked example to P60.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@zzylol
zzylol force-pushed the stack/509-w6-counter-proof branch from ee6f881 to 761b2ce Compare October 5, 2026 06:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant