feat(types,asap-aware-mapping): add Hydra frequency-sketch grouping (#256) - #261
Conversation
d7749b1 to
1740824
Compare
|
Rebased onto
Turns out the answer is legality-neutral, not a gate: every condition All four checks still pass clean: |
8cc86fe to
066dc98
Compare
1740824 to
f6b2194
Compare
…licability.rs "Site" was an informal synonym introduced when this module's docs were written — every occurrence names the exact same thing the type system already calls TargetSubDAG (or, once discovered, a MemoGroup's own `target`). Replaced every free-standing "site"/"a site's..." with TargetSubDAG (or "the TargetSubDAG" as a phrase) throughout the module doc and the OptimizationKind/collect_locations/test doc comments, so the prose names the real type instead of a parallel, undefined term. Left two references untouched: `crate::search::discover_sites` (a real, unrenamed function name in search.rs) and the quoted section title "Where `for site in plan.bindable_sites()` comes from" (a verbatim quote of search.rs's own doc heading) — both are accurate references to search.rs's own content, which this PR doesn't touch. Also fixed a handful of stale `boundary::`-module doc links found while in here (the module was renamed to `implementation` well before this branch's fork point — same staleness class as PR #263/#262/#261/ #260, just not load-bearing for compilation since these were doc-only intra-doc links). Verified: cargo build --workspace --all-targets, cargo test --workspace (0 failures), cargo fmt --all -- --check, cargo clippy --workspace --all-targets --all-features -- -D warnings — all clean.
066dc98 to
6a7f560
Compare
f6b2194 to
068e2a7
Compare
6a7f560 to
db61054
Compare
068e2a7 to
b3f9bc1
Compare
's current tip This branch had been forked at the very first draft of #251 (boundary::implementation_for_with, a ForceSketchKind CostModel-wrapping hack to steer bind::implement_tree_with) and never rebased — completely missing #259's subsequent history: the boundary.rs -> implementation.rs rename, the "make binding literally a selector over ReplacementStrategy" refactor, and this session's deletion of bind::implement_tree/implement_tree_with. It would not compile against the current tip of feat/replacement-strategy-251. Rebuilt fresh: reset this branch onto the current #259 tip, then reapplied only the genuinely new content this PR adds on top — nothing here re-implements replacement.rs/bind.rs/cost_model.rs, which now come from #259 itself. - types crate (pure additions, brought over wholesale): `GroupingStrategy` gains a `grouping: GroupingStrategy` field on `SummaryExpr::SummaryAgg` (default `PerSubpopulationInstance`, so no existing behavior changes), plus the new `HydraKind`/`HydraParams` types and `hydra_kind_for`/`default_hydra_params` functions in `post_asap::sketch`, with module-doc cross-references updated for the current module layout. - grouping.rs (asap-aware-mapping): `HydraGroupingStrategy`, a `ReplacementStrategy` offering `GroupingStrategy::SharedMultiSubpopulation` as an additional candidate wherever the target has a real subpopulation concept (non-empty `by`) and its summary family has a modeled Hydra variant (KLL only, today). Its own legality checks, `ReplacementStrategy` impl shape, and tests needed only mechanical adaptation. - **`ForceSketchKind` was NOT resurrected.** The original draft (written against the first #251 draft) reused `replacement.rs`'s `ForceSketchKind` — a whole-recursive-bind `CostModel`-wrapping adapter that steers `implement_tree_with`'s decision procedure toward a forced `SketchKind`. That pattern was deliberately deleted from this crate as an anti-pattern (a real bug where the forced choice could leak into a target's own nested aggregates — see `replacement.rs`'s own module docs), and `bind::implement_tree`/`implement_tree_with` no longer exist. `grouping.rs` now calls `implementation::implementations_for_with(intent, cost_model)` to get every ranked candidate directly (already the full list, no forcing needed), finds the one `Implementation::Sketch { kind, .. }` matching the Hydra-eligible kind, and passes that exact, already-decided `Implementation` straight to `bind::bind_with_implementation` — the same first-class, one-candidate-at-a-time primitive `SketchFamilyStrategy` itself already uses. No adapter, no steering, no risk of a forced choice leaking into nested aggregates. - `replacement.rs`: `describe_intent` bumped to `pub(crate)` so `grouping::HydraGroupingStrategy` can reuse it for rationale strings (`bindable_intent` needed no bump — it now lives in `bind.rs`, already `pub`, since #259's "unify binding and replacement-strategy paths" refactor moved it there independently of this branch). - `bind.rs`/`cost_model.rs`: added the new `grouping` field to every `SummaryAgg` struct literal in their own test fixtures (mechanical, `GroupingStrategy::default()`), plus the same in `crates/integration-tests/tests/l4_binding.rs`/`l4_binding_sql.rs`'s destructuring patterns (`..`, since those tests don't assert on grouping). Verified: cargo build --workspace --all-targets, cargo test --workspace (0 failures, including all 14 grouping:: tests — the implementations_for_with + bind_with_implementation rewrite produces the same bound SketchKind/params the old ForceSketchKind-based code was designed to produce), cargo fmt --all -- --check, cargo clippy --workspace --all-targets --all-features -- -D warnings — all clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
b3f9bc1 to
ca0bd8d
Compare
…etch construction The Hydra paper (Manousis et al., VLDB 2022) proves its accuracy bound (Theorem 2) over one specific construction: hash each subpopulation into a shared w×r grid of linear, mergeable frequency-vector sketches (its own substrate is Count-Sketch; Count-Min Sketch is the same collision algebra). The paper is explicit that this construction excludes quantiles entirely (§4.3: "A statistic that cannot directly be estimated by Hydra-sketch is quantiles."). - Add HydraKind::HydraCms / HydraCountSketch, mapped from the existing SketchAlgorithm::Cms/CountSketch — direct instances of the paper's proven grid construction, with HydraParams carrying the paper's own (width, depth, shared_rows, shared_columns) knobs. - Keep HydraKind::HydraKll, but document it as an unproven extension beyond the paper (quoting the quantile-exclusion line above) rather than implying it shares the same guarantee. - Generalize per_subpopulation_k(node) -> Option<u32> into per_subpopulation_sketch_params(node) -> Option<SketchParams>, and change default_hydra_params to take the whole SketchParams and match per-HydraKind, instead of assuming every inner sketch has a scalar `k` the way KLL does. A Hydra "sketch of sketches" is a framework over any mergeable inner sketch, not just KLL. - HydraUnivMon (the paper's own actual named "Hydra-sketch" — L layers of Count-Sketch plus a heavy-hitter heap, estimating entropy/L1/L2-norm/ cardinality as one instance) is deliberately not added here: it needs new AggIntent/category vocabulary this crate doesn't have yet. Tracked as a follow-up on issue #256. New/updated tests in both crates cover the two new HydraKind mappings, default_hydra_params's per-kind dispatch (including a mismatched kind/params pair returning None), and a grouped Count intent now offering both HydraCms and HydraCountSketch candidates. Verified: cargo build --workspace --all-targets, cargo test (asap-types, asap-aware-mapping, asap-integration-tests — all passing, including the new tests), cargo fmt --all -- --check, cargo clippy --all-targets --all-features -- -D warnings — all clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Pushed a follow-up commit (4513a61) that scopes The paper's accuracy bound (Theorem 2) is over one specific construction: hash each subpopulation into a shared w×r grid of linear, mergeable frequency-vector sketches (its own heavy-hitter substrate is Count-Sketch; Count-Min Sketch is the same collision algebra). It's explicit that this construction excludes quantiles entirely (§4.3: "A statistic that cannot directly be estimated by Hydra-sketch is quantiles."). Changes:
New tests cover the two new Verified: |
|
Follow-up fix pushed in
Local validation: 133 |
Closes #256. Part of #33.
What this adds
This PR makes physical grouping of grouped sketch state an explicit planning axis:
GroupingStrategy::PerSubpopulationInstancekeeps the existing/default behavior: one independent sketch per group key.GroupingStrategy::SharedMultiSubpopulationrepresents one Hydra structure shared across an aggregate's subpopulations.HydraCmsandHydraCountSketchmodel the shared-grid frequency-sketch constructions backed by Hydra's collision/error analysis.HydraGroupingStrategyis registered in the default and custom-cost-model workload-search paths, so eligible grouped approximate-count aggregates receive Hydra alternatives alongside their independent sketch alternatives.The grouping choice is recorded in two places for different purposes:
SummaryExpr::SummaryAgg::grouping, next toreduction, gives planning access to theby/withoutcontext that determines legality.SummaryFamilyType::Sketch(SketchKind, GroupingStrategy)commits the physical layout to the state carried on the DAG edge, so independent and Hydra-backed states cannot be treated as merge-compatible merely because their inner sketch kind matches.Implementationremains focused on which summary implementation satisfies anAggIntent; grouping is applied after selecting and constructing that exact implementation.Candidate generation
For each bindable aggregate target, the strategy:
by(...)or awithout(...)reduction. Globalby []and per-entity reductions are rejected.hydra_kind_forexposes a semantics-preserving Hydra construction. This is currently CMS and Count-Sketch.SummaryAggwithSharedMultiSubpopulationand updates the sketch-valued output schema with the same grouping layout.When Hydra helps
Hydra is most useful when a grouped workload has a very large and dynamic subpopulation space, so allocating a full independent sketch for every active group would dominate memory.
For example:
In a multi-tenant service, hundreds of thousands of tenants may access thousands of endpoints. The active
(tenant_id, endpoint)combinations form a large, sparse, long-tailed set: a few groups are hot, while many groups receive only occasional requests.With independent grouping, every active combination needs its own CMS or Count-Sketch state:
With Hydra grouping, all combinations hash into one shared
w x rgrid of frequency sketches:This trades strict per-group isolation for bounded collision noise while avoiding per-subpopulation allocation overhead. It is attractive when group cardinality is high, the distribution is sparse or long-tailed, memory is constrained, and the query's accuracy target tolerates the modeled collision error.
Hydra is less attractive when there are only a few groups, when strict isolation is required for every group, or when the requested statistic has no modeled Hydra error guarantee. In particular, this PR does not automatically offer Hydra for quantile/KLL workloads.
How subpopulation cardinality is estimated
The core planner does not guess cardinality from column names or multiply table-wide distinct counts. Cardinality statistics are deployment-specific, so this PR adds the following
CostModelhook:A deployment overrides this method and inspects the aggregate target and its grouping keys -- for this example,
(tenant_id, endpoint)-- then returns an estimated number of distinct active combinations from its catalog, optimizer statistics, telemetry, or another workload-specific source.The example match above is illustrative pseudocode; production implementations resolve the grouping-key column references in
targetagainst their statistics catalog.Once an estimate
Nis available, the default grouping cost compares:Hydra ranks ahead when its shared-state estimate is smaller. For the regression fixture,
N = 10,000selects Hydra, whileN = 10selects independent sketches. If the hook returnsNone, both layouts remain legal and the planner preserves discovery order instead of fabricating a cardinality estimate.Before / after IR
Example logical query:
Assume the count intent has an approximate
EpsilonDeltaaccuracy target. Before this PR, search can bind it only to independent per-subpopulation sketches.Before -- one CMS instance per active
(tenant_id, endpoint):Count-Sketch is another independent candidate with the same grouping shape.
After -- search retains the independent candidates and additionally offers a shared Hydra candidate:
The equivalent
HydraCountSketchcandidate is also emitted. The grouping value appears both onSummaryAggand in the output state type deliberately: planning can inspect the former, while the latter prevents a downstream merge from confusing shared Hydra state with an independent CMS/Count-Sketch state.This PR enumerates both alternatives and makes final ranking cardinality-aware. A deployment supplies
CostModel::estimated_subpopulation_count(target): independent state is costed asestimated_subpopulations x inner_sketch_size, while Hydra state is costed asshared_rows x shared_columns x inner_sketch_size. The lower-cost layout ranks first. If no cardinality estimate is available, the planner preserves discovery order rather than inventing statistics.Strategy candidates
Names used below:
S:Scan(requests)I_cms: independent CMS summary, one instance per active(tenant_id, endpoint)I_cs: independent Count-Sketch summary, one instance per active(tenant_id, endpoint)H_cms: shared Hydra-CMS summary across all(tenant_id, endpoint)subpopulationsH_cs: shared Hydra-Count-Sketch summary across all(tenant_id, endpoint)subpopulationsE: count estimate/readoutBefore this PR:
After this PR:
HydraGroupingStrategyproposes the two shared alternatives.PlanSpace::cost_sortedthen selects their order: a supplied group-cardinality estimate compares independent allocation with the fixed shared-grid state, while an absent estimate preserves the existing candidate order.Complete selected-plan DAG
Every arrow below represents data flow from producer to result. These diagrams show one possible CMS-based selection; Count-Sketch has the same shape.
Before selecting Hydra
flowchart LR S["S: Scan(requests)"] --> I["I_cms: SummaryAgg CMS<br/>GROUP BY tenant_id, endpoint<br/>PerSubpopulationInstance"] I --> E["E: count estimate"] E --> Q["query result"] classDef result fill:#eef,stroke:#668; classDef summary fill:#efe,stroke:#484; classDef scan fill:#fee,stroke:#844; class Q result; class I,E summary; class S scan;I_cmsrepresents one independent CMS instance for each active(tenant_id, endpoint).After selecting Hydra
flowchart LR S["S: Scan(requests)"] --> H["H_cms: SummaryAgg CMS<br/>GROUP BY tenant_id, endpoint<br/>SharedMultiSubpopulation / HydraCms"] H --> E["E: count estimate"] E --> Q["query result"] classDef result fill:#eef,stroke:#668; classDef summary fill:#efe,stroke:#484; classDef scan fill:#fee,stroke:#844; class Q result; class H,E summary; class S scan;The relational DAG topology is unchanged; the selected
SummaryAgg's physical state layout changes from many independent per-key sketches to one Hydra grid shared across the aggregate's subpopulations. That layout difference is now explicit in both the node and its output state type.Accuracy boundary
HydraKllremains representable as an explicit experimental IR value, but it is not returned byhydra_kind_forand is never emitted by semantics-preserving workload search. The Hydra construction used here does not provide a quantile/KLL error guarantee, so grouped quantile workloads retain only their independent KLL and DDSketch alternatives.HydraUnivMonis not modeled yet. It needs additional intent/category vocabulary for the statistics it serves.The shared structure multiplexes subpopulations within one aggregate. This PR does not combine distinct query roots into one Hydra instance, and grouping alone does not make a sketch eligible for roll-up; sketch roll-up still requires explicit merge semantics and legality checks.
Validation
cargo test -p asap-types -p asap-aware-mapping -p asap-integration-testscargo clippy -p asap-types -p asap-aware-mapping -p asap-integration-tests --all-targets --all-features -- -D warningscargo fmt --all -- --checkCoverage includes CMS/Count-Sketch Hydra discovery, exclusion of unproven HydraKll from automatic selection, custom-cost-model behavior, agreement between
SummaryAgg::groupingand the sketch state type stored in the output schema, and a tenant/endpoint regression test proving that 10,000 estimated groups rank Hydra first while 10 estimated groups rank independent sketches first.