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 2134b43d9c0 branch-4.2: [fix](cloud) Skip stale delete bitmap
aggregation #68084 (#69306)
2134b43d9c0 is described below
commit 2134b43d9c0f2129823a131ebd7499d659ccf6c2
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Wed Oct 21 10:49:55 2026 +0800
branch-4.2: [fix](cloud) Skip stale delete bitmap aggregation #68084
(#69306)
Cherry-picked from #68084
Co-authored-by: meiyi <[email protected]>
---
cloud/src/meta-service/meta_service.cpp | 106 +++++++++++---
cloud/test/meta_service_test.cpp | 162 ++++++++++++++++++++-
...t_cloud_mow_compaction_agg_bitmap_two_be.groovy | 145 ++++++++++++++++++
3 files changed, 389 insertions(+), 24 deletions(-)
diff --git a/cloud/src/meta-service/meta_service.cpp
b/cloud/src/meta-service/meta_service.cpp
index 2a6e71fe35d..b5171c53bf2 100644
--- a/cloud/src/meta-service/meta_service.cpp
+++ b/cloud/src/meta-service/meta_service.cpp
@@ -3905,7 +3905,51 @@ static bool
check_delete_bitmap_point_delete(MetaServiceCode& code, std::string&
return true;
}
-void _write_delete_bitmap_kvs(MetaServiceCode& code, std::string& msg,
std::stringstream& ss,
+// False stops the whole request, including cleanup. An obsolete aggregation
is a successful no-op.
+static bool check_pre_rowset_aggregation(MetaServiceCode& code, std::string&
msg, Transaction* txn,
+ const UpdateDeleteBitmapRequest*
request,
+ const std::string& instance_id) {
+ if (!request->without_lock() ||
+ request->lock_id() != COMPACTION_WITHOUT_LOCK_DELETE_BITMAP_LOCK_ID ||
+ !request->has_pre_rowset_agg_start_version() ||
+ !request->has_pre_rowset_agg_end_version()) {
+ return true;
+ }
+
+ auto tablet_id = request->tablet_id();
+ auto start = request->pre_rowset_agg_start_version();
+ auto end = request->pre_rowset_agg_end_version();
+ auto key = meta_rowset_key({instance_id, tablet_id, end});
+ std::string value;
+ // Compaction removes/replaces this live key even with versioned metadata
enabled.
+ // A non-snapshot read conflicts with a compaction that invalidates the
output rowset
+ // before this transaction commits. Historical versioned rowsets cannot
establish liveness.
+ auto err = txn->get(key, &value);
+ if (err != TxnErrorCode::TXN_OK && err != TxnErrorCode::TXN_KEY_NOT_FOUND)
{
+ code = cast_as<ErrCategory::READ>(err);
+ msg = fmt::format("failed to get aggregation output rowset,
tablet_id={}, key={}, err={}",
+ tablet_id, hex(key), err);
+ return false;
+ }
+ RowsetMetaCloudPB rowset;
+ if (err == TxnErrorCode::TXN_OK && !rowset.ParseFromString(value)) {
+ code = MetaServiceCode::PROTOBUF_PARSE_ERR;
+ msg = fmt::format("malformed aggregation output rowset, tablet_id={},
key={}", tablet_id,
+ hex(key));
+ return false;
+ }
+ if (err == TxnErrorCode::TXN_KEY_NOT_FOUND || rowset.start_version() !=
start ||
+ rowset.end_version() != end) {
+ LOG(INFO) << "skip obsolete pre rowset delete bitmap aggregation,
instance_id="
+ << instance_id << " tablet_id=" << tablet_id << "
start_version=" << start
+ << " end_version=" << end;
+ return false;
+ }
+
TEST_SYNC_POINT_CALLBACK("update_delete_bitmap:check_pre_rowset_aggregation",
request);
+ return true;
+}
+
+bool _write_delete_bitmap_kvs(MetaServiceCode& code, std::string& msg,
std::stringstream& ss,
std::shared_ptr<TxnKv> txn_kv,
std::unique_ptr<Transaction>& txn,
std::string& use_version, std::set<std::string>&
non_exist_rowset_ids,
size_t i, std::string& log, KVStats& stats,
@@ -3939,7 +3983,7 @@ void _write_delete_bitmap_kvs(MetaServiceCode& code,
std::string& msg, std::stri
<< " txn_size=" << txn->approximate_bytes();
msg = ss.str();
g_bvar_update_delete_bitmap_fail_counter << 1;
- return;
+ return false;
}
stats.get_bytes += txn->get_bytes();
stats.put_bytes += txn->put_bytes();
@@ -3953,7 +3997,10 @@ void _write_delete_bitmap_kvs(MetaServiceCode& code,
std::string& msg, std::stri
if (err != TxnErrorCode::TXN_OK) {
code = cast_as<ErrCategory::CREATE>(err);
msg = "failed to init txn";
- return;
+ return false;
+ }
+ if (!check_pre_rowset_aggregation(code, msg, txn.get(), request,
instance_id)) {
+ return false;
}
if (!without_lock) {
std::string lock_key =
meta_delete_bitmap_update_lock_key({instance_id, table_id, -1});
@@ -3964,7 +4011,7 @@ void _write_delete_bitmap_kvs(MetaServiceCode& code,
std::string& msg, std::stri
LOG(WARNING) << "failed to check delete bitmap lock,
table_id=" << table_id
<< " request lock_id=" << request->lock_id()
<< " request initiator=" << request->initiator()
<< " msg " << msg;
- return;
+ return false;
}
}
}
@@ -3973,7 +4020,7 @@ void _write_delete_bitmap_kvs(MetaServiceCode& code,
std::string& msg, std::stri
if (config::enable_update_delete_bitmap_kv_check) {
if (!check_delete_bitmap_kv_exists(code, msg, txn, key, instance_id,
tablet_id,
request->lock_id())) {
- return;
+ return false;
}
}
#endif
@@ -3985,7 +4032,7 @@ void _write_delete_bitmap_kvs(MetaServiceCode& code,
std::string& msg, std::stri
LOG(INFO) << "skip update delete bitmap, rowset_id=" <<
request->rowset_ids(i)
<< " version=" << request->pre_rowset_versions(i)
<< " tablet_id=" << tablet_id << " because the rowset
does not exist";
- return;
+ return true;
}
TxnErrorCode err = TxnErrorCode::TXN_OK;
@@ -4001,7 +4048,7 @@ void _write_delete_bitmap_kvs(MetaServiceCode& code,
std::string& msg, std::stri
<< " err=" << err;
msg = ss.str();
code = cast_as<ErrCategory::READ>(err);
- return;
+ return false;
}
if (err == TxnErrorCode::TXN_OK &&
!rs.ParseFromArray(rowset_val.data(), rowset_val.size())) {
@@ -4009,7 +4056,7 @@ void _write_delete_bitmap_kvs(MetaServiceCode& code,
std::string& msg, std::stri
ss << "malformed rowset meta, unable to deserialize,
tablet_id=" << tablet_id
<< " key=" << hex(rowset_key);
msg = ss.str();
- return;
+ return false;
}
} else {
CloneChainReader reader(instance_id, resource_mgr);
@@ -4020,7 +4067,7 @@ void _write_delete_bitmap_kvs(MetaServiceCode& code,
std::string& msg, std::stri
<< " version=" << request->pre_rowset_versions(i);
msg = ss.str();
code = cast_as<ErrCategory::READ>(err);
- return;
+ return false;
}
}
if (err == TxnErrorCode::TXN_KEY_NOT_FOUND) {
@@ -4028,14 +4075,14 @@ void _write_delete_bitmap_kvs(MetaServiceCode& code,
std::string& msg, std::stri
LOG(INFO) << "skip update delete bitmap, rowset_id=" <<
request->rowset_ids(i)
<< " version=" << request->pre_rowset_versions(i)
<< " tablet_id=" << tablet_id << " because the rowset is
not exist";
- return;
+ return true;
}
if (rs.rowset_id_v2() != request->rowset_ids(i)) {
LOG(INFO) << "skip update delete bitmap, rowset_id=" <<
request->rowset_ids(i)
<< " version=" << request->pre_rowset_versions(i)
<< " tablet_id=" << tablet_id << " because the rowset is
not exist";
non_exist_rowset_ids.emplace(request->rowset_ids(i));
- return;
+ return true;
}
}
// remove first
@@ -4045,7 +4092,7 @@ void _write_delete_bitmap_kvs(MetaServiceCode& code,
std::string& msg, std::stri
std::optional<uint16_t> max_blob_sequence;
if (!check_delete_bitmap_point_delete(code, msg, txn, key, request, i,
point_delete,
delete_bitmap_exists,
max_blob_sequence)) {
- return;
+ return false;
}
if (point_delete) {
if (delete_bitmap_exists) {
@@ -4081,12 +4128,15 @@ void _write_delete_bitmap_kvs(MetaServiceCode& code,
std::string& msg, std::stri
VLOG_DEBUG << "xxx update delete bitmap put delete_bitmap_key=" << hex(key)
<< " lock_id=" << request->lock_id() << " initiator=" <<
request->initiator()
<< " key_size: " << key.size() << " value_size: " << val.size();
+ return true;
}
static bool commit_pre_rowset_delete_bitmap_removal(
MetaServiceCode& code, std::string& msg, std::stringstream& ss,
const std::shared_ptr<TxnKv>& txn_kv, std::unique_ptr<Transaction>&
txn, KVStats& stats,
- UpdateDeleteBitmapTxnStats& txn_stats, int64_t tablet_id, const
std::string& rowset_id) {
+ UpdateDeleteBitmapTxnStats& txn_stats, const
UpdateDeleteBitmapRequest* request,
+ const std::string& instance_id, const std::string& rowset_id) {
+ auto tablet_id = request->tablet_id();
auto txn_size = txn->approximate_bytes();
LOG(INFO) << "commit delete bitmap point deletes before transaction size
exceeds limit, "
"tablet_id="
@@ -4119,7 +4169,7 @@ static bool commit_pre_rowset_delete_bitmap_removal(
msg = "failed to init txn when removing pre rowsets delete bitmap";
return false;
}
- return true;
+ return check_pre_rowset_aggregation(code, msg, txn.get(), request,
instance_id);
}
static bool remove_pre_rowset_delete_bitmap(
@@ -4183,7 +4233,7 @@ static bool remove_pre_rowset_delete_bitmap(
if (txn_size > 0 &&
txn_size + key.size() * 3 >
static_cast<size_t>(config::max_txn_commit_byte) &&
!commit_pre_rowset_delete_bitmap_removal(code, msg, ss,
txn_kv, txn, stats,
- txn_stats,
tablet_id,
+ txn_stats,
request, instance_id,
rowset_stats.rowset_id())) {
return false;
}
@@ -4283,6 +4333,10 @@ void
MetaServiceImpl::update_delete_bitmap(google::protobuf::RpcController* cont
return;
}
+ if (!check_pre_rowset_aggregation(code, msg, txn.get(), request,
instance_id)) {
+ return;
+ }
+
bool is_explicit_txn = (request->has_is_explicit_txn() &&
request->is_explicit_txn());
bool is_first_sub_txn = (is_explicit_txn && request->txn_id() ==
request->lock_id());
std::string log = ", update delete bitmap for tablet " +
std::to_string(tablet_id);
@@ -4409,10 +4463,13 @@ void
MetaServiceImpl::update_delete_bitmap(google::protobuf::RpcController* cont
auto& key = delete_bitmap_keys_v1[i];
auto& val = request->segment_delete_bitmaps(i);
bool is_versioned_read = is_version_read_enabled(instance_id);
- _write_delete_bitmap_kvs(code, msg, ss, txn_kv_, txn, use_version,
non_exist_rowset_ids, i,
- log, stats, request, instance_id, key, val,
txn_stats,
- is_versioned_read, resource_mgr_.get());
- if (code != MetaServiceCode::OK) return;
+ if (!_write_delete_bitmap_kvs(code, msg, ss, txn_kv_, txn, use_version,
+ non_exist_rowset_ids, i, log, stats,
request, instance_id,
+ key, val, txn_stats, is_versioned_read,
+ resource_mgr_.get()) ||
+ code != MetaServiceCode::OK) {
+ return;
+ }
}
// write v2 kvs
for (size_t i = 0; i < delete_bitmap_keys_v2.size(); ++i) {
@@ -4428,10 +4485,13 @@ void
MetaServiceImpl::update_delete_bitmap(google::protobuf::RpcController* cont
<<
request->delete_bitmap_storages(i).delete_bitmap().rowset_ids_size()
<< ". key=" << hex(key) << ", value_size=" << val.size();
bool is_versioned_read = is_version_read_enabled(instance_id);
- _write_delete_bitmap_kvs(code, msg, ss, txn_kv_, txn, use_version,
non_exist_rowset_ids, i,
- log, stats, request, instance_id, key, val,
txn_stats,
- is_versioned_read, resource_mgr_.get());
- if (code != MetaServiceCode::OK) return;
+ if (!_write_delete_bitmap_kvs(code, msg, ss, txn_kv_, txn, use_version,
+ non_exist_rowset_ids, i, log, stats,
request, instance_id,
+ key, val, txn_stats, is_versioned_read,
+ resource_mgr_.get()) ||
+ code != MetaServiceCode::OK) {
+ return;
+ }
}
if (!remove_pre_rowset_delete_bitmap(code, msg, ss, txn_kv_, txn, stats,
request, instance_id,
diff --git a/cloud/test/meta_service_test.cpp b/cloud/test/meta_service_test.cpp
index 6738b498cc1..1c5fc0c06c1 100644
--- a/cloud/test/meta_service_test.cpp
+++ b/cloud/test/meta_service_test.cpp
@@ -7664,23 +7664,179 @@ TEST(MetaServiceTest, UpdateDeleteBitmapWithException)
{
static void put_delete_bitmap_test_rowset(MetaServiceProxy* meta_service,
const std::string& instance_id,
int64_t tablet_id,
- int64_t version, const std::string&
rowset_id) {
+ int64_t version, const std::string&
rowset_id,
+ int64_t start_version = -1) {
std::unique_ptr<Transaction> txn;
ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
std::string rowset_key = meta_rowset_key({instance_id, tablet_id,
version});
doris::RowsetMetaCloudPB rowset_meta;
rowset_meta.set_rowset_id(0);
rowset_meta.set_rowset_id_v2(rowset_id);
+ rowset_meta.set_start_version(start_version < 0 ? version : start_version);
+ rowset_meta.set_end_version(version);
std::string rowset_value;
ASSERT_TRUE(rowset_meta.SerializeToString(&rowset_value));
txn->put(rowset_key, rowset_value);
ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
}
+TEST(MetaServiceTest, PreRowsetAggregationChecksOutputInEveryTransaction) {
+ auto sp = SyncPoint::get_instance();
+ auto old_max_txn_commit_byte = config::max_txn_commit_byte;
+ DORIS_CLOUD_DEFER {
+ config::max_txn_commit_byte = old_max_txn_commit_byte;
+ sp->clear_all_call_backs();
+ sp->disable_processing();
+ };
+
+ for (bool point_delete : {false, true}) {
+ for (std::string_view scenario :
+ {"valid", "same_range", "replaced", "removed", "malformed",
"after_check",
+ "same_range_after_check", "write_split", "cleanup_split"}) {
+ // Range cleanup does not split transactions.
+ if (!point_delete && scenario == "cleanup_split") {
+ continue;
+ }
+ SCOPED_TRACE(fmt::format("scenario={}, point_delete={}", scenario,
point_delete));
+ const bool same_range =
+ scenario == "same_range" || scenario ==
"same_range_after_check";
+ const bool after_check =
+ scenario == "after_check" || scenario ==
"same_range_after_check";
+ const bool valid = scenario == "valid" || scenario == "same_range";
+ sp->clear_all_call_backs();
+ sp->disable_processing();
+ config::max_txn_commit_byte = old_max_txn_commit_byte;
+ auto meta_service = get_meta_service();
+ const std::string instance_id = "test_instance";
+ constexpr int64_t tablet_id = 652;
+ put_delete_bitmap_test_rowset(meta_service.get(), instance_id,
tablet_id, 2, "r2");
+ put_delete_bitmap_test_rowset(meta_service.get(), instance_id,
tablet_id, 5, "r4-5", 4);
+ auto output_key = meta_rowset_key({instance_id, tablet_id, 5});
+ auto old_key = meta_delete_bitmap_key({instance_id, tablet_id,
"r2", 4, 0});
+ const std::string old_bitmap(
+ scenario == "cleanup_split" ? DEFAULT_BLOB_SPLIT_SIZE * 20
: 10, 'o');
+ const std::string complete_bitmap = "D3|D4|D5";
+ const std::string stale_bitmap(128, 'a');
+ std::unique_ptr<Transaction> txn;
+ ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn),
TxnErrorCode::TXN_OK);
+ blob_put(txn.get(), old_key, old_bitmap, 0);
+ for (int segment = 0; segment < 2; ++segment) {
+ auto key = meta_delete_bitmap_key({instance_id, tablet_id,
"r2", 5, segment});
+ blob_put(txn.get(), key, complete_bitmap, 0);
+ }
+ ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
+
+ UpdateDeleteBitmapRequest req;
+ req.set_cloud_unique_id("test_cloud_unique_id");
+ req.set_table_id(650);
+ req.set_partition_id(651);
+ req.set_tablet_id(tablet_id);
+ req.set_lock_id(-3);
+ req.set_without_lock(true);
+ req.set_initiator(tablet_id);
+ req.set_pre_rowset_agg_start_version(4);
+ req.set_pre_rowset_agg_end_version(5);
+
req.set_enable_remove_agg_pre_rowsets_delete_bitmap_by_keys(point_delete);
+
req.set_enable_remove_pre_rowsets_delete_bitmap_by_keys(point_delete);
+ for (int segment = 0; segment < 2; ++segment) {
+ req.add_rowset_ids("r2");
+ req.add_segment_ids(segment);
+ req.add_versions(5);
+ req.add_segment_delete_bitmaps(stale_bitmap);
+ req.add_pre_rowset_versions(2);
+ }
+ auto* rowset_stats = req.add_pre_rowset_delete_bitmap_stats();
+ rowset_stats->set_rowset_id("r2");
+ auto* bitmap_stats = rowset_stats->add_delete_bitmap_stats();
+ bitmap_stats->set_segment_id(0);
+ bitmap_stats->set_version(4);
+ bitmap_stats->set_delete_bitmap_size(old_bitmap.size());
+
+ auto replace_output = [&] {
+ // Rewriting the same range changes the output ID, but not the
aggregation range
+ // for the unchanged pre-rowset. Replacing the key still
conflicts with a reader.
+ put_delete_bitmap_test_rowset(meta_service.get(), instance_id,
tablet_id, 5,
+ "replacement", same_range ? 4 :
3);
+ };
+ bool injected = false;
+ auto after_split = [&](auto&&) {
+ ASSERT_FALSE(injected);
+ injected = true;
+ // A newer compaction and its aggregation finish between two
batches of A.
+ replace_output();
+ std::unique_ptr<Transaction> newer_txn;
+ ASSERT_EQ(meta_service->txn_kv()->create_txn(&newer_txn),
TxnErrorCode::TXN_OK);
+ for (int segment = 0; segment < 2; ++segment) {
+ auto key = meta_delete_bitmap_key({instance_id, tablet_id,
"r2", 5, segment});
+ blob_put(newer_txn.get(), key, complete_bitmap, 0);
+ }
+ ASSERT_EQ(newer_txn->commit(), TxnErrorCode::TXN_OK);
+ };
+ if (scenario == "replaced" || scenario == "same_range") {
+ replace_output();
+ } else if (scenario == "removed" || scenario == "malformed") {
+ ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn),
TxnErrorCode::TXN_OK);
+ if (scenario == "removed") {
+ txn->remove(output_key);
+ } else {
+ txn->put(output_key, "invalid protobuf");
+ }
+ ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
+ } else if (after_check) {
+
sp->set_call_back("update_delete_bitmap:check_pre_rowset_aggregation",
[&](auto&&) {
+ injected = true;
+ // Change only the rowset key: the conflict must come from
+ // the validity read, not a concurrent bitmap write.
+ replace_output();
+ });
+ } else if (scenario == "write_split") {
+ config::max_txn_commit_byte = 600;
+ sp->set_call_back("update_delete_bitmap:commit:err",
[&](auto&& args) {
+ ASSERT_EQ(try_any_cast<size_t>(args[1]), 1);
+ after_split(args);
+ });
+ } else if (scenario == "cleanup_split") {
+ config::max_txn_commit_byte = 1000;
+
sp->set_call_back("update_delete_bitmap:remove_pre_rowsets:commit",
after_split);
+ }
+ sp->enable_processing();
+ UpdateDeleteBitmapResponse res;
+ brpc::Controller cntl;
+ meta_service->update_delete_bitmap(&cntl, &req, &res, nullptr);
+ auto expected_code = after_check ?
MetaServiceCode::KV_TXN_CONFLICT
+ : scenario == "malformed" ?
MetaServiceCode::PROTOBUF_PARSE_ERR
+ :
MetaServiceCode::OK;
+ ASSERT_EQ(res.status().code(), expected_code) <<
res.status().msg();
+ EXPECT_EQ(injected,
+ after_check || scenario == "write_split" || scenario ==
"cleanup_split");
+ if (valid) {
+ // Repeating an aggregation whose output is still live remains
valid.
+ meta_service->update_delete_bitmap(&cntl, &req, &res, nullptr);
+ ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
+ }
+
+ ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn),
TxnErrorCode::TXN_OK);
+ for (int segment = 0; segment < 2; ++segment) {
+ auto key = meta_delete_bitmap_key({instance_id, tablet_id,
"r2", 5, segment});
+ ValueBuf value;
+ ASSERT_EQ(blob_get(txn.get(), key, &value),
TxnErrorCode::TXN_OK);
+ EXPECT_EQ(value.value(), valid ? stale_bitmap :
complete_bitmap);
+ }
+ // Cleanup may have committed a prefix before becoming stale, but
must stop before
+ // removing the remaining keys. Other stale requests must preserve
the entire source.
+ auto remaining_key = encode_blob_key(old_key, 0, scenario ==
"cleanup_split" ? 19 : 0);
+ std::string remaining_value;
+ EXPECT_EQ(txn->get(remaining_key, &remaining_value),
+ valid ? TxnErrorCode::TXN_KEY_NOT_FOUND :
TxnErrorCode::TXN_OK);
+ }
+ }
+}
+
void update_delete_bitmap_with_remove_pre(MetaServiceProxy* meta_service,
int64_t table_id,
int64_t tablet_id, bool inject =
false,
bool rowset_non_exist = false,
bool use_delete_bitmap_stats = true)
{
+ put_delete_bitmap_test_rowset(meta_service, "test_instance", tablet_id, 6,
"r4-6", 4);
// create rowset, if `rowset_non_exist` enabled, only r4 exists
{
std::unique_ptr<Transaction> txn;
@@ -7933,6 +8089,7 @@ TEST(MetaServiceTest, EmptyPreRowsetStatsUsesKeyRemoval) {
constexpr int64_t tablet_id = 622;
const std::string rowset_id = "empty_pre_rowset_stats";
put_delete_bitmap_test_rowset(meta_service.get(), instance_id, tablet_id,
1, rowset_id);
+ put_delete_bitmap_test_rowset(meta_service.get(), instance_id, tablet_id,
2, "r1-2", 1);
auto old_delete_bitmap_key = meta_delete_bitmap_key({instance_id,
tablet_id, rowset_id, 1, 0});
const std::string old_delete_bitmap = "old_delete_bitmap";
@@ -7977,6 +8134,7 @@ TEST(MetaServiceTest,
UnderestimatedPreRowsetDeleteBitmapSizeLeavesSkippedTail)
constexpr int64_t tablet_id = 632;
const std::string rowset_id = "underestimated_delete_bitmap";
put_delete_bitmap_test_rowset(meta_service.get(), instance_id, tablet_id,
1, rowset_id);
+ put_delete_bitmap_test_rowset(meta_service.get(), instance_id, tablet_id,
2, "r1-2", 1);
auto old_delete_bitmap_key = meta_delete_bitmap_key({instance_id,
tablet_id, rowset_id, 1, 0});
const std::string old_delete_bitmap(DEFAULT_BLOB_SPLIT_SIZE * 3 + 1, 'a');
@@ -8059,6 +8217,7 @@ static void
test_get_delete_bitmap_during_point_cleanup(size_t delete_bitmap_siz
constexpr int64_t tablet_id = 642;
const std::string rowset_id = "point_cleanup_during_paginated_read";
put_delete_bitmap_test_rowset(meta_service.get(), instance_id, tablet_id,
1, rowset_id);
+ put_delete_bitmap_test_rowset(meta_service.get(), instance_id, tablet_id,
2, "r1-2", 1);
auto old_delete_bitmap_key = meta_delete_bitmap_key({instance_id,
tablet_id, rowset_id, 1, 0});
std::string old_delete_bitmap(DEFAULT_BLOB_SPLIT_SIZE, 'a');
@@ -8502,6 +8661,7 @@ TEST(MetaServiceTest,
RemovePreDeleteBitmapBatchesEachBlobKey) {
const std::string& cloud_unique_id);
auto instance_id = get_instance_id(meta_service->resource_mgr(),
"test_cloud_unique_id");
constexpr int64_t tablet_id = 612;
+ put_delete_bitmap_test_rowset(meta_service.get(), instance_id, tablet_id,
2, "r1-2", 1);
const std::string rowset_id = "batch_blob_keys_rowset";
const std::string value(DEFAULT_BLOB_SPLIT_SIZE * 20, 'a');
auto delete_bitmap_key = meta_delete_bitmap_key({instance_id, tablet_id,
rowset_id, 1, 0});
diff --git
a/regression-test/suites/compaction/test_cloud_mow_compaction_agg_bitmap_two_be.groovy
b/regression-test/suites/compaction/test_cloud_mow_compaction_agg_bitmap_two_be.groovy
new file mode 100644
index 00000000000..fcfd96582c2
--- /dev/null
+++
b/regression-test/suites/compaction/test_cloud_mow_compaction_agg_bitmap_two_be.groovy
@@ -0,0 +1,145 @@
+// 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
+
+// Use an existing cloud Docker image with the debug points below enabled.
+// Set image and excludeDockerTest=false in regression-conf-custom.groovy,
then run:
+// bash run-regression-test.sh --run -d compaction \
+// -s test_cloud_mow_compaction_agg_bitmap_two_be -noKillDocker
+// The final comparison fails on affected builds: 18 rows instead of 12, with
+// stale (k, 0) rows for k=0,2,4,6,8,10. No generated .out file is required.
+suite("test_cloud_mow_compaction_agg_bitmap_two_be", "docker") {
+ def options = new ClusterOptions()
+ options.cloudMode = true
+ options.setFeNum(1)
+ options.setBeNum(2)
+ options.enableDebugPoints()
+ options.beConfigs += [
+ 'disable_auto_compaction=true',
+ 'enable_parallel_cumu_compaction=false',
+ 'cumulative_compaction_min_deltas=2',
+ 'delete_bitmap_store_read_version=1',
+ 'delete_bitmap_store_write_version=1',
+ 'enable_sync_tablet_delete_bitmap_by_cache=false',
+ 'enable_agg_and_remove_pre_rowsets_delete_bitmap=true',
+ 'enable_remove_agg_pre_rowsets_delete_bitmap_by_keys=true',
+ 'enable_remove_pre_rowsets_delete_bitmap_by_keys=true',
+ ]
+
+ docker(options) {
+ sql "DROP TABLE IF EXISTS test_cloud_mow_compaction_agg_bitmap_two_be"
+ sql """
+ CREATE TABLE test_cloud_mow_compaction_agg_bitmap_two_be (k
BIGINT, v INT)
+ UNIQUE KEY(k)
+ DISTRIBUTED BY HASH(k) BUCKETS 1
+ PROPERTIES (
+ "replication_num" = "1",
+ "enable_unique_key_merge_on_write" = "true",
+ "disable_auto_compaction" = "true"
+ )
+ """
+ // R2 contains all old rows; R3, R4 and R5 overwrite disjoint sets of
keys.
+ sql """INSERT INTO test_cloud_mow_compaction_agg_bitmap_two_be
+ SELECT number, 0 FROM numbers("number" = "12")"""
+ sql """INSERT INTO test_cloud_mow_compaction_agg_bitmap_two_be
+ SELECT number, 10 FROM numbers("number" = "12") WHERE number %
2 = 0"""
+ sql """INSERT INTO test_cloud_mow_compaction_agg_bitmap_two_be
+ SELECT number, 20 FROM numbers("number" = "12") WHERE number %
4 = 1"""
+ sql """INSERT INTO test_cloud_mow_compaction_agg_bitmap_two_be
+ SELECT number, 30 FROM numbers("number" = "12") WHERE number %
4 = 3"""
+
+ def actualSql = "SELECT k, v FROM
test_cloud_mow_compaction_agg_bitmap_two_be ORDER BY k, v"
+ def expectedSql = """SELECT number AS k,
+ CASE WHEN number % 2 = 0 THEN 10 WHEN number % 4 = 1 THEN 20
ELSE 30 END AS v
+ FROM numbers("number" = "12") ORDER BY k, v"""
+ quickTest("before_compaction", actualSql, expectedSql)
+
+ def tabletId = sql_return_maparray("SHOW TABLETS FROM
test_cloud_mow_compaction_agg_bitmap_two_be")[0].TabletId
+ def beA = cluster.getBeByIndex(1)
+ def beB = cluster.getBeByIndex(2)
+ def selector =
"CloudSizeBasedCumulativeCompactionPolicy::pick_input_rowsets.set_input_rowsets"
+ def barrier =
"CumulativeCompaction.modify_rowsets.cloud_update_delete_bitmap_without_lock.block"
+ def request = { be, method, path ->
+ def (code, out, err) = curl(method,
"http://${be.host}:${be.httpPort}${path}")
+ assertEquals(0, code, "${method} ${path}: ${err}")
+ return out
+ }
+ def rowsets = { be ->
+ parseJson(request(be, "GET",
"/api/compaction/show?tablet_id=${tabletId}")).rowsets
+ }
+ def running = { be ->
+ def status = parseJson(request(be, "GET",
"/api/compaction/run_status?tablet_id=${tabletId}"))
+ assertEquals("success", status.status.toLowerCase())
+ return status.run_status
+ }
+ def submit = { be, start ->
+ be.enableDebugPoint(selector, [tablet_id: tabletId, start_version:
"${start}", end_version: "5"])
+ def result = parseJson(request(be, "POST",
+
"/api/compaction/run?tablet_id=${tabletId}&compact_type=cumulative"))
+ assertEquals("Success", result.status, result.toString())
+ }
+ def waitForAggregation = { be, start ->
+ awaitUntil(120) {
+ !running(be) && new File(be.getLogFilePath()).readLines().any {
+ it.contains("agg pre rowsets delete bitmap to ms.
tablet_id=${tabletId},") &&
+ it.contains("output version=[${start}-5],
cost(us)=")
+ }
+ }
+ }
+
+ try {
+ // A publishes R45, computes D4 | D5 for R2, then pauses before
the MS write.
+ beA.enableDebugPoint(barrier, [timeout: "600"])
+ // Cache warmup may have loaded A before all four inserts were
published.
+ assertTrue(request(beA, "GET",
"/api/clear_cache/CloudTabletCache").contains("prune win"))
+ def initialRowsets = rowsets(beA)
+ assertTrue(initialRowsets.any { it.contains("[4-4]") },
initialRowsets.toString())
+ assertTrue(initialRowsets.any { it.contains("[5-5]") },
initialRowsets.toString())
+ submit(beA, 4)
+ def beALog = new File(beA.getLogFilePath())
+ awaitUntil(120) {
+ beALog.text.contains("start debug block ${barrier}")
+ }
+ assertTrue(running(beA))
+ assertTrue(rowsets(beA).any { it.contains("[4-5]") })
+
+ // B must reload the published R45 instead of using its
pre-compaction tablet.
+ assertTrue(request(beB, "GET",
"/api/clear_cache/CloudTabletCache").contains("prune win"))
+ submit(beB, 3)
+ waitForAggregation(beB, 3)
+ assertTrue(rowsets(beB).any { it.contains("[3-5]") })
+ assertTrue(running(beA))
+ // B stores D3 | D4 | D5 at version 5 and removes source versions
3 and 4.
+ quickTest("after_B_while_A_blocked", actualSql, expectedSql)
+
+ // A's delayed D4 | D5 overwrites B's version-5 bitmap on affected
builds.
+ beA.disableDebugPoint(barrier)
+ waitForAggregation(beA, 4)
+ quickTest("after_A_released", actualSql, expectedSql)
+ } finally {
+ beA.disableDebugPoint(barrier)
+ beA.disableDebugPoint(selector)
+ beB.disableDebugPoint(selector)
+ }
+
+ // Both BEs can retain the complete bitmap in memory; restart both for
a cold read.
+ cluster.restartBackends()
+ logger.info("Rows after restarting both BEs: ${sql(actualSql)}")
+ quickTest("after_restart", actualSql, expectedSql)
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]