hudi-agent commented on code in PR #19914:
URL: https://github.com/apache/hudi/pull/19914#discussion_r3992078084


##########
hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/HoodieFileGroupReaderBasedFileFormat.scala:
##########
@@ -366,6 +387,12 @@ class HoodieFileGroupReaderBasedFileFormat(tablePath: 
String,
             .getSparkPartitionedFileUtils.getPathFromPartitionedFile(file))
           fileSliceMapping.getSlice(fileGroupName) match {
             case Some(fileSlice) if !isCount && (requiredSchema.nonEmpty || 
fileSlice.getLogFiles.findAny().isPresent) =>
+              if (!fileSlice.getLogFiles.findAny().isPresent && !isBootstrap 
&& broadcastStockReader.isDefined) {

Review Comment:
   πŸ€– Going straight to stock `ParquetFileFormat` drops everything Hudi's own 
parquet reader layers on top: `internalSchemaOpt` (schema-on-read renames/type 
changes), `tableSchemaAsMessageType`-driven type promotion (int→long etc. for 
files written with an older schema), `mandatoryFields` partition columns that 
must be read from the file rather than the path (timestamp/custom keygen, 
precombine-as-partition β€” and `vectorTypes` still declares those as 
`OnHeapColumnVector` while the stock reader emits `ConstantColumnVector`, so 
codegen would CCE), and the VECTOR BinaryType rewrite. Have you considered 
routing this case through `readBaseFile(file, baseFileReader.value, ...)` 
instead? That already skips `HoodieFileGroupReader` while keeping the Hudi 
reader semantics β€” it'd be useful to know how much of the benchmark win is from 
bypassing the FG reader vs. bypassing the Hudi parquet reader.
   
   <sub><i>⚠️ AI-generated; verify before applying. React πŸ‘/πŸ‘Ž to flag 
quality.</i></sub>



##########
hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/HoodieFileGroupReaderBasedFileFormat.scala:
##########
@@ -218,7 +218,11 @@ class HoodieFileGroupReaderBasedFileFormat(tablePath: 
String,
       } else {
         throw new HoodieNotSupportedException("Unsupported file format: " + 
hoodieFileFormat)
       }
-      supportVectorizedRead = !isIncremental && !isBootstrap && supportBatch
+      // MOR incremental embeds file slices that may contain log files 
requiring row-level
+      // merging, so vectorized reading must be disabled. All other 
combinations (COW snapshot,
+      // COW incremental, MOR snapshot) either have no log merging or handle 
it via a separate
+      // non-vectorized fileGroupBaseFileReader while the base file reader 
stays vectorized.
+      supportVectorizedRead = !(isMOR && isIncremental) && !isBootstrap && 
supportBatch

Review Comment:
   πŸ€– One thing to double-check: the incremental `requiredFilters` 
(`_hoodie_commit_time IN (...)`) only get a plan-level `Filter` via 
`HoodieAnalysis.maybeApplyForNewFileFormat`, i.e. when 
`HoodieSparkSessionExtension` is registered. Without the extension the 
row-based `ParquetRecordReader` + `FilterCompat` was doing record-level 
filtering, but the vectorized reader only prunes row groups/pages β€” so a 
rewritten COW base file containing rows from several commits could leak 
out-of-range rows. Is DataFrame incremental read without the extension still a 
supported path? @yihua might be able to confirm.
   
   <sub><i>⚠️ AI-generated; verify before applying. React πŸ‘/πŸ‘Ž to flag 
quality.</i></sub>



##########
hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/HoodieFileGroupReaderBasedFileFormat.scala:
##########
@@ -347,6 +353,21 @@ class HoodieFileGroupReaderBasedFileFormat(tablePath: 
String,
     }
 
     val broadcastedStorageConf = spark.sparkContext.broadcast(new 
SerializableConfiguration(augmentedStorageConf.unwrap()))
+
+    // Build stock Spark ParquetFileFormat reader for COW base-file-only 
bypass.
+    // When a file slice has only a base file (no log files), this avoids the 
overhead of
+    // HoodieFileGroupReader (schema handler, record merger, row copy/seal) by 
using Spark's
+    // native parquet reader directly.
+    val stockParquetReader: Option[PartitionedFile => Iterator[InternalRow]] = 
if (!isMOR && !isBootstrap) {

Review Comment:
   πŸ€– This gate doesn't check `hoodieFileFormat`, so a COW table with ORC (or 
Lance/Vortex/multi-format) base files will be handed to `ParquetFileFormat` and 
fail. Also note `HoodieCopyOnWriteSnapshotHadoopFsRelationFactory` builds its 
`HoodieFileIndex` with `shouldEmbedFileSlices = true`, so 
`file.partitionValues` is a `HoodiePartitionFileSliceMapping` for COW snapshot 
reads as well β€” this bypass ends up on every COW snapshot scan, not just 
incremental. Could you scope it to `hoodieFileFormat == PARQUET && 
!isMultipleBaseFileFormatsEnabled` at minimum, and update the description to 
reflect the snapshot impact?
   
   <sub><i>⚠️ AI-generated; verify before applying. React πŸ‘/πŸ‘Ž to flag 
quality.</i></sub>



##########
hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/HoodieFileGroupReaderBasedFileFormat.scala:
##########
@@ -273,7 +277,9 @@ class HoodieFileGroupReaderBasedFileFormat(tablePath: 
String,
     // For overly large single files, we can use multiple concurrent tasks to 
read them, thereby reducing the overall job reading time consumption
     val superSplitable = super.isSplitable(sparkSession, options, path)
     val isLance = hoodieFileFormat == HoodieFileFormat.LANCE
-    val splitable = !isMOR && !isIncremental && !isBootstrap && !isLance && 
superSplitable
+    // COW incremental reads have no log files to merge, so file splitting is 
safe.
+    // Only MOR and bootstrap reads need to disable splitting.
+    val splitable = !isMOR && !isBootstrap && !isLance && superSplitable

Review Comment:
   πŸ€– With splitting now enabled for incremental, the `HoodieFileGroupReader` 
branch below still does `.withStart(file.start).withLength(baseFileLength)` 
(full file size, not `file.length`), so if a split ever lands there (e.g. once 
the bypass is scoped to Parquet and an ORC/Lance COW incremental read falls 
through) the reader range would be `[start, start + fileSize)` and overlap the 
next split. Would it make sense to pass `file.length` there, or keep 
incremental non-splittable unless the bypass is taken?
   
   <sub><i>⚠️ AI-generated; verify before applying. React πŸ‘/πŸ‘Ž to flag 
quality.</i></sub>



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

Reply via email to