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

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


The following commit(s) were added to refs/heads/master by this push:
     new c049c02c1a [core] Parallelize row ID reassignment manifest rewrites 
(#9441)
c049c02c1a is described below

commit c049c02c1a4cbc3a94f0030b8205e2e8ebeac16b
Author: Jingsong Lee <[email protected]>
AuthorDate: Fri Aug 28 13:26:09 2026 +0800

    [core] Parallelize row ID reassignment manifest rewrites (#9441)
---
 .../DataEvolutionRowIdReassigner.java              | 117 ++++++++++++++++-----
 .../DataEvolutionRowIdReassignerTest.java          |  95 +++++++++++++++++
 2 files changed, 185 insertions(+), 27 deletions(-)

diff --git 
a/paimon-core/src/main/java/org/apache/paimon/append/dataevolution/DataEvolutionRowIdReassigner.java
 
b/paimon-core/src/main/java/org/apache/paimon/append/dataevolution/DataEvolutionRowIdReassigner.java
index 7c49757f07..c7e5e77a69 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/append/dataevolution/DataEvolutionRowIdReassigner.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/append/dataevolution/DataEvolutionRowIdReassigner.java
@@ -44,6 +44,7 @@ import org.apache.paimon.utils.CloseableIterator;
 import org.apache.paimon.utils.Pair;
 import org.apache.paimon.utils.Range;
 import org.apache.paimon.utils.RetryWaiter;
+import org.apache.paimon.utils.ThreadPoolUtils.CloseableBatchIterator;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -60,7 +61,11 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Function;
 
+import static java.util.Collections.singletonList;
+import static 
org.apache.paimon.utils.ManifestReadThreadPool.sequentialBatchedExecuteCloseable;
 import static org.apache.paimon.utils.Preconditions.checkArgument;
 import static org.apache.paimon.utils.Preconditions.checkState;
 
@@ -73,6 +78,7 @@ public class DataEvolutionRowIdReassigner {
     private final FileStoreTable table;
     private final @Nullable PartitionPredicate partitionPredicate;
     private final Runnable beforeCommit;
+    private final Consumer<ManifestFileMeta> beforeManifestRewrite;
 
     public DataEvolutionRowIdReassigner(FileStoreTable table) {
         this(table, null);
@@ -88,9 +94,19 @@ public class DataEvolutionRowIdReassigner {
             FileStoreTable table,
             @Nullable PartitionPredicate partitionPredicate,
             Runnable beforeCommit) {
+        this(table, partitionPredicate, beforeCommit, manifest -> {});
+    }
+
+    @VisibleForTesting
+    DataEvolutionRowIdReassigner(
+            FileStoreTable table,
+            @Nullable PartitionPredicate partitionPredicate,
+            Runnable beforeCommit,
+            Consumer<ManifestFileMeta> beforeManifestRewrite) {
         this.table = table;
         this.partitionPredicate = partitionPredicate;
         this.beforeCommit = beforeCommit;
+        this.beforeManifestRewrite = beforeManifestRewrite;
     }
 
     public Result reassign() {
@@ -612,39 +628,71 @@ public class DataEvolutionRowIdReassigner {
 
     private RewrittenDataManifests writeManifestReplacements(
             Assignment assignment, ManifestFile manifestFile) {
-        Map<String, List<ManifestFileMeta>> rewrittenManifestMetas = new 
HashMap<>();
-        long fileCount = 0L;
-        for (ManifestFileMeta manifestMeta : 
assignment.manifestMetasToRewrite) {
-            List<ManifestEntry> entries =
-                    manifestFile.read(manifestMeta.fileName(), 
manifestMeta.fileSize());
-            long reassignedAddFileCount = 0L;
-            boolean hasRewrittenEntry = false;
-            for (int i = 0; i < entries.size(); i++) {
-                ManifestEntry entry = entries.get(i);
-                RowRangeMappingIndex mapping = 
assignment.rowIdMappings.get(entry.partition());
-                if (mapping == null) {
-                    continue;
-                }
-                Optional<Range> reassignedRange = 
mapping.map(entry.file().nonNullRowIdRange());
-                if (reassignedRange.isPresent()) {
-                    validatePlanningEntry(entry);
-                    entries.set(i, 
entry.assignFirstRowId(reassignedRange.get().from));
-                    hasRewrittenEntry = true;
-                    if (entry.kind() == FileKind.ADD) {
-                        reassignedAddFileCount++;
-                    }
+        Integer parallelism = table.coreOptions().scanManifestParallelism();
+        List<RewrittenDataManifest> rewritten =
+                new ArrayList<>(assignment.manifestMetasToRewrite.size());
+        if (assignment.manifestMetasToRewrite.size() == 1
+                || (parallelism != null && parallelism == 1)) {
+            for (ManifestFileMeta manifestMeta : 
assignment.manifestMetasToRewrite) {
+                rewritten.add(rewriteDataManifest(assignment, manifestFile, 
manifestMeta));
+            }
+        } else {
+            Function<ManifestFileMeta, List<RewrittenDataManifest>> rewriter =
+                    manifestMeta ->
+                            singletonList(
+                                    rewriteDataManifest(
+                                            assignment,
+                                            
table.store().manifestFileFactory().create(),
+                                            manifestMeta));
+            try (CloseableBatchIterator<RewrittenDataManifest> results =
+                    sequentialBatchedExecuteCloseable(
+                            rewriter, assignment.manifestMetasToRewrite, 
parallelism)) {
+                while (results.hasNext()) {
+                    rewritten.add(results.next());
                 }
             }
-            checkState(
-                    hasRewrittenEntry,
-                    "Cannot find entries to reassign in planned manifest %s.",
-                    manifestMeta.fileName());
-            rewrittenManifestMetas.put(manifestMeta.fileName(), 
manifestFile.write(entries));
-            fileCount += reassignedAddFileCount;
+        }
+
+        Map<String, List<ManifestFileMeta>> rewrittenManifestMetas = new 
LinkedHashMap<>();
+        long fileCount = 0L;
+        for (RewrittenDataManifest manifest : rewritten) {
+            rewrittenManifestMetas.put(manifest.originalFileName, 
manifest.replacements);
+            fileCount += manifest.reassignedAddFileCount;
         }
         return new RewrittenDataManifests(rewrittenManifestMetas, fileCount);
     }
 
+    private RewrittenDataManifest rewriteDataManifest(
+            Assignment assignment, ManifestFile manifestFile, ManifestFileMeta 
manifestMeta) {
+        beforeManifestRewrite.accept(manifestMeta);
+        List<ManifestEntry> entries =
+                manifestFile.read(manifestMeta.fileName(), 
manifestMeta.fileSize());
+        long reassignedAddFileCount = 0L;
+        boolean hasRewrittenEntry = false;
+        for (int i = 0; i < entries.size(); i++) {
+            ManifestEntry entry = entries.get(i);
+            RowRangeMappingIndex mapping = 
assignment.rowIdMappings.get(entry.partition());
+            if (mapping == null) {
+                continue;
+            }
+            Optional<Range> reassignedRange = 
mapping.map(entry.file().nonNullRowIdRange());
+            if (reassignedRange.isPresent()) {
+                validatePlanningEntry(entry);
+                entries.set(i, 
entry.assignFirstRowId(reassignedRange.get().from));
+                hasRewrittenEntry = true;
+                if (entry.kind() == FileKind.ADD) {
+                    reassignedAddFileCount++;
+                }
+            }
+        }
+        checkState(
+                hasRewrittenEntry,
+                "Cannot find entries to reassign in planned manifest %s.",
+                manifestMeta.fileName());
+        return new RewrittenDataManifest(
+                manifestMeta.fileName(), manifestFile.write(entries), 
reassignedAddFileCount);
+    }
+
     private void validatePlanningEntry(ManifestEntry entry) {
         List<String> writeCols = entry.file().writeCols();
         checkState(
@@ -902,6 +950,21 @@ public class DataEvolutionRowIdReassigner {
         }
     }
 
+    private static class RewrittenDataManifest {
+        private final String originalFileName;
+        private final List<ManifestFileMeta> replacements;
+        private final long reassignedAddFileCount;
+
+        private RewrittenDataManifest(
+                String originalFileName,
+                List<ManifestFileMeta> replacements,
+                long reassignedAddFileCount) {
+            this.originalFileName = originalFileName;
+            this.replacements = replacements;
+            this.reassignedAddFileCount = reassignedAddFileCount;
+        }
+    }
+
     private static class CommittedAssignment {
         private final Assignment assignment;
         private final CommitAssignmentResult commitResult;
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/append/dataevolution/DataEvolutionRowIdReassignerTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/append/dataevolution/DataEvolutionRowIdReassignerTest.java
index af0178bb27..0b6fbb6141 100644
--- 
a/paimon-core/src/test/java/org/apache/paimon/append/dataevolution/DataEvolutionRowIdReassignerTest.java
+++ 
b/paimon-core/src/test/java/org/apache/paimon/append/dataevolution/DataEvolutionRowIdReassignerTest.java
@@ -87,6 +87,11 @@ import java.util.Optional;
 import java.util.Random;
 import java.util.Set;
 import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -1321,6 +1326,96 @@ public class DataEvolutionRowIdReassignerTest extends 
TableTestBase {
         
assertThat(afterManifestFiles).doesNotContainAnyElementsOf(affectedManifests);
     }
 
+    @Test
+    public void testReassignRewritesDataManifestsInParallel() throws Exception 
{
+        FileStoreTable originalTable = createTableWithInterleavedPartitions();
+        assertThat(dataManifestFileNames(originalTable)).hasSizeGreaterThan(1);
+        FileStoreTable table =
+                originalTable.copy(
+                        
Collections.singletonMap(CoreOptions.SCAN_MANIFEST_PARALLELISM.key(), "2"));
+
+        CountDownLatch twoRewritesStarted = new CountDownLatch(2);
+        CountDownLatch releaseRewrites = new CountDownLatch(1);
+        AtomicInteger startedRewriteCount = new AtomicInteger();
+        ExecutorService executor = Executors.newSingleThreadExecutor();
+        Future<DataEvolutionRowIdReassigner.Result> future =
+                executor.submit(
+                        () ->
+                                new DataEvolutionRowIdReassigner(
+                                                table,
+                                                null,
+                                                () -> {},
+                                                ignored -> {
+                                                    
startedRewriteCount.incrementAndGet();
+                                                    
twoRewritesStarted.countDown();
+                                                    try {
+                                                        if 
(!releaseRewrites.await(
+                                                                30, 
TimeUnit.SECONDS)) {
+                                                            throw new 
AssertionError(
+                                                                    "Timed out 
waiting to release manifest rewrites.");
+                                                        }
+                                                    } catch 
(InterruptedException e) {
+                                                        
Thread.currentThread().interrupt();
+                                                        throw new 
RuntimeException(e);
+                                                    }
+                                                })
+                                        
.reassign("test-parallel-manifest-rewrite"));
+
+        boolean rewritesOverlapped;
+        int startedBeforeRelease;
+        try {
+            rewritesOverlapped = twoRewritesStarted.await(10, 
TimeUnit.SECONDS);
+            startedBeforeRelease = startedRewriteCount.get();
+        } finally {
+            releaseRewrites.countDown();
+        }
+
+        try {
+            DataEvolutionRowIdReassigner.Result result = future.get(30, 
TimeUnit.SECONDS);
+            assertThat(result.fileCount).isEqualTo(5L);
+            assertThat(rowIdsByPartition(table))
+                    .containsEntry("pt=a/", Arrays.asList(5L, 6L, 7L))
+                    .containsEntry("pt=b/", Arrays.asList(8L, 9L));
+        } finally {
+            executor.shutdownNow();
+        }
+        assertThat(rewritesOverlapped).isTrue();
+        assertThat(startedBeforeRelease).isEqualTo(2);
+    }
+
+    @Test
+    public void testReassignDoesNotCommitWhenParallelManifestRewriteFails() 
throws Exception {
+        FileStoreTable originalTable = createTableWithInterleavedPartitions();
+        FileStoreTable table =
+                originalTable.copy(
+                        
Collections.singletonMap(CoreOptions.SCAN_MANIFEST_PARALLELISM.key(), "2"));
+        Snapshot before = table.snapshotManager().latestSnapshot();
+        AtomicBoolean failureInjected = new AtomicBoolean();
+
+        assertThatThrownBy(
+                        () ->
+                                new DataEvolutionRowIdReassigner(
+                                                table,
+                                                null,
+                                                () -> {},
+                                                ignored -> {
+                                                    if 
(failureInjected.compareAndSet(
+                                                            false, true)) {
+                                                        throw new 
IllegalStateException(
+                                                                "Injected 
manifest rewrite failure.");
+                                                    }
+                                                })
+                                        
.reassign("test-failed-parallel-manifest-rewrite"))
+                .isInstanceOf(IllegalStateException.class)
+                .hasMessage("Injected manifest rewrite failure.");
+
+        assertThat(failureInjected).isTrue();
+        
assertThat(table.snapshotManager().latestSnapshot().id()).isEqualTo(before.id());
+        assertThat(rowIdsByPartition(table))
+                .containsEntry("pt=a/", Arrays.asList(0L, 2L, 4L))
+                .containsEntry("pt=b/", Arrays.asList(1L, 3L));
+    }
+
     @Test
     public void testReassignDoesNotCompactManifests() throws Exception {
         testReassignSkipsManifestOptimization(false);

Reply via email to