Skip to content

fix: apply broadcast fetch before fan-out - #624

Open
shinzoxD wants to merge 1 commit into
datafusion-contrib:mainfrom
shinzoxD:fix-broadcast-fetch-before-fanout
Open

fix: apply broadcast fetch before fan-out#624
shinzoxD wants to merge 1 commit into
datafusion-contrib:mainfrom
shinzoxD:fix-broadcast-fetch-before-fanout

Conversation

@shinzoxD

Copy link
Copy Markdown
Contributor

What

  • keep a fetch-bearing CoalescePartitionsExec inside BroadcastExec
  • continue stripping fetch-less coalesces before broadcast
  • document why the regression count is invariant to unordered LIMIT row selection

Why

This follows up on #618. Moving the fetch to the coalesce above BroadcastExec changes a global build-side limit into a per-consumer limit after fan-out. The post-merge unit run demonstrated the resulting correctness failure: single-node and distributed count(*) returned 2500 and 3750 even though every possible set of 50 build IDs has exactly 2,500 probe matches.

Keeping the fetch-bearing coalesce as the broadcast input applies the limit once before replication. The resulting distributed plan has BroadcastExec over CoalescePartitionsExec: fetch=50.

Failure evidence: https://github.com/datafusion-contrib/datafusion-distributed/actions/runs/31358856004/job/93363560852

Validation

  • cargo fmt --all -- --check
  • git diff --check
  • cargo test --features integration --test multi_task_collect_join_repros build_side_fetch_is_preserved_by_broadcast (passed, then passed 5 repeated runs)

Copilot AI lite review requested due to automatic review settings August 11, 2026 16:12

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Comment on lines -136 to +142
let (broadcast_input, coalesce_fetch) = build_child
let broadcast_input = build_child
.downcast_ref::<CoalescePartitionsExec>()
.filter(|coalesce| coalesce.fetch().is_none())
.map_or_else(
|| (Arc::clone(build_child), None),
|coalesce| (Arc::clone(coalesce.input()), coalesce.fetch()),
|| Arc::clone(build_child),
|coalesce| Arc::clone(coalesce.input()),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤔 Unless I'm missing something, I think this should be showing up as an intermediate stage that is collapsed to 1 task and immediately broadcasted to several nodes.

Is there any chance we can have a test in this same file that asserts that?

Also, It'd be great to add another test that exercises this in inject_network_boundaries.rs

@gabotechs

Copy link
Copy Markdown
Collaborator

cc @barbarj, if you could give a look to this one that'd be great 🙏

Comment on lines 352 to 360
async fn build_side_fetch_is_preserved_by_broadcast() {
assert_distributed_matches_single_node(
"SELECT count(*) FROM (SELECT id FROM build_side LIMIT 50) b \
JOIN probe_side p ON b.id = p.id",
true,
)
.await
.unwrap();
}

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.

Can we assert the plan snapshot in this test as well? It'd make reasoning about the effects of this change easier. build_side_fetch_is_preserved_by_normalize doesn't do so because of some non-determinism in file partioning, but given this is a different query, I wonder if that might not be a problem here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants