This is an automated email from the ASF dual-hosted git repository.
nzw921rx pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/seatunnel.git
The following commit(s) were added to refs/heads/dev by this push:
new d33bd82a4d [Fix][Zeta] Restore jobs from their own latest checkpoint
on master failover (#12387)
d33bd82a4d is described below
commit d33bd82a4df68f2d981923b57804ad35455c00ea
Author: 88fantasy <[email protected]>
AuthorDate: Fri Oct 23 00:30:27 2026 +0800
[Fix][Zeta] Restore jobs from their own latest checkpoint on master
failover (#12387)
---
.../server/checkpoint/CheckpointManager.java | 65 ++++++++++++++--
.../seatunnel/engine/server/master/JobMaster.java | 1 +
.../server/checkpoint/CheckpointManagerTest.java | 89 ++++++++++++++++++++++
3 files changed, 147 insertions(+), 8 deletions(-)
diff --git
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/checkpoint/CheckpointManager.java
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/checkpoint/CheckpointManager.java
index e1adaad108..5a85a16d17 100644
---
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/checkpoint/CheckpointManager.java
+++
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/checkpoint/CheckpointManager.java
@@ -107,6 +107,44 @@ public class CheckpointManager {
IMap<Object, Object> runningJobStateIMap,
SeaTunnelEngineContext engineContext,
CheckpointMonitorService checkpointMonitorService) {
+ this(
+ jobId,
+ isRestoreJob,
+ restoreMode,
+ restoreSourceJobId,
+ false,
+ nodeEngine,
+ jobMaster,
+ checkpointPlanMap,
+ checkpointConfig,
+ checkpointStorage,
+ executorService,
+ runningJobStateIMap,
+ engineContext,
+ checkpointMonitorService);
+ }
+
+ /**
+ * @param masterFailoverRestore true when the job is being restored after
a master node
+ * switch/restart. The latest completed checkpoint of this job itself
is preferred in that
+ * case, falling back to {@code restoreSourceJobId}/{@code
restoreMode} only when this job
+ * has not completed any checkpoint yet.
+ */
+ public CheckpointManager(
+ long jobId,
+ boolean isRestoreJob,
+ RestoreMode restoreMode,
+ Long restoreSourceJobId,
+ boolean masterFailoverRestore,
+ NodeEngine nodeEngine,
+ JobMaster jobMaster,
+ Map<Integer, CheckpointPlan> checkpointPlanMap,
+ CheckpointConfig checkpointConfig,
+ CheckpointStorage checkpointStorage,
+ ExecutorService executorService,
+ IMap<Object, Object> runningJobStateIMap,
+ SeaTunnelEngineContext engineContext,
+ CheckpointMonitorService checkpointMonitorService) {
this.jobId = jobId;
this.nodeEngine = nodeEngine;
this.jobMaster = jobMaster;
@@ -128,14 +166,25 @@ public class CheckpointManager {
try {
idCounter.start();
PipelineState pipelineState = null;
- if
(checkpointConfig.isCheckpointEnable()
- && isRestoreJob
- && restoreSourceJobId != null)
{
- pipelineState =
-
getLatestCheckpointStateByType(
-
String.valueOf(restoreSourceJobId),
-
String.valueOf(plan.getPipelineId()),
- restoreMode);
+ if
(checkpointConfig.isCheckpointEnable() && isRestoreJob) {
+ if (masterFailoverRestore) {
+ pipelineState =
+ checkpointStorage
+
.getLatestCheckpointByJobIdAndPipelineId(
+
String.valueOf(jobId),
+
String.valueOf(
+
plan
+
.getPipelineId()));
+ }
+ if (pipelineState == null
+ && restoreSourceJobId !=
null) {
+ pipelineState =
+
getLatestCheckpointStateByType(
+
String.valueOf(restoreSourceJobId),
+ String.valueOf(
+
plan.getPipelineId()),
+ restoreMode);
+ }
if (pipelineState != null) {
long checkpointId =
pipelineState.getCheckpointId();
idCounter.setCount(checkpointId + 1);
diff --git
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/master/JobMaster.java
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/master/JobMaster.java
index e3df6760d2..d9068ddf40 100644
---
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/master/JobMaster.java
+++
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/master/JobMaster.java
@@ -341,6 +341,7 @@ public class JobMaster {
jobImmutableInformation.isRestoreJob() || restart,
jobImmutableInformation.getRestoreMode(),
jobImmutableInformation.getRestoreSourceJobId(),
+ restart,
nodeEngine,
this,
checkpointPlanMap,
diff --git
a/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/checkpoint/CheckpointManagerTest.java
b/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/checkpoint/CheckpointManagerTest.java
index a1aecbddd7..d19f2835a6 100644
---
a/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/checkpoint/CheckpointManagerTest.java
+++
b/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/checkpoint/CheckpointManagerTest.java
@@ -135,6 +135,76 @@ public class CheckpointManagerTest extends
AbstractSeaTunnelServerTest {
checkpointManager.getCheckpointCoordinator(1).getCheckpointIdCounter().get());
}
+ @Test
+ public void testMasterFailoverRestoreShouldLoadLatestCheckpointOfOwnJob()
throws Exception {
+ long jobId = (long) (Math.random() * 1000000L);
+ CheckpointStorage checkpointStorage = createCheckpointStorage();
+ storeCompletedCheckpoint(checkpointStorage, jobId, 7L);
+
+ // A normally submitted job has no restore source; after a master
switch it is
+ // re-initialized with restart = true and must still continue from its
own checkpoint.
+ CheckpointManager checkpointManager =
+ createCheckpointManager(
+ jobId,
+ true,
+ RestoreMode.NONE,
+ null,
+ true,
+ checkpointStorage,
+ new CheckpointConfig());
+
+ Assertions.assertEquals(
+ 8L,
checkpointManager.getCheckpointCoordinator(1).getCheckpointIdCounter().get());
+ }
+
+ @Test
+ public void
testMasterFailoverRestoreShouldPreferOwnLatestCheckpointOverSavepoint()
+ throws Exception {
+ long jobId = (long) (Math.random() * 1000000L);
+ CheckpointStorage checkpointStorage = createCheckpointStorage();
+ storeCompletedCheckpoint(checkpointStorage, jobId, 3L,
CheckpointType.SAVEPOINT_TYPE);
+ // checkpoint files are ordered by their store timestamp (millis)
+ Thread.sleep(5);
+ storeCompletedCheckpoint(checkpointStorage, jobId, 7L);
+
+ // Job started with -r and later hit a master switch: resume from
checkpoint 7, not
+ // from the savepoint it was originally restored from.
+ CheckpointManager checkpointManager =
+ createCheckpointManager(
+ jobId,
+ true,
+ RestoreMode.SAVEPOINT,
+ jobId,
+ true,
+ checkpointStorage,
+ new CheckpointConfig());
+
+ Assertions.assertEquals(
+ 8L,
checkpointManager.getCheckpointCoordinator(1).getCheckpointIdCounter().get());
+ }
+
+ @Test
+ public void
testMasterFailoverRestoreShouldFallBackToSourceJobWithoutOwnCheckpoint()
+ throws Exception {
+ long jobId = (long) (Math.random() * 1000000L);
+ long sourceJobId = jobId + 1;
+ CheckpointStorage checkpointStorage = createCheckpointStorage();
+ storeCompletedCheckpoint(checkpointStorage, sourceJobId, 5L);
+
+ CheckpointManager checkpointManager =
+ createCheckpointManager(
+ jobId,
+ true,
+ RestoreMode.CHECKPOINT,
+ sourceJobId,
+ true,
+ checkpointStorage,
+ new CheckpointConfig());
+
+ Assertions.assertEquals(
+ 6L,
checkpointManager.getCheckpointCoordinator(1).getCheckpointIdCounter().get());
+ }
+
private CheckpointStorage createCheckpointStorage() throws
CheckpointStorageException {
return FactoryUtil.discoverFactory(
Thread.currentThread().getContextClassLoader(),
@@ -196,6 +266,24 @@ public class CheckpointManagerTest extends
AbstractSeaTunnelServerTest {
Long restoreSourceJobId,
CheckpointStorage checkpointStorage,
CheckpointConfig checkpointConfig) {
+ return createCheckpointManager(
+ jobId,
+ isRestoreJob,
+ restoreMode,
+ restoreSourceJobId,
+ false,
+ checkpointStorage,
+ checkpointConfig);
+ }
+
+ private CheckpointManager createCheckpointManager(
+ long jobId,
+ boolean isRestoreJob,
+ RestoreMode restoreMode,
+ Long restoreSourceJobId,
+ boolean masterFailoverRestore,
+ CheckpointStorage checkpointStorage,
+ CheckpointConfig checkpointConfig) {
Map<Integer, CheckpointPlan> planMap = new HashMap<>();
planMap.put(1, CheckpointPlan.builder().pipelineId(1).build());
return new CheckpointManager(
@@ -203,6 +291,7 @@ public class CheckpointManagerTest extends
AbstractSeaTunnelServerTest {
isRestoreJob,
restoreMode,
restoreSourceJobId,
+ masterFailoverRestore,
nodeEngine,
null,
planMap,