You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@doris.apache.org by hu...@apache.org on 2026/04/08 13:56:16 UTC

(doris) branch fe_local_shuffle updated: [fix](fe) Guard PASSTHROUGH insertion with useSerialSource() check

This is an automated email from the ASF dual-hosted git repository.

huajianlan pushed a commit to branch fe_local_shuffle
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/fe_local_shuffle by this push:
     new d9524ee3965 [fix](fe) Guard PASSTHROUGH insertion with useSerialSource() check
d9524ee3965 is described below

commit d9524ee3965ab89a0baacd2972f345886747eb23
Author: 924060929 <la...@selectdb.com>
AuthorDate: Wed Apr 8 21:55:45 2026 +0800

    [fix](fe) Guard PASSTHROUGH insertion with useSerialSource() check
    
    ### What problem does this PR solve?
    
    Problem Summary: The previous PASSTHROUGH insertion in ExchangeNode.enforceAndDeriveLocalExchange()
    only checked isSerialOperator() to decide whether to insert a PASSTHROUGH local exchange.
    However, the BE only treats an exchange as serial when BOTH isSerialOperator() AND
    fragment.useSerialSource() are true. Without the useSerialSource() guard, PASSTHROUGH was
    incorrectly inserted in non-pooling fragments where the exchange has N tasks, corrupting
    broadcast join data distribution (PASSTHROUGH round-robin splits complete-dataset sinks
    into 1/N subsets per source).
    
    The fix adds fragment.useSerialSource() check to match the BE's serial condition in toThrift().
    
    ### Release note
    
    None
    
    ### Check List (For Author)
    
    - Test: Manual EXPLAIN verification for UNION, broadcast join, and BUCKET_SHUFFLE join
    - Behavior changed: No
    - Does this need documentation: No
    
    Co-authored-by: Copilot <22...@users.noreply.github.com>
---
 .../org/apache/doris/planner/ExchangeNode.java     | 23 ++++++++++++++--------
 1 file changed, 15 insertions(+), 8 deletions(-)

diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/ExchangeNode.java b/fe/fe-core/src/main/java/org/apache/doris/planner/ExchangeNode.java
index 508788717c4..59c89f701bb 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/planner/ExchangeNode.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/ExchangeNode.java
@@ -180,14 +180,21 @@ public class ExchangeNode extends PlanNode {
     @Override
     public Pair<PlanNode, LocalExchangeType> enforceAndDeriveLocalExchange(PlanTranslatorContext translatorContext,
             PlanNode parent, LocalExchangeTypeRequire parentRequire) {
-        // Mirror BE's ExchangeSourceOperatorX::required_data_distribution():
-        // serial Exchange returns NOOP. This covers both:
-        // 1. UNPARTITIONED exchange (naturally serial) → already NOOP in else branch
-        // 2. HASH/BUCKET exchange made serial by use_serial_exchange=true → override to NOOP
-        // Mirror BE ExchangeSourceOperatorX::required_data_distribution():
-        // serial → NOOP; else HASH/BUCKET/NOOP based on partition type.
-        if (isSerialOperator()) {
-            return Pair.of(this, LocalExchangeType.NOOP);
+        // Must match the BE serial condition in toThrift(): isSerialOperator() && useSerialSource().
+        // Only insert PASSTHROUGH when the exchange will actually be serial on the BE.
+        // Without useSerialSource() check, we'd insert PASSTHROUGH in non-pooling fragments
+        // where the exchange has N tasks, corrupting broadcast join data distribution
+        // (PASSTHROUGH round-robin splits complete-dataset sinks into 1/N subsets per source).
+        boolean willBeSerialOnBe = isSerialOperator()
+                && fragment != null
+                && fragment.useSerialSource(ConnectContext.get());
+        if (willBeSerialOnBe) {
+            // Serial exchange → 1 task. Insert PASSTHROUGH to fan out to N tasks.
+            // Parent nodes (HashJoin, etc.) may add PASS_TO_ONE on top if they need
+            // single-builder semantics (e.g., broadcast join build side).
+            PlanNode pt = new LocalExchangeNode(translatorContext.nextPlanNodeId(),
+                    this, LocalExchangeType.PASSTHROUGH, null);
+            return Pair.of(pt, LocalExchangeType.PASSTHROUGH);
         } else if (partitionType == TPartitionType.HASH_PARTITIONED) {
             return Pair.of(this, LocalExchangeType.GLOBAL_EXECUTION_HASH_SHUFFLE);
         } else if (partitionType == TPartitionType.BUCKET_SHFFULE_HASH_PARTITIONED) {


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org