You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@ozone.apache.org by "sreejasahithi (via GitHub)" <gi...@apache.org> on 2026/05/19 06:00:54 UTC

[PR] HDDS-14945. Implement Iceberg position delete file rewrite for path migration [ozone]

sreejasahithi opened a new pull request, #10306:
URL: https://github.com/apache/ozone/pull/10306

   ## What changes were proposed in this pull request?
   Position delete files of an Iceberg table contain the absolute path to the data files which contains the rows deleted.
   We need to rewrite these position delete files as part of path migration. For each selected position delete file, we can take help of Iceberg's RewriteTablePathUtil to change sourcePrefix to targetPrefix for each data file absolute path mentioned in it, and add the rewritten position delete file to a staging location.
   
   Introduce OzonePositionDeleteReaderWriter, which implements Iceberg’s PositionDeleteReaderWriter, to perform format-specific reads and writes for Avro, Parquet, and ORC.
   
   Also added test coverage wrt position delete files and manifest file.
   
   ## What is the link to the Apache JIRA
   
   [HDDS-14945](https://issues.apache.org/jira/browse/HDDS-14945)
   
   ## How was this patch tested?
   Updated testcases
   Green CI : https://github.com/sreejasahithi/ozone/actions/runs/26036208134
   


-- 
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: issues-unsubscribe@ozone.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org


Re: [PR] HDDS-14945. Implement Iceberg position delete file rewrite for path migration [ozone]

Posted by "sreejasahithi (via GitHub)" <gi...@apache.org>.
sreejasahithi commented on code in PR #10306:
URL: https://github.com/apache/ozone/pull/10306#discussion_r3281240596


##########
hadoop-ozone/iceberg/src/main/java/org/apache/hadoop/ozone/iceberg/RewriteTablePathOzoneAction.java:
##########
@@ -675,4 +695,188 @@ private static RewriteResult<DeleteFile> writeDeleteManifest(
       throw new RuntimeIOException(e);
     }
   }
