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

yiguolei pushed a commit to branch branch-4.2
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.2 by this push:
     new 6704c8400b2 branch-4.2: [fix](cloud) Match transaction coordinators by 
stable backend ID #68756 (#69345)
6704c8400b2 is described below

commit 6704c8400b2cbefa45dee661a8b23cf42bdd6825
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Wed Oct 21 10:48:54 2026 +0800

    branch-4.2: [fix](cloud) Match transaction coordinators by stable backend 
ID #68756 (#69345)
    
    Cherry-picked from #68756
    
    Co-authored-by: Tran Ngoc Tuan 
<[email protected]>
---
 cloud/src/meta-service/meta_service_txn.cpp        |  15 ++-
 cloud/test/meta_service_test.cpp                   |  70 +++++++++++++-
 .../doris/transaction/GlobalTransactionMgr.java    |   4 +-
 .../doris/transaction/CloudTxnCoordinatorTest.java | 102 +++++++++++++++++++++
 4 files changed, 182 insertions(+), 9 deletions(-)

diff --git a/cloud/src/meta-service/meta_service_txn.cpp 
b/cloud/src/meta-service/meta_service_txn.cpp
index b003a5baa14..3e6026ec35a 100644
--- a/cloud/src/meta-service/meta_service_txn.cpp
+++ b/cloud/src/meta-service/meta_service_txn.cpp
@@ -4300,9 +4300,12 @@ void 
MetaServiceImpl::abort_txn_with_coordinator(::google::protobuf::RpcControll
                 return;
             }
             const auto& coordinate = info_pb.coordinator();
+            // Backend IDs survive address changes; use the address only for 
legacy unknown IDs.
+            const bool same_coordinator = (coordinate.id() != 0 && 
request->id() != 0)
+                                                  ? coordinate.id() == 
request->id()
+                                                  : coordinate.ip() == 
request->ip();
             if (info_pb.status() == TxnStatusPB::TXN_STATUS_PREPARED &&
-                coordinate.sourcetype() == TXN_SOURCE_TYPE_BE && 
coordinate.id() == request->id() &&
-                coordinate.ip() == request->ip() &&
+                coordinate.sourcetype() == TXN_SOURCE_TYPE_BE && 
same_coordinator &&
                 coordinate.start_time() < request->start_time()) {
                 need_commit = true;
                 TxnInfoPB return_txn_info;
@@ -4404,10 +4407,12 @@ void MetaServiceImpl::get_prepare_txn_by_coordinator(
             return TxnErrorCode::TXN_INVALID_DATA;
         }
         const auto& coordinate = info_pb.coordinator();
+        // Backend IDs survive address changes; use the address only for 
legacy unknown IDs.
+        const bool same_coordinator = (coordinate.id() != 0 && request->id() 
!= 0)
+                                              ? coordinate.id() == 
request->id()
+                                              : coordinate.ip() == 
request->ip();
         bool matches = info_pb.status() == TxnStatusPB::TXN_STATUS_PREPARED &&
-                       coordinate.sourcetype() == TXN_SOURCE_TYPE_BE &&
-                       coordinate.ip() == request->ip() &&
-                       (coordinate.id() == 0 || coordinate.id() == 
request->id());
+                       coordinate.sourcetype() == TXN_SOURCE_TYPE_BE && 
same_coordinator;
         if (matches && has_start_time_filter) {
             matches = coordinate.start_time() < request->start_time();
         }
diff --git a/cloud/test/meta_service_test.cpp b/cloud/test/meta_service_test.cpp
index e0d91a4c863..6738b498cc1 100644
--- a/cloud/test/meta_service_test.cpp
+++ b/cloud/test/meta_service_test.cpp
@@ -2922,7 +2922,25 @@ TEST(MetaServiceTest, AbortTxnWithCoordinatorTest) {
     AbortTxnWithCoordinatorResponse abort_txn_resp;
 
     abort_txn_req.set_id(coordinator_id);
-    abort_txn_req.set_ip(host);
+    abort_txn_req.set_cloud_unique_id(cloud_unique_id);
+    abort_txn_req.set_ip("192.168.100.200:9050"); // Same backend, changed 
address.
+
+    // The current backend process must not be aborted even when its address 
changed.
+    abort_txn_req.set_start_time(cur_time);
+    meta_service->abort_txn_with_coordinator(&abort_txn_cntl, &abort_txn_req, 
&abort_txn_resp,
+                                             nullptr);
+    ASSERT_EQ(abort_txn_resp.status().code(), MetaServiceCode::OK);
+    {
+        std::unique_ptr<Transaction> txn;
+        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), 
TxnErrorCode::TXN_OK);
+        std::string value;
+        ASSERT_EQ(txn->get(txn_info_key({mock_instance, db_id, txn_id}), 
&value),
+                  TxnErrorCode::TXN_OK);
+        TxnInfoPB info;
+        ASSERT_TRUE(info.ParseFromString(value));
+        ASSERT_EQ(info.status(), TxnStatusPB::TXN_STATUS_PREPARED);
+    }
+    abort_txn_resp.Clear();
     abort_txn_req.set_start_time(cur_time + 3600);
 
     // first time to check txn conflict
