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

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


The following commit(s) were added to refs/heads/master by this push:
     new ec2831ec932d perf(spark): check file existence in parallel in MOR 
incremental fallback scan (#20211)
ec2831ec932d is described below

commit ec2831ec932d3da193b15fee0164724913d33da5
Author: Prashant Wason <[email protected]>
AuthorDate: Tue Oct 6 16:50:07 2026 -0700

    perf(spark): check file existence in parallel in MOR incremental fallback 
scan (#20211)
---
 .../scala/org/apache/hudi/HoodieBaseRelation.scala | 29 ++++++++++++++++++++++
 .../org/apache/hudi/IncrementalRelationV1.scala    | 15 +++--------
 .../org/apache/hudi/IncrementalRelationV2.scala    | 15 +++--------
 .../hudi/MergeOnReadIncrementalRelationV1.scala    | 14 +++++++++--
 .../hudi/MergeOnReadIncrementalRelationV2.scala    | 14 +++++++++--
 5 files changed, 59 insertions(+), 28 deletions(-)

diff --git 
a/hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/hudi/HoodieBaseRelation.scala
 
b/hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/hudi/HoodieBaseRelation.scala
index 9d804622e6fd..e7d389d666c3 100644
--- 
a/hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/hudi/HoodieBaseRelation.scala
+++ 
b/hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/hudi/HoodieBaseRelation.scala
@@ -796,4 +796,33 @@ object HoodieBaseRelation extends SparkAdapterSupport {
       
DataSourceReadOptions.SCHEMA_EVOLUTION_ENABLED.defaultValue.toString).toBoolean 
||
       ProvidesHoodieConfig.isSchemaEvolutionEnabled(sparkSession)
   }
+
+  /**
+   * Checks in parallel whether any of the given file paths are missing from 
storage.
+   * Uses sparkContext.parallelize to distribute the exists() checks across 
executors,
+   * avoiding sequential driver-side RPCs that can take minutes on cloud 
storage for
+   * large file counts.
+   */
+  def checkIfAnyFilesMissing(sqlContext: SQLContext,
+                             metaClient: HoodieTableMetaClient,
+                             filePaths: Iterable[String]): Boolean = {
+    val sc = sqlContext.sparkContext
+    val allFilesToCheck = filePaths.toSeq
+    // Use the meta client's storage conf so that session-scoped fs.* settings 
reach the executors
+    val storageConf = metaClient.getStorageConf
+    val localBasePathStr = metaClient.getBasePath.toString
+    val numPartitions = Math.max(1, Math.min(allFilesToCheck.size, 
sc.defaultParallelism))
+    val previousJobDescription = sc.getLocalProperty("spark.job.description")
+    try {
+      sc.setJobDescription(s"Checking existence of ${allFilesToCheck.size} 
files for incremental query")
+      // Instantiate the storage once per Spark partition and stop at the 
first missing file
+      sc.parallelize(allFilesToCheck, numPartitions)
+        .mapPartitions(paths => {
+          val storage = HoodieStorageUtils.getStorage(localBasePathStr, 
storageConf)
+          Iterator.single(paths.forall(path => storage.exists(new 
StoragePath(path))))
+        }).collect().exists(allExist => !allExist)
+    } finally {
+      sc.setJobDescription(previousJobDescription)
+    }
+  }
 }
diff --git 
a/hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/hudi/IncrementalRelationV1.scala
 
b/hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/hudi/IncrementalRelationV1.scala
index 52b80cd4ac3e..f2e4511e2783 100644
--- 
a/hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/hudi/IncrementalRelationV1.scala
+++ 
b/hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/hudi/IncrementalRelationV1.scala
@@ -32,11 +32,8 @@ import org.apache.hudi.common.table.timeline.{HoodieInstant, 
HoodieTimeline}
 import 
org.apache.hudi.common.table.timeline.TimelineUtils.{handleHollowCommitIfNeeded,
 HollowCommitHandling}
 import 
org.apache.hudi.common.table.timeline.TimelineUtils.HollowCommitHandling.USE_TRANSITION_TIME
 import org.apache.hudi.common.util.{HoodieTimer, InternalSchemaCache}
-import org.apache.hudi.common.util.HoodieStorageUtils
 import org.apache.hudi.config.HoodieWriteConfig
 import org.apache.hudi.exception.{HoodieException, 
HoodieIncrementalPathNotFoundException}
-import org.apache.hudi.hadoop.fs.HadoopFSUtils
-import org.apache.hudi.storage.StoragePath
 import org.apache.hudi.table.HoodieSparkTable
 import org.apache.hudi.util.IncrementalRelationUtil
 
@@ -255,18 +252,12 @@ class IncrementalRelationV1(val sqlContext: SQLContext,
           if (fallbackToFullTableScan) {
             val timer = HoodieTimer.start
 
-            val allFilesToCheck = filteredMetaBootstrapFullPaths ++ 
filteredRegularFullPaths
-            val storageConf = 
HadoopFSUtils.getStorageConfWithCopy(sqlContext.sparkContext.hadoopConfiguration)
-            val localBasePathStr = basePath.toString
-            val firstNotFoundPath = 
sqlContext.sparkContext.parallelize(allFilesToCheck.toSeq, allFilesToCheck.size)
-              .map(path => {
-                val storage = HoodieStorageUtils.getStorage(localBasePathStr, 
storageConf)
-                storage.exists(new StoragePath(path))
-              }).collect().find(v => !v)
+            val missingFileFound = HoodieBaseRelation.checkIfAnyFilesMissing(
+              sqlContext, metaClient, filteredMetaBootstrapFullPaths ++ 
filteredRegularFullPaths)
             val timeTaken = timer.endTimer()
             log.info("Checking if paths exists took " + timeTaken + "ms")
 
-            if (firstNotFoundPath.isDefined) {
+            if (missingFileFound) {
               doFullTableScan = true
               log.info("Falling back to full table scan as some files cannot 
be found.")
             }
diff --git 
a/hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/hudi/IncrementalRelationV2.scala
 
b/hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/hudi/IncrementalRelationV2.scala
index 49c94c0f8532..48e5ea81094e 100644
--- 
a/hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/hudi/IncrementalRelationV2.scala
+++ 
b/hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/hudi/IncrementalRelationV2.scala
@@ -30,10 +30,7 @@ import 
org.apache.hudi.common.table.log.InstantRange.RangeType
 import org.apache.hudi.common.table.read.IncrementalQueryAnalyzer
 import org.apache.hudi.common.table.timeline.{HoodieInstant, HoodieTimeline}
 import org.apache.hudi.common.util.{HoodieTimer, InternalSchemaCache}
-import org.apache.hudi.common.util.HoodieStorageUtils
 import org.apache.hudi.exception.{HoodieException, 
HoodieIncrementalPathNotFoundException}
-import org.apache.hudi.hadoop.fs.HadoopFSUtils
-import org.apache.hudi.storage.StoragePath
 import org.apache.hudi.util.IncrementalRelationUtil
 
 import org.apache.avro.Schema
@@ -242,18 +239,12 @@ class IncrementalRelationV2(val sqlContext: SQLContext,
           if (fallbackToFullTableScan) {
             val timer = HoodieTimer.start
 
-            val allFilesToCheck = filteredMetaBootstrapFullPaths ++ 
filteredRegularFullPaths
-            val storageConf = 
HadoopFSUtils.getStorageConfWithCopy(sqlContext.sparkContext.hadoopConfiguration)
-            val localBasePathStr = basePath.toString
-            val firstNotFoundPath = 
sqlContext.sparkContext.parallelize(allFilesToCheck.toSeq, allFilesToCheck.size)
-              .map(path => {
-                val storage = HoodieStorageUtils.getStorage(localBasePathStr, 
storageConf)
-                storage.exists(new StoragePath(path))
-              }).collect().find(v => !v)
+            val missingFileFound = HoodieBaseRelation.checkIfAnyFilesMissing(
+              sqlContext, metaClient, filteredMetaBootstrapFullPaths ++ 
filteredRegularFullPaths)
             val timeTaken = timer.endTimer()
             log.info("Checking if paths exists took " + timeTaken + "ms")
 
-            if (firstNotFoundPath.isDefined) {
+            if (missingFileFound) {
               doFullTableScan = true
               log.info("Falling back to full table scan as some files cannot 
be found.")
             }
diff --git 
a/hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/hudi/MergeOnReadIncrementalRelationV1.scala
 
b/hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/hudi/MergeOnReadIncrementalRelationV1.scala
index 46444a719a38..0cc4be8df532 100644
--- 
a/hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/hudi/MergeOnReadIncrementalRelationV1.scala
+++ 
b/hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/hudi/MergeOnReadIncrementalRelationV1.scala
@@ -198,8 +198,18 @@ trait HoodieIncrementalRelationV1Trait extends 
HoodieBaseRelation {
   //   3. there are files in metadata be deleted
   protected lazy val fullTableScan: Boolean = {
     val fallbackToFullTableScan = 
optParams.getOrElse(DataSourceReadOptions.INCREMENTAL_FALLBACK_TO_FULL_TABLE_SCAN.key,
 "false").toBoolean
-    fallbackToFullTableScan && (startInstantArchived || endInstantArchived
-      || affectedFilesInCommits.asScala.exists(fileStatus => 
!metaClient.getStorage.exists(fileStatus.getPath)))
+    if (!fallbackToFullTableScan) {
+      false
+    } else if (startInstantArchived || endInstantArchived) {
+      true
+    } else {
+      val missingFileFound = HoodieBaseRelation.checkIfAnyFilesMissing(
+        sqlContext, metaClient, 
affectedFilesInCommits.asScala.map(_.getPath.toString))
+      if (missingFileFound) {
+        logInfo("Falling back to full table scan as some files cannot be 
found.")
+      }
+      missingFileFound
+    }
   }
 
   protected lazy val includedCommits: immutable.Seq[HoodieInstant] = {
diff --git 
a/hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/hudi/MergeOnReadIncrementalRelationV2.scala
 
b/hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/hudi/MergeOnReadIncrementalRelationV2.scala
index 169ea5936c44..fb4ef6c38342 100644
--- 
a/hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/hudi/MergeOnReadIncrementalRelationV2.scala
+++ 
b/hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/hudi/MergeOnReadIncrementalRelationV2.scala
@@ -199,8 +199,18 @@ trait HoodieIncrementalRelationV2Trait extends 
HoodieBaseRelation {
     val fallbackToFullTableScan = 
optParams.getOrElse(DataSourceReadOptions.INCREMENTAL_FALLBACK_TO_FULL_TABLE_SCAN.key,
       
DataSourceReadOptions.INCREMENTAL_FALLBACK_TO_FULL_TABLE_SCAN.defaultValue).toBoolean
 
-    fallbackToFullTableScan && (startInstantArchived
-      || affectedFilesInCommits.asScala.exists(fileStatus => 
!metaClient.getStorage.exists(fileStatus.getPath)))
+    if (!fallbackToFullTableScan) {
+      false
+    } else if (startInstantArchived) {
+      true
+    } else {
+      val missingFileFound = HoodieBaseRelation.checkIfAnyFilesMissing(
+        sqlContext, metaClient, 
affectedFilesInCommits.asScala.map(_.getPath.toString))
+      if (missingFileFound) {
+        logInfo("Falling back to full table scan as some files cannot be 
found.")
+      }
+      missingFileFound
+    }
   }
 
   protected val rangeType: RangeType

Reply via email to