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

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


The following commit(s) were added to refs/heads/branch-4.1 by this push:
     new d02f448bba0 branch-4.1: [fix](cloud) Prevent urgent load from 
preempting schema change lock #66082 (#66226)
d02f448bba0 is described below

commit d02f448bba00c6ac71f1599bb0decd401c89bfae
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Thu Jul 30 09:21:32 2026 +0800

    branch-4.1: [fix](cloud) Prevent urgent load from preempting schema change 
lock #66082 (#66226)
    
    Cherry-picked from #66082
    
    Co-authored-by: bobhan1 <[email protected]>
---
 cloud/src/meta-service/meta_service.cpp            |   8 +-
 cloud/test/meta_service_test.cpp                   | 118 ++++++++++++++++++++-
 ...cloud_mow_load_preserves_schema_change_lock.out |   4 +
 ...ud_mow_load_preserves_schema_change_lock.groovy | 101 ++++++++++++++++++
 4 files changed, 226 insertions(+), 5 deletions(-)

diff --git a/cloud/src/meta-service/meta_service.cpp 
b/cloud/src/meta-service/meta_service.cpp
index 2fe48980f6f..525e6391e05 100644
--- a/cloud/src/meta-service/meta_service.cpp
+++ b/cloud/src/meta-service/meta_service.cpp
@@ -4576,6 +4576,10 @@ static bool 
put_delete_bitmap_update_lock_key(MetaServiceCode& code, std::string
     return true;
 }
 
