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

zhangshenghang 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 d774e8ee8c [Feature][Zeta] Decouple Hazelcast IMap via StateStore 
Abstraction (#10812)
d774e8ee8c is described below

commit d774e8ee8cea6148a7f95562a411c9f770c70aa0
Author: Doyeon Kim <[email protected]>
AuthorDate: Sun Jun 21 23:12:28 2026 +0900

    [Feature][Zeta] Decouple Hazelcast IMap via StateStore Abstraction (#10812)
---
 .../apache/seatunnel/engine/common/Constant.java   |   4 -
 .../engine/server/CoordinatorService.java          | 169 +----------
 .../seatunnel/engine/server/SeaTunnelServer.java   |  98 ++-----
 .../engine/server/TaskExecutionService.java        |   9 +-
 .../monitor/CheckpointMonitorService.java          | 213 +-------------
 .../server/common/SeaTunnelEngineContext.java      |  69 +++++
 .../statestore/AuthoritativeStateStores.java       |  38 +++
 .../common/statestore/AuxiliaryStateStores.java    |  54 ++++
 .../DefaultAuthoritativeStateStores.java           |  21 ++
 .../statestore/DefaultAuxiliaryStateStores.java    |  66 +++++
 .../common/statestore/EngineStateStoreNames.java   |  31 ++
 .../common/statestore/EngineStateStores.java       |  75 +++++
 .../common/statestore/IterableStateStore.java      |  57 ++++
 .../server/common/statestore/StateStore.java       | 100 +++++++
 .../checkpoint/CheckpointOverviewStateStore.java   |  67 +++++
 .../HazelcastCheckpointOverviewStateStore.java     | 282 ++++++++++++++++++
 .../hazelcast/HazelcastEngineStateStores.java      | 100 +++++++
 .../metrics/MetricsSnapshotStateStore.java         | 113 ++++++++
 .../HazelcastMetricsSnapshotStateStore.java        | 322 +++++++++++++++++++++
 .../server/execution/TaskExecutionContext.java     |  29 +-
 .../exports/EngineStateStoreMetricExports.java     |   5 +-
 .../CoordinatorServicePipelineCleanupTest.java     |  12 +-
 .../engine/server/CoordinatorServiceTest.java      |  45 +--
 .../HazelcastCheckpointOverviewStateStoreTest.java | 133 +++++++++
 .../HazelcastMetricsSnapshotStateStoreTest.java    | 203 +++++++++++++
 .../EngineStateStoreLogicalMetricExportsTest.java  |   3 +-
 .../exports/EngineStateStoreMetricExportsTest.java |   5 +-
 27 files changed, 1838 insertions(+), 485 deletions(-)

diff --git 
a/seatunnel-engine/seatunnel-engine-common/src/main/java/org/apache/seatunnel/engine/common/Constant.java
 
b/seatunnel-engine/seatunnel-engine-common/src/main/java/org/apache/seatunnel/engine/common/Constant.java
index ab5e522fdf..582c7f0cc6 100644
--- 
a/seatunnel-engine/seatunnel-engine-common/src/main/java/org/apache/seatunnel/engine/common/Constant.java
+++ 
b/seatunnel-engine/seatunnel-engine-common/src/main/java/org/apache/seatunnel/engine/common/Constant.java
@@ -57,14 +57,10 @@ public class Constant {
 
     public static final String IMAP_CHECKPOINT_ID = "engine_checkpoint-id-map";
 
-    public static final String IMAP_RUNNING_JOB_METRICS = 
"engine_runningJobMetrics";
-
     public static final String IMAP_PENDING_PIPELINE_CLEANUP = 
"engine_pendingPipelineCleanup";
 
     public static final String IMAP_PENDING_JOB_CLEANUP = 
"engine_pendingJobCleanup";
 
-    public static final String IMAP_CHECKPOINT_MONITOR = 
"engine_checkpoint_monitor";
-
     public static final String IMAP_CONNECTOR_JAR_REF_COUNTERS = 
"engine_connectorJarRefCounters";
 
     public static final String PROP_FILE = "zeta.version.properties";
diff --git 
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/CoordinatorService.java
 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/CoordinatorService.java
index 6d1453b91b..4cd0c92475 100644
--- 
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/CoordinatorService.java
+++ 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/CoordinatorService.java
@@ -51,6 +51,8 @@ import org.apache.seatunnel.engine.core.job.JobDAGInfo;
 import org.apache.seatunnel.engine.core.job.JobImmutableInformation;
 import org.apache.seatunnel.engine.core.job.JobInfo;
 import org.apache.seatunnel.engine.core.job.PipelineStatus;
+import org.apache.seatunnel.engine.server.common.SeaTunnelEngineContext;
+import 
org.apache.seatunnel.engine.server.common.statestore.metrics.MetricsSnapshotStateStore;
 import org.apache.seatunnel.engine.server.dag.DAGUtils;
 import org.apache.seatunnel.engine.server.dag.physical.PhysicalVertex;
 import org.apache.seatunnel.engine.server.dag.physical.PipelineLocation;
@@ -67,13 +69,11 @@ import 
org.apache.seatunnel.engine.server.execution.PendingJobInfo;
 import org.apache.seatunnel.engine.server.execution.PendingSourceState;
 import org.apache.seatunnel.engine.server.execution.TaskExecutionState;
 import org.apache.seatunnel.engine.server.execution.TaskGroupLocation;
-import org.apache.seatunnel.engine.server.execution.TaskLocation;
 import org.apache.seatunnel.engine.server.master.JobHistoryService;
 import org.apache.seatunnel.engine.server.master.JobMaster;
 import org.apache.seatunnel.engine.server.master.cleanup.JobCleanupRecord;
 import org.apache.seatunnel.engine.server.master.cleanup.PipelineCleanupRecord;
 import org.apache.seatunnel.engine.server.metrics.JobMetricsUtil;
-import org.apache.seatunnel.engine.server.metrics.SeaTunnelMetricsContext;
 import 
org.apache.seatunnel.engine.server.resourcemanager.NoEnoughResourceException;
 import org.apache.seatunnel.engine.server.resourcemanager.ResourceManager;
 import 
org.apache.seatunnel.engine.server.resourcemanager.ResourceManagerFactory;
@@ -88,15 +88,11 @@ import 
org.apache.seatunnel.engine.server.utils.PeekBlockingQueue;
 
 import com.hazelcast.cluster.Address;
 import com.hazelcast.config.Config;
-import com.hazelcast.core.EntryEvent;
 import com.hazelcast.core.HazelcastInstanceNotActiveException;
 import com.hazelcast.internal.serialization.Data;
 import com.hazelcast.internal.services.MembershipServiceEvent;
 import com.hazelcast.logging.ILogger;
 import com.hazelcast.map.IMap;
-import com.hazelcast.map.listener.EntryAddedListener;
-import com.hazelcast.map.listener.EntryRemovedListener;
-import com.hazelcast.map.listener.EntryUpdatedListener;
 import com.hazelcast.ringbuffer.Ringbuffer;
 import com.hazelcast.spi.impl.NodeEngineImpl;
 import lombok.NonNull;
@@ -113,7 +109,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
-import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -135,6 +130,7 @@ import static 
org.apache.seatunnel.engine.server.metrics.JobMetricsUtil.toJobMet
 public class CoordinatorService {
     private static final int PIPELINE_CLEANUP_INTERVAL_SECONDS = 60;
     private final NodeEngineImpl nodeEngine;
+    private final SeaTunnelEngineContext engineContext;
     private final ILogger logger;
 
     private volatile ResourceManager resourceManager;
@@ -197,8 +193,6 @@ public class CoordinatorService {
      */
     private IMap<PipelineLocation, Map<TaskGroupLocation, SlotProfile>> 
ownedSlotProfilesIMap;
 
-    private IMap<Long, HashMap<TaskLocation, SeaTunnelMetricsContext>> 
metricsImap;
-
     private IMap<PipelineLocation, PipelineCleanupRecord> 
pendingPipelineCleanupIMap;
 
     private IMap<Long, JobCleanupRecord> pendingJobCleanupIMap;
@@ -229,25 +223,13 @@ public class CoordinatorService {
 
     private final AtomicBoolean coordinatorServiceCleared = new 
AtomicBoolean(false);
 
-    private final AtomicLong runningJobMetricsPartitionKeyCount = new 
AtomicLong();
-
-    private final AtomicLong runningJobMetricsTaskContextCount = new 
AtomicLong();
-
-    private final Object runningJobMetricsStatsLock = new Object();
-
-    private final Set<Long> runningJobMetricsDirtyKeys = 
ConcurrentHashMap.newKeySet();
-
-    private final Map<Long, RunningJobMetricsStats> 
runningJobMetricsStatsByJobId = new HashMap<>();
-
-    private final AtomicBoolean runningJobMetricsInitializing = new 
AtomicBoolean(false);
-
-    private volatile UUID runningJobMetricsListenerId;
-
     public CoordinatorService(
             @NonNull NodeEngineImpl nodeEngine,
             @NonNull SeaTunnelServer seaTunnelServer,
+            @NonNull SeaTunnelEngineContext engineContext,
             EngineConfig engineConfig) {
         this.nodeEngine = nodeEngine;
+        this.engineContext = engineContext;
         this.engineConfig = engineConfig;
         this.logger = nodeEngine.getLogger(getClass());
         this.executorService = createCoordinatorExecutor();
@@ -538,8 +520,6 @@ public class CoordinatorService {
                 
nodeEngine.getHazelcastInstance().getMap(Constant.IMAP_STATE_TIMESTAMPS);
         ownedSlotProfilesIMap =
                 
nodeEngine.getHazelcastInstance().getMap(Constant.IMAP_OWNED_SLOT_PROFILES);
-        metricsImap = 
nodeEngine.getHazelcastInstance().getMap(Constant.IMAP_RUNNING_JOB_METRICS);
-        initRunningJobMetricsStoreStats();
         pendingPipelineCleanupIMap =
                 
nodeEngine.getHazelcastInstance().getMap(Constant.IMAP_PENDING_PIPELINE_CLEANUP);
         pendingJobCleanupIMap =
@@ -1124,7 +1104,6 @@ public class CoordinatorService {
         if (!coordinatorServiceCleared.compareAndSet(false, true)) {
             return;
         }
-        removeRunningJobMetricsListener();
         // interrupt all JobMaster
         runningJobMasterMap.values().forEach(JobMaster::interrupt);
         if (isWaitStrategy) {
@@ -1834,7 +1813,6 @@ public class CoordinatorService {
         if (pipelineCleanupScheduler != null) {
             pipelineCleanupScheduler.shutdown();
         }
-        removeRunningJobMetricsListener();
         clearCoordinatorService();
         awaitSchedulerTermination("master active listener", 
masterActiveListener);
         awaitSchedulerTermination("pipeline cleanup scheduler", 
pipelineCleanupScheduler);
@@ -2097,11 +2075,11 @@ public class CoordinatorService {
     }
 
     public long getRunningJobMetricsPartitionKeyCount() {
-        return runningJobMetricsPartitionKeyCount.get();
+        return getMetricsSnapshotStateStore().activePartitionKeyCount();
     }
 
     public long getRunningJobMetricsTaskContextCount() {
-        return runningJobMetricsTaskContextCount.get();
+        return getMetricsSnapshotStateStore().size();
     }
 
     public EngineConfig getEngineConfig() {
@@ -2109,8 +2087,8 @@ public class CoordinatorService {
     }
 
     @VisibleForTesting
-    protected IMap<Long, HashMap<TaskLocation, SeaTunnelMetricsContext>> 
getMetricsImap() {
-        return metricsImap;
+    protected MetricsSnapshotStateStore getMetricsSnapshotStateStore() {
+        return engineContext.getStateStores().metricsSnapshotStore();
     }
 
     @VisibleForTesting
@@ -2128,135 +2106,6 @@ public class CoordinatorService {
         }
     }
 
-    private void initRunningJobMetricsStoreStats() {
-        removeRunningJobMetricsListener();
-        runningJobMetricsListenerId =
-                metricsImap.addEntryListener(new 
RunningJobMetricsEntryListener(), true);
-        runningJobMetricsInitializing.set(true);
-        runningJobMetricsDirtyKeys.clear();
-
-        Map<Long, RunningJobMetricsStats> snapshotStats = new HashMap<>();
-        metricsImap.forEach(
-                (partitionKey, metrics) ->
-                        snapshotStats.put(partitionKey, 
toRunningJobMetricsStats(metrics)));
-
-        Set<Long> dirtyKeys = new HashSet<>(runningJobMetricsDirtyKeys);
-        for (Long partitionKey : dirtyKeys) {
-            snapshotStats.put(
-                    partitionKey, 
toRunningJobMetricsStats(metricsImap.get(partitionKey)));
-        }
-
-        synchronized (runningJobMetricsStatsLock) {
-            runningJobMetricsStatsByJobId.clear();
-            runningJobMetricsPartitionKeyCount.set(0L);
-            runningJobMetricsTaskContextCount.set(0L);
-            snapshotStats.forEach(this::replaceRunningJobMetricsStatsLocked);
-            runningJobMetricsInitializing.set(false);
-
-            Set<Long> postSnapshotDirtyKeys = new 
HashSet<>(runningJobMetricsDirtyKeys);
-            runningJobMetricsDirtyKeys.clear();
-            for (Long partitionKey : postSnapshotDirtyKeys) {
-                replaceRunningJobMetricsStatsLocked(
-                        partitionKey, 
toRunningJobMetricsStats(metricsImap.get(partitionKey)));
-            }
-        }
-    }
-
-    private RunningJobMetricsStats toRunningJobMetricsStats(
-            Map<TaskLocation, SeaTunnelMetricsContext> metrics) {
-        if (metrics == null || metrics.isEmpty()) {
-            return RunningJobMetricsStats.EMPTY;
-        }
-        return new RunningJobMetricsStats(1L, metrics.size());
-    }
-
-    private void replaceRunningJobMetricsStatsLocked(
-            Long partitionKey, RunningJobMetricsStats stats) {
-        RunningJobMetricsStats currentStats = 
runningJobMetricsStatsByJobId.get(partitionKey);
-        if (currentStats != null) {
-            
runningJobMetricsPartitionKeyCount.addAndGet(-currentStats.partitionKeyCount);
-            
runningJobMetricsTaskContextCount.addAndGet(-currentStats.taskContextCount);
-        }
-
-        if (stats.isEmpty()) {
-            runningJobMetricsStatsByJobId.remove(partitionKey);
-            return;
-        }
-
-        runningJobMetricsStatsByJobId.put(partitionKey, stats);
-        runningJobMetricsPartitionKeyCount.addAndGet(stats.partitionKeyCount);
-        runningJobMetricsTaskContextCount.addAndGet(stats.taskContextCount);
-    }
-
-    private void removeRunningJobMetricsListener() {
-        if (metricsImap != null && runningJobMetricsListenerId != null) {
-            metricsImap.removeEntryListener(runningJobMetricsListenerId);
-            runningJobMetricsListenerId = null;
-        }
-        runningJobMetricsInitializing.set(false);
-        runningJobMetricsDirtyKeys.clear();
-        synchronized (runningJobMetricsStatsLock) {
-            runningJobMetricsStatsByJobId.clear();
-        }
-        runningJobMetricsPartitionKeyCount.set(0L);
-        runningJobMetricsTaskContextCount.set(0L);
-    }
-
-    private final class RunningJobMetricsEntryListener
-            implements EntryAddedListener<Long, HashMap<TaskLocation, 
SeaTunnelMetricsContext>>,
-                    EntryUpdatedListener<Long, HashMap<TaskLocation, 
SeaTunnelMetricsContext>>,
-                    EntryRemovedListener<Long, HashMap<TaskLocation, 
SeaTunnelMetricsContext>> {
-
-        @Override
-        public void entryAdded(
-                EntryEvent<Long, HashMap<TaskLocation, 
SeaTunnelMetricsContext>> event) {
-            replaceRunningJobMetricsStats(event.getKey(), event.getValue());
-        }
-
-        @Override
-        public void entryUpdated(
-                EntryEvent<Long, HashMap<TaskLocation, 
SeaTunnelMetricsContext>> event) {
-            replaceRunningJobMetricsStats(event.getKey(), event.getValue());
-        }
-
-        @Override
-        public void entryRemoved(
-                EntryEvent<Long, HashMap<TaskLocation, 
SeaTunnelMetricsContext>> event) {
-            replaceRunningJobMetricsStats(event.getKey(), null);
-        }
-    }
-
-    private void replaceRunningJobMetricsStats(
-            Long partitionKey, Map<TaskLocation, SeaTunnelMetricsContext> 
metrics) {
-        if (runningJobMetricsInitializing.get()) {
-            runningJobMetricsDirtyKeys.add(partitionKey);
-            return;
-        }
-        synchronized (runningJobMetricsStatsLock) {
-            if (runningJobMetricsInitializing.get()) {
-                runningJobMetricsDirtyKeys.add(partitionKey);
-                return;
-            }
-            replaceRunningJobMetricsStatsLocked(partitionKey, 
toRunningJobMetricsStats(metrics));
-        }
-    }
-
-    private static final class RunningJobMetricsStats {
-        private static final RunningJobMetricsStats EMPTY = new 
RunningJobMetricsStats(0L, 0L);
-
-        private final long partitionKeyCount;
-        private final long taskContextCount;
-
-        private RunningJobMetricsStats(long partitionKeyCount, long 
taskContextCount) {
-            this.partitionKeyCount = partitionKeyCount;
-            this.taskContextCount = taskContextCount;
-        }
-
-        private boolean isEmpty() {
-            return partitionKeyCount == 0L && taskContextCount == 0L;
-        }
-    }
-
     @VisibleForTesting
     public PeekBlockingQueue<PendingJobInfo> getPendingJobQueue() {
         return pendingJobQueue;
diff --git 
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/SeaTunnelServer.java
 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/SeaTunnelServer.java
index c7a362c53f..37f39b03f8 100644
--- 
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/SeaTunnelServer.java
+++ 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/SeaTunnelServer.java
@@ -27,6 +27,10 @@ import 
org.apache.seatunnel.engine.common.exception.SeaTunnelEngineRetryableExce
 import org.apache.seatunnel.engine.core.classloader.ClassLoaderService;
 import org.apache.seatunnel.engine.core.classloader.DefaultClassLoaderService;
 import 
org.apache.seatunnel.engine.server.checkpoint.monitor.CheckpointMonitorService;
+import org.apache.seatunnel.engine.server.common.SeaTunnelEngineContext;
+import org.apache.seatunnel.engine.server.common.statestore.EngineStateStores;
+import 
org.apache.seatunnel.engine.server.common.statestore.hazelcast.HazelcastEngineStateStores;
+import 
org.apache.seatunnel.engine.server.common.statestore.metrics.MetricsSnapshotStateStore;
 import org.apache.seatunnel.engine.server.dag.physical.PipelineLocation;
 import org.apache.seatunnel.engine.server.execution.ExecutionState;
 import org.apache.seatunnel.engine.server.execution.TaskGroupLocation;
@@ -58,14 +62,11 @@ import lombok.NonNull;
 import lombok.extern.slf4j.Slf4j;
 
 import java.sql.DriverManager;
-import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
 
 @Slf4j
 public class SeaTunnelServer
@@ -87,6 +88,7 @@ public class SeaTunnelServer
 
     public static final String SERVICE_NAME = "st:impl:seaTunnelServer";
 
+    @Getter private SeaTunnelEngineContext engineContext;
     private NodeEngineImpl nodeEngine;
     private final LiveOperationRegistry liveOperationRegistry;
 
@@ -146,6 +148,12 @@ public class SeaTunnelServer
         // TODO Determine whether to execute there method on the master node 
according to the deploy
         // type
 
+        EngineStateStores stateStores =
+                new HazelcastEngineStateStores(
+                        nodeEngine,
+                        
seaTunnelConfig.getEngineConfig().getJobMetricsPartitionCount());
+        this.engineContext = 
SeaTunnelEngineContext.builder(stateStores).build();
+
         classLoaderService =
                 new DefaultClassLoaderService(
                         
seaTunnelConfig.getEngineConfig().isClassloaderCacheMode(), nodeEngine);
@@ -191,10 +199,11 @@ public class SeaTunnelServer
     private void startMaster() {
         checkpointService =
                 new 
CheckpointService(seaTunnelConfig.getEngineConfig().getCheckpointConfig());
-        checkpointMonitorService = new CheckpointMonitorService(nodeEngine, 
32);
+        checkpointMonitorService = new CheckpointMonitorService(engineContext, 
32);
         monitorService = Executors.newSingleThreadScheduledExecutor();
         coordinatorService =
-                new CoordinatorService(nodeEngine, this, 
seaTunnelConfig.getEngineConfig());
+                new CoordinatorService(
+                        nodeEngine, this, engineContext, 
seaTunnelConfig.getEngineConfig());
         monitorService.scheduleAtFixedRate(
                 this::printExecutionInfo,
                 0,
@@ -204,7 +213,8 @@ public class SeaTunnelServer
 
     private void startWorker() {
         taskExecutionService =
-                new TaskExecutionService(classLoaderService, nodeEngine, 
eventService);
+                new TaskExecutionService(
+                        classLoaderService, nodeEngine, engineContext, 
eventService);
         
nodeEngine.getMetricsRegistry().registerDynamicMetricsProvider(taskExecutionService);
         taskExecutionService.start();
         getSlotService();
@@ -243,6 +253,7 @@ public class SeaTunnelServer
         }
 
         MetadataProviderManager.closeProviders();
+        engineContext.close();
     }
 
     @Override
@@ -376,78 +387,15 @@ public class SeaTunnelServer
     }
 
     public void updateMetrics(Map<TaskLocation, SeaTunnelMetricsContext> 
localMap) {
-        if (localMap == null || localMap.isEmpty()) {
-            return;
-        }
-        int partitionCount = 
seaTunnelConfig.getEngineConfig().getJobMetricsPartitionCount();
-
-        IMap<Long, Map<TaskLocation, SeaTunnelMetricsContext>> metricsImap =
-                
getNodeEngine().getHazelcastInstance().getMap(Constant.IMAP_RUNNING_JOB_METRICS);
-
-        Map<Long, Map<TaskLocation, SeaTunnelMetricsContext>> partitioned = 
new HashMap<>();
-        localMap.forEach(
-                (key, value) -> {
-                    long partition = getMetricsImapPartition(key, 
partitionCount);
-                    partitioned.computeIfAbsent(partition, k -> new 
HashMap<>()).put(key, value);
-                });
-
-        partitioned
-                .entrySet()
-                .parallelStream()
-                .forEach(
-                        entry -> {
-                            metricsImap.compute(
-                                    entry.getKey(),
-                                    (k, oldVal) -> {
-                                        if (oldVal == null) oldVal = new 
HashMap<>();
-                                        oldVal.putAll(entry.getValue());
-                                        return oldVal;
-                                    });
-                        });
+        MetricsSnapshotStateStore metricsSnapshotStateStore =
+                engineContext.getStateStores().metricsSnapshotStore();
+        metricsSnapshotStateStore.merge(localMap);
     }
 
     public void removeMetrics(PipelineLocation pipelineLocation) {
-        IMap<Long, Map<TaskLocation, SeaTunnelMetricsContext>> metricsImap =
-                
getNodeEngine().getHazelcastInstance().getMap(Constant.IMAP_RUNNING_JOB_METRICS);
-
-        Map<Long, List<TaskLocation>> partitionedTasks = new HashMap<>();
-        for (Map.Entry<Long, Map<TaskLocation, SeaTunnelMetricsContext>> entry 
:
-                metricsImap.entrySet()) {
-            long partition = entry.getKey();
-            List<TaskLocation> tasksToRemove =
-                    entry.getValue().keySet().stream()
-                            .filter(
-                                    t ->
-                                            t.getTaskGroupLocation()
-                                                    .getPipelineLocation()
-                                                    .equals(pipelineLocation))
-                            .collect(Collectors.toList());
-            if (!tasksToRemove.isEmpty()) {
-                partitionedTasks.put(partition, tasksToRemove);
-            }
-        }
-
-        partitionedTasks
-                .entrySet()
-                .parallelStream()
-                .forEach(
-                        entry -> {
-                            long partition = entry.getKey();
-                            List<TaskLocation> tasks = entry.getValue();
-                            metricsImap.compute(
-                                    partition,
-                                    (k, oldVal) -> {
-                                        if (oldVal != null) {
-                                            tasks.forEach(oldVal::remove);
-                                            if (oldVal.isEmpty()) return null;
-                                        }
-                                        return oldVal;
-                                    });
-                        });
-    }
-
-    public static long getMetricsImapPartition(TaskLocation key, int 
partitionCount) {
-        return (key.hashCode() & 0x7FFFFFFF) % partitionCount;
+        MetricsSnapshotStateStore metricsSnapshotStateStore =
+                engineContext.getStateStores().metricsSnapshotStore();
+        metricsSnapshotStateStore.removePipeline(pipelineLocation);
     }
 
     public boolean isCoordinatorActive() {
diff --git 
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/TaskExecutionService.java
 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/TaskExecutionService.java
index 5e011d5391..b3bc49dd6d 100644
--- 
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/TaskExecutionService.java
+++ 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/TaskExecutionService.java
@@ -33,6 +33,7 @@ import 
org.apache.seatunnel.engine.common.utils.PassiveCompletableFuture;
 import org.apache.seatunnel.engine.common.utils.concurrent.CompletableFuture;
 import org.apache.seatunnel.engine.core.classloader.ClassLoaderService;
 import org.apache.seatunnel.engine.core.job.ConnectorJarIdentifier;
+import org.apache.seatunnel.engine.server.common.SeaTunnelEngineContext;
 import 
org.apache.seatunnel.engine.server.exception.TaskGroupContextNotFoundException;
 import org.apache.seatunnel.engine.server.execution.ExecutionState;
 import org.apache.seatunnel.engine.server.execution.ProgressState;
@@ -147,6 +148,9 @@ public class TaskExecutionService implements 
DynamicMetricsProvider {
     /** The NodeEngine implementation for this Hazelcast node. */
     private final NodeEngineImpl nodeEngine;
 
+    /** Shared engine context exposing state-store abstractions. */
+    private final SeaTunnelEngineContext engineContext;
+
     /** Service for managing class loaders for connector jars. */
     private final ClassLoaderService classLoaderService;
 
@@ -225,10 +229,12 @@ public class TaskExecutionService implements 
DynamicMetricsProvider {
     public TaskExecutionService(
             ClassLoaderService classLoaderService,
             NodeEngineImpl nodeEngine,
+            SeaTunnelEngineContext engineContext,
             EventService eventService) {
         seaTunnelConfig = ConfigProvider.locateAndGetSeaTunnelConfig();
         this.hzInstanceName = nodeEngine.getHazelcastInstance().getName();
         this.nodeEngine = nodeEngine;
+        this.engineContext = engineContext;
         this.classLoaderService = classLoaderService;
         this.logger = 
nodeEngine.getLoggingService().getLogger(TaskExecutionService.class);
 
@@ -538,7 +544,8 @@ public class TaskExecutionService implements 
DynamicMetricsProvider {
                             .peek(
                                     task -> {
                                         TaskExecutionContext 
taskExecutionContext =
-                                                new TaskExecutionContext(task, 
nodeEngine, this);
+                                                new TaskExecutionContext(
+                                                        task, nodeEngine, 
engineContext, this);
                                         
task.setTaskExecutionContext(taskExecutionContext);
                                         taskExecutionContextMap.put(
                                                 task.getTaskID(), 
taskExecutionContext);
diff --git 
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/checkpoint/monitor/CheckpointMonitorService.java
 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/checkpoint/monitor/CheckpointMonitorService.java
index 44ec00b857..eb5b92553b 100644
--- 
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/checkpoint/monitor/CheckpointMonitorService.java
+++ 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/checkpoint/monitor/CheckpointMonitorService.java
@@ -19,7 +19,6 @@ package org.apache.seatunnel.engine.server.checkpoint.monitor;
 
 import org.apache.seatunnel.shade.com.google.common.base.Strings;
 
-import org.apache.seatunnel.engine.common.Constant;
 import org.apache.seatunnel.engine.core.checkpoint.CheckpointHistoryEntry;
 import org.apache.seatunnel.engine.core.checkpoint.CheckpointInfo;
 import org.apache.seatunnel.engine.core.checkpoint.CheckpointOverview;
@@ -31,61 +30,41 @@ import 
org.apache.seatunnel.engine.server.checkpoint.CheckpointCloseReason;
 import org.apache.seatunnel.engine.server.checkpoint.CompletedCheckpoint;
 import org.apache.seatunnel.engine.server.checkpoint.SubtaskStatistics;
 import org.apache.seatunnel.engine.server.checkpoint.TaskStatistics;
+import org.apache.seatunnel.engine.server.common.SeaTunnelEngineContext;
+import 
org.apache.seatunnel.engine.server.common.statestore.checkpoint.CheckpointOverviewStateStore;
 
-import com.hazelcast.core.EntryEvent;
-import com.hazelcast.map.IMap;
-import com.hazelcast.map.listener.EntryAddedListener;
-import com.hazelcast.map.listener.EntryExpiredListener;
-import com.hazelcast.map.listener.EntryRemovedListener;
-import com.hazelcast.map.listener.EntryUpdatedListener;
-import com.hazelcast.spi.impl.NodeEngine;
 import lombok.extern.slf4j.Slf4j;
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
-import java.util.Set;
-import java.util.UUID;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
 @Slf4j
 public class CheckpointMonitorService {
 
-    private final NodeEngine nodeEngine;
-    private volatile IMap<Long, CheckpointOverview> overviewMap;
+    private final SeaTunnelEngineContext engineContext;
     private final int maxHistorySize;
-    private final AtomicLong overviewJobCount = new AtomicLong();
-    private final AtomicLong inProgressCheckpointCount = new AtomicLong();
-    private final AtomicLong retainedHistoryCount = new AtomicLong();
-    private final Object overviewStatsLock = new Object();
-    private final Set<Long> overviewDirtyJobIds = 
ConcurrentHashMap.newKeySet();
-    private final Map<Long, CheckpointOverviewStats> overviewStatsByJobId = 
new HashMap<>();
-    private final AtomicBoolean overviewStatsInitializing = new 
AtomicBoolean(false);
-    private volatile UUID overviewListenerId;
 
-    public CheckpointMonitorService(NodeEngine nodeEngine, int maxHistorySize) 
{
-        this.nodeEngine = nodeEngine;
+    private volatile CheckpointOverviewStateStore overviewMap;
+
+    public CheckpointMonitorService(SeaTunnelEngineContext engineContext, int 
maxHistorySize) {
+        this.engineContext = engineContext;
         this.maxHistorySize = maxHistorySize;
     }
 
-    private IMap<Long, CheckpointOverview> getOverviewMap() {
+    private CheckpointOverviewStateStore getOverviewMap() {
         if (overviewMap == null) {
             synchronized (this) {
                 if (overviewMap == null) {
                     overviewMap =
-                            nodeEngine
-                                    .getHazelcastInstance()
-                                    .getMap(Constant.IMAP_CHECKPOINT_MONITOR);
-                    initOverviewStats();
+                            engineContext
+                                    .getStateStores()
+                                    .auxiliary()
+                                    .checkpointOverviewStateStore();
                 }
             }
         }
@@ -259,186 +238,26 @@ public class CheckpointMonitorService {
     }
 
     public long getOverviewJobCount() {
-        return overviewJobCount.get();
+        return getOverviewMap().getOverviewJobCount();
     }
 
     public long getInProgressCheckpointCount() {
-        return inProgressCheckpointCount.get();
+        return getOverviewMap().getInProgressCheckpointCount();
     }
 
     public long getRetainedHistoryCount() {
-        return retainedHistoryCount.get();
+        return getOverviewMap().getRetainedHistoryCount();
     }
 
     private void updateOverview(
             long jobId, int pipelineId, Consumer<PipelineCheckpointOverview> 
consumer) {
-        getOverviewMap()
-                .compute(
-                        jobId,
-                        (id, overview) -> {
-                            CheckpointOverview snapshot =
-                                    overview == null ? new 
CheckpointOverview(jobId) : overview;
-                            PipelineCheckpointOverview pipeline =
-                                    snapshot.getOrCreatePipeline(pipelineId);
-                            consumer.accept(pipeline);
-                            snapshot.setUpdatedAt(System.currentTimeMillis());
-                            return snapshot;
-                        });
+        getOverviewMap().updateOverview(jobId, pipelineId, consumer);
     }
 
     private void removeInProgressIfExists(PipelineCheckpointOverview pipeline, 
long checkpointId) {
         pipeline.getInProgress().removeIf(cp -> cp.getCheckpointId() == 
checkpointId);
     }
 
-    private void initOverviewStats() {
-        removeOverviewListener();
-        overviewListenerId =
-                overviewMap.addEntryListener(new 
CheckpointOverviewEntryListener(), true);
-        overviewStatsInitializing.set(true);
-        overviewDirtyJobIds.clear();
-
-        Map<Long, CheckpointOverviewStats> snapshotStats = new HashMap<>();
-        overviewMap.forEach(
-                (jobId, overview) -> snapshotStats.put(jobId, 
toOverviewStats(overview)));
-
-        Set<Long> dirtyJobIds = new HashSet<>(overviewDirtyJobIds);
-        for (Long jobId : dirtyJobIds) {
-            snapshotStats.put(jobId, toOverviewStats(overviewMap.get(jobId)));
-        }
-
-        synchronized (overviewStatsLock) {
-            overviewStatsByJobId.clear();
-            overviewJobCount.set(0L);
-            inProgressCheckpointCount.set(0L);
-            retainedHistoryCount.set(0L);
-            snapshotStats.forEach(this::replaceOverviewStatsLocked);
-            overviewStatsInitializing.set(false);
-
-            Set<Long> postSnapshotDirtyJobIds = new 
HashSet<>(overviewDirtyJobIds);
-            overviewDirtyJobIds.clear();
-            for (Long jobId : postSnapshotDirtyJobIds) {
-                replaceOverviewStatsLocked(jobId, 
toOverviewStats(overviewMap.get(jobId)));
-            }
-        }
-    }
-
-    private void removeOverviewListener() {
-        if (overviewMap != null && overviewListenerId != null) {
-            overviewMap.removeEntryListener(overviewListenerId);
-            overviewListenerId = null;
-        }
-        overviewStatsInitializing.set(false);
-        overviewDirtyJobIds.clear();
-        synchronized (overviewStatsLock) {
-            overviewStatsByJobId.clear();
-        }
-        overviewJobCount.set(0L);
-        inProgressCheckpointCount.set(0L);
-        retainedHistoryCount.set(0L);
-    }
-
-    private CheckpointOverviewStats toOverviewStats(CheckpointOverview 
overview) {
-        if (overview == null) {
-            return CheckpointOverviewStats.EMPTY;
-        }
-        return new CheckpointOverviewStats(
-                1L, getInProgressCount(overview), getHistoryCount(overview));
-    }
-
-    private void replaceOverviewStatsLocked(Long jobId, 
CheckpointOverviewStats stats) {
-        CheckpointOverviewStats currentStats = overviewStatsByJobId.get(jobId);
-        if (currentStats != null) {
-            overviewJobCount.addAndGet(-currentStats.jobCount);
-            
inProgressCheckpointCount.addAndGet(-currentStats.inProgressCheckpointCount);
-            retainedHistoryCount.addAndGet(-currentStats.retainedHistoryCount);
-        }
-
-        if (stats.isEmpty()) {
-            overviewStatsByJobId.remove(jobId);
-            return;
-        }
-
-        overviewStatsByJobId.put(jobId, stats);
-        overviewJobCount.addAndGet(stats.jobCount);
-        inProgressCheckpointCount.addAndGet(stats.inProgressCheckpointCount);
-        retainedHistoryCount.addAndGet(stats.retainedHistoryCount);
-    }
-
-    private long getInProgressCount(CheckpointOverview overview) {
-        return overview.getPipelines().values().stream()
-                .filter(Objects::nonNull)
-                .mapToLong(pipelineOverview -> 
pipelineOverview.getInProgress().size())
-                .sum();
-    }
-
-    private long getHistoryCount(CheckpointOverview overview) {
-        return overview.getPipelines().values().stream()
-                .filter(Objects::nonNull)
-                .mapToLong(pipelineOverview -> 
pipelineOverview.getHistory().size())
-                .sum();
-    }
-
-    private final class CheckpointOverviewEntryListener
-            implements EntryAddedListener<Long, CheckpointOverview>,
-                    EntryUpdatedListener<Long, CheckpointOverview>,
-                    EntryRemovedListener<Long, CheckpointOverview>,
-                    EntryExpiredListener<Long, CheckpointOverview> {
-
-        @Override
-        public void entryAdded(EntryEvent<Long, CheckpointOverview> event) {
-            replaceOverviewStats(event.getKey(), event.getValue());
-        }
-
-        @Override
-        public void entryUpdated(EntryEvent<Long, CheckpointOverview> event) {
-            replaceOverviewStats(event.getKey(), event.getValue());
-        }
-
-        @Override
-        public void entryRemoved(EntryEvent<Long, CheckpointOverview> event) {
-            replaceOverviewStats(event.getKey(), null);
-        }
-
-        @Override
-        public void entryExpired(EntryEvent<Long, CheckpointOverview> event) {
-            replaceOverviewStats(event.getKey(), null);
-        }
-    }
-
-    private void replaceOverviewStats(Long jobId, CheckpointOverview overview) 
{
-        if (overviewStatsInitializing.get()) {
-            overviewDirtyJobIds.add(jobId);
-            return;
-        }
-        synchronized (overviewStatsLock) {
-            if (overviewStatsInitializing.get()) {
-                overviewDirtyJobIds.add(jobId);
-                return;
-            }
-            replaceOverviewStatsLocked(jobId, toOverviewStats(overview));
-        }
-    }
-
-    private static final class CheckpointOverviewStats {
-        private static final CheckpointOverviewStats EMPTY =
-                new CheckpointOverviewStats(0L, 0L, 0L);
-
-        private final long jobCount;
-        private final long inProgressCheckpointCount;
-        private final long retainedHistoryCount;
-
-        private CheckpointOverviewStats(
-                long jobCount, long inProgressCheckpointCount, long 
retainedHistoryCount) {
-            this.jobCount = jobCount;
-            this.inProgressCheckpointCount = inProgressCheckpointCount;
-            this.retainedHistoryCount = retainedHistoryCount;
-        }
-
-        private boolean isEmpty() {
-            return jobCount == 0L && inProgressCheckpointCount == 0L && 
retainedHistoryCount == 0L;
-        }
-    }
-
     public static long calculateStateSize(CompletedCheckpoint checkpoint) {
         return checkpoint.getTaskStatistics().values().stream()
                 .map(TaskStatistics::getSubtaskStats)
diff --git 
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/SeaTunnelEngineContext.java
 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/SeaTunnelEngineContext.java
new file mode 100644
index 0000000000..18128fcab4
--- /dev/null
+++ 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/SeaTunnelEngineContext.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.seatunnel.engine.server.common;
+
+import org.apache.seatunnel.engine.server.common.statestore.EngineStateStores;
+
+import java.util.Objects;
+
+/**
+ * Shared engine context propagated inside the engine.
+ *
+ * <p>This context is intended to reduce direct propagation of 
infrastructure-specific runtime
+ * objects into engine services. At the current stage, it only exposes state 
store bundles and
+ * serves as a small entry point for state-related abstractions.
+ */
+public final class SeaTunnelEngineContext implements AutoCloseable {
+
+    private final EngineStateStores stateStores;
+
+    private SeaTunnelEngineContext(Builder builder) {
+        this.stateStores = builder.stateStores;
+    }
+
+    public static Builder builder(EngineStateStores stateStores) {
+        return new Builder(stateStores);
+    }
+
+    /**
+     * Returns the grouped engine state stores.
+     *
+     * @return engine state stores
+     */
+    public EngineStateStores getStateStores() {
+        return stateStores;
+    }
+
+    @Override
+    public void close() {
+        stateStores.close();
+    }
+
+    /** Builder for {@link SeaTunnelEngineContext}. */
+    public static final class Builder {
+        private final EngineStateStores stateStores;
+
+        private Builder(EngineStateStores stateStores) {
+            this.stateStores = Objects.requireNonNull(stateStores, 
"stateStores");
+        }
+
+        public SeaTunnelEngineContext build() {
+            return new SeaTunnelEngineContext(this);
+        }
+    }
+}
diff --git 
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/AuthoritativeStateStores.java
 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/AuthoritativeStateStores.java
new file mode 100644
index 0000000000..1e14e769a9
--- /dev/null
+++ 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/AuthoritativeStateStores.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.seatunnel.engine.server.common.statestore;
+
+/**
+ * Bundle of authoritative control state that a new leader must trust during 
leader handoff.
+ *
+ * <p>This layer groups states that are likely to live on top of a consensus 
layer. The actual
+ * storage backend may still be a local store, but the responsibility for 
deciding what is
+ * authoritative belongs more strongly to this group.
+ */
+public interface AuthoritativeStateStores extends AutoCloseable {
+
+    /**
+     * Releases resources owned by authoritative stores.
+     *
+     * <p>Implementations that only wrap non-closeable stores may keep the 
default no-op behavior.
+     */
+    @Override
+    default void close() {
+        // no-op
+    }
+}
diff --git 
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/AuxiliaryStateStores.java
 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/AuxiliaryStateStores.java
new file mode 100644
index 0000000000..af7c8bdae5
--- /dev/null
+++ 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/AuxiliaryStateStores.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.seatunnel.engine.server.common.statestore;
+
+import 
org.apache.seatunnel.engine.server.common.statestore.checkpoint.CheckpointOverviewStateStore;
+import 
org.apache.seatunnel.engine.server.common.statestore.metrics.MetricsSnapshotStateStore;
+
+/**
+ * Bundle of state that is closer to observability, recent history, or cleanup 
than to failover
+ * correctness.
+ *
+ * <p>This group collects states that can often tolerate more staleness, or 
for which limited loss
+ * is less likely to break system correctness immediately.
+ */
+public interface AuxiliaryStateStores extends AutoCloseable {
+    /**
+     * Returns the store for runtime task metrics snapshots.
+     *
+     * @return metrics snapshot store
+     */
+    MetricsSnapshotStateStore metricsSnapshotStore();
+
+    /**
+     * Returns the store for checkpoint overviews.
+     *
+     * @return checkpoint overview state store
+     */
+    CheckpointOverviewStateStore checkpointOverviewStateStore();
+
+    /**
+     * Releases resources owned by auxiliary stores.
+     *
+     * <p>Implementations that only wrap non-closeable stores may keep the 
default no-op behavior.
+     */
+    @Override
+    default void close() {
+        // no-op
+    }
+}
diff --git 
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/DefaultAuthoritativeStateStores.java
 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/DefaultAuthoritativeStateStores.java
new file mode 100644
index 0000000000..f658f31d39
--- /dev/null
+++ 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/DefaultAuthoritativeStateStores.java
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.seatunnel.engine.server.common.statestore;
+
+/** Default immutable implementation of {@link AuthoritativeStateStores}. */
+public class DefaultAuthoritativeStateStores implements 
AuthoritativeStateStores {}
diff --git 
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/DefaultAuxiliaryStateStores.java
 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/DefaultAuxiliaryStateStores.java
new file mode 100644
index 0000000000..87017e8f3e
--- /dev/null
+++ 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/DefaultAuxiliaryStateStores.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.seatunnel.engine.server.common.statestore;
+
+import 
org.apache.seatunnel.engine.server.common.statestore.checkpoint.CheckpointOverviewStateStore;
+import 
org.apache.seatunnel.engine.server.common.statestore.metrics.MetricsSnapshotStateStore;
+
+import java.util.Objects;
+
+/** Default immutable implementation of {@link AuxiliaryStateStores}. */
+public class DefaultAuxiliaryStateStores implements AuxiliaryStateStores {
+    private final MetricsSnapshotStateStore metricsSnapshotStore;
+    private final CheckpointOverviewStateStore checkpointOverviewStateStore;
+
+    public DefaultAuxiliaryStateStores(
+            MetricsSnapshotStateStore metricsSnapshotStore,
+            CheckpointOverviewStateStore checkpointOverviewStateStore) {
+        this.metricsSnapshotStore =
+                Objects.requireNonNull(metricsSnapshotStore, 
"metricsSnapshotStore");
+        this.checkpointOverviewStateStore =
+                Objects.requireNonNull(
+                        checkpointOverviewStateStore, 
"checkpointOverviewStateStore");
+    }
+
+    @Override
+    public MetricsSnapshotStateStore metricsSnapshotStore() {
+        return metricsSnapshotStore;
+    }
+
+    @Override
+    public CheckpointOverviewStateStore checkpointOverviewStateStore() {
+        return checkpointOverviewStateStore;
+    }
+
+    @Override
+    public void close() {
+        closeIfPossible(checkpointOverviewStateStore);
+        closeIfPossible(metricsSnapshotStore);
+    }
+
+    private void closeIfPossible(Object store) {
+        if (!(store instanceof AutoCloseable)) {
+            return;
+        }
+        try {
+            ((AutoCloseable) store).close();
+        } catch (Exception e) {
+            throw new IllegalStateException("Failed to close auxiliary state 
store", e);
+        }
+    }
+}
diff --git 
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/EngineStateStoreNames.java
 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/EngineStateStoreNames.java
new file mode 100644
index 0000000000..3ed6dfd244
--- /dev/null
+++ 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/EngineStateStoreNames.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.seatunnel.engine.server.common.statestore;
+
+import lombok.Getter;
+
+/**
+ * Bundle of map names required to build a Hazelcast-based {@link 
EngineStateStores} implementation.
+ */
+@Getter
+public class EngineStateStoreNames {
+    private EngineStateStoreNames() {}
+
+    public static final String RUNNING_JOB_METRICS = 
"engine_runningJobMetrics";
+    public static final String CHECKPOINT_MONITOR = 
"engine_checkpoint_monitor";
+}
diff --git 
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/EngineStateStores.java
 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/EngineStateStores.java
new file mode 100644
index 0000000000..e86f206126
--- /dev/null
+++ 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/EngineStateStores.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.seatunnel.engine.server.common.statestore;
+
+import 
org.apache.seatunnel.engine.server.common.statestore.checkpoint.CheckpointOverviewStateStore;
+import 
org.apache.seatunnel.engine.server.common.statestore.metrics.MetricsSnapshotStateStore;
+
+/**
+ * Top-level bundle of state stores used directly by the engine.
+ *
+ * <p>This is the high-level port that lets engine code depend on state 
semantics rather than
+ * concrete implementations such as {@code HazelcastRuntimeStateStore}.
+ *
+ * <p>Implementation construction is expected to stay in 
Hazelcast/RocksDB-specific providers, while
+ * engine code depends only on this interface.
+ */
+public interface EngineStateStores extends AutoCloseable {
+    /**
+     * Returns the bundle of control state that must be treated as 
authoritative during leader
+     * handoff.
+     *
+     * @return authoritative state stores
+     */
+    AuthoritativeStateStores authoritative();
+
+    /**
+     * Returns the bundle of auxiliary state used mainly for observability, 
recent history, or
+     * cleanup.
+     *
+     * @return auxiliary state stores
+     */
+    AuxiliaryStateStores auxiliary();
+
+    /**
+     * Returns the store for runtime task metrics snapshots.
+     *
+     * @return metrics snapshot store
+     */
+    default MetricsSnapshotStateStore metricsSnapshotStore() {
+        return auxiliary().metricsSnapshotStore();
+    }
+
+    /**
+     * Returns the store for checkpoint overviews.
+     *
+     * @return checkpoint overview state store
+     */
+    default CheckpointOverviewStateStore checkpointOverviewStateStore() {
+        return auxiliary().checkpointOverviewStateStore();
+    }
+
+    /**
+     * Releases resources owned by the stores.
+     *
+     * <p>Hazelcast implementations are usually no-op, while RocksDB 
implementations use this to
+     * close the underlying database resources.
+     */
+    @Override
+    void close();
+}
diff --git 
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/IterableStateStore.java
 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/IterableStateStore.java
new file mode 100644
index 0000000000..0ead0ef613
--- /dev/null
+++ 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/IterableStateStore.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.seatunnel.engine.server.common.statestore;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Backend storage capability for full iteration.
+ *
+ * <p>Operations that read all values can have very different costs depending 
on the backend, so
+ * this capability is separated from the base {@link StateStore}.
+ *
+ * <p>This is better treated as a backend SPI used internally by higher-level 
stores such as history
+ * or metrics stores, rather than as an engine-facing contract.
+ *
+ * @param <K> key type
+ * @param <V> value type
+ */
+public interface IterableStateStore<K, V> extends StateStore<K, V> {
+
+    /** @return all stored entries */
+    Set<Map.Entry<K, V>> entrySet();
+
+    /** @return all stored values */
+    Collection<V> values();
+
+    /**
+     * Returns whether the store is empty.
+     *
+     * @return {@code true} if the store is empty
+     */
+    boolean isEmpty();
+
+    /**
+     * Returns the total number of entries.
+     *
+     * @return entry count
+     */
+    int size();
+}
diff --git 
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/StateStore.java
 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/StateStore.java
new file mode 100644
index 0000000000..60c09deabb
--- /dev/null
+++ 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/StateStore.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.seatunnel.engine.server.common.statestore;
+
+/**
+ * Low-level backend storage SPI.
+ *
+ * <p>This is not meant to be the public state contract used directly by 
engine code. Instead, it is
+ * the minimal key-value storage contract used to implement real backends such 
as RocksDB-backed
+ * stores.
+ *
+ * <p>Rather than copying the full Hazelcast {@code IMap} surface, it keeps 
only the minimum
+ * operations that remain meaningful when switching between backend 
implementations.
+ *
+ * @param <K> key type
+ * @param <V> value type
+ */
+public interface StateStore<K, V> {
+
+    /**
+     * Retrieves the value for the given key.
+     *
+     * @param key key to look up
+     * @return stored value, or {@code null} if absent
+     */
+    V get(K key);
+
+    /**
+     * Stores a value for the given key.
+     *
+     * @param key key to store
+     * @param value value to store
+     */
+    void put(K key, V value);
+
+    /**
+     * Stores a value only when the key is currently absent.
+     *
+     * @param key key to store
+     * @param value value to store
+     * @return existing value if present, otherwise {@code null}
+     */
+    V putIfAbsent(K key, V value);
+
+    /**
+     * Removes the value for the given key.
+     *
+     * @param key key to remove
+     */
+    void remove(K key);
+
+    /**
+     * Returns whether the key exists.
+     *
+     * @param key key to check
+     * @return {@code true} if the key exists
+     */
+    boolean containsKey(K key);
+
+    /**
+     * Returns whether the store is empty.
+     *
+     * @return {@code true} if the store is empty
+     */
+    boolean isEmpty();
+
+    /**
+     * Returns the number of key-value pairs in the store.
+     *
+     * @return size of the store
+     */
+    int size();
+
+    /**
+     * Returns the default value when the key is absent.
+     *
+     * @param key key to look up
+     * @param defaultValue default value
+     * @return stored value or the default value
+     */
+    default V getOrDefault(K key, V defaultValue) {
+        V value = get(key);
+        return value != null ? value : defaultValue;
+    }
+}
diff --git 
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/checkpoint/CheckpointOverviewStateStore.java
 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/checkpoint/CheckpointOverviewStateStore.java
new file mode 100644
index 0000000000..342024ccfe
--- /dev/null
+++ 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/checkpoint/CheckpointOverviewStateStore.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.seatunnel.engine.server.common.statestore.checkpoint;
+
+import org.apache.seatunnel.engine.core.checkpoint.CheckpointOverview;
+import org.apache.seatunnel.engine.core.checkpoint.PipelineCheckpointOverview;
+import org.apache.seatunnel.engine.server.common.statestore.StateStore;
+
+import java.util.function.Consumer;
+
+/**
+ * Store for checkpoint monitoring overview state.
+ *
+ * <p>This store keeps checkpoint overview data used by monitoring and REST 
queries. It is
+ * operational and observability-oriented state rather than core failover 
state.
+ */
+public interface CheckpointOverviewStateStore extends StateStore<Long, 
CheckpointOverview> {
+
+    /**
+     * Updates the overview for the given job and pipeline.
+     *
+     * <p>If no overview exists for the job, a new one is created. If no 
pipeline overview exists,
+     * it is created before applying the updater.
+     *
+     * @param jobId job identifier
+     * @param pipelineId pipeline identifier
+     * @param updater pipeline-level updater
+     */
+    void updateOverview(long jobId, int pipelineId, 
Consumer<PipelineCheckpointOverview> updater);
+
+    /**
+     * Returns the number of jobs currently tracked by the overview store.
+     *
+     * @return tracked job count
+     */
+    long getOverviewJobCount();
+
+    /**
+     * Returns the total number of in-progress checkpoints across all tracked 
jobs and pipelines.
+     *
+     * @return in-progress checkpoint count
+     */
+    long getInProgressCheckpointCount();
+
+    /**
+     * Returns the total number of retained checkpoint history entries across 
all tracked jobs and
+     * pipelines.
+     *
+     * @return retained checkpoint history entry count
+     */
+    long getRetainedHistoryCount();
+}
diff --git 
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/checkpoint/hazelcast/HazelcastCheckpointOverviewStateStore.java
 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/checkpoint/hazelcast/HazelcastCheckpointOverviewStateStore.java
new file mode 100644
index 0000000000..0a7b9482be
--- /dev/null
+++ 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/checkpoint/hazelcast/HazelcastCheckpointOverviewStateStore.java
@@ -0,0 +1,282 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package 
org.apache.seatunnel.engine.server.common.statestore.checkpoint.hazelcast;
+
+import org.apache.seatunnel.engine.core.checkpoint.CheckpointOverview;
+import org.apache.seatunnel.engine.core.checkpoint.PipelineCheckpointOverview;
+import 
org.apache.seatunnel.engine.server.common.statestore.checkpoint.CheckpointOverviewStateStore;
+
+import com.hazelcast.core.EntryEvent;
+import com.hazelcast.map.IMap;
+import com.hazelcast.map.listener.EntryAddedListener;
+import com.hazelcast.map.listener.EntryExpiredListener;
+import com.hazelcast.map.listener.EntryRemovedListener;
+import com.hazelcast.map.listener.EntryUpdatedListener;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Consumer;
+
+/** Hazelcast-backed implementation of {@link CheckpointOverviewStateStore}. */
+public class HazelcastCheckpointOverviewStateStore
+        implements CheckpointOverviewStateStore, AutoCloseable {
+
+    private final IMap<Long, CheckpointOverview> overviewMap;
+    private final AtomicLong overviewJobCount = new AtomicLong();
+    private final AtomicLong inProgressCheckpointCount = new AtomicLong();
+    private final AtomicLong retainedHistoryCount = new AtomicLong();
+    private final Object overviewStatsLock = new Object();
+    private final Set<Long> dirtyJobIds = ConcurrentHashMap.newKeySet();
+    private final Map<Long, CheckpointOverviewStats> statsByJobId = new 
HashMap<>();
+    private final AtomicBoolean statsInitializing = new AtomicBoolean(false);
+    private volatile UUID overviewListenerId;
+
+    public HazelcastCheckpointOverviewStateStore(IMap<Long, 
CheckpointOverview> overviewMap) {
+        this.overviewMap = Objects.requireNonNull(overviewMap, "overviewMap");
+        initStats();
+    }
+
+    @Override
+    public CheckpointOverview get(Long jobId) {
+        return overviewMap.get(jobId);
+    }
+
+    @Override
+    public void put(Long jobId, CheckpointOverview overview) {
+        overviewMap.put(jobId, overview);
+    }
+
+    @Override
+    public CheckpointOverview putIfAbsent(Long jobId, CheckpointOverview 
overview) {
+        return overviewMap.putIfAbsent(jobId, overview);
+    }
+
+    @Override
+    public void remove(Long jobId) {
+        overviewMap.remove(jobId);
+    }
+
+    @Override
+    public boolean containsKey(Long jobId) {
+        return overviewMap.containsKey(jobId);
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return overviewJobCount.get() == 0L;
+    }
+
+    @Override
+    public int size() {
+        return Math.toIntExact(overviewJobCount.get());
+    }
+
+    @Override
+    public void updateOverview(
+            long jobId, int pipelineId, Consumer<PipelineCheckpointOverview> 
updater) {
+        overviewMap.compute(
+                jobId,
+                (id, overview) -> {
+                    CheckpointOverview snapshot =
+                            overview == null ? new CheckpointOverview(jobId) : 
overview;
+                    PipelineCheckpointOverview pipeline = 
snapshot.getOrCreatePipeline(pipelineId);
+                    updater.accept(pipeline);
+                    snapshot.setUpdatedAt(System.currentTimeMillis());
+                    return snapshot;
+                });
+    }
+
+    @Override
+    public long getOverviewJobCount() {
+        return overviewJobCount.get();
+    }
+
+    @Override
+    public long getInProgressCheckpointCount() {
+        return inProgressCheckpointCount.get();
+    }
+
+    @Override
+    public long getRetainedHistoryCount() {
+        return retainedHistoryCount.get();
+    }
+
+    @Override
+    public void close() {
+        removeOverviewListener();
+    }
+
+    private void initStats() {
+        removeOverviewListener();
+        overviewListenerId =
+                overviewMap.addEntryListener(new 
CheckpointOverviewEntryListener(), true);
+        statsInitializing.set(true);
+        dirtyJobIds.clear();
+
+        Map<Long, CheckpointOverviewStats> snapshotStats = new HashMap<>();
+        overviewMap.forEach(
+                (jobId, overview) -> snapshotStats.put(jobId, 
toOverviewStats(overview)));
+
+        Set<Long> dirtyJobIdsDuringSnapshot = new HashSet<>(dirtyJobIds);
+        for (Long jobId : dirtyJobIdsDuringSnapshot) {
+            snapshotStats.put(jobId, toOverviewStats(overviewMap.get(jobId)));
+        }
+
+        synchronized (overviewStatsLock) {
+            statsByJobId.clear();
+            overviewJobCount.set(0L);
+            inProgressCheckpointCount.set(0L);
+            retainedHistoryCount.set(0L);
+            snapshotStats.forEach(this::replaceOverviewStatsLocked);
+            statsInitializing.set(false);
+
+            Set<Long> postSnapshotDirtyJobIds = new HashSet<>(dirtyJobIds);
+            dirtyJobIds.clear();
+            for (Long jobId : postSnapshotDirtyJobIds) {
+                replaceOverviewStatsLocked(jobId, 
toOverviewStats(overviewMap.get(jobId)));
+            }
+        }
+    }
+
+    private void removeOverviewListener() {
+        if (overviewListenerId != null) {
+            overviewMap.removeEntryListener(overviewListenerId);
+            overviewListenerId = null;
+        }
+        statsInitializing.set(false);
+        dirtyJobIds.clear();
+        synchronized (overviewStatsLock) {
+            statsByJobId.clear();
+        }
+        overviewJobCount.set(0L);
+        inProgressCheckpointCount.set(0L);
+        retainedHistoryCount.set(0L);
+    }
+
+    private CheckpointOverviewStats toOverviewStats(CheckpointOverview 
overview) {
+        if (overview == null) {
+            return CheckpointOverviewStats.EMPTY;
+        }
+        return new CheckpointOverviewStats(
+                1L, getInProgressCount(overview), getHistoryCount(overview));
+    }
+
+    private void replaceOverviewStatsLocked(Long jobId, 
CheckpointOverviewStats stats) {
+        CheckpointOverviewStats currentStats = statsByJobId.get(jobId);
+        if (currentStats != null) {
+            overviewJobCount.addAndGet(-currentStats.jobCount);
+            
inProgressCheckpointCount.addAndGet(-currentStats.inProgressCheckpointCount);
+            retainedHistoryCount.addAndGet(-currentStats.retainedHistoryCount);
+        }
+
+        if (stats.isEmpty()) {
+            statsByJobId.remove(jobId);
+            return;
+        }
+
+        statsByJobId.put(jobId, stats);
+        overviewJobCount.addAndGet(stats.jobCount);
+        inProgressCheckpointCount.addAndGet(stats.inProgressCheckpointCount);
+        retainedHistoryCount.addAndGet(stats.retainedHistoryCount);
+    }
+
+    private long getInProgressCount(CheckpointOverview overview) {
+        return overview.getPipelines().values().stream()
+                .filter(Objects::nonNull)
+                .mapToLong(pipelineOverview -> 
pipelineOverview.getInProgress().size())
+                .sum();
+    }
+
+    private long getHistoryCount(CheckpointOverview overview) {
+        return overview.getPipelines().values().stream()
+                .filter(Objects::nonNull)
+                .map(PipelineCheckpointOverview::getHistory)
+                .filter(Objects::nonNull)
+                .mapToLong(Collection::size)
+                .sum();
+    }
+
+    private void replaceOverviewStats(Long jobId, CheckpointOverview overview) 
{
+        if (statsInitializing.get()) {
+            dirtyJobIds.add(jobId);
+            return;
+        }
+        synchronized (overviewStatsLock) {
+            if (statsInitializing.get()) {
+                dirtyJobIds.add(jobId);
+                return;
+            }
+            replaceOverviewStatsLocked(jobId, toOverviewStats(overview));
+        }
+    }
+
+    private final class CheckpointOverviewEntryListener
+            implements EntryAddedListener<Long, CheckpointOverview>,
+                    EntryUpdatedListener<Long, CheckpointOverview>,
+                    EntryRemovedListener<Long, CheckpointOverview>,
+                    EntryExpiredListener<Long, CheckpointOverview> {
+
+        @Override
+        public void entryAdded(EntryEvent<Long, CheckpointOverview> event) {
+            replaceOverviewStats(event.getKey(), event.getValue());
+        }
+
+        @Override
+        public void entryUpdated(EntryEvent<Long, CheckpointOverview> event) {
+            replaceOverviewStats(event.getKey(), event.getValue());
+        }
+
+        @Override
+        public void entryRemoved(EntryEvent<Long, CheckpointOverview> event) {
+            replaceOverviewStats(event.getKey(), null);
+        }
+
+        @Override
+        public void entryExpired(EntryEvent<Long, CheckpointOverview> event) {
+            replaceOverviewStats(event.getKey(), null);
+        }
+    }
+
+    private static final class CheckpointOverviewStats {
+        private static final CheckpointOverviewStats EMPTY =
+                new CheckpointOverviewStats(0L, 0L, 0L);
+
+        private final long jobCount;
+        private final long inProgressCheckpointCount;
+        private final long retainedHistoryCount;
+
+        private CheckpointOverviewStats(
+                long jobCount, long inProgressCheckpointCount, long 
retainedHistoryCount) {
+            this.jobCount = jobCount;
+            this.inProgressCheckpointCount = inProgressCheckpointCount;
+            this.retainedHistoryCount = retainedHistoryCount;
+        }
+
+        private boolean isEmpty() {
+            return jobCount == 0L && inProgressCheckpointCount == 0L && 
retainedHistoryCount == 0L;
+        }
+    }
+}
diff --git 
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/hazelcast/HazelcastEngineStateStores.java
 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/hazelcast/HazelcastEngineStateStores.java
new file mode 100644
index 0000000000..6e5a3a9bde
--- /dev/null
+++ 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/hazelcast/HazelcastEngineStateStores.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.seatunnel.engine.server.common.statestore.hazelcast;
+
+import 
org.apache.seatunnel.engine.server.common.statestore.AuthoritativeStateStores;
+import 
org.apache.seatunnel.engine.server.common.statestore.AuxiliaryStateStores;
+import 
org.apache.seatunnel.engine.server.common.statestore.DefaultAuthoritativeStateStores;
+import 
org.apache.seatunnel.engine.server.common.statestore.DefaultAuxiliaryStateStores;
+import org.apache.seatunnel.engine.server.common.statestore.EngineStateStores;
+import 
org.apache.seatunnel.engine.server.common.statestore.checkpoint.CheckpointOverviewStateStore;
+import 
org.apache.seatunnel.engine.server.common.statestore.checkpoint.hazelcast.HazelcastCheckpointOverviewStateStore;
+import 
org.apache.seatunnel.engine.server.common.statestore.metrics.MetricsSnapshotStateStore;
+import 
org.apache.seatunnel.engine.server.common.statestore.metrics.hazelcast.HazelcastMetricsSnapshotStateStore;
+
+import com.hazelcast.spi.impl.NodeEngine;
+
+import java.util.Objects;
+
+import static 
org.apache.seatunnel.engine.server.common.statestore.EngineStateStoreNames.CHECKPOINT_MONITOR;
+import static 
org.apache.seatunnel.engine.server.common.statestore.EngineStateStoreNames.RUNNING_JOB_METRICS;
+
+/**
+ * {@link EngineStateStores} implementation backed by Hazelcast.
+ *
+ * <p>Engine code is not expected to reference this implementation directly. 
It is intended to be
+ * created only during bootstrap and injected through interfaces.
+ */
+public class HazelcastEngineStateStores implements EngineStateStores {
+
+    private final NodeEngine nodeEngine;
+    private final int metricsPartitionCount;
+    private volatile AuthoritativeStateStores authoritativeStateStores;
+    private volatile AuxiliaryStateStores auxiliaryStateStores;
+
+    public HazelcastEngineStateStores(NodeEngine nodeEngine, int 
metricsPartitionCount) {
+        Objects.requireNonNull(nodeEngine, "nodeEngine");
+        this.nodeEngine = nodeEngine;
+        this.metricsPartitionCount = metricsPartitionCount;
+    }
+
+    private void ensureInitialized() {
+        if (authoritativeStateStores != null && auxiliaryStateStores != null) {
+            return;
+        }
+        synchronized (this) {
+            if (authoritativeStateStores != null && auxiliaryStateStores != 
null) {
+                return;
+            }
+
+            MetricsSnapshotStateStore metricsSnapshotStore =
+                    new HazelcastMetricsSnapshotStateStore(
+                            
nodeEngine.getHazelcastInstance().getMap(RUNNING_JOB_METRICS),
+                            metricsPartitionCount);
+            CheckpointOverviewStateStore checkpointOverviewStateStore =
+                    new HazelcastCheckpointOverviewStateStore(
+                            
nodeEngine.getHazelcastInstance().getMap(CHECKPOINT_MONITOR));
+            this.authoritativeStateStores = new 
DefaultAuthoritativeStateStores();
+            this.auxiliaryStateStores =
+                    new DefaultAuxiliaryStateStores(
+                            metricsSnapshotStore, 
checkpointOverviewStateStore);
+        }
+    }
+
+    @Override
+    public AuthoritativeStateStores authoritative() {
+        ensureInitialized();
+        return authoritativeStateStores;
+    }
+
+    @Override
+    public AuxiliaryStateStores auxiliary() {
+        ensureInitialized();
+        return auxiliaryStateStores;
+    }
+
+    @Override
+    public void close() {
+        if (auxiliaryStateStores != null) {
+            auxiliaryStateStores.close();
+        }
+        if (authoritativeStateStores != null) {
+            authoritativeStateStores.close();
+        }
+    }
+}
diff --git 
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/metrics/MetricsSnapshotStateStore.java
 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/metrics/MetricsSnapshotStateStore.java
new file mode 100644
index 0000000000..03f01ec5ef
--- /dev/null
+++ 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/metrics/MetricsSnapshotStateStore.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.seatunnel.engine.server.common.statestore.metrics;
+
+import org.apache.seatunnel.engine.server.common.statestore.StateStore;
+import org.apache.seatunnel.engine.server.dag.physical.PipelineLocation;
+import org.apache.seatunnel.engine.server.execution.TaskLocation;
+import org.apache.seatunnel.engine.server.metrics.SeaTunnelMetricsContext;
+
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Store for runtime task metrics snapshots.
+ *
+ * <p>Here, {@code merge()} does not mean merging the internals of {@link 
SeaTunnelMetricsContext}.
+ * It means batch-applying the latest snapshots for multiple tasks.
+ *
+ * <p>In the current SeaTunnel metrics reporting flow, each task sends its 
latest {@link
+ * SeaTunnelMetricsContext} to the coordinator, and the coordinator replaces 
the snapshot by task
+ * key. The Hazelcast implementation uses {@code compute()} internally to 
safely update the outer
+ * partition map, while the RocksDB implementation uses task location directly 
as the key, so
+ * task-level overwrite preserves the same storage meaning. Even when a 
backend uses internal
+ * partition buckets, the exposed {@link StateStore} contract remains 
task-based, so {@link #size()}
+ * must report the number of task snapshots rather than backend-specific 
buckets.
+ */
+public interface MetricsSnapshotStateStore
+        extends StateStore<TaskLocation, SeaTunnelMetricsContext> {
+
+    /**
+     * Applies metrics snapshots for multiple tasks in a single batch.
+     *
+     * @param snapshot snapshot batch to apply
+     */
+    void merge(Map<TaskLocation, SeaTunnelMetricsContext> snapshot);
+
+    /**
+     * Stores a single task snapshot by delegating to {@link #merge(Map)}.
+     *
+     * @param taskLocation task location to store
+     * @param metricsContext metrics snapshot to store
+     */
+    @Override
+    default void put(TaskLocation taskLocation, SeaTunnelMetricsContext 
metricsContext) {
+        merge(Collections.singletonMap(taskLocation, metricsContext));
+    }
+
+    /**
+     * Checks whether a task snapshot exists.
+     *
+     * @param taskLocation task location to check
+     * @return {@code true} if a snapshot exists for the task
+     */
+    @Override
+    default boolean containsKey(TaskLocation taskLocation) {
+        return get(taskLocation) != null;
+    }
+
+    /**
+     * Conditional insertion is intentionally not exposed for metrics 
snapshots because the current
+     * engine model treats them as latest-snapshot overwrites.
+     *
+     * @param taskLocation task location to store
+     * @param metricsContext metrics snapshot to store
+     * @return never returns normally
+     */
+    @Override
+    default SeaTunnelMetricsContext putIfAbsent(
+            TaskLocation taskLocation, SeaTunnelMetricsContext metricsContext) 
{
+        throw new UnsupportedOperationException(
+                "Metrics snapshots are updated through merge semantics rather 
than putIfAbsent.");
+    }
+
+    /**
+     * Removes all task metrics belonging to a specific pipeline.
+     *
+     * @param pipelineLocation pipeline location to remove
+     */
+    void removePipeline(PipelineLocation pipelineLocation);
+
+    /**
+     * Checks whether any task snapshot exists for a specific pipeline.
+     *
+     * @param pipelineLocation pipeline location to check
+     * @return {@code true} if any snapshot exists for the pipeline
+     */
+    boolean containsPipeline(PipelineLocation pipelineLocation);
+
+    /**
+     * Returns the number of active backend partition buckets currently 
holding task snapshots.
+     *
+     * <p>This value is observability-oriented and may differ from {@link 
#size()}, which reports
+     * logical task snapshot count.
+     *
+     * @return active backend partition bucket count
+     */
+    int activePartitionKeyCount();
+}
diff --git 
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/metrics/hazelcast/HazelcastMetricsSnapshotStateStore.java
 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/metrics/hazelcast/HazelcastMetricsSnapshotStateStore.java
new file mode 100644
index 0000000000..be4e3f83eb
--- /dev/null
+++ 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/common/statestore/metrics/hazelcast/HazelcastMetricsSnapshotStateStore.java
@@ -0,0 +1,322 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.seatunnel.engine.server.common.statestore.metrics.hazelcast;
+
+import 
org.apache.seatunnel.engine.server.common.statestore.metrics.MetricsSnapshotStateStore;
+import org.apache.seatunnel.engine.server.dag.physical.PipelineLocation;
+import org.apache.seatunnel.engine.server.execution.TaskLocation;
+import org.apache.seatunnel.engine.server.metrics.SeaTunnelMetricsContext;
+
+import com.hazelcast.core.EntryEvent;
+import com.hazelcast.map.IMap;
+import com.hazelcast.map.listener.EntryAddedListener;
+import com.hazelcast.map.listener.EntryRemovedListener;
+import com.hazelcast.map.listener.EntryUpdatedListener;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
+
+/** Implementation backed by a partitioned Hazelcast metrics {@link IMap}. */
+public class HazelcastMetricsSnapshotStateStore
+        implements MetricsSnapshotStateStore, AutoCloseable {
+
+    private final IMap<Long, Map<TaskLocation, SeaTunnelMetricsContext>> 
metricsImap;
+    private final int partitionCount;
+    private final AtomicLong activePartitionKeyCount = new AtomicLong();
+    private final AtomicLong taskSnapshotCount = new AtomicLong();
+    private final Object metricsStatsLock = new Object();
+    private final Set<Long> dirtyPartitionKeys = ConcurrentHashMap.newKeySet();
+    private final Map<Long, RunningJobMetricsStats> statsByPartitionKey = new 
HashMap<>();
+    private final AtomicBoolean statsInitializing = new AtomicBoolean(false);
+    private volatile UUID metricsListenerId;
+
+    public HazelcastMetricsSnapshotStateStore(
+            IMap<Long, Map<TaskLocation, SeaTunnelMetricsContext>> metricsImap,
+            int partitionCount) {
+        this.metricsImap = metricsImap;
+        this.partitionCount = partitionCount;
+        initStats();
+    }
+
+    @Override
+    public void merge(Map<TaskLocation, SeaTunnelMetricsContext> snapshot) {
+        if (snapshot == null || snapshot.isEmpty()) {
+            return;
+        }
+
+        Map<Long, Map<TaskLocation, SeaTunnelMetricsContext>> partitioned = 
new HashMap<>();
+        snapshot.forEach(
+                (key, value) -> {
+                    long partition = partition(key);
+                    partitioned.computeIfAbsent(partition, k -> new 
HashMap<>()).put(key, value);
+                });
+
+        partitioned
+                .entrySet()
+                .parallelStream()
+                .forEach(
+                        entry -> {
+                            metricsImap.compute(
+                                    entry.getKey(),
+                                    (k, oldVal) -> {
+                                        if (oldVal == null) oldVal = new 
HashMap<>();
+                                        oldVal.putAll(entry.getValue());
+                                        return oldVal;
+                                    });
+                        });
+    }
+
+    @Override
+    public SeaTunnelMetricsContext get(TaskLocation taskLocation) {
+        Map<TaskLocation, SeaTunnelMetricsContext> partitionMap =
+                metricsImap.get(partition(taskLocation));
+        if (partitionMap == null) {
+            return null;
+        }
+        return partitionMap.get(taskLocation);
+    }
+
+    @Override
+    public void remove(final TaskLocation taskLocation) {
+        metricsImap.compute(
+                partition(taskLocation),
+                (ignored, current) -> {
+                    if (current == null) {
+                        return null;
+                    }
+                    Map<TaskLocation, SeaTunnelMetricsContext> updated = new 
HashMap<>(current);
+                    updated.remove(taskLocation);
+                    return updated.isEmpty() ? null : updated;
+                });
+    }
+
+    @Override
+    public void removePipeline(final PipelineLocation pipelineLocation) {
+        Map<Long, List<TaskLocation>> partitionedTasks = new HashMap<>();
+        for (Map.Entry<Long, Map<TaskLocation, SeaTunnelMetricsContext>> entry 
:
+                metricsImap.entrySet()) {
+            long partition = entry.getKey();
+            List<TaskLocation> tasksToRemove =
+                    entry.getValue().keySet().stream()
+                            .filter(
+                                    t ->
+                                            t.getTaskGroupLocation()
+                                                    .getPipelineLocation()
+                                                    .equals(pipelineLocation))
+                            .collect(Collectors.toList());
+            if (!tasksToRemove.isEmpty()) {
+                partitionedTasks.put(partition, tasksToRemove);
+            }
+        }
+
+        partitionedTasks
+                .entrySet()
+                .parallelStream()
+                .forEach(
+                        entry -> {
+                            long partition = entry.getKey();
+                            List<TaskLocation> tasks = entry.getValue();
+                            metricsImap.compute(
+                                    partition,
+                                    (k, oldVal) -> {
+                                        if (oldVal != null) {
+                                            tasks.forEach(oldVal::remove);
+                                            if (oldVal.isEmpty()) return null;
+                                        }
+                                        return oldVal;
+                                    });
+                        });
+    }
+
+    @Override
+    public boolean containsPipeline(PipelineLocation pipelineLocation) {
+        for (Map<TaskLocation, SeaTunnelMetricsContext> partitionMap : 
metricsImap.values()) {
+            boolean found =
+                    partitionMap.keySet().stream()
+                            .anyMatch(
+                                    taskLocation ->
+                                            pipelineLocation.equals(
+                                                    taskLocation
+                                                            
.getTaskGroupLocation()
+                                                            
.getPipelineLocation()));
+            if (found) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public int size() {
+        return Math.toIntExact(taskSnapshotCount.get());
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return taskSnapshotCount.get() == 0L;
+    }
+
+    @Override
+    public int activePartitionKeyCount() {
+        return Math.toIntExact(activePartitionKeyCount.get());
+    }
+
+    private long partition(TaskLocation taskLocation) {
+        return (taskLocation.hashCode() & Integer.MAX_VALUE) % partitionCount;
+    }
+
+    @Override
+    public void close() {
+        removeMetricsListener();
+    }
+
+    private void initStats() {
+        removeMetricsListener();
+        metricsListenerId = metricsImap.addEntryListener(new 
MetricsEntryListener(), true);
+        statsInitializing.set(true);
+        dirtyPartitionKeys.clear();
+
+        Map<Long, RunningJobMetricsStats> snapshotStats = new HashMap<>();
+        metricsImap.forEach(
+                (partitionKey, metrics) ->
+                        snapshotStats.put(partitionKey, 
toRunningJobMetricsStats(metrics)));
+
+        Set<Long> dirtyKeysDuringSnapshot = new HashSet<>(dirtyPartitionKeys);
+        for (Long partitionKey : dirtyKeysDuringSnapshot) {
+            snapshotStats.put(
+                    partitionKey, 
toRunningJobMetricsStats(metricsImap.get(partitionKey)));
+        }
+
+        synchronized (metricsStatsLock) {
+            statsByPartitionKey.clear();
+            activePartitionKeyCount.set(0L);
+            taskSnapshotCount.set(0L);
+            snapshotStats.forEach(this::replaceRunningJobMetricsStatsLocked);
+            statsInitializing.set(false);
+
+            Set<Long> postSnapshotDirtyKeys = new 
HashSet<>(dirtyPartitionKeys);
+            dirtyPartitionKeys.clear();
+            for (Long partitionKey : postSnapshotDirtyKeys) {
+                replaceRunningJobMetricsStatsLocked(
+                        partitionKey, 
toRunningJobMetricsStats(metricsImap.get(partitionKey)));
+            }
+        }
+    }
+
+    private RunningJobMetricsStats toRunningJobMetricsStats(
+            Map<TaskLocation, SeaTunnelMetricsContext> metrics) {
+        if (metrics == null || metrics.isEmpty()) {
+            return RunningJobMetricsStats.EMPTY;
+        }
+        return new RunningJobMetricsStats(1L, metrics.size());
+    }
+
+    private void replaceRunningJobMetricsStatsLocked(
+            Long partitionKey, RunningJobMetricsStats stats) {
+        RunningJobMetricsStats currentStats = 
statsByPartitionKey.get(partitionKey);
+        if (currentStats != null) {
+            activePartitionKeyCount.addAndGet(-currentStats.partitionKeyCount);
+            taskSnapshotCount.addAndGet(-currentStats.taskSnapshotCount);
+        }
+
+        if (stats.isEmpty()) {
+            statsByPartitionKey.remove(partitionKey);
+            return;
+        }
+
+        statsByPartitionKey.put(partitionKey, stats);
+        activePartitionKeyCount.addAndGet(stats.partitionKeyCount);
+        taskSnapshotCount.addAndGet(stats.taskSnapshotCount);
+    }
+
+    private void replaceRunningJobMetricsStats(
+            Long partitionKey, Map<TaskLocation, SeaTunnelMetricsContext> 
metrics) {
+        if (statsInitializing.get()) {
+            dirtyPartitionKeys.add(partitionKey);
+            return;
+        }
+        synchronized (metricsStatsLock) {
+            if (statsInitializing.get()) {
+                dirtyPartitionKeys.add(partitionKey);
+                return;
+            }
+            replaceRunningJobMetricsStatsLocked(partitionKey, 
toRunningJobMetricsStats(metrics));
+        }
+    }
+
+    private void removeMetricsListener() {
+        if (metricsListenerId != null) {
+            metricsImap.removeEntryListener(metricsListenerId);
+            metricsListenerId = null;
+        }
+        statsInitializing.set(false);
+        dirtyPartitionKeys.clear();
+        synchronized (metricsStatsLock) {
+            statsByPartitionKey.clear();
+        }
+        activePartitionKeyCount.set(0L);
+        taskSnapshotCount.set(0L);
+    }
+
+    private final class MetricsEntryListener
+            implements EntryAddedListener<Long, Map<TaskLocation, 
SeaTunnelMetricsContext>>,
+                    EntryUpdatedListener<Long, Map<TaskLocation, 
SeaTunnelMetricsContext>>,
+                    EntryRemovedListener<Long, Map<TaskLocation, 
SeaTunnelMetricsContext>> {
+
+        @Override
+        public void entryAdded(EntryEvent<Long, Map<TaskLocation, 
SeaTunnelMetricsContext>> event) {
+            replaceRunningJobMetricsStats(event.getKey(), event.getValue());
+        }
+
+        @Override
+        public void entryUpdated(
+                EntryEvent<Long, Map<TaskLocation, SeaTunnelMetricsContext>> 
event) {
+            replaceRunningJobMetricsStats(event.getKey(), event.getValue());
+        }
+
+        @Override
+        public void entryRemoved(
+                EntryEvent<Long, Map<TaskLocation, SeaTunnelMetricsContext>> 
event) {
+            replaceRunningJobMetricsStats(event.getKey(), null);
+        }
+    }
+
+    private static final class RunningJobMetricsStats {
+        private static final RunningJobMetricsStats EMPTY = new 
RunningJobMetricsStats(0L, 0L);
+
+        private final long partitionKeyCount;
+        private final long taskSnapshotCount;
+
+        private RunningJobMetricsStats(long partitionKeyCount, long 
taskSnapshotCount) {
+            this.partitionKeyCount = partitionKeyCount;
+            this.taskSnapshotCount = taskSnapshotCount;
+        }
+
+        private boolean isEmpty() {
+            return partitionKeyCount == 0L && taskSnapshotCount == 0L;
+        }
+    }
+}
diff --git 
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/execution/TaskExecutionContext.java
 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/execution/TaskExecutionContext.java
index 74ffb7aad9..ad79765930 100644
--- 
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/execution/TaskExecutionContext.java
+++ 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/execution/TaskExecutionContext.java
@@ -17,32 +17,33 @@
 
 package org.apache.seatunnel.engine.server.execution;
 
-import org.apache.seatunnel.engine.common.Constant;
-import org.apache.seatunnel.engine.server.SeaTunnelServer;
 import org.apache.seatunnel.engine.server.TaskExecutionService;
+import org.apache.seatunnel.engine.server.common.SeaTunnelEngineContext;
 import org.apache.seatunnel.engine.server.metrics.SeaTunnelMetricsContext;
 import org.apache.seatunnel.engine.server.utils.NodeEngineUtil;
 
 import com.hazelcast.cluster.Address;
 import com.hazelcast.core.HazelcastInstance;
 import com.hazelcast.logging.ILogger;
-import com.hazelcast.map.IMap;
 import com.hazelcast.spi.impl.NodeEngineImpl;
 import com.hazelcast.spi.impl.operationservice.Operation;
 import com.hazelcast.spi.impl.operationservice.impl.InvocationFuture;
 
-import java.util.HashMap;
-
 public class TaskExecutionContext {
 
     private final Task task;
     private final NodeEngineImpl nodeEngine;
+    private final SeaTunnelEngineContext engineContext;
     private final TaskExecutionService taskExecutionService;
 
     public TaskExecutionContext(
-            Task task, NodeEngineImpl nodeEngine, TaskExecutionService 
taskExecutionService) {
+            Task task,
+            NodeEngineImpl nodeEngine,
+            SeaTunnelEngineContext engineContext,
+            TaskExecutionService taskExecutionService) {
         this.task = task;
         this.nodeEngine = nodeEngine;
+        this.engineContext = engineContext;
         this.taskExecutionService = taskExecutionService;
     }
 
@@ -59,18 +60,10 @@ public class TaskExecutionContext {
     }
 
     public SeaTunnelMetricsContext getOrCreateMetricsContext(TaskLocation 
taskLocation) {
-        IMap<Long, HashMap<TaskLocation, SeaTunnelMetricsContext>> map =
-                
nodeEngine.getHazelcastInstance().getMap(Constant.IMAP_RUNNING_JOB_METRICS);
-        int partitionCount =
-                taskExecutionService
-                        .getSeaTunnelConfig()
-                        .getEngineConfig()
-                        .getJobMetricsPartitionCount();
-        long partition = SeaTunnelServer.getMetricsImapPartition(taskLocation, 
partitionCount);
-        HashMap<TaskLocation, SeaTunnelMetricsContext> centralMap = 
map.get(partition);
-        return centralMap == null || centralMap.get(taskLocation) == null
-                ? new SeaTunnelMetricsContext()
-                : centralMap.get(taskLocation);
+        return engineContext
+                .getStateStores()
+                .metricsSnapshotStore()
+                .getOrDefault(taskLocation, new SeaTunnelMetricsContext());
     }
 
     public <T> T getTask() {
diff --git 
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/telemetry/metrics/exports/EngineStateStoreMetricExports.java
 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/telemetry/metrics/exports/EngineStateStoreMetricExports.java
index c8a8697039..0369818ea9 100644
--- 
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/telemetry/metrics/exports/EngineStateStoreMetricExports.java
+++ 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/telemetry/metrics/exports/EngineStateStoreMetricExports.java
@@ -18,6 +18,7 @@
 package org.apache.seatunnel.engine.server.telemetry.metrics.exports;
 
 import org.apache.seatunnel.engine.common.Constant;
+import 
org.apache.seatunnel.engine.server.common.statestore.EngineStateStoreNames;
 import org.apache.seatunnel.engine.server.telemetry.metrics.AbstractCollector;
 
 import com.hazelcast.core.HazelcastInstanceNotActiveException;
@@ -39,11 +40,11 @@ public class EngineStateStoreMetricExports extends 
AbstractCollector {
                     Constant.IMAP_RUNNING_JOB_STATE,
                     Constant.IMAP_STATE_TIMESTAMPS,
                     Constant.IMAP_OWNED_SLOT_PROFILES,
-                    Constant.IMAP_RUNNING_JOB_METRICS,
+                    EngineStateStoreNames.RUNNING_JOB_METRICS,
                     Constant.IMAP_FINISHED_JOB_STATE,
                     Constant.IMAP_FINISHED_JOB_METRICS,
                     Constant.IMAP_FINISHED_JOB_VERTEX_INFO,
-                    Constant.IMAP_CHECKPOINT_MONITOR,
+                    EngineStateStoreNames.CHECKPOINT_MONITOR,
                     Constant.IMAP_CONNECTOR_JAR_REF_COUNTERS,
                     Constant.IMAP_CHECKPOINT_ID,
                     Constant.IMAP_PENDING_PIPELINE_CLEANUP);
diff --git 
a/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/CoordinatorServicePipelineCleanupTest.java
 
b/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/CoordinatorServicePipelineCleanupTest.java
index 07f32459d1..e8218e46ba 100644
--- 
a/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/CoordinatorServicePipelineCleanupTest.java
+++ 
b/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/CoordinatorServicePipelineCleanupTest.java
@@ -19,6 +19,7 @@ package org.apache.seatunnel.engine.server;
 
 import org.apache.seatunnel.engine.common.Constant;
 import org.apache.seatunnel.engine.core.job.PipelineStatus;
+import 
org.apache.seatunnel.engine.server.common.statestore.metrics.MetricsSnapshotStateStore;
 import org.apache.seatunnel.engine.server.dag.physical.PipelineLocation;
 import org.apache.seatunnel.engine.server.execution.TaskGroupLocation;
 import org.apache.seatunnel.engine.server.execution.TaskLocation;
@@ -252,14 +253,9 @@ class CoordinatorServicePipelineCleanupTest extends 
AbstractSeaTunnelServerTest
     }
 
     private boolean hasMetricsForPipeline(PipelineLocation pipelineLocation) {
-        IMap<Long, Map<TaskLocation, SeaTunnelMetricsContext>> metricsIMap =
-                
nodeEngine.getHazelcastInstance().getMap(Constant.IMAP_RUNNING_JOB_METRICS);
-        return metricsIMap.entrySet().stream()
-                .flatMap(entry -> entry.getValue().keySet().stream())
-                .anyMatch(
-                        taskLocation ->
-                                pipelineLocation.equals(
-                                        
taskLocation.getTaskGroupLocation().getPipelineLocation()));
+        MetricsSnapshotStateStore metricsStore =
+                
server.getEngineContext().getStateStores().metricsSnapshotStore();
+        return metricsStore.containsPipeline(pipelineLocation);
     }
 
     private void awaitCoordinatorActive(CoordinatorService coordinatorService) 
{
diff --git 
a/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/CoordinatorServiceTest.java
 
b/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/CoordinatorServiceTest.java
index a2ab949248..59d86896a7 100644
--- 
a/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/CoordinatorServiceTest.java
+++ 
b/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/CoordinatorServiceTest.java
@@ -35,6 +35,8 @@ import org.apache.seatunnel.engine.core.job.JobDAGInfo;
 import org.apache.seatunnel.engine.core.job.JobImmutableInformation;
 import org.apache.seatunnel.engine.core.job.JobInfo;
 import org.apache.seatunnel.engine.core.job.PipelineStatus;
+import org.apache.seatunnel.engine.server.common.SeaTunnelEngineContext;
+import 
org.apache.seatunnel.engine.server.common.statestore.metrics.MetricsSnapshotStateStore;
 import org.apache.seatunnel.engine.server.dag.physical.PhysicalPlan;
 import org.apache.seatunnel.engine.server.dag.physical.PipelineLocation;
 import org.apache.seatunnel.engine.server.dag.physical.SubPlan;
@@ -525,9 +527,11 @@ public class CoordinatorServiceTest {
         
Mockito.when(nodeEngine.getLogger(Mockito.any(Class.class))).thenReturn(logger);
         
Mockito.when(nodeEngine.getHazelcastInstance()).thenReturn(hazelcastInstance);
         
Mockito.when(hazelcastInstance.getMap(Mockito.anyString())).thenReturn(map);
+        SeaTunnelEngineContext engineContext = 
Mockito.mock(SeaTunnelEngineContext.class);
+        Mockito.when(server.getEngineContext()).thenReturn(engineContext);
 
         CoordinatorService coordinatorService =
-                new CoordinatorService(nodeEngine, server, engineConfig);
+                new CoordinatorService(nodeEngine, server, 
server.getEngineContext(), engineConfig);
         stopCoordinatorSchedulers(coordinatorService);
         return coordinatorService;
     }
@@ -920,12 +924,12 @@ public class CoordinatorServiceTest {
                         "batch_fake_to_console.conf",
                         "test_cleanup_metrics_imap");
         CoordinatorService coordinatorService = 
jobInformation.coordinatorService;
-        IMap<Long, HashMap<TaskLocation, SeaTunnelMetricsContext>> metricsImap 
=
-                coordinatorService.getMetricsImap();
-        await().atMost(60000, TimeUnit.MILLISECONDS)
-                .untilAsserted(() -> 
Assertions.assertFalse(metricsImap.isEmpty()));
-        await().atMost(60000, TimeUnit.MILLISECONDS)
-                .untilAsserted(() -> 
Assertions.assertTrue(metricsImap.isEmpty()));
+        MetricsSnapshotStateStore metricsSnapshotStateStore =
+                coordinatorService.getMetricsSnapshotStateStore();
+        await().atMost(10000, TimeUnit.MILLISECONDS)
+                .untilAsserted(() -> 
Assertions.assertFalse(metricsSnapshotStateStore.isEmpty()));
+        await().atMost(10000, TimeUnit.MILLISECONDS)
+                .untilAsserted(() -> 
Assertions.assertTrue(metricsSnapshotStateStore.isEmpty()));
 
         jobInformation.coordinatorService.clearCoordinatorService();
         jobInformation.coordinatorServiceTest.shutdown();
@@ -941,12 +945,12 @@ public class CoordinatorServiceTest {
                         "batch_fake_to_console.conf",
                         "test_cleanup_metrics_imap_with_partition_config");
         CoordinatorService coordinatorService = 
jobInformation.coordinatorService;
-        IMap<Long, HashMap<TaskLocation, SeaTunnelMetricsContext>> metricsImap 
=
-                coordinatorService.getMetricsImap();
-        await().atMost(60000, TimeUnit.MILLISECONDS)
-                .untilAsserted(() -> 
Assertions.assertFalse(metricsImap.isEmpty()));
-        await().atMost(60000, TimeUnit.MILLISECONDS)
-                .untilAsserted(() -> 
Assertions.assertTrue(metricsImap.isEmpty()));
+        MetricsSnapshotStateStore metricsSnapshotStateStore =
+                coordinatorService.getMetricsSnapshotStateStore();
+        await().atMost(10000, TimeUnit.MILLISECONDS)
+                .untilAsserted(() -> 
Assertions.assertFalse(metricsSnapshotStateStore.isEmpty()));
+        await().atMost(10000, TimeUnit.MILLISECONDS)
+                .untilAsserted(() -> 
Assertions.assertTrue(metricsSnapshotStateStore.isEmpty()));
 
         jobInformation.coordinatorService.clearCoordinatorService();
         jobInformation.coordinatorServiceTest.shutdown();
@@ -971,8 +975,8 @@ public class CoordinatorServiceTest {
                 taskLocation.setTaskID(i);
                 localMap.put(taskLocation, new SeaTunnelMetricsContext());
             }
-            IMap<Long, HashMap<TaskLocation, SeaTunnelMetricsContext>> 
metricsImap =
-                    server1.getCoordinatorService().getMetricsImap();
+            MetricsSnapshotStateStore metricsSnapshotStateStore =
+                    
server1.getCoordinatorService().getMetricsSnapshotStateStore();
             CompletableFuture.runAsync(
                     () -> {
                         try {
@@ -988,8 +992,15 @@ public class CoordinatorServiceTest {
                             throw new CompletionException(e);
                         }
                     });
-            await().atMost(60000, TimeUnit.MILLISECONDS)
-                    .untilAsserted(() -> Assertions.assertEquals(10, 
metricsImap.size()));
+            await().atMost(10000, TimeUnit.MILLISECONDS)
+                    .untilAsserted(
+                            () ->
+                                    Assertions.assertEquals(
+                                            10,
+                                            
metricsSnapshotStateStore.activePartitionKeyCount()));
+            await().atMost(10000, TimeUnit.MILLISECONDS)
+                    .untilAsserted(
+                            () -> Assertions.assertEquals(100, 
metricsSnapshotStateStore.size()));
         } finally {
             instance1.shutdown();
             setDefaultConfigFile();
diff --git 
a/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/common/statestore/checkpoint/hazelcast/HazelcastCheckpointOverviewStateStoreTest.java
 
b/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/common/statestore/checkpoint/hazelcast/HazelcastCheckpointOverviewStateStoreTest.java
new file mode 100644
index 0000000000..17254bc969
--- /dev/null
+++ 
b/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/common/statestore/checkpoint/hazelcast/HazelcastCheckpointOverviewStateStoreTest.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package 
org.apache.seatunnel.engine.server.common.statestore.checkpoint.hazelcast;
+
+import org.apache.seatunnel.engine.core.checkpoint.CheckpointHistoryEntry;
+import org.apache.seatunnel.engine.core.checkpoint.CheckpointInfo;
+import org.apache.seatunnel.engine.core.checkpoint.CheckpointOverview;
+import org.apache.seatunnel.engine.core.checkpoint.CheckpointStatus;
+import org.apache.seatunnel.engine.core.checkpoint.CheckpointType;
+import org.apache.seatunnel.engine.core.checkpoint.InProgressCheckpoint;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import com.hazelcast.config.Config;
+import com.hazelcast.core.Hazelcast;
+import com.hazelcast.core.HazelcastInstance;
+import com.hazelcast.map.IMap;
+
+import java.util.concurrent.TimeUnit;
+
+import static org.awaitility.Awaitility.await;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class HazelcastCheckpointOverviewStateStoreTest {
+
+    private static HazelcastInstance hazelcastInstance;
+
+    @BeforeAll
+    static void beforeAll() {
+        Config config = new Config();
+        config.setClusterName("HazelcastCheckpointOverviewStateStoreTest-" + 
System.nanoTime());
+        hazelcastInstance = Hazelcast.newHazelcastInstance(config);
+    }
+
+    @AfterAll
+    static void afterAll() {
+        if (hazelcastInstance != null) {
+            hazelcastInstance.shutdown();
+        }
+    }
+
+    @Test
+    void countAccessorsShouldUseLogicalOverviewCounts() {
+        IMap<Long, CheckpointOverview> iMap =
+                hazelcastInstance.getMap("checkpoint-overview-counts");
+        iMap.clear();
+        HazelcastCheckpointOverviewStateStore store =
+                new HazelcastCheckpointOverviewStateStore(iMap);
+
+        store.updateOverview(
+                1L,
+                1,
+                pipeline -> {
+                    pipeline.getInProgress()
+                            .add(
+                                    new InProgressCheckpoint(
+                                            101L, 
CheckpointType.CHECKPOINT_TYPE, 10L, 1, 2));
+                    pipeline.getInProgress()
+                            .add(
+                                    new InProgressCheckpoint(
+                                            102L, 
CheckpointType.CHECKPOINT_TYPE, 20L, 1, 2));
+                    pipeline.addHistory(historyEntry(1L, 1, 201L), 8);
+                });
+        store.updateOverview(1L, 2, pipeline -> 
pipeline.addHistory(historyEntry(1L, 2, 202L), 8));
+        store.updateOverview(
+                2L,
+                1,
+                pipeline -> {
+                    pipeline.getInProgress()
+                            .add(
+                                    new InProgressCheckpoint(
+                                            103L, 
CheckpointType.CHECKPOINT_TYPE, 30L, 1, 2));
+                    pipeline.addHistory(historyEntry(2L, 1, 203L), 8);
+                });
+
+        awaitOverviewStats(store, 2L, 3L, 3L, 2);
+
+        store.remove(1L);
+
+        awaitOverviewStats(store, 1L, 1L, 1L, 1);
+    }
+
+    private static void awaitOverviewStats(
+            HazelcastCheckpointOverviewStateStore store,
+            long expectedOverviewJobCount,
+            long expectedInProgressCheckpointCount,
+            long expectedRetainedHistoryCount,
+            int expectedSize) {
+        await().atMost(10, TimeUnit.SECONDS)
+                .untilAsserted(
+                        () -> {
+                            assertEquals(expectedOverviewJobCount, 
store.getOverviewJobCount());
+                            assertEquals(
+                                    expectedInProgressCheckpointCount,
+                                    store.getInProgressCheckpointCount());
+                            assertEquals(
+                                    expectedRetainedHistoryCount, 
store.getRetainedHistoryCount());
+                            assertEquals(expectedSize, store.size());
+                        });
+    }
+
+    private static CheckpointHistoryEntry historyEntry(
+            long jobId, int pipelineId, long checkpointId) {
+        return CheckpointHistoryEntry.builder()
+                .jobId(jobId)
+                .pipelineId(pipelineId)
+                .checkpointInfo(
+                        CheckpointInfo.builder()
+                                .checkpointId(checkpointId)
+                                .checkpointType(CheckpointType.CHECKPOINT_TYPE)
+                                .status(CheckpointStatus.COMPLETED)
+                                .triggerTimestamp(checkpointId)
+                                .build())
+                .build();
+    }
+}
diff --git 
a/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/common/statestore/metrics/hazelcast/HazelcastMetricsSnapshotStateStoreTest.java
 
b/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/common/statestore/metrics/hazelcast/HazelcastMetricsSnapshotStateStoreTest.java
new file mode 100644
index 0000000000..7ef1ada64e
--- /dev/null
+++ 
b/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/common/statestore/metrics/hazelcast/HazelcastMetricsSnapshotStateStoreTest.java
@@ -0,0 +1,203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.seatunnel.engine.server.common.statestore.metrics.hazelcast;
+
+import org.apache.seatunnel.engine.server.dag.physical.PipelineLocation;
+import org.apache.seatunnel.engine.server.execution.TaskGroupLocation;
+import org.apache.seatunnel.engine.server.execution.TaskLocation;
+import org.apache.seatunnel.engine.server.metrics.SeaTunnelMetricsContext;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import com.hazelcast.config.Config;
+import com.hazelcast.core.Hazelcast;
+import com.hazelcast.core.HazelcastInstance;
+import com.hazelcast.map.IMap;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import static org.awaitility.Awaitility.await;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+class HazelcastMetricsSnapshotStateStoreTest {
+
+    private static HazelcastInstance hazelcastInstance;
+    private static final String METRIC_NAME = "test.metric";
+
+    @BeforeAll
+    static void beforeAll() {
+        Config config = new Config();
+        config.setClusterName("HazelcastMetricsSnapshotStateStoreTest-" + 
System.nanoTime());
+        hazelcastInstance = Hazelcast.newHazelcastInstance(config);
+    }
+
+    @AfterAll
+    static void afterAll() {
+        if (hazelcastInstance != null) {
+            hazelcastInstance.shutdown();
+        }
+    }
+
+    @Test
+    void mergeShouldStoreAndOverwriteSnapshots() {
+        IMap<Long, Map<TaskLocation, SeaTunnelMetricsContext>> iMap =
+                hazelcastInstance.getMap("metrics-snapshot-merge");
+        iMap.clear();
+        HazelcastMetricsSnapshotStateStore store = new 
HazelcastMetricsSnapshotStateStore(iMap, 8);
+
+        TaskLocation taskOne = taskLocation(1L, 10, 100L, 0L, 0);
+        TaskLocation taskTwo = taskLocation(1L, 11, 101L, 0L, 0);
+        SeaTunnelMetricsContext metricsOne = metricsContextWithCounterValue(1);
+        SeaTunnelMetricsContext metricsTwo = metricsContextWithCounterValue(2);
+        SeaTunnelMetricsContext updatedMetricsOne = 
metricsContextWithCounterValue(3);
+
+        Map<TaskLocation, SeaTunnelMetricsContext> initialSnapshot = new 
LinkedHashMap<>();
+        initialSnapshot.put(taskOne, metricsOne);
+        initialSnapshot.put(taskTwo, metricsTwo);
+        store.merge(initialSnapshot);
+
+        assertCounterValue(1, store.get(taskOne));
+        assertCounterValue(2, store.get(taskTwo));
+        awaitSize(store, 2);
+
+        store.merge(singletonSnapshot(taskOne, updatedMetricsOne));
+
+        assertCounterValue(3, store.get(taskOne));
+        assertCounterValue(2, store.get(taskTwo));
+        awaitSize(store, 2);
+    }
+
+    @Test
+    void removeShouldDeleteSingleTaskSnapshot() {
+        IMap<Long, Map<TaskLocation, SeaTunnelMetricsContext>> iMap =
+                hazelcastInstance.getMap("metrics-snapshot-remove");
+        iMap.clear();
+        HazelcastMetricsSnapshotStateStore store = new 
HazelcastMetricsSnapshotStateStore(iMap, 8);
+
+        TaskLocation taskOne = taskLocation(2L, 20, 200L, 0L, 0);
+        TaskLocation taskTwo = taskLocation(2L, 21, 201L, 0L, 0);
+        SeaTunnelMetricsContext metricsOne = 
metricsContextWithCounterValue(10);
+        SeaTunnelMetricsContext metricsTwo = 
metricsContextWithCounterValue(20);
+
+        Map<TaskLocation, SeaTunnelMetricsContext> snapshot = new 
LinkedHashMap<>();
+        snapshot.put(taskOne, metricsOne);
+        snapshot.put(taskTwo, metricsTwo);
+        store.merge(snapshot);
+
+        store.remove(taskOne);
+
+        assertNull(store.get(taskOne));
+        assertCounterValue(20, store.get(taskTwo));
+        awaitSize(store, 1);
+
+        store.remove(taskTwo);
+
+        assertNull(store.get(taskTwo));
+        awaitSize(store, 0);
+    }
+
+    @Test
+    void removePipelineShouldDeleteOnlyMatchingPipelineSnapshots() {
+        IMap<Long, Map<TaskLocation, SeaTunnelMetricsContext>> iMap =
+                hazelcastInstance.getMap("metrics-snapshot-remove-pipeline");
+        iMap.clear();
+        HazelcastMetricsSnapshotStateStore store = new 
HazelcastMetricsSnapshotStateStore(iMap, 8);
+
+        PipelineLocation pipelineToRemove = new PipelineLocation(3L, 30);
+        PipelineLocation pipelineToKeep = new PipelineLocation(3L, 31);
+        TaskLocation removedOne = taskLocation(3L, 30, 300L, 0L, 0);
+        TaskLocation removedTwo = taskLocation(3L, 30, 301L, 0L, 0);
+        TaskLocation kept = taskLocation(3L, 31, 302L, 0L, 0);
+        SeaTunnelMetricsContext removedOneMetrics = 
metricsContextWithCounterValue(100);
+        SeaTunnelMetricsContext removedTwoMetrics = 
metricsContextWithCounterValue(200);
+        SeaTunnelMetricsContext keptMetrics = 
metricsContextWithCounterValue(300);
+
+        Map<TaskLocation, SeaTunnelMetricsContext> snapshot = new 
LinkedHashMap<>();
+        snapshot.put(removedOne, removedOneMetrics);
+        snapshot.put(removedTwo, removedTwoMetrics);
+        snapshot.put(kept, keptMetrics);
+        store.merge(snapshot);
+
+        store.removePipeline(pipelineToRemove);
+
+        assertNull(store.get(removedOne));
+        assertNull(store.get(removedTwo));
+        assertCounterValue(300, store.get(kept));
+        awaitSize(store, 1);
+        assertEquals(pipelineToKeep, 
kept.getTaskGroupLocation().getPipelineLocation());
+    }
+
+    @Test
+    void sizeShouldCountTaskSnapshotsInsteadOfPartitionBuckets() {
+        IMap<Long, Map<TaskLocation, SeaTunnelMetricsContext>> iMap =
+                hazelcastInstance.getMap("metrics-snapshot-size");
+        iMap.clear();
+        HazelcastMetricsSnapshotStateStore store = new 
HazelcastMetricsSnapshotStateStore(iMap, 1);
+
+        TaskLocation taskOne = taskLocation(4L, 40, 400L, 0L, 0);
+        TaskLocation taskTwo = taskLocation(4L, 40, 401L, 0L, 1);
+
+        Map<TaskLocation, SeaTunnelMetricsContext> snapshot = new 
LinkedHashMap<>();
+        snapshot.put(taskOne, metricsContextWithCounterValue(1));
+        snapshot.put(taskTwo, metricsContextWithCounterValue(2));
+        store.merge(snapshot);
+
+        assertEquals(1, iMap.size());
+        awaitSize(store, 2);
+    }
+
+    private static void awaitSize(HazelcastMetricsSnapshotStateStore store, 
int expectedSize) {
+        await().atMost(10, TimeUnit.SECONDS)
+                .untilAsserted(() -> assertEquals(expectedSize, store.size()));
+    }
+
+    private static Map<TaskLocation, SeaTunnelMetricsContext> 
singletonSnapshot(
+            TaskLocation taskLocation, SeaTunnelMetricsContext metricsContext) 
{
+        Map<TaskLocation, SeaTunnelMetricsContext> snapshot = new HashMap<>();
+        snapshot.put(taskLocation, metricsContext);
+        return snapshot;
+    }
+
+    private static SeaTunnelMetricsContext metricsContextWithCounterValue(long 
value) {
+        SeaTunnelMetricsContext metricsContext = new SeaTunnelMetricsContext();
+        metricsContext.counter(METRIC_NAME).inc(value);
+        return metricsContext;
+    }
+
+    private static void assertCounterValue(long expected, 
SeaTunnelMetricsContext metricsContext) {
+        assertEquals(expected, metricsContext.counter(METRIC_NAME).getCount());
+    }
+
+    private static TaskLocation taskLocation(
+            long jobId,
+            int pipelineId,
+            long taskGroupId,
+            long taskInGroupIndex,
+            int parallelismIndex) {
+        return new TaskLocation(
+                new TaskGroupLocation(jobId, pipelineId, taskGroupId),
+                taskInGroupIndex,
+                parallelismIndex);
+    }
+}
diff --git 
a/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/telemetry/metrics/exports/EngineStateStoreLogicalMetricExportsTest.java
 
b/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/telemetry/metrics/exports/EngineStateStoreLogicalMetricExportsTest.java
index dbbfa1dcd5..7cb201cdac 100644
--- 
a/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/telemetry/metrics/exports/EngineStateStoreLogicalMetricExportsTest.java
+++ 
b/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/telemetry/metrics/exports/EngineStateStoreLogicalMetricExportsTest.java
@@ -32,6 +32,7 @@ import 
org.apache.seatunnel.engine.server.SeaTunnelServerStarter;
 import org.apache.seatunnel.engine.server.TestUtils;
 import org.apache.seatunnel.engine.server.checkpoint.CheckpointCloseReason;
 import org.apache.seatunnel.engine.server.checkpoint.CompletedCheckpoint;
+import 
org.apache.seatunnel.engine.server.common.statestore.EngineStateStoreNames;
 import org.apache.seatunnel.engine.server.execution.TaskGroupLocation;
 import org.apache.seatunnel.engine.server.execution.TaskLocation;
 import org.apache.seatunnel.engine.server.master.JobHistoryService.JobState;
@@ -187,7 +188,7 @@ class EngineStateStoreLogicalMetricExportsTest {
 
     private void seedRunningJobMetrics() {
         IMap<Long, HashMap<TaskLocation, SeaTunnelMetricsContext>> metricsMap =
-                instance.getMap(Constant.IMAP_RUNNING_JOB_METRICS);
+                instance.getMap(EngineStateStoreNames.RUNNING_JOB_METRICS);
         HashMap<TaskLocation, SeaTunnelMetricsContext> partitionZero = new 
HashMap<>();
         partitionZero.put(new TaskLocation(new TaskGroupLocation(1L, 1, 1L), 
0, 0), metricCtx());
         partitionZero.put(new TaskLocation(new TaskGroupLocation(1L, 1, 2L), 
0, 0), metricCtx());
diff --git 
a/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/telemetry/metrics/exports/EngineStateStoreMetricExportsTest.java
 
b/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/telemetry/metrics/exports/EngineStateStoreMetricExportsTest.java
index f9390dd993..611be9dac8 100644
--- 
a/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/telemetry/metrics/exports/EngineStateStoreMetricExportsTest.java
+++ 
b/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/telemetry/metrics/exports/EngineStateStoreMetricExportsTest.java
@@ -20,6 +20,7 @@ package 
org.apache.seatunnel.engine.server.telemetry.metrics.exports;
 import org.apache.seatunnel.engine.common.Constant;
 import org.apache.seatunnel.engine.server.SeaTunnelServerStarter;
 import org.apache.seatunnel.engine.server.TestUtils;
+import 
org.apache.seatunnel.engine.server.common.statestore.EngineStateStoreNames;
 
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
@@ -110,11 +111,11 @@ class EngineStateStoreMetricExportsTest {
                                 Constant.IMAP_RUNNING_JOB_STATE,
                                 Constant.IMAP_STATE_TIMESTAMPS,
                                 Constant.IMAP_OWNED_SLOT_PROFILES,
-                                Constant.IMAP_RUNNING_JOB_METRICS,
+                                EngineStateStoreNames.RUNNING_JOB_METRICS,
                                 Constant.IMAP_FINISHED_JOB_STATE,
                                 Constant.IMAP_FINISHED_JOB_METRICS,
                                 Constant.IMAP_FINISHED_JOB_VERTEX_INFO,
-                                Constant.IMAP_CHECKPOINT_MONITOR,
+                                EngineStateStoreNames.CHECKPOINT_MONITOR,
                                 Constant.IMAP_CONNECTOR_JAR_REF_COUNTERS,
                                 Constant.IMAP_CHECKPOINT_ID,
                                 Constant.IMAP_PENDING_PIPELINE_CLEANUP)),

Reply via email to