Skip to content

doc: Document the physical optimizer contract - #26351

Merged
2010YOUY01 merged 6 commits into
apache:mainfrom
2010YOUY01:optimizer-contract
Oct 19, 2026
Merged

2010YOUY01 merged 6 commits into
apache:mainfrom
2010YOUY01:optimizer-contract

Conversation

@2010YOUY01

Copy link
Copy Markdown
Member

Which issue does this PR close?

Rationale for this change

See issue for rationale, this PR documents what has been agreed on

What changes are included in this PR?

doc only

What is the testing strategy for this PR?

Are there any user-facing changes?

//! example, [`EnsureRequirements`] enforces input distribution and ordering.
//! Some constraints remain implicit or are documented only in individual rules.
//!
//! - **Optimization Coverage:** A non-default rule order may expose rules to

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A CI tool might help to verify optimization coverage is reachable from queries.

@codecov-commenter

codecov-commenter commented Oct 15, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 82.89%. Comparing base (3d9037c) to head (35bcb1b).

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #26351      +/-   ##
==========================================
- Coverage   82.89%   82.89%   -0.01%     
==========================================
  Files        1154     1154              
  Lines      457961   457961              
  Branches   457961   457961              
==========================================
- Hits       379637   379620      -17     
- Misses      55285    55302      +17     
  Partials    23039    23039              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@alamb alamb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @2010YOUY01 and @jayzhan211 and @zhuqi-lucas

I think this is better than what we current have, so I am approving it. However, I found it somewhat hard to understand as written and have a few suggestions on how to improve it.

It would also be fine with me to merge this PR in as is and I can make a follow on PR with a proposal to improve the wording

//! disables the distinct aggregation soft-limit optimization. Custom pipelines
//! require more care, as described below.
//!
//! ## Custom Optimizer Pipelines

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found this somewhat confusingly worded. I would find it clearer if we started by stating the invariants:

  1. All optimizer rules must preserve plan correctness (the output plan can be run and produces the same results as the input plan). We can note there are some built in plans that aren't yet there (like EnforceRequirements)
  2. That reordering optimizer rules (other than the exceptions) should result in a plan that produces the same results

Then we could then move on to the explicit warning that adding / reordering rules may make some of the built in rules less effective as some built in rules (e.g. CombinePartialFinalAggregate) match certain plan shapes.

p.s. I would love to split out EnforceRequrements into its own Analyzer thing precisely so we don't have to have to mention the special case for EnforceRequirements above

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the review! I've addressed other feedbacks since they're smaller wording improvements, but not this one.

I think i don't fully get how you want to express it better, feel free to directly push changes, or do a follow-up PR 🙏🏼

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you

feel free to directly push changes, or do a follow-up PR 🙏🏼

I pushed cc2ab97 which is a fairly substantial rewording -- please feel free to revert it if you don't agree

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reads like a great improvement. Thank you.

/// computes the same results, but in a potentially more efficient way.
///
/// Use [`SessionState::add_physical_optimizer_rule`] to register additional
/// The returned plan must be valid and produce the same results as the input

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also felt adding some more cross references here will make it easier to find

/// [`EnsureRequirements`] and only merges a `Partial` aggregate that is the
/// direct child of a `Final` aggregate.
///
/// ```text

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel this example isn't a straightforward demonstration of canonical vs. non-canonical forms, as it requires some knowledge of DataFusion-specific implementation details.

It would be better to find a simpler example, though I can't think of one yet. I'll update it when I come up with one.

@2010YOUY01
2010YOUY01 added this pull request to the merge queue Oct 19, 2026
Merged via the queue into apache:main with commit 862843f Oct 19, 2026
42 checks passed
@2010YOUY01
2010YOUY01 deleted the optimizer-contract branch October 19, 2026 08:19
lyne7-sc pushed a commit to lyne7-sc/datafusion that referenced this pull request Oct 19, 2026
… optimizer pipeline (apache#26479)

## Which issue does this PR close?

- Related to apache#26351 (split out
so it can be reviewed and merged independently)

## Rationale for this change

While reviewing apache#26351 @jayzhan211 @2010YOUY01 and I have observed that
`EnsureRequirements` is special among the physical optimizer rules, but
that isn't documented on the rule itself

## What changes are included in this PR?

Documentation only, in
`datafusion/physical-optimizer/src/ensure_requirements/mod.rs`:

## What is the testing strategy for this PR?

CI
## Are there any user-facing changes?

