Skip to content

feat(sid): L4 binder rule emits PhysicalExpr::ExactAgg for Sum/Rate/Increase/Count - #201

Merged
zzylol merged 1 commit into
mainfrom
refactor/sid-l4-bind-exact-agg
May 14, 2026
Merged

zzylol merged 1 commit into
mainfrom
refactor/sid-l4-bind-exact-agg

Conversation

@zzylol

@zzylol zzylol commented May 14, 2026

Copy link
Copy Markdown
Contributor

Summary

PR 6 follow-up part 2. PR #200 flipped capability_for to route Sum / Rate / Increase / Count{Exact} to ExactAgg. This PR adds the L4 binder rule so the optimizer's plan output reflects the flip — without it, those intents passed through as PhysicalExpr::Logical and the L5 emitter routed them to archive anyway.

What's new

  • PhysicalExpr::ExactAgg { agg_type, child } variant — L4 counterpart to the data-plane AggKind::ExactAgg. No SketchEstimate wrapper because ExactAgg produces the answer directly.
  • bind_exact_agg::BindExactAgg rule, priority 2:
    • SumExactAgg(Sum)
    • Rate { window } / Increase { window }ExactAgg(Increase)
    • Count { Exact }ExactAgg(Sum) (count_over_time = sum-of-1s)
  • Wired into dispatch; exhaustive-match coverage added in emit::extract_root_sketch_kind, optimizer::rules::extract_family, colored_dag::allocator (StageId::Edge, same locality as SketchAgg), and the test walkers.

Stays on archive

  • Avg — needs cross-policy Sum+Count join.
  • Min / Max — quantile-sketch path (DDSketch/KLL via quantile(0)/quantile(1)) is unchanged.
  • Approximate intents (Quantile/Cardinality/TopK/Frequency) — sketch families.
  • Archive-only intents (Absent/Present/Delta/etc).
  • Keyed by: [..] Sum/Rate/Increase — MultipleSum / MultipleIncrease ExactAgg binding deferred to a follow-up.

Test plan

  • cargo check --workspace clean
  • cargo test --workspace --lib --bins green (one pre-existing HashMap-iteration-order flake unchanged)
  • 9 new unit tests on BindExactAgg
  • Updated 2 existing tests that locked in the old "Sum passes through as Logical" behavior

🤖 Generated with Claude Code

…ncrease/Count