+
+  static class OzonePositionDeleteReaderWriter implements RewriteTablePathUtil.PositionDeleteReaderWriter {
+    @Override
+    public CloseableIterable<Record> reader(
+        InputFile inputFile, FileFormat format, PartitionSpec spec) {
+      return positionDeletesReader(inputFile, format, spec);
+    }
+
+    @Override
+    public PositionDeleteWriter<Record> writer(
+        OutputFile outputFile,
+        FileFormat format,
+        PartitionSpec spec,
+        StructLike partition,
+        Schema rowSchema)
+        throws IOException {
+      return positionDeletesWriter(outputFile, format, spec, partition, rowSchema);
+    }
+  }
+
+  private void rewritePositionDeletes(Set<DeleteFile> toRewrite) {
+    /*
+     * NOTE: Rewriting position delete files updates embedded data file paths, which changes the
+     * resulting file size. This causes a metadata mismatch in the manifests:
+     *
+     * 1. Dependency: Manifests MUST be rewritten first because they are the source of truth used to identify which 
+     *    position delete files exist and need processing.
+     * 2. Issue: Because manifests are written before the delete files are updated, the'file_size_in_bytes' field 
+     *    in the manifest reflects the original size, not the new size.
+     * 3. Impact: Some catalogs (e.g., REST catalogs like Polaris) will fail to read these files as the reader uses
+     *    the stale size from the manifest.
+     *
+     * This is a known Iceberg limitation being addressed by the Iceberg community. Once that fix is available
+     * in the Iceberg core, this action should be updated accordingly.
+     */
+    if (toRewrite.isEmpty()) {
+      return;
+    }
+    
+    RewriteTablePathUtil.PositionDeleteReaderWriter posDeleteReaderWriter = new OzonePositionDeleteReaderWriter();
+    int maxInFlight = parallelism * MAX_INFLIGHT_MULTIPLIER;
+    Semaphore semaphore = new Semaphore(maxInFlight);
+    ExecutorCompletionService<Void> completionService = new ExecutorCompletionService<>(executorService);
+    int submittedTasks = 0;
+    int completedTasks = 0;
+
+    try {
+      for (DeleteFile deleteFile : toRewrite) {
+        semaphore.acquire();
+        boolean taskSubmitted = false;
+        try {
+          completionService.submit(() -> {
+            try {
+              rewritePositionDelete(deleteFile, table, sourcePrefix, targetPrefix, stagingDir, posDeleteReaderWriter);
+              return null;
+            } finally {
+              semaphore.release();
+            }
+          });
+          taskSubmitted = true;
+          submittedTasks++;
+        } finally {
+          if (!taskSubmitted) {
+            semaphore.release();
+          }
+        }

Review Comment:
   The Semaphore + ExecutorCompletionService pattern here is intentional, and here is the reasoning:
   
   `Executors.newFixedThreadPool` uses an unbounded `LinkedBlockingQueue` internally. While the thread count limits concurrent execution, all submitted tasks are still queued immediately. For a table with a very large number of position delete files, submitting everything upfront would create a task wrapper per file and can lead to significant memory pressure.
   
   The semaphore provides explicit backpressure and keeps the number of tasks admitted into the executor bounded at any point in time.
   
   The `ExecutorCompletionService` polling inside the submission loop allows us to observe task completion and detect failures early, so we can stop submitting additional work instead of enqueueing the full workload before encountering an error during the drain phase.
   
   A bounded `ThreadPoolExecutor` with an `ArrayBlockingQueue` is an alternative, but it does not eliminate the need for backpressure control or result tracking. When the queue fills, behaviour depends on the rejection policy:
   
   * `CallerRunsPolicy` changes execution semantics by running tasks on the submitting thread, which can stall submission progress, this naturally slows down the rate of new task submissions.
   * custom RejectedExecutionHandler provides similar backpressure behavior to `Semaphore.acquire()`, but moves the control flow into executor internals rather than keeping it explicit at the call site.
   
   In both cases, `ExecutorCompletionService` is still required for result collection and error propagation. The semaphore keeps the backpressure and concurrency limits explicit and visible in the code, rather than implicit in executor configuration.
   



-- 
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: issues-unsubscribe@ozone.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org


Re: [PR] HDDS-14945. Implement Iceberg position delete file rewrite for path migration [ozone]

Posted by "sreejasahithi (via GitHub)" <gi...@apache.org>.
sreejasahithi commented on code in PR #10306:
URL: https://github.com/apache/ozone/pull/10306#discussion_r3705177901


##########
hadoop-ozone/iceberg/src/main/java/org/apache/hadoop/ozone/iceberg/RewriteTablePathOzoneAction.java:
##########
@@ -675,4 +695,188 @@ private static RewriteResult<DeleteFile> writeDeleteManifest(
       throw new RuntimeIOException(e);
     }
   }
+
+  static class OzonePositionDeleteReaderWriter implements RewriteTablePathUtil.PositionDeleteReaderWriter {
+    @Override
+    public CloseableIterable<Record> reader(
+        InputFile inputFile, FileFormat format, PartitionSpec spec) {
+      return positionDeletesReader(inputFile, format, spec);
+    }
+
+    @Override
+    public PositionDeleteWriter<Record> writer(
+        OutputFile outputFile,
+        FileFormat format,
+        PartitionSpec spec,
+        StructLike partition,
+        Schema rowSchema)
+        throws IOException {
+      return positionDeletesWriter(outputFile, format, spec, partition, rowSchema);
+    }
+  }
+
+  private void rewritePositionDeletes(Set<DeleteFile> toRewrite) {
+    /*
+     * NOTE: Rewriting position delete files updates embedded data file paths, which changes the
+     * resulting file size. This causes a metadata mismatch in the manifests:
+     *
+     * 1. Dependency: Manifests MUST be rewritten first because they are the source of truth used to identify which 
+     *    position delete files exist and need processing.
+     * 2. Issue: Because manifests are written before the delete files are updated, the'file_size_in_bytes' field 
+     *    in the manifest reflects the original size, not the new size.
+     * 3. Impact: Some catalogs (e.g., REST catalogs like Polaris) will fail to read these files as the reader uses
+     *    the stale size from the manifest.
+     *
+     * This is a known Iceberg limitation being addressed by the Iceberg community. Once that fix is available
+     * in the Iceberg core, this action should be updated accordingly.
+     */
+    if (toRewrite.isEmpty()) {
+      return;
+    }
+    
+    RewriteTablePathUtil.PositionDeleteReaderWriter posDeleteReaderWriter = new OzonePositionDeleteReaderWriter();
+    int maxInFlight = parallelism * MAX_INFLIGHT_MULTIPLIER;
+    Semaphore semaphore = new Semaphore(maxInFlight);
+    ExecutorCompletionService<Void> completionService = new ExecutorCompletionService<>(executorService);
+    int submittedTasks = 0;
+    int completedTasks = 0;
+
+    try {
+      for (DeleteFile deleteFile : toRewrite) {
+        semaphore.acquire();
+        boolean taskSubmitted = false;
+        try {
+          completionService.submit(() -> {
+            try {
+              rewritePositionDelete(deleteFile, table, sourcePrefix, targetPrefix, stagingDir, posDeleteReaderWriter);
+              return null;
+            } finally {
+              semaphore.release();
+            }
+          });
+          taskSubmitted = true;
+          submittedTasks++;
+        } finally {
+          if (!taskSubmitted) {
+            semaphore.release();
+          }
+        }

Review Comment:
   I haven't had time to open the followup yet. It is on my list, will pick it up once I have the bandwidth.
   Thanks.



##########
hadoop-ozone/iceberg/src/main/java/org/apache/hadoop/ozone/iceberg/RewriteTablePathOzoneAction.java:
##########
@@ -675,4 +695,188 @@ private static RewriteResult<DeleteFile> writeDeleteManifest(
       throw new RuntimeIOException(e);
     }
   }
+
+  static class OzonePositionDeleteReaderWriter implements RewriteTablePathUtil.PositionDeleteReaderWriter {
+    @Override
+    public CloseableIterable<Record> reader(
+        InputFile inputFile, FileFormat format, PartitionSpec spec) {
+      return positionDeletesReader(inputFile, format, spec);
+    }
+
+    @Override
+    public PositionDeleteWriter<Record> writer(
+        OutputFile outputFile,
+        FileFormat format,
+        PartitionSpec spec,
+        StructLike partition,
+        Schema rowSchema)
+        throws IOException {
+      return positionDeletesWriter(outputFile, format, spec, partition, rowSchema);
+    }
+  }
+
+  private void rewritePositionDeletes(Set<DeleteFile> toRewrite) {
+    /*
+     * NOTE: Rewriting position delete files updates embedded data file paths, which changes the
+     * resulting file size. This causes a metadata mismatch in the manifests:
+     *
+     * 1. Dependency: Manifests MUST be rewritten first because they are the source of truth used to identify which 
+     *    position delete files exist and need processing.
+     * 2. Issue: Because manifests are written before the delete files are updated, the'file_size_in_bytes' field 
+     *    in the manifest reflects the original size, not the new size.
+     * 3. Impact: Some catalogs (e.g., REST catalogs like Polaris) will fail to read these files as the reader uses
+     *    the stale size from the manifest.
+     *
+     * This is a known Iceberg limitation being addressed by the Iceberg community. Once that fix is available
+     * in the Iceberg core, this action should be updated accordingly.
+     */
+    if (toRewrite.isEmpty()) {
+      return;
+    }
+    
+    RewriteTablePathUtil.PositionDeleteReaderWriter posDeleteReaderWriter = new OzonePositionDeleteReaderWriter();
+    int maxInFlight = parallelism * MAX_INFLIGHT_MULTIPLIER;
+    Semaphore semaphore = new Semaphore(maxInFlight);
+    ExecutorCompletionService<Void> completionService = new ExecutorCompletionService<>(executorService);
+    int submittedTasks = 0;
+    int completedTasks = 0;
+
+    try {
+      for (DeleteFile deleteFile : toRewrite) {
+        semaphore.acquire();
+        boolean taskSubmitted = false;
+        try {
+          completionService.submit(() -> {
+            try {
+              rewritePositionDelete(deleteFile, table, sourcePrefix, targetPrefix, stagingDir, posDeleteReaderWriter);
+              return null;
+            } finally {
+              semaphore.release();
+            }
+          });
+          taskSubmitted = true;
+          submittedTasks++;
+        } finally {
+          if (!taskSubmitted) {
+            semaphore.release();
+          }
+        }

Review Comment:
   @peterxcli I haven't had time to open the followup yet. It is on my list, will pick it up once I have the bandwidth.
   Thanks.



-- 
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: issues-unsubscribe@ozone.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org


Re: [PR] HDDS-14945. Implement Iceberg position delete file rewrite for path migration [ozone]

Posted by "peterxcli (via GitHub)" <gi...@apache.org>.
peterxcli commented on code in PR #10306:
URL: https://github.com/apache/ozone/pull/10306#discussion_r3283104079


##########
hadoop-ozone/iceberg/src/main/java/org/apache/hadoop/ozone/iceberg/RewriteTablePathOzoneAction.java:
##########
@@ -675,4 +695,188 @@ private static RewriteResult<DeleteFile> writeDeleteManifest(
       throw new RuntimeIOException(e);
     }
   }
+
+  static class OzonePositionDeleteReaderWriter implements RewriteTablePathUtil.PositionDeleteReaderWriter {
+    @Override
+    public CloseableIterable<Record> reader(
+        InputFile inputFile, FileFormat format, PartitionSpec spec) {
+      return positionDeletesReader(inputFile, format, spec);
+    }
+
+    @Override
+    public PositionDeleteWriter<Record> writer(
+        OutputFile outputFile,
+        FileFormat format,
+        PartitionSpec spec,
+        StructLike partition,
+        Schema rowSchema)
+        throws IOException {
+      return positionDeletesWriter(outputFile, format, spec, partition, rowSchema);
+    }
+  }
+
+  private void rewritePositionDeletes(Set<DeleteFile> toRewrite) {
+    /*
+     * NOTE: Rewriting position delete files updates embedded data file paths, which changes the
+     * resulting file size. This causes a metadata mismatch in the manifests:
+     *
+     * 1. Dependency: Manifests MUST be rewritten first because they are the source of truth used to identify which 
+     *    position delete files exist and need processing.
+     * 2. Issue: Because manifests are written before the delete files are updated, the'file_size_in_bytes' field 
+     *    in the manifest reflects the original size, not the new size.
+     * 3. Impact: Some catalogs (e.g., REST catalogs like Polaris) will fail to read these files as the reader uses
+     *    the stale size from the manifest.
+     *
+     * This is a known Iceberg limitation being addressed by the Iceberg community. Once that fix is available
+     * in the Iceberg core, this action should be updated accordingly.
+     */
+    if (toRewrite.isEmpty()) {
+      return;
+    }
+    
+    RewriteTablePathUtil.PositionDeleteReaderWriter posDeleteReaderWriter = new OzonePositionDeleteReaderWriter();
+    int maxInFlight = parallelism * MAX_INFLIGHT_MULTIPLIER;
+    Semaphore semaphore = new Semaphore(maxInFlight);
+    ExecutorCompletionService<Void> completionService = new ExecutorCompletionService<>(executorService);
+    int submittedTasks = 0;
+    int completedTasks = 0;
+
+    try {
+      for (DeleteFile deleteFile : toRewrite) {
+        semaphore.acquire();
+        boolean taskSubmitted = false;
+        try {
+          completionService.submit(() -> {
+            try {
+              rewritePositionDelete(deleteFile, table, sourcePrefix, targetPrefix, stagingDir, posDeleteReaderWriter);
+              return null;
+            } finally {
+              semaphore.release();
+            }
+          });
+          taskSubmitted = true;
+          submittedTasks++;
+        } finally {
+          if (!taskSubmitted) {
+            semaphore.release();
+          }
+        }

Review Comment:
   I also noticed the Semaphore + ExecutorCompletionService pattern is repeated across every `rewriteXXX` method. We could extract it into a shared utility as a follow up.



-- 
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: issues-unsubscribe@ozone.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org


Re: [PR] HDDS-14945. Implement Iceberg position delete file rewrite for path migration [ozone]

Posted by "ashishkumar50 (via GitHub)" <gi...@apache.org>.
ashishkumar50 commented on code in PR #10306:
URL: https://github.com/apache/ozone/pull/10306#discussion_r3332694982


##########
hadoop-ozone/iceberg/src/main/java/org/apache/hadoop/ozone/iceberg/RewriteTablePathOzoneAction.java:
##########
@@ -675,4 +695,188 @@ private static RewriteResult<DeleteFile> writeDeleteManifest(
       throw new RuntimeIOException(e);
     }
   }
+
+  static class OzonePositionDeleteReaderWriter implements RewriteTablePathUtil.PositionDeleteReaderWriter {
+    @Override
+    public CloseableIterable<Record> reader(
+        InputFile inputFile, FileFormat format, PartitionSpec spec) {
+      return positionDeletesReader(inputFile, format, spec);
+    }
+
+    @Override
+    public PositionDeleteWriter<Record> writer(
+        OutputFile outputFile,
+        FileFormat format,
+        PartitionSpec spec,
+        StructLike partition,
+        Schema rowSchema)
+        throws IOException {
+      return positionDeletesWriter(outputFile, format, spec, partition, rowSchema);
+    }
+  }
+
+  private void rewritePositionDeletes(Set<DeleteFile> toRewrite) {
+    /*
+     * NOTE: Rewriting position delete files updates embedded data file paths, which changes the
+     * resulting file size. This causes a metadata mismatch in the manifests:
+     *
+     * 1. Dependency: Manifests MUST be rewritten first because they are the source of truth used to identify which 
+     *    position delete files exist and need processing.
+     * 2. Issue: Because manifests are written before the delete files are updated, the'file_size_in_bytes' field 
+     *    in the manifest reflects the original size, not the new size.
+     * 3. Impact: Some catalogs (e.g., REST catalogs like Polaris) will fail to read these files as the reader uses
+     *    the stale size from the manifest.
+     *
+     * This is a known Iceberg limitation being addressed by the Iceberg community. Once that fix is available

Review Comment:
   You can add this limitation in ozone-iceberg doc as well.



##########
hadoop-ozone/iceberg/src/main/java/org/apache/hadoop/ozone/iceberg/RewriteTablePathOzoneAction.java:
##########
@@ -675,4 +695,188 @@ private static RewriteResult<DeleteFile> writeDeleteManifest(
       throw new RuntimeIOException(e);
     }
   }
+
+  static class OzonePositionDeleteReaderWriter implements RewriteTablePathUtil.PositionDeleteReaderWriter {
+    @Override
+    public CloseableIterable<Record> reader(
+        InputFile inputFile, FileFormat format, PartitionSpec spec) {
+      return positionDeletesReader(inputFile, format, spec);
+    }
+
+    @Override
+    public PositionDeleteWriter<Record> writer(
+        OutputFile outputFile,
+        FileFormat format,
+        PartitionSpec spec,
+        StructLike partition,
+        Schema rowSchema)
+        throws IOException {
+      return positionDeletesWriter(outputFile, format, spec, partition, rowSchema);
+    }
+  }
+
+  private void rewritePositionDeletes(Set<DeleteFile> toRewrite) {
+    /*
+     * NOTE: Rewriting position delete files updates embedded data file paths, which changes the
+     * resulting file size. This causes a metadata mismatch in the manifests:
+     *
+     * 1. Dependency: Manifests MUST be rewritten first because they are the source of truth used to identify which 
+     *    position delete files exist and need processing.
+     * 2. Issue: Because manifests are written before the delete files are updated, the'file_size_in_bytes' field 

Review Comment:
   nit: the 'file_size_in_bytes'



##########
hadoop-ozone/iceberg/src/test/java/org/apache/hadoop/ozone/iceberg/TestRewriteTablePathOzoneAction.java:
##########
@@ -366,6 +393,110 @@ void statsFileCopyPlanReturnsBeforeToAfterPathPairs() {
         Pair.of("before-1.stats", "after-1.stats"),
         Pair.of("before-2.stats", "after-2.stats")), copyPlan);
   }
+
+  @Test
+  void rejectsTablesWithPartitionStatistics() {
+    TableMetadata baseMetadata = ((HasTableOperations) table).operations().current();
+    long snapshotId = baseMetadata.currentSnapshot().snapshotId();
+    PartitionStatisticsFile statsFile = Mockito.mock(PartitionStatisticsFile.class);
+    Mockito.when(statsFile.snapshotId()).thenReturn(snapshotId);
+    Mockito.when(statsFile.path()).thenReturn(sourcePrefix + "/metadata/dummy.stats");
+    Mockito.when(statsFile.fileSizeInBytes()).thenReturn(100L);
+    TableMetadata metadataWithStats = TableMetadata.buildFrom(baseMetadata)
+        .setPartitionStatistics(statsFile)
+        .build();
+
+    TableOperations ops = ((HasTableOperations) table).operations();
+    ops.commit(baseMetadata, metadataWithStats);
+
+    RewriteTablePath action = new RewriteTablePathOzoneAction(table)
+        .rewriteLocationPrefix(sourcePrefix, targetPrefix)
+        .stagingLocation(stagingDir + "/");
+
+    IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, action::execute);
+    assertThat(exception).hasMessageContaining("Partition statistics files are not supported yet.");
+  }
+
+  @Test
+  public void positionDeletesReaderUnsupportedFormat() {
+    InputFile mockInput = Mockito.mock(InputFile.class);
+    Mockito.when(mockInput.location()).thenReturn("s3://bucket/test.txt");
+    PartitionSpec spec = PartitionSpec.unpartitioned();
+    FileFormat mockUnsupportedFormat = Mockito.mock(FileFormat.class);
+    Mockito.when(mockUnsupportedFormat.toString()).thenReturn("txt");
+
+    UnsupportedOperationException exception = assertThrows(UnsupportedOperationException.class,
+        () -> RewriteTablePathOzoneAction.positionDeletesReader(mockInput, mockUnsupportedFormat, spec));
+
+    assertThat(exception).hasMessageContaining("Unsupported file format: txt");
+  }
+
+  @Test
+  public void positionDeletesWriterUnsupportedFormat() {
+    OutputFile mockOutput = Mockito.mock(OutputFile.class);
+    Mockito.when(mockOutput.location()).thenReturn("s3://bucket/test.txt");
+    PartitionSpec spec = PartitionSpec.unpartitioned();
+    FileFormat mockUnsupportedFormat = Mockito.mock(FileFormat.class);
+    Mockito.when(mockUnsupportedFormat.toString()).thenReturn("txt");
+
+    UnsupportedOperationException exception = assertThrows(UnsupportedOperationException.class,
+        () -> RewriteTablePathOzoneAction.positionDeletesWriter(
+            mockOutput, mockUnsupportedFormat, spec, null, null));
+
+    assertThat(exception).hasMessageContaining("Unsupported file format: txt");
+  }
+
+  @ParameterizedTest
+  @EnumSource(value = FileFormat.class, names = {"AVRO", "ORC"})

Review Comment:
   Add PARQUET as well?



-- 
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: issues-unsubscribe@ozone.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org


Re: [PR] HDDS-14945. Implement Iceberg position delete file rewrite for path migration [ozone]

Posted by "peterxcli (via GitHub)" <gi...@apache.org>.
peterxcli commented on code in PR #10306:
URL: https://github.com/apache/ozone/pull/10306#discussion_r3704943006


##########
hadoop-ozone/iceberg/src/main/java/org/apache/hadoop/ozone/iceberg/RewriteTablePathOzoneAction.java:
##########
@@ -675,4 +695,188 @@ private static RewriteResult<DeleteFile> writeDeleteManifest(
       throw new RuntimeIOException(e);
     }
   }
+
+  static class OzonePositionDeleteReaderWriter implements RewriteTablePathUtil.PositionDeleteReaderWriter {
+    @Override
+    public CloseableIterable<Record> reader(
+        InputFile inputFile, FileFormat format, PartitionSpec spec) {
+      return positionDeletesReader(inputFile, format, spec);
+    }
+
+    @Override
+    public PositionDeleteWriter<Record> writer(
+        OutputFile outputFile,
+        FileFormat format,
+        PartitionSpec spec,
+        StructLike partition,
+        Schema rowSchema)
+        throws IOException {
+      return positionDeletesWriter(outputFile, format, spec, partition, rowSchema);
+    }
+  }
+
+  private void rewritePositionDeletes(Set<DeleteFile> toRewrite) {
+    /*
+     * NOTE: Rewriting position delete files updates embedded data file paths, which changes the
+     * resulting file size. This causes a metadata mismatch in the manifests:
+     *
+     * 1. Dependency: Manifests MUST be rewritten first because they are the source of truth used to identify which 
+     *    position delete files exist and need processing.
+     * 2. Issue: Because manifests are written before the delete files are updated, the'file_size_in_bytes' field 
+     *    in the manifest reflects the original size, not the new size.
+     * 3. Impact: Some catalogs (e.g., REST catalogs like Polaris) will fail to read these files as the reader uses
+     *    the stale size from the manifest.
+     *
+     * This is a known Iceberg limitation being addressed by the Iceberg community. Once that fix is available
+     * in the Iceberg core, this action should be updated accordingly.
+     */
+    if (toRewrite.isEmpty()) {
+      return;
+    }
+    
+    RewriteTablePathUtil.PositionDeleteReaderWriter posDeleteReaderWriter = new OzonePositionDeleteReaderWriter();
+    int maxInFlight = parallelism * MAX_INFLIGHT_MULTIPLIER;
+    Semaphore semaphore = new Semaphore(maxInFlight);
+    ExecutorCompletionService<Void> completionService = new ExecutorCompletionService<>(executorService);
+    int submittedTasks = 0;
+    int completedTasks = 0;
+
+    try {
+      for (DeleteFile deleteFile : toRewrite) {
+        semaphore.acquire();
+        boolean taskSubmitted = false;
+        try {
+          completionService.submit(() -> {
+            try {
+              rewritePositionDelete(deleteFile, table, sourcePrefix, targetPrefix, stagingDir, posDeleteReaderWriter);
+              return null;
+            } finally {
+              semaphore.release();
+            }
+          });
+          taskSubmitted = true;
+          submittedTasks++;
+        } finally {
+          if (!taskSubmitted) {
+            semaphore.release();
+          }
+        }

Review Comment:
   @sreejasahithi curious if you have any followup for this specifically? I just encounter the similar issue, would appreciate the cleanup solution from your side, thanks!



-- 
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: issues-unsubscribe@ozone.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org


Re: [PR] HDDS-14945. Implement Iceberg position delete file rewrite for path migration [ozone]

Posted by "sreejasahithi (via GitHub)" <gi...@apache.org>.
sreejasahithi commented on code in PR #10306:
URL: https://github.com/apache/ozone/pull/10306#discussion_r3283125463


##########
hadoop-ozone/iceberg/src/main/java/org/apache/hadoop/ozone/iceberg/RewriteTablePathOzoneAction.java:
##########
@@ -675,4 +695,188 @@ private static RewriteResult<DeleteFile> writeDeleteManifest(
       throw new RuntimeIOException(e);
     }
   }
+
+  static class OzonePositionDeleteReaderWriter implements RewriteTablePathUtil.PositionDeleteReaderWriter {
+    @Override
+    public CloseableIterable<Record> reader(
+        InputFile inputFile, FileFormat format, PartitionSpec spec) {
+      return positionDeletesReader(inputFile, format, spec);
+    }
+
+    @Override
+    public PositionDeleteWriter<Record> writer(
+        OutputFile outputFile,
+        FileFormat format,
+        PartitionSpec spec,
+        StructLike partition,
+        Schema rowSchema)
+        throws IOException {
+      return positionDeletesWriter(outputFile, format, spec, partition, rowSchema);
+    }
+  }
+
+  private void rewritePositionDeletes(Set<DeleteFile> toRewrite) {
+    /*
+     * NOTE: Rewriting position delete files updates embedded data file paths, which changes the
+     * resulting file size. This causes a metadata mismatch in the manifests:
+     *
+     * 1. Dependency: Manifests MUST be rewritten first because they are the source of truth used to identify which 
+     *    position delete files exist and need processing.
+     * 2. Issue: Because manifests are written before the delete files are updated, the'file_size_in_bytes' field 
+     *    in the manifest reflects the original size, not the new size.
+     * 3. Impact: Some catalogs (e.g., REST catalogs like Polaris) will fail to read these files as the reader uses
+     *    the stale size from the manifest.
+     *
+     * This is a known Iceberg limitation being addressed by the Iceberg community. Once that fix is available
+     * in the Iceberg core, this action should be updated accordingly.
+     */
+    if (toRewrite.isEmpty()) {
+      return;
+    }
+    
+    RewriteTablePathUtil.PositionDeleteReaderWriter posDeleteReaderWriter = new OzonePositionDeleteReaderWriter();
+    int maxInFlight = parallelism * MAX_INFLIGHT_MULTIPLIER;
+    Semaphore semaphore = new Semaphore(maxInFlight);
+    ExecutorCompletionService<Void> completionService = new ExecutorCompletionService<>(executorService);
+    int submittedTasks = 0;
+    int completedTasks = 0;
+
+    try {
+      for (DeleteFile deleteFile : toRewrite) {
+        semaphore.acquire();
+        boolean taskSubmitted = false;
+        try {
+          completionService.submit(() -> {
+            try {
+              rewritePositionDelete(deleteFile, table, sourcePrefix, targetPrefix, stagingDir, posDeleteReaderWriter);
+              return null;
+            } finally {
+              semaphore.release();
+            }
+          });
+          taskSubmitted = true;
+          submittedTasks++;
+        } finally {
+          if (!taskSubmitted) {
+            semaphore.release();
+          }
+        }

Review Comment:
   Yes, that cleanup is under my consideration. I think it would be better to take it up in a separate PR since it would change the scope/context of this PR.



-- 
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: issues-unsubscribe@ozone.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org


Re: [PR] HDDS-14945. Implement Iceberg position delete file rewrite for path migration [ozone]

Posted by "sreejasahithi (via GitHub)" <gi...@apache.org>.
sreejasahithi commented on PR #10306:
URL: https://github.com/apache/ozone/pull/10306#issuecomment-4574237485

   @ashishkumar50 could you please review this patch.


-- 
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: issues-unsubscribe@ozone.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org


Re: [PR] HDDS-14945. Implement Iceberg position delete file rewrite for path migration [ozone]

Posted by "peterxcli (via GitHub)" <gi...@apache.org>.
peterxcli commented on code in PR #10306:
URL: https://github.com/apache/ozone/pull/10306#discussion_r3268735742


##########
hadoop-ozone/iceberg/src/main/java/org/apache/hadoop/ozone/iceberg/RewriteTablePathOzoneAction.java:
##########
@@ -255,6 +268,13 @@ private String rebuildMetadata() {
     RewriteContentFileResult rewriteManifestResult =
         rewriteManifests(deltaSnapshotIds, endMetadata, rewriteManifestListResult.toRewrite());
 
+    Set<DeleteFile> deleteFiles =
+        rewriteManifestResult.toRewrite().stream()
+            .filter(e -> e instanceof DeleteFile)
+            .map(e -> (DeleteFile) e)
+            .collect(Collectors.toSet());
+    rewritePositionDeletes(deleteFiles);

Review Comment:
   Should we rewrite deletion vector puffin files? I think if users are using V3 then they need it.



-- 
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: issues-unsubscribe@ozone.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org


Re: [PR] HDDS-14945. Implement Iceberg position delete file rewrite for path migration [ozone]

Posted by "sreejasahithi (via GitHub)" <gi...@apache.org>.
sreejasahithi commented on code in PR #10306:
URL: https://github.com/apache/ozone/pull/10306#discussion_r3281240596


##########
hadoop-ozone/iceberg/src/main/java/org/apache/hadoop/ozone/iceberg/RewriteTablePathOzoneAction.java:
##########
@@ -675,4 +695,188 @@ private static RewriteResult<DeleteFile> writeDeleteManifest(
       throw new RuntimeIOException(e);
     }
   }
+
+  static class OzonePositionDeleteReaderWriter implements RewriteTablePathUtil.PositionDeleteReaderWriter {
+    @Override
+    public CloseableIterable<Record> reader(
+        InputFile inputFile, FileFormat format, PartitionSpec spec) {
+      return positionDeletesReader(inputFile, format, spec);
+    }
+
+    @Override
+    public PositionDeleteWriter<Record> writer(
+        OutputFile outputFile,
+        FileFormat format,
+        PartitionSpec spec,
+        StructLike partition,
+        Schema rowSchema)
+        throws IOException {
+      return positionDeletesWriter(outputFile, format, spec, partition, rowSchema);
+    }
+  }
+
+  private void rewritePositionDeletes(Set<DeleteFile> toRewrite) {
+    /*
+     * NOTE: Rewriting position delete files updates embedded data file paths, which changes the
+     * resulting file size. This causes a metadata mismatch in the manifests:
+     *
+     * 1. Dependency: Manifests MUST be rewritten first because they are the source of truth used to identify which 
+     *    position delete files exist and need processing.
+     * 2. Issue: Because manifests are written before the delete files are updated, the'file_size_in_bytes' field 
+     *    in the manifest reflects the original size, not the new size.
+     * 3. Impact: Some catalogs (e.g., REST catalogs like Polaris) will fail to read these files as the reader uses
+     *    the stale size from the manifest.
+     *
+     * This is a known Iceberg limitation being addressed by the Iceberg community. Once that fix is available
+     * in the Iceberg core, this action should be updated accordingly.
+     */
+    if (toRewrite.isEmpty()) {
+      return;
+    }
+    
+    RewriteTablePathUtil.PositionDeleteReaderWriter posDeleteReaderWriter = new OzonePositionDeleteReaderWriter();
+    int maxInFlight = parallelism * MAX_INFLIGHT_MULTIPLIER;
+    Semaphore semaphore = new Semaphore(maxInFlight);
+    ExecutorCompletionService<Void> completionService = new ExecutorCompletionService<>(executorService);
+    int submittedTasks = 0;
+    int completedTasks = 0;
+
+    try {
+      for (DeleteFile deleteFile : toRewrite) {
+        semaphore.acquire();
+        boolean taskSubmitted = false;
+        try {
+          completionService.submit(() -> {
+            try {
+              rewritePositionDelete(deleteFile, table, sourcePrefix, targetPrefix, stagingDir, posDeleteReaderWriter);
+              return null;
+            } finally {
+              semaphore.release();
+            }
+          });
+          taskSubmitted = true;
+          submittedTasks++;
+        } finally {
+          if (!taskSubmitted) {
+            semaphore.release();
+          }
+        }

Review Comment:
   The Semaphore + ExecutorCompletionService pattern here is intentional, and here is the reasoning:
   
   `Executors.newFixedThreadPool` uses an unbounded `LinkedBlockingQueue` internally. While the thread count limits concurrent execution, all submitted tasks are still queued immediately. For a table with a very large number of position delete files, submitting everything upfront would create a task wrapper per file and can lead to memory pressure proportional to the number of files.
   
   The semaphore provides explicit backpressure and keeps the number of tasks admitted into the executor bounded at any point in time.
   
   The `ExecutorCompletionService` polling inside the submission loop allows us to opportunistically drain already completed tasks during submission. This helps surface task failures earlier during execution compared to waiting for a final drain phase after all submissions are completed.
   
   A bounded `ThreadPoolExecutor` with an `ArrayBlockingQueue` is an alternative, but it does not eliminate the need for backpressure control or result tracking. When the queue fills, behaviour depends on the rejection policy:
   
   * `CallerRunsPolicy` changes execution semantics by running tasks on the submitting thread, which can stall submission progress, this naturally slows down the rate of new task submissions.
   * custom RejectedExecutionHandler provides similar backpressure behavior to `Semaphore.acquire()`, but moves the control flow into executor internals rather than keeping it explicit at the call site.
   
   In both cases, `ExecutorCompletionService` is still required for result collection and error propagation. The semaphore keeps the backpressure and concurrency limits explicit and visible in the code, rather than implicit in executor configuration.
   



-- 
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: issues-unsubscribe@ozone.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org


Re: [PR] HDDS-14945. Implement Iceberg position delete file rewrite for path migration [ozone]

Posted by "sreejasahithi (via GitHub)" <gi...@apache.org>.
sreejasahithi commented on PR #10306:
URL: https://github.com/apache/ozone/pull/10306#issuecomment-4574228758

   Reverted the fat jar commit as RewriteTablePathOzoneAction will be invoked via CLI command RewriteTablePathCommand(will add in future patch) in Ozone so we only need the fat jar if it is used in other environment.


-- 
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: issues-unsubscribe@ozone.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org


Re: [PR] HDDS-14945. Implement Iceberg position delete file rewrite for path migration [ozone]

Posted by "peterxcli (via GitHub)" <gi...@apache.org>.
peterxcli commented on PR #10306:
URL: https://github.com/apache/ozone/pull/10306#issuecomment-4604263839

   Thanks @sreejasahithi for the patch, @adoroszlai, @ashishkumar50 for the review!


-- 
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: issues-unsubscribe@ozone.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org


Re: [PR] HDDS-14945. Implement Iceberg position delete file rewrite for path migration [ozone]

Posted by "sreejasahithi (via GitHub)" <gi...@apache.org>.
sreejasahithi commented on code in PR #10306:
URL: https://github.com/apache/ozone/pull/10306#discussion_r3281240596


##########
hadoop-ozone/iceberg/src/main/java/org/apache/hadoop/ozone/iceberg/RewriteTablePathOzoneAction.java:
##########
@@ -675,4 +695,188 @@ private static RewriteResult<DeleteFile> writeDeleteManifest(
       throw new RuntimeIOException(e);
     }
   }
+
+  static class OzonePositionDeleteReaderWriter implements RewriteTablePathUtil.PositionDeleteReaderWriter {
+    @Override
+    public CloseableIterable<Record> reader(
+        InputFile inputFile, FileFormat format, PartitionSpec spec) {
+      return positionDeletesReader(inputFile, format, spec);
+    }
+
+    @Override
+    public PositionDeleteWriter<Record> writer(
+        OutputFile outputFile,
+        FileFormat format,
+        PartitionSpec spec,
+        StructLike partition,
+        Schema rowSchema)
+        throws IOException {
+      return positionDeletesWriter(outputFile, format, spec, partition, rowSchema);
+    }
+  }
+
+  private void rewritePositionDeletes(Set<DeleteFile> toRewrite) {
+    /*
+     * NOTE: Rewriting position delete files updates embedded data file paths, which changes the
+     * resulting file size. This causes a metadata mismatch in the manifests:
+     *
+     * 1. Dependency: Manifests MUST be rewritten first because they are the source of truth used to identify which 
+     *    position delete files exist and need processing.
+     * 2. Issue: Because manifests are written before the delete files are updated, the'file_size_in_bytes' field 
+     *    in the manifest reflects the original size, not the new size.
+     * 3. Impact: Some catalogs (e.g., REST catalogs like Polaris) will fail to read these files as the reader uses
+     *    the stale size from the manifest.
+     *
+     * This is a known Iceberg limitation being addressed by the Iceberg community. Once that fix is available
+     * in the Iceberg core, this action should be updated accordingly.
+     */
+    if (toRewrite.isEmpty()) {
+      return;
+    }
+    
+    RewriteTablePathUtil.PositionDeleteReaderWriter posDeleteReaderWriter = new OzonePositionDeleteReaderWriter();
+    int maxInFlight = parallelism * MAX_INFLIGHT_MULTIPLIER;
+    Semaphore semaphore = new Semaphore(maxInFlight);
+    ExecutorCompletionService<Void> completionService = new ExecutorCompletionService<>(executorService);
+    int submittedTasks = 0;
+    int completedTasks = 0;
+
+    try {
+      for (DeleteFile deleteFile : toRewrite) {
+        semaphore.acquire();
+        boolean taskSubmitted = false;
+        try {
+          completionService.submit(() -> {
+            try {
+              rewritePositionDelete(deleteFile, table, sourcePrefix, targetPrefix, stagingDir, posDeleteReaderWriter);
+              return null;
+            } finally {
+              semaphore.release();
+            }
+          });
+          taskSubmitted = true;
+          submittedTasks++;
+        } finally {
+          if (!taskSubmitted) {
+            semaphore.release();
+          }
+        }

Review Comment:
   The Semaphore + ExecutorCompletionService pattern here is intentional, and here is the reasoning:
   
   `Executors.newFixedThreadPool` uses an unbounded `LinkedBlockingQueue` internally. While the thread count limits concurrent execution, all submitted tasks are still queued immediately. For a table with a very large number of position delete files, submitting everything upfront would create a task wrapper per file and can lead to memory pressure proportional to the number of files.
   
   The semaphore provides explicit backpressure and keeps the number of tasks admitted into the executor bounded at any point in time.
   
   The `ExecutorCompletionService` polling inside the submission loop allows us to opportunistically drain already completed tasks during submission. This helps surface task failures earlier during execution compared to waiting for a final drain phase after all submissions are completed.
   
   A bounded `ThreadPoolExecutor` with an `ArrayBlockingQueue` is an alternative, but when the queue fills, behaviour depends on the rejection policy:
   
   * `CallerRunsPolicy` changes execution semantics by running tasks on the submitting thread, which can stall submission progress, this naturally slows down the rate of new task submissions.
   * custom RejectedExecutionHandler provides similar backpressure behavior to `Semaphore.acquire()`, but moves the control flow into executor internals rather than keeping it explicit at the call site.
   
   In both cases, `ExecutorCompletionService` is still required for result collection and error propagation. The semaphore keeps the backpressure and concurrency limits explicit and visible in the code, rather than implicit in executor configuration.
   



-- 
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: issues-unsubscribe@ozone.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org


Re: [PR] HDDS-14945. Implement Iceberg position delete file rewrite for path migration [ozone]

Posted by "sreejasahithi (via GitHub)" <gi...@apache.org>.
sreejasahithi commented on code in PR #10306:
URL: https://github.com/apache/ozone/pull/10306#discussion_r3280533213


##########
hadoop-ozone/iceberg/src/main/java/org/apache/hadoop/ozone/iceberg/RewriteTablePathOzoneAction.java:
##########
@@ -255,6 +268,13 @@ private String rebuildMetadata() {
     RewriteContentFileResult rewriteManifestResult =
         rewriteManifests(deltaSnapshotIds, endMetadata, rewriteManifestListResult.toRewrite());
 
+    Set<DeleteFile> deleteFiles =
+        rewriteManifestResult.toRewrite().stream()
+            .filter(e -> e instanceof DeleteFile)
+            .map(e -> (DeleteFile) e)
+            .collect(Collectors.toSet());
+    rewritePositionDeletes(deleteFiles);

Review Comment:
   Currently we are supporting Iceberg version 1.10.1 in Ozone.
   Iceberg has added the capability to rewrite deletion vector files and partition statistics files in iceberg version 1.11.0 (recently released)
   so when we support 1.11.0 in ozone then we can look forward to support those file rewrites as well.



-- 
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: issues-unsubscribe@ozone.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org


Re: [PR] HDDS-14945. Implement Iceberg position delete file rewrite for path migration [ozone]

Posted by "peterxcli (via GitHub)" <gi...@apache.org>.
peterxcli commented on code in PR #10306:
URL: https://github.com/apache/ozone/pull/10306#discussion_r3268776387


##########
hadoop-ozone/iceberg/src/main/java/org/apache/hadoop/ozone/iceberg/RewriteTablePathOzoneAction.java:
##########
@@ -675,4 +695,188 @@ private static RewriteResult<DeleteFile> writeDeleteManifest(
       throw new RuntimeIOException(e);
     }
   }
+
+  static class OzonePositionDeleteReaderWriter implements RewriteTablePathUtil.PositionDeleteReaderWriter {
+    @Override
+    public CloseableIterable<Record> reader(
+        InputFile inputFile, FileFormat format, PartitionSpec spec) {
+      return positionDeletesReader(inputFile, format, spec);
+    }
+
+    @Override
+    public PositionDeleteWriter<Record> writer(
+        OutputFile outputFile,
+        FileFormat format,
+        PartitionSpec spec,
+        StructLike partition,
+        Schema rowSchema)
+        throws IOException {
+      return positionDeletesWriter(outputFile, format, spec, partition, rowSchema);
+    }
+  }
+
+  private void rewritePositionDeletes(Set<DeleteFile> toRewrite) {
+    /*
+     * NOTE: Rewriting position delete files updates embedded data file paths, which changes the
+     * resulting file size. This causes a metadata mismatch in the manifests:
+     *
+     * 1. Dependency: Manifests MUST be rewritten first because they are the source of truth used to identify which 
+     *    position delete files exist and need processing.
+     * 2. Issue: Because manifests are written before the delete files are updated, the'file_size_in_bytes' field 
+     *    in the manifest reflects the original size, not the new size.
+     * 3. Impact: Some catalogs (e.g., REST catalogs like Polaris) will fail to read these files as the reader uses
+     *    the stale size from the manifest.
+     *
+     * This is a known Iceberg limitation being addressed by the Iceberg community. Once that fix is available
+     * in the Iceberg core, this action should be updated accordingly.
+     */
+    if (toRewrite.isEmpty()) {
+      return;
+    }
+    
+    RewriteTablePathUtil.PositionDeleteReaderWriter posDeleteReaderWriter = new OzonePositionDeleteReaderWriter();
+    int maxInFlight = parallelism * MAX_INFLIGHT_MULTIPLIER;
+    Semaphore semaphore = new Semaphore(maxInFlight);
+    ExecutorCompletionService<Void> completionService = new ExecutorCompletionService<>(executorService);
+    int submittedTasks = 0;
+    int completedTasks = 0;
+
+    try {
+      for (DeleteFile deleteFile : toRewrite) {
+        semaphore.acquire();
+        boolean taskSubmitted = false;
+        try {
+          completionService.submit(() -> {
+            try {
+              rewritePositionDelete(deleteFile, table, sourcePrefix, targetPrefix, stagingDir, posDeleteReaderWriter);
+              return null;
+            } finally {
+              semaphore.release();
+            }
+          });
+          taskSubmitted = true;
+          submittedTasks++;
+        } finally {
+          if (!taskSubmitted) {
+            semaphore.release();
+          }
+        }

Review Comment:
   The ‎`executorService` is already a bounded thread pool with ‎`parallelism` threads
   
   ```suggestion
       List<Future<Void>> futures = new ArrayList<>();
       for (DeleteFile deleteFile : toRewrite) {
           futures.add(executorService.submit(() -> {
               rewritePositionDelete(deleteFile, table, sourcePrefix,
                   targetPrefix, stagingDir, posDeleteReaderWriter);
               return null;
           }));
       }
   
       for (Future<Void> f : futures) {
           try {
               f.get();
           } catch (ExecutionException e) {
               executorService.shutdownNow();
               throw new RuntimeException(
                   "Failed to rewrite position delete file", e.getCause());
           } catch (InterruptedException e) {
               Thread.currentThread().interrupt();
               executorService.shutdownNow();
               throw new RuntimeException(
                   "Interrupted while rewriting position delete files", e);
           }
       }
   ```



##########
hadoop-ozone/iceberg/src/main/java/org/apache/hadoop/ozone/iceberg/RewriteTablePathOzoneAction.java:
##########
@@ -675,4 +695,188 @@ private static RewriteResult<DeleteFile> writeDeleteManifest(
       throw new RuntimeIOException(e);
     }
   }
+
+  static class OzonePositionDeleteReaderWriter implements RewriteTablePathUtil.PositionDeleteReaderWriter {
+    @Override
+    public CloseableIterable<Record> reader(
+        InputFile inputFile, FileFormat format, PartitionSpec spec) {
+      return positionDeletesReader(inputFile, format, spec);
+    }
+
+    @Override
+    public PositionDeleteWriter<Record> writer(
+        OutputFile outputFile,
+        FileFormat format,
+        PartitionSpec spec,
+        StructLike partition,
+        Schema rowSchema)
+        throws IOException {
+      return positionDeletesWriter(outputFile, format, spec, partition, rowSchema);
+    }
+  }
+
+  private void rewritePositionDeletes(Set<DeleteFile> toRewrite) {
+    /*
+     * NOTE: Rewriting position delete files updates embedded data file paths, which changes the
+     * resulting file size. This causes a metadata mismatch in the manifests:
+     *
+     * 1. Dependency: Manifests MUST be rewritten first because they are the source of truth used to identify which 
+     *    position delete files exist and need processing.
+     * 2. Issue: Because manifests are written before the delete files are updated, the'file_size_in_bytes' field 
+     *    in the manifest reflects the original size, not the new size.
+     * 3. Impact: Some catalogs (e.g., REST catalogs like Polaris) will fail to read these files as the reader uses
+     *    the stale size from the manifest.
+     *
+     * This is a known Iceberg limitation being addressed by the Iceberg community. Once that fix is available
+     * in the Iceberg core, this action should be updated accordingly.
+     */
+    if (toRewrite.isEmpty()) {
+      return;
+    }
+    
+    RewriteTablePathUtil.PositionDeleteReaderWriter posDeleteReaderWriter = new OzonePositionDeleteReaderWriter();
+    int maxInFlight = parallelism * MAX_INFLIGHT_MULTIPLIER;
+    Semaphore semaphore = new Semaphore(maxInFlight);
+    ExecutorCompletionService<Void> completionService = new ExecutorCompletionService<>(executorService);
+    int submittedTasks = 0;
+    int completedTasks = 0;
+
+    try {
+      for (DeleteFile deleteFile : toRewrite) {
+        semaphore.acquire();
+        boolean taskSubmitted = false;
+        try {
+          completionService.submit(() -> {
+            try {
+              rewritePositionDelete(deleteFile, table, sourcePrefix, targetPrefix, stagingDir, posDeleteReaderWriter);
+              return null;
+            } finally {
+              semaphore.release();
+            }
+          });
+          taskSubmitted = true;
+          submittedTasks++;
+        } finally {
+          if (!taskSubmitted) {
+            semaphore.release();
+          }
+        }

Review Comment:
   I think there must be a cleaner way to do this.



-- 
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: issues-unsubscribe@ozone.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org


Re: [PR] HDDS-14945. Implement Iceberg position delete file rewrite for path migration [ozone]

Posted by "sreejasahithi (via GitHub)" <gi...@apache.org>.
sreejasahithi commented on code in PR #10306:
URL: https://github.com/apache/ozone/pull/10306#discussion_r3280533213


##########
hadoop-ozone/iceberg/src/main/java/org/apache/hadoop/ozone/iceberg/RewriteTablePathOzoneAction.java:
##########
@@ -255,6 +268,13 @@ private String rebuildMetadata() {
     RewriteContentFileResult rewriteManifestResult =
         rewriteManifests(deltaSnapshotIds, endMetadata, rewriteManifestListResult.toRewrite());
 
+    Set<DeleteFile> deleteFiles =
+        rewriteManifestResult.toRewrite().stream()
+            .filter(e -> e instanceof DeleteFile)
+            .map(e -> (DeleteFile) e)
+            .collect(Collectors.toSet());
+    rewritePositionDeletes(deleteFiles);

Review Comment:
   Currently we are supporting Iceberg version 1.10.1 in Ozone.
   Iceberg has added the capability to rewrite deletion vector files and partition statistics files in iceberg version 1.11.0 
   so when we start support 1.11.0 in ozone then we can look forward to support those file rewrites as well.



-- 
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: issues-unsubscribe@ozone.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org


Re: [PR] HDDS-14945. Implement Iceberg position delete file rewrite for path migration [ozone]

Posted by "peterxcli (via GitHub)" <gi...@apache.org>.
peterxcli merged PR #10306:
URL: https://github.com/apache/ozone/pull/10306


-- 
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: issues-unsubscribe@ozone.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org


Re: [PR] HDDS-14945. Implement Iceberg position delete file rewrite for path migration [ozone]

Posted by "adoroszlai (via GitHub)" <gi...@apache.org>.
adoroszlai commented on PR #10306:
URL: https://github.com/apache/ozone/pull/10306#issuecomment-4573094338

   Thanks @sreejasahithi for updating the patch.  Can you please split build changes into a separate PR, to be merged before this one?  I will review it.


-- 
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: issues-unsubscribe@ozone.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org


Re: [PR] HDDS-14945. Implement Iceberg position delete file rewrite for path migration [ozone]

Posted by "sreejasahithi (via GitHub)" <gi...@apache.org>.
sreejasahithi commented on PR #10306:
URL: https://github.com/apache/ozone/pull/10306#issuecomment-4486519251

   @ashishkumar50 could you please review this patch.


-- 
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: issues-unsubscribe@ozone.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org


Re: [PR] HDDS-14945. Implement Iceberg position delete file rewrite for path migration [ozone]

Posted by "peterxcli (via GitHub)" <gi...@apache.org>.
peterxcli commented on code in PR #10306:
URL: https://github.com/apache/ozone/pull/10306#discussion_r3283083455


##########
hadoop-ozone/iceberg/src/main/java/org/apache/hadoop/ozone/iceberg/RewriteTablePathOzoneAction.java:
##########
@@ -675,4 +695,188 @@ private static RewriteResult<DeleteFile> writeDeleteManifest(
       throw new RuntimeIOException(e);
     }
   }
+
+  static class OzonePositionDeleteReaderWriter implements RewriteTablePathUtil.PositionDeleteReaderWriter {
+    @Override
+    public CloseableIterable<Record> reader(
+        InputFile inputFile, FileFormat format, PartitionSpec spec) {
+      return positionDeletesReader(inputFile, format, spec);
+    }
+
+    @Override
+    public PositionDeleteWriter<Record> writer(
+        OutputFile outputFile,
+        FileFormat format,
+        PartitionSpec spec,
+        StructLike partition,
+        Schema rowSchema)
+        throws IOException {
+      return positionDeletesWriter(outputFile, format, spec, partition, rowSchema);
+    }
+  }
+
+  private void rewritePositionDeletes(Set<DeleteFile> toRewrite) {
+    /*
+     * NOTE: Rewriting position delete files updates embedded data file paths, which changes the
+     * resulting file size. This causes a metadata mismatch in the manifests:
+     *
+     * 1. Dependency: Manifests MUST be rewritten first because they are the source of truth used to identify which 
+     *    position delete files exist and need processing.
+     * 2. Issue: Because manifests are written before the delete files are updated, the'file_size_in_bytes' field 
+     *    in the manifest reflects the original size, not the new size.
+     * 3. Impact: Some catalogs (e.g., REST catalogs like Polaris) will fail to read these files as the reader uses
+     *    the stale size from the manifest.
+     *
+     * This is a known Iceberg limitation being addressed by the Iceberg community. Once that fix is available
+     * in the Iceberg core, this action should be updated accordingly.
+     */
+    if (toRewrite.isEmpty()) {
+      return;
+    }
+    
+    RewriteTablePathUtil.PositionDeleteReaderWriter posDeleteReaderWriter = new OzonePositionDeleteReaderWriter();
+    int maxInFlight = parallelism * MAX_INFLIGHT_MULTIPLIER;
+    Semaphore semaphore = new Semaphore(maxInFlight);
+    ExecutorCompletionService<Void> completionService = new ExecutorCompletionService<>(executorService);
+    int submittedTasks = 0;
+    int completedTasks = 0;
+
+    try {
+      for (DeleteFile deleteFile : toRewrite) {
+        semaphore.acquire();
+        boolean taskSubmitted = false;
+        try {
+          completionService.submit(() -> {
+            try {
+              rewritePositionDelete(deleteFile, table, sourcePrefix, targetPrefix, stagingDir, posDeleteReaderWriter);
+              return null;
+            } finally {
+              semaphore.release();
+            }
+          });
+          taskSubmitted = true;
+          submittedTasks++;
+        } finally {
+          if (!taskSubmitted) {
+            semaphore.release();
+          }
+        }

Review Comment:
   Got it, make sense. Thanks for the explanation!
   



-- 
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: issues-unsubscribe@ozone.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org


Re: [PR] HDDS-14945. Implement Iceberg position delete file rewrite for path migration [ozone]

Posted by "sreejasahithi (via GitHub)" <gi...@apache.org>.
sreejasahithi commented on code in PR #10306:
URL: https://github.com/apache/ozone/pull/10306#discussion_r3280533213


##########
hadoop-ozone/iceberg/src/main/java/org/apache/hadoop/ozone/iceberg/RewriteTablePathOzoneAction.java:
##########
@@ -255,6 +268,13 @@ private String rebuildMetadata() {
     RewriteContentFileResult rewriteManifestResult =
         rewriteManifests(deltaSnapshotIds, endMetadata, rewriteManifestListResult.toRewrite());
 
+    Set<DeleteFile> deleteFiles =
+        rewriteManifestResult.toRewrite().stream()
+            .filter(e -> e instanceof DeleteFile)
+            .map(e -> (DeleteFile) e)
+            .collect(Collectors.toSet());
+    rewritePositionDeletes(deleteFiles);

Review Comment:
   Currently we are supporting Iceberg version 1.10.1 in Ozone.
   Iceberg has added the capability to rewrite deletion vector files and partition statistics files in iceberg version 1.11.0 
   so when we support 1.11.0 in ozone then we can look forward to support those file rewrites as well.



-- 
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: issues-unsubscribe@ozone.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org


Re: [PR] HDDS-14945. Implement Iceberg position delete file rewrite for path migration [ozone]

Posted by "sreejasahithi (via GitHub)" <gi...@apache.org>.
sreejasahithi commented on code in PR #10306:
URL: https://github.com/apache/ozone/pull/10306#discussion_r3281240596


##########
hadoop-ozone/iceberg/src/main/java/org/apache/hadoop/ozone/iceberg/RewriteTablePathOzoneAction.java:
##########
@@ -675,4 +695,188 @@ private static RewriteResult<DeleteFile> writeDeleteManifest(
       throw new RuntimeIOException(e);
     }
   }
+
+  static class OzonePositionDeleteReaderWriter implements RewriteTablePathUtil.PositionDeleteReaderWriter {
+    @Override
+    public CloseableIterable<Record> reader(
+        InputFile inputFile, FileFormat format, PartitionSpec spec) {
+      return positionDeletesReader(inputFile, format, spec);
+    }
+
+    @Override
+    public PositionDeleteWriter<Record> writer(
+        OutputFile outputFile,
+        FileFormat format,
+        PartitionSpec spec,
+        StructLike partition,
+        Schema rowSchema)
+        throws IOException {
+      return positionDeletesWriter(outputFile, format, spec, partition, rowSchema);
+    }
+  }
+
+  private void rewritePositionDeletes(Set<DeleteFile> toRewrite) {
+    /*
+     * NOTE: Rewriting position delete files updates embedded data file paths, which changes the
+     * resulting file size. This causes a metadata mismatch in the manifests:
+     *
+     * 1. Dependency: Manifests MUST be rewritten first because they are the source of truth used to identify which 
+     *    position delete files exist and need processing.
+     * 2. Issue: Because manifests are written before the delete files are updated, the'file_size_in_bytes' field 
+     *    in the manifest reflects the original size, not the new size.
+     * 3. Impact: Some catalogs (e.g., REST catalogs like Polaris) will fail to read these files as the reader uses
+     *    the stale size from the manifest.
+     *
+     * This is a known Iceberg limitation being addressed by the Iceberg community. Once that fix is available
+     * in the Iceberg core, this action should be updated accordingly.
+     */
+    if (toRewrite.isEmpty()) {
+      return;
+    }
+    
+    RewriteTablePathUtil.PositionDeleteReaderWriter posDeleteReaderWriter = new OzonePositionDeleteReaderWriter();
+    int maxInFlight = parallelism * MAX_INFLIGHT_MULTIPLIER;
+    Semaphore semaphore = new Semaphore(maxInFlight);
+    ExecutorCompletionService<Void> completionService = new ExecutorCompletionService<>(executorService);
+    int submittedTasks = 0;
+    int completedTasks = 0;
+
+    try {
+      for (DeleteFile deleteFile : toRewrite) {
+        semaphore.acquire();
+        boolean taskSubmitted = false;
+        try {
+          completionService.submit(() -> {
+            try {
+              rewritePositionDelete(deleteFile, table, sourcePrefix, targetPrefix, stagingDir, posDeleteReaderWriter);
+              return null;
+            } finally {
+              semaphore.release();
+            }
+          });
+          taskSubmitted = true;
+          submittedTasks++;
+        } finally {
+          if (!taskSubmitted) {
+            semaphore.release();
+          }
+        }

Review Comment:
   The Semaphore + ExecutorCompletionService pattern here is intentional, and here is the reasoning:
   
   `Executors.newFixedThreadPool` uses an unbounded `LinkedBlockingQueue` internally. While the thread count limits concurrent execution, all submitted tasks are still queued immediately. For a table with a very large number of position delete files, submitting everything upfront would create a task wrapper per file and can lead to significant memory pressure.
   
   The semaphore provides explicit backpressure and keeps the number of tasks admitted into the executor bounded at any point in time.
   
   The `ExecutorCompletionService` polling inside the submission loop allows us to opportunistically drain already completed tasks during submission. This helps surface task failures earlier during execution compared to waiting for a final drain phase after all submissions are completed.
   
   A bounded `ThreadPoolExecutor` with an `ArrayBlockingQueue` is an alternative, but it does not eliminate the need for backpressure control or result tracking. When the queue fills, behaviour depends on the rejection policy:
   
   * `CallerRunsPolicy` changes execution semantics by running tasks on the submitting thread, which can stall submission progress, this naturally slows down the rate of new task submissions.
   * custom RejectedExecutionHandler provides similar backpressure behavior to `Semaphore.acquire()`, but moves the control flow into executor internals rather than keeping it explicit at the call site.
   
   In both cases, `ExecutorCompletionService` is still required for result collection and error propagation. The semaphore keeps the backpressure and concurrency limits explicit and visible in the code, rather than implicit in executor configuration.
   



-- 
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: issues-unsubscribe@ozone.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org