No, only rustdoc text.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
mkleen pushed a commit to mkleen/datafusion that referenced this pull request Oct 31, 2026
…roke it (apache#26461)

## Which issue does this PR close?

- Closes apache#26460.

Related: apache#26171 (option 3), apache#26351, apache#26360, apache#25688.

## Rationale for this change

`SanityCheckPlan` is the last rule in `PhysicalOptimizer::new()`, so
rules appended with `SessionStateBuilder::with_physical_optimizer_rule`
run after it and are never checked. On `main`, an appended rule that
removes a window's sort silently returns wrong `row_number()` values.
Custom pipelines have to keep the rule last by hand, and a custom list
without it runs plans whose requirements do not hold. When the check
does fail, the error only says `SanityCheckPlan`, so finding the rule
that broke the plan has needed a manual bisect each time (for example
apache#26391, apache#17077, apache#25921).

@alamb suggested this change in the review of apache#26107: remove
`SanityCheckPlan` as an optimizer pass and always run it after the
optimizer is done
([comment](apache#26107 (comment))).

apache#26171 agreed (option 3) that a custom pipeline must keep the plan
valid, while optimization coverage is not guaranteed. This PR makes that
validity part checked for every pipeline. The review on apache#26360 asked for
the same shape: one rule, `EnsureRequirements`, makes the plan valid,
and every later rule keeps it valid.

## What changes are included in this PR?

- `SanityCheckPlan` leaves the default rule list.
`DefaultPhysicalPlanner::optimize_physical_plan` runs the same check
(unbounded inputs, ordering, distribution,
`check_invariants(Executable)`) after the last rule, including appended
rules. It replaces the planner's own `InvariantChecker(Executable)`
walk, which `SanityCheckPlan` repeated.
- When the ordering or distribution requirements fail, the planner runs
the rules again from the input plan to name the rule that broke them, so
planning that succeeds keeps no intermediate plans:

  ```text
SanityCheckPlan: order and distribution requirements stopped holding
after physical optimizer rule 'ProjectionPushdown'
  caused by
Error during planning: Plan: [...] does not satisfy order requirements:
[...]
  ```

or, when the plan was already invalid as `EnsureRequirements` produced
it:

  ```text
SanityCheckPlan: the plan produced by physical optimizer rule
'EnsureRequirements' does not satisfy the order and distribution
requirements
  ```

Plans before `EnsureRequirements` need not satisfy the requirements, so
only plans from its last run onwards are checked. The rule is recognized
by its name; a separate enforcement phase (apache#25688) would give a cleaner
boundary. Errors about unbounded inputs keep the plain `SanityCheckPlan`
context, so existing messages do not change.
- `sanity_checker` exposes `check_plan_tree_sanity`,
`check_input_requirements` and `input_requirements_hold`, which the
planner uses.
- `SanityCheckPlan` is deprecated. Tests that used it to validate a plan
call `check_plan_tree_sanity` instead.
- The upgrade guide, the optimizer rule reference and `explain.slt` are
updated.

## What is the testing strategy for this PR?

- `sanity_checker.rs`: a rule appended after the default rules that
removes sorts is reported by name; a custom list without
`EnsureRequirements` reports that no rule produced a valid plan.
- `memory_limit/mod.rs`: the two sort merge join tests ran a
`SortMergeJoinExec` over unsorted streams through a custom rule list
without `EnsureRequirements`. They now sort the generated data by the
join keys and declare that order, so the plan is valid and the tests
still measure the join's memory use.
- The extended workspace suite passes.

## Are there any user-facing changes?

- Plans produced by rules added with `with_physical_optimizer_rule`, or
by a custom rule list, are now checked. A plan whose requirements do not
hold, such as a sort merge join over unsorted input, fails to plan
instead of running and possibly returning wrong results.
- `PhysicalOptimizer::new()` no longer contains `SanityCheckPlan`, and
`EXPLAIN VERBOSE` no longer prints `physical_plan after
SanityCheckPlan`. Code that looks the rule up in the default list no
longer finds it. `SanityCheckPlan` itself is deprecated; code that runs
optimizer rules without the planner can call
`sanity_checker::check_plan_tree_sanity` on the final plan.
- Errors for broken ordering or distribution requirements name the rule
that broke them.

This changes the public behavior of the default rule list, so it needs
the `api change` label.

---------

Co-authored-by: Andrew Lamb <andrew@nerdnetworks.org>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

optimizer Optimizer rules v56.0.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Discussion: Define the Contract for Custom Physical Optimizer Pipelines

5 participants