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

pan3793 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/master by this push:
     new ae2a97cbb83d [SPARK-58100][SQL] Use IndexedSeq to fix quadratic 
complexity in partition resolution
ae2a97cbb83d is described below

commit ae2a97cbb83d89580e81ec21e36dead02589e500
Author: Daniil Filippov <[email protected]>
AuthorDate: Wed Jul 15 11:41:09 2026 +0800

    [SPARK-58100][SQL] Use IndexedSeq to fix quadratic complexity in partition 
resolution
    
    This PR fixes the issue 
[57220](https://github.com/apache/spark/issues/57220).
    
    ### What changes were proposed in this pull request?
    
    The PR updates `resolveTypeConflicts` to return 
`IndexedSeq[TypedPartValue]` (backed by `Vector`) instead of `Seq`. 
`Vector.apply(index)` is effectively O(1), reducing the overall complexity to 
O(nk).
    
    ### Why are the changes needed?
    
    For now, `resolvedValues` is a `Seq[Seq[TypedPartValue]]`, where each inner 
collection is a linked list. So the `apply` method called there is O(n). This 
is called for every partition and every column, making the total complexity 
O(n²k), where n = number of partitions and k = number of partition columns.
    
    With thousands of daily partitions, this becomes a real issue: a thread 
"indefinitely" spins at the following stack trace:
    
    ```
    PartitioningUtils.resolvePartitions
      - resolvedValues.map(_(index))
        - List.apply(index)
          - List.drop(index)
            - StrictOptimizedLinearSeqOps.loop$2
    ```
    
    ### Does this PR introduce _any_ user-facing change?
    
    No
    
    ### How was this patch tested?
    
    The following benchmark confirms the issue and the fix:
    
    ```scala
    package org.apache.spark.sql.execution.datasources
    
    import org.apache.hadoop.fs.Path
    
    import org.apache.spark.benchmark.{Benchmark, BenchmarkBase}
    import 
org.apache.spark.sql.execution.datasources.PartitioningUtils.{PartitionValues, 
TypedPartValue}
    import org.apache.spark.sql.types.IntegerType
    
    /**
     * Benchmark for [[PartitioningUtils.resolvePartitions]] to guard against 
O(n²) regressions.
     *
     * See https://github.com/apache/spark/issues/57220 for details.
     *
     * To run this benchmark:
     * {{{
     *   1. build/sbt "sql/Test/runMain <this class>"
     *   2. generate result: SPARK_GENERATE_BENCHMARK_FILES=1 build/sbt 
"sql/Test/runMain <this class>"
     *      Results will be written to 
"benchmarks/PartitioningUtilsResolvePartitionsBenchmark-results.txt".
     * }}}
     */
    object PartitioningUtilsResolvePartitionsBenchmark extends BenchmarkBase {
      override def runBenchmarkSuite(mainArgs: Array[String]): Unit = {
        val partitionCounts = Seq(5000, 10000, 15000)
    
        resolvePartitionsBenchmark(partitionCounts, numCols = 1)
        resolvePartitionsBenchmark(partitionCounts, numCols = 3)
      }
    
      private def resolvePartitionsBenchmark(partitionCounts: Seq[Int], 
numCols: Int): Unit = {
        val benchmarkName = s"resolvePartitions with $numCols partition 
column(s)"
        runBenchmark(benchmarkName) {
          val benchmark = new Benchmark(benchmarkName, partitionCounts.last, 
output = output)
    
          partitionCounts.foreach { n =>
            val input = buildInput(n, numCols)
            benchmark.addCase(s"$n partitions") { _ =>
              PartitioningUtils.resolvePartitions(input, caseSensitive = true)
            }
          }
    
          benchmark.run()
        }
      }
    
      private def buildInput(n: Int, numCols: Int): List[(Path, 
PartitionValues)] = {
        val colNames = List.range(0, numCols).map(c => s"col$c")
        List.range(0, n).map { i =>
          val typedValues = (0 until numCols).map(c => TypedPartValue(s"${i + 
c}", IntegerType))
          val partitionedPart = colNames
            .zip(typedValues)
            .map { case (k, v) => s"$k=${v.value}" }
            .mkString("/")
          val path = new 
Path(s"file:/tmp/resolve_partitions_benchmark/$partitionedPart")
          path -> PartitionValues(colNames, typedValues)
        }
      }
    }
    ```
    
    Results before the fix:
    
    ```
    
================================================================================================
    resolvePartitions with 1 partition column(s)
    
================================================================================================
    
    OpenJDK 64-Bit Server VM 21.0.11+10-LTS on Mac OS X 26.5.2
    Apple M4 Pro
    resolvePartitions with 1 partition column(s):  Best Time(ms)   Avg Time(ms) 
  Stdev(ms)    Rate(M/s)   Per Row(ns)   Relative
    
----------------------------------------------------------------------------------------------------------------------------
    5000 partitions                                          19             20  
         1          0.8        1283.7       1.0X
    10000 partitions                                         79             81  
         2          0.2        5282.2       0.2X
    15000 partitions                                        179            187  
         6          0.1       11952.9       0.1X
    
    
================================================================================================
    resolvePartitions with 3 partition column(s)
    
================================================================================================
    
    OpenJDK 64-Bit Server VM 21.0.11+10-LTS on Mac OS X 26.5.2
    Apple M4 Pro
    resolvePartitions with 3 partition column(s):  Best Time(ms)   Avg Time(ms) 
  Stdev(ms)    Rate(M/s)   Per Row(ns)   Relative
    
----------------------------------------------------------------------------------------------------------------------------
    5000 partitions                                          60             64  
         3          0.3        3994.3       1.0X
    10000 partitions                                        254            258  
         3          0.1       16948.2       0.2X
    15000 partitions                                        524            529  
         5          0.0       34925.0       0.1X
    ```
    
    Results after the fix:
    ```
    
================================================================================================
    resolvePartitions with 1 partition column(s)
    
================================================================================================
    
    OpenJDK 64-Bit Server VM 21.0.11+10-LTS on Mac OS X 26.5.2
    Apple M4 Pro
    resolvePartitions with 1 partition column(s):  Best Time(ms)   Avg Time(ms) 
  Stdev(ms)    Rate(M/s)   Per Row(ns)   Relative
    
----------------------------------------------------------------------------------------------------------------------------
    5000 partitions                                           0              0  
         0         70.0          14.3       1.0X
    10000 partitions                                          0              0  
         0         34.5          29.0       0.5X
    15000 partitions                                          1              1  
         0         23.5          42.5       0.3X
    
    
================================================================================================
    resolvePartitions with 3 partition column(s)
    
================================================================================================
    
    OpenJDK 64-Bit Server VM 21.0.11+10-LTS on Mac OS X 26.5.2
    Apple M4 Pro
    resolvePartitions with 3 partition column(s):  Best Time(ms)   Avg Time(ms) 
  Stdev(ms)    Rate(M/s)   Per Row(ns)   Relative
    
----------------------------------------------------------------------------------------------------------------------------
    5000 partitions                                           0              1  
         0         34.2          29.3       1.0X
    10000 partitions                                          1              1  
         0         16.0          62.5       0.5X
    15000 partitions                                          1              2  
         0         10.1          99.4       0.3X
    ```
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    No
    
    Closes #57243 from flipp5b/fix-partition-resolution.
    
    Lead-authored-by: Daniil Filippov <[email protected]>
    Co-authored-by: Daniil Filippov <[email protected]>
    Signed-off-by: Cheng Pan <[email protected]>
---
 .../apache/spark/sql/execution/datasources/PartitioningUtils.scala   | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/PartitioningUtils.scala
 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/PartitioningUtils.scala
index 83a094c250d6..53dac003754c 100644
--- 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/PartitioningUtils.scala
+++ 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/PartitioningUtils.scala
@@ -648,11 +648,12 @@ object PartitioningUtils extends SQLConfHelper {
    * Given a collection of [[Literal]]s, resolves possible type conflicts by
    * [[findWiderTypeForPartitionColumn]].
    */
-  private def resolveTypeConflicts(typedValues: Seq[TypedPartValue]): 
Seq[TypedPartValue] = {
+  private def resolveTypeConflicts(typedValues: Seq[TypedPartValue]): 
IndexedSeq[TypedPartValue] = {
     val dataTypes = typedValues.map(_.dataType)
     val desiredType = dataTypes.reduce(findWiderTypeForPartitionColumn)
 
-    typedValues.map(tv => tv.copy(dataType = desiredType))
+    // IndexedSeq guarantees O(1) apply at the call site; a List would make 
the loop O(n^2).
+    typedValues.view.map(tv => tv.copy(dataType = desiredType)).toIndexedSeq
   }
 
   /**


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

Reply via email to