github-actions[bot] commented on code in PR #65129:
URL: https://github.com/apache/doris/pull/65129#discussion_r3567809685
##########
fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/PhysicalPlanTranslator.java:
##########
@@ -2496,14 +2496,26 @@ 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;
- // }
- // }
+ // A storage-bucketed child means ChildrenPropertiesRegulator chose
the bucket shuffle
+ // alternative for this set operation (it enforces the other children
to the basic
+ // child's storage layout), which it only does when set-op bucket
shuffle is allowed.
+ // So the marker follows the regulator's decision directly, without
re-checking the
+ // session variables here (that would risk drifting out of sync with
the request
+ // derivation gate). Mark the node BUCKET_SHUFFLE so the set
sink/probe align by bucket
+ // instead of execution-bucketed hash.
+ //
+ // 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.
+ for (Plan child : setOperation.children()) {
+ PhysicalPlan childPhysicalPlan = (PhysicalPlan) child;
+ if
(JoinUtils.isStorageBucketed(childPhysicalPlan.getPhysicalProperties())) {
Review Comment:
This still treats any storage-bucketed child as proof that this set
operation chose the bucket-shuffle alternative. A reduced reachable shape is an
outer `PhysicalUnion` using its cheap `ANY` request, with one child being a
nested `INTERSECT` that outputs `STORAGE_BUCKETED` under its own all-column
hash request and another child being realigned to `EXECUTION_ANY`.
`ChildOutputPropertyDeriver` correctly returns a non-specific property for the
outer union because its children are not uniformly bucket-aligned, but this
translator loop ignores that proof and marks the legacy `SetOperationNode`
`BUCKET_SHUFFLE` from the nested child's property alone. That marker is
consumed by `PlanFragment.hasBucketShuffleNode()`, bucket assignment, and
non-`INTERSECT` receiver fill-up, so it is not just an explain-label issue.
Please only set the marker when the current set operation itself has proved
bucket-shuffle alignment, for example by mirroring the all-children
same-layout/same-output-position
proof or by tracking that this set operation's regulator branch inserted the
storage-bucketed exchanges.
##########
fe/fe-core/src/main/java/org/apache/doris/planner/LocalExchangeNode.java:
##########
@@ -283,8 +283,15 @@ public LocalExchangeType preferType() {
@Override
public LocalExchangeTypeRequire autoRequireHash() {
- if (requireType == LocalExchangeType.GLOBAL_EXECUTION_HASH_SHUFFLE
- || requireType == LocalExchangeType.BUCKET_HASH_SHUFFLE) {
+ // Callers are pass-through operators (union / streaming agg /
sort) that report
+ // resolveExchangeType(requireChild) upward while leaving row
placement to their
+ // children. A specific hash require must therefore be forwarded
as-is: degrading
+ // LOCAL_EXECUTION_HASH_SHUFFLE to the generic RequireHash lets a
bucket-distributed
+ // child satisfy the requirement and keep its bucket placement,
while the operator
+ // still claims LOCAL_EXECUTION_HASH_SHUFFLE to its parent — the
parent (e.g. a
+ // bucket join upgraded to local hash) then skips its re-align
local exchange and
+ // the mixed placements compute wrong results.
+ if (requireType.isHashShuffle()) {
Review Comment:
This change newly preserves `LOCAL_EXECUTION_HASH_SHUFFLE` as a specific
hash requirement, but the added set-operation regression does not exercise that
path: `explain shape plan` runs before `AddLocalExchange`, and nothing here
forces the bucket-to-local-hash upgrade that creates
`requireSpecific(LOCAL_EXECUTION_HASH_SHUFFLE)`. The existing
`LocalShuffleNodeCoverageTest` union coverage also uses generic
`requireHash()`, so reverting this line back to the old global/bucket-only
check would not be caught. Please add a targeted local-exchange unit test for
`parentRequire.requireSpecific(LOCAL_EXECUTION_HASH_SHUFFLE)` through a
union/pass-through node, or an `EXPLAIN DISTRIBUTED PLAN` regression that
deterministically forces the bucket-to-local upgrade.
##########
regression-test/suites/query_p0/set_operations/bucket_shuffle_set_operation.groovy:
##########
@@ -95,6 +95,144 @@ suite("bucket_shuffle_set_operation") {
select id from bucket_shuffle_set_operation2 where id=1
""")
+ // The basic child of a bucket-shuffle set operation can be a join output
instead of a
+ // direct scan. In that shape the local exchange planned for the basic
side must still
+ // partition by the storage bucket function: an execution-hash local
exchange would not
+ // align with the bucket-distributed side and the set operation would
compute wrong results.
+ checkShapeAndResult("bucket_shuffle_join_as_basic_child", """
+ select a.id from bucket_shuffle_set_operation1 a
+ join bucket_shuffle_set_operation2 b on a.id = b.id
+ intersect
+ select id from bucket_shuffle_set_operation3""")
+
+ // a set operation child can itself be a set operation whose output claims
a bucket
+ // distribution; the outer set operation must only treat its children as
bucket-aligned
+ // when they share the same storage layout
+ checkShapeAndResult("bucket_shuffle_nested_set_operation", """
+ select id from bucket_shuffle_set_operation3
+ union all
+ (select a.id from bucket_shuffle_set_operation1 a
+ join bucket_shuffle_set_operation2 b on a.id = b.id
+ intersect
+ select id from bucket_shuffle_set_operation2)""")
+
+ // when local shuffle is disabled entirely, every pipeline runs a single
task per
+ // instance so the bucket alignment holds naturally and bucket shuffle is
still allowed
+ sql "set enable_local_shuffle=false"
+ checkShapeAndResult("bucket_shuffle_when_local_shuffle_off", """
+ select id from bucket_shuffle_set_operation1
+ intersect
+ select id from bucket_shuffle_set_operation2""")
+ sql "set enable_local_shuffle=true"
Review Comment:
This assertion only proves that the union line itself is not
`[bucketShuffle]`; it does not prove the parent hash request was pushed through
`PhysicalUnion`. If the `createHashRequestAccordingToParent(...)` path
regressed or was removed, the optimizer could still keep an unbucketed union
and insert one `PhysicalDistribute[DistributionSpecHash]` above it for the
shuffle join, so this check and the ordered result would still pass. Please
strengthen this to assert the intended shape, e.g. that the `PhysicalUnion`
children are `PhysicalDistribute[DistributionSpecHash]` under
`enable_local_shuffle_planner=false`, or convert this case to a golden
shape/result check.
--
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]