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

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


The following commit(s) were added to refs/heads/master by this push:
     new 647827578 [AMORO-4113] Do not update snapshot markers on 
completeProcess(false) (#4114)
647827578 is described below

commit 6478275784d526a399e294d3806770e6f57a2c08
Author: Jiwon Park <[email protected]>
AuthorDate: Mon Mar 9 17:40:41 2026 +0900

    [AMORO-4113] Do not update snapshot markers on completeProcess(false) 
(#4114)
    
    When an optimizing process is closed without success (e.g., optimizer
    group change, task retry exhaustion), completeProcess(false) was
    unconditionally updating lastOptimizedSnapshotId and
    lastOptimizedChangeSnapshotId. This caused SchedulingPolicy.isTablePending()
    to return false, permanently preventing the table from being re-planned.
    
    Fix by only updating snapshot markers when success is true.
    
    Signed-off-by: Jiwon Park <[email protected]>
---
 .../amoro/server/table/DefaultTableRuntime.java    |  7 ++-
 .../server/optimizing/TestOptimizingQueue.java     | 73 ++++++++++++++++++++++
 2 files changed, 78 insertions(+), 2 deletions(-)

diff --git 
a/amoro-ams/src/main/java/org/apache/amoro/server/table/DefaultTableRuntime.java
 
b/amoro-ams/src/main/java/org/apache/amoro/server/table/DefaultTableRuntime.java
index baaebd278..002e7b0f2 100644
--- 
a/amoro-ams/src/main/java/org/apache/amoro/server/table/DefaultTableRuntime.java
+++ 
b/amoro-ams/src/main/java/org/apache/amoro/server/table/DefaultTableRuntime.java
@@ -385,8 +385,11 @@ public class DefaultTableRuntime extends 
AbstractTableRuntime {
         .updateState(
             OPTIMIZING_STATE_KEY,
             state -> {
-              
state.setLastOptimizedSnapshotId(optimizingProcess.getTargetSnapshotId());
-              
state.setLastOptimizedChangeSnapshotId(optimizingProcess.getTargetChangeSnapshotId());
+              if (success) {
+                
state.setLastOptimizedSnapshotId(optimizingProcess.getTargetSnapshotId());
+                state.setLastOptimizedChangeSnapshotId(
+                    optimizingProcess.getTargetChangeSnapshotId());
+              }
               if (processType == OptimizingType.MINOR) {
                 
state.setLastMinorOptimizingTime(optimizingProcess.getPlanTime());
               } else if (processType == OptimizingType.MAJOR) {
diff --git 
a/amoro-ams/src/test/java/org/apache/amoro/server/optimizing/TestOptimizingQueue.java
 
b/amoro-ams/src/test/java/org/apache/amoro/server/optimizing/TestOptimizingQueue.java
index ddf8fe985..bf26651a3 100644
--- 
a/amoro-ams/src/test/java/org/apache/amoro/server/optimizing/TestOptimizingQueue.java
+++ 
b/amoro-ams/src/test/java/org/apache/amoro/server/optimizing/TestOptimizingQueue.java
@@ -537,6 +537,79 @@ public class TestOptimizingQueue extends AMSTableTestBase {
     queue.dispose();
   }
 
+  @Test
+  public void testProcessCloseKeepsLastOptimizedSnapshotId() {
+    DefaultTableRuntime tableRuntime = initTableWithFiles();
+    long snapshotIdBeforePlanning = tableRuntime.getLastOptimizedSnapshotId();
+    long changeSnapshotIdBeforePlanning = 
tableRuntime.getLastOptimizedChangeSnapshotId();
+
+    OptimizingQueue queue = buildOptimizingGroupService(tableRuntime);
+
+    // Poll task to trigger planning → process creation
+    TaskRuntime<?> task = queue.pollTask(optimizerThread, MAX_POLLING_TIME);
+    Assert.assertNotNull(task);
+
+    OptimizingProcess process = tableRuntime.getOptimizingProcess();
+    Assert.assertNotNull(process);
+    Assert.assertEquals(ProcessStatus.RUNNING, process.getStatus());
+
+    // Close process without success (simulates group change / forced 
termination)
+    process.close(false);
+
+    // lastOptimizedSnapshotId and lastOptimizedChangeSnapshotId should NOT be 
updated
+    Assert.assertEquals(snapshotIdBeforePlanning, 
tableRuntime.getLastOptimizedSnapshotId());
+    Assert.assertEquals(
+        changeSnapshotIdBeforePlanning, 
tableRuntime.getLastOptimizedChangeSnapshotId());
+    Assert.assertEquals(OptimizingStatus.IDLE, 
tableRuntime.getOptimizingStatus());
+    Assert.assertNull(tableRuntime.getOptimizingProcess());
+    queue.dispose();
+  }
+
+  @Test
+  public void testTableRescheduledAfterProcessClose() {
+    DefaultTableRuntime tableRuntime = initTableWithFiles();
+
+    OptimizingQueue queue = buildOptimizingGroupService(tableRuntime);
+
+    // Poll task to trigger planning → process creation
+    TaskRuntime<?> task = queue.pollTask(optimizerThread, MAX_POLLING_TIME);
+    Assert.assertNotNull(task);
+
+    OptimizingProcess process = tableRuntime.getOptimizingProcess();
+    Assert.assertNotNull(process);
+
+    // Force close instead of commit (simulates group change)
+    process.close(false);
+    Assert.assertEquals(OptimizingStatus.IDLE, 
tableRuntime.getOptimizingStatus());
+
+    // Verify snapshot marker was not updated — key condition for 
isTablePending()
+    Assert.assertNotEquals(
+        tableRuntime.getLastOptimizedSnapshotId(), 
tableRuntime.getCurrentSnapshotId());
+
+    // Simulate setPendingInput() transitioning IDLE → PENDING (done by 
evaluator in production)
+    tableRuntime
+        .store()
+        .begin()
+        .updateStatusCode(code -> OptimizingStatus.PENDING.getCode())
+        .commit();
+    Assert.assertEquals(OptimizingStatus.PENDING, 
tableRuntime.getOptimizingStatus());
+
+    // Set minPlanInterval to 0 so the table is immediately eligible for 
re-planning
+    tableRuntime
+        .store()
+        .begin()
+        .updateTableConfig(
+            config -> 
config.put(TableProperties.SELF_OPTIMIZING_MIN_PLAN_INTERVAL, "0"))
+        .commit();
+
+    // Build a new queue with the PENDING table — scheduler should NOT skip it
+    queue.dispose();
+    OptimizingQueue queue2 = buildOptimizingGroupService(tableRuntime);
+    TaskRuntime<?> task2 = queue2.pollTask(optimizerThread, MAX_POLLING_TIME);
+    Assert.assertNotNull(task2);
+    queue2.dispose();
+  }
+
   protected DefaultTableRuntime initTableWithFiles() {
     MixedTable mixedTable =
         (MixedTable) 
tableService().loadTable(serverTableIdentifier()).originalTable();

Reply via email to