fix: preserve fetch-carrying operators in remove_dist_changing_operators - #30
Open
v0y4g3r wants to merge 2 commits into
Open
Conversation
EnforceDistribution::remove_dist_changing_operators strips top-level RepartitionExec / CoalescePartitionsExec / SortPreservingMergeExec, assuming they can be regenerated later if necessary. A fetch on these operators is not a distribution concern but carries global limit semantics (e.g. produced by LimitPushdown folding a GlobalLimitExec into CoalescePartitionsExec: fetch=N), so stripping the operator silently drops the limit. This is latent in a single optimizer pass, but breaks when the physical optimizer pipeline runs twice on the same plan (e.g. GreptimeDB plans a nested subplan separately, then optimizes the enclosing plan again): the second pass removes CoalescePartitionsExec: fetch=N and never re-adds it, so a query like SELECT DISTINCT a FROM t LIMIT 1 can return one row per partition instead of one row in total. Stop the removal loop at any operator carrying a fetch. Regression tests: - EnforceDistribution preserves fetch-carrying CoalescePartitionsExec and SortPreservingMergeExec. - Running the full physical optimizer pipeline a second time over the optimized plan of SELECT DISTINCT a FROM t LIMIT 1 keeps the global limit boundary. Related: apache#23800 (orthogonal LimitPushdown fix) Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes a correctness bug in the physical optimizer where EnforceDistribution::remove_dist_changing_operators could strip distribution-changing operators that also carry fetch (global limit semantics), causing the global limit boundary to be lost when the physical optimizer pipeline is run multiple times on an already-optimized plan.
Changes:
- Stop stripping top-level
RepartitionExec/CoalescePartitionsExec/SortPreservingMergeExeconce an operator withfetchis encountered. - Add regression coverage ensuring fetch-carrying distribution-changing operators are preserved by
EnforceDistribution. - Add an integration regression test ensuring
SELECT DISTINCT a ... LIMIT 1retains a global limit boundary after a second optimizer pass.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| datafusion/physical-optimizer/src/enforce_distribution.rs | Preserve fetch-carrying distribution-changing operators during the “strip top operators” phase to avoid dropping global limit semantics. |
| datafusion/core/tests/physical_optimizer/limited_distinct_aggregation.rs | Add regression test that rerunning physical optimization keeps the global limit boundary for a limited DISTINCT query. |
| datafusion/core/tests/physical_optimizer/enforce_distribution.rs | Add targeted unit test that EnforceDistribution keeps fetch-carrying CoalescePartitionsExec and SortPreservingMergeExec. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The regression test test_global_limit_survives_second_optimizer_pass constructed a fresh ConfigOptions::new() and PhysicalOptimizer::new() for the second optimization pass, which could drift from the SessionContext's actual optimizer list and configuration. Reuse ctx.state().physical_optimizers() and ctx.state().config_options() instead, matching how DefaultPhysicalPlanner::optimize_physical_plan runs the pipeline in production. Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
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.
Problem
EnforceDistribution::remove_dist_changing_operatorsstrips top-levelRepartitionExec/CoalescePartitionsExec/SortPreservingMergeExec, assuming they are pure distribution-changing operators that can be regenerated later if necessary.A
fetchon these operators is not a distribution concern — it carries global limit semantics (e.g. produced byLimitPushdownfolding aGlobalLimitExecintoCoalescePartitionsExec: fetch=N). Stripping the operator silently drops the limit.This is latent in a single optimizer pass, but breaks when the physical optimizer pipeline runs twice on the same plan (e.g. GreptimeDB plans a nested subplan separately, then optimizes the enclosing plan again — the MergeScan local-execution fallback for information_schema tables): the second pass removes
CoalescePartitionsExec: fetch=Nand never re-adds it, socan return one row per partition instead of one row in total (GreptimeTeam/greptimedb#8958). Reproducible with vanilla DataFusion 53.1.0 by running the physical optimizer pipeline twice.
Fix
Stop the removal loop at any operator carrying a fetch.
Tests
keep_fetch_carrying_dist_changing_operators:EnforceDistributionpreserves fetch-carryingCoalescePartitionsExecandSortPreservingMergeExec.test_global_limit_survives_second_optimizer_pass: running the full physical optimizer pipeline a second time over the optimized plan ofSELECT DISTINCT a FROM t LIMIT 1keeps the global limit boundary (CoalescePartitionsExec: fetch=1/GlobalLimitExec).Verified:
cargo test -p datafusion --test core_integration917/917 passed; clippy and fmt clean.Related: apache#23800 (orthogonal
LimitPushdownfix — that one mistakes a per-partition fetch for a global limit; this one drops a global fetch when removing distribution operators).