You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hudi.apache.org by "Davis Zhang (Jira)" <ji...@apache.org> on 2025/09/04 17:30:00 UTC

[jira] [Created] (HUDI-9787) HoodieFileIndex file listing perf issue

Davis Zhang created HUDI-9787:
---------------------------------

             Summary: HoodieFileIndex file listing perf issue
                 Key: HUDI-9787
                 URL: https://issues.apache.org/jira/browse/HUDI-9787
             Project: Apache Hudi
          Issue Type: Bug
          Components: index
            Reporter: Davis Zhang
             Fix For: 1.2.0
         Attachments: image-2025-09-04-10-23-53-374.png

org.apache.hudi.HoodieFileIndex#filterFileSlices

 
{code:java}
val prunedPartitionsAndFilteredFileSlices = prunedPartitionsAndFileSlices.map {
  case (partitionOpt, fileSlices) =>
    // Filter in candidate files based on the col-stats or record level index lookup
    val candidateFileSlices: Seq[FileSlice] = {
      fileSlices.filter(fs => {
        val fileSliceFiles = fs.getLogFiles.map[String](JFunction.toJavaFunction[HoodieLogFile, String](lf => lf.getPath.getName))
          .collect(Collectors.toSet[String])
        val baseFileStatusOpt = getBaseFileStatus(Option.apply(fs.getBaseFile.orElse(null)))
        baseFileStatusOpt.exists(f => fileSliceFiles.add(f.getPath.getName))
        // NOTE: This predicate is true when {@code Option} is empty         candidateFilesNamesOpt.forall(files => files.exists(elem => fileSliceFiles.contains(elem)))
      })
    }

    totalFileSliceSize += fileSlices.size
    candidateFileSliceSize += candidateFileSlices.size
    (partitionOpt, candidateFileSlices)
}

val skippingRatio =
  if (!areAllFileSlicesCached) -1
  else if (getAllFiles().nonEmpty && totalFileSliceSize > 0)
    (totalFileSliceSize - candidateFileSliceSize) / totalFileSliceSize.toDouble
  else 0

logInfo(s"Total file slices: $totalFileSliceSize; " +
  s"candidate file slices after data skipping: $candidateFileSliceSize; " +
  s"skipping percentage $skippingRatio")
 {code}
This is doing a nested for loop like processing between 2 scala list candidateFilesNamesOpt and prunedPartitionsAndFileSlices to figure out the overlap.

 

In production use case (0.14.1 based branch), we saw that 
!image-2025-09-04-10-23-53-374.png!

It means both list are of size 55.8k. The total iteration the above for loop is 55.8k*55.8k which is not trivial.

We noticed on a cluster with 32 GB memory with no CPU throttling (this logic is anyway single threaded), in this set up the hoodie file index choose to use 
COLUMN_STATS_INDEX_PROCESSING_MODE_IN_MEMORY which means everything is on driver.
 
That nested for loop took ~10 min to complete. During this time, no spark tasks are running because this is driver only logic and there is no spark tasks available for execution. The performance impact could be more profound if the auto scaling logic of user's spark application choose to toss away executors and later scale them back. This churning would make the e2e spark application perf worse.
 
 
 
 



--
This message was sent by Atlassian Jira
(v8.20.10#820010)