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 02302d8597 [core] Repeated append-only compaction retains intermediate 
files (#10448)
02302d8597 is described below

commit 02302d859727e96b6eae570e6c6ad32c19edcb61
Author: Ran Tao <[email protected]>
AuthorDate: Fri Oct 9 21:56:06 2026 +0800

    [core] Repeated append-only compaction retains intermediate files (#10448)
---
 .../org/apache/paimon/append/AppendOnlyWriter.java |  50 ++++++----
 .../apache/paimon/append/AppendOnlyWriterTest.java | 109 +++++++++++++++++++++
 2 files changed, 142 insertions(+), 17 deletions(-)

diff --git 
a/paimon-core/src/main/java/org/apache/paimon/append/AppendOnlyWriter.java 
b/paimon-core/src/main/java/org/apache/paimon/append/AppendOnlyWriter.java
index 906573ad73..c26376395f 100644
--- a/paimon-core/src/main/java/org/apache/paimon/append/AppendOnlyWriter.java
+++ b/paimon-core/src/main/java/org/apache/paimon/append/AppendOnlyWriter.java
@@ -21,6 +21,7 @@ package org.apache.paimon.append;
 import org.apache.paimon.annotation.VisibleForTesting;
 import org.apache.paimon.compact.CompactDeletionFile;
 import org.apache.paimon.compact.CompactManager;
+import org.apache.paimon.compact.CompactResult;
 import org.apache.paimon.compression.CompressOptions;
 import org.apache.paimon.data.InternalRow;
 import org.apache.paimon.disk.IOManager;
@@ -58,6 +59,7 @@ import javax.annotation.Nullable;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Supplier;
@@ -92,8 +94,8 @@ public class AppendOnlyWriter implements BatchRecordWriter, 
MemoryOwner {
     private final boolean omitAllNonDedicatedWriteCols;
     private final List<DataFileMeta> newFiles;
     private final List<DataFileMeta> deletedFiles;
-    private final List<DataFileMeta> compactBefore;
-    private final List<DataFileMeta> compactAfter;
+    private final LinkedHashMap<String, DataFileMeta> compactBefore;
+    private final LinkedHashMap<String, DataFileMeta> compactAfter;
     private final Supplier<LongCounter> seqNumCounterProvider;
     private final String fileCompression;
     private final CompressOptions spillCompression;
@@ -160,8 +162,8 @@ public class AppendOnlyWriter implements BatchRecordWriter, 
MemoryOwner {
         this.omitAllNonDedicatedWriteCols = omitAllNonDedicatedWriteCols;
         this.newFiles = new ArrayList<>();
         this.deletedFiles = new ArrayList<>();
-        this.compactBefore = new ArrayList<>();
-        this.compactAfter = new ArrayList<>();
+        this.compactBefore = new LinkedHashMap<>();
+        this.compactAfter = new LinkedHashMap<>();
         final LongCounter seqNumCounter = new LongCounter(maxSequenceNumber + 
1);
         this.seqNumCounterProvider =
                 dataEvolutionEnabled ? () -> new LongCounter(0) : () -> 
seqNumCounter;
@@ -180,8 +182,14 @@ public class AppendOnlyWriter implements 
BatchRecordWriter, MemoryOwner {
         if (increment != null) {
             newFiles.addAll(increment.newFilesIncrement().newFiles());
             deletedFiles.addAll(increment.newFilesIncrement().deletedFiles());
-            compactBefore.addAll(increment.compactIncrement().compactBefore());
-            compactAfter.addAll(increment.compactIncrement().compactAfter());
+            increment
+                    .compactIncrement()
+                    .compactBefore()
+                    .forEach(file -> compactBefore.put(file.fileName(), file));
+            increment
+                    .compactIncrement()
+                    .compactAfter()
+                    .forEach(file -> compactAfter.put(file.fileName(), file));
             updateCompactDeletionFile(increment.compactDeletionFile());
         }
     }
@@ -285,7 +293,7 @@ public class AppendOnlyWriter implements BatchRecordWriter, 
MemoryOwner {
         sync();
 
         compactManager.close();
-        for (DataFileMeta file : compactAfter) {
+        for (DataFileMeta file : compactAfter.values()) {
             // appendOnlyCompactManager will rewrite the file and no file 
upgrade will occur, so we
             // can directly delete the file in compactAfter.
             file.collectFiles(pathFactory).forEach(fileIO::deleteQuietly);
@@ -367,14 +375,22 @@ public class AppendOnlyWriter implements 
BatchRecordWriter, MemoryOwner {
 
     private void trySyncLatestCompaction(boolean blocking)
             throws ExecutionException, InterruptedException {
-        compactManager
-                .getCompactionResult(blocking)
-                .ifPresent(
-                        result -> {
-                            compactBefore.addAll(result.before());
-                            compactAfter.addAll(result.after());
-                            updateCompactDeletionFile(result.deletionFile());
-                        });
+        
compactManager.getCompactionResult(blocking).ifPresent(this::updateCompactResult);
+    }
+
+    private void updateCompactResult(CompactResult result) {
+        for (DataFileMeta file : result.before()) {
+            DataFileMeta intermediate = compactAfter.remove(file.fileName());
+            if (intermediate != null) {
+                // Append compaction rewrites files. An output compacted again 
before the next
+                // prepareCommit has not been handed to a committer and can be 
deleted directly.
+                
intermediate.collectFiles(pathFactory).forEach(fileIO::deleteQuietly);
+            } else {
+                compactBefore.put(file.fileName(), file);
+            }
+        }
+        result.after().forEach(file -> compactAfter.put(file.fileName(), 
file));
+        updateCompactDeletionFile(result.deletionFile());
     }
 
     private void updateCompactDeletionFile(@Nullable CompactDeletionFile 
newDeletionFile) {
@@ -394,8 +410,8 @@ public class AppendOnlyWriter implements BatchRecordWriter, 
MemoryOwner {
                         Collections.emptyList());
         CompactIncrement compactIncrement =
                 new CompactIncrement(
-                        new ArrayList<>(compactBefore),
-                        new ArrayList<>(compactAfter),
+                        new ArrayList<>(compactBefore.values()),
+                        new ArrayList<>(compactAfter.values()),
                         Collections.emptyList());
         CompactDeletionFile drainDeletionFile = compactDeletionFile;
 
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/append/AppendOnlyWriterTest.java 
b/paimon-core/src/test/java/org/apache/paimon/append/AppendOnlyWriterTest.java
index 3b2464d311..742d219063 100644
--- 
a/paimon-core/src/test/java/org/apache/paimon/append/AppendOnlyWriterTest.java
+++ 
b/paimon-core/src/test/java/org/apache/paimon/append/AppendOnlyWriterTest.java
@@ -45,11 +45,15 @@ import org.apache.paimon.fs.Path;
 import org.apache.paimon.fs.local.LocalFileIO;
 import org.apache.paimon.io.DataFileMeta;
 import org.apache.paimon.io.DataFilePathFactory;
+import org.apache.paimon.manifest.FileEntry;
+import org.apache.paimon.manifest.FileKind;
 import org.apache.paimon.manifest.FileSource;
+import org.apache.paimon.manifest.ManifestEntry;
 import org.apache.paimon.memory.HeapMemorySegmentPool;
 import org.apache.paimon.memory.MemoryPoolFactory;
 import org.apache.paimon.operation.BaseAppendFileStoreWrite;
 import org.apache.paimon.operation.BlobFileContext;
+import org.apache.paimon.operation.commit.ManifestEntryChanges;
 import org.apache.paimon.options.MemorySize;
 import org.apache.paimon.options.Options;
 import org.apache.paimon.schema.Schema;
@@ -57,6 +61,7 @@ import org.apache.paimon.schema.TableSchema;
 import org.apache.paimon.stats.SimpleStatsConverter;
 import org.apache.paimon.table.AppendOnlyFileStoreTable;
 import org.apache.paimon.table.FileStoreTableFactory;
+import org.apache.paimon.table.sink.CommitMessageImpl;
 import org.apache.paimon.types.BlobType;
 import org.apache.paimon.types.DataType;
 import org.apache.paimon.types.DataTypes;
@@ -351,6 +356,79 @@ public class AppendOnlyWriterTest {
         assertThat(secInc.newFilesIncrement().newFiles()).hasSize(1);
     }
 
+    @ParameterizedTest
+    @ValueSource(booleans = {false, true})
+    public void testRepeatedCompaction(boolean commitBetweenCompactions) 
throws Exception {
+        List<DataFileMeta> inputs = createCompactionInputs(5);
+        AppendOnlyWriter writer = createWriter(1024 * 1024L, false, 
inputs.subList(0, 3)).getLeft();
+        try {
+            // The first compaction produces a small file which is eligible 
for compaction again.
+            writer.flush(false, false);
+            writer.sync();
+            assertThat(writer.dataFiles()).hasSize(1);
+            DataFileMeta intermediate = writer.dataFiles().iterator().next();
+            assertThat(intermediate.extraFiles()).hasSize(1);
+
+            List<CommitIncrement> increments = new ArrayList<>();
+            if (commitBetweenCompactions) {
+                CommitIncrement first = writer.prepareCommit(true);
+                assertThat(first.compactIncrement().compactBefore())
+                        .containsExactlyElementsOf(inputs.subList(0, 3));
+                
assertThat(first.compactIncrement().compactAfter()).containsExactly(intermediate);
+                increments.add(first);
+            }
+
+            // Add two more inputs so the next compaction rewrites the 
intermediate file.
+            writer.addNewFiles(inputs.subList(3, 5));
+            writer.flush(false, false);
+            writer.sync();
+            CommitIncrement last = writer.prepareCommit(true);
+            increments.add(last);
+
+            assertThat(last.compactIncrement().compactBefore())
+                    .containsExactlyInAnyOrderElementsOf(
+                            commitBetweenCompactions
+                                    ? Arrays.asList(intermediate, 
inputs.get(3), inputs.get(4))
+                                    : inputs);
+            assertThat(last.compactIncrement().compactAfter()).hasSize(1);
+            DataFileMeta output = 
last.compactIncrement().compactAfter().get(0);
+            assertThat(output.rowCount()).isEqualTo(5);
+
+            // A file handed to a previous commit must remain available even 
before that commit
+            // finishes. Otherwise, both the intermediate data file and its 
index can be removed.
+            for (Path path : intermediate.collectFiles(pathFactory)) {
+                
assertThat(LocalFileIO.create().exists(path)).isEqualTo(commitBetweenCompactions);
+            }
+            for (DataFileMeta file : inputs) {
+                
assertThat(LocalFileIO.create().exists(pathFactory.toPath(file))).isTrue();
+            }
+            for (Path path : output.collectFiles(pathFactory)) {
+                assertThat(LocalFileIO.create().exists(path)).isTrue();
+            }
+
+            List<ManifestEntry> entries = new ArrayList<>();
+            for (DataFileMeta file : inputs) {
+                entries.add(ManifestEntry.create(FileKind.ADD, 
BinaryRow.EMPTY_ROW, 0, 1, file));
+            }
+            ManifestEntryChanges changes = new ManifestEntryChanges(1);
+            for (CommitIncrement increment : increments) {
+                changes.collect(
+                        new CommitMessageImpl(
+                                BinaryRow.EMPTY_ROW,
+                                0,
+                                1,
+                                increment.newFilesIncrement(),
+                                increment.compactIncrement()));
+            }
+            entries.addAll(changes.compactTableFiles);
+            assertThat(FileEntry.mergeEntries(entries))
+                    .extracting(ManifestEntry::file)
+                    .containsExactly(output);
+        } finally {
+            writer.close();
+        }
+    }
+
     @Test
     public void testCloseUnexpectedly() throws Exception {
         final CountDownLatch latch = new CountDownLatch(1);
@@ -1317,6 +1395,37 @@ public class AppendOnlyWriterTest {
         return Pair.of(writer, compactManager.allFiles());
     }
 
+    private List<DataFileMeta> createCompactionInputs(int count) throws 
IOException {
+        List<DataFileMeta> files = new ArrayList<>();
+        for (int i = 0; i < count; i++) {
+            Path path = pathFactory.newPath("input-");
+            LocalFileIO.create().newOutputStream(path, false).close();
+            String name = String.format("%03d", i);
+            files.add(
+                    DataFileMeta.forAppend(
+                            path.getName(),
+                            10L,
+                            1L,
+                            STATS_SERIALIZER.toBinaryAllMode(
+                                    new SimpleColStats[] {
+                                        initStats(i, i, 0),
+                                        initStats(name, name, 0),
+                                        initStats(PART, PART, 0)
+                                    }),
+                            i,
+                            i,
+                            SCHEMA_ID,
+                            Collections.emptyList(),
+                            null,
+                            FileSource.APPEND,
+                            null,
+                            null,
+                            null,
+                            null));
+        }
+        return files;
+    }
+
     private DataFileMeta generateCompactAfter(List<DataFileMeta> toCompact) 
throws IOException {
         int size = toCompact.size();
         long minSeq = toCompact.get(0).minSequenceNumber();

Reply via email to