+static bool can_load_force_take_delete_bitmap_lock(int64_t current_lock_id) {
+    return current_lock_id != SCHEMA_CHANGE_DELETE_BITMAP_LOCK_ID;
+}
+
 bool MetaServiceImpl::get_mow_tablet_stats_and_meta(MetaServiceCode& code, 
std::string& msg,
                                                     const 
GetDeleteBitmapUpdateLockRequest* request,
                                                     
GetDeleteBitmapUpdateLockResponse* response,
@@ -4950,7 +4954,7 @@ void MetaServiceImpl::get_delete_bitmap_update_lock_v2(
                 msg = "failed to parse DeleteBitmapUpdateLockPB";
                 return;
             }
-            if (urgent) {
+            if (urgent && 
can_load_force_take_delete_bitmap_lock(lock_info.lock_id())) {
                 // since currently only the FE Master initiates the lock 
request for import tasks,
                 // and it does so in a single-threaded manner, there is no 
need to check the lock id here
                 DCHECK(request->lock_id() > 0);
@@ -5204,7 +5208,7 @@ void MetaServiceImpl::get_delete_bitmap_update_lock_v1(
                 msg = "failed to parse DeleteBitmapUpdateLockPB";
                 return;
             }
-            if (urgent) {
+            if (urgent && 
can_load_force_take_delete_bitmap_lock(lock_info.lock_id())) {
                 // since currently only the FE Master initiates the lock 
request for import tasks,
                 // and it does so in a single-threaded manner, there is no 
need to check the lock id here
                 DCHECK(request->lock_id() > 0);
diff --git a/cloud/test/meta_service_test.cpp b/cloud/test/meta_service_test.cpp
index 9b04e98a2e0..be3425c3736 100644
--- a/cloud/test/meta_service_test.cpp
+++ b/cloud/test/meta_service_test.cpp
@@ -4924,6 +4924,28 @@ void remove_delete_bitmap_lock(MetaServiceProxy* 
meta_service, int64_t table_id)
     ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
 }
 
+void check_delete_bitmap_lock_id(MetaServiceProxy* meta_service, int64_t 
table_id,
+                                 int64_t expected_lock_id) {
+    std::unique_ptr<Transaction> txn;
+    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
+    std::string lock_key = 
meta_delete_bitmap_update_lock_key({"test_instance", table_id, -1});
+    std::string lock_val;
+    ASSERT_EQ(txn->get(lock_key, &lock_val), TxnErrorCode::TXN_OK);
+    DeleteBitmapUpdateLockPB lock_info;
+    ASSERT_TRUE(lock_info.ParseFromString(lock_val));
+    EXPECT_EQ(lock_info.lock_id(), expected_lock_id);
+}
+
+void check_mow_tablet_job_key(MetaServiceProxy* meta_service, int64_t 
table_id, int64_t initiator,
+                              bool expected_exists) {
+    std::unique_ptr<Transaction> txn;
+    ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
+    std::string job_key = mow_tablet_job_key({"test_instance", table_id, 
initiator});
+    std::string job_val;
+    EXPECT_EQ(txn->get(job_key, &job_val),
+              expected_exists ? TxnErrorCode::TXN_OK : 
TxnErrorCode::TXN_KEY_NOT_FOUND);
+}
+
 void testGetDeleteBitmapUpdateLock(int lock_version, int job_lock_id) {
     config::delete_bitmap_lock_v2_white_list = lock_version == 1 ? "" : "*";
     auto meta_service = get_meta_service();
@@ -5140,7 +5162,33 @@ void testGetDeleteBitmapUpdateLock(int lock_version, int 
job_lock_id) {
             nullptr);
     ASSERT_EQ(remove_res.status().code(), MetaServiceCode::OK);
 
-    // case 11: lock by schema change but expired, compaction get lock but txn 
commit conflict, do fast retry
+    // case 11: urgent load can force take compaction lock but not schema 
change lock
+    req.set_lock_id(job_lock_id);
+    req.set_initiator(100);
+    req.set_expiration(100);
+    meta_service->get_delete_bitmap_update_lock(
+            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, 
&res, nullptr);
+    ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
+
+    req.set_lock_id(888);
+    req.set_initiator(-1);
+    req.set_expiration(60);
+    req.set_urgent(true);
+    meta_service->get_delete_bitmap_update_lock(
+            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, 
&res, nullptr);
+    ASSERT_EQ(res.status().code(), job_lock_id == 
SCHEMA_CHANGE_DELETE_BITMAP_LOCK_ID
+                                           ? MetaServiceCode::LOCK_CONFLICT
+                                           : MetaServiceCode::OK);
+    req.set_urgent(false);
+    remove_req.set_lock_id(job_lock_id == SCHEMA_CHANGE_DELETE_BITMAP_LOCK_ID 
? job_lock_id : 888);
+    remove_req.set_initiator(job_lock_id == 
SCHEMA_CHANGE_DELETE_BITMAP_LOCK_ID ? 100 : -1);
+    meta_service->remove_delete_bitmap_update_lock(
+            reinterpret_cast<::google::protobuf::RpcController*>(&cntl), 
&remove_req, &remove_res,
+            nullptr);
+    ASSERT_EQ(remove_res.status().code(), MetaServiceCode::OK);
+    remove_delete_bitmap_lock(meta_service.get(), table_id);
+
+    // case 12: lock by schema change but expired, compaction get lock but txn 
commit conflict, do fast retry
     sp->set_call_back("get_delete_bitmap_update_lock:commit:conflict", 
[&](auto&& args) {
         auto* first_retry = try_any_cast<bool*>(args[0]);
         auto lock_id = (try_any_cast<const 
GetDeleteBitmapUpdateLockRequest*>(args[1]))->lock_id();
@@ -5164,7 +5212,7 @@ void testGetDeleteBitmapUpdateLock(int lock_version, int 
job_lock_id) {
             nullptr);
     ASSERT_EQ(remove_res.status().code(), MetaServiceCode::OK);
 
-    // case 12: lock by load but expired, compaction get lock but txn commit 
conflict, do fast retry
+    // case 13: lock by load but expired, compaction get lock but txn commit 
conflict, do fast retry
     req.set_lock_id(300);
     req.set_initiator(-1);
     req.set_expiration(1);
@@ -5181,7 +5229,7 @@ void testGetDeleteBitmapUpdateLock(int lock_version, int 
job_lock_id) {
     ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
     remove_delete_bitmap_lock(meta_service.get(), table_id);
 
-    // case 13: lock key does not exist, compaction get lock but txn commit 
conflict, do fast retry
+    // case 14: lock key does not exist, compaction get lock but txn commit 
conflict, do fast retry
     meta_service->get_delete_bitmap_update_lock(
             reinterpret_cast<::google::protobuf::RpcController*>(&cntl), &req, 
&res, nullptr);
     ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
@@ -5199,6 +5247,70 @@ TEST(MetaServiceTest, GetDeleteBitmapUpdateLock) {
     testGetDeleteBitmapUpdateLock(1, SCHEMA_CHANGE_DELETE_BITMAP_LOCK_ID);
 }
 
+void testUrgentLoadDeleteBitmapLock(int lock_version) {
+    config::delete_bitmap_lock_v2_white_list = lock_version == 1 ? "" : "*";
+    auto meta_service = get_meta_service();
+    int64_t table_id = 90 + lock_version;
+    remove_delete_bitmap_lock(meta_service.get(), table_id);
+
+    brpc::Controller cntl;
+    GetDeleteBitmapUpdateLockRequest req;
+    GetDeleteBitmapUpdateLockResponse res;
+    req.set_cloud_unique_id("test_cloud_unique_id");
+    req.set_table_id(table_id);
+    req.add_partition_ids(123);
+
+    auto get_lock = [&](int64_t lock_id, int64_t initiator, int64_t 
expiration, bool urgent) {
+        req.set_lock_id(lock_id);
+        req.set_initiator(initiator);
+        req.set_expiration(expiration);
+        req.set_urgent(urgent);
+        res.Clear();
+        meta_service->get_delete_bitmap_update_lock(
+                reinterpret_cast<::google::protobuf::RpcController*>(&cntl), 
&req, &res, nullptr);
+        return res.status().code();
+    };
+
+    // An urgent load must preserve an active schema change lock.
+    ASSERT_EQ(get_lock(SCHEMA_CHANGE_DELETE_BITMAP_LOCK_ID, 100, 100, false), 
MetaServiceCode::OK);
+    ASSERT_EQ(get_lock(888, -1, 60, true), MetaServiceCode::LOCK_CONFLICT);
+    check_delete_bitmap_lock_id(meta_service.get(), table_id, 
SCHEMA_CHANGE_DELETE_BITMAP_LOCK_ID);
+    if (lock_version == 2) {
+        check_mow_tablet_job_key(meta_service.get(), table_id, 100, true);
+    }
+    remove_delete_bitmap_lock(meta_service.get(), table_id);
+
+    // Expired schema change locks still follow the ordinary stale-lock 
cleanup path.
+    ASSERT_EQ(get_lock(SCHEMA_CHANGE_DELETE_BITMAP_LOCK_ID, 101, 1, false), 
MetaServiceCode::OK);
+    sleep(2);
+    ASSERT_EQ(get_lock(888, -1, 60, true), MetaServiceCode::OK);
+    check_delete_bitmap_lock_id(meta_service.get(), table_id, 888);
+    if (lock_version == 2) {
+        check_mow_tablet_job_key(meta_service.get(), table_id, 101, false);
+    }
+    remove_delete_bitmap_lock(meta_service.get(), table_id);
+
+    // The existing force-take behavior for compaction locks is unchanged.
+    ASSERT_EQ(get_lock(COMPACTION_DELETE_BITMAP_LOCK_ID, 102, 100, false), 
MetaServiceCode::OK);
+    ASSERT_EQ(get_lock(888, -1, 60, true), MetaServiceCode::OK);
+    check_delete_bitmap_lock_id(meta_service.get(), table_id, 888);
+    if (lock_version == 2) {
+        check_mow_tablet_job_key(meta_service.get(), table_id, 102, false);
+    }
+    remove_delete_bitmap_lock(meta_service.get(), table_id);
+
+    // The existing force-take behavior for another load lock is unchanged.
+    ASSERT_EQ(get_lock(777, -1, 100, false), MetaServiceCode::OK);
+    ASSERT_EQ(get_lock(888, -1, 60, true), MetaServiceCode::OK);
+    check_delete_bitmap_lock_id(meta_service.get(), table_id, 888);
+    remove_delete_bitmap_lock(meta_service.get(), table_id);
+}
+
+TEST(MetaServiceTest, UrgentLoadDeleteBitmapLock) {
+    testUrgentLoadDeleteBitmapLock(2);
+    testUrgentLoadDeleteBitmapLock(1);
+}
+
 TEST(MetaServiceTest, GetDeleteBitmapUpdateLockNoReadStats) {
     auto meta_service = get_meta_service();
 
diff --git 
a/regression-test/data/fault_injection_p0/cloud/test_cloud_mow_load_preserves_schema_change_lock.out
 
b/regression-test/data/fault_injection_p0/cloud/test_cloud_mow_load_preserves_schema_change_lock.out
new file mode 100644
index 00000000000..f51f1281f0a
--- /dev/null
+++ 
b/regression-test/data/fault_injection_p0/cloud/test_cloud_mow_load_preserves_schema_change_lock.out
@@ -0,0 +1,4 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !final --
+1      10
+
diff --git 
a/regression-test/suites/fault_injection_p0/cloud/test_cloud_mow_load_preserves_schema_change_lock.groovy
 
b/regression-test/suites/fault_injection_p0/cloud/test_cloud_mow_load_preserves_schema_change_lock.groovy
new file mode 100644
index 00000000000..8316bb0f4c8
--- /dev/null
+++ 
b/regression-test/suites/fault_injection_p0/cloud/test_cloud_mow_load_preserves_schema_change_lock.groovy
@@ -0,0 +1,101 @@
+// 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.
+
+import org.apache.doris.regression.suite.ClusterOptions
+
+suite("test_cloud_mow_load_preserves_schema_change_lock", "docker") {
+    def options = new ClusterOptions()
+    options.setFeNum(1)
+    options.setBeNum(1)
+    options.cloudMode = true
+    options.enableDebugPoints()
+    options.feConfigs += [
+            "delete_bitmap_lock_expiration_seconds=60",
+            "enable_mow_load_force_take_ms_lock=true",
+            "mow_load_force_take_ms_lock_threshold_ms=0",
+            "meta_service_rpc_retry_times=2",
+            "enable_schema_change_retry=false"
+    ]
+    options.beConfigs += [
+            "delete_bitmap_lock_expiration_seconds=60"
+    ]
+
+    docker(options) {
+        GetDebugPoint().clearDebugPointsForAllBEs()
+
+        try {
+            sql "DROP TABLE IF EXISTS 
test_cloud_mow_load_preserves_schema_change_lock"
+            sql """
+                CREATE TABLE test_cloud_mow_load_preserves_schema_change_lock (
+                    k INT NOT NULL,
+                    v INT
+                )
+                UNIQUE KEY(k)
+                DISTRIBUTED BY HASH(k) BUCKETS 1
+                PROPERTIES (
+                    "enable_unique_key_merge_on_write" = "true",
+                    "disable_auto_compaction" = "true",
+                    "light_schema_change" = "false",
+                    "replication_num" = "1"
+                )
+            """
+            sql "INSERT INTO test_cloud_mow_load_preserves_schema_change_lock 
VALUES (1, 10)"
+
+            GetDebugPoint().enableDebugPointForAllBEs(
+                    
"CloudSchemaChangeJob::_process_delete_bitmap.inject_sleep",
+                    [percent: "1.0", sleep: "10"])
+            def alterFuture = thread {
+                sql """
+                    ALTER TABLE 
test_cloud_mow_load_preserves_schema_change_lock
+                    MODIFY COLUMN v BIGINT
+                """
+            }
+            // This debug point is after the schema change acquires the delete 
bitmap lock.
+            // The debug point runs after schema change acquires the delete 
bitmap lock.
+            // The table has only one rowset, so three seconds is ample for 
reaching it.
+            sleep(3000)
+
+            test {
+                sql "INSERT INTO 
test_cloud_mow_load_preserves_schema_change_lock VALUES (2, 20)"
+                exception "Failed to get delete bitmap lock due to conflict"
+            }
+
+            alterFuture.get()
+            waitForSchemaChangeDone {
+                sql """
+                    SHOW ALTER TABLE COLUMN
+                    WHERE TableName = 
'test_cloud_mow_load_preserves_schema_change_lock'
+                    ORDER BY CreateTime DESC LIMIT 1
+                """
+                time 120
+            }
+            def alterResult = sql_return_maparray """
+                SHOW ALTER TABLE COLUMN
+                WHERE TableName = 
'test_cloud_mow_load_preserves_schema_change_lock'
+                ORDER BY CreateTime DESC LIMIT 1
+            """
+            assertEquals("FINISHED", alterResult[0].State)
+
+            order_qt_final """
+                SELECT k, v FROM 
test_cloud_mow_load_preserves_schema_change_lock
+                ORDER BY k
+            """
+        } finally {
+            GetDebugPoint().clearDebugPointsForAllBEs()
+        }
+    }
+}


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

Reply via email to