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 449de5c284 [core] Retry snapshot existence checks after I/O failures 
(#9707)
449de5c284 is described below

commit 449de5c2841ab6ee92cf97181d0d1a52880cf3f4
Author: Xingyi Lee <[email protected]>
AuthorDate: Fri Sep 11 14:14:05 2026 +0800

    [core] Retry snapshot existence checks after I/O failures (#9707)
---
 .../org/apache/paimon/utils/SnapshotManager.java   | 46 +++++++++++++++++++---
 .../apache/paimon/utils/SnapshotManagerTest.java   | 26 ++++++++++++
 2 files changed, 66 insertions(+), 6 deletions(-)

diff --git 
a/paimon-core/src/main/java/org/apache/paimon/utils/SnapshotManager.java 
b/paimon-core/src/main/java/org/apache/paimon/utils/SnapshotManager.java
index 8cd1599978..18740a9a3f 100644
--- a/paimon-core/src/main/java/org/apache/paimon/utils/SnapshotManager.java
+++ b/paimon-core/src/main/java/org/apache/paimon/utils/SnapshotManager.java
@@ -67,6 +67,9 @@ public class SnapshotManager implements Serializable {
 
     public static final int EARLIEST_SNAPSHOT_DEFAULT_RETRY_NUM = 300;
 
+    private static final int SNAPSHOT_EXISTS_MAX_ATTEMPTS = 3;
+    private static final long SNAPSHOT_EXISTS_RETRY_INTERVAL_MILLIS = 1_000L;
+
     private final FileIO fileIO;
     private final Path tablePath;
     private final String branch;
@@ -147,13 +150,44 @@ public class SnapshotManager implements Serializable {
 
     public boolean snapshotExists(long snapshotId) {
         Path path = snapshotPath(snapshotId);
-        try {
-            return fileIO.exists(path);
-        } catch (IOException e) {
-            throw new RuntimeException(
-                    "Failed to determine if snapshot #" + snapshotId + " 
exists in path " + path,
-                    e);
+        IOException failure = null;
+        for (int attempt = 1; attempt <= SNAPSHOT_EXISTS_MAX_ATTEMPTS; 
attempt++) {
+            try {
+                return fileIO.exists(path);
+            } catch (IOException e) {
+                failure = e;
+                if (attempt == SNAPSHOT_EXISTS_MAX_ATTEMPTS) {
+                    break;
+                }
+                LOG.warn(
+                        "Failed to check whether snapshot #{} exists at {} 
(attempt {}/{}). Retrying.",
+                        snapshotId,
+                        path,
+                        attempt,
+                        SNAPSHOT_EXISTS_MAX_ATTEMPTS,
+                        e);
+                try {
+                    Thread.sleep(SNAPSHOT_EXISTS_RETRY_INTERVAL_MILLIS);
+                } catch (InterruptedException ie) {
+                    Thread.currentThread().interrupt();
+                    throw new RuntimeException(
+                            "Interrupted while checking whether snapshot #"
+                                    + snapshotId
+                                    + " exists at "
+                                    + path,
+                            ie);
+                }
+            }
         }
+        throw new RuntimeException(
+                "Failed to check whether snapshot #"
+                        + snapshotId
+                        + " exists at "
+                        + path
+                        + " after "
+                        + SNAPSHOT_EXISTS_MAX_ATTEMPTS
+                        + " attempts.",
+                failure);
     }
 
     public void deleteSnapshot(long snapshotId) {
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/utils/SnapshotManagerTest.java 
b/paimon-core/src/test/java/org/apache/paimon/utils/SnapshotManagerTest.java
index bf1a005878..1d30c0f882 100644
--- a/paimon-core/src/test/java/org/apache/paimon/utils/SnapshotManagerTest.java
+++ b/paimon-core/src/test/java/org/apache/paimon/utils/SnapshotManagerTest.java
@@ -95,6 +95,32 @@ public class SnapshotManagerTest {
                 .isFalse();
     }
 
+    @Test
+    public void testSnapshotExistsRetriesAfterIOException() throws IOException 
{
+        FileIO fileIO = Mockito.mock(FileIO.class);
+        Mockito.when(fileIO.exists(Mockito.any(Path.class)))
+                .thenThrow(new IOException("Temporary failure"))
+                .thenReturn(true);
+        SnapshotManager snapshotManager = newSnapshotManager(fileIO, new 
Path(tempDir.toString()));
+
+        assertThat(snapshotManager.snapshotExists(2)).isTrue();
+        Mockito.verify(fileIO, 
Mockito.times(2)).exists(Mockito.any(Path.class));
+    }
+
+    @Test
+    public void testSnapshotExistsFailsAfterMaxAttempts() throws IOException {
+        FileIO fileIO = Mockito.mock(FileIO.class);
+        Mockito.when(fileIO.exists(Mockito.any(Path.class)))
+                .thenThrow(new IOException("Persistent failure"));
+        SnapshotManager snapshotManager = newSnapshotManager(fileIO, new 
Path(tempDir.toString()));
+
+        assertThatThrownBy(() -> snapshotManager.snapshotExists(2))
+                .hasMessageContaining("Failed to check whether snapshot #2 
exists")
+                .hasMessageContaining("after 3 attempts")
+                .hasRootCauseMessage("Persistent failure");
+        Mockito.verify(fileIO, 
Mockito.times(3)).exists(Mockito.any(Path.class));
+    }
+
     @ParameterizedTest
     @ValueSource(booleans = {true, false})
     public void testEarliestSnapshot(boolean isRaceCondition) throws 
IOException {

Reply via email to