Skip to content

chore: comet-native integration branch (testing with Comet native executors) - #2755

Draft
andygrove wants to merge 24 commits into
mainfrom
comet-native
Draft

andygrove wants to merge 24 commits into
mainfrom
comet-native

Conversation

@andygrove

Copy link
Copy Markdown
Member

Which issue does this PR close?

Not intended to close an issue. This is a draft integration branch and is not meant to be merged as is.

Rationale for this change

I am using this branch (comet-native) to test Ballista with Comet native executors. It collects the open PRs that Comet native execution depends on, so they can be exercised together before each one lands on main.

What changes are included in this PR?

The branch combines the following open PRs, which are reviewed separately:

It also carries a small change on top of #2722: PinSubmittedShufflesRule keeps a submitted range repartition as a stage boundary.

Already merged to main and kept in sync by merging main into this branch: #2682 (run submitted physical plans through the adaptive planner) and #2743 (embedder rules for adaptive query planning).

Are there any user-facing changes?

Whatever the PRs above change. This PR adds nothing beyond them.

A physical plan submitted with `submit_physical_plan` or a `PhysicalPlan`
query always used the static planner, even with adaptive planning on.
Route it through `AdaptiveExecutionGraph` when
`ballista.planner.adaptive.enabled` is set, keeping the static planner
otherwise.

`AdaptivePlanner::try_new_from_physical` runs the plan-preparation rules
over the submitted plan and hands it to the adaptive optimizer chain.
Before that, `PinSubmittedShufflesRule` turns the plan's hash
repartitions into exchanges: DataFusion's `EnforceDistribution` removes
any repartition no operator requirement asks for, which would drop a
shuffle the submitter placed under an extension operator.

`CoalescePartitionsRule` now only coalesces a stage whose shuffles feed
operators known to be indifferent to the partition count. An extension
operator may depend on it, so such a stage is left alone.
# Conflicts:
#	ballista/scheduler/src/state/mod.rs
#2633 added a test to this module that names ExecutionPlan, and #2613 then
moved FailingPlanRewriteExec out of the module and dropped the imports it
had needed, ExecutionPlan among them. Each passed CI against its own base.
Together they leave the scheduler's test target uncompilable, so cargo check,
Clippy and the test jobs have failed on main since #2613 landed.
…ing it

The static planner kept only hash repartitions as stage boundaries and
dropped every other RepartitionExec. That is harmless for round-robin,
which only spreads rows, but a range repartition is part of what a plan
means. A plan that range-partitions and then sorts each partition (a
distributed global sort) came back as partitions that were each sorted
but not range-disjoint, with no error.

A RepartitionExec with Partitioning::Range is now a stage boundary. The
producer stage ends in a SortShuffleWriterExec that routes rows with
DataFusion's own RangeExpr (the function RepartitionExec applies), so
the consumer's partition i reads exactly range i. The writer shares
everything after routing with the hash shuffle: buffering, spilling, the
consolidated file and its index. The range travels in a new field of
SortShuffleWriterExecNode; hash stages keep using the old one.

Restricting a range-partitioned ShuffleReaderExec to a task's partitions
now slices the split points along with them, instead of failing. A slice
that does not ascend (a retried partition queued ahead of a later one)
or is empty has no range partitioning to report, so it reports
UnknownPartitioning rather than failing the task.

A range repartition that must preserve its input order is rejected with
an error: a shuffle cannot merge sorted inputs, and planning it anyway
would return partitions that are range-disjoint but no longer sorted.

RangeShuffleWriterExec and RangeShuffleReaderExec are not used. They
need an ordered producer, while the repartition here is followed by the
sort.

Closes #2680
#2613 deleted a test helper and the block of imports that served it,
which included ExecutionPlan. #2633, merged earlier, had added a test
that still uses ExecutionPlan, so each passed CI alone but the scheduler's
tests have not compiled on main since both landed.
A range RepartitionExec in a submitted physical plan is now planned as a
shuffle instead of being dropped, one that must preserve its order is
refused, and SortShuffleWriterExecNode gains a field.
#2723 adds partition_id_routing to the same message as field 10, and two
fields cannot share a number. 11 leaves 10 to it, and putting the field
next to output_partitioning keeps it clear of the fields that PR appends
at the end of the message.
…partitions

