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

nsivabalan 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 5178a3bedae8 fix(metadata-table): add config to skip zero-size data 
files on MDT initialization (#18611)
5178a3bedae8 is described below

commit 5178a3bedae848e1322842b1db5f1b03ee439b83
Author: Nada <[email protected]>
AuthorDate: Sun Jul 19 14:11:08 2026 -0400

    fix(metadata-table): add config to skip zero-size data files on MDT 
initialization (#18611)
    
    Adds an opt-in config `hoodie.metadata.skip.zero.size.files.on.initialize`
      (default false, advanced) that skips zero-size data files while listing 
the
      data table during MDT initialization. Prevents init failures on tables
      carrying leftover zero-byte files, and emits 
`skipped_zero_size_files_on_initialize`
      with the skipped count.
    
      Skip is scoped to the initialize path only — the restore-sync caller of
      `listAllPartitionsFromFilesystem` passes `skipZeroSizeFiles=false`
      unconditionally, so files already tracked in MDT are not spuriously 
deleted.
      Existing `DirectoryInfo` constructors delegate with `false` to preserve
      current behavior.
---
 .../metadata/HoodieBackedTableMetadataWriter.java  | 16 +++++++++----
 .../functional/TestHoodieMetadataBootstrap.java    | 28 ++++++++++++++++++++++
 .../hudi/common/config/HoodieMetadataConfig.java   | 17 +++++++++++++
 .../apache/hudi/metadata/model/DirectoryInfo.java  | 17 +++++++++++--
 4 files changed, 72 insertions(+), 6 deletions(-)

diff --git 
a/hudi-client/hudi-client-common/src/main/java/org/apache/hudi/metadata/HoodieBackedTableMetadataWriter.java
 
b/hudi-client/hudi-client-common/src/main/java/org/apache/hudi/metadata/HoodieBackedTableMetadataWriter.java
index 3e76d2d2ef1d..d16f894d3ce5 100644
--- 
a/hudi-client/hudi-client-common/src/main/java/org/apache/hudi/metadata/HoodieBackedTableMetadataWriter.java
+++ 
b/hudi-client/hudi-client-common/src/main/java/org/apache/hudi/metadata/HoodieBackedTableMetadataWriter.java
@@ -404,7 +404,8 @@ public abstract class HoodieBackedTableMetadataWriter<I, O> 
implements HoodieTab
     } else {
       // if auto initialization is enabled, then we need to list all 
partitions from the file system
       if (dataWriteConfig.getMetadataConfig().shouldAutoInitialize()) {
-        partitionInfoList = 
listAllPartitionsFromFilesystem(dataTableInstantTime, pendingDataInstants);
+        partitionInfoList = 
listAllPartitionsFromFilesystem(dataTableInstantTime, pendingDataInstants,
+            
dataWriteConfig.getMetadataConfig().shouldSkipZeroSizeFilesOnInitialize());
       } else {
         // if auto initialization is disabled, we can return an empty list
         partitionInfoList = Collections.emptyList();
@@ -581,9 +582,12 @@ public abstract class HoodieBackedTableMetadataWriter<I, 
O> implements HoodieTab
    *
    * @param initializationTime  Files which have a timestamp after this are 
neglected
    * @param pendingDataInstants Pending instants on data set
+   * @param skipZeroSizeFiles   Whether zero-size data files should be skipped 
during listing. This should only be
+   *                            enabled on the initialize path; other callers 
(e.g. restore) must pass false so that
+   *                            files already tracked in the metadata table 
are not spuriously deleted.
    * @return List consisting of {@code DirectoryInfo} for each partition found.
    */
-  private List<DirectoryInfo> listAllPartitionsFromFilesystem(String 
initializationTime, Set<String> pendingDataInstants) {
+  private List<DirectoryInfo> listAllPartitionsFromFilesystem(String 
initializationTime, Set<String> pendingDataInstants, boolean skipZeroSizeFiles) 
{
     if (dataMetaClient.getActiveTimeline().countInstants() == 0) {
       return Collections.emptyList();
     }
@@ -595,6 +599,7 @@ public abstract class HoodieBackedTableMetadataWriter<I, O> 
implements HoodieTab
     StorageConfiguration<?> storageConf = dataMetaClient.getStorageConf();
     final String dirFilterRegex = 
dataWriteConfig.getMetadataConfig().getDirectoryFilterRegex();
     StoragePath storageBasePath = dataMetaClient.getBasePath();
+    long totalZeroSizeFiles = 0;
 
     while (!pathsToList.isEmpty()) {
       // In each round we will list a section of directories
@@ -608,12 +613,13 @@ public abstract class HoodieBackedTableMetadataWriter<I, 
O> implements HoodieTab
       List<DirectoryInfo> processedDirectories = 
engineContext.map(pathsToProcess, path -> {
         HoodieStorage storage = HoodieStorageUtils.getStorage(path, 
storageConf);
         String relativeDirPath = 
FSUtils.getRelativePartitionPath(storageBasePath, path);
-        return new DirectoryInfo(relativeDirPath, 
storage.listDirectEntries(path), initializationTime, pendingDataInstants);
+        return new DirectoryInfo(relativeDirPath, 
storage.listDirectEntries(path), initializationTime, pendingDataInstants, true, 
skipZeroSizeFiles);
       }, numDirsToList);
 
       // If the listing reveals a directory, add it to queue. If the listing 
reveals a hoodie partition, add it to
       // the results.
       for (DirectoryInfo dirInfo : processedDirectories) {
+        totalZeroSizeFiles += dirInfo.getZeroSizeFileCount();
         if (!dirFilterRegex.isEmpty()) {
           final String relativePath = dirInfo.getRelativePath();
           if (!relativePath.isEmpty() && relativePath.matches(dirFilterRegex)) 
{
@@ -632,6 +638,8 @@ public abstract class HoodieBackedTableMetadataWriter<I, O> 
implements HoodieTab
       }
     }
 
+    final long zeroSizeCount = totalZeroSizeFiles;
+    metrics.ifPresent(m -> 
m.incrementMetric("skipped_zero_size_files_on_initialize", zeroSizeCount));
     return partitionsToBootstrap;
   }
 
@@ -1185,7 +1193,7 @@ public abstract class HoodieBackedTableMetadataWriter<I, 
O> implements HoodieTab
 
     // Restore requires the existing pipelines to be shutdown. So we can 
safely scan the dataset to find the current
     // list of files in the filesystem.
-    List<DirectoryInfo> dirInfoList = 
listAllPartitionsFromFilesystem(instantTime, Collections.emptySet());
+    List<DirectoryInfo> dirInfoList = 
listAllPartitionsFromFilesystem(instantTime, Collections.emptySet(), false);
     Map<String, DirectoryInfo> dirInfoMap = 
dirInfoList.stream().collect(Collectors.toMap(DirectoryInfo::getRelativePath, 
Function.identity()));
     dirInfoList.clear();
 
diff --git 
a/hudi-client/hudi-spark-client/src/test/java/org/apache/hudi/client/functional/TestHoodieMetadataBootstrap.java
 
b/hudi-client/hudi-spark-client/src/test/java/org/apache/hudi/client/functional/TestHoodieMetadataBootstrap.java
index 28b325889072..742e14874377 100644
--- 
a/hudi-client/hudi-spark-client/src/test/java/org/apache/hudi/client/functional/TestHoodieMetadataBootstrap.java
+++ 
b/hudi-client/hudi-spark-client/src/test/java/org/apache/hudi/client/functional/TestHoodieMetadataBootstrap.java
@@ -108,6 +108,34 @@ public class TestHoodieMetadataBootstrap extends 
TestHoodieMetadataBase {
     validateMetadata(testTable);
   }
 
+  @Test
+  public void testMetadataSkipsZeroSizeFilesOnInitialize() throws Exception {
+    HoodieTableType tableType = COPY_ON_WRITE;
+    init(tableType, false);
+    doPreBootstrapWriteOperation(testTable, INSERT, "0000001");
+    doPreBootstrapWriteOperation(testTable, "0000002");
+    // Add a zero-size base file — bootstrap should skip it without failing.
+    String fileName = UUID.randomUUID().toString();
+    Path zeroSizeFilePath = FileCreateUtilsLegacy.getBaseFilePath(basePath, 
"p1", "0000003", fileName);
+    FileCreateUtilsLegacy.createBaseFile(basePath, "p1", "0000003", fileName, 
0);
+
+    writeConfig = getWriteConfigBuilder(true, true, false)
+        .withMetadataConfig(HoodieMetadataConfig.newBuilder()
+            .enable(true)
+            .withSkipZeroSizeFilesOnInitialize(true)
+            .build())
+        .build();
+    initWriteConfigAndMetatableWriter(writeConfig, true);
+    syncTableMetadata(writeConfig);
+
+    // Delete the zero-size file before validation — it was skipped in MDT and 
must not
+    // exist on disk for the filesystem-vs-MDT consistency check to pass.
+    Files.delete(zeroSizeFilePath);
+    validateMetadata(testTable);
+    doWriteInsertAndUpsert(testTable);
+    validateMetadata(testTable);
+  }
+
   @ParameterizedTest
   @EnumSource(HoodieTableType.class)
   public void testMetadataBootstrapInsertUpsertRollback(HoodieTableType 
tableType) throws Exception {
diff --git 
a/hudi-common/src/main/java/org/apache/hudi/common/config/HoodieMetadataConfig.java
 
b/hudi-common/src/main/java/org/apache/hudi/common/config/HoodieMetadataConfig.java
index ed69f556b03e..5eaca878e5fa 100644
--- 
a/hudi-common/src/main/java/org/apache/hudi/common/config/HoodieMetadataConfig.java
+++ 
b/hudi-common/src/main/java/org/apache/hudi/common/config/HoodieMetadataConfig.java
@@ -195,6 +195,14 @@ public final class HoodieMetadataConfig extends 
HoodieConfig {
       .sinceVersion("0.7.0")
       .withDocumentation("Directories matching this regex, will be filtered 
out when initializing metadata table from lake storage for the first time.");
 
+  public static final ConfigProperty<Boolean> 
SKIP_ZERO_SIZE_FILES_ON_INITIALIZE = ConfigProperty
+      .key(METADATA_PREFIX + ".skip.zero.size.files.on.initialize")
+      .defaultValue(false)
+      .markAdvanced()
+      .sinceVersion("1.2.0")
+      .withDocumentation("When enabled, zero-size data files encountered while 
listing the data table during "
+          + "metadata table initialization are skipped instead of being 
recorded in the metadata table.");
+
   public static final ConfigProperty<Integer> FILE_LISTING_PARALLELISM_VALUE = 
ConfigProperty
       .key("hoodie.file.listing.parallelism")
       .defaultValue(200)
@@ -812,6 +820,10 @@ public final class HoodieMetadataConfig extends 
HoodieConfig {
     return getString(DIR_FILTER_REGEX);
   }
 
+  public boolean shouldSkipZeroSizeFilesOnInitialize() {
+    return getBoolean(SKIP_ZERO_SIZE_FILES_ON_INITIALIZE);
+  }
+
   public boolean shouldIgnoreSpuriousDeletes() {
     return getBoolean(IGNORE_SPURIOUS_DELETES);
   }
@@ -1194,6 +1206,11 @@ public final class HoodieMetadataConfig extends 
HoodieConfig {
       return this;
     }
 
+    public Builder withSkipZeroSizeFilesOnInitialize(boolean 
skipZeroSizeFiles) {
+      metadataConfig.setValue(SKIP_ZERO_SIZE_FILES_ON_INITIALIZE, 
String.valueOf(skipZeroSizeFiles));
+      return this;
+    }
+
     public Builder ignoreSpuriousDeletes(boolean 
validateMetadataPayloadConsistency) {
       metadataConfig.setValue(IGNORE_SPURIOUS_DELETES, 
String.valueOf(validateMetadataPayloadConsistency));
       return this;
diff --git 
a/hudi-common/src/main/java/org/apache/hudi/metadata/model/DirectoryInfo.java 
b/hudi-common/src/main/java/org/apache/hudi/metadata/model/DirectoryInfo.java
index 16d224d81208..3109adebe2b9 100644
--- 
a/hudi-common/src/main/java/org/apache/hudi/metadata/model/DirectoryInfo.java
+++ 
b/hudi-common/src/main/java/org/apache/hudi/metadata/model/DirectoryInfo.java
@@ -28,6 +28,7 @@ import org.apache.hudi.storage.StoragePath;
 import org.apache.hudi.storage.StoragePathInfo;
 
 import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
 
 import java.io.Serializable;
 import java.util.ArrayList;
@@ -48,6 +49,7 @@ import static 
org.apache.hudi.common.table.timeline.InstantComparison.compareTim
  * a very large number of files are present in the dataset being initialized.
  */
 @Getter
+@Slf4j
 public class DirectoryInfo implements Serializable {
   private static final long serialVersionUID = 1L;
 
@@ -59,9 +61,10 @@ public class DirectoryInfo implements Serializable {
   private final List<StoragePath> subDirectories = new ArrayList<>();
   // Is this a hoodie partition
   private boolean isHoodiePartition = false;
+  private int zeroSizeFileCount = 0;
 
   public DirectoryInfo(String relativePath, List<StoragePathInfo> pathInfos, 
String maxInstantTime, Set<String> pendingDataInstants) {
-    this(relativePath, pathInfos, maxInstantTime, pendingDataInstants, true);
+    this(relativePath, pathInfos, maxInstantTime, pendingDataInstants, true, 
false);
   }
 
   /**
@@ -69,6 +72,11 @@ public class DirectoryInfo implements Serializable {
    */
   public DirectoryInfo(String relativePath, List<StoragePathInfo> pathInfos, 
String maxInstantTime, Set<String> pendingDataInstants,
                        boolean validateHoodiePartitions) {
+    this(relativePath, pathInfos, maxInstantTime, pendingDataInstants, 
validateHoodiePartitions, false);
+  }
+
+  public DirectoryInfo(String relativePath, List<StoragePathInfo> pathInfos, 
String maxInstantTime, Set<String> pendingDataInstants,
+                       boolean validateHoodiePartitions, boolean 
skipZeroSizeFiles) {
     this.relativePath = relativePath;
 
     // Pre-allocate with the maximum length possible
@@ -89,7 +97,12 @@ public class DirectoryInfo implements Serializable {
         String dataFileCommitTime = 
FSUtils.getCommitTime(pathInfo.getPath().getName());
         // Limit the file listings to files which were created by successful 
commits before the maxInstant time.
         if (!pendingDataInstants.contains(dataFileCommitTime) && 
compareTimestamps(dataFileCommitTime, LESSER_THAN_OR_EQUALS, maxInstantTime)) {
-          filenameToSizeMap.put(pathInfo.getPath().getName(), 
pathInfo.getLength());
+          if (pathInfo.getLength() > 0 || !skipZeroSizeFiles) {
+            filenameToSizeMap.put(pathInfo.getPath().getName(), 
pathInfo.getLength());
+          } else {
+            log.warn("Skipping zero-size data file during MDT bootstrap: {}", 
pathInfo.getPath());
+            zeroSizeFileCount++;
+          }
         }
       }
     }

Reply via email to