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 c6418dd5ad [core] Avoid external sort for fragmented manifests (#9257)
c6418dd5ad is described below

commit c6418dd5ad6c066278ecbbccee8ef54679c7e127
Author: YeJunHao <[email protected]>
AuthorDate: Mon Aug 17 13:42:29 2026 +0800

    [core] Avoid external sort for fragmented manifests (#9257)
---
 .../paimon/operation/ManifestEntryRunMerge.java    |  19 +--
 .../operation/ManifestEntryRunMergePlan.java       |  21 +--
 .../paimon/manifest/ManifestFileMetaTest.java      |   4 +-
 .../operation/ManifestEntryRunMergeTest.java       | 152 +++++++++++++++++++++
 4 files changed, 162 insertions(+), 34 deletions(-)

diff --git 
a/paimon-core/src/main/java/org/apache/paimon/operation/ManifestEntryRunMerge.java
 
b/paimon-core/src/main/java/org/apache/paimon/operation/ManifestEntryRunMerge.java
index 18cd08dfb3..7eb4f66e32 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/operation/ManifestEntryRunMerge.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/operation/ManifestEntryRunMerge.java
@@ -59,7 +59,6 @@ import static 
org.apache.paimon.utils.Preconditions.checkState;
 final class ManifestEntryRunMerge {
 
     private static final int FRAGMENTED_RUN_THRESHOLD = 64;
-    private static final long MAX_IN_MEMORY_FRAGMENTED_ENTRIES = 25_000L;
     private static final int MAX_STREAM_CURSORS = 128;
     private static final int MAX_STREAM_READ_AMPLIFICATION = 8;
 
@@ -93,7 +92,7 @@ final class ManifestEntryRunMerge {
         if (plan == null) {
             return null;
         }
-        return plan.mergeToManifest(sortKey, manifestFile, newFilesForAbort);
+        return plan.mergeToManifest(manifestFile, newFilesForAbort);
     }
 
     /**
@@ -125,7 +124,7 @@ final class ManifestEntryRunMerge {
             if (plan == null) {
                 return null;
             }
-            return plan.mergeMinorToManifest(sortKey, manifestFile, 
newFilesForAbort);
+            return plan.mergeMinorToManifest(manifestFile, newFilesForAbort);
         } finally {
             deletes.release();
         }
@@ -146,7 +145,6 @@ final class ManifestEntryRunMerge {
                 new SortPartitionDictionary(sortKey::comparePartitions);
         List<ManifestEntryRunMergePlan.Source.Spec> sources = new 
ArrayList<>();
         int streamCursorCount = 0;
-        long inMemoryEntries = 0;
         List<Discovery.DiscoveredManifest> discovered = new 
ArrayList<>(section.size());
         if (section.size() <= 1
                 || (manifestReadParallelism != null && manifestReadParallelism 
<= 1)) {
@@ -234,11 +232,6 @@ final class ManifestEntryRunMerge {
             ManifestFileMeta meta = section.get(manifestIndex);
             Discovery.DiscoveredManifest manifest = 
discovered.get(manifestIndex);
             if (manifest.fragmented) {
-                long entryCount = meta.numAddedFiles() + 
meta.numDeletedFiles();
-                inMemoryEntries += entryCount;
-                if (inMemoryEntries > MAX_IN_MEMORY_FRAGMENTED_ENTRIES) {
-                    return null;
-                }
                 sources.add(new 
ManifestEntryRunMergePlan.Source.FragmentedManifestSpec(meta));
                 streamCursorCount++;
             } else {
@@ -297,7 +290,6 @@ final class ManifestEntryRunMerge {
         boolean hasPrevious = false;
         long runStart = 0;
         long position = 0;
-        long entryCount = meta.numAddedFiles() + meta.numDeletedFiles();
         boolean fragmented = false;
         ProjectedManifestEntry entry = 
ProjectedManifestEntry.ENTRY_LAYOUT_PROJECTION.createEntry();
         while (reader.hasNext()) {
@@ -336,9 +328,6 @@ final class ManifestEntryRunMerge {
                                     meta, runStart, position, blocks));
                     runStart = position;
                     if (runs.size() >= FRAGMENTED_RUN_THRESHOLD) {
-                        if (entryCount > MAX_IN_MEMORY_FRAGMENTED_ENTRIES) {
-                            return 
Discovery.DiscoveredManifest.requiresExternalSort();
-                        }
                         fragmented = true;
                         runs.clear();
                         blocks.clear();
@@ -366,9 +355,7 @@ final class ManifestEntryRunMerge {
                             meta, runStart, position, blocks));
         }
         if (exceedsStreamingReadAmplification(runs, blocks.size())) {
-            return entryCount > MAX_IN_MEMORY_FRAGMENTED_ENTRIES
-                    ? Discovery.DiscoveredManifest.requiresExternalSort()
-                    : Discovery.DiscoveredManifest.fragmented();
+            return Discovery.DiscoveredManifest.fragmented();
         }
         return Discovery.DiscoveredManifest.runs(runs, blocks);
     }
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/operation/ManifestEntryRunMergePlan.java
 
b/paimon-core/src/main/java/org/apache/paimon/operation/ManifestEntryRunMergePlan.java
index 997bfb362b..1b1d9562a0 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/operation/ManifestEntryRunMergePlan.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/operation/ManifestEntryRunMergePlan.java
@@ -69,15 +69,12 @@ final class ManifestEntryRunMergePlan {
     }
 
     List<ManifestFileMeta> mergeToManifest(
-            ManifestFileSorter.RowIdEntrySortKey sortKey,
-            ManifestFile manifestFile,
-            List<ManifestFileMeta> newFilesForAbort)
-            throws Exception {
+            ManifestFile manifestFile, List<ManifestFileMeta> 
newFilesForAbort) throws Exception {
         List<Cursor> cursors = new ArrayList<>(sources.size());
         Exception failure = null;
         try {
             for (Source.Spec source : sources) {
-                Cursor cursor = source.open(manifestFile, sortKey, deletes, 
minor, partitions);
+                Cursor cursor = source.open(manifestFile, deletes, minor, 
partitions);
                 cursors.add(cursor);
                 cursor.advance();
             }
@@ -104,15 +101,12 @@ final class ManifestEntryRunMergePlan {
     }
 
     Pair<List<ManifestFileMeta>, List<ManifestFileMeta>> mergeMinorToManifest(
-            ManifestFileSorter.RowIdEntrySortKey sortKey,
-            ManifestFile manifestFile,
-            List<ManifestFileMeta> newFilesForAbort)
-            throws Exception {
+            ManifestFile manifestFile, List<ManifestFileMeta> 
newFilesForAbort) throws Exception {
         List<Cursor> cursors = new ArrayList<>(sources.size());
         Exception failure = null;
         try {
             for (Source.Spec source : sources) {
-                Cursor cursor = source.open(manifestFile, sortKey, deletes, 
minor, partitions);
+                Cursor cursor = source.open(manifestFile, deletes, minor, 
partitions);
                 cursors.add(cursor);
                 cursor.advance();
             }
@@ -264,7 +258,6 @@ final class ManifestEntryRunMergePlan {
 
             Cursor open(
                     ManifestFile manifestFile,
-                    ManifestFileSorter.RowIdEntrySortKey sortKey,
                     CollectedDeletes deletes,
                     boolean minor,
                     ManifestEntryRunMerge.SortPartitionDictionary partitions)
@@ -306,7 +299,6 @@ final class ManifestEntryRunMergePlan {
             @Override
             public Cursor open(
                     ManifestFile manifestFile,
-                    ManifestFileSorter.RowIdEntrySortKey sortKey,
                     CollectedDeletes deletes,
                     boolean minor,
                     ManifestEntryRunMerge.SortPartitionDictionary partitions)
@@ -327,13 +319,11 @@ final class ManifestEntryRunMergePlan {
             @Override
             public Cursor open(
                     ManifestFile manifestFile,
-                    ManifestFileSorter.RowIdEntrySortKey sortKey,
                     CollectedDeletes deletes,
                     boolean minor,
                     ManifestEntryRunMerge.SortPartitionDictionary partitions)
                     throws Exception {
-                return new InMemoryManifestCursor(
-                        manifestFile, meta, sortKey, deletes, minor, 
partitions);
+                return new InMemoryManifestCursor(manifestFile, meta, deletes, 
minor, partitions);
             }
         }
     }
@@ -673,7 +663,6 @@ final class ManifestEntryRunMergePlan {
         InMemoryManifestCursor(
                 ManifestFile manifestFile,
                 ManifestFileMeta meta,
-                ManifestFileSorter.RowIdEntrySortKey sortKey,
                 CollectedDeletes deletes,
                 boolean minor,
                 ManifestEntryRunMerge.SortPartitionDictionary partitions)
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/manifest/ManifestFileMetaTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/manifest/ManifestFileMetaTest.java
index fe345048c3..6da1b8d2f7 100644
--- 
a/paimon-core/src/test/java/org/apache/paimon/manifest/ManifestFileMetaTest.java
+++ 
b/paimon-core/src/test/java/org/apache/paimon/manifest/ManifestFileMetaTest.java
@@ -1922,7 +1922,7 @@ public class ManifestFileMetaTest extends 
ManifestFileMetaTestBase {
     }
 
     @Test
-    public void 
testDataEvolutionManifestRunMergeFallsBackForLargeFragmentedManifest() {
+    public void 
testDataEvolutionManifestRunMergeSortsLargeFragmentedManifest() {
         List<ManifestEntry> firstManifest = new ArrayList<>();
         List<ManifestEntry> secondManifest = new ArrayList<>();
         for (long firstRowId = 25_000; firstRowId >= 12_500; firstRowId--) {
@@ -1956,7 +1956,7 @@ public class ManifestFileMetaTest extends 
ManifestFileMetaTestBase {
     }
 
     @Test
-    public void 
testDataEvolutionMinorRunMergeFallsBackForLargeFragmentedManifest() {
+    public void testDataEvolutionMinorRunMergeSortsLargeFragmentedManifest() {
         List<ManifestEntry> fragmentedEntries = new ArrayList<>();
         for (long firstRowId = 25_000; firstRowId >= 0; firstRowId--) {
             fragmentedEntries.add(
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/operation/ManifestEntryRunMergeTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/operation/ManifestEntryRunMergeTest.java
new file mode 100644
index 0000000000..f93d1e7134
--- /dev/null
+++ 
b/paimon-core/src/test/java/org/apache/paimon/operation/ManifestEntryRunMergeTest.java
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.paimon.operation;
+
+import org.apache.paimon.data.BinaryRow;
+import org.apache.paimon.data.BinaryRowWriter;
+import org.apache.paimon.data.Timestamp;
+import org.apache.paimon.io.DataFileMeta;
+import org.apache.paimon.manifest.CollectedDeletes;
+import org.apache.paimon.manifest.FileKind;
+import org.apache.paimon.manifest.FileSource;
+import org.apache.paimon.manifest.ManifestEntry;
+import org.apache.paimon.manifest.ManifestFile;
+import org.apache.paimon.manifest.ManifestFileMeta;
+import org.apache.paimon.manifest.ManifestFileMetaTestBase;
+import org.apache.paimon.stats.StatsTestUtils;
+import org.apache.paimon.types.DataTypes;
+import org.apache.paimon.types.RowType;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/** Tests for {@link ManifestEntryRunMerge}. */
+class ManifestEntryRunMergeTest extends ManifestFileMetaTestBase {
+
+    private static final int ENTRY_COUNT = 25_001;
+
+    @TempDir java.nio.file.Path tempDir;
+
+    private final RowType partitionType = RowType.of(DataTypes.INT());
+    private final BinaryRow partition = new BinaryRow(1);
+    private ManifestFile manifestFile;
+
+    @BeforeEach
+    void beforeEach() {
+        BinaryRowWriter writer = new BinaryRowWriter(partition);
+        writer.writeInt(0, 0);
+        writer.complete();
+        manifestFile = createManifestFile(tempDir.toString());
+    }
+
+    @Test
+    void testLargeFragmentedManifestsUseRunMerge() throws Exception {
+        List<ManifestEntry> firstManifest = new ArrayList<>();
+        List<ManifestEntry> secondManifest = new ArrayList<>();
+        for (long firstRowId = ENTRY_COUNT - 1L; firstRowId >= 0; 
firstRowId--) {
+            ManifestEntry entry = rowIdEntry("row-" + firstRowId, firstRowId);
+            (firstRowId >= ENTRY_COUNT / 2 ? firstManifest : 
secondManifest).add(entry);
+        }
+
+        List<ManifestFileMeta> manifests = new ArrayList<>();
+        manifests.add(makeManifest(firstManifest.toArray(new 
ManifestEntry[0])));
+        manifests.add(makeManifest(secondManifest.toArray(new 
ManifestEntry[0])));
+        ManifestFileSorter.RowIdEntrySortKey sortKey =
+                (ManifestFileSorter.RowIdEntrySortKey)
+                        ManifestFileSorter.createSortKey(true, manifests, 
null, partitionType);
+
+        CollectedDeletes deletes = new CollectedDeletes(true);
+        List<ManifestFileMeta> output;
+        try {
+            output =
+                    ManifestEntryRunMerge.sortAndWriteFullEntries(
+                            manifests,
+                            sortKey,
+                            partitionType,
+                            manifestFile,
+                            new ArrayList<>(),
+                            deletes,
+                            128,
+                            1);
+        } finally {
+            deletes.release();
+        }
+
+        assertThat(output).isNotNull();
+        assertThat(
+                        output.stream()
+                                .flatMap(
+                                        meta ->
+                                                
manifestFile.read(meta.fileName(), meta.fileSize())
+                                                        .stream())
+                                .map(entry -> entry.file().nonNullFirstRowId())
+                                .collect(Collectors.toList()))
+                .containsExactlyElementsOf(
+                        java.util.stream.LongStream.range(0, ENTRY_COUNT)
+                                .boxed()
+                                .collect(Collectors.toList()));
+    }
+
+    private ManifestEntry rowIdEntry(String fileName, long firstRowId) {
+        return ManifestEntry.create(
+                FileKind.ADD,
+                partition,
+                0,
+                0,
+                DataFileMeta.create(
+                        fileName,
+                        0,
+                        1,
+                        partition,
+                        partition,
+                        StatsTestUtils.newEmptySimpleStats(),
+                        StatsTestUtils.newEmptySimpleStats(),
+                        0,
+                        0,
+                        0,
+                        0,
+                        Collections.emptyList(),
+                        Timestamp.fromEpochMillis(200000),
+                        0L,
+                        null,
+                        FileSource.APPEND,
+                        null,
+                        null,
+                        firstRowId,
+                        Collections.singletonList("f0")));
+    }
+
+    @Override
+    protected ManifestFile getManifestFile() {
+        return manifestFile;
+    }
+
+    @Override
+    protected RowType getPartitionType() {
+        return partitionType;
+    }
+}

Reply via email to