peter-toth commented on code in PR #58681:
URL: https://github.com/apache/spark/pull/58681#discussion_r4034344416


##########
sql/core/src/test/scala/org/apache/spark/sql/connector/KeyGroupedPartitioningSuite.scala:
##########
@@ -8191,17 +8234,27 @@ class KeyGroupedPartitioningSuite
       withSQLConf(
           SQLConf.V2_BUCKETING_SHUFFLE_ENABLED.key -> "true",
           "spark.sql.autoBroadcastJoinThreshold" -> "-1",
-          SQLConf.ADAPTIVE_EXECUTION_ENABLED.key -> "false") {
+          SQLConf.ADAPTIVE_EXECUTION_ENABLED.key -> "false",
+          // Filtering narrows the joined groups to the intersection, which 
leaves 2 groups over
+          // the marked side's 3 input partitions. The grouping stays 
index-identity but forfeits
+          // its keyed claim (SPARK-59272), and this test pins that claim 
surviving a regrouping,
+          // so it runs with filtering off.
+          SQLConf.V2_BUCKETING_PARTITION_FILTER_ENABLED.key -> "false") {

Review Comment:
   **Finding 6.** This pin and the count clause you added to `identityGpe` are 
both obsolete once you rebase, and the conflict here is where you will meet 
that.
   
   #58858 rewrote this block: the test now runs both settings of 
`partition.filter.enabled`, asserts `ValidateRequirements` and asserts 
`k.numPartitions === 3`, so the marked side keeping all three declared keys is 
pinned directly. Take master's side of the hunk and both the pin and the 
tightening go with it.
   
   On the tightening specifically, so it does not look like it was dropped 
silently: it was your answer to finding 1 last round and it was right, but 
master's finder no longer needs it. The comment there says why — the finder is 
deliberately weaker than `identityGrouping`, and `numPartitions === 3` is what 
carries the assertion. Nothing you fixed is lost.
   
   Resolve the hunk, not the file. Taking master's whole 
`KeyGroupedPartitioningSuite.scala` also drops your `SPARK-55992` pin and all 
seven parameterized cases — I did exactly that on my first attempt before 
noticing.
   



##########
sql/core/src/test/scala/org/apache/spark/sql/connector/KeyGroupedPartitioningSuite.scala:
##########
@@ -5235,7 +5259,9 @@ class KeyGroupedPartitioningSuite
     sql(s"INSERT INTO testcat.ns.$purchases VALUES (2, 10.0, cast('2021-01-01' 
as timestamp))")
     withSQLConf(
       SQLConf.V2_BUCKETING_ALLOW_KEYS_SUBSET_OF_PARTITION_KEYS.key -> "true",
-      SQLConf.V2_BUCKETING_ALLOW_COMPATIBLE_TRANSFORMS.key -> "true") {
+      SQLConf.V2_BUCKETING_ALLOW_COMPATIBLE_TRANSFORMS.key -> "true",
+      // The explained key count is the union of both sides' reduced keys.
+      SQLConf.V2_BUCKETING_PARTITION_FILTER_ENABLED.key -> "false") {

Review Comment:
   **Finding 9.** This is the one pinned case left after finding 6, and I think 
it can be parameterized like the other seven, which finishes the ask at 
[issuecomment-5615352605](https://github.com/apache/spark/pull/58681#issuecomment-5615352605).
   
   I flipped this line to `true` and ran the test. Only the count moves:
   
   ```
   +- GroupPartitions JoinKeyPositions: [0] ExpectedPartitionKeys: 0 Reducers: 
[BucketReducer(2)] DistributePartitions: false SortedMerge: false
      " did not contain "... ExpectedPartitionKeys: 2 Reducers: 
[BucketReducer(2)] ..."
   ```
   
   `items` holds `id=1`, so `bucket(4, 1) = 1` reduces to `1 % 2 = 1`; 
`purchases` holds `item_id=2`, so `bucket(6, 2) = 2` reduces to `2 % 2 = 0`. 
The two reduced key sets are disjoint, so the inner join's union is `[0, 1]` 
and its intersection is empty. Nothing is wrong here: the join can produce no 
row either way, and the test asserts only the explain string.
   
   That is also why your comment's reason does not quite carry the pin. If the 
count is incidental to what this test is about, which is that the node renders 
all five fields in all three explain modes, then asserting it at both values 
costs nothing. And pinned to `false` the test never renders the node under the 
shipped default at all, which is the property the other seven were 
parameterized for.
   
   ```scala
   Seq(2 -> false, 0 -> true).foreach { case (expectedKeys, filter) =>
     withSQLConf(
       SQLConf.V2_BUCKETING_ALLOW_KEYS_SUBSET_OF_PARTITION_KEYS.key -> "true",
       SQLConf.V2_BUCKETING_ALLOW_COMPATIBLE_TRANSFORMS.key -> "true",
       // Both sides' reduced keys are disjoint, so filtering leaves the join 
no key group at all.
       SQLConf.V2_BUCKETING_PARTITION_FILTER_ENABLED.key -> filter.toString) {
   ```
   
   with `ExpectedPartitionKeys: $expectedKeys` in the two keyword strings. 
Worth saying what this does not buy: the empty-intersection *behaviour* is 
already covered by `SPARK-48949: test partition filters with no matches`, which 
sets the config explicitly. What is uncovered is the rendering, and the 
rendering at the default.
   



##########
docs/sql-performance-tuning.md:
##########
@@ -688,6 +688,46 @@ The following SQL properties enable Storage Partition Join 
in different join que
       </td>
       <td>4.0.0</td>
     </tr>
+    <tr>
+      
<td><code>spark.sql.sources.v2.bucketing.partition.filter.enabled</code></td>
+      <td>true</td>
+      <td>
+        When enabled, key groups that cannot produce output for the join type 
are not scanned at all, instead of being filled with empty partitions on the 
side that does not hold them. For example, an inner join only scans the key 
groups present on both sides. This config requires 
<code>spark.sql.sources.v2.bucketing.enabled</code> to be true, together with 
either <code>spark.sql.sources.v2.bucketing.pushPartValues.enabled</code> or 
<code>spark.sql.sources.v2.bucketing.allowKeysSubsetOfPartitionKeys.enabled</code>.
+      </td>
+      <td>4.0.0</td>
+    </tr>
+    <tr>
+      <td><code>spark.sql.sources.v2.bucketing.sorting.enabled</code></td>
+      <td>false</td>
+      <td>
+        When enabled, Spark satisfies a sort on the partition key expressions 
from the partitioning reported by a V2 data source, so no shuffle is added for 
that sort. The parallelism of the sorted output is then whatever the data 
source's partition layout provides, and there is no shuffle stage left for 
adaptive partition coalescing or skew splitting to balance. This config 
requires <code>spark.sql.sources.v2.bucketing.enabled</code> to be true.
+      </td>
+      <td>4.0.0</td>
+    </tr>
+    <tr>
+      
<td><code>spark.sql.sources.v2.bucketing.partitionKeyOrdering.enabled</code></td>
+      <td>true</td>
+      <td>
+        When enabled, Spark derives the output ordering of a V2 scan from its 
partition key expressions, if the source reports a keyed partitioning but no 
explicit ordering. All rows of such a partition share one key value, so the 
partition is trivially sorted by those expressions, and a sort Spark would 
otherwise add becomes unnecessary. This config requires 
<code>spark.sql.sources.v2.bucketing.enabled</code> to be true.
+      </td>
+      <td>4.2.0</td>
+    </tr>
+    <tr>
+      
<td><code>spark.sql.sources.v2.bucketing.preserveKeyOrderingOnCoalesce.enabled</code></td>
+      <td>true</td>
+      <td>
+        When enabled, <code>GroupPartitionsExec</code> reports sort orders 
over partition key expressions after coalescing several input partitions into 
one. The merged partitions share the same partition key value, so these orders 
still hold, while orders over other columns are lost by the concatenation. This 
config requires <code>spark.sql.sources.v2.bucketing.enabled</code> to be true.

Review Comment:
   **Finding 8.** `GroupPartitionsExec.outputOrdering` gates the key-expression 
filter on `reducers.isEmpty` as well as on this config 
(`sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/GroupPartitionsExec.scala:527`).
 With reducers the merged partitions share only the reduced key, so no original 
key expression is constant in them and the node reports nothing at all. A user 
running with `spark.sql.sources.v2.bucketing.allowCompatibleTransforms.enabled` 
on gets no ordering out of this config on the joins that reduced, and the row 
gives them no way to know.
   
   The migration-guide entry has the same gap 
(`docs/sql-migration-guide.md:34`), and its "since those expressions are 
constant within the merged partition" is exactly the clause that stops holding 
there.
   
   ```suggestion
           When enabled, <code>GroupPartitionsExec</code> reports sort orders 
over partition key expressions after coalescing several input partitions into 
one. The merged partitions share the same partition key value, so these orders 
still hold, while orders over other columns are lost by the concatenation. No 
order is reported when the join reduced the partition keys onto a common key 
space (see 
<code>spark.sql.sources.v2.bucketing.allowCompatibleTransforms.enabled</code>), 
because the merged partitions then share only the reduced key. This config 
requires <code>spark.sql.sources.v2.bucketing.enabled</code> to be true.
   ```
   



##########
docs/sql-performance-tuning.md:
##########
@@ -688,6 +688,46 @@ The following SQL properties enable Storage Partition Join 
in different join que
       </td>
       <td>4.0.0</td>
     </tr>
+    <tr>
+      
<td><code>spark.sql.sources.v2.bucketing.partition.filter.enabled</code></td>
+      <td>true</td>
+      <td>
+        When enabled, key groups that cannot produce output for the join type 
are not scanned at all, instead of being filled with empty partitions on the 
side that does not hold them. For example, an inner join only scans the key 
groups present on both sides. This config requires 
<code>spark.sql.sources.v2.bucketing.enabled</code> to be true, together with 
either <code>spark.sql.sources.v2.bucketing.pushPartValues.enabled</code> or 
<code>spark.sql.sources.v2.bucketing.allowKeysSubsetOfPartitionKeys.enabled</code>.
+      </td>
+      <td>4.0.0</td>
+    </tr>
+    <tr>
+      <td><code>spark.sql.sources.v2.bucketing.sorting.enabled</code></td>
+      <td>false</td>
+      <td>
+        When enabled, Spark satisfies a sort on the partition key expressions 
from the partitioning reported by a V2 data source, so no shuffle is added for 
that sort. The parallelism of the sorted output is then whatever the data 
source's partition layout provides, and there is no shuffle stage left for 
adaptive partition coalescing or skew splitting to balance. This config 
requires <code>spark.sql.sources.v2.bucketing.enabled</code> to be true.
+      </td>
+      <td>4.0.0</td>
+    </tr>
+    <tr>
+      
<td><code>spark.sql.sources.v2.bucketing.partitionKeyOrdering.enabled</code></td>
+      <td>true</td>
+      <td>
+        When enabled, Spark derives the output ordering of a V2 scan from its 
partition key expressions, if the source reports a keyed partitioning but no 
explicit ordering. All rows of such a partition share one key value, so the 
partition is trivially sorted by those expressions, and a sort Spark would 
otherwise add becomes unnecessary. This config requires 
<code>spark.sql.sources.v2.bucketing.enabled</code> to be true.
+      </td>
+      <td>4.2.0</td>
+    </tr>
+    <tr>
+      
<td><code>spark.sql.sources.v2.bucketing.preserveKeyOrderingOnCoalesce.enabled</code></td>
+      <td>true</td>
+      <td>
+        When enabled, <code>GroupPartitionsExec</code> reports sort orders 
over partition key expressions after coalescing several input partitions into 
one. The merged partitions share the same partition key value, so these orders 
still hold, while orders over other columns are lost by the concatenation. This 
config requires <code>spark.sql.sources.v2.bucketing.enabled</code> to be true.
+      </td>
+      <td>4.2.0</td>
+    </tr>
+    <tr>
+      
<td><code>spark.sql.sources.v2.bucketing.preserveOrderingOnCoalesce.enabled</code></td>
+      <td>false</td>
+      <td>
+        When enabled, <code>GroupPartitionsExec</code> may preserve the 
child's full ordering through a sorted merge instead of concatenation, rather 
than only the orderings over partition key expressions that 
<code>spark.sql.sources.v2.bucketing.preserveKeyOrderingOnCoalesce.enabled</code>
 preserves. The sorted merge is selected only where a downstream ordering is 
otherwise unsatisfied and the merge is feasible, that is, the node coalesces 
partitions sharing a key, the child reports a non-empty ordering, and its 
subtree is <code>SafeForKWayMerge</code>; otherwise the node concatenates. 
Where it applies, it removes a downstream sort when data is both partitioned 
and sorted, but a sorted merge costs more than concatenation, especially when 
merging many partitions, and it gives up columnar execution for the merged 
plan. This config requires <code>spark.sql.sources.v2.bucketing.enabled</code> 
to be true.

Review Comment:
   **Finding 7.** `SafeForKWayMerge` is a Scala marker trait in `sql/core`. It 
shows up in no plan string, no config and no other doc, so a reader cannot tell 
whether their subtree has it. The condition it stands for is describable: 
`childIsSafeForKWayMerge` 
(`sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/GroupPartitionsExec.scala:396`)
 asks that every operator below the node is one the merge can drive, because 
the merge holds all its input iterators open at once and an operator keeping 
per-partition state in a field would share it across them.
   
   The qualification itself is right and it answers 
[r3995045894](https://github.com/apache/spark/pull/58681#discussion_r3995045894)
 — only the naming is worth changing.
   
   ```suggestion
           When enabled, <code>GroupPartitionsExec</code> may preserve the 
child's full ordering through a sorted merge instead of concatenation, rather 
than only the orderings over partition key expressions that 
<code>spark.sql.sources.v2.bucketing.preserveKeyOrderingOnCoalesce.enabled</code>
 preserves. The sorted merge is selected only where a downstream ordering is 
otherwise unsatisfied and the merge is feasible, that is, the node coalesces 
partitions sharing a key, the child reports a non-empty ordering, and every 
operator below it is one Spark can drive from several partitions at once; 
otherwise the node concatenates. Where it applies, it removes a downstream sort 
when data is both partitioned and sorted, but a sorted merge costs more than 
concatenation, especially when merging many partitions, and it gives up 
columnar execution for the merged plan. This config requires 
<code>spark.sql.sources.v2.bucketing.enabled</code> to be true.
   ```
   



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