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

github-merge-queue[bot] pushed a commit to branch 
gh-readonly-queue/dev/pr-12026-65936d6e08aa34f5cfa967dec0e4cdf96b9d101e
in repository https://gitbox.apache.org/repos/asf/seatunnel.git

commit c6b363af5c18702102dcc5536f6adf47b0ccb067
Author: zhiweiniu <[email protected]>
AuthorDate: Tue Sep 1 15:37:34 2026 +0000

    [Feature][Zeta] Add checkpoint completion benchmark (#12026)
---
 docs/en/engines/zeta/benchmark.md                  |  23 +-
 docs/zh/engines/zeta/benchmark.md                  |  21 +-
 .../benchmark/CheckpointBenchmarkTrigger.java      |  69 ++++
 .../benchmark/CheckpointingTimeBenchmark.java      |  66 ++++
 .../CheckpointingTimeBenchmarkPipeline.java        | 295 ++++++++++++++++
 .../SeaTunnelCheckpointEnvironmentContext.java     | 380 +++++++++++++++++++++
 .../connector/source/BenchmarkSourceReader.java    |   4 +-
 .../benchmark/engine-checkpoint.yaml.template      |  42 +++
 .../hazelcast-checkpoint-master.yaml.template      |  50 +++
 .../hazelcast-checkpoint-worker.yaml.template      |  38 +++
 .../benchmark/source-sink-checkpoint.conf.template |  49 +++
 .../benchmark/CheckpointingTimeBenchmarkTest.java  |  72 ++++
 12 files changed, 1104 insertions(+), 5 deletions(-)

diff --git a/docs/en/engines/zeta/benchmark.md 
b/docs/en/engines/zeta/benchmark.md
index b20db41fa6..e093286ea7 100644
--- a/docs/en/engines/zeta/benchmark.md
+++ b/docs/en/engines/zeta/benchmark.md
@@ -15,12 +15,14 @@ not replace a production proof of concept.
 
 ## How It Works
 
-The `seatunnel-benchmarks` module provides two types of tests:
+The `seatunnel-benchmarks` module provides three types of tests:
 
 - `SeaTunnelRowBenchmark` measures hot paths such as row creation, access, 
copying, projection, and
   size calculation.
 - `SeaTunnelPipelineBenchmark` starts an embedded single-node Zeta cluster and 
runs complete bounded
   jobs through the normal client and configuration parser APIs.
+- `CheckpointingTimeBenchmark` keeps one streaming job running and measures 
the time required to
+  complete explicitly triggered regular checkpoints.
 
 The MiniCluster starts during JMH Trial setup and is outside the measurement. 
Job submission,
 scheduling, Source, Transform, Sink, and job completion are inside the JMH 
measurement.
@@ -149,6 +151,23 @@ java -jar seatunnel-benchmarks/target/benchmarks.jar 
SeaTunnelRowBenchmark \
 For a quick functional validation, add `-f 1 -wi 0 -i 1 -r 1s` to shorten the 
run. A single un-warmed
 sample is not valid performance evidence.
 
+### Run the Checkpoint Benchmark
+
+```bash
+java -jar seatunnel-benchmarks/target/benchmarks.jar CheckpointingTimeBenchmark
+```
+
+The benchmark runs both `recordSize=1b` and `recordSize=1kb`. 
`checkpointSingleInput` uses a
+controlled input rate and equal Source/Sink parallelism. Its dedicated JMH 
environment starts an
+isolated two-node Zeta cluster with separate master and worker roles and one 
streaming job per trial.
+The master has no worker slots, the worker executes the pipeline, and IMap 
backup count is zero. A
+separate checkpoint engine configuration (not the shared benchmark engine 
configuration) enables
+the `engine*` MapStore with the HDFS file storage implementation on the local 
filesystem, and stores
+checkpoints through the HDFS checkpoint plugin in local mode. Every invocation 
explicitly triggers
+one regular checkpoint and waits until Zeta has persisted and completed it. 
The score is checkpoint
+completion time in `s/op`, so lower is better. Job startup, workload ramp-up, 
persistence
+verification, and job shutdown are outside the measured invocation.
+
 ### Read Workflow Reports
 
 The scheduled and manually triggered `Benchmarks` workflow runs each selected 
benchmark on Java 8
@@ -234,7 +253,7 @@ These conditions reject incomplete output and prove that 
Transform work reached
 
 | Field | Description |
 |---|---|
-| `Score` | Processed rows per second for a Pipeline benchmark; higher is 
better. Row microbenchmarks retain `ops/ms`. |
+| `Score` | Processed rows per second for a Pipeline benchmark; higher is 
better. Row microbenchmarks retain `ops/ms`, while the checkpoint benchmark 
reports `s/op` and lower is better. |
 | `Error` | Uncertainty calculated from samples inside this JMH run. |
 | `Cnt` | Aggregated measurement samples, not processed rows. |
 | `Units` | Unit of the score. |
diff --git a/docs/zh/engines/zeta/benchmark.md 
b/docs/zh/engines/zeta/benchmark.md
index e4f91bc13d..294e4a799c 100644
--- a/docs/zh/engines/zeta/benchmark.md
+++ b/docs/zh/engines/zeta/benchmark.md
@@ -13,11 +13,13 @@ title: Zeta 基准测试
 
 ## 工作原理
 
-`seatunnel-benchmarks` 提供两类测试:
+`seatunnel-benchmarks` 提供三类测试:
 
 - `SeaTunnelRowBenchmark`:测试 Row 创建、读取、复制、投影和大小计算等热点代码。
 - `SeaTunnelPipelineBenchmark`:启动单节点嵌入式 Zeta 集群,并通过正常的 Client 和配置
   解析 API 运行完整的有界作业。
+- `CheckpointingTimeBenchmark`:保持一个流式作业运行,并测量显式触发普通 Checkpoint 的
+  完成耗时。
 
 MiniCluster 在每个 JMH Trial 的 Setup 阶段启动,不计入测量。作业提交、调度、Source、
 Transform、Sink 和作业完成都计入 JMH 测量。
@@ -143,6 +145,21 @@ java -jar seatunnel-benchmarks/target/benchmarks.jar 
SeaTunnelRowBenchmark \
 快速功能验证时可以增加 `-f 1 -wi 0 -i 1 -r 1s` 缩短运行时间。没有预热且只有一个样本的
 结果不能用于性能结论。
 
+### 运行 Checkpoint 基准测试
+
+```bash
+java -jar seatunnel-benchmarks/target/benchmarks.jar CheckpointingTimeBenchmark
+```
+
+该测试覆盖 `recordSize=1b` 和 `recordSize=1kb`。`checkpointSingleInput` 使用受控输入速率
+以及相同的 Source/Sink 并行度。专用 JMH 环境会在每个 Trial 中启动 master/worker 角色
+分离的双节点 Zeta 集群和一个流式作业。master 不提供 worker slot,pipeline 只在 worker
+执行,IMap backup count 为 0。该环境使用一份独立的 Checkpoint Engine 配置(不复用普通
+Benchmark 的 Engine 配置),为 `engine*` 开启基于本地文件系统的 HDFS MapStore,并通过
+HDFS Checkpoint 插件的 local 模式保存状态。每次 invocation 显式触发一个普通 Checkpoint,
+并等待 Zeta 完成持久化。Score 使用 `s/op`,数值越低越好;作业启动、负载建立、持久化
+校验和作业关闭不计入 invocation 时间。
+
 ### 查看 Workflow 报告
 
 定时或手动触发的 `Benchmarks` workflow 会在 Java 8 和 Java 11 上运行所选 benchmark。
@@ -220,7 +237,7 @@ Linux runner 使用 async-profiler 的 `ctimer` 事件进行 CPU 
profiling,不
 
 | 字段 | 说明 |
 |---|---|
-| `Score` | Pipeline Benchmark 每秒处理的行数,越大越好;Row 微基准仍使用 `ops/ms`。 |
+| `Score` | Pipeline Benchmark 每秒处理的行数,越大越好;Row 微基准使用 `ops/ms`;Checkpoint 
Benchmark 使用 `s/op`,越低越好。 |
 | `Error` | 根据本次 JMH 运行内部样本计算的不确定性。 |
 | `Cnt` | 参与聚合的 Measurement 样本数,不是处理行数。 |
 | `Units` | Score 的单位。 |
diff --git 
a/seatunnel-benchmarks/src/main/java/org/apache/seatunnel/benchmark/CheckpointBenchmarkTrigger.java
 
b/seatunnel-benchmarks/src/main/java/org/apache/seatunnel/benchmark/CheckpointBenchmarkTrigger.java
new file mode 100644
index 0000000000..ab754e7b0a
--- /dev/null
+++ 
b/seatunnel-benchmarks/src/main/java/org/apache/seatunnel/benchmark/CheckpointBenchmarkTrigger.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.benchmark;
+
+import org.apache.seatunnel.common.utils.ReflectionUtils;
+import org.apache.seatunnel.engine.common.utils.PassiveCompletableFuture;
+import org.apache.seatunnel.engine.common.utils.concurrent.CompletableFuture;
+import org.apache.seatunnel.engine.core.checkpoint.CheckpointType;
+import org.apache.seatunnel.engine.server.checkpoint.CheckpointCoordinator;
+import org.apache.seatunnel.engine.server.checkpoint.CompletedCheckpoint;
+import org.apache.seatunnel.engine.server.checkpoint.PendingCheckpoint;
+
+import java.time.Instant;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static 
org.apache.seatunnel.engine.core.checkpoint.CheckpointType.CHECKPOINT_TYPE;
+
+/** Benchmark-only test bridge for explicitly triggering a regular checkpoint. 
*/
+final class CheckpointBenchmarkTrigger {
+
+    private CheckpointBenchmarkTrigger() {}
+
+    @SuppressWarnings("unchecked")
+    static PassiveCompletableFuture<CompletedCheckpoint> trigger(
+            CheckpointCoordinator coordinator) {
+        // This bridge bypasses the coordinator trigger lock, so callers must 
keep periodic
+        // checkpoints effectively disabled and use a single JMH thread to 
prevent concurrent
+        // checkpoint triggers.
+        CompletableFuture<PendingCheckpoint> pendingCheckpoint =
+                (CompletableFuture<PendingCheckpoint>)
+                        ReflectionUtils.invoke(
+                                coordinator,
+                                "createPendingCheckpoint",
+                                new Class<?>[] {long.class, 
CheckpointType.class},
+                                new Object[] {Instant.now().toEpochMilli(), 
CHECKPOINT_TYPE});
+        ReflectionUtils.invoke(
+                coordinator,
+                "startTriggerPendingCheckpoint",
+                new Class<?>[] {CompletableFuture.class},
+                new Object[] {pendingCheckpoint});
+        return pendingCheckpoint.join().getCompletableFuture();
+    }
+
+    static boolean hasPendingCheckpoint(CheckpointCoordinator coordinator) {
+        AtomicInteger pendingCounter =
+                (AtomicInteger)
+                        ReflectionUtils.getField(coordinator, "pendingCounter")
+                                .orElseThrow(
+                                        () ->
+                                                new IllegalStateException(
+                                                        "Checkpoint pending 
counter is unavailable"));
+        return pendingCounter.get() > 0;
+    }
+}
diff --git 
a/seatunnel-benchmarks/src/main/java/org/apache/seatunnel/benchmark/CheckpointingTimeBenchmark.java
 
b/seatunnel-benchmarks/src/main/java/org/apache/seatunnel/benchmark/CheckpointingTimeBenchmark.java
new file mode 100644
index 0000000000..a0a273a448
--- /dev/null
+++ 
b/seatunnel-benchmarks/src/main/java/org/apache/seatunnel/benchmark/CheckpointingTimeBenchmark.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.benchmark;
+
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Threads;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.RunnerException;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+import org.openjdk.jmh.runner.options.VerboseMode;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+/** Measures regular checkpoint completion time for one input pipeline. */
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(SECONDS)
+@Threads(1)
+@Fork(
+        value = 3,
+        jvmArgsAppend = {
+            "-Xms4g",
+            "-Xmx4g",
+            "-XX:+UseG1GC",
+            "-XX:+AlwaysPreTouch",
+            "-XX:+DisableExplicitGC",
+            "-XX:ActiveProcessorCount=4",
+            "-Djava.net.preferIPv4Stack=true"
+        })
+public class CheckpointingTimeBenchmark extends BenchmarkBase {
+
+    public static void main(String[] args) throws RunnerException {
+        Options options =
+                new OptionsBuilder()
+                        .verbosity(VerboseMode.NORMAL)
+                        
.include(CheckpointingTimeBenchmark.class.getCanonicalName())
+                        .build();
+
+        new Runner(options).run();
+    }
+
+    @Benchmark
+    public void checkpointSingleInput(CheckpointingTimeBenchmarkPipeline 
pipeline)
+            throws Exception {
+        pipeline.triggerCheckpoint();
+    }
+}
diff --git 
a/seatunnel-benchmarks/src/main/java/org/apache/seatunnel/benchmark/CheckpointingTimeBenchmarkPipeline.java
 
b/seatunnel-benchmarks/src/main/java/org/apache/seatunnel/benchmark/CheckpointingTimeBenchmarkPipeline.java
new file mode 100644
index 0000000000..4383212022
--- /dev/null
+++ 
b/seatunnel-benchmarks/src/main/java/org/apache/seatunnel/benchmark/CheckpointingTimeBenchmarkPipeline.java
@@ -0,0 +1,295 @@
+/*
+ * 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.benchmark;
+
+import org.apache.seatunnel.engine.client.job.ClientJobExecutionEnvironment;
+import org.apache.seatunnel.engine.client.job.ClientJobProxy;
+import org.apache.seatunnel.engine.common.config.JobConfig;
+import org.apache.seatunnel.engine.common.job.JobStatus;
+import org.apache.seatunnel.engine.common.utils.PassiveCompletableFuture;
+import org.apache.seatunnel.engine.core.checkpoint.CheckpointHistoryEntry;
+import org.apache.seatunnel.engine.core.checkpoint.CheckpointInfo;
+import org.apache.seatunnel.engine.core.checkpoint.CheckpointStatus;
+import org.apache.seatunnel.engine.server.SeaTunnelServer;
+import org.apache.seatunnel.engine.server.checkpoint.CheckpointCoordinator;
+import org.apache.seatunnel.engine.server.checkpoint.CompletedCheckpoint;
+import 
org.apache.seatunnel.engine.server.checkpoint.monitor.CheckpointMonitorService;
+import org.apache.seatunnel.engine.server.dag.physical.SubPlan;
+import org.apache.seatunnel.engine.server.master.JobMaster;
+
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.Duration;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/** Long-running source-to-sink pipeline whose checkpoint completion time is 
measured. */
+@State(Scope.Thread)
+public class CheckpointingTimeBenchmarkPipeline {
+
+    private static final String JOB_TEMPLATE =
+            loadTemplate("/benchmark/source-sink-checkpoint.conf.template");
+    private static final Duration START_TIMEOUT = Duration.ofMinutes(2);
+    private static final Duration CHECKPOINT_TIMEOUT = Duration.ofMinutes(2);
+    private static final Duration CANCEL_TIMEOUT = Duration.ofSeconds(30);
+    private static final String DEBLOATING_RECORD_SIZE_VALUE = "1b";
+    private static final String UNALIGNED_RECORD_SIZE_VALUE = "1kb";
+    private static final long SOURCE_RATE_PER_SECOND = 10_000L;
+    private static final int PIPELINE_PARALLELISM = 4;
+
+    @Param({"1b", "1kb"})
+    public String recordSize;
+
+    private SeaTunnelCheckpointEnvironmentContext environment;
+    private ClientJobProxy jobProxy;
+    private CheckpointCoordinator checkpointCoordinator;
+    private CheckpointMonitorService checkpointMonitorService;
+    private int pipelineId;
+    private boolean checkpointCompleted;
+
+    /** Submits the streaming pipeline after the checkpoint benchmark cluster 
has started. */
+    @Setup(Level.Trial)
+    public void setUp(SeaTunnelCheckpointEnvironmentContext environment) 
throws Exception {
+        this.environment = environment;
+        try {
+            Path jobConfigFile = 
environment.getClusterHome().resolve("checkpoint-benchmark.conf");
+            Path resultPath = 
environment.getClusterHome().resolve("checkpoint-results");
+            Files.write(
+                    jobConfigFile,
+                    
createCheckpointJobConfig(resultPath).getBytes(StandardCharsets.UTF_8));
+            JobConfig jobConfig = new JobConfig();
+            jobConfig.setName("checkpoint-benchmark-" + UUID.randomUUID());
+            ClientJobExecutionEnvironment executionEnvironment =
+                    environment
+                            .getClient()
+                            .createExecutionContext(
+                                    jobConfigFile.toString(),
+                                    jobConfig,
+                                    environment.getMasterConfig());
+            jobProxy = executionEnvironment.execute();
+            waitUntilRunning();
+            resolveCheckpointControl();
+
+            // The MASTER node has no worker slots, so RUNNING proves 
execution is on WORKER.
+            Thread.sleep(2_000L);
+        } catch (Exception setupFailure) {
+            try {
+                tearDown();
+            } catch (Exception cleanupFailure) {
+                setupFailure.addSuppressed(cleanupFailure);
+            }
+            throw setupFailure;
+        }
+    }
+
+    /** Stops the streaming pipeline after checking checkpoint and IMap 
persistence. */
+    @TearDown(Level.Trial)
+    public void tearDown() throws Exception {
+        try {
+            if (checkpointCompleted) {
+                verifyCheckpointWasPersisted();
+            }
+            if (jobProxy != null && !jobProxy.getJobStatus().isEndState()) {
+                jobProxy.cancelJob();
+                waitUntilCanceled();
+            }
+        } finally {
+            environment = null;
+            jobProxy = null;
+            checkpointCoordinator = null;
+            checkpointMonitorService = null;
+            checkpointCompleted = false;
+        }
+    }
+
+    /** Triggers a regular checkpoint and waits until the coordinator records 
its completion. */
+    public void triggerCheckpoint() throws Exception {
+        if (checkpointCoordinator == null || checkpointMonitorService == null) 
{
+            throw new IllegalStateException("Checkpoint benchmark pipeline has 
not been started");
+        }
+        long previousCheckpointId = latestCompletedCheckpointId();
+        PassiveCompletableFuture<CompletedCheckpoint> checkpoint =
+                CheckpointBenchmarkTrigger.trigger(checkpointCoordinator);
+        checkpoint.get(CHECKPOINT_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
+
+        long deadline = System.nanoTime() + CHECKPOINT_TIMEOUT.toNanos();
+        while (System.nanoTime() < deadline) {
+            if (latestCompletedCheckpointId() > previousCheckpointId
+                    && 
!CheckpointBenchmarkTrigger.hasPendingCheckpoint(checkpointCoordinator)) {
+                checkpointCompleted = true;
+                return;
+            }
+            Thread.sleep(1L);
+        }
+        throw new IllegalStateException("Timed out waiting for benchmark 
checkpoint to complete");
+    }
+
+    String createCheckpointJobConfig(Path resultPath) {
+        return renderTemplate(
+                JOB_TEMPLATE,
+                "payload_size",
+                selectedRecordSize().getBytes(),
+                "source_rate_per_second",
+                SOURCE_RATE_PER_SECOND,
+                "pipeline_parallelism",
+                PIPELINE_PARALLELISM,
+                "result_path",
+                resultPath.toAbsolutePath());
+    }
+
+    private void waitUntilRunning() throws InterruptedException {
+        long deadline = System.nanoTime() + START_TIMEOUT.toNanos();
+        while (System.nanoTime() < deadline) {
+            JobStatus status = jobProxy.getJobStatus();
+            if (status == JobStatus.RUNNING) {
+                return;
+            }
+            if (status.isEndState()) {
+                throw new IllegalStateException(
+                        "Checkpoint benchmark job ended during setup with " + 
status);
+            }
+            Thread.sleep(100L);
+        }
+        throw new IllegalStateException("Timed out waiting for checkpoint 
benchmark job to run");
+    }
+
+    private void waitUntilCanceled() throws InterruptedException {
+        long deadline = System.nanoTime() + CANCEL_TIMEOUT.toNanos();
+        while (System.nanoTime() < deadline) {
+            if (jobProxy.getJobStatus().isEndState()) {
+                return;
+            }
+            Thread.sleep(10L);
+        }
+        throw new IllegalStateException("Timed out canceling checkpoint 
benchmark job");
+    }
+
+    private void resolveCheckpointControl() {
+        SeaTunnelServer server =
+                environment
+                        .getMasterInstance()
+                        .node
+                        .getNodeEngine()
+                        .getService(SeaTunnelServer.SERVICE_NAME);
+        if (!server.isMasterNode()) {
+            throw new IllegalStateException("Checkpoint benchmark master node 
is not active");
+        }
+        JobMaster jobMaster = 
server.getCoordinatorService().getJobMaster(jobProxy.getJobId());
+        if (jobMaster == null) {
+            throw new IllegalStateException("Checkpoint benchmark JobMaster is 
unavailable");
+        }
+        List<SubPlan> pipelines = 
jobMaster.getPhysicalPlan().getPipelineList();
+        if (pipelines.size() != 1) {
+            throw new IllegalStateException(
+                    "Checkpoint benchmark requires one pipeline but found " + 
pipelines.size());
+        }
+        pipelineId = pipelines.get(0).getPipelineId();
+        checkpointCoordinator =
+                
jobMaster.getCheckpointManager().getCheckpointCoordinator(pipelineId);
+        checkpointMonitorService = server.getCheckpointMonitorService();
+    }
+
+    private long latestCompletedCheckpointId() {
+        return checkpointMonitorService
+                .getHistory(jobProxy.getJobId(), pipelineId, 1, 
CheckpointStatus.COMPLETED).stream()
+                .map(CheckpointHistoryEntry::getCheckpointInfo)
+                .mapToLong(CheckpointInfo::getCheckpointId)
+                .findFirst()
+                .orElse(-1L);
+    }
+
+    private void verifyCheckpointWasPersisted() throws Exception {
+        verifyPersistenceDirectory(
+                environment.getCheckpointStorageDirectory(), ".ser", 
"checkpoint");
+        verifyPersistenceDirectory(environment.getMapStoreDirectory(), null, 
"MapStore");
+    }
+
+    private static void verifyPersistenceDirectory(
+            Path directory, String fileSuffix, String storageName) throws 
Exception {
+        if (!Files.isDirectory(directory)) {
+            throw new IllegalStateException(
+                    storageName + " storage directory was not created: " + 
directory);
+        }
+        try (Stream<Path> files = Files.walk(directory)) {
+            if (files.noneMatch(
+                    path ->
+                            Files.isRegularFile(path)
+                                    && (fileSuffix == null
+                                            || path.getFileName()
+                                                    .toString()
+                                                    .endsWith(fileSuffix)))) {
+                throw new IllegalStateException(
+                        "No persisted " + storageName + " state was found 
under " + directory);
+            }
+        }
+    }
+
+    private SeaTunnelCheckpointEnvironmentContext.MemorySize 
selectedRecordSize() {
+        if (DEBLOATING_RECORD_SIZE_VALUE.equals(recordSize)) {
+            return 
SeaTunnelCheckpointEnvironmentContext.DEBLOATING_RECORD_SIZE;
+        }
+        if (UNALIGNED_RECORD_SIZE_VALUE.equals(recordSize)) {
+            return SeaTunnelCheckpointEnvironmentContext.UNALIGNED_RECORD_SIZE;
+        }
+        throw new IllegalArgumentException("Unsupported checkpoint record 
size: " + recordSize);
+    }
+
+    private static String loadTemplate(String resourceName) {
+        InputStream input =
+                
CheckpointingTimeBenchmarkPipeline.class.getResourceAsStream(resourceName);
+        if (input == null) {
+            throw new IllegalStateException("Benchmark template was not found: 
" + resourceName);
+        }
+        try (BufferedReader reader =
+                new BufferedReader(new InputStreamReader(input, 
StandardCharsets.UTF_8))) {
+            return reader.lines().collect(Collectors.joining("\n", "", "\n"));
+        } catch (IOException e) {
+            throw new IllegalStateException("Could not read benchmark template 
" + resourceName, e);
+        }
+    }
+
+    private static String renderTemplate(String template, Object... 
replacements) {
+        String rendered = template;
+        for (int index = 0; index < replacements.length; index += 2) {
+            rendered =
+                    rendered.replace(
+                            "{{" + replacements[index] + "}}",
+                            String.valueOf(replacements[index + 1]));
+        }
+        if (rendered.contains("{{")) {
+            throw new IllegalStateException(
+                    "Checkpoint benchmark template contains an unresolved 
placeholder");
+        }
+        return rendered;
+    }
+}
diff --git 
a/seatunnel-benchmarks/src/main/java/org/apache/seatunnel/benchmark/SeaTunnelCheckpointEnvironmentContext.java
 
b/seatunnel-benchmarks/src/main/java/org/apache/seatunnel/benchmark/SeaTunnelCheckpointEnvironmentContext.java
new file mode 100644
index 0000000000..9a19889edc
--- /dev/null
+++ 
b/seatunnel-benchmarks/src/main/java/org/apache/seatunnel/benchmark/SeaTunnelCheckpointEnvironmentContext.java
@@ -0,0 +1,380 @@
+/*
+ * 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.benchmark;
+
+import org.apache.seatunnel.common.config.Common;
+import org.apache.seatunnel.common.config.DeployMode;
+import org.apache.seatunnel.engine.client.SeaTunnelClient;
+import org.apache.seatunnel.engine.common.config.ConfigProvider;
+import org.apache.seatunnel.engine.common.config.SeaTunnelClientConfig;
+import org.apache.seatunnel.engine.common.config.SeaTunnelConfig;
+import org.apache.seatunnel.engine.server.SeaTunnelServerStarter;
+
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+
+import com.hazelcast.client.config.ClientConfig;
+import com.hazelcast.config.Config;
+import com.hazelcast.config.YamlConfigBuilder;
+import com.hazelcast.instance.impl.HazelcastInstanceImpl;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.Duration;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Locale;
+import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/** JMH environment that owns a dedicated two-node Zeta master/worker cluster 
and its client. */
+@State(Scope.Thread)
+public class SeaTunnelCheckpointEnvironmentContext {
+
+    public static final MemorySize DEBLOATING_RECORD_SIZE = 
MemorySize.parse("1b");
+    public static final MemorySize UNALIGNED_RECORD_SIZE = 
MemorySize.parse("1kb");
+
+    private static final String ENGINE_CONFIG_TEMPLATE =
+            loadTemplate("/benchmark/engine-checkpoint.yaml.template");
+    private static final String HAZELCAST_MASTER_CONFIG_TEMPLATE =
+            
loadTemplate("/benchmark/hazelcast-checkpoint-master.yaml.template");
+    private static final String HAZELCAST_WORKER_CONFIG_TEMPLATE =
+            
loadTemplate("/benchmark/hazelcast-checkpoint-worker.yaml.template");
+    private static final Duration START_TIMEOUT = Duration.ofMinutes(2);
+    private static final int WORKER_SLOT_COUNT = 12;
+
+    private Path clusterHome;
+    private Path checkpointStorageDirectory;
+    private Path mapStoreDirectory;
+    private SeaTunnelConfig masterConfig;
+    private HazelcastInstanceImpl masterInstance;
+    private HazelcastInstanceImpl workerInstance;
+    private SeaTunnelClient client;
+    private String previousSeaTunnelHome;
+    private String previousUppercaseSeaTunnelHome;
+    private String previousSeaTunnelConfig;
+    private String previousCommonSeaTunnelHome;
+    private DeployMode previousDeployMode;
+
+    /** Starts a dedicated master, worker, and client for checkpoint benchmark 
pipelines. */
+    @Setup(Level.Trial)
+    public void setUp() throws Exception {
+        try {
+            prepareClusterHome();
+            String clusterName = "seatunnel-checkpoint-benchmark-" + 
UUID.randomUUID();
+            writeEngineConfig();
+
+            masterConfig = createMasterConfig(clusterName);
+            masterInstance = 
SeaTunnelServerStarter.createMasterHazelcastInstance(masterConfig);
+
+            int masterPort =
+                    
masterInstance.getCluster().getLocalMember().getSocketAddress().getPort();
+            SeaTunnelConfig workerConfig =
+                    createWorkerConfig(clusterName, "127.0.0.1:" + masterPort);
+            workerInstance = 
SeaTunnelServerStarter.createWorkerHazelcastInstance(workerConfig);
+            waitUntilClusterFormed();
+
+            ClientConfig clientConfig = new SeaTunnelClientConfig();
+            clientConfig.setClusterName(clusterName);
+            clientConfig
+                    .getNetworkConfig()
+                    .setAddresses(Collections.singletonList("127.0.0.1:" + 
masterPort));
+            clientConfig
+                    .getConnectionStrategyConfig()
+                    .getConnectionRetryConfig()
+                    .setClusterConnectTimeoutMillis(30_000L);
+            client = new SeaTunnelClient(clientConfig);
+        } catch (Exception setupFailure) {
+            try {
+                tearDown();
+            } catch (Exception cleanupFailure) {
+                setupFailure.addSuppressed(cleanupFailure);
+            }
+            throw setupFailure;
+        }
+    }
+
+    /** Stops the client and both benchmark cluster nodes. */
+    @TearDown(Level.Trial)
+    public void tearDown() throws Exception {
+        Exception failure = null;
+        try {
+            if (client != null) {
+                client.close();
+            }
+        } catch (Exception cleanupFailure) {
+            failure = cleanupFailure;
+        } finally {
+            if (workerInstance != null) {
+                workerInstance.shutdown();
+            }
+            if (masterInstance != null) {
+                masterInstance.shutdown();
+            }
+            restoreEnvironment();
+            try {
+                deleteRecursively(clusterHome);
+            } catch (Exception deletionFailure) {
+                if (failure == null) {
+                    failure = deletionFailure;
+                } else {
+                    failure.addSuppressed(deletionFailure);
+                }
+            }
+            clearState();
+        }
+        if (failure != null) {
+            throw failure;
+        }
+    }
+
+    private void prepareClusterHome() throws IOException {
+        clusterHome = 
Files.createTempDirectory("seatunnel-checkpoint-benchmark-");
+        Path persistenceDirectory = 
clusterHome.resolve("checkpoint-persistence");
+        checkpointStorageDirectory = 
persistenceDirectory.resolve("checkpoints");
+        mapStoreDirectory = persistenceDirectory.resolve("imap");
+
+        previousSeaTunnelHome = System.getProperty("seatunnel.home");
+        previousUppercaseSeaTunnelHome = System.getProperty("SEATUNNEL_HOME");
+        previousSeaTunnelConfig = System.getProperty("seatunnel.config");
+        previousCommonSeaTunnelHome = Common.getSeaTunnelHome();
+        previousDeployMode = Common.getDeployMode();
+        System.setProperty("seatunnel.home", clusterHome.toString());
+        System.setProperty("SEATUNNEL_HOME", clusterHome.toString());
+        Common.setSeaTunnelHome(clusterHome.toString());
+        Common.setDeployMode(DeployMode.CLIENT);
+    }
+
+    private void writeEngineConfig() throws IOException {
+        Path engineConfigFile = clusterHome.resolve("seatunnel.yaml");
+        Files.write(
+                engineConfigFile,
+                renderTemplate(
+                                ENGINE_CONFIG_TEMPLATE,
+                                "slot_count",
+                                WORKER_SLOT_COUNT,
+                                "checkpoint_storage_directory",
+                                checkpointStorageDirectory.toAbsolutePath())
+                        .getBytes(StandardCharsets.UTF_8));
+        System.setProperty("seatunnel.config", engineConfigFile.toString());
+    }
+
+    private SeaTunnelConfig createMasterConfig(String clusterName) {
+        String hazelcastConfig =
+                renderTemplate(
+                        HAZELCAST_MASTER_CONFIG_TEMPLATE,
+                        "cluster_name",
+                        clusterName,
+                        "imap_directory",
+                        mapStoreDirectory.toAbsolutePath());
+        return createSeaTunnelConfig(hazelcastConfig);
+    }
+
+    private SeaTunnelConfig createWorkerConfig(String clusterName, String 
masterAddress) {
+        String hazelcastConfig =
+                renderTemplate(
+                        HAZELCAST_WORKER_CONFIG_TEMPLATE,
+                        "cluster_name",
+                        clusterName,
+                        "master_address",
+                        masterAddress);
+        return createSeaTunnelConfig(hazelcastConfig);
+    }
+
+    private SeaTunnelConfig createSeaTunnelConfig(String hazelcastConfig) {
+        String engineConfig =
+                renderTemplate(
+                        ENGINE_CONFIG_TEMPLATE,
+                        "slot_count",
+                        WORKER_SLOT_COUNT,
+                        "checkpoint_storage_directory",
+                        checkpointStorageDirectory.toAbsolutePath());
+        SeaTunnelConfig config = 
ConfigProvider.locateAndGetSeaTunnelConfigFromString(engineConfig);
+        Config memberConfig =
+                new YamlConfigBuilder(
+                                new ByteArrayInputStream(
+                                        
hazelcastConfig.getBytes(StandardCharsets.UTF_8)))
+                        .build();
+        config.setHazelcastConfig(memberConfig);
+        return config;
+    }
+
+    private void waitUntilClusterFormed() throws InterruptedException {
+        long deadline = System.nanoTime() + START_TIMEOUT.toNanos();
+        while (System.nanoTime() < deadline) {
+            if (masterInstance.getCluster().getMembers().size() == 2
+                    && workerInstance.getCluster().getMembers().size() == 2) {
+                return;
+            }
+            Thread.sleep(100L);
+        }
+        throw new IllegalStateException("Timed out forming checkpoint 
master/worker cluster");
+    }
+
+    Path getClusterHome() {
+        return clusterHome;
+    }
+
+    Path getCheckpointStorageDirectory() {
+        return checkpointStorageDirectory;
+    }
+
+    Path getMapStoreDirectory() {
+        return mapStoreDirectory;
+    }
+
+    SeaTunnelConfig getMasterConfig() {
+        return masterConfig;
+    }
+
+    HazelcastInstanceImpl getMasterInstance() {
+        return masterInstance;
+    }
+
+    SeaTunnelClient getClient() {
+        return client;
+    }
+
+    /** Immutable binary memory size used only by this checkpoint environment. 
*/
+    public static final class MemorySize {
+
+        private static final Pattern SIZE_PATTERN = 
Pattern.compile("([0-9]+)\\s*([a-zA-Z]+)");
+
+        private final long bytes;
+
+        private MemorySize(long bytes) {
+            this.bytes = bytes;
+        }
+
+        public static MemorySize parse(String value) {
+            Matcher matcher = SIZE_PATTERN.matcher(value.trim());
+            if (!matcher.matches()) {
+                throw new IllegalArgumentException("Invalid memory size: " + 
value);
+            }
+
+            long amount = Long.parseLong(matcher.group(1));
+            String unit = matcher.group(2).toLowerCase(Locale.ROOT);
+            long multiplier;
+            switch (unit) {
+                case "b":
+                    multiplier = 1L;
+                    break;
+                case "kb":
+                case "kib":
+                    multiplier = 1L << 10;
+                    break;
+                case "mb":
+                case "mib":
+                    multiplier = 1L << 20;
+                    break;
+                case "gb":
+                case "gib":
+                    multiplier = 1L << 30;
+                    break;
+                default:
+                    throw new IllegalArgumentException("Unsupported memory 
size unit: " + unit);
+            }
+            return new MemorySize(Math.multiplyExact(amount, multiplier));
+        }
+
+        public long getBytes() {
+            return bytes;
+        }
+    }
+
+    private void restoreEnvironment() {
+        restoreSystemProperty("seatunnel.home", previousSeaTunnelHome);
+        restoreSystemProperty("SEATUNNEL_HOME", 
previousUppercaseSeaTunnelHome);
+        restoreSystemProperty("seatunnel.config", previousSeaTunnelConfig);
+        Common.setSeaTunnelHome(previousCommonSeaTunnelHome);
+        Common.setDeployMode(previousDeployMode);
+    }
+
+    private void clearState() {
+        clusterHome = null;
+        checkpointStorageDirectory = null;
+        mapStoreDirectory = null;
+        masterConfig = null;
+        masterInstance = null;
+        workerInstance = null;
+        client = null;
+    }
+
+    private static String loadTemplate(String resourceName) {
+        InputStream input =
+                
SeaTunnelCheckpointEnvironmentContext.class.getResourceAsStream(resourceName);
+        if (input == null) {
+            throw new IllegalStateException("Benchmark template was not found: 
" + resourceName);
+        }
+        try (BufferedReader reader =
+                new BufferedReader(new InputStreamReader(input, 
StandardCharsets.UTF_8))) {
+            return reader.lines().collect(Collectors.joining("\n", "", "\n"));
+        } catch (IOException e) {
+            throw new IllegalStateException("Could not read benchmark template 
" + resourceName, e);
+        }
+    }
+
+    private static String renderTemplate(String template, Object... 
replacements) {
+        String rendered = template;
+        for (int index = 0; index < replacements.length; index += 2) {
+            rendered =
+                    rendered.replace(
+                            "{{" + replacements[index] + "}}",
+                            String.valueOf(replacements[index + 1]));
+        }
+        if (rendered.contains("{{")) {
+            throw new IllegalStateException(
+                    "Checkpoint benchmark template contains an unresolved 
placeholder");
+        }
+        return rendered;
+    }
+
+    private static void restoreSystemProperty(String key, String value) {
+        if (value == null) {
+            System.clearProperty(key);
+        } else {
+            System.setProperty(key, value);
+        }
+    }
+
+    private static void deleteRecursively(Path root) throws IOException {
+        if (root == null || !Files.exists(root)) {
+            return;
+        }
+        List<Path> paths;
+        try (Stream<Path> stream = Files.walk(root)) {
+            paths = 
stream.sorted(Comparator.reverseOrder()).collect(Collectors.toList());
+        }
+        for (Path path : paths) {
+            Files.deleteIfExists(path);
+        }
+    }
+}
diff --git 
a/seatunnel-benchmarks/src/main/java/org/apache/seatunnel/benchmark/connector/source/BenchmarkSourceReader.java
 
b/seatunnel-benchmarks/src/main/java/org/apache/seatunnel/benchmark/connector/source/BenchmarkSourceReader.java
index 7c0cdf7d61..7fa96b98b9 100644
--- 
a/seatunnel-benchmarks/src/main/java/org/apache/seatunnel/benchmark/connector/source/BenchmarkSourceReader.java
+++ 
b/seatunnel-benchmarks/src/main/java/org/apache/seatunnel/benchmark/connector/source/BenchmarkSourceReader.java
@@ -115,7 +115,9 @@ public final class BenchmarkSourceReader
     }
 
     @Override
-    public void notifyCheckpointComplete(long checkpointId) {}
+    public void notifyCheckpointComplete(long checkpointId) {
+        // The synthetic source has no external offsets to commit after 
coordinator completion.
+    }
 
     static long scheduledMillis(BenchmarkSourceSplit split, long sequence) {
         if (split.getRatePerSecond() == 0) {
diff --git 
a/seatunnel-benchmarks/src/main/resources/benchmark/engine-checkpoint.yaml.template
 
b/seatunnel-benchmarks/src/main/resources/benchmark/engine-checkpoint.yaml.template
new file mode 100644
index 0000000000..cd1e9d4eb8
--- /dev/null
+++ 
b/seatunnel-benchmarks/src/main/resources/benchmark/engine-checkpoint.yaml.template
@@ -0,0 +1,42 @@
+#
+# 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.
+#
+
+seatunnel:
+  engine:
+    backup-count: 0
+    classloader-cache-mode: true
+    slot-service:
+      dynamic-slot: false
+      slot-num: {{slot_count}}
+    checkpoint:
+      interval: 86400000
+      timeout: 120000
+      storage:
+        type: hdfs
+        max-retained: 20
+        plugin-config:
+          namespace: "{{checkpoint_storage_directory}}"
+          storage.type: local
+          fs.defaultFS: file:///
+    telemetry:
+      metric:
+        enabled: false
+      logs:
+        scheduled-deletion-enable: false
+    http:
+      enable-http: false
+      enable-https: false
diff --git 
a/seatunnel-benchmarks/src/main/resources/benchmark/hazelcast-checkpoint-master.yaml.template
 
b/seatunnel-benchmarks/src/main/resources/benchmark/hazelcast-checkpoint-master.yaml.template
new file mode 100644
index 0000000000..9c5223190a
--- /dev/null
+++ 
b/seatunnel-benchmarks/src/main/resources/benchmark/hazelcast-checkpoint-master.yaml.template
@@ -0,0 +1,50 @@
+#
+# 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.
+#
+
+hazelcast:
+  cluster-name: "{{cluster_name}}"
+  network:
+    interfaces:
+      enabled: true
+      interfaces:
+        - 127.0.0.1
+    join:
+      auto-detection:
+        enabled: false
+      multicast:
+        enabled: false
+      tcp-ip:
+        enabled: true
+        member-list: []
+    port:
+      auto-increment: true
+      port: 5701
+  map:
+    engine*:
+      backup-count: 0
+      async-backup-count: 0
+      map-store:
+        enabled: true
+        initial-mode: EAGER
+        factory-class-name: 
org.apache.seatunnel.engine.server.persistence.FileMapStoreFactory
+        properties:
+          type: hdfs
+          namespace: "{{imap_directory}}"
+          clusterName: "{{cluster_name}}"
+          fs.defaultFS: file:///
+  properties:
+    hazelcast.phone.home.enabled: false
diff --git 
a/seatunnel-benchmarks/src/main/resources/benchmark/hazelcast-checkpoint-worker.yaml.template
 
b/seatunnel-benchmarks/src/main/resources/benchmark/hazelcast-checkpoint-worker.yaml.template
new file mode 100644
index 0000000000..8f19d4bc9f
--- /dev/null
+++ 
b/seatunnel-benchmarks/src/main/resources/benchmark/hazelcast-checkpoint-worker.yaml.template
@@ -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.
+#
+
+hazelcast:
+  cluster-name: "{{cluster_name}}"
+  network:
+    interfaces:
+      enabled: true
+      interfaces:
+        - 127.0.0.1
+    join:
+      auto-detection:
+        enabled: false
+      multicast:
+        enabled: false
+      tcp-ip:
+        enabled: true
+        member-list:
+          - "{{master_address}}"
+    port:
+      auto-increment: true
+      port: 5701
+  properties:
+    hazelcast.phone.home.enabled: false
diff --git 
a/seatunnel-benchmarks/src/main/resources/benchmark/source-sink-checkpoint.conf.template
 
b/seatunnel-benchmarks/src/main/resources/benchmark/source-sink-checkpoint.conf.template
new file mode 100644
index 0000000000..b96e731f76
--- /dev/null
+++ 
b/seatunnel-benchmarks/src/main/resources/benchmark/source-sink-checkpoint.conf.template
@@ -0,0 +1,49 @@
+#
+# 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.
+#
+
+env {
+  job.name = "checkpoint-benchmark"
+  job.mode = "STREAMING"
+  checkpoint.interval = 86400000
+  checkpoint.timeout = 120000
+  parallelism = {{pipeline_parallelism}}
+}
+
+source {
+  BenchmarkSource {
+    plugin_output = "checkpoint_rows"
+    parallelism = {{pipeline_parallelism}}
+    total_rows = 9223372036854775807
+    rate_per_second = {{source_rate_per_second}}
+    payload_size = {{payload_size}}
+    start_delay_millis = 0
+    emit_batch_size = 1
+  }
+}
+
+sink {
+  BenchmarkSink {
+    plugin_input = "checkpoint_rows"
+    parallelism = {{pipeline_parallelism}}
+    result_path = "{{result_path}}"
+    run_id = "checkpoint-benchmark"
+    expected_rows = 9223372036854775807
+    rate_per_second = 0
+    payload_size = {{payload_size}}
+    transform_operations = 0
+  }
+}
diff --git 
a/seatunnel-benchmarks/src/test/java/org/apache/seatunnel/benchmark/CheckpointingTimeBenchmarkTest.java
 
b/seatunnel-benchmarks/src/test/java/org/apache/seatunnel/benchmark/CheckpointingTimeBenchmarkTest.java
new file mode 100644
index 0000000000..991b670a63
--- /dev/null
+++ 
b/seatunnel-benchmarks/src/test/java/org/apache/seatunnel/benchmark/CheckpointingTimeBenchmarkTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.benchmark;
+
+import org.junit.jupiter.api.Test;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Threads;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class CheckpointingTimeBenchmarkTest {
+
+    @Test
+    void shouldUseCheckpointRecordSizesAndSeconds() {
+        assertEquals(1L, 
SeaTunnelCheckpointEnvironmentContext.DEBLOATING_RECORD_SIZE.getBytes());
+        assertEquals(
+                1_024L, 
SeaTunnelCheckpointEnvironmentContext.UNALIGNED_RECORD_SIZE.getBytes());
+        assertEquals(
+                TimeUnit.SECONDS,
+                
CheckpointingTimeBenchmark.class.getAnnotation(OutputTimeUnit.class).value());
+        assertEquals(
+                Mode.AverageTime,
+                
CheckpointingTimeBenchmark.class.getAnnotation(BenchmarkMode.class).value()[0]);
+        assertEquals(1, 
CheckpointingTimeBenchmark.class.getAnnotation(Threads.class).value());
+    }
+
+    @Test
+    void shouldRejectInvalidMemorySize() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> 
SeaTunnelCheckpointEnvironmentContext.MemorySize.parse("1tb"));
+    }
+
+    @Test
+    void shouldRenderCheckpointJobConfig() {
+        CheckpointingTimeBenchmarkPipeline pipeline = new 
CheckpointingTimeBenchmarkPipeline();
+        pipeline.recordSize = "1kb";
+        Path resultPath = Paths.get("target", 
"checkpoint-results").toAbsolutePath();
+
+        String jobConfig = pipeline.createCheckpointJobConfig(resultPath);
+
+        assertTrue(jobConfig.contains("payload_size = 1024"));
+        assertTrue(jobConfig.contains("rate_per_second = 10000"));
+        assertTrue(jobConfig.contains("parallelism = 4"));
+        assertTrue(jobConfig.contains("result_path = \"" + resultPath + "\""));
+        assertFalse(jobConfig.contains("{{"));
+    }
+}

Reply via email to