feat(data_plane): implement SummaryExecutor for cumulative quantile/cardinality (Step C, first cut) - #411
Conversation
| /// reducer merges the returned sketches across series (register-wise max) | ||
| /// before estimating, so the answer is the distinct UNION cardinality, not | ||
| /// the sum of per-series cardinalities. | ||
| pub fn cumulative_hll_state( |
There was a problem hiding this comment.
You don't need to keep it separately if cumulative rolling state func covers it.
There was a problem hiding this comment.
Removed — evaluate_cardinality_global now calls cumulative_rolling_state directly and reads out via RollingState::cardinality() instead. Confirmed via the existing 294-test sketch_db suite passing unchanged.
…Executor
Closes the "per-window/matrix output" gap flagged as follow-up in the
previous commit -- SummaryExecutor now covers both cumulative (instant)
and per-window (matrix/range) quantile/cardinality queries.
delta_apply.rs: adds per_window_rolling_states, generalizing
per_window_evaluate the same way cumulative_rolling_state generalized
cumulative_hll_state -- returns each window's reconstructed RollingState
instead of an already-evaluated scalar, so a caller can merge same-window
states across several sids before evaluating. per_window_evaluate now
delegates to it (behaviorally identical: same decode_full/apply_delta_bytes
walk, verified by the existing 294-test sketch_db suite passing unchanged).
summary_executor.rs: readout now dispatches on is_cumulative to
readout_cumulative (existing) or the new readout_per_window, which
reconstructs each of a group's sids' own per-window states, then unions
by window_end and merges same-window states across sids (mirroring
SummaryMerge's "fold whatever's present" semantics from ASAPController#161
-- a sid missing a particular window just doesn't contribute to it, the
window isn't dropped). Drops carry-in base windows ending before t0,
matching sketch_reducer.rs's evaluate_core's identical existing filter.
## Test plan
- [x] cargo test -p data_plane --lib summary_executor -- 9 tests (6
existing + 3 new), all passing:
- per_window_matrix_produces_multiple_points_for_one_sid -- basic
multi-window correctness.
- per_window_matrix_merges_across_sids_per_window -- the cross-sid
analog of the cumulative merge test: two sids sharing one window_end
produce one merged point reflecting both, not either alone.
- per_window_matrix_drops_carry_in_base_before_t0.
- [x] cargo test -p data_plane -- full suite: 902 passed in the main lib
target (was 896 before this commit's 6 new tests... note: 3 net
new here, 3 from the prior commit), same pre-existing unrelated
e2e failures as already documented on PR #411.
- [x] cargo build --workspace -- clean.
- [x] cargo clippy -p data_plane --lib -- -D warnings -- zero warnings
in the touched files.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Added the range-query (matrix) follow-up in a second commit —
3 new tests (multi-window correctness, cross-sid per-window merge, carry-in-base filtering) — all passing, plus the existing 6 unaffected. Same pre-existing unrelated e2e failures as before, still unrelated (verified). Remaining scoped-out items, unchanged from before: Frequency family (TopK/CMS) and 🤖 Generated with Claude Code |
| @@ -0,0 +1,1041 @@ | |||
| //! `data_plane`'s `asap_sketch::exec::SummaryExecutor` implementation — | |||
| //! Step C of the plan-shaped-serving migration | |||
There was a problem hiding this comment.
Could you make the code comments not showing step X, phase Y etc, and not talking about the detailed wrong case before, but just why currently we have this design and how it is.
|
Can you check any inefficiency in SummaryExecutor implementation, e.g., memory copy, not needed deserialization/serialization, any potential caching, any redundant indexing/inefficient indexing? Any extra memory usage, computation overhead? |
Three fixes, addressing review comments on PR #411: 1. Removes the now-redundant cumulative_hll_state wrapper -- its one caller (evaluate_cardinality_global) now calls cumulative_rolling_state directly and reads out via RollingState::cardinality() instead of HllSketch::estimate(), since cumulative_rolling_state already covers the HLL case. Verified via the existing 294-test sketch_db suite passing unchanged. 2. Rewrites summary_executor.rs's comments to describe the current design and its rationale, not the sequence of steps/issues that produced it. 3. Fixes a real inefficiency: find_candidates was calling query_range (which clones every in-range sample's bytes to build its owned return value) to read a candidate sid's label values for the group key, and then fetch_state/readout called query_range AGAIN on the same (sid, t0, t1) to get the actual sample data for decoding -- double the clone/decode work per candidate, and a narrow window where the two calls could observe different data under concurrent writes. SidHandle now carries the already-fetched series (Rc, so further clones are just a refcount bump) from find_candidates through to fetch_state/merge_states/readout, so query_range is called exactly once per candidate. Also folds delta_kind onto the handle at the same time (find_candidates already looks up the sid's SketchConfig for the exact-match check), removing a second with_instance lookup fetch_state used to make on its own. ## Test plan - [x] cargo test -p data_plane --lib summary_executor -- 9 tests, all passing (caught and fixed two real bugs introduced while doing this refactor before it ever reached this point: a per-window carry-in-base filter that lost access to t0_ms when readout moved to a free function, and a latest-window-end fallback that would have always reported t1_ms instead of the real latest window). - [x] cargo test -p data_plane sketch_db:: -- 294 passed, confirms the evaluate_cardinality_global change is behaviorally identical. - [x] cargo test -p data_plane -- full suite: 902 passed (main lib target), same pre-existing unrelated failures as already documented on PR #411. - [x] cargo clippy -p data_plane --lib -- -D warnings -- zero warnings in the touched files. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Efficiency review, and one real fix landed (pushed): Fixed — double fetch + double decode. Considered, not changed:
Verified no regression: 🤖 Generated with Claude Code |
The design table and open questions described the target shape before implementation started; several turned out to differ once real code got written. Updates: - Status note: SummaryExecutor is implemented and tested for quantile/cardinality (DDSketch/Kll/Hll), both cumulative and per-window, in #411 (unmerged). Frequency family (CMS/CountSketch) in progress; ExactAgg readout and wiring execute() into the live serving path still not started. - Table: Handle is SidHandle (sid + already-fetched series + decode params), not a bare u64 -- find_candidates needs the series anyway for the group key, so it's threaded through rather than re-fetched. merge_states/fetch_state are deliberately lazy; the real merge math lives in readout, which is the only place that knows cumulative-vs-per-window mode. - Open question 1 (tree source): resolved as both -- the control_plane seam for planning-time tree construction, plus a direct asap-sketch/asap-ir dependency for everything serving-time (implementing the trait / matching its types requires importing the defining crate directly, not just consuming a function that returns those types). - Open question 2: partially resolved -- KLL/DDSketch/HLL merge done (via a generalized existing HLL-only primitive, not built from scratch), CMS/CountSketch still open. Partial-group-coverage question resolved (fold whatever's present). Accuracy-math and resize/downsample questions remain genuinely open. - Questions 3-4 unchanged -- neither addressed yet. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… follow-up)
Started as a separate frequency_apply.rs (CMS/CountSketch/heap-bearing
decode+merge), then unified into delta_apply.rs after reconsidering: the
Frequency family's "decode each frame independently, then merge" is the
same fold operation delta_apply.rs's DD/KLL/HLL families already use for
every delta case except two genuine sparse-in-place special cases (DD's
bucket-index proto delta, HLL's register proto delta) -- both already
decode via decode_full-and-merge for their other encodings. So it's one
merge_same_family/cumulative_rolling_state/per_window_rolling_states
pipeline for all six sketch kinds, not two parallel ones.
- RollingState renamed to SummaryState (no longer just the "rolling"
DD/KLL/HLL families).
- DeltaSketchKind gains Cms/CountSketch/Heap variants (Heap covers both
CmsWithHeap and CountSketchWithHeap -- they already share one wire
representation and read out identically in the existing reducer code).
- decode_full/apply_delta_bytes/merge_same_family extended; new
total()/topk_items() readout accessors.
- summary_executor.rs: dropped the CandidateKind wrapper enum this no
longer needs -- SidHandle/GroupState carry one DeltaSketchKind
directly. summary_params_match/to_delta_kind extended for
Cms/CmsWithHeap/CountSketch/CountSketchWithHeap (width=cols/depth=rows,
matching the existing wire convention; SketchConfig has no heap_size
field at all, since heap-bearing kinds reuse their heap-less base's
config shape for sid identity -- confirmed via
drivers/ingest/otel.rs's base_sketch_kind_handle).
Also refactored evaluate_cardinality_global (sketch_reducer.rs) to use
SummaryState directly, dropping the redundant cumulative_hll_state
wrapper this obsoletes.
Scope actually covered: quantile/cardinality (unchanged) plus the
Frequency family's BARE total (SketchQuery::PointCount{key:
ColumnRef::SampleValue}) -- no specific item key, both cumulative and
per-window. Explicitly NOT covered, and erroring rather than silently
wrong:
- SketchQuery::TopK -- its answer (K items per timestamp) doesn't fit
this executor's Value = Vec<(i64, f64)> shape at all; forcing it in
would silently drop data. Needs a Value type redesign, a separate
decision.
- PointCount with a named item key -- the value to filter by isn't
carried by SketchQuery or available in readout's signature at all.
## Test plan
- [x] cargo test -p data_plane --lib summary_executor -- 12 tests (9
existing + 3 new), all passing:
- single_cms_sid_total_readout / two_cms_sids_same_group_totals_actually_merge
-- same two properties proven for DD/KLL earlier (real readout, real
cross-sid merge via matrix addition), now for CMS.
- topk_query_is_explicitly_unsupported_not_silently_wrong -- proves
the scope boundary errors instead of returning a wrong/truncated
answer.
- [x] cargo test -p data_plane sketch_db:: -- 294 passed, confirms
evaluate_cardinality_global's refactor is behaviorally identical.
- [x] cargo test -p data_plane -- full suite: 905 passed (main lib
target), same pre-existing unrelated failures already documented
on PR #411.
- [x] cargo build --workspace / cargo clippy -p data_plane --lib -- -D
warnings -- clean.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Frequency family (CMS/CountSketch) follow-up landed, in two parts: Part 1 (superseded by part 2, kept as commit history): started as a separate `frequency_apply.rs`, justified by "no rolling reconstruction needed for this family." That framing was wrong — re-examined the actual delta-decode implementations and found the Frequency family's "decode independently, then merge" is the same fold operation `delta_apply.rs` already uses for every DD/KLL/HLL delta case except two genuine sparse-in-place special cases (DD's bucket-index proto delta, HLL's register proto delta) — both of which also decode-and-merge for their other encodings. Part 2: unified. `RollingState` → `SummaryState`, extended with `Cms`/`CountSketch`/`Heap` variants (Heap covers both `CmsWithHeap` and `CountSketchWithHeap` — they already share one wire representation and read out identically in the existing reducer). One `merge_same_family`/`cumulative_rolling_state`/`per_window_rolling_states` pipeline for all six sketch kinds now, not two parallel ones. `frequency_apply.rs` deleted. `summary_executor.rs`'s `CandidateKind` wrapper enum also dropped — no longer needed once there's one unified kind. Real finding worth flagging explicitly: `SketchQuery::TopK`'s answer (K items per timestamp) doesn't fit this executor's `Value = Vec<(i64, f64)>` shape at all — forcing it in (e.g. returning only the top item) would silently drop data rather than error. Scoped out, with a test (`topk_query_is_explicitly_unsupported_not_silently_wrong`) proving it errors instead of returning something wrong. Needs a `Value` type redesign — a separate decision, not bundled into this PR. Also refactored `evaluate_cardinality_global` to use `SummaryState` directly and dropped the now-redundant `cumulative_hll_state` wrapper this obsoletes (following up on the earlier review comment about it). Actual scope covered now: quantile/cardinality (unchanged) + the Frequency family's bare total (no specific item key), both cumulative and per-window. Test plan
🤖 Generated with Claude Code |
…ulative/per_window_summary_state Addresses two new review comments on PR #411. 1. SummaryState::Heap collapsed CmsWithHeap and CountSketchWithHeap into one shared variant, reasoning that they share a wire representation (asap_sketchlib has no separate CountSketchWithHeap type) and today's reducer reads both out identically. That reasoning missed a real bug: a CMS-substrate heap and a CountSketch-substrate heap are different sketch algorithms that merely happen to share a storage shape -- merge_same_family's single Heap-Heap arm would have silently allowed merging one into the other (mathematically invalid, but type-checks fine since both wrap CountMinSketchWithHeap). Split into two distinct variants (CmsWithHeap/CountSketchWithHeap), both still backed by CountMinSketchWithHeap since that's the only type available, but now merge_same_family's per-variant match rejects the cross-family case the same way it already rejects e.g. merging a Cms into a Kll. 2. Renamed cumulative_rolling_state -> cumulative_summary_state and per_window_rolling_states -> per_window_summary_states, matching the RollingState -> SummaryState rename from the prior commit (the function names were left stale). ## Test plan - [x] New test: cms_with_heap_and_count_sketch_with_heap_are_not_the_same_family -- proves the fix: merge_same_family now rejects the cross-family case with a family-mismatch error instead of silently succeeding. - [x] cargo test -p data_plane --lib summary_executor -- 12/12 passing, unaffected. - [x] cargo test -p data_plane --lib delta_apply -- 10/10 passing (9 existing + 1 new). - [x] cargo test -p data_plane -- full suite: 906 passed (main lib target), same pre-existing unrelated failures already documented on PR #411. - [x] cargo clippy -p data_plane --lib -- -D warnings -- clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Addressed both new comments (couldn't reply inline — the pending-review comments can't take a threaded reply while that review is still open, so posting here instead): "there should be a CountSketchWithHeap here" ( "cumulative_rolling_state should be renamed to cumulative_summary_state to match SummaryState" — done, and Pushed. 906/906 passing (main lib target), 12/12 🤖 Generated with Claude Code |
| t1_ms: u64, | ||
| ) -> Result<ASAPTierResult, ASAPTierError> { | ||
| use super::delta_apply::cumulative_hll_state; | ||
| use super::delta_apply::{cumulative_rolling_state, DeltaSketchKind, SummaryState}; |
There was a problem hiding this comment.
cumulative_rolling_state should be renamed to cumulative_summary_state to match SummaryState
|
Follow-up on the "there should be a CountSketchWithHeap here" comment: rather than keep working around the gap in |
…families Adds RollingState::merge_same_family and a generic cumulative_rolling_state, following the exact decode/merge/apply_delta pattern cumulative_hll_state already used for the HLL-only global cardinality rollup -- generalized to DD/KLL too. cumulative_hll_state itself now delegates to the generic function (behaviorally identical: same decode_full/merge/apply_delta_bytes calls, just reachable for any RollingState family instead of hardcoded to Hll). This is the cross-sid merge building block SummaryExecutor::merge_states needs (Step C, #409): reconstruct each candidate sid's own RollingState over the query range via cumulative_rolling_state, then fold them together via merge_same_family before reading out one cross-sid answer -- the same real bug evaluate_cardinality_global already fixed for the HLL-global special case (issue: every other grouped sketch case still emits duplicate un-merged series today), generalized so it isn't HLL-only anymore. Also picks up asap_sketchlib's updated Cargo.lock entry (serde_bytes dependency, from the msgpack wire format work already on asap_sketchlib main) via the local path-patch sibling checkout. Verified: cargo test -p data_plane (sketch_db module) -- 294 passed, 0 failed, including the existing HLL global-cardinality tests this refactor touches indirectly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ardinality
Step C of the plan-shaped-serving migration
(data_plane/docs/l4node-plan-executor-design.md), scoped to cumulative
(instant) quantile/cardinality queries over the DDSketch/Kll/Hll
families -- the ones `RollingState` (delta_apply.rs, generalized in the
prior commit) covers. See the module doc for exactly what's in and out
of scope for this first cut (per-window/matrix output and the Frequency
family are explicitly deferred, not silently mishandled).
`QueryExecutionContext` implements `asap_sketch::exec::SummaryExecutor`
-- constructed fresh per incoming query (never shared, never mutated),
carrying `t0_ms`/`t1_ms`/`is_cumulative` as plain fields. The trait
itself has no time-range parameter and `ASAPQueryEngine` is called
concurrently, so this is the safe alternative to threading the range
through shared mutable state on the engine.
- `find_candidates`: walks the `SummaryAgg`'s child subtree down to a
`Scan { source: Source::TimeSeries { metric }, .. }` to recover the
metric (mirrors `control_plane::asap_tier_implement::collect_aggregate_roots`'s
recursion style over the same `QueryExpr` type), resolves `by`
`ColumnId`s to names against the child's `L4Schema`, and filters
candidate sids by EXACT `(SummaryKind, SummaryParams)` match (not the
family-level `Capability::is_satisfied_by` the legacy analyzer path
uses) -- required so `SummaryMerge`'s precondition holds by
construction.
- `fetch_state`/`merge_states` are deliberately lazy: they just
accumulate a group's sid list. The real cross-sid merge (the actual
fix for the "duplicate un-merged series" gap #409 flagged) happens in
`readout`, via `cumulative_rolling_state` + `RollingState::merge_same_family`
-- reusing the exact primitives the HLL-only global-cardinality rollup
already proved correct, generalized to DD/KLL too.
- `logical`: errors (matches today's CapabilityMiss-and-fail-over-to-archive
contract).
Adds `asap-sketch`/`asap-ir` as direct `data_plane` dependencies
(pin-matched to `control_plane`'s, same rule as the existing
`crates/asap_types/Cargo.toml` pin comment) -- required even though
`control_plane::asap_tier_implement` already returns `Vec<Rc<L4Node>>`,
because implementing a trait on / matching variants of a type requires
importing its defining crate directly.
## Test plan
- [x] `cargo test -p data_plane --lib summary_executor` -- 6 new tests,
all passing:
- `single_kll_sid_quantile_readout` -- basic correctness.
- `two_sids_same_group_actually_merge_not_just_first` -- proves real
merge: median of two sids' disjoint value ranges lands between
both, not at either one alone (would fail if merge silently
dropped a sid).
- `two_sids_different_groups_produce_two_series_not_one_merged_blob`
-- the ASAPController#159 fix, exercised end-to-end through
`asap_sketch::exec::execute()`: two zones produce two independent
series with correct per-zone values, not one merged blob.
- `hll_cardinality_readout`, `no_matching_sid_is_no_candidates`,
`mismatched_params_does_not_match` (exact-param-match, not
family-only).
- [x] `cargo test -p data_plane` -- full suite, only pre-existing,
unrelated failures (verified identical on the parent commit
before this file existed): the same
`optimizer::rules::tests::invalid_sketch_type_override_falls_back_to_default`
failure from the rev-bump commits, plus 4 pre-existing
`e2e_controller_plans_and_backend_serves` CMS/CountSketch
failures (verified identical with `git stash` against the parent
commit).
- [x] `cargo build --workspace` -- clean.
- [x] `cargo clippy -p data_plane --lib -- -D warnings` -- zero
warnings in this file (pre-existing warnings elsewhere in the
crate untouched).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…Executor
Closes the "per-window/matrix output" gap flagged as follow-up in the
previous commit -- SummaryExecutor now covers both cumulative (instant)
and per-window (matrix/range) quantile/cardinality queries.
delta_apply.rs: adds per_window_rolling_states, generalizing
per_window_evaluate the same way cumulative_rolling_state generalized
cumulative_hll_state -- returns each window's reconstructed RollingState
instead of an already-evaluated scalar, so a caller can merge same-window
states across several sids before evaluating. per_window_evaluate now
delegates to it (behaviorally identical: same decode_full/apply_delta_bytes
walk, verified by the existing 294-test sketch_db suite passing unchanged).
summary_executor.rs: readout now dispatches on is_cumulative to
readout_cumulative (existing) or the new readout_per_window, which
reconstructs each of a group's sids' own per-window states, then unions
by window_end and merges same-window states across sids (mirroring
SummaryMerge's "fold whatever's present" semantics from ASAPController#161
-- a sid missing a particular window just doesn't contribute to it, the
window isn't dropped). Drops carry-in base windows ending before t0,
matching sketch_reducer.rs's evaluate_core's identical existing filter.
## Test plan
- [x] cargo test -p data_plane --lib summary_executor -- 9 tests (6
existing + 3 new), all passing:
- per_window_matrix_produces_multiple_points_for_one_sid -- basic
multi-window correctness.
- per_window_matrix_merges_across_sids_per_window -- the cross-sid
analog of the cumulative merge test: two sids sharing one window_end
produce one merged point reflecting both, not either alone.
- per_window_matrix_drops_carry_in_base_before_t0.
- [x] cargo test -p data_plane -- full suite: 902 passed in the main lib
target (was 896 before this commit's 6 new tests... note: 3 net
new here, 3 from the prior commit), same pre-existing unrelated
e2e failures as already documented on PR #411.
- [x] cargo build --workspace -- clean.
- [x] cargo clippy -p data_plane --lib -- -D warnings -- zero warnings
in the touched files.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Three fixes, addressing review comments on PR #411: 1. Removes the now-redundant cumulative_hll_state wrapper -- its one caller (evaluate_cardinality_global) now calls cumulative_rolling_state directly and reads out via RollingState::cardinality() instead of HllSketch::estimate(), since cumulative_rolling_state already covers the HLL case. Verified via the existing 294-test sketch_db suite passing unchanged. 2. Rewrites summary_executor.rs's comments to describe the current design and its rationale, not the sequence of steps/issues that produced it. 3. Fixes a real inefficiency: find_candidates was calling query_range (which clones every in-range sample's bytes to build its owned return value) to read a candidate sid's label values for the group key, and then fetch_state/readout called query_range AGAIN on the same (sid, t0, t1) to get the actual sample data for decoding -- double the clone/decode work per candidate, and a narrow window where the two calls could observe different data under concurrent writes. SidHandle now carries the already-fetched series (Rc, so further clones are just a refcount bump) from find_candidates through to fetch_state/merge_states/readout, so query_range is called exactly once per candidate. Also folds delta_kind onto the handle at the same time (find_candidates already looks up the sid's SketchConfig for the exact-match check), removing a second with_instance lookup fetch_state used to make on its own. ## Test plan - [x] cargo test -p data_plane --lib summary_executor -- 9 tests, all passing (caught and fixed two real bugs introduced while doing this refactor before it ever reached this point: a per-window carry-in-base filter that lost access to t0_ms when readout moved to a free function, and a latest-window-end fallback that would have always reported t1_ms instead of the real latest window). - [x] cargo test -p data_plane sketch_db:: -- 294 passed, confirms the evaluate_cardinality_global change is behaviorally identical. - [x] cargo test -p data_plane -- full suite: 902 passed (main lib target), same pre-existing unrelated failures as already documented on PR #411. - [x] cargo clippy -p data_plane --lib -- -D warnings -- zero warnings in the touched files. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… follow-up)
Started as a separate frequency_apply.rs (CMS/CountSketch/heap-bearing
decode+merge), then unified into delta_apply.rs after reconsidering: the
Frequency family's "decode each frame independently, then merge" is the
same fold operation delta_apply.rs's DD/KLL/HLL families already use for
every delta case except two genuine sparse-in-place special cases (DD's
bucket-index proto delta, HLL's register proto delta) -- both already
decode via decode_full-and-merge for their other encodings. So it's one
merge_same_family/cumulative_rolling_state/per_window_rolling_states
pipeline for all six sketch kinds, not two parallel ones.
- RollingState renamed to SummaryState (no longer just the "rolling"
DD/KLL/HLL families).
- DeltaSketchKind gains Cms/CountSketch/Heap variants (Heap covers both
CmsWithHeap and CountSketchWithHeap -- they already share one wire
representation and read out identically in the existing reducer code).
- decode_full/apply_delta_bytes/merge_same_family extended; new
total()/topk_items() readout accessors.
- summary_executor.rs: dropped the CandidateKind wrapper enum this no
longer needs -- SidHandle/GroupState carry one DeltaSketchKind
directly. summary_params_match/to_delta_kind extended for
Cms/CmsWithHeap/CountSketch/CountSketchWithHeap (width=cols/depth=rows,
matching the existing wire convention; SketchConfig has no heap_size
field at all, since heap-bearing kinds reuse their heap-less base's
config shape for sid identity -- confirmed via
drivers/ingest/otel.rs's base_sketch_kind_handle).
Also refactored evaluate_cardinality_global (sketch_reducer.rs) to use
SummaryState directly, dropping the redundant cumulative_hll_state
wrapper this obsoletes.
Scope actually covered: quantile/cardinality (unchanged) plus the
Frequency family's BARE total (SketchQuery::PointCount{key:
ColumnRef::SampleValue}) -- no specific item key, both cumulative and
per-window. Explicitly NOT covered, and erroring rather than silently
wrong:
- SketchQuery::TopK -- its answer (K items per timestamp) doesn't fit
this executor's Value = Vec<(i64, f64)> shape at all; forcing it in
would silently drop data. Needs a Value type redesign, a separate
decision.
- PointCount with a named item key -- the value to filter by isn't
carried by SketchQuery or available in readout's signature at all.
## Test plan
- [x] cargo test -p data_plane --lib summary_executor -- 12 tests (9
existing + 3 new), all passing:
- single_cms_sid_total_readout / two_cms_sids_same_group_totals_actually_merge
-- same two properties proven for DD/KLL earlier (real readout, real
cross-sid merge via matrix addition), now for CMS.
- topk_query_is_explicitly_unsupported_not_silently_wrong -- proves
the scope boundary errors instead of returning a wrong/truncated
answer.
- [x] cargo test -p data_plane sketch_db:: -- 294 passed, confirms
evaluate_cardinality_global's refactor is behaviorally identical.
- [x] cargo test -p data_plane -- full suite: 905 passed (main lib
target), same pre-existing unrelated failures already documented
on PR #411.
- [x] cargo build --workspace / cargo clippy -p data_plane --lib -- -D
warnings -- clean.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ulative/per_window_summary_state Addresses two new review comments on PR #411. 1. SummaryState::Heap collapsed CmsWithHeap and CountSketchWithHeap into one shared variant, reasoning that they share a wire representation (asap_sketchlib has no separate CountSketchWithHeap type) and today's reducer reads both out identically. That reasoning missed a real bug: a CMS-substrate heap and a CountSketch-substrate heap are different sketch algorithms that merely happen to share a storage shape -- merge_same_family's single Heap-Heap arm would have silently allowed merging one into the other (mathematically invalid, but type-checks fine since both wrap CountMinSketchWithHeap). Split into two distinct variants (CmsWithHeap/CountSketchWithHeap), both still backed by CountMinSketchWithHeap since that's the only type available, but now merge_same_family's per-variant match rejects the cross-family case the same way it already rejects e.g. merging a Cms into a Kll. 2. Renamed cumulative_rolling_state -> cumulative_summary_state and per_window_rolling_states -> per_window_summary_states, matching the RollingState -> SummaryState rename from the prior commit (the function names were left stale). ## Test plan - [x] New test: cms_with_heap_and_count_sketch_with_heap_are_not_the_same_family -- proves the fix: merge_same_family now rejects the cross-family case with a family-mismatch error instead of silently succeeding. - [x] cargo test -p data_plane --lib summary_executor -- 12/12 passing, unaffected. - [x] cargo test -p data_plane --lib delta_apply -- 10/10 passing (9 existing + 1 new). - [x] cargo test -p data_plane -- full suite: 906 passed (main lib target), same pre-existing unrelated failures already documented on PR #411. - [x] cargo clippy -p data_plane --lib -- -D warnings -- clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ummaryState SummaryState::CountSketchWithHeap wrapped the CMS-family CountMinSketchWithHeap type as a stand-in, since asap_sketchlib had no separate CountSketchWithHeap wire type. Now that asap_sketchlib exposes one (ProjectASAP/asap_sketchlib#78, median-of-signed-rows estimator vs CMS's min-over-rows), point the variant at the real type: new decode_cs_with_heap_from_msgpack{,_delta} decoders, and merge_same_family/decode_full/apply_delta_bytes/total/topk_items updated accordingly. The two SummaryState variants now hold genuinely different Rust types, so a cross-family merge is caught by the type system, not just by the enum-variant check. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
decode_frequency_estimate's heap-bearing arm always decoded through CountMinSketchWithHeap and rebuilt a CountMinSketch (min-over-rows) regardless of the sid's actual kind -- silently wrong for CountSketchWithHeap sids (median-of-signed-rows). Same conflation bug as SummaryState::CountSketchWithHeap in delta_apply.rs (61f3425), in this separate legacy reducer path. Split the collapsed match arms in decode_frequency_total, decode_frequency_estimate, and the FrequencyTopk per-frame heap decode loop to dispatch on the sid's real kind and decode through the matching asap_sketchlib type. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
6386113 to
0614b72
Compare
The design table and open questions described the target shape before implementation started; several turned out to differ once real code got written. Updates: - Status note: SummaryExecutor is implemented and tested for quantile/cardinality (DDSketch/Kll/Hll), both cumulative and per-window, in #411 (unmerged). Frequency family (CMS/CountSketch) in progress; ExactAgg readout and wiring execute() into the live serving path still not started. - Table: Handle is SidHandle (sid + already-fetched series + decode params), not a bare u64 -- find_candidates needs the series anyway for the group key, so it's threaded through rather than re-fetched. merge_states/fetch_state are deliberately lazy; the real merge math lives in readout, which is the only place that knows cumulative-vs-per-window mode. - Open question 1 (tree source): resolved as both -- the control_plane seam for planning-time tree construction, plus a direct asap-sketch/asap-ir dependency for everything serving-time (implementing the trait / matching its types requires importing the defining crate directly, not just consuming a function that returns those types). - Open question 2: partially resolved -- KLL/DDSketch/HLL merge done (via a generalized existing HLL-only primitive, not built from scratch), CMS/CountSketch still open. Partial-group-coverage question resolved (fold whatever's present). Accuracy-math and resize/downsample questions remain genuinely open. - Questions 3-4 unchanged -- neither addressed yet. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Summary
Step C of the plan-shaped-serving migration (
data_plane/docs/l4node-plan-executor-design.md, #409). Stacked on #410 (rev pin bump) sinceasap_sketch::exec::SummaryExecutordoesn't exist before that.Scoped to quantile/cardinality queries (DDSketch/Kll/Hll) and the Frequency family's bare total (CMS/CountSketch/CMS-with-heap/CountSketch-with-heap —
count/sumwith no specific item key), both cumulative (instant) and per-window (matrix/range) — see the module doc insummary_executor.rsfor exactly what's in/out of scope. Explicitly deferred, erroring rather than silently mishandled:SketchQuery::TopK(its answer shape — K items per timestamp — doesn't fit this executor'sValue = Vec<(i64, f64)>at all; needs aValuetype redesign, a separate decision) andPointCountwith a named item key (the filter value isn't carried bySketchQueryor available inreadout's signature).Seven commits:
refactor(delta_apply): generalize cumulative_hll_state to all sketch families— the DD/KLL/HLL cross-sid merge primitive.feat(data_plane): implement SummaryExecutor for cumulative quantile/cardinality—QueryExecutionContextimplements the trait; exact(SummaryKind, SummaryParams)matching; lazyfetch_state/merge_states, real merge inreadout.feat(data_plane): per-window (matrix/range-query) readout for SummaryExecutor—per_window_rolling_states, cross-sid per-window merge.fix(data_plane): address PR review feedback on SummaryExecutor— removed the now-redundantcumulative_hll_statewrapper, rewrote comments to describe current design instead of history, fixed a real inefficiency (doublequery_rangefetch per candidate —SidHandlenow carries the already-fetched series viaRc).feat(data_plane): unify Frequency family into SummaryState— CMS/CountSketch/CMS-with-heap/CountSketch-with-heap folded into the sameSummaryState/merge_same_familypipeline DD/KLL/HLL already use (not a separate parallel implementation — see the PR comment on this commit for why the original separate-file approach was wrong).fix(data_plane): wire real asap_sketchlib::CountSketchWithHeap into SummaryState—CmsWithHeapandCountSketchWithHeapwere still sharing one underlying wire type (CountMinSketchWithHeap, CMS's min-over-rows estimator) becauseasap_sketchlibhad no separateCountSketchWithHeap(median-of-signed-rows estimator) type. Rather than work around that gap indata_plane, added the real type upstream (ProjectASAP/asap_sketchlib#78, now merged) and wired it in here: newdecode_cs_with_heap_from_msgpack{,_delta}decoders,SummaryState::CountSketchWithHeapnow holds the genuine type. The two variants holding distinct Rust types means a cross-family merge is now a type-level impossibility, not just a runtime check.fix(sketch_reducer): route CountSketchWithHeap through its own estimator— the same conflation bug, found separately:sketch_reducer.rs's legacydecode_frequency_estimate(a different, still-live code path fromSummaryState/delta_apply.rs) always decoded heap-bearing sids throughCountMinSketchWithHeapand rebuilt a plainCountMinSketchregardless of the sid's real kind — silently wrong forCountSketchWithHeap's per-keytopk/FrequencyEstimatequeries. Split the collapsed match arms indecode_frequency_total,decode_frequency_estimate, and theFrequencyTopkper-frame heap-decode loop to dispatch on the sid's actual kind.Also adds
asap-sketch/asap-iras directdata_planedependencies (pin-matched tocontrol_plane's — required even thoughcontrol_plane::asap_tier_implementalready returnsVec<Rc<L4Node>>, since implementing a trait on / matching a type's variants requires importing its defining crate directly).Leaving this unmerged for review, per the same reasoning as #410/#161: this changes the live query-serving path's dependency surface and introduces a new, not-yet-wired-in code path — worth a human look before anything merges, especially the scope boundary (what's covered vs. deferred) and the
find_candidates/lazy-state design.Test plan
cargo test -p data_plane --lib summary_executor— 12 tests, all passing. The ones that matter most:two_sids_same_group_actually_merge_not_just_first/two_cms_sids_same_group_totals_actually_merge— proves real cross-sid merge for both the Sketch and Frequency families.two_sids_different_groups_produce_two_series_not_one_merged_blob— the feat(engine): read precomputes from SketchIndex when attached #159 fix, exercised end-to-end.per_window_matrix_produces_multiple_points_for_one_sid/per_window_matrix_merges_across_sids_per_window— matrix output correctness.topk_query_is_explicitly_unsupported_not_silently_wrong— the scope boundary errors instead of returning wrong data.cargo test -p data_plane --lib delta_apply— 11 tests, all passing, includingcms_with_heap_and_count_sketch_with_heap_are_not_the_same_family(now backed by genuinely distinct types) andcount_sketch_with_heap_full_and_delta_decode_via_new_asap_sketchlib_type(new — proves both the FULL and DELTA-HEAP decode paths reconstruct the same matrix/estimate()as the in-memory sketch they were encoded from).cargo test -p data_plane --lib frequency_heap_tests— newdecode_frequency_estimate_uses_each_kinds_own_estimator, built via realupdate()calls on both kinds and assertingdecode_frequency_estimatereproduces each kind's own in-memoryestimate()truth.cargo test -p data_plane sketch_db::— 294 passed, confirms theevaluate_cardinality_globalrefactor is behaviorally identical.cargo test -p data_plane --lib— full lib suite: 908 passed, 2 ignored. Only pre-existing, unrelated failures elsewhere in the workspace, verified identical on the parent commit before this branch's changes existed.cargo build --workspace— clean, against the real (now-merged)asap_sketchlibmain — no local[patch]workaround needed.cargo clippy -p data_plane --lib -- -D warnings— zero warnings in the touched files.🤖 Generated with Claude Code