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 099464e7ec [common] Version the memory-mode cache key in CachingFileIO 
(#9642)
099464e7ec is described below

commit 099464e7ec17bae65af40ae2725b6d3b8e58b746
Author: YangJie <[email protected]>
AuthorDate: Fri Sep 11 03:10:24 2026 -0400

    [common] Version the memory-mode cache key in CachingFileIO (#9642)
---
 .../java/org/apache/paimon/utils/FileType.java     | 14 +++++-
 .../apache/paimon/fs/cache/CachingFileIOTest.java  | 57 ++++++++++++++++++++++
 .../java/org/apache/paimon/utils/FileTypeTest.java | 32 ++++++++++++
 3 files changed, 101 insertions(+), 2 deletions(-)

diff --git a/paimon-common/src/main/java/org/apache/paimon/utils/FileType.java 
b/paimon-common/src/main/java/org/apache/paimon/utils/FileType.java
index 46c767bb24..9b30c864c1 100644
--- a/paimon-common/src/main/java/org/apache/paimon/utils/FileType.java
+++ b/paimon-common/src/main/java/org/apache/paimon/utils/FileType.java
@@ -114,8 +114,18 @@ public enum FileType {
 
     /** Returns {@code true} if the file is mutable and should not be cached. 
*/
     public static boolean isMutable(Path filePath) {
-        String name = filePath.getName();
-        return "EARLIEST".equals(name) || "LATEST".equals(name);
+        String name = unwrapTempFileName(filePath.getName());
+        // Files rewritten in place under a stable path: caching them by path 
keeps serving the
+        // pre-overwrite content (and a len+mtime key still collides when a 
rewrite lands at the
+        // same size within the same clock second). Hint files, consumer and 
service progress
+        // files, replaceable tags and _SUCCESS all go through 
overwriteFileUtf8.
+        return "EARLIEST".equals(name)
+                || "LATEST".equals(name)
+                || "_SUCCESS".equals(name)
+                || name.endsWith("_SUCCESS")
+                || name.startsWith(CONSUMER_PREFIX)
+                || name.startsWith(SERVICE_PREFIX)
+                || name.startsWith(TAG_PREFIX);
     }
 
     /**
diff --git 
a/paimon-common/src/test/java/org/apache/paimon/fs/cache/CachingFileIOTest.java 
b/paimon-common/src/test/java/org/apache/paimon/fs/cache/CachingFileIOTest.java
index ded23667a9..05ddd7cad0 100644
--- 
a/paimon-common/src/test/java/org/apache/paimon/fs/cache/CachingFileIOTest.java
+++ 
b/paimon-common/src/test/java/org/apache/paimon/fs/cache/CachingFileIOTest.java
@@ -94,6 +94,63 @@ class CachingFileIOTest {
         MockFileIO.resetGlobalInputStreamCalls();
     }
 
+    @Test
+    void testMemoryModeDoesNotCacheInPlaceOverwrittenFiles() throws 
IOException {
+        MockFileIO delegate = new MockFileIO();
+        CachingFileIO cachingIO =
+                newCachingFileIO(
+                        delegate,
+                        new LocalMemoryCacheManager(Long.MAX_VALUE, 64),
+                        EnumSet.of(FileType.META),
+                        64);
+        Path consumer = new Path("consumer-1");
+
+        // consumer-* is written in place by overwriteFileUtf8, so it is 
mutable and bypasses the
+        // cache: each read reaches the delegate and sees the current content.
+        delegate.addFile("consumer-1", "v1cc".getBytes());
+        try (SeekableInputStream in = cachingIO.newInputStream(consumer)) {
+            assertThat(in).isNotInstanceOf(CachingSeekableInputStream.class);
+            byte[] buf = new byte[4];
+            in.read(buf, 0, 4);
+            assertThat(new String(buf)).isEqualTo("v1cc");
+        }
+        
assertThat(delegate.newInputStreamCallCount("consumer-1")).isEqualTo(1);
+
+        delegate.addFile("consumer-1", "v2cc".getBytes());
+        try (SeekableInputStream in = cachingIO.newInputStream(consumer)) {
+            byte[] buf = new byte[4];
+            in.read(buf, 0, 4);
+            assertThat(new String(buf)).isEqualTo("v2cc");
+        }
+        // never cached: the overwrite is visible and the delegate was opened 
again
+        
assertThat(delegate.newInputStreamCallCount("consumer-1")).isEqualTo(2);
+    }
+
+    @Test
+    void testMemoryModeImmutableCacheHitsDoNotRestatDelegate() throws 
IOException {
+        MockFileIO delegate = new MockFileIO();
+        CachingFileIO cachingIO =
+                newCachingFileIO(
+                        delegate,
+                        new LocalMemoryCacheManager(Long.MAX_VALUE, 64),
+                        EnumSet.of(FileType.META),
+                        64);
+        Path snapshot = new Path("snapshot-1");
+        delegate.addFile("snapshot-1", "0123456789abcdef".getBytes());
+
+        for (int i = 0; i < 3; i++) {
+            try (SeekableInputStream in = cachingIO.newInputStream(snapshot)) {
+                assertThat(readAll(in, 
16)).isEqualTo("0123456789abcdef".getBytes());
+            }
+        }
+
+        // An immutable file keeps the path-only memory key: opened once, then 
served from cache.
+        // Its size is resolved lazily and remembered, so repeated hits do not 
re-stat the
+        // delegate the way moving getFileStatus onto every open would.
+        
assertThat(delegate.newInputStreamCallCount("snapshot-1")).isEqualTo(1);
+        assertThat(delegate.getFileStatusCallCount("snapshot-1")).isEqualTo(1);
+    }
+
     @Test
     void testCreateBlobPresignedUrlDelegates() throws IOException {
         FileIO delegate = mock(FileIO.class);
diff --git 
a/paimon-common/src/test/java/org/apache/paimon/utils/FileTypeTest.java 
b/paimon-common/src/test/java/org/apache/paimon/utils/FileTypeTest.java
index d946f71bb7..d043d6bf97 100644
--- a/paimon-common/src/test/java/org/apache/paimon/utils/FileTypeTest.java
+++ b/paimon-common/src/test/java/org/apache/paimon/utils/FileTypeTest.java
@@ -31,6 +31,38 @@ public class FileTypeTest {
 
     private static final String TABLE_ROOT = 
"hdfs://cluster/warehouse/db.db/table";
 
+    // ===== mutable files (bypass the cache) =====
+
+    @Test
+    public void testIsMutable() {
+        // Files overwritten in place under a stable path must bypass the 
cache.
+        assertThat(FileType.isMutable(new Path(TABLE_ROOT + 
"/snapshot/EARLIEST"))).isTrue();
+        assertThat(FileType.isMutable(new Path(TABLE_ROOT + 
"/snapshot/LATEST"))).isTrue();
+        assertThat(FileType.isMutable(new Path(TABLE_ROOT + 
"/consumer/consumer-myGroup")))
+                .isTrue();
+        assertThat(FileType.isMutable(new Path(TABLE_ROOT + 
"/service/service-primary-key-lookup")))
+                .isTrue();
+        assertThat(FileType.isMutable(new Path(TABLE_ROOT + 
"/tag/tag-myTag"))).isTrue();
+        assertThat(FileType.isMutable(new Path(TABLE_ROOT + 
"/dt=2024-01-01/bucket-0/_SUCCESS")))
+                .isTrue();
+        // A temp rewrite of a mutable file is still mutable.
+        assertThat(
+                        FileType.isMutable(
+                                new Path(
+                                        TABLE_ROOT
+                                                + 
"/consumer/.consumer-myGroup."
+                                                + UUID.randomUUID()
+                                                + ".tmp")))
+                .isTrue();
+
+        // Write-once files stay cacheable: a new version lands under a new 
name.
+        assertThat(FileType.isMutable(new Path(TABLE_ROOT + 
"/snapshot/snapshot-1"))).isFalse();
+        assertThat(FileType.isMutable(new Path(TABLE_ROOT + 
"/schema/schema-0"))).isFalse();
+        assertThat(FileType.isMutable(new Path(TABLE_ROOT + 
"/manifest/manifest-a1b2c3d4-0")))
+                .isFalse();
+        assertThat(FileType.isMutable(new Path(TABLE_ROOT + 
"/bucket-0/data-abc.orc"))).isFalse();
+    }
+
     // ===== META files =====
 
     @Test

Reply via email to