924060929 opened a new pull request, #66295:
URL: https://github.com/apache/doris/pull/66295

   [fix](local shuffle) align bucket-shuffle union branches by the storage 
bucket function
   
   Related PR: #65129
   
   Problem Summary:
   
   A `UNION ALL` feeding a window function returned a duplicated
   `row_number()` for the same partition key:
   
       SELECT k, v, ROW_NUMBER() OVER (PARTITION BY k ORDER BY v, src) rn
       FROM (SELECT k, v, 1 src FROM t_left
             UNION ALL
             SELECT k, v, 2 src FROM t_right) s
       WHERE k IN (2, 4) ORDER BY k, rn;
   
       -- expected           -- actual with enable_local_shuffle_planner=true
       2 | 20 | 1            2 | 20 | 1
       2 | 20 | 2            2 | 20 | 1
       4 | 40 | 1            4 | 40 | 1
       4 | 40 | 2            4 | 40 | 1
   
   The two duplicated rows are exactly one row per union branch, i.e. the
   branches of the same key are landing in different pipeline tasks and the
   analytic sink numbers each task from 1.
   
   `SetOperationNode.enforceAndDeriveLocalExchange` handled the colocate /
   bucket-shuffle mode only in the intersect / except branch. For a union it
   always asked each child for the generic hash requirement, which
   `GLOBAL_EXECUTION_HASH_SHUFFLE`, `LOCAL_EXECUTION_HASH_SHUFFLE` and
   `BUCKET_HASH_SHUFFLE` all satisfy — it expresses "I need hash-partitioned
   input", not "I need the same hash function as my sibling branches". So in a
   bucket-shuffle union:
   
     - the branch that scans its own buckets is serial under a pooling scan,
       claims NOOP, and gets an `LE(LOCAL_EXECUTION_HASH_SHUFFLE)`
       (execution hash modulo the local task count);
     - the branch arriving through a bucket-shuffle exchange claims
       `BUCKET_HASH_SHUFFLE`, already satisfies the requirement, and keeps the
       storage bucket to instance mapping with no local exchange at all.
   
   Two different placement functions feed the same union, so a key present in
   both branches sits in two tasks. The failing plan (`explain distributed
   plan`, which is where FE-planned local exchanges show up):
   
       3:VUNION
       |----2:VEXCHANGE                              <- no local exchange, 
placement
       |                                                comes from the sender's
       |                                                
BUCKET_SHFFULE_HASH_PARTITIONED
       10:VLOCAL-EXCHANGE
       |  type: LOCAL_EXECUTION_HASH_SHUFFLE         <- execution hash
       9:VLOCAL-EXCHANGE
       |  type: PASSTHROUGH
       0:VOlapScanNode  POOLING-SCAN
   
   The shape only became reachable when #65129 re-enabled the bucket-shuffle
   alternative for set operations (union included) and marked every set
   operation with a storage-bucketed child as `DistributionMode.BUCKET_SHUFFLE`;
   before that both branches entered a fresh fragment through global hash
   exchanges, i.e. one consistent placement.
   
   Fix: when a union is propagating a hash requirement downward and is itself
   colocate / bucket-shuffle, require `BUCKET_HASH_SHUFFLE` from every branch
   and report `BUCKET_HASH_SHUFFLE` upward — the same pattern `HashJoinNode` and
   the intersect / except branch already use. Unlike intersect / except, a union
   does not need its branches aligned for its own semantics, so the bucket
   requirement is gated on `hasShuffleForCorrectnessAncestor`: with no
   downstream correctness consumer nothing has to move.
   
   The existing `bucket_shuffle_union_with_all_column` case did not catch this
   because it puts the union under a `join[shuffle]`, whose requirement is a
   specific GLOBAL hash that neither branch satisfies, so both get re-aligned to
   the same placement. Only consumers asking for the generic hash requirement
   (the analytic sort, and the aggregation paths that use `requireHash()` /
   `autoRequireHash()`) expose the mixed placement.
   
   Verified end to end on a cluster with the BE binary held fixed and only the
   FE swapped: wrong result before, correct after, and the same bucket-shuffle
   union under a `group by` consumer is correct too. The new regression case was
   checked against the unmodified FE and fails there with
   `duplicated row_number 1 in window partition 2`.
   
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)
   
   https://claude.ai/code/session_012ZmtXicM2eb3Yrn4b4keR5
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to