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

liaoxin01 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 cb505369752 [fix](cloud) Prevent stale delete rowset replay from 
recreating tmp metadata (#69007)
cb505369752 is described below

commit cb50536975217fb1bd26cf98250b3e0887689077
Author: Yixuan Wang <[email protected]>
AuthorDate: Fri Oct 23 00:19:57 2026 +0800

    [fix](cloud) Prevent stale delete rowset replay from recreating tmp 
metadata (#69007)
    
    ### What problem does this PR solve?
    
    Issue Number: N/A
    
    Related PR: N/A
    
    Problem Summary: When recycle rowset key checking is enabled,
    commit_rowset skips the load transaction status check.
    
    For a delete-predicate rowset, the recycle key check treats the key as
    present for compatibility with older BEs.
    
    A replay after the transaction becomes visible can therefore recreate a
    tmp rowset key that the Recycler cannot safely remove.
    
    Run the existing transaction status check for delete-predicate rowsets
    before writing tmp metadata. Add a unit test covering the post-commit
    replay.
    
    ### Release note
    
    Prevent stale DELETE rowset requests from recreating temporary rowset
    metadata after transaction commit.
---
 cloud/src/meta-service/meta_service.cpp | 128 ++++++++++++++------------------
 cloud/test/meta_service_job_test.cpp    |   4 +-
 cloud/test/meta_service_test.cpp        |  50 +++++++++++++
 3 files changed, 109 insertions(+), 73 deletions(-)

diff --git a/cloud/src/meta-service/meta_service.cpp 
b/cloud/src/meta-service/meta_service.cpp
index 19e1ed1ccd3..3b691083730 100644
--- a/cloud/src/meta-service/meta_service.cpp
+++ b/cloud/src/meta-service/meta_service.cpp
@@ -40,6 +40,7 @@
 #include <limits>
 #include <memory>
 #include <numeric>
+#include <optional>
 #include <ostream>
 #include <sstream>
 #include <string>
@@ -2655,44 +2656,33 @@ void 
MetaServiceImpl::prepare_rowset(::google::protobuf::RpcController* controll
     }
 }
 
-// Returns false only when reading or parsing the recycle rowset key fails, or 
when the rowset has
-// already been marked as recycled. recycle_rs_key_exists tells the caller 
whether the key exists,
-bool check_recycle_rowset_key(Transaction* txn, const std::string& 
recycle_rs_key,
-                              const doris::RowsetMetaCloudPB& rowset_meta,
-                              bool* recycle_rs_key_exists, MetaServiceCode& 
code,
-                              std::string& msg) {
-    *recycle_rs_key_exists = false;
-    if (!rowset_meta.has_delete_predicate()) {
-        std::string recycle_rs_val;
-        TxnErrorCode err = txn->get(recycle_rs_key, &recycle_rs_val);
-        if (err == TxnErrorCode::TXN_OK) {
-            *recycle_rs_key_exists = true;
-            RecycleRowsetPB recycle_rowset;
-            if (!recycle_rowset.ParseFromString(recycle_rs_val)) {
-                code = MetaServiceCode::PROTOBUF_PARSE_ERR;
-                msg = fmt::format("malformed recycle rowset value. key={}", 
hex(recycle_rs_key));
-                return false;
-            }
-            auto rs_meta = recycle_rowset.rowset_meta();
-            if (rs_meta.has_is_recycled() && rs_meta.is_recycled()) {
-                code = MetaServiceCode::INVALID_ARGUMENT;
-                msg = fmt::format("rowset has already been marked as recycled, 
key={}, rs_meta={}",
-                                  hex(recycle_rs_key), 
rs_meta.ShortDebugString());
-                return false;
-            }
-        } else if (err == TxnErrorCode::TXN_KEY_NOT_FOUND) {
-            return true;
-        } else {
-            code = cast_as<ErrCategory::READ>(err);
-            msg = fmt::format("failed to get recycle rowset, err={}, key={}", 
err,
-                              hex(recycle_rs_key));
-            return false;
-        }
-    } else {
-        // Old BE does not write recycle rowset keys for delete predicate 
rowsets during
-        // commit_rowset, so treat the key as existing for compatibility.
-        *recycle_rs_key_exists = true;
+bool check_recycle_rowset_key_exists(Transaction* txn, const std::string& 
recycle_rs_key,
+                                     std::optional<bool>& 
recycle_rs_key_exists,
+                                     MetaServiceCode& code, std::string& msg) {
+    std::string value;
+    auto err = txn->get(recycle_rs_key, &value);
+
+    if (err == TxnErrorCode::TXN_KEY_NOT_FOUND) {
+        recycle_rs_key_exists = false;
+        return true;
+    }
+
+    if (err != TxnErrorCode::TXN_OK) {
+        code = cast_as<ErrCategory::READ>(err);
+        msg = fmt::format("failed to get recycle rowset, err={}, key={}", err, 
hex(recycle_rs_key));
+        return false;
     }
+
+    RecycleRowsetPB recycle_rowset;
+    if (!recycle_rowset.ParseFromString(value)) {
+        code = MetaServiceCode::PROTOBUF_PARSE_ERR;
+        msg = fmt::format("malformed recycle rowset value. key={}", 
hex(recycle_rs_key));
+        return false;
+    }
+
+    recycle_rs_key_exists = recycle_rowset.rowset_meta().has_is_recycled()
+                                    ? 
!recycle_rowset.rowset_meta().is_recycled()
+                                    : true;
     return true;
 }
 
@@ -2744,8 +2734,18 @@ void 
MetaServiceImpl::commit_rowset(::google::protobuf::RpcController* controlle
 
     bool is_versioned_read = is_version_read_enabled(instance_id);
     auto recycle_rs_key = recycle_rowset_key({instance_id, tablet_id, 
rowset_id});
+    std::optional<bool> recycle_rs_key_exists;
 
-    if (!config::enable_recycle_delete_rowset_key_check) {
+    if (config::enable_recycle_delete_rowset_key_check && 
!rowset_meta.has_delete_predicate()) {
+        if (!check_recycle_rowset_key_exists(txn.get(), recycle_rs_key, 
recycle_rs_key_exists, code,
+                                             msg)) {
+            return;
+        }
+    } else {
+        // Fallback path:
+        // Use the old job and transaction checks when the recycle key check 
is off
+        // or the rowset has a delete predicate, because older BEs do not call 
prepare_rowset for these rowsets.
+        // enable_tablet_job_check and enable_load_txn_status_check are both 
true by default.
         std::string tablet_job_id = request->tablet_job_id();
         if (config::enable_tablet_job_check && !tablet_job_id.empty()) {
             if (!check_job_existed(txn.get(), code, msg, instance_id, 
tablet_id, rowset_id,
@@ -2780,25 +2780,18 @@ void 
MetaServiceImpl::commit_rowset(::google::protobuf::RpcController* controlle
             return;
         }
         if (existed_rowset_meta->rowset_id_v2() == rowset_meta.rowset_id_v2()) 
{
-            if (config::enable_recycle_delete_rowset_key_check) {
-                bool recycle_rs_key_exists = false;
-                if (!check_recycle_rowset_key(txn.get(), recycle_rs_key, 
rowset_meta,
-                                              &recycle_rs_key_exists, code, 
msg)) {
-                    return;
-                }
-                if (recycle_rs_key_exists) {
-                    code = MetaServiceCode::INVALID_ARGUMENT;
-                    msg = fmt::format(
-                            "tmp rowset key and recycle rowset key are 
mutually exclusive, "
-                            "tmp_rs_key={}, recycle_rs_key={}",
-                            hex(tmp_rs_key), hex(recycle_rs_key));
-                    LOG(INFO) << "skip commit rowset because tmp rowset key 
and recycle rowset key "
-                                 "are mutually exclusive, txn_id="
-                              << rowset_meta.txn_id() << ", tablet_id=" << 
tablet_id
-                              << ", rowset_id=" << rowset_id << ", 
tmp_rs_key=" << hex(tmp_rs_key)
-                              << ", recycle_rs_key=" << hex(recycle_rs_key) << 
", msg=" << msg;
-                    return;
-                }
+            if (recycle_rs_key_exists.has_value() && 
recycle_rs_key_exists.value()) {
+                code = MetaServiceCode::INVALID_ARGUMENT;
+                msg = fmt::format(
+                        "tmp rowset key and recycle rowset key are mutually 
exclusive, "
+                        "tmp_rs_key={}, recycle_rs_key={}",
+                        hex(tmp_rs_key), hex(recycle_rs_key));
+                LOG(INFO) << "skip commit rowset because tmp rowset key and 
recycle rowset key "
+                             "are mutually exclusive, txn_id="
+                          << rowset_meta.txn_id() << ", tablet_id=" << 
tablet_id
+                          << ", rowset_id=" << rowset_id << ", tmp_rs_key=" << 
hex(tmp_rs_key)
+                          << ", recycle_rs_key=" << hex(recycle_rs_key) << ", 
msg=" << msg;
+                return;
             }
             // Same request, return OK
             response->set_allocated_existed_rowset_meta(nullptr);
@@ -2851,20 +2844,13 @@ void 
MetaServiceImpl::commit_rowset(::google::protobuf::RpcController* controlle
         msg = fmt::format("failed to check whether rowset exists, err={}", 
err);
         return;
     }
-    if (config::enable_recycle_delete_rowset_key_check) {
-        bool recycle_rs_key_exists = false;
-        if (!check_recycle_rowset_key(txn.get(), recycle_rs_key, rowset_meta,
-                                      &recycle_rs_key_exists, code, msg)) {
-            return;
-        }
-        if (!recycle_rs_key_exists) {
-            code = MetaServiceCode::INVALID_ARGUMENT;
-            msg = fmt::format("recycle rowset key not found, key={}", 
hex(recycle_rs_key));
-            LOG(INFO) << "skip commit rowset because recycle rowset key does 
not exist, txn_id="
-                      << rowset_meta.txn_id() << ", tablet_id=" << tablet_id
-                      << ", rowset_id=" << rowset_id << ", recycle_rs_key=" << 
hex(recycle_rs_key);
-            return;
-        }
+    if (recycle_rs_key_exists.has_value() && !recycle_rs_key_exists.value()) {
+        code = MetaServiceCode::INVALID_ARGUMENT;
+        msg = fmt::format("recycle rowset key not found, key={}", 
hex(recycle_rs_key));
+        LOG(INFO) << "skip commit rowset because recycle rowset key does not 
exist, txn_id="
+                  << rowset_meta.txn_id() << ", tablet_id=" << tablet_id
+                  << ", rowset_id=" << rowset_id << ", recycle_rs_key=" << 
hex(recycle_rs_key);
+        return;
     }
     // write schema kv if rowset_meta has schema
     if (config::write_schema_kv && rowset_meta.has_tablet_schema()) {
diff --git a/cloud/test/meta_service_job_test.cpp 
b/cloud/test/meta_service_job_test.cpp
index 87a07325294..eddb734b503 100644
--- a/cloud/test/meta_service_job_test.cpp
+++ b/cloud/test/meta_service_job_test.cpp
@@ -7159,7 +7159,7 @@ TEST(MetaServiceJobTest, AbortTxnForRelatedRowsetTest3) {
             if (!arena) delete req;
             // Should fail because transaction is aborted
             ASSERT_NE(res.status().code(), MetaServiceCode::OK);
-            ASSERT_TRUE(res.status().msg().find("rowset has already been 
marked as recycled") !=
+            ASSERT_TRUE(res.status().msg().find("recycle rowset key not 
found") !=
                         std::string::npos)
                     << res.status().msg();
             LOG(INFO) << "Step 5: commit_rowset correctly failed for aborted 
txn";
@@ -7271,7 +7271,7 @@ TEST(MetaServiceJobTest, AbortJobForRelatedRowsetTest3) {
             if (!arena) delete req;
             // Should fail because recycle_rowset_key is marked recycled
             ASSERT_NE(res.status().code(), MetaServiceCode::OK);
-            ASSERT_TRUE(res.status().msg().find("rowset has already been 
marked as recycled") !=
+            ASSERT_TRUE(res.status().msg().find("recycle rowset key not 
found") !=
                         std::string::npos)
                     << res.status().msg();
             LOG(INFO) << "Step 5: commit_rowset executed with code=" << 
res.status().code();
diff --git a/cloud/test/meta_service_test.cpp b/cloud/test/meta_service_test.cpp
index 6655c3da64d..ec393bae1de 100644
--- a/cloud/test/meta_service_test.cpp
+++ b/cloud/test/meta_service_test.cpp
@@ -12869,6 +12869,56 @@ TEST(MetaServiceTest, StaleCommitRowset) {
     ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT) << 
res.status().code();
 }
 
+TEST(MetaServiceTest, StaleDeletePredicateCommitRowset) {
+    auto meta_service = get_meta_service();
+
+    const bool old_recycle_key_check = 
config::enable_recycle_delete_rowset_key_check;
+    const bool old_load_txn_status_check = 
config::enable_load_txn_status_check;
+    config::enable_recycle_delete_rowset_key_check = true;
+    config::enable_load_txn_status_check = true;
+    DORIS_CLOUD_DEFER {
+        config::enable_recycle_delete_rowset_key_check = old_recycle_key_check;
+        config::enable_load_txn_status_check = old_load_txn_status_check;
+    };
+
+    constexpr int64_t db_id = 100201;
+    constexpr int64_t table_id = 1;
+    constexpr int64_t partition_id = 1;
+    constexpr int64_t tablet_id = 1;
+    const std::string label = "test_stale_delete_predicate_commit_rowset";
+    ASSERT_NO_FATAL_FAILURE(
+            create_tablet(meta_service.get(), table_id, 1, partition_id, 
tablet_id));
+
+    int64_t txn_id = 0;
+    ASSERT_NO_FATAL_FAILURE(begin_txn(meta_service.get(), db_id, label, 
table_id, txn_id));
+    auto rowset = create_rowset(txn_id, tablet_id, partition_id, -1, 0);
+    rowset.set_num_segments(0);
+    rowset.set_resource_id("1");
+    rowset.mutable_load_id()->set_hi(0);
+    rowset.mutable_load_id()->set_lo(0);
+    rowset.mutable_delete_predicate()->set_version(1);
+
+    // Old BE commits delete-predicate rowsets without a recycle rowset key.
+    CreateRowsetResponse res;
+    ASSERT_NO_FATAL_FAILURE(commit_rowset(meta_service.get(), rowset, res));
+    ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();
+    ASSERT_NO_FATAL_FAILURE(commit_txn(meta_service.get(), db_id, txn_id, 
label));
+
+    const auto tmp_rs_key = meta_rowset_tmp_key({mock_instance, txn_id, 
tablet_id});
+    std::unique_ptr<Transaction> txn;
+    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
+    std::string val;
+    ASSERT_EQ(txn->get(tmp_rs_key, &val), TxnErrorCode::TXN_KEY_NOT_FOUND);
+
+    res.Clear();
+    ASSERT_NO_FATAL_FAILURE(commit_rowset(meta_service.get(), rowset, res));
+    ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT) << 
res.status().msg();
+    ASSERT_NE(res.status().msg().find("txn is not in"), std::string::npos) << 
res.status().msg();
+
+    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
+    ASSERT_EQ(txn->get(tmp_rs_key, &val), TxnErrorCode::TXN_KEY_NOT_FOUND);
+}
+
 TEST(MetaServiceTest, CommitRowsetCheckTmpAndRecycleKeyExclusion) {
     auto meta_service = get_meta_service();
 


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

Reply via email to