Skip to content

fix(sketch): SummaryExecutor::execute() gets a grouping model - #161

Merged
zzylol merged 1 commit into
mainfrom
feat/grouped-summary-executor
Jul 25, 2026
Merged

zzylol merged 1 commit into
mainfrom
feat/grouped-summary-executor

Conversation

@zzylol

@zzylol zzylol commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #159.

SummaryAgg's by: Vec<ColumnId> field was passed to find_candidates but never used structurally — fold_states merged everything find_candidates returned into one State, and execute read out one Value per tree. There was no concept anywhere in the walker of "one output series per distinct group value" — the existing test suite only exercised ungrouped, single-scalar cases.

For a deployment whose real traffic is dominated by grouped queries (quantile by (zone) (...), the normal case, not an edge case — the motivating example is ASAPQuery-backend's data_plane), calling execute() as originally specified would silently merge every group's state into one blob instead of erroring, since nothing checked for it.

  • Adds SummaryExecutor::GroupKey: Clone + Ord + Default — an opaque, deployment-chosen per-group identity (e.g. a label-value map).
  • find_candidates now tags each handle it returns with the group it belongs to; execute groups them itself before folding (fold_states/merge_states only ever combine same-group states).
  • Every ExecOutcome (State or Value) is now a per-group list rather than a single value. The ungrouped case (by empty, or a Logical leaf) is simply a list of one entry under GroupKey::default() — same shape, not a special case.
  • SummaryMerge's (SummaryKind, SummaryParams) agreement check stays global across every group from every child (a planning-time property of the node, fixed before any group value is known); the actual state folding happens within each group independently, mirroring how evaluate_exact_agg's existing per-(group, window) fold already works downstream.

No consumer depends on the pre-#159 shape yet (#155 just merged, nothing in this workspace or ASAPQuery-backend's control_plane/data_plane implements the trait yet), so this is a clean breaking change rather than a migration.

Also updates docs/l4node-execution-model.md's trait signature and adds a "Grouping" subsection explaining the design.

Leaving this unmerged for review — flagging since I (Claude, working with @zzylol) have merge access to this repo; wanted a human to look at the grouping/merge semantics (particularly the "global kind/params check, per-group state fold" split and the "fold whatever's present per group, don't require every SummaryMerge child to cover every group" choice) before this lands, given it changes the trait every future deployment implements against.

Test plan

  • cargo test -p asap-sketch — 11 tests (9 existing, adapted to the new per-group-list shape via a small only() helper for the single-group assertions, + 2 new: grouped SummaryAgg produces one series per group; grouped SummaryMerge folds within a group, not across groups — proven by asserting the merged values don't equal the cross-group sum).
  • cargo build --workspace / cargo test --workspace — clean, no other crate touches this trait yet.
  • cargo clippy --workspace --all-targets --all-features -- -D warnings — clean.
  • cargo fmt --all --check — clean.

🤖 Generated with Claude Code

Fixes #159.

`SummaryAgg`'s `by: Vec<ColumnId>` field was passed to `find_candidates`
but never used structurally -- `fold_states` merged *everything*
`find_candidates` returned into one `State`, and `execute` read out one
`Value` per tree. There was no concept anywhere in the walker of "one
output series per distinct group value" -- the existing test suite only
exercised ungrouped, single-scalar cases.

For a deployment whose real traffic is dominated by grouped queries
(`quantile by (zone) (...)`, the normal case, not an edge case -- the
motivating example is ASAPQuery-backend's `data_plane`), calling
`execute()` as originally specified would silently merge every group's
state into one blob instead of erroring, since nothing checked for it.

Adds `SummaryExecutor::GroupKey: Clone + Ord + Default` -- an opaque,
deployment-chosen per-group identity (e.g. a label-value map).
`find_candidates` now tags each handle it returns with the group it
belongs to; `execute` groups them itself before folding
(`fold_states`/`merge_states` only ever combine same-group states), and
every `ExecOutcome` (`State` or `Value`) is now a per-group list rather
than a single value. The ungrouped case (`by` empty, or a `Logical` leaf)
is simply a list of one entry under `GroupKey::default()` -- same shape,
not a special case.

`SummaryMerge`'s `(SummaryKind, SummaryParams)` agreement check stays
global across every group from every child (a planning-time property of
the node, fixed before any group value is known); the actual state
folding happens within each group independently, mirroring how
`evaluate_exact_agg`'s existing per-(group, window) fold already works
downstream.

No consumer depends on the pre-#159 shape yet (#155 just merged, nothing
in this workspace or ASAPQuery-backend's `control_plane`/`data_plane`
implements the trait yet), so this is a clean breaking change rather
than a migration.

## Test plan
- [x] `cargo test -p asap-sketch` -- 11 tests (9 existing, adapted to the
      new per-group-list shape via a small `only()` helper for the
      single-group assertions, + 2 new: grouped SummaryAgg produces one
      series per group; grouped SummaryMerge folds within a group, not
      across groups -- proven by asserting the merged values don't equal
      the cross-group sum).
- [x] `cargo build --workspace` / `cargo test --workspace` -- clean, no
      other crate touches this trait yet.
- [x] `cargo clippy --workspace --all-targets --all-features -- -D
      warnings` -- clean.
- [x] `cargo fmt --all --check` -- clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@zzylol
zzylol merged commit d4c1756 into main Jul 25, 2026
1 check passed
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.

SummaryExecutor::execute() has no grouping model -- would silently cross-merge grouped queries

1 participant