This is an automated email from the ASF dual-hosted git repository.
WangzJi pushed a commit to branch 2.x
in repository https://gitbox.apache.org/repos/asf/incubator-seata.git
The following commit(s) were added to refs/heads/2.x by this push:
new 8c2659f93e feature: add XA mode to benchmark CLI (#8094)
8c2659f93e is described below
commit 8c2659f93e33b65eb1c259ae352adbd13681f3bf
Author: zihenzzz <[email protected]>
AuthorDate: Sun May 10 10:47:36 2026 +0800
feature: add XA mode to benchmark CLI (#8094)
---
changes/en-us/2.x.md | 1 +
changes/zh-cn/2.x.md | 1 +
test-suite/seata-benchmark-cli/README.md | 63 +++++-
.../seata/benchmark/BenchmarkApplication.java | 2 +-
.../apache/seata/benchmark/BenchmarkRunner.java | 9 +-
.../seata/benchmark/config/BenchmarkConfig.java | 4 +-
.../seata/benchmark/executor/XAModeExecutor.java | 235 +++++++++++++++++++++
7 files changed, 306 insertions(+), 9 deletions(-)
diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md
index ce6b679e06..01742db8a1 100644
--- a/changes/en-us/2.x.md
+++ b/changes/en-us/2.x.md
@@ -19,6 +19,7 @@ Add changes here for all PR submitted to the 2.x branch.
<!-- Please add the `changes` to the following
location(feature/bugfix/optimize/test) based on the type of PR -->
### feature:
+- [[#8094](https://github.com/apache/incubator-seata/pull/8094)] add XA mode
to benchmark CLI
- [[#8062](https://github.com/apache/incubator-seata/pull/8062)] implement
real TCC mode in benchmark CLI
- [[#8052](https://github.com/apache/incubator-seata/pull/8052)] add
SAGA_ANNOTATION mode to benchmark CLI
- [[#8014](https://github.com/apache/incubator-seata/pull/8014)] add P99.9
latency percentile to benchmark CLI
diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md
index 30dd88a9eb..44d153784a 100644
--- a/changes/zh-cn/2.x.md
+++ b/changes/zh-cn/2.x.md
@@ -20,6 +20,7 @@
### feature:
+- [[#8094](https://github.com/apache/incubator-seata/pull/8094)] 为 benchmark
CLI 增加 XA 模式支持
- [[#8062](https://github.com/apache/incubator-seata/pull/8062)] 为benchmark
CLI实现真实TCC模式(try/confirm/cancel)
- [[#8052](https://github.com/apache/incubator-seata/pull/8052)] 为benchmark
CLI新增SAGA_ANNOTATION模式
- [[#7882](https://github.com/apache/incubator-seata/pull/7882)]
为NamingServer增加Metrics监控
diff --git a/test-suite/seata-benchmark-cli/README.md
b/test-suite/seata-benchmark-cli/README.md
index 2caedcd89f..eb7e3beae0 100644
--- a/test-suite/seata-benchmark-cli/README.md
+++ b/test-suite/seata-benchmark-cli/README.md
@@ -20,7 +20,7 @@ A command-line benchmark tool for stress testing Seata
transaction modes.
## Features
-- Support for **AT**, **TCC**, **SAGA**, and **SAGA_ANNOTATION** transaction
modes
+- Support for **AT**, **TCC**, **SAGA**, **XA**, and **SAGA_ANNOTATION**
transaction modes
- **Dual execution modes**:
- **Empty mode** (`--branches 0`): Pure Seata protocol overhead testing
- **Real mode** (`--branches N`): Actual distributed transaction execution
@@ -90,6 +90,14 @@ java -jar seata-benchmark-cli.jar \
--tps 100 \
--threads 1 \
--duration 60
+
+# XA mode benchmark (empty transaction)
+java -jar seata-benchmark-cli.jar \
+ --server 127.0.0.1:8091 \
+ --mode XA \
+ --tps 100 \
+ --threads 1 \
+ --duration 60
```
### Real Mode (with actual database operations)
@@ -178,6 +186,15 @@ java -jar seata-benchmark-cli.jar \
--saga-shape order \
--saga-timeout-step payment \
--saga-timeout-ms 3000
+
+# XA mode with real MySQL XA branches (via Testcontainers)
+java -jar seata-benchmark-cli.jar \
+ --server 127.0.0.1:8091 \
+ --mode XA \
+ --tps 100 \
+ --threads 1 \
+ --duration 60 \
+ --branches 3
```
### Performance Testing Modes
@@ -252,7 +269,7 @@ Usage: seata-benchmark [-hV]
[--application-id=<applicationId>]
Options:
-s, --server=<server> Seata Server address (host:port)
- -m, --mode=<mode> Transaction mode: AT, TCC, SAGA, or
SAGA_ANNOTATION
+ -m, --mode=<mode> Transaction mode: AT, TCC, SAGA, XA, or
SAGA_ANNOTATION
-t, --tps=<targetTps> Target TPS (default: 100)
--threads=<threads> Concurrent threads (default: 10)
-d, --duration=<duration> Duration in seconds (default: 60)
@@ -523,6 +540,37 @@ java -jar seata-benchmark-cli.jar --server 127.0.0.1:8091 \
--mode SAGA_ANNOTATION --tps 10000 --threads 50 --duration 60 --branches 3
```
+### Test XA Mode
+
+```bash
+# Empty mode: protocol overhead only
+java -jar seata-benchmark-cli.jar \
+ --server 127.0.0.1:8091 \
+ --mode XA \
+ --tps 100 \
+ --threads 1 \
+ --duration 60
+
+# Real mode: 3 XA branches per transaction
+java -jar seata-benchmark-cli.jar \
+ --server 127.0.0.1:8091 \
+ --mode XA \
+ --tps 100 \
+ --threads 1 \
+ --duration 60 \
+ --branches 3
+
+# Real mode with rollback injection
+java -jar seata-benchmark-cli.jar \
+ --server 127.0.0.1:8091 \
+ --mode XA \
+ --tps 100 \
+ --threads 1 \
+ --duration 60 \
+ --branches 3 \
+ --rollback-percentage 10
+```
+
### Test TCC Mode (try/confirm/cancel)
```bash
@@ -574,6 +622,7 @@ java -jar seata-benchmark-cli.jar \
| AT | Pure protocol overhead | MySQL via Testcontainers
(account transfer) |
| TCC | Pure protocol overhead | Real try/confirm/cancel via
@LocalTCC interceptor |
| SAGA | Mock simulation | State machine engine with
compensation |
+| XA | Pure protocol overhead | MySQL XA via Testcontainers
(account transfer) |
| SAGA_ANNOTATION | Pure protocol overhead | Annotation interceptor + TC
compensation callback |
### Empty Transaction Mode
@@ -583,6 +632,7 @@ For accurate benchmarking of Seata Server capacity, the
tool executes **empty tr
- **AT Mode**: Only `begin()` and `commit()` operations, no SQL execution
- **TCC Mode**: Empty global transaction, no branch registration
- **SAGA Mode**: Simplified simulation without state machine
+- **XA Mode**: Empty global transaction, no XA branch registration
- **SAGA_ANNOTATION Mode**: Empty global transaction, no branch registration
This approach:
@@ -621,6 +671,12 @@ When `--branches` is set to a value greater than 0:
- Creates benchmark inventory, account, and order tables
- Executes DB-backed inventory/payment/order actions with compensation
+- **XA Mode**:
+ - Starts MySQL container via Testcontainers
+ - Wraps the datasource with `DataSourceProxyXA`
+ - Creates an XA benchmark account table with initial test data
+ - Executes `--branches N` XA-backed account transfer branches per global
transaction
+
### SAGA Workloads
- `mock` workload:
@@ -707,7 +763,7 @@ Logs are written to `seata-benchmark.log` in the current
directory.
## Roadmap
### Current Version (v1.0)
-- AT, TCC, SAGA, and SAGA_ANNOTATION mode support
+- AT, TCC, SAGA, XA, and SAGA_ANNOTATION mode support
- Empty and real transaction modes
- YAML configuration file support
- Fault injection (rollback percentage)
@@ -719,7 +775,6 @@ Logs are written to `seata-benchmark.log` in the current
directory.
**v1.1 - Enhancement:**
- P99.9 percentile
-- XA mode support
**v2.0 - Advanced Features:**
- Real-time TUI (Terminal User Interface)
diff --git
a/test-suite/seata-benchmark-cli/src/main/java/org/apache/seata/benchmark/BenchmarkApplication.java
b/test-suite/seata-benchmark-cli/src/main/java/org/apache/seata/benchmark/BenchmarkApplication.java
index 189fdd8460..6ceda317d6 100644
---
a/test-suite/seata-benchmark-cli/src/main/java/org/apache/seata/benchmark/BenchmarkApplication.java
+++
b/test-suite/seata-benchmark-cli/src/main/java/org/apache/seata/benchmark/BenchmarkApplication.java
@@ -45,7 +45,7 @@ public class BenchmarkApplication implements
Callable<Integer> {
@Option(
names = {"-m", "--mode"},
- description = "Transaction mode: AT, TCC, SAGA, or
SAGA_ANNOTATION",
+ description = "Transaction mode: AT, TCC, SAGA, XA, or
SAGA_ANNOTATION",
required = false)
private String mode;
diff --git
a/test-suite/seata-benchmark-cli/src/main/java/org/apache/seata/benchmark/BenchmarkRunner.java
b/test-suite/seata-benchmark-cli/src/main/java/org/apache/seata/benchmark/BenchmarkRunner.java
index 0fda84659c..540948b62e 100644
---
a/test-suite/seata-benchmark-cli/src/main/java/org/apache/seata/benchmark/BenchmarkRunner.java
+++
b/test-suite/seata-benchmark-cli/src/main/java/org/apache/seata/benchmark/BenchmarkRunner.java
@@ -23,6 +23,7 @@ import org.apache.seata.benchmark.executor.SagaModeExecutor;
import org.apache.seata.benchmark.executor.TCCModeExecutor;
import org.apache.seata.benchmark.executor.TransactionExecutor;
import org.apache.seata.benchmark.executor.WorkloadGenerator;
+import org.apache.seata.benchmark.executor.XAModeExecutor;
import org.apache.seata.benchmark.model.BenchmarkMetrics;
import org.apache.seata.benchmark.monitor.MetricsCollector;
import org.apache.seata.core.model.BranchType;
@@ -134,13 +135,17 @@ public class BenchmarkRunner {
String sagaMode = isRealMode ? " (state machine engine)" : "
(empty transaction)";
System.out.println("Creating Saga mode executor" + sagaMode +
"\n");
return new SagaModeExecutor(config);
+ case XA:
+ String xaMode = isRealMode ? " (MySQL XA via Testcontainers)"
: " (empty transaction)";
+ System.out.println("Creating XA mode executor" + xaMode +
"\n");
+ return new XAModeExecutor(config);
case SAGA_ANNOTATION:
String sagaAnnotationMode = isRealMode ? " (annotation-based
compensation)" : " (empty transaction)";
System.out.println("Creating SAGA_ANNOTATION mode executor" +
sagaAnnotationMode + "\n");
return new SagaAnnotationModeExecutor(config);
default:
- throw new IllegalArgumentException(
- "Unsupported mode: " + branchType + ". Only AT, TCC,
SAGA, and SAGA_ANNOTATION are supported.");
+ throw new IllegalArgumentException("Unsupported mode: " +
branchType
+ + ". Only AT, TCC, SAGA, XA, and SAGA_ANNOTATION are
supported.");
}
}
}
diff --git
a/test-suite/seata-benchmark-cli/src/main/java/org/apache/seata/benchmark/config/BenchmarkConfig.java
b/test-suite/seata-benchmark-cli/src/main/java/org/apache/seata/benchmark/config/BenchmarkConfig.java
index 20d34cd439..ab609afe66 100644
---
a/test-suite/seata-benchmark-cli/src/main/java/org/apache/seata/benchmark/config/BenchmarkConfig.java
+++
b/test-suite/seata-benchmark-cli/src/main/java/org/apache/seata/benchmark/config/BenchmarkConfig.java
@@ -196,12 +196,12 @@ public class BenchmarkConfig {
}
private static final EnumSet<BranchType> SUPPORTED_MODES =
- EnumSet.of(BranchType.AT, BranchType.TCC, BranchType.SAGA,
BranchType.SAGA_ANNOTATION);
+ EnumSet.of(BranchType.AT, BranchType.TCC, BranchType.SAGA,
BranchType.XA, BranchType.SAGA_ANNOTATION);
private void validateMode() {
if (!SUPPORTED_MODES.contains(mode)) {
throw new IllegalArgumentException(
- "Unsupported mode: " + mode + ". Only AT, TCC, SAGA, and
SAGA_ANNOTATION are supported.");
+ "Unsupported mode: " + mode + ". Only AT, TCC, SAGA, XA,
and SAGA_ANNOTATION are supported.");
}
}
diff --git
a/test-suite/seata-benchmark-cli/src/main/java/org/apache/seata/benchmark/executor/XAModeExecutor.java
b/test-suite/seata-benchmark-cli/src/main/java/org/apache/seata/benchmark/executor/XAModeExecutor.java
new file mode 100644
index 0000000000..5909d0a83e
--- /dev/null
+++
b/test-suite/seata-benchmark-cli/src/main/java/org/apache/seata/benchmark/executor/XAModeExecutor.java
@@ -0,0 +1,235 @@
+/*
+ * 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.seata.benchmark.executor;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import org.apache.seata.benchmark.config.BenchmarkConfig;
+import org.apache.seata.benchmark.constant.BenchmarkConstants;
+import org.apache.seata.rm.datasource.xa.DataSourceProxyXA;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.containers.MySQLContainer;
+import org.testcontainers.utility.DockerImageName;
+
+import java.security.SecureRandom;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+/**
+ * XA mode transaction executor supporting both empty and real transaction
modes.
+ *
+ * <p>Supports two sub-modes controlled by {@code --branches}:
+ * <ul>
+ * <li><b>Empty mode</b> ({@code branches == 0}): starts and commits an
empty global transaction.
+ * Measures pure Seata XA protocol overhead with no branch
registration.</li>
+ * <li><b>Real mode</b> ({@code branches > 0}): executes {@code branches} XA
branch operations
+ * per transaction using MySQL via Testcontainers and {@link
DataSourceProxyXA}.</li>
+ * </ul>
+ */
+public class XAModeExecutor extends AbstractTransactionExecutor {
+
+ private static final Logger LOGGER =
LoggerFactory.getLogger(XAModeExecutor.class);
+ private static final ThreadLocal<SecureRandom> SECURE_RANDOM =
ThreadLocal.withInitial(SecureRandom::new);
+
+ private MySQLContainer<?> mysqlContainer;
+ private HikariDataSource rawDataSource;
+ private DataSourceProxyXA dataSourceProxyXA;
+
+ public XAModeExecutor(BenchmarkConfig config) {
+ super(config);
+ }
+
+ private boolean isRealMode() {
+ return config.getBranches() > 0;
+ }
+
+ @Override
+ public void init() {
+ if (isRealMode()) {
+ LOGGER.info("Initializing XA mode executor (MySQL XA via
Testcontainers)");
+ initRealMode();
+ } else {
+ LOGGER.info("XA mode executor initialized (empty transaction
mode)");
+ }
+ }
+
+ private void initRealMode() {
+ try {
+ startMySQLContainer();
+ createDataSource();
+ initDatabase();
+ dataSourceProxyXA = new DataSourceProxyXA(rawDataSource);
+ LOGGER.info("XA DataSourceProxy initialized, dbType: {}",
dataSourceProxyXA.getDbType());
+ LOGGER.info("Real XA mode executor initialized with {} accounts",
BenchmarkConstants.ACCOUNT_COUNT);
+ } catch (Exception e) {
+ cleanupOnFailure();
+ throw e instanceof RuntimeException
+ ? (RuntimeException) e
+ : new RuntimeException("Failed to initialize real XA mode
executor", e);
+ }
+ }
+
+ private void cleanupOnFailure() {
+ LOGGER.warn("Cleaning up XA mode resources due to initialization
failure");
+ if (rawDataSource != null && !rawDataSource.isClosed()) {
+ try {
+ rawDataSource.close();
+ } catch (Exception e) {
+ LOGGER.warn("Failed to close XA DataSource during cleanup", e);
+ }
+ }
+ if (mysqlContainer != null && mysqlContainer.isRunning()) {
+ try {
+ mysqlContainer.stop();
+ } catch (Exception e) {
+ LOGGER.warn("Failed to stop XA MySQL container during
cleanup", e);
+ }
+ }
+ }
+
+ private void startMySQLContainer() {
+ LOGGER.info("Starting MySQL container for XA mode...");
+ mysqlContainer = new
MySQLContainer<>(DockerImageName.parse("mysql:8.0"))
+ .withDatabaseName("benchmark_xa")
+ .withUsername("test")
+ .withPassword("test")
+ .withCommand("--character-set-server=utf8mb4",
"--collation-server=utf8mb4_unicode_ci");
+ mysqlContainer.start();
+ LOGGER.info("XA MySQL container started: {}",
mysqlContainer.getJdbcUrl());
+ }
+
+ private void createDataSource() {
+ HikariConfig hikariConfig = new HikariConfig();
+ hikariConfig.setJdbcUrl(mysqlContainer.getJdbcUrl());
+ hikariConfig.setUsername(mysqlContainer.getUsername());
+ hikariConfig.setPassword(mysqlContainer.getPassword());
+ hikariConfig.setDriverClassName("com.mysql.cj.jdbc.Driver");
+ hikariConfig.setMaximumPoolSize(config.getThreads() * 2);
+ hikariConfig.setMinimumIdle(Math.max(1, config.getThreads()));
+ hikariConfig.setConnectionTimeout(30000);
+ hikariConfig.setIdleTimeout(600000);
+ hikariConfig.setMaxLifetime(1800000);
+ rawDataSource = new HikariDataSource(hikariConfig);
+ LOGGER.info("XA HikariCP DataSource created");
+ }
+
+ private void initDatabase() {
+ try (Connection conn = rawDataSource.getConnection();
+ Statement stmt = conn.createStatement()) {
+ stmt.execute("CREATE TABLE IF NOT EXISTS xa_accounts ("
+ + "id BIGINT PRIMARY KEY, "
+ + "balance INT NOT NULL, "
+ + "updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON
UPDATE CURRENT_TIMESTAMP"
+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
+
+ stmt.execute("TRUNCATE TABLE xa_accounts");
+ try (PreparedStatement pstmt =
+ conn.prepareStatement("INSERT INTO xa_accounts (id,
balance) VALUES (?, ?)")) {
+ for (int i = 1; i <= BenchmarkConstants.ACCOUNT_COUNT; i++) {
+ pstmt.setLong(1, i);
+ pstmt.setInt(2, BenchmarkConstants.INITIAL_BALANCE);
+ pstmt.addBatch();
+ if (i % 100 == 0) {
+ pstmt.executeBatch();
+ }
+ }
+ pstmt.executeBatch();
+ }
+
+ LOGGER.info(
+ "XA database initialized: {} accounts with balance {}",
+ BenchmarkConstants.ACCOUNT_COUNT,
+ BenchmarkConstants.INITIAL_BALANCE);
+ } catch (SQLException e) {
+ throw new RuntimeException("Failed to initialize XA database", e);
+ }
+ }
+
+ @Override
+ protected String getTransactionName() {
+ return isRealMode() ? "benchmark-real-xa-tx" : "benchmark-xa-tx";
+ }
+
+ @Override
+ protected int getBranchCount() {
+ return config.getBranches();
+ }
+
+ @Override
+ protected void executeBusinessLogic() throws Exception {
+ if (!isRealMode()) {
+ return;
+ }
+ executeBranchOperations(config.getBranches());
+ }
+
+ private void executeBranchOperations(int branchCount) throws SQLException {
+ for (int i = 0; i < branchCount; i++) {
+ executeSingleBranch();
+ }
+ }
+
+ private void executeSingleBranch() throws SQLException {
+ try (Connection conn = dataSourceProxyXA.getConnection()) {
+ conn.setAutoCommit(false);
+
+ SecureRandom secureRandom = SECURE_RANDOM.get();
+ long fromAccount =
secureRandom.nextInt(BenchmarkConstants.ACCOUNT_COUNT) + 1L;
+ long toAccount = (fromAccount % BenchmarkConstants.ACCOUNT_COUNT)
+ 1L;
+ int amount =
secureRandom.nextInt(BenchmarkConstants.MAX_TRANSFER_AMOUNT)
+ + BenchmarkConstants.MIN_TRANSFER_AMOUNT;
+
+ try (PreparedStatement pstmt =
+ conn.prepareStatement("UPDATE xa_accounts SET balance =
balance - ? WHERE id = ?")) {
+ pstmt.setInt(1, amount);
+ pstmt.setLong(2, fromAccount);
+ pstmt.executeUpdate();
+ }
+
+ try (PreparedStatement pstmt =
+ conn.prepareStatement("UPDATE xa_accounts SET balance =
balance + ? WHERE id = ?")) {
+ pstmt.setInt(1, amount);
+ pstmt.setLong(2, toAccount);
+ pstmt.executeUpdate();
+ }
+
+ conn.commit();
+ }
+ }
+
+ @Override
+ protected Logger getLogger() {
+ return LOGGER;
+ }
+
+ @Override
+ public void destroy() {
+ if (rawDataSource != null && !rawDataSource.isClosed()) {
+ rawDataSource.close();
+ LOGGER.info("XA DataSource closed");
+ }
+ if (mysqlContainer != null && mysqlContainer.isRunning()) {
+ mysqlContainer.stop();
+ LOGGER.info("XA MySQL container stopped");
+ }
+ dataSourceProxyXA = null;
+ LOGGER.info("XA mode executor destroyed");
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]