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]

Reply via email to