@@ -3062,6 +3080,49 @@ TEST(MetaServiceTest, GetPrepareTxnByCoordinatorTest) {
 
         ASSERT_EQ(resp.status().code(), MetaServiceCode::INVALID_ARGUMENT);
     }
+    // A rescheduled backend retains its ID while its address changes. Test 
both scan paths.
+    {
+        const bool original_mode = 
config::enable_get_prepare_txn_by_coordinator_by_running_key;
+        DORIS_CLOUD_DEFER {
+            config::enable_get_prepare_txn_by_coordinator_by_running_key = 
original_mode;
+        };
+        for (bool scan_by_running_key : {false, true}) {
+            config::enable_get_prepare_txn_by_coordinator_by_running_key = 
scan_by_running_key;
+            brpc::Controller cntl;
+            GetPrepareTxnByCoordinatorRequest req;
+            GetPrepareTxnByCoordinatorResponse resp;
+            req.set_cloud_unique_id(cloud_unique_id);
+            req.set_id(coordinator_id);
+            req.set_ip("192.168.100.200:9050");
+            meta_service->get_prepare_txn_by_coordinator(&cntl, &req, &resp, 
nullptr);
+            ASSERT_EQ(resp.status().code(), MetaServiceCode::OK);
+            ASSERT_EQ(resp.txn_infos_size(), 5);
+
+            // Startup time still protects transactions belonging to the 
current process.
+            resp.Clear();
+            req.set_start_time(cur_time);
+            meta_service->get_prepare_txn_by_coordinator(&cntl, &req, &resp, 
nullptr);
+            ASSERT_EQ(resp.status().code(), MetaServiceCode::OK);
+            ASSERT_EQ(resp.txn_infos_size(), 0);
+
+            // A reused address with a different known ID is a different 
coordinator.
+            resp.Clear();
+            req.clear_start_time();
+            req.set_id(coordinator_id + 1);
+            req.set_ip(host);
+            meta_service->get_prepare_txn_by_coordinator(&cntl, &req, &resp, 
nullptr);
+            ASSERT_EQ(resp.status().code(), MetaServiceCode::OK);
+            ASSERT_EQ(resp.txn_infos_size(), 0);
+
+            // An unknown request ID retains address-based compatibility.
+            resp.Clear();
+            req.set_id(0);
+            meta_service->get_prepare_txn_by_coordinator(&cntl, &req, &resp, 
nullptr);
+            ASSERT_EQ(resp.status().code(), MetaServiceCode::OK);
+            ASSERT_EQ(resp.txn_infos_size(), 5);
+        }
+    }
+
     // Running entries also include precommitted/lazy-committed and expired 
transactions.
     {
         std::unique_ptr<Transaction> txn;
@@ -3376,9 +3437,11 @@ TEST(MetaServiceTest, 
GetPrepareTxnByCoordinatorScanModeTest) {
         req.set_start_time(150);
         meta_service->get_prepare_txn_by_coordinator(&cntl, &req, &resp, 
nullptr);
         ASSERT_EQ(resp.status().code(), MetaServiceCode::OK);
-        ASSERT_EQ(resp.txn_infos_size(), 2);
+        // Txn 3 has the same backend ID despite its changed address.
+        ASSERT_EQ(resp.txn_infos_size(), 3);
         EXPECT_EQ(resp.txn_infos(0).txn_id(), 1);
         EXPECT_EQ(resp.txn_infos(1).txn_id(), 2);
+        EXPECT_EQ(resp.txn_infos(2).txn_id(), 3);
     }
 
     // Skip malformed running keys on the first, middle and last data pages.
@@ -3412,9 +3475,10 @@ TEST(MetaServiceTest, 
GetPrepareTxnByCoordinatorScanModeTest) {
         req.set_start_time(150);
         meta_service->get_prepare_txn_by_coordinator(&cntl, &req, &resp, 
nullptr);
         ASSERT_EQ(resp.status().code(), MetaServiceCode::OK);
-        ASSERT_EQ(resp.txn_infos_size(), 2);
+        ASSERT_EQ(resp.txn_infos_size(), 3);
         EXPECT_EQ(resp.txn_infos(0).txn_id(), 1);
         EXPECT_EQ(resp.txn_infos(1).txn_id(), 2);
+        EXPECT_EQ(resp.txn_infos(2).txn_id(), 3);
         EXPECT_EQ(pages, 9 + 3 + 1);
     }
 
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/transaction/GlobalTransactionMgr.java
 
b/fe/fe-core/src/main/java/org/apache/doris/transaction/GlobalTransactionMgr.java
index bd1ddf2ba6a..3a0b30da9c8 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/transaction/GlobalTransactionMgr.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/transaction/GlobalTransactionMgr.java
@@ -491,8 +491,10 @@ public class GlobalTransactionMgr implements 
GlobalTransactionMgrIface {
         } else if (coordinator.sourceType == 
TransactionState.TxnSourceType.BE) {
             Backend be = Env.getCurrentSystemInfo().getBackend(coordinator.id);
             if (be != null) {
+                // Cloud backend registration is stable across pod IP changes 
and FQDN addressing.
+                boolean sameInstance = Config.isCloudMode() || 
be.getHost().equals(coordinator.ip);
                 offline = false;
-                if (be.getHost().equals(coordinator.ip) && 
(be.getLastStartTime() > coordinator.startTime
+                if (sameInstance && (be.getLastStartTime() > 
coordinator.startTime
                         || (!be.isAlive() && System.currentTimeMillis() - 
be.getLastUpdateMs()
                                     >= 
Config.abort_txn_after_lost_heartbeat_time_second * 1000L))) {
                     return true;
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/transaction/CloudTxnCoordinatorTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/transaction/CloudTxnCoordinatorTest.java
new file mode 100644
index 00000000000..675500e7f3d
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/transaction/CloudTxnCoordinatorTest.java
@@ -0,0 +1,102 @@
+// 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.doris.transaction;
+
+import org.apache.doris.catalog.Env;
+import org.apache.doris.common.Config;
+import org.apache.doris.system.Backend;
+import org.apache.doris.system.SystemInfoService;
+import org.apache.doris.transaction.TransactionState.LoadJobSourceType;
+import org.apache.doris.transaction.TransactionState.TxnCoordinator;
+import org.apache.doris.transaction.TransactionState.TxnSourceType;
+
+import com.google.common.collect.Lists;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+
+public class CloudTxnCoordinatorTest {
+    private String previousDeployMode;
+    private String previousCloudUniqueId;
+    private MockedStatic<Env> mockedEnv;
+    private Backend backend;
+    private TransactionState transaction;
+
+    @BeforeEach
+    public void setUp() {
+        previousDeployMode = Config.deploy_mode;
+        previousCloudUniqueId = Config.cloud_unique_id;
+        Config.deploy_mode = "cloud";
+        Config.cloud_unique_id = "";
+        backend = Mockito.mock(Backend.class);
+        Mockito.when(backend.getHost()).thenReturn("be-0.example");
+        Mockito.when(backend.isAlive()).thenReturn(true);
+        Mockito.when(backend.getLastStartTime()).thenReturn(100L);
+        SystemInfoService systemInfo = Mockito.mock(SystemInfoService.class);
+        Mockito.when(systemInfo.getBackend(123L)).thenReturn(backend);
+        mockedEnv = Mockito.mockStatic(Env.class);
+        mockedEnv.when(Env::getCurrentSystemInfo).thenReturn(systemInfo);
+        transaction = new TransactionState(1L, Lists.newArrayList(2L), 3L,
+                "coordinator", null, LoadJobSourceType.BACKEND_STREAMING,
+                new TxnCoordinator(TxnSourceType.BE, 123L, "old-pod-ip", 
100L), -1L, 1000L);
+    }
+
+    @AfterEach
+    public void tearDown() {
+        mockedEnv.close();
+        Config.deploy_mode = previousDeployMode;
+        Config.cloud_unique_id = previousCloudUniqueId;
+    }
+
+    @Test
+    public void testRestartWithDifferentAddress() {
+        Mockito.when(backend.getLastStartTime()).thenReturn(101L);
+        
Assertions.assertTrue(GlobalTransactionMgr.checkFailedTxnsByCoordinator(transaction));
+    }
+
+    @Test
+    public void testCurrentProcessWithDifferentAddress() {
+        
Assertions.assertFalse(GlobalTransactionMgr.checkFailedTxnsByCoordinator(transaction));
+    }
+
+    @Test
+    public void testLostHeartbeatWithDifferentAddress() {
+        Mockito.when(backend.isAlive()).thenReturn(false);
+        Mockito.when(backend.getLastUpdateMs()).thenReturn(0L);
+        
Assertions.assertTrue(GlobalTransactionMgr.checkFailedTxnsByCoordinator(transaction));
+    }
+
+    @Test
+    public void testRecentHeartbeatWithDifferentAddress() {
+        Mockito.when(backend.isAlive()).thenReturn(false);
+        
Mockito.when(backend.getLastUpdateMs()).thenReturn(System.currentTimeMillis());
+        
Assertions.assertFalse(GlobalTransactionMgr.checkFailedTxnsByCoordinator(transaction));
+    }
+
+    @Test
+    public void testNonCloudRetainsAddressCheck() {
+        Config.deploy_mode = "local";
+        Mockito.when(backend.getLastStartTime()).thenReturn(101L);
+        
Assertions.assertFalse(GlobalTransactionMgr.checkFailedTxnsByCoordinator(transaction));
+        Mockito.when(backend.getHost()).thenReturn("old-pod-ip");
+        
Assertions.assertTrue(GlobalTransactionMgr.checkFailedTxnsByCoordinator(transaction));
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to