924060929 commented on code in PR #65129:
URL: https://github.com/apache/doris/pull/65129#discussion_r3568004494


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/PhysicalPlanTranslator.java:
##########
@@ -2496,14 +2497,56 @@ public PlanFragment visitPhysicalSetOperation(
             setOperationNode.setColocate(true);
         }
 
-        // TODO: open comment when support `enable_local_shuffle_planner`
-        // for (Plan child : setOperation.children()) {
-        //     PhysicalPlan childPhysicalPlan = (PhysicalPlan) child;
-        //     if 
(JoinUtils.isStorageBucketed(childPhysicalPlan.getPhysicalProperties())) {
-        //         
setOperationNode.setDistributionMode(DistributionMode.BUCKET_SHUFFLE);
-        //         break;
-        //     }
-        // }
+        // Mark BUCKET_SHUFFLE only when every child is bucket-distributed on 
the same storage
+        // layout, i.e. ChildrenPropertiesRegulator actually aligned all 
children to one basic
+        // child's buckets: it enforces the non-basic children to the basic 
child's storage
+        // layout, and only does so when set-op bucket shuffle is allowed, so 
the marker follows
+        // the regulator's decision without re-checking the session variables 
here (that would
+        // risk drifting out of sync with the request-derivation gate).
+        //
+        // A single storage-bucketed child is not proof on its own that this 
set operation chose
+        // bucket shuffle: under a non-bucket (ANY) request a child can stay 
storage-bucketed
+        // independently while the other children are left unaligned, and 
marking from that child
+        // alone would drive PlanFragment.hasBucketShuffleNode(), bucket 
assignment and non-
+        // INTERSECT receiver fill-up on a set operation whose rows are not 
actually bucket-co-
+        // located. Requiring one shared storage layout keeps the marker in 
step with
+        // ChildOutputPropertyDeriver, which likewise only claims a bucket 
distribution when all
+        // children agree on the layout.
+        //
+        // Unlike hash join, BUCKET_SHUFFLE is not exclusive with isColocate 
above: for a set
+        // operation isColocate describes the bucket-aligned scheduling of the 
fragment (the
+        // basic child scans buckets directly), while BUCKET_SHUFFLE describes 
how the other
+        // children arrive (bucket-shuffle exchanges). Both routes converge to 
the same
+        // bucket-hash local exchange requirement in 
SetOperationNode.enforceAndDeriveLocalExchange.
+        boolean anyStorageBucketed = false;
+        boolean allChildrenBucketAligned = !setOperation.children().isEmpty();
+        DistributionSpecHash basicLayout = null;
+        for (Plan child : setOperation.children()) {
+            DistributionSpec childSpec = ((PhysicalPlan) 
child).getPhysicalProperties().getDistributionSpec();
+            if (!(childSpec instanceof DistributionSpecHash)) {
+                allChildrenBucketAligned = false;
+                break;
+            }
+            DistributionSpecHash childHash = (DistributionSpecHash) childSpec;
+            ShuffleType shuffleType = childHash.getShuffleType();
+            if ((shuffleType != ShuffleType.NATURAL && shuffleType != 
ShuffleType.STORAGE_BUCKETED)
+                    || childHash.getTableId() < 0) {
+                allChildrenBucketAligned = false;
+                break;
+            }
+            anyStorageBucketed |= shuffleType == ShuffleType.STORAGE_BUCKETED;
+            if (basicLayout == null) {
+                basicLayout = childHash;
+            } else if (childHash.getTableId() != basicLayout.getTableId()

Review Comment:
   Good catch — confirmed and fixed. `shouldFillUpInstances` imported the 
Nereids algebra `Intersect`, but the collected nodes are legacy 
`org.apache.doris.planner.SetOperationNode`s, so `instanceof Intersect` was 
always false and the intersect skip was dead. It now checks `instanceof 
IntersectNode` (the legacy planner node).
   
   On a 4-BE cluster I reproduced that, before the fix, a bucket-shuffle 
`INTERSECT` with a bucket-pruned anchor did create missing-bucket fill-up 
instances; the result stayed correct (an empty anchor bucket intersects to 
empty, so the shuffled rows there produce nothing) but the receivers were 
wasted work. After the fix the intersect no longer fills up while 
`UNION`/`EXCEPT` still do, and results are unchanged.
   
   Added a deterministic unit test 
`UnassignedScanBucketOlapTableJobTest#testShouldFillUpInstancesSkipsBucketShuffleIntersectOnly`:
 a bucket-shuffle `IntersectNode` → no fill-up, bucket-shuffle `UnionNode` / 
`ExceptNode` → fill-up, and a non-bucket-shuffle union → no fill-up. Reverting 
the check to the Nereids type makes the intersect assertion fail.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/PhysicalPlanTranslator.java:
##########
@@ -2496,14 +2497,56 @@ public PlanFragment visitPhysicalSetOperation(
             setOperationNode.setColocate(true);
         }
 
-        // TODO: open comment when support `enable_local_shuffle_planner`
-        // for (Plan child : setOperation.children()) {
-        //     PhysicalPlan childPhysicalPlan = (PhysicalPlan) child;
-        //     if 
(JoinUtils.isStorageBucketed(childPhysicalPlan.getPhysicalProperties())) {
-        //         
setOperationNode.setDistributionMode(DistributionMode.BUCKET_SHUFFLE);
-        //         break;
-        //     }
-        // }
+        // Mark BUCKET_SHUFFLE only when every child is bucket-distributed on 
the same storage
+        // layout, i.e. ChildrenPropertiesRegulator actually aligned all 
children to one basic
+        // child's buckets: it enforces the non-basic children to the basic 
child's storage
+        // layout, and only does so when set-op bucket shuffle is allowed, so 
the marker follows
+        // the regulator's decision without re-checking the session variables 
here (that would
+        // risk drifting out of sync with the request-derivation gate).
+        //
+        // A single storage-bucketed child is not proof on its own that this 
set operation chose
+        // bucket shuffle: under a non-bucket (ANY) request a child can stay 
storage-bucketed
+        // independently while the other children are left unaligned, and 
marking from that child
+        // alone would drive PlanFragment.hasBucketShuffleNode(), bucket 
assignment and non-
+        // INTERSECT receiver fill-up on a set operation whose rows are not 
actually bucket-co-
+        // located. Requiring one shared storage layout keeps the marker in 
step with
+        // ChildOutputPropertyDeriver, which likewise only claims a bucket 
distribution when all
+        // children agree on the layout.
+        //
+        // Unlike hash join, BUCKET_SHUFFLE is not exclusive with isColocate 
above: for a set
+        // operation isColocate describes the bucket-aligned scheduling of the 
fragment (the
+        // basic child scans buckets directly), while BUCKET_SHUFFLE describes 
how the other
+        // children arrive (bucket-shuffle exchanges). Both routes converge to 
the same
+        // bucket-hash local exchange requirement in 
SetOperationNode.enforceAndDeriveLocalExchange.
+        boolean anyStorageBucketed = false;
+        boolean allChildrenBucketAligned = !setOperation.children().isEmpty();
+        DistributionSpecHash basicLayout = null;
+        for (Plan child : setOperation.children()) {
+            DistributionSpec childSpec = ((PhysicalPlan) 
child).getPhysicalProperties().getDistributionSpec();
+            if (!(childSpec instanceof DistributionSpecHash)) {
+                allChildrenBucketAligned = false;
+                break;
+            }
+            DistributionSpecHash childHash = (DistributionSpecHash) childSpec;
+            ShuffleType shuffleType = childHash.getShuffleType();
+            if ((shuffleType != ShuffleType.NATURAL && shuffleType != 
ShuffleType.STORAGE_BUCKETED)

Review Comment:
   Made the marker use the deriver's exact proof. Extracted the alignment check 
— all children hash NATURAL/STORAGE_BUCKETED on one storage layout **and** 
mapping their shuffle columns to the same set-operation output positions — into 
a shared `ChildOutputPropertyDeriver.isSetOperationBucketAligned(setOperation, 
childDistributions)` (with `mapShuffleColumnsToOutputPositions` now static). 
`visitPhysicalSetOperation` (to decide the output property) and 
`PhysicalPlanTranslator` (to set the `BUCKET_SHUFFLE` marker) both call it, so 
they can never disagree: a same-layout shape whose bucket key feeds output 
position `k` in one child and `v` in another fails the shared check, so the 
deriver returns `createAnyFromHash(...)` and the marker is not set.
   
   The deriver's own behavior is unchanged — the output-position proof was 
already there; I only lifted it into a shared method and made the translator 
use it instead of the layout-only check. Verified the full 
`bucket_shuffle_set_operation` suite still matches the committed `.out` (no 
shape/result delta).



-- 
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