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 a60f34a296e [fix](cloud) Skip stale delete bitmap aggregation (#68084)
a60f34a296e is described below
commit a60f34a296ecff4d720a6d68faab6f4b68679a42
Author: meiyi <[email protected]>
AuthorDate: Tue Oct 20 09:45:31 2026 +0800
[fix](cloud) Skip stale delete bitmap aggregation (#68084)
Problem Summary: Delayed compaction aggregation can overwrite newer
delete bitmaps and make deleted rows visible. Validate the compaction
output range in every MS transaction, stopping stale requests before
writes or cleanup. Preserve non-OK error checks and add unit tests and a
two-BE Docker reproducer.
---
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 8a5e2953c99..b12569a9afc 100644
--- a/cloud/src/meta-service/meta_service.cpp
+++ b/cloud/src/meta-service/meta_service.cpp
@@ -3935,7 +3935,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,
@@ -3969,7 +4013,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();
@@ -3983,7 +4027,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});
@@ -3994,7 +4041,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;
}
}
}
@@ -4003,7 +4050,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
@@ -4015,7 +4062,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;
@@ -4031,7 +4078,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())) {
@@ -4039,7 +4086,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);
@@ -4050,7 +4097,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) {
@@ -4058,14 +4105,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
@@ -4075,7 +4122,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) {
@@ -4111,12 +4158,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="
@@ -4149,7 +4199,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(
@@ -4213,7 +4263,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;
}
@@ -4313,6 +4363,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);
@@ -4439,10 +4493,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) {
@@ -4458,10 +4515,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 e40d79998f2..f1b6a5734d2 100644
--- a/cloud/test/meta_service_test.cpp
+++ b/cloud/test/meta_service_test.cpp
@@ -7738,23 +7738,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;
@@ -8007,6 +8163,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";
@@ -8051,6 +8208,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');
@@ -8133,6 +8291,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');
@@ -8240,6 +8399,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]