PR 6 follow-up part 2. The PR 6 follow-up (PR #200) flipped
`capability_for` to return `Some(Capability::ExactAgg(...))` for Sum /
Rate / Increase / Count{Exact} so the analyzer surfaces those intents
as warm-tier candidates. But the L4 binder had no rule to lower them
into the algebra — they kept passing through as `PhysicalExpr::Logical`,
which the L5 emitter routes to archive. This PR adds the missing
binder so the optimizer's plan output finally reflects the analyzer
flip.

## What

1. New variant `PhysicalExpr::ExactAgg { agg_type, child }`
   (`sketch_algebra/physical_expr.rs`). Counterpart to the data-plane
   `AggKind::ExactAgg` / `AggPayload::ExactAgg` shape. No
   `SketchEstimate` wrapper because ExactAgg produces the answer
   directly — the accumulator's value IS the result.

2. New convenience constructor `PhysicalExpr::exact_agg_over_logical`,
   mirroring the existing `estimate_over_agg` for sketches.

3. New binder rule `bind_exact_agg::BindExactAgg`
   (`sketch_algebra/rules/bind_exact_agg.rs`) with priority `2`
   (above `bind_archive_only`'s 1, below the sketch families at 4–6).
   Maps:
   - `AggIntent::Sum`                  → `ExactAgg(Sum)`
   - `AggIntent::Rate { window }`      → `ExactAgg(Increase)`
   - `AggIntent::Increase { window }`  → `ExactAgg(Increase)`
   - `AggIntent::Count{Exact}`         → `ExactAgg(Sum)` (count = sum-of-1s)

4. Wired into `dispatch` in `sketch_algebra/rules/mod.rs`.

5. Exhaustive-match coverage added in three downstream walkers:
   - `emit::extract_root_sketch_kind` → `None` (no sketch family)
   - `optimizer::rules::extract_family` → `None`
   - `physical::colored_dag::allocator::visit` → `StageId::Edge`
     (same locality as `SketchAgg`; accumulator runs in the precompute
     pipeline at edge)
   - `sketch_algebra::tests::collect_sketch_kinds` → recurse into child
   - `sketch_algebra::tests::binding_is_archive` → `false` (warm path,
     not cold)

6. Updated two existing tests that asserted Sum's old behaviour:
   - `bind_no_match_passes_through_logical` →
     `sum_now_binds_to_exact_agg_after_pr_6_followup`
   - `phase_b_pattern_only_temporal_sum_falls_through_to_logical` →
     `phase_b_pattern_only_temporal_sum_binds_to_exact_agg`

## What's not in scope

- `bind_exact_agg` rejects `Aggregate{by: [..]}` inputs. A keyed-group
  ExactAgg follow-up would emit `MultipleSum` / `MultipleIncrease`
  instead. The dispatch / allocator / emit walkers already handle
  ExactAgg structurally; only the binder logic gates on `by.is_empty()`.
- `AggIntent::Avg` still routes to archive — needs cross-policy
  Sum+Count join, separate concern.
- `AggIntent::Min` / `Max` stay on the quantile-sketch path
  (DDSketch/KLL answer them via quantile(0)/quantile(1)); adding a
  MinMax ExactAgg binding would compete with that path unprofiled.
  Decision deferred.

## Test plan

- [x] `cargo check --workspace` clean
- [x] `cargo test --workspace --lib --bins` green (one pre-existing
      `avg_finds_sum_and_count` HashMap-iteration-order flake is
      unchanged; passes on retry)
- [x] 9 new unit tests on `BindExactAgg` covering each intent +
      negative cases (by-clause / Avg / approximate-Count / zero-window)
      + priority gate

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zzylol
zzylol merged commit f26cbf0 into main May 14, 2026
zzylol added a commit that referenced this pull request May 14, 2026
`count_over_time` was routed to the warm-tier ExactAgg path by PRs
#200 (capability_for) and #201 (bind_exact_agg) on the theory
"count = sum-of-1s". But the data plane has no count accumulator:

  struct SumAccumulator { sum: f64 }   // no count field

  fn query(&self, statistic, ..) {
      match statistic {
          Statistic::Sum | Statistic::Count => Ok(self.sum),  // ← both!
          ...
      }
  }

`SumAccumulatorUpdater::update_single` does `self.sum += value` — no
projection-to-1 anywhere. So a `count_over_time(m[5m])` query matched
against an `m` Sum policy returned the **sum of the sample values**,
not the count of samples. Silently wrong.

This regression was introduced by my PRs #200/#201 — before them
`capability_for(Count{Exact})` returned `None` and the query went to
the archive engine, which counts correctly.

## Changes

- `capability_for(AggIntent::Count { accuracy: Exact })` →  `None`
  (was `Some(ExactAgg(Sum))`). `count_over_time` routes to archive.
- `bind_exact_agg` — `Count{Exact}` arm removed; the rule no longer
  emits `ExactAgg(Sum)` / `ExactAgg(MultipleSum)` for count.
- Tests updated: `capability_for_count_exact_routes_to_archive`,
  `exact_agg_routing_covers_sum_rate_increase_only`,
  `does_not_bind_count_exact`, `keyed_count_exact_does_not_bind`.

`AggIntent::Count { accuracy: Epsilon/EpsilonDelta }` is unchanged —
the approximate-count (`count by (...) (count_over_time(...))`
distinct-count idiom) still routes to `CardinalityApprox`.

## Follow-up

The correct fix — a real `SumCountAccumulator { sum, count }` that
answers Sum / Count / Avg — lands with the temporal/spatial-split
work. Until then, `count_over_time` and `avg_over_time` are
archive-served (correct, just not warm-tier-accelerated).

## Test plan

- [x] `cargo check --workspace` clean
- [x] `cargo test --workspace --lib --bins` green

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zzylol
zzylol deleted the refactor/sid-l4-bind-exact-agg branch July 17, 2026 20:06
zzylol added a commit that referenced this pull request Jul 20, 2026
…ivergences on Count and Min/Max

Fixes the two real behavioral divergences found while auditing
capability_for against sketch_algebra::rules::dispatch (both claim to
answer the same AggIntent -> ASAP-tier-capability question but disagreed):

- Count{non-Exact}: capability_for claimed CardinalityApprox (HLL,
  "distinct count" semantics); rules::dispatch's BindCmsOnCount treats
  it as a bare frequency point-query (CMS). ASAPController's own
  crates/plan/src/bind.rs::readout maps Count to SketchQuery::PointCount,
  confirming CMS is correct -- the CardinalityApprox premise never had a
  real caller anyway (distinct_over_time/COUNT(DISTINCT) always lower to
  AggIntent::Cardinality, never Count). AggIntent::Count{non-Exact} is
  unreachable via this repo's own PromQL frontend today regardless
  (lower.rs only constructs Count{Exact} or the Extension-based
  Frequency intent), so this is a consistency fix, not a live routing
  change.

- Min/Max: capability_for claimed QuantileApprox (min = quantile(0),
  max = quantile(1)), but no rule in sketch_algebra::rules actually
  implements that -- bind_kll_quantile/bind_ddsketch_quantile only ever
  match AggIntent::Quantile, never Min/Max, so the promised coverage
  didn't exist and Min/Max silently fell through to archive regardless.
  ASAPController's own crates/plan/src/boundary.rs treats Min/Max as an
  exact mergeable accumulator (SummaryKind::MinMax), same tier as
  Sum/Rate/Increase -- correct, since comparing two partial extrema
  needs no approximation at all. bind_exact_agg.rs now binds
  Min/Max -> AggregationType::MinMax (keyed -> MultipleMinMax), using
  the data plane's already-fully-wired MinMaxAccumulator; capability_for
  now returns ExactAgg(MinMax) to match.

Also fixes a stale doc-table line (Count{Exact} claimed to return
Some(ExactAgg(Sum)); the actual code has returned None since the
PR #200/#201 revert).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zzylol added a commit that referenced this pull request Jul 21, 2026
…ivergences on Count and Min/Max

Fixes the two real behavioral divergences found while auditing
capability_for against sketch_algebra::rules::dispatch (both claim to
answer the same AggIntent -> ASAP-tier-capability question but disagreed):

- Count{non-Exact}: capability_for claimed CardinalityApprox (HLL,
  "distinct count" semantics); rules::dispatch's BindCmsOnCount treats
  it as a bare frequency point-query (CMS). ASAPController's own
  crates/plan/src/bind.rs::readout maps Count to SketchQuery::PointCount,
  confirming CMS is correct -- the CardinalityApprox premise never had a
  real caller anyway (distinct_over_time/COUNT(DISTINCT) always lower to
  AggIntent::Cardinality, never Count). AggIntent::Count{non-Exact} is
  unreachable via this repo's own PromQL frontend today regardless
  (lower.rs only constructs Count{Exact} or the Extension-based
  Frequency intent), so this is a consistency fix, not a live routing
  change.

- Min/Max: capability_for claimed QuantileApprox (min = quantile(0),
  max = quantile(1)), but no rule in sketch_algebra::rules actually
  implements that -- bind_kll_quantile/bind_ddsketch_quantile only ever
  match AggIntent::Quantile, never Min/Max, so the promised coverage
  didn't exist and Min/Max silently fell through to archive regardless.
  ASAPController's own crates/plan/src/boundary.rs treats Min/Max as an
  exact mergeable accumulator (SummaryKind::MinMax), same tier as
  Sum/Rate/Increase -- correct, since comparing two partial extrema
  needs no approximation at all. bind_exact_agg.rs now binds
  Min/Max -> AggregationType::MinMax (keyed -> MultipleMinMax), using
  the data plane's already-fully-wired MinMaxAccumulator; capability_for
  now returns ExactAgg(MinMax) to match.

Also fixes a stale doc-table line (Count{Exact} claimed to return
Some(ExactAgg(Sum)); the actual code has returned None since the
PR #200/#201 revert).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zzylol added a commit that referenced this pull request Jul 22, 2026
…#408)

* feat(control_plane): adopt asap_sketch::L4Node for the L4 IR (Step B)

Retires control_plane's own locally-defined, flat PhysicalExpr L4 algebra
(Logical/SketchAgg/SketchEstimate/SketchMerge/ExactAgg) in favor of
ASAPController's canonical L4 IR, asap_sketch::{SummaryExpr, L4Node} --
the same move Step 3 of the enum-unification made for
SketchKind -> SummaryKind, one layer up.

PhysicalExpr is now a thin L5 wrapper: `Committed(L4Plan)` for the
common case, plus the two Phase eps.1 placement variants. L4Plan keeps
only what asap_sketch genuinely doesn't have -- named LetBinding/Ref
fan-in sharing (asap_sketch's own DAG sharing is structural, via Rc, but
this crate's rule-firing walk still needs a name to thread a bound value
across sibling calls).

The 7 bind_kll_quantile/bind_ddsketch_quantile/bind_hll_cardinality/
bind_cms_count/bind_cms_topk/bind_exact_agg/bind_archive_only Rule
structs are retired -- their selection/sizing policy (KLL's k rungs,
DDSketch-over-KLL priority, TopK recall tiers, wire-cost tie-breaks) is
preserved verbatim in the new ControlPlaneCostModel, plugged into
asap_plan::bind::implement_tree_in_with via the CostModel trait
(rank_candidates + the new size_params hook, ASAPController#146) instead
of a bespoke dispatcher -- so schema derivation, col/by computation, and
DAG construction are asap_plan::bind's, not a forked copy.

Three node shapes get a small local pre-pass in sketch_algebra::lower
before delegating, because asap_plan::boundary::implementation_for
actively binds them to something this deployment's data plane can't (or
deliberately shouldn't) serve:
- AggIntent::Count{accuracy: Exact} would bind SummaryKind::Count, which
  has no data-plane accumulator (PR #200/#201 already established this
  is wrong -- reverted, stays on archive).
- AggIntent::Rate would bind its own SummaryKind::Rate; this deployment
  represents Rate as an Increase accumulator (rate = increase / window,
  a query-time division, not a separate accumulator).
- AggIntent::Extension (Frequency) and TopK{accuracy: Exact} both
  decline to bind at all (asap_plan's Extension/exact-TopK coverage gaps
  -- filed upstream as ASAPController#150 and #151); this is a real,
  accepted behavior change from the retired bind_cms_count/bind_cms_topk
  rules, not a bug -- see the updated tests in optimizer/rules/mod.rs
  and emit/mod.rs.

Also fixes a real bug surfaced by the migration: PhysicalExpr now
carries Rc<L4Node> (asap_sketch's own DAG-sharing mechanism), so holding
a bound PhysicalExpr across an .await point made handle_plan's generated
Future !Send, breaking axum::Handler. Scoped the Rc-bearing computation
into a synchronous block that resolves down to Send-safe StageConfig
output before the first await.

Pins ASAPQuery-backend's ASAPController dependency to
12482fd77945ab5771a021c6112750fd9284f8ed (ASAPController PR #146,
CostModel::size_params + bind::logical).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: re-pin ASAPController to main (post #146/#154 merge)

ASAPController#146 (CostModel::size_params) and #154 (bind::logical
visibility) are both merged. Re-pin from the feat/costmodel-size-params-hook
branch tip to main's current commit now that both land there.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
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