When a stage is split between tasks, the scheduler restricts each task's
plan to the partitions that task runs. It could only restrict the leaves it
knew and the two PartitionSliceable operators on a hand-kept list. Any other
node holding data per partition was left as it was, so a task that ran
partitions 4 and 5 of it executed its partitions 0 and 1, and the stage
returned some partitions twice and others not at all, with no error.

- PartitionSliceable::restrict_to_partitions takes any number of children,
  leaves included. It replaces slice_to_partitions, which took one.
- SchedulerConfig::partition_sliceable_resolver finds the PartitionSliceable
  of nodes Ballista does not list. It is asked after the built-in list.
- A leaf that nothing can restrict now fails the task unless the task runs
  all of its partitions in order, instead of running and duplicating output.
  The scheduler also rejects a restriction that returns the wrong number of
  partitions.
A hash shuffle always assigns rows to partitions with DataFusion's hash,
so it cannot match data that is already partitioned by another function,
such as Spark's Murmur3 for bucketed tables.

Add a partition-id mode to the sort-based shuffle writer. The plan below
the writer computes each row's output partition into an integer column,
and the writer sends the row to that partition and drops the column
before writing. Output partition i holds exactly the rows whose id is i.
An id that is null, negative or out of range fails the task.

- ShuffleRouting selects between hashing and partition-id routing in
  SortShuffleWriterExec. try_new is unchanged; try_new_with_routing takes
  the routing, and the executor stamps the decoded writer rather than
  rebuilding it field by field, so the routing is carried over.
- PartitionIdRepartitionExec is the marker a client places where a hash
  RepartitionExec would go. The static distributed planner turns it into
  a stage boundary ending in the new writer. It cannot be executed.
- Both the marker and the writer's routing have proto messages and codec
  support (a oneof on the sort writer node, wire-compatible with the old
  field), so plans containing them can be submitted over gRPC.
- The adaptive planner rejects the marker: it may coalesce or re-plan an
  exchange's partitions, which would break the placement.

Closes #2681.
`losing_own_shuffle_output_restores_the_cache_reader` (#2633) names
`dyn ExecutionPlan` but relied on the test module's import of it, which
#2613 removed along with the test helper that was its only other user.
Each passed on its own base, but together `ballista-scheduler`'s lib
tests no longer compile on main.
# Conflicts:
#	ballista/scheduler/src/state/aqe/planner.rs
# Conflicts:
#	ballista/scheduler/src/state/execution_graph.rs
# Conflicts:
#	ballista/scheduler/src/state/task_builder.rs
# Conflicts:
#	ballista/core/proto/ballista.proto
#	ballista/core/src/execution_plans/shuffle_writer.rs
#	ballista/core/src/execution_plans/sort_shuffle/writer.rs
#	ballista/core/src/serde/generated/ballista.rs
#	ballista/core/src/serde/mod.rs
#	ballista/scheduler/src/planner.rs
…e shuffle PRs

Pass the new resolver argument to restrict_plan_to_partitions in
range_shuffle_tests and drop the now-unused internal_err import from
task_builder.
PinSubmittedShufflesRule pinned only hash repartitions, so
EnforceDistribution removed a client's range repartition and nothing put
it back: a consumer that sorts each partition then no longer sorted the
whole result. It now pins a range repartition too, unless it must
preserve the order of its input, which a shuffle cannot.
# Conflicts:
#	ballista/scheduler/src/state/mod.rs
#	ballista/scheduler/src/state/task_builder.rs
Cherry-picked from #2743 (adaptive-rules-hook): SchedulerConfig::adaptive_rules
takes preparation rules, which run once while a job's plan is created, and
replan rules, which run last each time the plan is optimized. ExchangeExec is
exported, with accessors and with_inactive_stage, from state::adaptive.

On this branch the rules are also passed to the physical-plan path from
#2682, where preparation rules run after the submitted shuffles are pinned,
so a rule can hold back a stage the submitter placed. Comet Native Executors
use it to measure a join's build side before shuffling its probe side
(apache/datafusion-comet#7391).
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Nov 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant