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

mymeiyi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new 37c97fef5ec [fix](cloud) fix get_prepare_txn_by_coordinator method 
(#67761)
37c97fef5ec is described below

commit 37c97fef5ec9e39666cf6304a2901067c01c8b3c
Author: meiyi <[email protected]>
AuthorDate: Wed Sep 16 15:46:56 2026 +0800

    [fix](cloud) fix get_prepare_txn_by_coordinator method (#67761)
    
    `get_prepare_txn_by_coordinator` scans all `txn_info_key` records in a
    single KV transaction, which may frequently fail with `KV_TXN_TOO_OLD`
    when there are many records. Creating a new transaction and resuming
    from the failed page addresses the expiry issue, but scanning all
    transaction info records remains expensive.
    
    Scan `txn_running_key` records instead and batch-read the corresponding
    transaction info in groups of 128, while retaining the ability to resume
    from the failed page after transaction expiry.
---
 cloud/src/common/config.h                   |   4 +
 cloud/src/meta-service/meta_service_txn.cpp | 180 +++++++----
 cloud/src/meta-store/mem_txn_kv.cpp         |   2 -
 cloud/test/meta_service_test.cpp            | 448 ++++++++++++++++++++++++++++
 4 files changed, 574 insertions(+), 60 deletions(-)

diff --git a/cloud/src/common/config.h b/cloud/src/common/config.h
index 4e4e6340fd4..fcce2b76b4a 100644
--- a/cloud/src/common/config.h
+++ b/cloud/src/common/config.h
@@ -349,6 +349,10 @@ CONF_Bool(delete_bitmap_enable_retry_txn_conflict, "true");
 // more reasonable.
 CONF_mInt64(max_txn_commit_byte, "7340032");
 
+// true: scan txn_running_key entries and fetch transaction info via the 
corresponding txn_info_key.
+// false: scan txn_info_key entries directly; these usually far outnumber 
txn_running_key entries.
+CONF_mBool(enable_get_prepare_txn_by_coordinator_by_running_key, "true");
+
 CONF_Bool(enable_cloud_txn_lazy_commit, "true");
 CONF_Int32(txn_lazy_commit_rowsets_thresold, "1000");
 CONF_Int32(txn_lazy_commit_num_threads, "8");
diff --git a/cloud/src/meta-service/meta_service_txn.cpp 
b/cloud/src/meta-service/meta_service_txn.cpp
index 76963cb0627..de3cfb26c78 100644
--- a/cloud/src/meta-service/meta_service_txn.cpp
+++ b/cloud/src/meta-service/meta_service_txn.cpp
@@ -4620,6 +4620,25 @@ void 
MetaServiceImpl::abort_txn_with_coordinator(::google::protobuf::RpcControll
     }
 }
 
+std::string get_txn_info_key_from_txn_running_key(std::string_view 
txn_running_key) {
+    std::string conflict_txn_info_key;
+    std::vector<std::tuple<std::variant<int64_t, std::string>, int, int>> out;
+    txn_running_key.remove_prefix(1);
+    int ret = decode_key(&txn_running_key, &out);
+    if (ret != 0) [[unlikely]] {
+        // decode version key error means this is something wrong,
+        // we can not continue this txn
+        LOG(WARNING) << "failed to decode key, ret=" << ret << " key=" << 
hex(txn_running_key);
+    } else {
+        DCHECK(out.size() == 5) << " key=" << hex(txn_running_key) << " " << 
out.size();
+        const std::string& decode_instance_id = 
std::get<1>(std::get<0>(out[1]));
+        int64_t db_id = std::get<0>(std::get<0>(out[3]));
+        int64_t txn_id = std::get<0>(std::get<0>(out[4]));
+        conflict_txn_info_key = txn_info_key({decode_instance_id, db_id, 
txn_id});
+    }
+    return conflict_txn_info_key;
+}
+
 void MetaServiceImpl::get_prepare_txn_by_coordinator(
         ::google::protobuf::RpcController* controller,
         const GetPrepareTxnByCoordinatorRequest* request,
@@ -4641,9 +4660,13 @@ void MetaServiceImpl::get_prepare_txn_by_coordinator(
         return;
     }
     RPC_RATE_LIMIT(get_prepare_txn_by_coordinator);
-    std::string begin_info_key = txn_info_key({instance_id, 0, 0});
-    std::string end_info_key = txn_info_key({instance_id, INT64_MAX, 
INT64_MAX});
-    LOG(INFO) << "begin_info_key:" << hex(begin_info_key) << " end_info_key:" 
<< hex(end_info_key);
+    const bool scan_by_running_key = 
config::enable_get_prepare_txn_by_coordinator_by_running_key;
+    std::string begin_key = scan_by_running_key ? 
txn_running_key({instance_id, 0, 0})
+                                                : txn_info_key({instance_id, 
0, 0});
+    std::string end_key = scan_by_running_key ? txn_running_key({instance_id, 
INT64_MAX, INT64_MAX})
+                                              : txn_info_key({instance_id, 
INT64_MAX, INT64_MAX});
+    LOG(INFO) << "begin_key:" << hex(begin_key) << " end_key:" << hex(end_key)
+              << " scan_by_running_key=" << scan_by_running_key;
 
     TxnErrorCode err = txn_kv_->create_txn(&txn);
     if (err != TxnErrorCode::TXN_OK) {
@@ -4653,75 +4676,116 @@ void MetaServiceImpl::get_prepare_txn_by_coordinator(
     }
     std::unique_ptr<RangeGetIterator> it;
     int32_t result_count = 0;
-    int64_t total_iteration_cnt = 0;
+    int64_t scanned_count = 0;
     bool has_start_time_filter = request->has_start_time();
 
-    do {
-        err = txn->get(begin_info_key, end_info_key, &it, true);
-        if (err != TxnErrorCode::TXN_OK) {
-            code = cast_as<ErrCategory::READ>(err);
-            ss << "failed to get txn info. err=" << err;
-            msg = ss.str();
+    auto process_txn_info = [&](std::string_view key, std::string_view value) 
-> TxnErrorCode {
+        scanned_count++;
+        VLOG_DEBUG << "check txn info txn_info_key=" << hex(key);
+        TxnInfoPB info_pb;
+        if (!info_pb.ParseFromArray(value.data(), value.size())) {
+            code = MetaServiceCode::PROTOBUF_PARSE_ERR;
+            msg = "malformed txn info, key=" + hex(key);
             LOG(WARNING) << msg;
-            return;
+            return TxnErrorCode::TXN_INVALID_DATA;
+        }
+        const auto& coordinate = info_pb.coordinator();
+        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());
+        if (matches && has_start_time_filter) {
+            matches = coordinate.start_time() < request->start_time();
+        }
+        if (matches) {
+            TxnInfoPB* txn_info = response->add_txn_infos();
+            txn_info->CopyFrom(info_pb);
+            result_count++;
         }
+        return TxnErrorCode::TXN_OK;
+    };
 
+    // Each txn_info value can be much larger than its running index entry.
+    constexpr int batch_size = 128;
+    const int scan_batch_size = scan_by_running_key ? batch_size : 
RangeGetOptions().batch_limit;
+    auto read_page = [&]() -> TxnErrorCode {
+        auto ret = txn->get(begin_key, end_key, &it, true, scan_batch_size);
+        TEST_SYNC_POINT_CALLBACK("get_prepare_txn_by_coordinator::range_get", 
&ret);
+        if (ret != TxnErrorCode::TXN_OK) {
+            return ret;
+        }
+        std::vector<std::string> info_keys;
+        info_keys.reserve(scan_by_running_key ? it->size() : 0);
         while (it->has_next()) {
-            total_iteration_cnt++;
-            auto [k, v] = it->next();
-            VLOG_DEBUG << "check txn info txn_info_key=" << hex(k);
-            TxnInfoPB info_pb;
-            if (!info_pb.ParseFromArray(v.data(), v.size())) {
-                code = MetaServiceCode::PROTOBUF_PARSE_ERR;
-                ss << "malformed txn running info";
-                msg = ss.str();
-                ss << " key=" << hex(k);
-                LOG(WARNING) << ss.str();
-                return;
+            auto [key, value] = it->next();
+            if (scan_by_running_key) {
+                auto info_key = get_txn_info_key_from_txn_running_key(key);
+                if (info_key.empty()) {
+                    continue;
+                }
+                info_keys.push_back(std::move(info_key));
+            } else {
+                ret = process_txn_info(key, value);
+                if (ret != TxnErrorCode::TXN_OK) {
+                    return ret;
+                }
             }
-            const auto& coordinate = info_pb.coordinator();
-            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());
-            if (matches && has_start_time_filter) {
-                matches = coordinate.start_time() < request->start_time();
+        }
+        if (!scan_by_running_key) {
+            return TxnErrorCode::TXN_OK;
+        }
+        std::vector<std::optional<std::string>> info_values;
+        ret = txn->batch_get(&info_values, info_keys, 
Transaction::BatchGetOptions(true));
+        TEST_SYNC_POINT_CALLBACK("get_prepare_txn_by_coordinator::batch_get", 
&ret, &info_values);
+        if (ret != TxnErrorCode::TXN_OK) {
+            return ret;
+        }
+        for (size_t i = 0; i < info_keys.size(); ++i) {
+            if (!info_values[i].has_value()) {
+                code = MetaServiceCode::TXN_ID_NOT_FOUND;
+                msg = "missing txn info for running txn, key=" + 
hex(info_keys[i]);
+                LOG(WARNING) << msg;
+                return TxnErrorCode::TXN_KEY_NOT_FOUND;
             }
-
-            if (matches) {
-                TxnInfoPB* txn_info = response->add_txn_infos();
-                txn_info->CopyFrom(info_pb);
-                result_count++;
+            ret = process_txn_info(info_keys[i], *info_values[i]);
+            if (ret != TxnErrorCode::TXN_OK) {
+                return ret;
             }
+        }
+        return TxnErrorCode::TXN_OK;
+    };
 
-            if (!it->has_next()) {
-                begin_info_key = k;
+    do {
+        err = read_page();
+        if (err == TxnErrorCode::TXN_TOO_OLD) {
+            stats.get_bytes += txn->get_bytes();
+            stats.get_counter += txn->num_get_keys();
+            txn.reset();
+            err = txn_kv_->create_txn(&txn);
+            if (err != TxnErrorCode::TXN_OK) {
+                msg = "failed to create txn";
+                code = cast_as<ErrCategory::CREATE>(err);
+                return;
             }
+            err = read_page();
+        }
+        if (code != MetaServiceCode::OK) {
+            return;
+        }
+        if (err != TxnErrorCode::TXN_OK) {
+            code = cast_as<ErrCategory::READ>(err);
+            ss << "get_prepare_txn_by_coordinator: failed to get txn info. 
err=" << err;
+            msg = ss.str();
+            LOG(WARNING) << msg;
+            return;
         }
-        begin_info_key.push_back('\x00'); // Update to next smallest key for 
iteration
-    } while (it->more());
 
-    LOG(INFO) << "get_prepare_txn_by_coordinator: found " << result_count << " 
transactions"
-              << " total iteration count: " << total_iteration_cnt;
-}
+        begin_key = it->next_begin_key();
+    } while (it->more());
 
-std::string get_txn_info_key_from_txn_running_key(std::string_view 
txn_running_key) {
-    std::string conflict_txn_info_key;
-    std::vector<std::tuple<std::variant<int64_t, std::string>, int, int>> out;
-    txn_running_key.remove_prefix(1);
-    int ret = decode_key(&txn_running_key, &out);
-    if (ret != 0) [[unlikely]] {
-        // decode version key error means this is something wrong,
-        // we can not continue this txn
-        LOG(WARNING) << "failed to decode key, ret=" << ret << " key=" << 
hex(txn_running_key);
-    } else {
-        DCHECK(out.size() == 5) << " key=" << hex(txn_running_key) << " " << 
out.size();
-        const std::string& decode_instance_id = 
std::get<1>(std::get<0>(out[1]));
-        int64_t db_id = std::get<0>(std::get<0>(out[3]));
-        int64_t txn_id = std::get<0>(std::get<0>(out[4]));
-        conflict_txn_info_key = txn_info_key({decode_instance_id, db_id, 
txn_id});
-    }
-    return conflict_txn_info_key;
+    LOG(INFO) << "get_prepare_txn_by_coordinator: scanned_count=" << 
scanned_count
+              << " matched_count=" << result_count
+              << " scan_by_running_key=" << scan_by_running_key;
 }
 
 void MetaServiceImpl::check_txn_conflict(::google::protobuf::RpcController* 
controller,
diff --git a/cloud/src/meta-store/mem_txn_kv.cpp 
b/cloud/src/meta-store/mem_txn_kv.cpp
index 81755043ea1..e4f74337db5 100644
--- a/cloud/src/meta-store/mem_txn_kv.cpp
+++ b/cloud/src/meta-store/mem_txn_kv.cpp
@@ -815,8 +815,6 @@ TxnErrorCode 
Transaction::batch_get(std::vector<std::optional<std::string>>* res
         auto ret = inner_get(k, &val, opts.snapshot);
         ret == TxnErrorCode::TXN_OK ? res->push_back(val) : 
res->push_back(std::nullopt);
     }
-    kv_->get_count_ += keys.size();
-    num_get_keys_ += keys.size();
     return TxnErrorCode::TXN_OK;
 }
 
diff --git a/cloud/test/meta_service_test.cpp b/cloud/test/meta_service_test.cpp
index 91c28d8e7ac..ab703c9a529 100644
--- a/cloud/test/meta_service_test.cpp
+++ b/cloud/test/meta_service_test.cpp
@@ -20,11 +20,13 @@
 #include <brpc/controller.h>
 #include <bvar/window.h>
 #include <fmt/core.h>
+#include <fmt/ranges.h>
 #include <gen_cpp/cloud.pb.h>
 #include <gen_cpp/olap_file.pb.h>
 #include <google/protobuf/repeated_field.h>
 #include <gtest/gtest.h>
 
+#include <algorithm>
 #include <atomic>
 #include <chrono>
 #include <condition_variable>
@@ -2872,6 +2874,11 @@ TEST(MetaServiceTest, AbortTxnWithCoordinatorTest) {
 
 TEST(MetaServiceTest, GetPrepareTxnByCoordinatorTest) {
     auto meta_service = get_meta_service();
+    const bool original_mode = 
config::enable_get_prepare_txn_by_coordinator_by_running_key;
+    config::enable_get_prepare_txn_by_coordinator_by_running_key = true;
+    DORIS_CLOUD_DEFER {
+        config::enable_get_prepare_txn_by_coordinator_by_running_key = 
original_mode;
+    };
 
     const int64_t db_id = 888;
     const int64_t table_id = 999;
@@ -2977,6 +2984,447 @@ TEST(MetaServiceTest, GetPrepareTxnByCoordinatorTest) {
 
         ASSERT_EQ(resp.status().code(), MetaServiceCode::INVALID_ARGUMENT);
     }
+    // Running entries also include precommitted/lazy-committed and expired 
transactions.
+    {
+        std::unique_ptr<Transaction> txn;
+        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), 
TxnErrorCode::TXN_OK);
+        for (int i = 0; i < 2; ++i) {
+            const auto key = txn_info_key({mock_instance, db_id, txn_ids[i]});
+            std::string value;
+            ASSERT_EQ(txn->get(key, &value), TxnErrorCode::TXN_OK);
+            TxnInfoPB info;
+            ASSERT_TRUE(info.ParseFromString(value));
+            info.set_status(i == 0 ? TxnStatusPB::TXN_STATUS_PRECOMMITTED
+                                   : TxnStatusPB::TXN_STATUS_COMMITTED);
+            txn->put(key, info.SerializeAsString());
+        }
+        TxnRunningPB expired;
+        expired.set_timeout_time(0);
+        txn->put(txn_running_key({mock_instance, db_id, txn_ids[2]}), 
expired.SerializeAsString());
+        ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
+
+        brpc::Controller cntl;
+        GetPrepareTxnByCoordinatorRequest req;
+        GetPrepareTxnByCoordinatorResponse resp;
+        req.set_cloud_unique_id(cloud_unique_id);
+        req.set_id(coordinator_id);
+        req.set_ip(host);
+        req.set_start_time(cur_time + 100);
+        meta_service->get_prepare_txn_by_coordinator(&cntl, &req, &resp, 
nullptr);
+        ASSERT_EQ(resp.status().code(), MetaServiceCode::OK);
+        ASSERT_EQ(resp.txn_infos_size(), 3);
+        EXPECT_EQ(resp.txn_infos(0).txn_id(), txn_ids[2]);
+
+        // Retry the entire page if its snapshot expires while a candidate is 
completed/recycled.
+        auto sp = SyncPoint::get_instance();
+        DORIS_CLOUD_DEFER {
+            sp->disable_processing();
+            sp->clear_all_call_backs();
+        };
+        int info_batches = 0;
+        sp->set_call_back("get_prepare_txn_by_coordinator::batch_get", 
[&](auto&& args) {
+            if (++info_batches == 1) {
+                std::unique_ptr<Transaction> update;
+                ASSERT_EQ(meta_service->txn_kv()->create_txn(&update), 
TxnErrorCode::TXN_OK);
+                update->remove(txn_running_key({mock_instance, db_id, 
txn_ids[2]}));
+                update->remove(txn_info_key({mock_instance, db_id, 
txn_ids[2]}));
+                ASSERT_EQ(update->commit(), TxnErrorCode::TXN_OK);
+                *try_any_cast<TxnErrorCode*>(args[0]) = 
TxnErrorCode::TXN_TOO_OLD;
+            }
+        });
+        sp->enable_processing();
+        resp.Clear();
+        meta_service->get_prepare_txn_by_coordinator(&cntl, &req, &resp, 
nullptr);
+        ASSERT_EQ(resp.status().code(), MetaServiceCode::OK);
+        ASSERT_EQ(info_batches, 2);
+        ASSERT_EQ(resp.txn_infos_size(), 2);
+        EXPECT_EQ(resp.txn_infos(0).txn_id(), txn_ids[3]);
+        EXPECT_EQ(resp.txn_infos(1).txn_id(), txn_ids[4]);
+
+        // A running entry without its info in the same snapshot is an error.
+        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), 
TxnErrorCode::TXN_OK);
+        txn->remove(txn_info_key({mock_instance, db_id, txn_ids[3]}));
+        ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
+        resp.Clear();
+        meta_service->get_prepare_txn_by_coordinator(&cntl, &req, &resp, 
nullptr);
+        EXPECT_EQ(resp.status().code(), MetaServiceCode::TXN_ID_NOT_FOUND);
+    }
+}
+
+TEST(MetaServiceTest, GetPrepareTxnByCoordinatorScanRetryTest) {
+    auto meta_service = get_meta_service();
+    const bool original_mode = 
config::enable_get_prepare_txn_by_coordinator_by_running_key;
+    const bool original_metrics = config::use_detailed_metrics;
+    const bool original_retry = config::enable_txn_store_retry;
+    const auto original_retry_times = config::txn_store_retry_times;
+    config::txn_store_retry_times = 1;
+    config::use_detailed_metrics = true;
+    DORIS_CLOUD_DEFER {
+        config::enable_get_prepare_txn_by_coordinator_by_running_key = 
original_mode;
+        config::use_detailed_metrics = original_metrics;
+        config::enable_txn_store_retry = original_retry;
+        config::txn_store_retry_times = original_retry_times;
+    };
+
+    GetPrepareTxnByCoordinatorRequest req;
+    req.set_cloud_unique_id("test_cloud_unique_id");
+    req.set_id(12345);
+    req.set_ip("127.0.0.1");
+    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;
+        GetPrepareTxnByCoordinatorResponse resp;
+        meta_service->get_prepare_txn_by_coordinator(&cntl, &req, &resp, 
nullptr);
+        ASSERT_EQ(resp.status().code(), MetaServiceCode::OK);
+        EXPECT_EQ(resp.txn_infos_size(), 0);
+    }
+
+    constexpr int64_t db_id = 888;
+    constexpr int prepared_count = 5;
+    std::vector<int64_t> info_bytes;
+    std::vector<int64_t> running_bytes;
+    std::unique_ptr<Transaction> txn;
+    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
+    for (int64_t id = 1; id <= prepared_count + 1; ++id) {
+        TxnInfoPB info;
+        info.set_db_id(db_id);
+        info.set_txn_id(id);
+        info.set_status(id <= prepared_count ? TxnStatusPB::TXN_STATUS_PREPARED
+                                             : 
TxnStatusPB::TXN_STATUS_VISIBLE);
+        
info.mutable_coordinator()->set_sourcetype(TxnSourceTypePB::TXN_SOURCE_TYPE_BE);
+        info.mutable_coordinator()->set_id(12345);
+        info.mutable_coordinator()->set_ip("127.0.0.1");
+        const auto key = txn_info_key({mock_instance, db_id, id});
+        const auto value = info.SerializeAsString();
+        txn->put(key, value);
+        info_bytes.push_back(key.size() + value.size());
+        // Historical info must not be read in running-key mode.
+        if (id <= prepared_count) {
+            TxnRunningPB running;
+            running.set_timeout_time(0);
+            const auto running_key = txn_running_key({mock_instance, db_id, 
id});
+            const auto running_value = running.SerializeAsString();
+            txn->put(running_key, running_value);
+            running_bytes.push_back(running_key.size() + running_value.size());
+        }
+    }
+    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
+
+    for (const auto& [scan_by_running_key, expire_on_info] :
+         {std::pair {false, false}, std::pair {true, false}, std::pair {true, 
true}}) {
+        const auto& scan_bytes = scan_by_running_key ? running_bytes : 
info_bytes;
+        // MemTxnKv needs an empty page after full data pages to finish the 
scan.
+        const int terminal_page = scan_bytes.size() + 1;
+        for (const auto& failures : std::vector<std::vector<int>> {
+                     {}, {1}, {3}, {terminal_page - 1}, {terminal_page}, {2, 
5}}) {
+            SCOPED_TRACE(fmt::format("scan_by_running_key={} expire_on_info={} 
failures={}",
+                                     scan_by_running_key, expire_on_info,
+                                     fmt::join(failures, ",")));
+            auto [success, message] =
+                    
config::set_config({{"enable_get_prepare_txn_by_coordinator_by_running_key",
+                                         scan_by_running_key ? "true" : 
"false"}},
+                                       false, "");
+            ASSERT_TRUE(success) << message;
+            const auto counter_before =
+                    
g_bvar_rpc_kv_get_prepare_txn_by_coordinator_get_counter.get({mock_instance});
+            const auto bytes_before =
+                    
g_bvar_rpc_kv_get_prepare_txn_by_coordinator_get_bytes.get({mock_instance});
+            auto sp = SyncPoint::get_instance();
+            DORIS_CLOUD_DEFER {
+                sp->disable_processing();
+                sp->clear_all_call_backs();
+            };
+            int pages = 0;
+            int batches = 0;
+            sp->set_call_back("memkv::Transaction::get",
+                              [&](auto&& args) { *try_any_cast<int*>(args[0]) 
= 1; });
+            sp->set_call_back("get_prepare_txn_by_coordinator::range_get", 
[&](auto&& args) {
+                // A config change must only affect subsequent RPCs, even 
after expiry.
+                config::enable_get_prepare_txn_by_coordinator_by_running_key = 
!scan_by_running_key;
+                ++pages;
+                if (!expire_on_info && std::count(failures.begin(), 
failures.end(), pages) != 0) {
+                    *try_any_cast<TxnErrorCode*>(args[0]) = 
TxnErrorCode::TXN_TOO_OLD;
+                }
+            });
+            sp->set_call_back("get_prepare_txn_by_coordinator::batch_get", 
[&](auto&& args) {
+                ++batches;
+                if (expire_on_info && std::count(failures.begin(), 
failures.end(), batches) != 0) {
+                    *try_any_cast<TxnErrorCode*>(args[0]) = 
TxnErrorCode::TXN_TOO_OLD;
+                }
+            });
+            sp->enable_processing();
+
+            brpc::Controller cntl;
+            GetPrepareTxnByCoordinatorResponse resp;
+            meta_service->get_prepare_txn_by_coordinator(&cntl, &req, &resp, 
nullptr);
+            ASSERT_EQ(resp.status().code(), MetaServiceCode::OK);
+            ASSERT_EQ(resp.txn_infos_size(), prepared_count);
+            for (int i = 0; i < prepared_count; ++i) {
+                EXPECT_EQ(resp.txn_infos(i).txn_id(), i + 1);
+            }
+            EXPECT_EQ(pages, terminal_page + failures.size());
+            EXPECT_EQ(batches, scan_by_running_key
+                                       ? terminal_page + (expire_on_info ? 
failures.size() : 0)
+                                       : 0);
+
+            // Each earlier retry shifts subsequent read-attempt numbers by 
one.
+            std::vector<int> failed_pages;
+            for (size_t i = 0; i < failures.size(); ++i) {
+                failed_pages.push_back(failures[i] - static_cast<int>(i));
+            }
+            int64_t expected_counter = 0;
+            int64_t expected_bytes = 0;
+            for (size_t i = 0; i < scan_bytes.size(); ++i) {
+                const int reads = 1 + std::count(failed_pages.begin(), 
failed_pages.end(), i + 1);
+                expected_counter += reads;
+                expected_bytes += reads * scan_bytes[i];
+            }
+            if (scan_by_running_key) {
+                for (int i = 0; i < prepared_count; ++i) {
+                    const int reads = 1 + (expire_on_info ? 
std::count(failed_pages.begin(),
+                                                                       
failed_pages.end(), i + 1)
+                                                          : 0);
+                    expected_counter += reads;
+                    expected_bytes += reads * info_bytes[i];
+                }
+            }
+            EXPECT_EQ(
+                    
g_bvar_rpc_kv_get_prepare_txn_by_coordinator_get_counter.get({mock_instance}) -
+                            counter_before,
+                    expected_counter);
+            
EXPECT_EQ(g_bvar_rpc_kv_get_prepare_txn_by_coordinator_get_bytes.get({mock_instance})
 -
+                              bytes_before,
+                      expected_bytes);
+        }
+
+        // Fail after two pages have already appended results. Check handler 
failure separately
+        // from the proxy retry, which must clear partial results before 
restarting the RPC.
+        for (const auto& [error, retry_whole_rpc] :
+             {std::pair {TxnErrorCode::TXN_TOO_OLD, false},
+              std::pair {TxnErrorCode::TXN_UNIDENTIFIED_ERROR, false},
+              std::pair {TxnErrorCode::TXN_TOO_OLD, true}}) {
+            SCOPED_TRACE(
+                    fmt::format("scan_by_running_key={} expire_on_info={} 
error={} proxy_retry={}",
+                                scan_by_running_key, expire_on_info, error, 
retry_whole_rpc));
+            config::enable_get_prepare_txn_by_coordinator_by_running_key = 
scan_by_running_key;
+            config::enable_txn_store_retry = retry_whole_rpc;
+            auto sp = SyncPoint::get_instance();
+            DORIS_CLOUD_DEFER {
+                sp->disable_processing();
+                sp->clear_all_call_backs();
+            };
+            int attempts = 0;
+            sp->set_call_back("memkv::Transaction::get",
+                              [&](auto&& args) { *try_any_cast<int*>(args[0]) 
= 1; });
+            sp->set_call_back(expire_on_info ? 
"get_prepare_txn_by_coordinator::batch_get"
+                                             : 
"get_prepare_txn_by_coordinator::range_get",
+                              [&](auto&& args) {
+                                  ++attempts;
+                                  if (attempts == 3 || attempts == 4) {
+                                      *try_any_cast<TxnErrorCode*>(args[0]) = 
error;
+                                  }
+                              });
+            sp->enable_processing();
+            brpc::Controller cntl;
+            GetPrepareTxnByCoordinatorResponse resp;
+            meta_service->get_prepare_txn_by_coordinator(&cntl, &req, &resp, 
nullptr);
+            if (retry_whole_rpc) {
+                ASSERT_EQ(resp.status().code(), MetaServiceCode::OK);
+                EXPECT_EQ(attempts, 4 + terminal_page);
+                ASSERT_EQ(resp.txn_infos_size(), prepared_count);
+                for (int i = 0; i < prepared_count; ++i) {
+                    EXPECT_EQ(resp.txn_infos(i).txn_id(), i + 1);
+                }
+            } else {
+                EXPECT_EQ(resp.status().code(), error == 
TxnErrorCode::TXN_TOO_OLD
+                                                        ? 
MetaServiceCode::KV_TXN_TOO_OLD
+                                                        : 
MetaServiceCode::KV_TXN_GET_ERR);
+                EXPECT_EQ(attempts, error == TxnErrorCode::TXN_TOO_OLD ? 4 : 
3);
+                EXPECT_EQ(resp.status().msg(),
+                          fmt::format(
+                                  "get_prepare_txn_by_coordinator: failed to 
get txn info. err={}",
+                                  error));
+            }
+        }
+    }
+}
+
+TEST(MetaServiceTest, GetPrepareTxnByCoordinatorScanModeTest) {
+    auto meta_service = get_meta_service();
+    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;
+    };
+
+    constexpr int64_t db_id = 888;
+    std::unique_ptr<Transaction> txn;
+    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
+    for (int64_t id = 1; id <= 10; ++id) {
+        TxnInfoPB info;
+        info.set_db_id(db_id);
+        info.set_txn_id(id);
+        info.set_status(id == 6   ? TxnStatusPB::TXN_STATUS_VISIBLE
+                        : id == 7 ? TxnStatusPB::TXN_STATUS_PRECOMMITTED
+                        : id == 8 ? TxnStatusPB::TXN_STATUS_COMMITTED
+                                  : TxnStatusPB::TXN_STATUS_PREPARED);
+        auto* coordinator = info.mutable_coordinator();
+        coordinator->set_sourcetype(id == 4 ? 
TxnSourceTypePB::TXN_SOURCE_TYPE_FE
+                                            : 
TxnSourceTypePB::TXN_SOURCE_TYPE_BE);
+        coordinator->set_id(id == 2 ? 0 : id == 9 ? 54321 : 12345);
+        coordinator->set_ip(id == 3 ? "127.0.0.2" : "127.0.0.1");
+        coordinator->set_start_time(id == 5 ? 200 : id == 10 ? 150 : 100);
+        const auto key = txn_info_key({mock_instance, db_id, id});
+        const auto value = info.SerializeAsString();
+        txn->put(key, value);
+        if (id != 6) {
+            TxnRunningPB running;
+            running.set_timeout_time(0);
+            const auto running_key = txn_running_key({mock_instance, db_id, 
id});
+            const auto running_value = running.SerializeAsString();
+            txn->put(running_key, running_value);
+        }
+    }
+    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
+
+    for (bool scan_by_running_key : {false, true}) {
+        SCOPED_TRACE(fmt::format("scan_by_running_key={}", 
scan_by_running_key));
+        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("test_cloud_unique_id");
+        req.set_id(12345);
+        req.set_ip("127.0.0.1");
+        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);
+        EXPECT_EQ(resp.txn_infos(0).txn_id(), 1);
+        EXPECT_EQ(resp.txn_infos(1).txn_id(), 2);
+    }
+
+    // Skip malformed running keys on the first, middle and last data pages.
+    {
+        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), 
TxnErrorCode::TXN_OK);
+        for (int64_t id : {0, 3, 11}) {
+            auto key = txn_running_key({mock_instance, db_id, id});
+            key.push_back('\xff');
+            txn->put(key, "");
+        }
+        ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
+        config::enable_get_prepare_txn_by_coordinator_by_running_key = true;
+        auto sp = SyncPoint::get_instance();
+        DORIS_CLOUD_DEFER {
+            sp->disable_processing();
+            sp->clear_all_call_backs();
+        };
+        int pages = 0;
+        sp->set_call_back("memkv::Transaction::get", [&](auto&& args) {
+            *try_any_cast<int*>(args[0]) = 1;
+            ++pages;
+        });
+        sp->enable_processing();
+
+        brpc::Controller cntl;
+        GetPrepareTxnByCoordinatorRequest req;
+        GetPrepareTxnByCoordinatorResponse resp;
+        req.set_cloud_unique_id("test_cloud_unique_id");
+        req.set_id(12345);
+        req.set_ip("127.0.0.1");
+        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);
+        EXPECT_EQ(resp.txn_infos(0).txn_id(), 1);
+        EXPECT_EQ(resp.txn_infos(1).txn_id(), 2);
+        EXPECT_EQ(pages, 9 + 3 + 1);
+    }
+
+    // Processing inside read_page must preserve the RPC parse error in both 
scan modes.
+    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
+    const auto malformed_key = txn_info_key({mock_instance, db_id, 1});
+    txn->put(malformed_key, std::string(1, '\xff'));
+    ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
+    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("test_cloud_unique_id");
+        req.set_id(12345);
+        req.set_ip("127.0.0.1");
+        meta_service->get_prepare_txn_by_coordinator(&cntl, &req, &resp, 
nullptr);
+        EXPECT_EQ(resp.status().code(), MetaServiceCode::PROTOBUF_PARSE_ERR);
+        EXPECT_EQ(resp.status().msg(), "malformed txn info, key=" + 
hex(malformed_key));
+    }
+}
+
+TEST(MetaServiceTest, GetPrepareTxnByCoordinatorLargeInfoTest) {
+    auto meta_service = get_meta_service();
+    const bool original_mode = 
config::enable_get_prepare_txn_by_coordinator_by_running_key;
+    config::enable_get_prepare_txn_by_coordinator_by_running_key = true;
+    DORIS_CLOUD_DEFER {
+        config::enable_get_prepare_txn_by_coordinator_by_running_key = 
original_mode;
+    };
+    constexpr int txn_count = 257;
+    constexpr int64_t db_id = 888;
+    const std::string large_reason(50 * 1024, 'x');
+    for (int64_t id = 1; id <= txn_count; ++id) {
+        TxnInfoPB info;
+        info.set_db_id(db_id);
+        info.set_txn_id(id);
+        info.set_status(TxnStatusPB::TXN_STATUS_PREPARED);
+        info.set_reason(large_reason);
+        
info.mutable_coordinator()->set_sourcetype(TxnSourceTypePB::TXN_SOURCE_TYPE_BE);
+        info.mutable_coordinator()->set_id(12345);
+        info.mutable_coordinator()->set_ip("127.0.0.1");
+        TxnRunningPB running;
+        running.set_timeout_time(0);
+
+        std::unique_ptr<Transaction> txn;
+        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), 
TxnErrorCode::TXN_OK);
+        txn->put(txn_info_key({mock_instance, db_id, id}), 
info.SerializeAsString());
+        txn->put(txn_running_key({mock_instance, db_id, id}), 
running.SerializeAsString());
+        ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
+    }
+
+    auto sp = SyncPoint::get_instance();
+    DORIS_CLOUD_DEFER {
+        sp->disable_processing();
+        sp->clear_all_call_backs();
+    };
+    int pages = 0;
+    size_t read_count = 0;
+    sp->set_call_back("get_prepare_txn_by_coordinator::batch_get", [&](auto&& 
args) {
+        const auto& values = 
*try_any_cast<std::vector<std::optional<std::string>>*>(args[1]);
+        EXPECT_LE(values.size(), 128);
+        size_t bytes = 0;
+        for (const auto& value : values) {
+            ASSERT_TRUE(value.has_value());
+            EXPECT_GE(value->size(), large_reason.size());
+            bytes += value->size();
+        }
+        EXPECT_LT(bytes, 7 * 1024 * 1024);
+        read_count += values.size();
+        ++pages;
+    });
+    sp->enable_processing();
+
+    brpc::Controller cntl;
+    GetPrepareTxnByCoordinatorRequest req;
+    GetPrepareTxnByCoordinatorResponse resp;
+    req.set_cloud_unique_id("test_cloud_unique_id");
+    req.set_id(12345);
+    req.set_ip("127.0.0.1");
+    meta_service->get_prepare_txn_by_coordinator(&cntl, &req, &resp, nullptr);
+    ASSERT_EQ(resp.status().code(), MetaServiceCode::OK);
+    EXPECT_EQ(pages, 3);
+    EXPECT_EQ(read_count, txn_count);
+    ASSERT_EQ(resp.txn_infos_size(), txn_count);
+    for (int i = 0; i < txn_count; ++i) {
+        EXPECT_EQ(resp.txn_infos(i).txn_id(), i + 1);
+        EXPECT_EQ(resp.txn_infos(i).reason(), large_reason);
+    }
 }
 
 TEST(MetaServiceTest, CheckTxnConflictTest) {


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

Reply via email to