Repository navigation
fix(cse): share equal nodes that return the same rows for the same input, not only keyed ones - #1032
Merged
Merged
Conversation
The per-series quantile in #1011's example has no provable unique key, so CSE keeps G2 and G3 as two groups even though both readers of G2 / G3 read the same rows. The new memo test fails on main (groups 2 and 3). A guard test pins that an unordered LIMIT, whose copies may return different rows, stays unshared after the legality rule changes. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…nput Group identity no longer needs a provable unique key. A non-ASAP node is legal to share when every execution over the same input rows returns the same output rows (NonASAPOp::returns_same_rows_for_same_input_rows); the hard-coded list of shareable shapes goes. Tie order of a stable Sort counts as fixed, so exact top-k and a Limit over a Sort stay shared; an unordered Limit, a Dedup on a column subset and a position-reading window function without ORDER BY are never shared. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…emo example pre-asap-ir.md and the planner-layering memo example explained sharing by the unique key; both now state the same-rows rule. Schema::has_unique_key no longer claims to be the CSE gate. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…omments; pins assert the shared group - A Limit over a Sort partitioned differently still picks rows by input order, so it is not shared. - The sharing rule's doc states its assumptions: scalar functions the IR accepts are deterministic, and Extension intents return the same rows. - Comments in memo/groups.rs, asap_aware_cse.rs, summary_inputs.rs and summary_sharing.rs no longer describe the unique-key gate. - The top-k snapshot pins also assert that the readers' sum_over_time is one group. Co-Authored-By: Claude Opus 5.5 <[email protected]>
zzylol
force-pushed
the
fix/memo-share-deterministic-groups
branch
from
October 29, 2026 00:25
16166af to
7f292b3
Compare
… every operator - NonASAPOp::returns_same_rows_for_same_input_rows -> is_shareable_when_equal, which says what CSE asks; the same-rows definition stays in its doc. - The match lists every NonASAPOp variant, so a new operator must decide whether it may be shared. - Dedup on every column is a length check over its distinct columns. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…the exact DAG is valid Without the series identity the two quantile 0.5 are now one group, so the memo has 5 x 5 = 25 choices (9 whole-budget), not 343. Its exact DAG prices at 0.0032169/s against 0.0037502/s for the same exact DAG with the identity column, so its price is no longer compared with the identity-lowered selection (#1035); the production-lowering test keeps that check. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Contributor
Author
|
Merging with
🤖 Generated with Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #1011 (checklist 1, first item, and 5, first item). Refs #979.
Why
Whether two equal logical nodes become one memo group was decided by a hand-written list in
cse.rs. A non-ASAP node was shared only when its schema had a provable unique key, or when it was aScan, aTimeRangeover aScan, an exact PromQLTopK, or a global aggregate whose intent was on a list. A unique key is not what makes sharing safe. Sharing is safe when one copy returns each reader the rows it would have computed alone. For example, a per-series quantile without a key is safe to share. ALIMIT 10withoutORDER BYmay return any 10 rows, so this PR keeps its copies separate. That is a conservative choice: one shared copy is still a valid answer for each reader, the way a database may materialize a CTE once.Production impact is small, and I measured it. On main, the CLI and facade path (
stage_pipeline) already gives #1011's example 7 groups (G3 reads[2, 2]). The frontend gives the per-series quantile a$promql_series_identitycolumn and the unique key[[3, 0]], and the SQL frontend returns one shared node for identical sub-plans before CSE runs. #1011's 8 groups only appear with the test helpertest_support::lower_promql, whose output has no key. Running the same 8 workloads throughstage_pipelineon main and on this branch gave the same groups and the same selected cost for every one: #1011's example,sum without (instance),count_values, keyless SQL projections,UNION ALL, a keyless sub-plan shared across two SQL queries, and a> 10filter over a sharedwithoutsum. This PR therefore:LIMITunshared, conservatively (a keyed one was shared before);Where the rule lives (#1011 checklist 1). It stays in
asap-types, as a property of the IR operator. Any caller can ask it, and the memo and the DAG viewer can show why a node stayed separate.What
NonASAPOp::is_shareable_when_equal(), which defines what "deterministic" means for sharing.Schema::has_unique_keyno longer gates sharing.Limitor offset whose child is not aSortwith the same partitions;Dedupon some but not all columns;ROW_NUMBER,LAG/LEAD,FIRST/LAST/NTH_VALUE, or an aggregate with aROWSframe) without anORDER BY.How
Equal nodes are interned children first, so two equal nodes always read one input. When an operator's output depends only on its input rows, one copy serves every reader. Ties are the one place where input order decides the output. A
Sortis stable, and exact top-k ranks with it, so that tie order counts as fixed. This keeps main's sharing of exact top-k (decided in review of #1011). An operator that picks rows by input order alone is never shared.Key code interfaces added / modified
crates/types/src/ir/operator/non_asap.rscrates/types/src/ir/cse.rscrates/types/src/ir/schema/mod.rsUsage (from
memo::tests::equal_keyless_deterministic_results_share_one_group, which runs at this commit):Major algorithm changes
CSE legality (Stage 0 group identity).
q(0.5) / q(0.5)kept two groups. Each was planned and sized separately, with three budget shares instead of two. A keyed unorderedLIMITwas shared.has_unique_key(), or one of five hand-listed shapes.is_shareable_when_equal().cse::tests(unordered_limits_are_not_shared,limits_over_a_sort_with_their_partitions_are_shared,dedup_shares_only_on_every_column,without_grouping_is_shared) and by main'smemo_enumeration, which measures the production memo and passes unchanged.AggIntent::Extensionintents return the same rows.x + x), so the half-budget sketch read twice is never offered. That gap predates this PR (found in review); see the follow-up.Before this PR
The CSE gate was a unique key plus a hand-written list, and a keyed unordered
LIMITcould be shared.After this PR
One rule on the IR decides group identity. planner-layering.md states it, including what "same rows" means. Comments and docs that described the unique-key gate are updated (pre-asap-ir.md, the memo example,
memo/groups.rs,asap_aware_cse.rs,summary_inputs.rs).Verification
Rebased onto main
61ee27f30; headd9821d272.cargo fmt --checkpassed; clippy (-D warnings) passed onasap-types,asap-logical-asap-optimizationandasap-planner.asap-types,asap-logical-asap-optimizationandasap-plan-selection: 822 passed;asap-planner --test summary_sharingandasap-integration-tests --test memo_enumeration: all passed.memo_pricing.rs(from feat(devtools): stage_pipeline --price-enumerated prices each enumerated memo choice and flags any cheaper than the selected plan #1028), keyless ratio of quantiles: the memo now has 25 choices (9 whole-budget), not 343 (27). Its only valid DAG is the exact one. That DAG prices at 0.0037502/s with the series-identity column and 0.0032169/s without it, so the keyless price is no longer compared with the identity-lowered selection; that cross-lowering gap is PromQL planner test helpers skip the series-identity step production runs, so ~400 Stage 1–3 tests plan over a schema production never produces #1035. The test is renamedratio_of_quantiles_25_pass1_choices_without_identity_only_exact_is_valid, and the now-unusedassert_none_cheaperis removed.ratio_of_quantiles_no_listed_choice_is_cheaper_than_the_selected_plankeeps the "none cheaper" check on the production lowering; it passes, and so does all ofmemo_pricing(5/5).sum_over_timeis one group.16f07f896): the only failures were tests that also fail on main at the based728d05cf, or planning-time-budget tests that pass when run on their own.stage_pipeline, main vs this branch, on the 8 workloads in Why: identical groups and selected costs (Stage 0 review: frontends and the logical memo (hash-consed groups) #1011's example: 0.000470097/s on both).7f292b351; the chosen interface changes (rename, explicit match,Deduplength check) are in18218ad06. Re-ran fmt, clippy and the 822 lib tests at18218ad06: all passed.🤖 Generated with Claude Code