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 cf1288086ae branch-4.1: [fix](cloud) Correct cumulative point
advancement when enable parallel cumulative compaction (#65915) (#67425)
cf1288086ae is described below
commit cf1288086ae5b75397ac4b370f911f5552ff00a1
Author: meiyi <[email protected]>
AuthorDate: Mon Sep 7 22:40:59 2026 +0800
branch-4.1: [fix](cloud) Correct cumulative point advancement when enable
parallel cumulative compaction (#65915) (#67425)
pick https://github.com/apache/doris/pull/65915
---
be/src/cloud/cloud_base_compaction.cpp | 3 +
be/src/cloud/cloud_cumulative_compaction.cpp | 281 ++++++++++---
be/src/cloud/cloud_cumulative_compaction.h | 11 +-
be/src/cloud/cloud_index_change_compaction.cpp | 24 +-
be/src/storage/compaction/compaction.cpp | 20 +
be/src/storage/compaction/compaction.h | 3 +
be/test/cloud/cloud_compaction_test.cpp | 461 +++++++++++++++++++++
.../cloud_index_change_compaction_test.cpp | 20 +
cloud/src/meta-service/meta_service_job.cpp | 113 ++++-
cloud/test/meta_service_job_test.cpp | 348 +++++++++++++++-
10 files changed, 1186 insertions(+), 98 deletions(-)
diff --git a/be/src/cloud/cloud_base_compaction.cpp
b/be/src/cloud/cloud_base_compaction.cpp
index 33fca48daed..87e2378fdba 100644
--- a/be/src/cloud/cloud_base_compaction.cpp
+++ b/be/src/cloud/cloud_base_compaction.cpp
@@ -439,6 +439,9 @@ Status CloudBaseCompaction::modify_rowsets() {
// ATTN: MUST NOT update `cumu_compaction_cnt` or `cumu_point` which
are used when sync rowsets, otherwise may cause
// the tablet to be unable to synchronize the rowset meta changes
generated by cumu compaction.
cloud_tablet()->set_base_compaction_cnt(stats.base_compaction_cnt());
+ if (stats.cumulative_point() >
cloud_tablet()->cumulative_layer_point()) {
+ cloud_tablet()->last_sync_time_s = 0;
+ }
if (output_rowset_delete_bitmap) {
_tablet->tablet_meta()->delete_bitmap().merge(*output_rowset_delete_bitmap);
}
diff --git a/be/src/cloud/cloud_cumulative_compaction.cpp
b/be/src/cloud/cloud_cumulative_compaction.cpp
index ee8784fb8c1..cd61ea9c426 100644
--- a/be/src/cloud/cloud_cumulative_compaction.cpp
+++ b/be/src/cloud/cloud_cumulative_compaction.cpp
@@ -19,6 +19,8 @@
#include <gen_cpp/cloud.pb.h>
+#include <random>
+
#include "cloud/cloud_meta_mgr.h"
#include "cloud/cloud_tablet_mgr.h"
#include "cloud/config.h"
@@ -29,6 +31,7 @@
#include "service/backend_options.h"
#include "storage/compaction/compaction.h"
#include "storage/compaction/cumulative_compaction_policy.h"
+#include "storage/compaction/cumulative_compaction_time_series_policy.h"
#include "util/debug_points.h"
#include "util/trace.h"
#include "util/uuid_generator.h"
@@ -44,7 +47,8 @@ bvar::LatencyRecorder
g_cu_compaction_hold_delete_bitmap_lock_time_ms(
CloudCumulativeCompaction::CloudCumulativeCompaction(CloudStorageEngine&
engine,
CloudTabletSPtr tablet)
: CloudCompactionMixin(engine, tablet,
- "BaseCompaction:" +
std::to_string(tablet->tablet_id())) {}
+ "BaseCompaction:" +
std::to_string(tablet->tablet_id())),
+
_enable_parallel_cumu_compaction(config::enable_parallel_cumu_compaction) {}
CloudCumulativeCompaction::~CloudCumulativeCompaction() = default;
@@ -65,11 +69,11 @@ Status CloudCumulativeCompaction::prepare_compact() {
std::vector<std::shared_ptr<CloudCumulativeCompaction>> cumu_compactions;
_engine.get_cumu_compaction(_tablet->tablet_id(), cumu_compactions);
- if (!cumu_compactions.empty()) {
- for (auto& cumu : cumu_compactions) {
- _max_conflict_version =
- std::max(_max_conflict_version,
cumu->_input_rowsets.back()->end_version());
- }
+ for (const auto& cumu : cumu_compactions) {
+ _min_conflict_version =
+ std::min(_min_conflict_version,
cumu->_input_rowsets.front()->start_version());
+ _max_conflict_version =
+ std::max(_max_conflict_version,
cumu->_input_rowsets.back()->end_version());
}
bool need_sync_tablet = true;
@@ -95,7 +99,10 @@ Status CloudCumulativeCompaction::prepare_compact() {
// we meet a delete version, should increase the cumulative point
to let base compaction handle the delete version.
// plus 1 to skip the delete version.
// NOTICE: after that, the cumulative point may be larger than max
version of this tablet, but it doesn't matter.
- update_cumulative_point();
+ // The picker only preserves a delete version reached continuously
from the
+ // cumulative point.
+ DORIS_CHECK_LE(_picked_cumulative_point,
_last_delete_version.first);
+ update_cumulative_point(_picked_cumulative_point,
_last_delete_version.first + 1);
if (!config::enable_sleep_between_delete_cumu_compaction) {
st = Status::Error<CUMULATIVE_MEET_DELETE_VERSION>(
"cumulative compaction meet delete version");
@@ -151,7 +158,7 @@ Status CloudCumulativeCompaction::request_global_lock() {
compaction_job->add_input_versions(_input_rowsets.front()->start_version());
compaction_job->add_input_versions(_input_rowsets.back()->end_version());
// Set input version range to let meta-service check version range conflict
-
compaction_job->set_check_input_versions_range(config::enable_parallel_cumu_compaction);
+
compaction_job->set_check_input_versions_range(_enable_parallel_cumu_compaction);
cloud::StartTabletJobResponse resp;
Status st = _engine.meta_mgr().prepare_tablet_job(job, &resp);
if (!st.ok()) {
@@ -245,12 +252,33 @@ Status CloudCumulativeCompaction::execute_compact() {
Status CloudCumulativeCompaction::modify_rowsets() {
// calculate new cumulative point
- int64_t input_cumulative_point = cloud_tablet()->cumulative_layer_point();
+ int64_t input_cumulative_point;
+ int64_t proposal_base_compaction_cnt;
+ int64_t proposal_cumulative_compaction_cnt;
+ TabletState input_tablet_state;
+ int64_t input_alter_version;
+ {
+ std::shared_lock rlock(_tablet->get_header_lock());
+ input_cumulative_point = cloud_tablet()->cumulative_layer_point();
+ proposal_base_compaction_cnt = cloud_tablet()->base_compaction_cnt();
+ proposal_cumulative_compaction_cnt =
cloud_tablet()->cumulative_compaction_cnt();
+ input_tablet_state = _tablet->tablet_state();
+ input_alter_version = cloud_tablet()->alter_version();
+ }
auto compaction_policy =
cloud_tablet()->tablet_meta()->compaction_policy();
- int64_t new_cumulative_point =
- _engine.cumu_compaction_policy(compaction_policy)
- ->new_cumulative_point(cloud_tablet(), _output_rowset,
_last_delete_version,
- input_cumulative_point);
+ int64_t new_cumulative_point = input_cumulative_point;
+ if (!_enable_parallel_cumu_compaction && input_tablet_state ==
TABLET_NOTREADY &&
+ _output_rowset->start_version() > input_cumulative_point) {
+ // Historical rowsets are absent from a schema-change target until
conversion finishes.
+ DORIS_CHECK_LE(input_cumulative_point, input_alter_version);
+ DORIS_CHECK_GT(_output_rowset->start_version(), input_alter_version);
+ } else if (!_enable_parallel_cumu_compaction ||
+ _output_rowset->start_version() == input_cumulative_point) {
+ new_cumulative_point =
+ _engine.cumu_compaction_policy(compaction_policy)
+ ->new_cumulative_point(cloud_tablet(), _output_rowset,
_last_delete_version,
+ input_cumulative_point);
+ }
// commit compaction job
cloud::TabletJobInfoPB job;
auto idx = job.mutable_idx();
@@ -263,6 +291,8 @@ Status CloudCumulativeCompaction::modify_rowsets() {
compaction_job->set_initiator(BackendOptions::get_localhost() + ':' +
std::to_string(config::heartbeat_service_port));
compaction_job->set_type(cloud::TabletCompactionJobPB::CUMULATIVE);
+ compaction_job->set_base_compaction_cnt(proposal_base_compaction_cnt);
+
compaction_job->set_cumulative_compaction_cnt(proposal_cumulative_compaction_cnt);
compaction_job->set_input_cumulative_point(input_cumulative_point);
compaction_job->set_output_cumulative_point(new_cumulative_point);
compaction_job->set_num_input_rows(_input_row_num);
@@ -308,6 +338,39 @@ Status CloudCumulativeCompaction::modify_rowsets() {
}
});
+ DBUG_EXECUTE_IF("CloudCumulativeCompaction::modify_rowsets.random_sleep", {
+ auto probability = dp->param("probability", dp->param("percent", 0.0));
+ DORIS_CHECK(probability >= 0.0 && probability <= 1.0);
+ static thread_local std::mt19937 gen(std::random_device {}());
+ std::bernoulli_distribution inject_sleep {probability};
+ if (inject_sleep(gen)) {
+ auto max_sleep_ms = dp->param<int64_t>(
+ "max_sleep_ms", dp->param<int64_t>("max_sleep_time_ms",
+
dp->param<int64_t>("max_sleep_time", 0)));
+ DORIS_CHECK(max_sleep_ms >= 0);
+ std::uniform_int_distribution<int64_t> sleep_dist(0, max_sleep_ms);
+ auto sleep_ms = sleep_dist(gen);
+ LOG(INFO) <<
"CloudCumulativeCompaction::modify_rowsets.random_sleep"
+ << ", tablet_id=" << _tablet->tablet_id() << ",
sleep_ms=" << sleep_ms
+ << ", probability=" << probability;
+ std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms));
+ }
+ });
+
+ DBUG_EXECUTE_IF("CloudCumulativeCompaction::modify_rowsets.random_fail", {
+ auto probability = dp->param("probability", dp->param("percent", 0.0));
+ DORIS_CHECK(probability >= 0.0 && probability <= 1.0);
+ static thread_local std::mt19937 gen(std::random_device {}());
+ std::bernoulli_distribution inject_fail {probability};
+ if (inject_fail(gen)) {
+ LOG(WARNING) <<
"CloudCumulativeCompaction::modify_rowsets.random_fail"
+ << ", tablet_id=" << _tablet->tablet_id()
+ << ", probability=" << probability;
+ return Status::InternalError(
+ "debug cloud cumulative compaction modify rowsets random
failed");
+ }
+ });
+
DeleteBitmapPtr output_rowset_delete_bitmap = nullptr;
int64_t initiator = this->initiator();
int64_t get_delete_bitmap_lock_start_time = 0;
@@ -375,20 +438,10 @@ Status CloudCumulativeCompaction::modify_rowsets() {
cloud_tablet()->set_last_cumu_compaction_success_time(std::max(cloud_tablet()->last_cumu_compaction_success_time(),
stats.last_cumu_compaction_time_ms()));
cloud_tablet()->set_last_full_compaction_success_time(std::max(cloud_tablet()->last_full_compaction_success_time(),
stats.last_full_compaction_time_ms()));
// clang-format on
- if (cloud_tablet()->cumulative_compaction_cnt() >=
stats.cumulative_compaction_cnt()) {
- // This could happen while calling `sync_tablet_rowsets` during
`commit_tablet_job`, or parallel cumu compactions which are
- // committed later increase tablet.cumulative_compaction_cnt (see
CloudCompactionTest.parallel_cumu_compaction)
+ if
(!should_apply_cumulative_compaction_result(stats.cumulative_compaction_cnt()))
{
return Status::OK();
}
// Try to make output rowset visible immediately in tablet cache,
instead of waiting for next synchronization from meta-service.
- if (stats.cumulative_point() >
cloud_tablet()->cumulative_layer_point() &&
- stats.cumulative_compaction_cnt() !=
cloud_tablet()->cumulative_compaction_cnt() + 1) {
- // This could happen when there are multiple parallel cumu
compaction committed, tablet cache lags several
- // cumu compactions behind meta-service
(stats.cumulative_compaction_cnt > tablet.cumulative_compaction_cnt + 1).
- // If `cumu_point` of the tablet cache also falls behind, MUST
ONLY synchronize tablet cache from meta-service,
- // otherwise may cause the tablet to be unable to synchronize the
rowset meta changes generated by other cumu compaction.
- return Status::OK();
- }
if (_input_rowsets.size() == 1) {
DCHECK_EQ(_output_rowset->version(), _input_rowsets[0]->version());
// MUST NOT move input rowset to stale path
@@ -492,34 +545,73 @@ Status CloudCumulativeCompaction::garbage_collection() {
return st;
}
-Status CloudCumulativeCompaction::pick_rowsets_to_compact() {
- _input_rowsets.clear();
+Status CloudCumulativeCompaction::advance_cumulative_point_before_pick(
+ int64_t min_conflict_version) {
+ if (!_enable_parallel_cumu_compaction) {
+ return Status::OK();
+ }
- std::vector<RowsetSharedPtr> candidate_rowsets;
+ auto compaction_policy =
+
_engine.cumu_compaction_policy(cloud_tablet()->tablet_meta()->compaction_policy());
+ int64_t input_cumulative_point;
+ int64_t output_cumulative_point;
+ std::vector<RowsetSharedPtr> candidates;
{
std::shared_lock rlock(_tablet->get_header_lock());
+ input_cumulative_point = cloud_tablet()->cumulative_layer_point();
+ output_cumulative_point = input_cumulative_point;
_base_compaction_cnt = cloud_tablet()->base_compaction_cnt();
_cumulative_compaction_cnt =
cloud_tablet()->cumulative_compaction_cnt();
- int64_t candidate_version = std::max(
- std::max(cloud_tablet()->cumulative_layer_point(),
_max_conflict_version + 1),
- cloud_tablet()->alter_version() + 1);
- // Get all rowsets whose version >= `candidate_version` as candidate
rowsets
cloud_tablet()->traverse_rowsets_unlocked(
- [&candidate_rowsets, candidate_version](const RowsetSharedPtr&
rs) {
- if (rs->start_version() >= candidate_version) {
- candidate_rowsets.push_back(rs);
+ [&candidates, input_cumulative_point,
+ min_conflict_version](const RowsetSharedPtr& rs) {
+ if (rs->start_version() >= input_cumulative_point &&
+ rs->end_version() < min_conflict_version) {
+ candidates.push_back(rs);
}
});
}
- if (candidate_rowsets.empty()) {
- return Status::Error<CUMULATIVE_NO_SUITABLE_VERSION>(
- "no suitable versions: candidate rowsets empty");
+ std::sort(candidates.begin(), candidates.end(), Rowset::comparator);
+ const bool is_time_series_policy = compaction_policy->name() ==
CUMULATIVE_TIME_SERIES_POLICY;
+ Version no_delete_version {-1, -1};
+ for (const auto& rowset : candidates) {
+ if (rowset->start_version() != output_cumulative_point) {
+ break;
+ }
+
+ auto rowset_meta = rowset->rowset_meta();
+ if (rowset_meta->has_delete_predicate()) {
+ output_cumulative_point = rowset->end_version() + 1;
+ continue;
+ }
+ // A time-series singleton is a raw delta. Its post-compaction point
rule is not safe here.
+ if (rowset_meta->is_segments_overlapping() ||
+ (is_time_series_policy && rowset_meta->is_singleton_delta())) {
+ break;
+ }
+
+ int64_t new_cumulative_point = compaction_policy->new_cumulative_point(
+ cloud_tablet(), rowset, no_delete_version,
output_cumulative_point);
+ if (new_cumulative_point == output_cumulative_point) {
+ break;
+ }
+ DORIS_CHECK_EQ(new_cumulative_point, rowset->end_version() + 1);
+ output_cumulative_point = new_cumulative_point;
}
- std::sort(candidate_rowsets.begin(), candidate_rowsets.end(),
Rowset::comparator);
- if (auto st = check_version_continuity(candidate_rowsets); !st.ok()) {
- DCHECK(false) << st;
- return st;
+ if (output_cumulative_point == input_cumulative_point) {
+ return Status::OK();
}
+ update_cumulative_point(input_cumulative_point, output_cumulative_point);
+ return Status::Error<CUMULATIVE_NO_SUITABLE_VERSION>(
+ "cumulative point advanced before picking rowsets");
+}
+
+Status CloudCumulativeCompaction::pick_rowsets_to_compact() {
+ _input_rowsets.clear();
+
+ int64_t min_conflict_version = _min_conflict_version;
+ int64_t max_conflict_version = _max_conflict_version;
+
RETURN_IF_ERROR(advance_cumulative_point_before_pick(min_conflict_version));
int64_t max_score = config::cumulative_compaction_max_deltas;
double process_memory_usage =
@@ -533,29 +625,93 @@ Status
CloudCumulativeCompaction::pick_rowsets_to_compact() {
config::cumulative_compaction_min_deltas + 1);
}
- size_t compaction_score = 0;
auto compaction_policy =
cloud_tablet()->tablet_meta()->compaction_policy();
- _engine.cumu_compaction_policy(compaction_policy)
- ->pick_input_rowsets(cloud_tablet(), candidate_rowsets, max_score,
- config::cumulative_compaction_min_deltas,
&_input_rowsets,
- &_last_delete_version, &compaction_score);
-
- if (_input_rowsets.empty()) {
- return Status::Error<CUMULATIVE_NO_SUITABLE_VERSION>(
- "no suitable versions: input rowsets empty");
- } else if (_input_rowsets.size() == 1 &&
-
!_input_rowsets.front()->rowset_meta()->is_segments_overlapping()) {
- VLOG_DEBUG << "there is only one rowset and not overlapping.
tablet_id="
- << _tablet->tablet_id() << ", version=" <<
_input_rowsets.front()->version();
- return Status::Error<CUMULATIVE_NO_SUITABLE_VERSION>(
- "no suitable versions: only one rowset and not overlapping");
+ auto pick_from_candidates = [&](std::vector<RowsetSharedPtr>& candidates) {
+ _input_rowsets.clear();
+ _last_delete_version = Version {-1, -1};
+
+ if (candidates.empty()) {
+ return Status::Error<CUMULATIVE_NO_SUITABLE_VERSION>(
+ "no suitable versions: candidate rowsets empty");
+ }
+ std::sort(candidates.begin(), candidates.end(), Rowset::comparator);
+ if (auto st = check_version_continuity(candidates); !st.ok()) {
+ DCHECK(false) << st;
+ return st;
+ }
+
+ size_t compaction_score = 0;
+ _engine.cumu_compaction_policy(compaction_policy)
+ ->pick_input_rowsets(cloud_tablet(), candidates, max_score,
+ config::cumulative_compaction_min_deltas,
&_input_rowsets,
+ &_last_delete_version, &compaction_score);
+
+ if (_input_rowsets.empty()) {
+ return Status::Error<CUMULATIVE_NO_SUITABLE_VERSION>(
+ "no suitable versions: input rowsets empty");
+ }
+ if (_input_rowsets.size() == 1 &&
+ !_input_rowsets.front()->rowset_meta()->is_segments_overlapping())
{
+ VLOG_DEBUG << "there is only one rowset and not overlapping.
tablet_id="
+ << _tablet->tablet_id() << ", version=" <<
_input_rowsets.front()->version();
+ _input_rowsets.clear();
+ return Status::Error<CUMULATIVE_NO_SUITABLE_VERSION>(
+ "no suitable versions: only one rowset and not
overlapping");
+ }
+ return Status::OK();
+ };
+
+ auto pick_from_version_range = [&](int64_t start_version, int64_t
end_version,
+ bool preserve_delete_version) {
+ std::vector<RowsetSharedPtr> candidates;
+ {
+ std::shared_lock rlock(_tablet->get_header_lock());
+ _base_compaction_cnt = cloud_tablet()->base_compaction_cnt();
+ _cumulative_compaction_cnt =
cloud_tablet()->cumulative_compaction_cnt();
+ _picked_cumulative_point =
cloud_tablet()->cumulative_layer_point();
+ int64_t range_start_version =
+ std::max(std::max(_picked_cumulative_point, start_version),
+ cloud_tablet()->alter_version() + 1);
+ cloud_tablet()->traverse_rowsets_unlocked(
+ [&candidates, range_start_version, end_version](const
RowsetSharedPtr& rs) {
+ if (rs->start_version() >= range_start_version &&
+ rs->start_version() < end_version) {
+ candidates.push_back(rs);
+ }
+ });
+ }
+ auto st = pick_from_candidates(candidates);
+ if (_last_delete_version.first != -1) {
+ DORIS_CHECK(!candidates.empty());
+ if (!preserve_delete_version ||
+ candidates.front()->start_version() !=
_picked_cumulative_point) {
+ _last_delete_version = Version {-1, -1};
+ }
+ }
+ return st;
+ };
+
+ auto st = pick_from_version_range(0, min_conflict_version, true);
+ if (!st.ok()) {
+ if (_last_delete_version.first != -1) {
+ return st;
+ }
+ if (!st.is<CUMULATIVE_NO_SUITABLE_VERSION>() ||
+ min_conflict_version == std::numeric_limits<int64_t>::max()) {
+ return st;
+ }
+ st = pick_from_version_range(max_conflict_version + 1,
std::numeric_limits<int64_t>::max(),
+ false);
+ RETURN_IF_ERROR(st);
}
apply_txn_size_truncation_and_log("CloudCumulativeCompaction");
return Status::OK();
}
-void CloudCumulativeCompaction::update_cumulative_point() {
+void CloudCumulativeCompaction::update_cumulative_point(int64_t
input_cumulative_point,
+ int64_t
output_cumulative_point) {
+ DORIS_CHECK_LT(input_cumulative_point, output_cumulative_point);
cloud::TabletJobInfoPB job;
auto idx = job.mutable_idx();
idx->set_tablet_id(_tablet->tablet_id());
@@ -569,6 +725,9 @@ void CloudCumulativeCompaction::update_cumulative_point() {
compaction_job->set_type(cloud::TabletCompactionJobPB::EMPTY_CUMULATIVE);
compaction_job->set_base_compaction_cnt(_base_compaction_cnt);
compaction_job->set_cumulative_compaction_cnt(_cumulative_compaction_cnt);
+ compaction_job->add_input_versions(input_cumulative_point);
+ compaction_job->add_input_versions(output_cumulative_point - 1);
+
compaction_job->set_check_input_versions_range(_enable_parallel_cumu_compaction);
int64_t now = time(nullptr);
compaction_job->set_lease(now + config::lease_compaction_interval_seconds);
// No need to set expiration time, since there is no output rowset
@@ -588,8 +747,6 @@ void CloudCumulativeCompaction::update_cumulative_point() {
.error(st);
return;
}
- int64_t input_cumulative_point = cloud_tablet()->cumulative_layer_point();
- int64_t output_cumulative_point = _last_delete_version.first + 1;
compaction_job->set_input_cumulative_point(input_cumulative_point);
compaction_job->set_output_cumulative_point(output_cumulative_point);
cloud::FinishTabletJobResponse finish_resp;
@@ -617,14 +774,12 @@ void CloudCumulativeCompaction::update_cumulative_point()
{
cloud_tablet()->set_last_base_compaction_success_time(std::max(cloud_tablet()->last_base_compaction_success_time(),
stats.last_base_compaction_time_ms()));
cloud_tablet()->set_last_cumu_compaction_success_time(std::max(cloud_tablet()->last_cumu_compaction_success_time(),
stats.last_cumu_compaction_time_ms()));
// clang-format on
- if (cloud_tablet()->cumulative_compaction_cnt() >=
stats.cumulative_compaction_cnt()) {
- // This could happen while calling `sync_tablet_rowsets` during
`commit_tablet_job`
+ if
(!should_apply_cumulative_compaction_result(stats.cumulative_compaction_cnt()))
{
return;
}
// ATTN: MUST NOT update `base_compaction_cnt` which are used when
sync rowsets, otherwise may cause
// the tablet to be unable to synchronize the rowset meta changes
generated by base compaction.
-
cloud_tablet()->set_cumulative_compaction_cnt(cloud_tablet()->cumulative_compaction_cnt()
+
- 1);
+
cloud_tablet()->set_cumulative_compaction_cnt(stats.cumulative_compaction_cnt());
cloud_tablet()->set_cumulative_layer_point(stats.cumulative_point());
if (stats.base_compaction_cnt() >=
cloud_tablet()->base_compaction_cnt()) {
cloud_tablet()->reset_approximate_stats(stats.num_rowsets(),
stats.num_segments(),
diff --git a/be/src/cloud/cloud_cumulative_compaction.h
b/be/src/cloud/cloud_cumulative_compaction.h
index 473e5a916b2..28f88c6432f 100644
--- a/be/src/cloud/cloud_cumulative_compaction.h
+++ b/be/src/cloud/cloud_cumulative_compaction.h
@@ -17,6 +17,7 @@
#pragma once
+#include <limits>
#include <memory>
#include <optional>
@@ -49,23 +50,31 @@ public:
int64_t get_input_num_rows() const { return _input_row_num; }
private:
+ Status advance_cumulative_point_before_pick(int64_t min_conflict_version);
+
Status pick_rowsets_to_compact();
std::string_view compaction_name() const override { return
"CloudCumulativeCompaction"; }
+protected:
Status modify_rowsets() override;
+private:
Status garbage_collection() override;
- void update_cumulative_point();
+ void update_cumulative_point(int64_t input_cumulative_point, int64_t
output_cumulative_point);
ReaderType compaction_type() const override { return
ReaderType::READER_CUMULATIVE_COMPACTION; }
int64_t _input_segments = 0;
+ // A task must use one execution mode even if the dynamic config changes
while it is running.
+ const bool _enable_parallel_cumu_compaction;
+ int64_t _min_conflict_version = std::numeric_limits<int64_t>::max();
int64_t _max_conflict_version = 0;
// Snapshot values when pick input rowsets
int64_t _base_compaction_cnt = 0;
int64_t _cumulative_compaction_cnt = 0;
+ int64_t _picked_cumulative_point = 0;
Version _last_delete_version {-1, -1};
};
diff --git a/be/src/cloud/cloud_index_change_compaction.cpp
b/be/src/cloud/cloud_index_change_compaction.cpp
index 001b9d64f4b..66e008dba84 100644
--- a/be/src/cloud/cloud_index_change_compaction.cpp
+++ b/be/src/cloud/cloud_index_change_compaction.cpp
@@ -257,8 +257,13 @@ Status CloudIndexChangeCompaction::modify_rowsets() {
compaction_job->set_id(_uuid);
compaction_job->set_initiator(BackendOptions::get_localhost() + ':' +
std::to_string(config::heartbeat_service_port));
-
compaction_job->set_input_cumulative_point(cloud_tablet()->cumulative_layer_point());
-
compaction_job->set_output_cumulative_point(cloud_tablet()->cumulative_layer_point());
+ {
+ std::shared_lock rlock(_tablet->get_header_lock());
+
compaction_job->set_input_cumulative_point(cloud_tablet()->cumulative_layer_point());
+
compaction_job->set_output_cumulative_point(cloud_tablet()->cumulative_layer_point());
+
compaction_job->set_base_compaction_cnt(cloud_tablet()->base_compaction_cnt());
+
compaction_job->set_cumulative_compaction_cnt(cloud_tablet()->cumulative_compaction_cnt());
+ }
compaction_job->set_num_input_rows(_input_row_num);
compaction_job->set_num_output_rows(_output_rowset->num_rows());
compaction_job->set_size_input_rowsets(_input_rowsets_total_size);
@@ -368,6 +373,9 @@ void
CloudIndexChangeCompaction::_update_tablet_for_base_compaction(
// ATTN: MUST NOT update `cumu_compaction_cnt` or `cumu_point` which
are used when sync rowsets, otherwise may cause
// the tablet to be unable to synchronize the rowset meta changes
generated by cumu compaction.
cloud_tablet()->set_base_compaction_cnt(stats.base_compaction_cnt());
+ if (stats.cumulative_point() >
cloud_tablet()->cumulative_layer_point()) {
+ cloud_tablet()->last_sync_time_s = 0;
+ }
if (output_rowset_delete_bitmap) {
_tablet->tablet_meta()->delete_bitmap().merge(*output_rowset_delete_bitmap);
}
@@ -389,20 +397,10 @@ void
CloudIndexChangeCompaction::_update_tablet_for_cumu_compaction(
cloud_tablet()->set_last_cumu_compaction_success_time(std::max(cloud_tablet()->last_cumu_compaction_success_time(),
stats.last_cumu_compaction_time_ms()));
cloud_tablet()->set_last_full_compaction_success_time(std::max(cloud_tablet()->last_full_compaction_success_time(),
stats.last_full_compaction_time_ms()));
// clang-format on
- if (cloud_tablet()->cumulative_compaction_cnt() >=
stats.cumulative_compaction_cnt()) {
- // This could happen while calling `sync_tablet_rowsets` during
`commit_tablet_job`, or parallel cumu compactions which are
- // committed later increase tablet.cumulative_compaction_cnt (see
CloudCompactionTest.parallel_cumu_compaction)
+ if
(!should_apply_cumulative_compaction_result(stats.cumulative_compaction_cnt()))
{
return;
}
// Try to make output rowset visible immediately in tablet cache,
instead of waiting for next synchronization from meta-service.
- if (stats.cumulative_point() >
cloud_tablet()->cumulative_layer_point() &&
- stats.cumulative_compaction_cnt() !=
cloud_tablet()->cumulative_compaction_cnt() + 1) {
- // This could happen when there are multiple parallel cumu
compaction committed, tablet cache lags several
- // cumu compactions behind meta-service
(stats.cumulative_compaction_cnt > tablet.cumulative_compaction_cnt + 1).
- // If `cumu_point` of the tablet cache also falls behind, MUST
ONLY synchronize tablet cache from meta-service,
- // otherwise may cause the tablet to be unable to synchronize the
rowset meta changes generated by other cumu compaction.
- return;
- }
if (_input_rowsets.size() == 1) {
DCHECK_EQ(_output_rowset->version(), _input_rowsets[0]->version());
// MUST NOT move input rowset to stale path.
diff --git a/be/src/storage/compaction/compaction.cpp
b/be/src/storage/compaction/compaction.cpp
index 37222b29b59..0112fc2d279 100644
--- a/be/src/storage/compaction/compaction.cpp
+++ b/be/src/storage/compaction/compaction.cpp
@@ -1610,6 +1610,26 @@
CloudCompactionMixin::CloudCompactionMixin(CloudStorageEngine& engine, CloudTabl
_uuid = ss.str();
}
+bool CloudCompactionMixin::should_apply_cumulative_compaction_result(
+ int64_t response_cumulative_compaction_cnt) {
+ int64_t local_cumulative_compaction_cnt =
cloud_tablet()->cumulative_compaction_cnt();
+ if (local_cumulative_compaction_cnt >= response_cumulative_compaction_cnt)
{
+ // sync_rowsets or another compaction has already installed this
result.
+ return false;
+ }
+ if (response_cumulative_compaction_cnt != local_cumulative_compaction_cnt
+ 1) {
+ // Only the current task's output is available locally. Sync all
missing outputs instead.
+ cloud_tablet()->last_sync_time_s = 0;
+ LOG_INFO("defer applying cumulative compaction result until tablet
sync")
+ .tag("tablet_id", _tablet->tablet_id())
+ .tag("job_id", _uuid)
+ .tag("local_cumulative_compaction_cnt",
local_cumulative_compaction_cnt)
+ .tag("response_cumulative_compaction_cnt",
response_cumulative_compaction_cnt);
+ return false;
+ }
+ return true;
+}
+
Status CloudCompactionMixin::execute_compact_impl(int64_t permits) {
OlapStopWatch watch;
diff --git a/be/src/storage/compaction/compaction.h
b/be/src/storage/compaction/compaction.h
index b4d609a3f00..4bc0b4e3ad2 100644
--- a/be/src/storage/compaction/compaction.h
+++ b/be/src/storage/compaction/compaction.h
@@ -254,6 +254,9 @@ protected:
// Returns the number of rowsets that were truncated
size_t apply_txn_size_truncation_and_log(const std::string&
compaction_name);
+ // Caller must hold the tablet header lock.
+ bool should_apply_cumulative_compaction_result(int64_t
response_cumulative_compaction_cnt);
+
CloudStorageEngine& _engine;
std::string _uuid;
diff --git a/be/test/cloud/cloud_compaction_test.cpp
b/be/test/cloud/cloud_compaction_test.cpp
index f7916ab511e..6c83d12f254 100644
--- a/be/test/cloud/cloud_compaction_test.cpp
+++ b/be/test/cloud/cloud_compaction_test.cpp
@@ -22,20 +22,26 @@
#include <gtest/gtest.h>
#include <memory>
+#include <mutex>
+#include <unordered_map>
#include "cloud/cloud_base_compaction.h"
#include "cloud/cloud_cluster_info.h"
+#include "cloud/cloud_cumulative_compaction.h"
#include "cloud/cloud_storage_engine.h"
#include "cloud/cloud_tablet.h"
#include "cloud/cloud_tablet_mgr.h"
#include "cloud/config.h"
+#include "cpp/sync_point.h"
#include "json2pb/json_to_pb.h"
+#include "storage/compaction/cumulative_compaction_time_series_policy.h"
#include "storage/olap_common.h"
#include "storage/rowset/rowset_factory.h"
#include "storage/rowset/rowset_meta.h"
#include "storage/storage_policy.h"
#include "storage/tablet/tablet_meta.h"
#include "util/defer_op.h"
+#include "util/time.h"
#include "util/uid_util.h"
namespace doris {
@@ -218,6 +224,15 @@ static RowsetSharedPtr create_rowset(Version version, int
num_segments, bool ove
return rowset;
}
+static RowsetSharedPtr create_delete_rowset(Version version) {
+ auto rowset = create_rowset(version, 0, false, 0);
+ DORIS_CHECK(rowset != nullptr);
+ DeletePredicatePB delete_predicate;
+ delete_predicate.set_version(version.second);
+ rowset->rowset_meta()->set_delete_predicate(std::move(delete_predicate));
+ return rowset;
+}
+
class TestableCloudCompaction : public CloudCompactionMixin {
public:
TestableCloudCompaction(CloudStorageEngine& engine, CloudTabletSPtr tablet)
@@ -236,6 +251,11 @@ public:
return apply_txn_size_truncation_and_log(compaction_name);
}
+ bool test_should_apply_cumulative_compaction_result(
+ int64_t response_cumulative_compaction_cnt) {
+ return
should_apply_cumulative_compaction_result(response_cumulative_compaction_cnt);
+ }
+
Status prepare_compact() override { return Status::OK(); }
ReaderType compaction_type() const override { return
ReaderType::READER_CUMULATIVE_COMPACTION; }
@@ -243,6 +263,447 @@ public:
std::string_view compaction_name() const override { return
"test_compaction"; }
};
+TEST_F(CloudCompactionTest, cumulative_result_requires_next_counter) {
+ auto tablet = std::make_shared<CloudTablet>(_engine, _tablet_meta);
+ tablet->set_cumulative_compaction_cnt(1);
+ tablet->last_sync_time_s = 1;
+ TestableCloudCompaction compaction(_engine, tablet);
+
+ std::unique_lock lock(tablet->get_header_lock());
+ EXPECT_FALSE(compaction.test_should_apply_cumulative_compaction_result(1));
+ EXPECT_EQ(tablet->last_sync_time_s, 1);
+ EXPECT_TRUE(compaction.test_should_apply_cumulative_compaction_result(2));
+ EXPECT_EQ(tablet->last_sync_time_s, 1);
+ EXPECT_FALSE(compaction.test_should_apply_cumulative_compaction_result(3));
+ EXPECT_EQ(tablet->last_sync_time_s, 0);
+}
+
+class TestableCloudCumulativeCompaction : public CloudCumulativeCompaction {
+public:
+ TestableCloudCumulativeCompaction(CloudStorageEngine& engine,
CloudTabletSPtr tablet)
+ : CloudCumulativeCompaction(engine, tablet) {}
+
+ void set_input_rowsets(const std::vector<RowsetSharedPtr>& rowsets) {
+ _input_rowsets = rowsets;
+ }
+
+ const std::vector<RowsetSharedPtr>& input_rowsets() const { return
_input_rowsets; }
+
+ void set_output_rowset(RowsetSharedPtr rowset) { _output_rowset =
std::move(rowset); }
+
+ Status test_modify_rowsets() { return modify_rowsets(); }
+};
+
+static TabletMetaSharedPtr create_cloud_compaction_test_tablet_meta(int64_t
tablet_id) {
+ return std::make_shared<TabletMeta>(1, 2, tablet_id, 15674, 4, 5,
TTabletSchema(), 6,
+ std::unordered_map<uint32_t, uint32_t>
{{7, 8}},
+ UniqueId(9, 10),
TTabletType::TABLET_TYPE_DISK,
+ TCompressionType::LZ4F);
+}
+
+static CloudTabletSPtr create_cloud_tablet_with_rowsets(CloudStorageEngine&
engine,
+ const
TabletMetaSharedPtr& tablet_meta,
+ int64_t
cumulative_point,
+
std::vector<RowsetSharedPtr> rowsets) {
+ auto tablet = std::make_shared<CloudTablet>(engine, tablet_meta);
+ auto num_rowsets = rowsets.size();
+ {
+ std::unique_lock wlock(tablet->get_header_lock());
+ tablet->add_rowsets(std::move(rowsets), false, wlock, false);
+ }
+ tablet->set_cumulative_layer_point(cumulative_point);
+
tablet->fetch_add_approximate_num_rowsets(static_cast<int64_t>(num_rowsets) -
+
tablet->fetch_add_approximate_num_rowsets(0));
+ tablet->last_sync_time_s = 1;
+ return tablet;
+}
+
+static CloudTabletSPtr create_cloud_tablet_with_rowsets(CloudStorageEngine&
engine,
+ const
TabletMetaSharedPtr& tablet_meta,
+ int64_t
cumulative_point,
+ const
std::vector<int64_t>& versions,
+ int64_t data_size =
1024 * 1024) {
+ std::vector<RowsetSharedPtr> rowsets;
+ rowsets.reserve(versions.size());
+ for (int64_t version : versions) {
+ auto rowset = create_rowset(Version(version, version), 1, false,
data_size);
+ DORIS_CHECK(rowset != nullptr);
+ rowsets.push_back(rowset);
+ }
+ return create_cloud_tablet_with_rowsets(engine, tablet_meta,
cumulative_point,
+ std::move(rowsets));
+}
+
+static std::shared_ptr<TestableCloudCumulativeCompaction>
create_inflight_cumu_compaction(
+ CloudStorageEngine& engine, const CloudTabletSPtr& tablet, int64_t
start, int64_t end) {
+ std::vector<RowsetSharedPtr> input_rowsets;
+ input_rowsets.reserve(end - start + 1);
+ for (int64_t version = start; version <= end; ++version) {
+ auto rowset = create_rowset(Version(version, version), 1, false, 1024
* 1024);
+ DORIS_CHECK(rowset != nullptr);
+ input_rowsets.push_back(rowset);
+ }
+ auto compaction =
std::make_shared<TestableCloudCumulativeCompaction>(engine, tablet);
+ compaction->set_input_rowsets(input_rowsets);
+ return compaction;
+}
+
+TEST_F(CloudCompactionTest,
base_result_with_newer_cumulative_point_forces_sync) {
+ auto* sync_point = SyncPoint::get_instance();
+ Defer clear_sync_points([&] {
+ sync_point->disable_processing();
+ sync_point->clear_all_call_backs();
+ });
+ int64_t response_cumulative_point = 6;
+ sync_point->set_call_back("CloudMetaMgr::commit_tablet_job", [&](auto&&
outcome) {
+ auto* result = try_any_cast_ret<Status>(outcome);
+ result->first = Status::OK();
+ result->second = true;
+ auto* response =
try_any_cast<cloud::FinishTabletJobResponse*>(outcome[1]);
+ response->mutable_status()->set_code(cloud::MetaServiceCode::OK);
+ auto* stats = response->mutable_stats();
+ stats->set_base_compaction_cnt(1);
+ stats->set_cumulative_compaction_cnt(0);
+ stats->set_cumulative_point(response_cumulative_point);
+ stats->set_num_rowsets(1);
+ });
+ sync_point->enable_processing();
+
+ auto run_case = [&](int64_t tablet_id, int64_t response_point, int64_t
expected_sync_time) {
+ auto input = create_rowset(Version(2, 7), 1, false, 1024);
+ auto tablet = create_cloud_tablet_with_rowsets(
+ _engine, create_cloud_compaction_test_tablet_meta(tablet_id),
6, {input});
+ auto output = create_rowset(Version(2, 7), 1, false, 1024);
+ CloudBaseCompaction compaction(_engine, tablet);
+ compaction._input_rowsets = {input};
+ compaction._output_rowset = output;
+ response_cumulative_point = response_point;
+
+ ASSERT_TRUE(compaction.modify_rowsets().ok());
+ EXPECT_EQ(tablet->cumulative_layer_point(), 6);
+ EXPECT_EQ(tablet->last_sync_time_s, expected_sync_time);
+ };
+
+ run_case(10008, 8, 0);
+ run_case(10009, 6, 1);
+}
+
+TEST_F(CloudCompactionTest, cumulative_pick_uses_local_conflict_window) {
+ auto old_min_deltas = config::cumulative_compaction_min_deltas;
+ auto old_parallel_cumu_compaction =
config::enable_parallel_cumu_compaction;
+ Defer restore_config([&] {
+ config::cumulative_compaction_min_deltas = old_min_deltas;
+ config::enable_parallel_cumu_compaction = old_parallel_cumu_compaction;
+ });
+ config::cumulative_compaction_min_deltas = 2;
+ config::enable_parallel_cumu_compaction = true;
+
+ {
+ auto tablet_meta = create_cloud_compaction_test_tablet_meta(10001);
+ auto tablet =
+ create_cloud_tablet_with_rowsets(_engine, tablet_meta, 114,
+ {114, 115, 116, 117, 118,
119, 120, 121, 122, 123,
+ 124, 125, 126, 127, 128,
129, 130, 131, 132});
+ _engine._submitted_cumu_compactions[tablet->tablet_id()] = {
+ create_inflight_cumu_compaction(_engine, tablet, 117, 119),
+ create_inflight_cumu_compaction(_engine, tablet, 126, 130)};
+
+ TestableCloudCumulativeCompaction compaction(_engine, tablet);
+ auto st = compaction.prepare_compact();
+ ASSERT_TRUE(st.ok()) << st;
+ ASSERT_EQ(compaction.input_rowsets().size(), 3);
+ EXPECT_EQ(compaction.input_rowsets().front()->start_version(), 114);
+ EXPECT_EQ(compaction.input_rowsets().back()->end_version(), 116);
+ _engine._submitted_cumu_compactions.clear();
+ }
+
+ {
+ auto tablet_meta = create_cloud_compaction_test_tablet_meta(10002);
+ auto tablet = create_cloud_tablet_with_rowsets(_engine, tablet_meta,
1, {1, 2, 3, 4});
+ _engine._submitted_cumu_compactions[tablet->tablet_id()] = {
+ create_inflight_cumu_compaction(_engine, tablet, 1, 2)};
+
+ TestableCloudCumulativeCompaction compaction(_engine, tablet);
+ auto st = compaction.prepare_compact();
+ ASSERT_TRUE(st.ok()) << st;
+ ASSERT_EQ(compaction.input_rowsets().size(), 2);
+ EXPECT_EQ(compaction.input_rowsets().front()->start_version(), 3);
+ EXPECT_EQ(compaction.input_rowsets().back()->end_version(), 4);
+ _engine._submitted_cumu_compactions.clear();
+ }
+
+ {
+ auto tablet_meta = create_cloud_compaction_test_tablet_meta(10003);
+ auto tablet = create_cloud_tablet_with_rowsets(_engine, tablet_meta,
114,
+ {114, 115, 116, 117,
118, 119});
+ _engine._submitted_cumu_compactions[tablet->tablet_id()] = {
+ create_inflight_cumu_compaction(_engine, tablet, 115, 116)};
+
+ TestableCloudCumulativeCompaction compaction(_engine, tablet);
+ auto st = compaction.prepare_compact();
+ ASSERT_TRUE(st.ok()) << st;
+ ASSERT_EQ(compaction.input_rowsets().size(), 3);
+ EXPECT_EQ(compaction.input_rowsets().front()->start_version(), 117);
+ EXPECT_EQ(compaction.input_rowsets().back()->end_version(), 119);
+ _engine._submitted_cumu_compactions.clear();
+ }
+}
+
+TEST_F(CloudCompactionTest,
serial_suffix_compaction_on_running_tablet_keeps_point) {
+ auto old_parallel_cumu_compaction =
config::enable_parallel_cumu_compaction;
+ Defer restore_config(
+ [&] { config::enable_parallel_cumu_compaction =
old_parallel_cumu_compaction; });
+ config::enable_parallel_cumu_compaction = false;
+
+ auto* sync_point = SyncPoint::get_instance();
+ Defer clear_sync_points([&] {
+ sync_point->disable_processing();
+ sync_point->clear_all_call_backs();
+ });
+ sync_point->set_call_back("CloudMetaMgr::commit_tablet_job", [&](auto&&
outcome) {
+ auto job = try_any_cast<cloud::TabletJobInfoPB>(outcome[0]);
+ ASSERT_EQ(job.compaction_size(), 1);
+ EXPECT_EQ(job.compaction(0).input_cumulative_point(), 2);
+ EXPECT_EQ(job.compaction(0).output_cumulative_point(), 2);
+
+ auto* result = try_any_cast_ret<Status>(outcome);
+ result->first = Status::InternalError("stop after checking cumulative
point");
+ result->second = true;
+ });
+ sync_point->enable_processing();
+
+ auto tablet_meta = create_cloud_compaction_test_tablet_meta(10007);
+ auto tablet = create_cloud_tablet_with_rowsets(_engine, tablet_meta, 2,
{2});
+ TestableCloudCumulativeCompaction compaction(_engine, tablet);
+ compaction.set_input_rowsets({create_rowset(Version(3, 22), 1, true, 20 *
1024 * 1024)});
+ compaction.set_output_rowset(create_rowset(Version(3, 22), 1, false, 20 *
1024 * 1024));
+
+ EXPECT_FALSE(compaction.test_modify_rowsets().ok());
+}
+
+TEST_F(CloudCompactionTest,
parallel_time_series_pick_preserves_raw_singletons) {
+ auto old_parallel_cumu_compaction =
config::enable_parallel_cumu_compaction;
+ Defer restore_config(
+ [&] { config::enable_parallel_cumu_compaction =
old_parallel_cumu_compaction; });
+ config::enable_parallel_cumu_compaction = true;
+
+ auto* sync_point = SyncPoint::get_instance();
+ Defer clear_sync_points([&] {
+ sync_point->disable_processing();
+ sync_point->clear_all_call_backs();
+ });
+ bool point_update_called = false;
+ sync_point->set_call_back("CloudMetaMgr::prepare_tablet_job", [&](auto&&
outcome) {
+ point_update_called = true;
+ auto* result = try_any_cast_ret<Status>(outcome);
+ result->first = Status::InternalError("unexpected cumulative point
update");
+ result->second = true;
+ });
+ sync_point->enable_processing();
+
+ auto tablet_meta = create_cloud_compaction_test_tablet_meta(10008);
+
tablet_meta->set_compaction_policy(std::string(CUMULATIVE_TIME_SERIES_POLICY));
+ tablet_meta->set_time_series_compaction_level_threshold(1);
+ std::vector<RowsetSharedPtr> rowsets {
+ create_rowset(Version(2, 2), 1, false, 1024 * 1024),
+ create_rowset(Version(3, 3), 1, false, 1024 * 1024),
+ };
+ for (const auto& rowset : rowsets) {
+ rowset->rowset_meta()->set_creation_time(UnixSeconds());
+ }
+ auto tablet = create_cloud_tablet_with_rowsets(_engine, tablet_meta, 2,
std::move(rowsets));
+ TestableCloudCumulativeCompaction compaction(_engine, tablet);
+
+ auto st = compaction.prepare_compact();
+ EXPECT_TRUE(st.is<ErrorCode::CUMULATIVE_NO_SUITABLE_VERSION>()) << st;
+ EXPECT_FALSE(point_update_called);
+ EXPECT_EQ(tablet->cumulative_layer_point(), 2);
+}
+
+TEST_F(CloudCompactionTest,
parallel_pick_keeps_mode_after_dynamic_config_change) {
+ auto old_parallel_cumu_compaction =
config::enable_parallel_cumu_compaction;
+ Defer restore_config(
+ [&] { config::enable_parallel_cumu_compaction =
old_parallel_cumu_compaction; });
+ config::enable_parallel_cumu_compaction = true;
+
+ auto* sync_point = SyncPoint::get_instance();
+ Defer clear_sync_points([&] {
+ sync_point->disable_processing();
+ sync_point->clear_all_call_backs();
+ });
+
+ bool prepare_called = false;
+ sync_point->set_call_back("CloudMetaMgr::prepare_tablet_job", [&](auto&&
outcome) {
+ prepare_called = true;
+ auto job = try_any_cast<cloud::TabletJobInfoPB>(outcome[0]);
+ ASSERT_EQ(job.compaction_size(), 1);
+ const auto& compaction = job.compaction(0);
+ EXPECT_EQ(compaction.type(),
cloud::TabletCompactionJobPB::EMPTY_CUMULATIVE);
+ ASSERT_EQ(compaction.input_versions_size(), 2);
+ EXPECT_EQ(compaction.input_versions(0), 2);
+ EXPECT_EQ(compaction.input_versions(1), 4);
+ EXPECT_TRUE(compaction.check_input_versions_range());
+ EXPECT_EQ(compaction.base_compaction_cnt(), 0);
+ EXPECT_EQ(compaction.cumulative_compaction_cnt(), 0);
+
+ auto* result = try_any_cast_ret<Status>(outcome);
+ result->first = Status::OK();
+ result->second = true;
+ auto* response =
try_any_cast<cloud::StartTabletJobResponse*>(outcome[1]);
+ response->mutable_status()->set_code(cloud::MetaServiceCode::OK);
+ });
+
+ bool commit_called = false;
+ sync_point->set_call_back("CloudMetaMgr::commit_tablet_job", [&](auto&&
outcome) {
+ commit_called = true;
+ auto job = try_any_cast<cloud::TabletJobInfoPB>(outcome[0]);
+ ASSERT_EQ(job.compaction_size(), 1);
+ const auto& compaction = job.compaction(0);
+ EXPECT_EQ(compaction.input_cumulative_point(), 2);
+ EXPECT_EQ(compaction.output_cumulative_point(), 5);
+
+ auto* result = try_any_cast_ret<Status>(outcome);
+ result->first = Status::OK();
+ result->second = true;
+ auto* response =
try_any_cast<cloud::FinishTabletJobResponse*>(outcome[1]);
+ response->mutable_status()->set_code(cloud::MetaServiceCode::OK);
+ auto* stats = response->mutable_stats();
+ stats->set_base_compaction_cnt(0);
+ stats->set_cumulative_compaction_cnt(2);
+ stats->set_cumulative_point(5);
+ stats->set_num_rowsets(3);
+ stats->set_num_segments(3);
+ stats->set_num_rows(0);
+ stats->set_data_size(300 * 1024 * 1024);
+ });
+ sync_point->enable_processing();
+
+ auto tablet_meta = create_cloud_compaction_test_tablet_meta(10004);
+ auto tablet =
+ create_cloud_tablet_with_rowsets(_engine, tablet_meta, 2, {2, 3,
4}, 100 * 1024 * 1024);
+ TestableCloudCumulativeCompaction compaction(_engine, tablet);
+ config::enable_parallel_cumu_compaction = false;
+ auto st = compaction.prepare_compact();
+
+ EXPECT_TRUE(st.is<ErrorCode::CUMULATIVE_NO_SUITABLE_VERSION>()) << st;
+ EXPECT_TRUE(prepare_called);
+ EXPECT_TRUE(commit_called);
+ EXPECT_EQ(tablet->cumulative_compaction_cnt(), 0);
+ EXPECT_EQ(tablet->cumulative_layer_point(), 2);
+ EXPECT_EQ(tablet->last_sync_time_s, 0);
+}
+
+TEST_F(CloudCompactionTest,
parallel_pick_advances_continuous_low_prefix_through_delete) {
+ auto old_parallel_cumu_compaction =
config::enable_parallel_cumu_compaction;
+ Defer restore_config(
+ [&] { config::enable_parallel_cumu_compaction =
old_parallel_cumu_compaction; });
+ config::enable_parallel_cumu_compaction = true;
+
+ auto* sync_point = SyncPoint::get_instance();
+ Defer clear_sync_points([&] {
+ sync_point->disable_processing();
+ sync_point->clear_all_call_backs();
+ });
+
+ bool prepare_called = false;
+ sync_point->set_call_back("CloudMetaMgr::prepare_tablet_job", [&](auto&&
outcome) {
+ prepare_called = true;
+ auto job = try_any_cast<cloud::TabletJobInfoPB>(outcome[0]);
+ ASSERT_EQ(job.compaction_size(), 1);
+ const auto& compaction = job.compaction(0);
+ EXPECT_EQ(compaction.type(),
cloud::TabletCompactionJobPB::EMPTY_CUMULATIVE);
+ ASSERT_EQ(compaction.input_versions_size(), 2);
+ EXPECT_EQ(compaction.input_versions(0), 160);
+ EXPECT_EQ(compaction.input_versions(1), 162);
+ EXPECT_TRUE(compaction.check_input_versions_range());
+
+ auto* result = try_any_cast_ret<Status>(outcome);
+ result->first = Status::OK();
+ result->second = true;
+ auto* response =
try_any_cast<cloud::StartTabletJobResponse*>(outcome[1]);
+ response->mutable_status()->set_code(cloud::MetaServiceCode::OK);
+ });
+
+ bool commit_called = false;
+ sync_point->set_call_back("CloudMetaMgr::commit_tablet_job", [&](auto&&
outcome) {
+ commit_called = true;
+ auto job = try_any_cast<cloud::TabletJobInfoPB>(outcome[0]);
+ ASSERT_EQ(job.compaction_size(), 1);
+ const auto& compaction = job.compaction(0);
+ EXPECT_EQ(compaction.input_cumulative_point(), 160);
+ EXPECT_EQ(compaction.output_cumulative_point(), 163);
+
+ auto* result = try_any_cast_ret<Status>(outcome);
+ result->first = Status::OK();
+ result->second = true;
+ auto* response =
try_any_cast<cloud::FinishTabletJobResponse*>(outcome[1]);
+ response->mutable_status()->set_code(cloud::MetaServiceCode::OK);
+ auto* stats = response->mutable_stats();
+ stats->set_base_compaction_cnt(0);
+ stats->set_cumulative_compaction_cnt(1);
+ stats->set_cumulative_point(163);
+ stats->set_num_rowsets(2);
+ stats->set_num_segments(1);
+ stats->set_num_rows(0);
+ stats->set_data_size(1024);
+ });
+ sync_point->enable_processing();
+
+ auto tablet_meta = create_cloud_compaction_test_tablet_meta(10005);
+ std::vector<RowsetSharedPtr> rowsets {
+ create_rowset(Version(160, 161), 1, false, 1024),
+ create_delete_rowset(Version(162, 162)),
+ };
+ auto tablet = create_cloud_tablet_with_rowsets(_engine, tablet_meta, 160,
std::move(rowsets));
+ TestableCloudCumulativeCompaction compaction(_engine, tablet);
+ auto st = compaction.prepare_compact();
+
+ EXPECT_FALSE(st.ok()) << st;
+ EXPECT_TRUE(prepare_called);
+ EXPECT_TRUE(commit_called);
+ EXPECT_EQ(tablet->cumulative_layer_point(), 163);
+}
+
+TEST_F(CloudCompactionTest,
parallel_pick_does_not_advance_from_high_range_delete) {
+ auto old_parallel_cumu_compaction =
config::enable_parallel_cumu_compaction;
+ Defer restore_config(
+ [&] { config::enable_parallel_cumu_compaction =
old_parallel_cumu_compaction; });
+ config::enable_parallel_cumu_compaction = true;
+
+ auto* sync_point = SyncPoint::get_instance();
+ Defer clear_sync_points([&] {
+ sync_point->disable_processing();
+ sync_point->clear_all_call_backs();
+ });
+ bool prepare_called = false;
+ sync_point->set_call_back("CloudMetaMgr::prepare_tablet_job", [&](auto&&
outcome) {
+ prepare_called = true;
+ auto* result = try_any_cast_ret<Status>(outcome);
+ result->first = Status::InternalError("unexpected cumulative point
update");
+ result->second = true;
+ });
+ sync_point->enable_processing();
+
+ auto tablet_meta = create_cloud_compaction_test_tablet_meta(10006);
+ std::vector<RowsetSharedPtr> rowsets {
+ create_rowset(Version(100, 199), 1, false, 1024),
+ create_rowset(Version(200, 300), 1, false, 1024),
+ create_rowset(Version(301, 301), 1, false, 1024),
+ create_delete_rowset(Version(302, 302)),
+ };
+ auto tablet = create_cloud_tablet_with_rowsets(_engine, tablet_meta, 100,
std::move(rowsets));
+ _engine._submitted_cumu_compactions[tablet->tablet_id()] = {
+ create_inflight_cumu_compaction(_engine, tablet, 200, 300)};
+ Defer clear_compactions([&] { _engine._submitted_cumu_compactions.clear();
});
+
+ TestableCloudCumulativeCompaction compaction(_engine, tablet);
+ auto st = compaction.prepare_compact();
+
+ EXPECT_TRUE(st.is<ErrorCode::CUMULATIVE_NO_SUITABLE_VERSION>()) << st;
+ EXPECT_FALSE(prepare_called);
+ EXPECT_EQ(tablet->cumulative_layer_point(), 100);
+}
+
TEST_F(CloudCompactionTest, test_set_storage_resource_from_input_rowsets) {
S3Conf s3_conf {.bucket = "bucket",
.prefix = "prefix",
diff --git a/be/test/storage/compaction/cloud_index_change_compaction_test.cpp
b/be/test/storage/compaction/cloud_index_change_compaction_test.cpp
index 663dd23aebb..c1654a96914 100644
--- a/be/test/storage/compaction/cloud_index_change_compaction_test.cpp
+++ b/be/test/storage/compaction/cloud_index_change_compaction_test.cpp
@@ -326,6 +326,26 @@ TEST_F(CloudIndexChangeCompactionTest, ms_ret_status_test)
{
ASSERT_TRUE(!ret.ok());
ASSERT_TRUE(contains_str(ret.to_string(), "failed in schema change"));
}
+
+ {
+ tablet->set_base_compaction_cnt(0);
+ tablet->set_cumulative_layer_point(6);
+ tablet->last_sync_time_s = 1;
+ auto index_change_compact =
std::make_shared<CloudIndexChangeCompaction>(
+ *_engine, tablet, 0, index_list, columns);
+ index_change_compact->_input_rowsets.push_back(rowset_ptr);
+ index_change_compact->_output_rowset = rowset_ptr;
+ index_change_compact->_compact_type =
cloud::TabletCompactionJobPB::BASE;
+ cloud::FinishTabletJobResponse response;
+ response.mutable_stats()->set_base_compaction_cnt(1);
+ response.mutable_stats()->set_cumulative_compaction_cnt(0);
+ response.mutable_stats()->set_cumulative_point(8);
+
+ index_change_compact->_update_tablet_for_base_compaction(response,
nullptr);
+
+ EXPECT_EQ(tablet->cumulative_layer_point(), 6);
+ EXPECT_EQ(tablet->last_sync_time_s, 0);
+ }
}
TEST_F(CloudIndexChangeCompactionTest, basic_compaction_test) {
diff --git a/cloud/src/meta-service/meta_service_job.cpp
b/cloud/src/meta-service/meta_service_job.cpp
index f357418b4fc..1553624851f 100644
--- a/cloud/src/meta-service/meta_service_job.cpp
+++ b/cloud/src/meta-service/meta_service_job.cpp
@@ -807,20 +807,96 @@ static void
remove_delete_bitmap_update_lock(std::unique_ptr<Transaction>& txn,
}
}
+static bool should_accept_cumulative_point(const std::string& instance_id,
int64_t tablet_id,
+ const TabletCompactionJobPB&
compaction,
+ const TabletCompactionJobPB&
recorded_compaction,
+ const TabletStatsPB& stats) {
+ if (compaction.type() != TabletCompactionJobPB::CUMULATIVE &&
+ compaction.type() != TabletCompactionJobPB::EMPTY_CUMULATIVE) {
+ return true;
+ }
+ // Safe because tablet stats keep max(current, proposal).
+ if (compaction.output_cumulative_point() <= stats.cumulative_point()) {
+ return true;
+ }
+ // The committed output covers [current point, proposal - 1].
+ if (compaction.type() == TabletCompactionJobPB::CUMULATIVE &&
+ compaction.input_versions_size() == 2 &&
+ compaction.input_versions(0) == stats.cumulative_point() &&
+ compaction.output_cumulative_point() == compaction.input_versions(1) +
1) {
+ return true;
+ }
+
+ // For legacy BEs:
+ // 1. The snapshot comes from START.
+ // 2. Parallel jobs may share it, so it cannot prove that a FINISH
proposal is current.
+ const bool finish_has_counters = compaction.has_base_compaction_cnt();
+ const auto& snapshot = finish_has_counters ? compaction :
recorded_compaction;
+ const int64_t snapshot_base_cnt = snapshot.base_compaction_cnt();
+ const int64_t snapshot_cumu_cnt = snapshot.cumulative_compaction_cnt();
+ // FULL also increments base_compaction_cnt
+ bool accept = snapshot_base_cnt == stats.base_compaction_cnt() &&
+ snapshot_cumu_cnt == stats.cumulative_compaction_cnt();
+ if (!finish_has_counters) {
+ if (compaction.type() == TabletCompactionJobPB::CUMULATIVE) {
+ // An advancing legacy CUMULATIVE proposal is safe only when:
+ // With point=2, accept [2-4] -> 5 but reject [5-7] -> 8.
+ // 1. The BASE/FULL layout is unchanged.
+ // 2. The current point is inside its input range.
+ // 3. The proposal is exactly input_end + 1.
+ accept = compaction.input_versions_size() == 2 &&
+ recorded_compaction.base_compaction_cnt() ==
stats.base_compaction_cnt() &&
+ compaction.input_versions(0) <= stats.cumulative_point()
&&
+ stats.cumulative_point() <= compaction.input_versions(1)
&&
+ compaction.output_cumulative_point() ==
compaction.input_versions(1) + 1;
+ }
+ // Legacy EMPTY has no output range, so its START snapshot must match
current stats.
+ }
+ if (accept) {
+ return true;
+ }
+
+ INSTANCE_LOG(INFO) << "ignore stale cumulative point=" <<
compaction.output_cumulative_point()
+ << ", tablet_id=" << tablet_id << ", job_id=" <<
compaction.id()
+ << ", base_cnt=" <<
recorded_compaction.base_compaction_cnt() << "(start),"
+ << snapshot_base_cnt << "(finish)," <<
stats.base_compaction_cnt()
+ << "(stats); cumu_cnt=" <<
recorded_compaction.cumulative_compaction_cnt()
+ << "(start)," << snapshot_cumu_cnt << "(finish),"
+ << stats.cumulative_compaction_cnt()
+ << "(stats); full_cnt=" << stats.full_compaction_cnt()
+ << "; finish_has_counters=" << finish_has_counters;
+ return false;
+}
+
int compaction_update_tablet_stats(const TabletCompactionJobPB& compaction,
TabletStatsPB* stats,
- MetaServiceCode& code, std::string& msg,
int64_t now) {
+ bool accept_cumulative_point_proposal,
MetaServiceCode& code,
+ std::string& msg, int64_t now) {
if (compaction.type() == TabletCompactionJobPB::EMPTY_CUMULATIVE) {
stats->set_cumulative_compaction_cnt(stats->cumulative_compaction_cnt() + 1);
- stats->set_cumulative_point(compaction.output_cumulative_point());
+ if (accept_cumulative_point_proposal) {
+ stats->set_cumulative_point(
+ std::max(stats->cumulative_point(),
compaction.output_cumulative_point()));
+ }
stats->set_last_cumu_compaction_time_ms(now * 1000);
} else if (compaction.type() == TabletCompactionJobPB::CUMULATIVE) {
// clang-format off
stats->set_cumulative_compaction_cnt(stats->cumulative_compaction_cnt() + 1);
- if (compaction.output_cumulative_point() > stats->cumulative_point()) {
- // After supporting parallel cumu compaction, compaction with
older cumu point may be committed after
- // new cumu point has been set, MUST NOT set cumu point back to
old value
- stats->set_cumulative_point(compaction.output_cumulative_point());
- }
+ int64_t output_cumulative_point = stats->cumulative_point();
+ if (accept_cumulative_point_proposal) {
+ output_cumulative_point =
+ std::max(compaction.output_cumulative_point(),
output_cumulative_point);
+ }
+ if (compaction.input_versions_size() == 2 &&
+ output_cumulative_point > compaction.input_versions(0) &&
+ output_cumulative_point <= compaction.input_versions(1)) {
+ LOG_WARNING("cumulative point falls inside cumulative compaction
input range")
+ .tag("job_id", compaction.id())
+ .tag("cumulative_point", output_cumulative_point)
+ .tag("input_start_version", compaction.input_versions(0))
+ .tag("input_end_version", compaction.input_versions(1));
+ output_cumulative_point = compaction.input_versions(1) + 1;
+ }
+ stats->set_cumulative_point(output_cumulative_point);
stats->set_num_rows(stats->num_rows() + (compaction.num_output_rows()
- compaction.num_input_rows()));
stats->set_data_size(stats->data_size() +
(compaction.size_output_rowsets() - compaction.size_input_rowsets()));
stats->set_num_rowsets(stats->num_rowsets() +
(compaction.num_output_rowsets() - compaction.num_input_rowsets()));
@@ -832,6 +908,16 @@ int compaction_update_tablet_stats(const
TabletCompactionJobPB& compaction, Tabl
} else if (compaction.type() == TabletCompactionJobPB::BASE) {
// clang-format off
stats->set_base_compaction_cnt(stats->base_compaction_cnt() + 1);
+ if (compaction.input_versions_size() == 2 &&
+ stats->cumulative_point() > compaction.input_versions(0) &&
+ stats->cumulative_point() <= compaction.input_versions(1)) {
+ LOG_WARNING("cumulative point falls inside base compaction input
range")
+ .tag("job_id", compaction.id())
+ .tag("cumulative_point", stats->cumulative_point())
+ .tag("input_start_version", compaction.input_versions(0))
+ .tag("input_end_version", compaction.input_versions(1));
+ stats->set_cumulative_point(compaction.input_versions(1) + 1);
+ }
stats->set_num_rows(stats->num_rows() + (compaction.num_output_rows()
- compaction.num_input_rows()));
stats->set_data_size(stats->data_size() +
(compaction.size_output_rowsets() - compaction.size_input_rowsets()));
stats->set_num_rowsets(stats->num_rowsets() +
(compaction.num_output_rowsets() - compaction.num_input_rowsets()));
@@ -1025,6 +1111,14 @@ void process_compaction_job(MetaServiceCode& code,
std::string& msg, std::string
// 4. remove compaction job
//
//==========================================================================
+ if ((compaction.type() == TabletCompactionJobPB::CUMULATIVE ||
+ compaction.type() == TabletCompactionJobPB::EMPTY_CUMULATIVE) &&
+ compaction.has_base_compaction_cnt() !=
compaction.has_cumulative_compaction_cnt()) {
+ code = MetaServiceCode::INVALID_ARGUMENT;
+ msg = "incomplete compaction counters for cumulative point proposal";
+ return;
+ }
+
// Update tablet stats
//==========================================================================
auto stats = response->mutable_stats();
@@ -1069,7 +1163,10 @@ void process_compaction_job(MetaServiceCode& code,
std::string& msg, std::string
}
}
- if (compaction_update_tablet_stats(compaction, stats, code, msg, now) ==
-1) {
+ const bool accept_cumulative_point_proposal =
should_accept_cumulative_point(
+ instance_id, tablet_id, compaction, *recorded_compaction, *stats);
+ if (compaction_update_tablet_stats(compaction, stats,
accept_cumulative_point_proposal, code,
+ msg, now) == -1) {
return;
}
diff --git a/cloud/test/meta_service_job_test.cpp
b/cloud/test/meta_service_job_test.cpp
index b2b37084040..58ce61cfce1 100644
--- a/cloud/test/meta_service_job_test.cpp
+++ b/cloud/test/meta_service_job_test.cpp
@@ -120,7 +120,8 @@ void start_compaction_job(MetaService* meta_service,
int64_t tablet_id, const st
const std::string& initiator, int
base_compaction_cnt,
int cumu_compaction_cnt,
TabletCompactionJobPB::CompactionType type,
StartTabletJobResponse& res,
- std::pair<int64_t, int64_t> input_version = {0, 0}) {
+ std::pair<int64_t, int64_t> input_version = {0, 0},
+ bool check_input_versions_range = true) {
brpc::Controller cntl;
StartTabletJobRequest req;
req.mutable_job()->mutable_idx()->set_tablet_id(tablet_id);
@@ -136,7 +137,7 @@ void start_compaction_job(MetaService* meta_service,
int64_t tablet_id, const st
if (input_version.second > 0) {
compaction->add_input_versions(input_version.first);
compaction->add_input_versions(input_version.second);
- compaction->set_check_input_versions_range(true);
+ compaction->set_check_input_versions_range(check_input_versions_range);
}
meta_service->start_tablet_job(&cntl, &req, &res, nullptr);
};
@@ -175,6 +176,45 @@ void finish_compaction_job(MetaService* meta_service,
int64_t tablet_id, const s
meta_service->finish_tablet_job(&cntl, &req, &res, nullptr);
}
+void finish_rowset_compaction_job(
+ MetaService* meta_service, int64_t tablet_id, const std::string&
job_id,
+ TabletCompactionJobPB::CompactionType type, const
doris::RowsetMetaCloudPB& output_rowset,
+ int num_input_rowsets, int64_t output_cumulative_point,
FinishTabletJobResponse& res,
+ int64_t proposal_base_compaction_cnt = 0, int64_t
proposal_cumulative_compaction_cnt = 0,
+ bool include_proposal_snapshot = true) {
+ brpc::Controller cntl;
+ FinishTabletJobRequest req;
+ req.set_action(FinishTabletJobRequest::COMMIT);
+ req.mutable_job()->mutable_idx()->set_tablet_id(tablet_id);
+ auto* compaction = req.mutable_job()->add_compaction();
+ compaction->set_id(job_id);
+ compaction->set_initiator("BE1");
+ compaction->set_type(type);
+ if (include_proposal_snapshot) {
+ compaction->set_base_compaction_cnt(proposal_base_compaction_cnt);
+
compaction->set_cumulative_compaction_cnt(proposal_cumulative_compaction_cnt);
+ }
+ compaction->add_input_versions(output_rowset.start_version());
+ compaction->add_input_versions(output_rowset.end_version());
+ compaction->add_output_versions(output_rowset.end_version());
+ compaction->add_txn_id(output_rowset.txn_id());
+ compaction->add_output_rowset_ids(output_rowset.rowset_id_v2());
+ compaction->set_output_cumulative_point(output_cumulative_point);
+ compaction->set_num_input_rows(num_input_rowsets * 100);
+ compaction->set_num_output_rows(output_rowset.num_rows());
+ compaction->set_size_input_rowsets(num_input_rowsets * 10000);
+ compaction->set_size_output_rowsets(output_rowset.total_disk_size());
+ compaction->set_num_input_segments(num_input_rowsets);
+ compaction->set_num_output_segments(output_rowset.num_segments());
+ compaction->set_num_input_rowsets(num_input_rowsets);
+ compaction->set_num_output_rowsets(1);
+ compaction->set_index_size_input_rowsets(num_input_rowsets * 5000);
+ compaction->set_segment_size_input_rowsets(num_input_rowsets * 5000);
+ compaction->set_index_size_output_rowsets(output_rowset.index_disk_size());
+
compaction->set_segment_size_output_rowsets(output_rowset.data_disk_size());
+ meta_service->finish_tablet_job(&cntl, &req, &res, nullptr);
+}
+
void get_tablet_stats(MetaService* meta_service, int64_t tablet_id,
TabletStatsPB& stats) {
brpc::Controller cntl;
GetTabletStatsRequest req;
@@ -728,6 +768,8 @@ TEST(MetaServiceJobTest, ProcessCompactionArguments) {
// Prepare job kv
recorded_compaction->set_expiration(::time(nullptr) + 10);
+ recorded_compaction->set_base_compaction_cnt(0);
+ recorded_compaction->set_cumulative_compaction_cnt(0);
job_val = recorded_job.SerializeAsString();
ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
txn->put(job_key, job_val);
@@ -754,6 +796,12 @@ TEST(MetaServiceJobTest, ProcessCompactionArguments) {
<< res.status().msg();
compaction->set_type(TabletCompactionJobPB::EMPTY_CUMULATIVE);
+ compaction->set_base_compaction_cnt(0);
+ meta_service->finish_tablet_job(&cntl, &req, &res, nullptr);
+ ASSERT_EQ(res.status().code(), MetaServiceCode::INVALID_ARGUMENT) <<
res.status().msg();
+ EXPECT_NE(res.status().msg().find("incomplete compaction counters"),
std::string::npos)
+ << res.status().msg();
+ compaction->clear_base_compaction_cnt();
meta_service->finish_tablet_job(&cntl, &req, &res, nullptr);
ASSERT_EQ(res.status().code(), MetaServiceCode::OK) << res.status().msg();
}
@@ -988,8 +1036,8 @@ TEST(MetaServiceJobTest, CompactionJobTest) {
req.mutable_job()->mutable_idx()->set_index_id(index_id);
req.mutable_job()->mutable_idx()->set_partition_id(partition_id);
req.mutable_job()->mutable_idx()->set_tablet_id(tablet_id);
- compaction->set_base_compaction_cnt(10);
- compaction->set_cumulative_compaction_cnt(20);
+ compaction->set_base_compaction_cnt(9);
+ compaction->set_cumulative_compaction_cnt(19);
// Action is not set
meta_service->finish_tablet_job(reinterpret_cast<::google::protobuf::RpcController*>(&cntl),
&req, &res, nullptr);
@@ -1045,8 +1093,8 @@ TEST(MetaServiceJobTest, CompactionJobTest) {
compaction->set_segment_size_output_rowsets(dist(rng));
compaction->set_type(type);
- tablet_stats_pb.set_cumulative_compaction_cnt(dist(rng));
- tablet_stats_pb.set_base_compaction_cnt(dist(rng));
+ tablet_stats_pb.set_cumulative_compaction_cnt(19);
+ tablet_stats_pb.set_base_compaction_cnt(9);
tablet_stats_pb.set_cumulative_point(tablet_meta_pb.cumulative_layer_point());
// MUST let data stats be larger than input data size
tablet_stats_pb.set_num_rows(dist(rng) + compaction->num_input_rows());
@@ -1068,7 +1116,9 @@ TEST(MetaServiceJobTest, CompactionJobTest) {
ASSERT_NE(res.status().msg().find("invalid input"), std::string::npos);
// Provide input and output rowset info
- int64_t input_version_start = dist(rng);
+ int64_t input_version_start = type == TabletCompactionJobPB::BASE
+ ?
tablet_meta_pb.cumulative_layer_point()
+ : dist(rng);
int64_t input_version_end = input_version_start + 100;
compaction->add_input_versions(input_version_start);
compaction->add_input_versions(input_version_end);
@@ -1833,8 +1883,8 @@ TEST(MetaServiceJobTest,
DeleteBitmapUpdateLockCompatibilityTest) {
compaction->set_size_output_rowsets(dist(rng));
compaction->set_type(type);
- tablet_stats_pb.set_cumulative_compaction_cnt(dist(rng));
- tablet_stats_pb.set_base_compaction_cnt(dist(rng));
+ tablet_stats_pb.set_cumulative_compaction_cnt(19);
+ tablet_stats_pb.set_base_compaction_cnt(9);
tablet_stats_pb.set_cumulative_point(tablet_meta_pb.cumulative_layer_point());
// MUST let data stats be larger than input data size
tablet_stats_pb.set_num_rows(dist(rng) + compaction->num_input_rows());
@@ -3398,8 +3448,8 @@ void testCompactionJobWithMoWTest(int lock_version) {
compaction->set_size_output_rowsets(dist(rng));
compaction->set_type(type);
- tablet_stats_pb.set_cumulative_compaction_cnt(dist(rng));
- tablet_stats_pb.set_base_compaction_cnt(dist(rng));
+ tablet_stats_pb.set_cumulative_compaction_cnt(19);
+ tablet_stats_pb.set_base_compaction_cnt(9);
tablet_stats_pb.set_cumulative_point(tablet_meta_pb.cumulative_layer_point());
// MUST let data stats be larger than input data size
tablet_stats_pb.set_num_rows(dist(rng) + compaction->num_input_rows());
@@ -4304,6 +4354,8 @@ TEST(MetaServiceJobTest, ConcurrentCompactionTest) {
compaction->set_id("job5");
compaction->set_initiator("BE2");
compaction->set_type(TabletCompactionJobPB::CUMULATIVE);
+ compaction->set_base_compaction_cnt(0);
+ compaction->set_cumulative_compaction_cnt(0);
compaction->add_input_versions(5);
compaction->add_input_versions(10);
compaction->add_txn_id(output_rowset.txn_id());
@@ -4631,6 +4683,276 @@ TEST(MetaServiceJobTest, ParallelCumuCompactionTest) {
ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
}
+TEST(MetaServiceJobTest, LegacyParallelCumuFinishValidatesCurrentInputRange) {
+ auto meta_service = get_meta_service();
+
+ auto sp = SyncPoint::get_instance();
+ DORIS_CLOUD_DEFER {
+ SyncPoint::get_instance()->clear_all_call_backs();
+ };
+ sp->set_call_back("get_instance_id", [&](auto&& args) {
+ auto* ret = try_any_cast_ret<std::string>(args);
+ ret->first = instance_id;
+ ret->second = true;
+ });
+ sp->enable_processing();
+
+ auto run_case = [&](int64_t tablet_id, bool higher_first, int64_t
lower_proposal,
+ int64_t expected_final_point) {
+ constexpr int64_t table_id = 1;
+ constexpr int64_t index_id = 2;
+ constexpr int64_t partition_id = 3;
+ ASSERT_NO_FATAL_FAILURE(create_tablet(meta_service.get(), table_id,
index_id, partition_id,
+ tablet_id, false));
+
+ std::vector<doris::RowsetMetaCloudPB> input_rowsets;
+ for (int64_t version = 2; version <= 7; ++version) {
+ input_rowsets.push_back(create_rowset(tablet_id, version,
version));
+ }
+ insert_rowsets(meta_service->txn_kv().get(), table_id, index_id,
partition_id, tablet_id,
+ input_rowsets);
+
+ StartTabletJobResponse start_res;
+ start_compaction_job(meta_service.get(), tablet_id, "lower", "BE1", 0,
0,
+ TabletCompactionJobPB::CUMULATIVE, start_res, {2,
4});
+ ASSERT_EQ(start_res.status().code(), MetaServiceCode::OK);
+ start_res.Clear();
+ start_compaction_job(meta_service.get(), tablet_id, "higher", "BE1",
0, 0,
+ TabletCompactionJobPB::CUMULATIVE, start_res, {5,
7});
+ ASSERT_EQ(start_res.status().code(), MetaServiceCode::OK);
+
+ auto lower_output = create_rowset(tablet_id, 2, 4);
+ auto higher_output = create_rowset(tablet_id, 5, 7);
+ for (const auto* output : {&lower_output, &higher_output}) {
+ CreateRowsetResponse rowset_res;
+ prepare_rowset(meta_service.get(), *output, rowset_res);
+ ASSERT_EQ(rowset_res.status().code(), MetaServiceCode::OK);
+ commit_rowset(meta_service.get(), *output, rowset_res);
+ ASSERT_EQ(rowset_res.status().code(), MetaServiceCode::OK);
+ }
+
+ // Legacy regular and index-change cumulative compactions omit
counters from FINISH.
+ FinishTabletJobResponse finish_res;
+ auto finish = [&](const std::string& job_id, const auto& output,
int64_t proposal,
+ int64_t expected_point) {
+ finish_res.Clear();
+ finish_rowset_compaction_job(meta_service.get(), tablet_id, job_id,
+ TabletCompactionJobPB::CUMULATIVE,
output, 3, proposal,
+ finish_res, 0, 0, false);
+ ASSERT_EQ(finish_res.status().code(), MetaServiceCode::OK);
+ EXPECT_EQ(finish_res.stats().cumulative_point(), expected_point);
+ };
+ if (higher_first) {
+ finish("higher", higher_output, 8, 2);
+ finish("lower", lower_output, lower_proposal,
expected_final_point);
+ } else {
+ finish("lower", lower_output, lower_proposal, 5);
+ finish("higher", higher_output, 8, expected_final_point);
+ }
+
+ TabletStatsPB stats;
+ get_tablet_stats(meta_service.get(), tablet_id, stats);
+ EXPECT_EQ(stats.cumulative_point(), expected_final_point);
+ };
+
+ run_case(40001, false, 5, 8);
+ run_case(40004, true, 5, 5);
+ run_case(40005, true, 6, 2);
+}
+
+TEST(MetaServiceJobTest, ParallelCumuCompactionUsesPointProposalSnapshot) {
+ auto meta_service = get_meta_service();
+
+ auto sp = SyncPoint::get_instance();
+ DORIS_CLOUD_DEFER {
+ SyncPoint::get_instance()->clear_all_call_backs();
+ };
+ sp->set_call_back("get_instance_id", [&](auto&& args) {
+ auto* ret = try_any_cast_ret<std::string>(args);
+ ret->first = instance_id;
+ ret->second = true;
+ });
+ sp->enable_processing();
+
+ constexpr int64_t table_id = 1;
+ constexpr int64_t index_id = 2;
+ constexpr int64_t partition_id = 3;
+ auto run_case = [&](int64_t tablet_id, int64_t lower_proposal_cumu_cnt,
+ int64_t expected_cumulative_point) {
+ ASSERT_NO_FATAL_FAILURE(create_tablet(meta_service.get(), table_id,
index_id, partition_id,
+ tablet_id, false));
+
+ std::vector<doris::RowsetMetaCloudPB> input_rowsets;
+ for (int64_t version = 2; version <= 7; ++version) {
+ input_rowsets.push_back(create_rowset(tablet_id, version,
version));
+ }
+ insert_rowsets(meta_service->txn_kv().get(), table_id, index_id,
partition_id, tablet_id,
+ input_rowsets);
+
+ StartTabletJobResponse start_res;
+ start_compaction_job(meta_service.get(), tablet_id, "lower", "BE1", 0,
0,
+ TabletCompactionJobPB::CUMULATIVE, start_res, {2,
4});
+ ASSERT_EQ(start_res.status().code(), MetaServiceCode::OK);
+ start_res.Clear();
+ start_compaction_job(meta_service.get(), tablet_id, "higher", "BE1",
0, 0,
+ TabletCompactionJobPB::CUMULATIVE, start_res, {5,
7});
+ ASSERT_EQ(start_res.status().code(), MetaServiceCode::OK);
+
+ auto lower_output = create_rowset(tablet_id, 2, 4);
+ auto higher_output = create_rowset(tablet_id, 5, 7);
+ for (const auto* output : {&lower_output, &higher_output}) {
+ CreateRowsetResponse rowset_res;
+ prepare_rowset(meta_service.get(), *output, rowset_res);
+ ASSERT_EQ(rowset_res.status().code(), MetaServiceCode::OK);
+ commit_rowset(meta_service.get(), *output, rowset_res);
+ ASSERT_EQ(rowset_res.status().code(), MetaServiceCode::OK);
+ }
+
+ FinishTabletJobResponse finish_res;
+ finish_rowset_compaction_job(meta_service.get(), tablet_id, "higher",
+ TabletCompactionJobPB::CUMULATIVE,
higher_output, 3, 2,
+ finish_res);
+ ASSERT_EQ(finish_res.status().code(), MetaServiceCode::OK);
+ EXPECT_EQ(finish_res.stats().cumulative_point(), 2);
+
+ finish_res.Clear();
+ finish_rowset_compaction_job(meta_service.get(), tablet_id, "lower",
+ TabletCompactionJobPB::CUMULATIVE,
lower_output, 3, 6,
+ finish_res, 0, lower_proposal_cumu_cnt);
+ ASSERT_EQ(finish_res.status().code(), MetaServiceCode::OK);
+ EXPECT_EQ(finish_res.stats().cumulative_point(),
expected_cumulative_point);
+
+ TabletStatsPB stats;
+ get_tablet_stats(meta_service.get(), tablet_id, stats);
+ EXPECT_EQ(stats.cumulative_point(), expected_cumulative_point);
+ };
+
+ // Both jobs calculated from the START layout: reject the lower job's
stale proposal.
+ run_case(40002, 0, 2);
+ // The lower job recalculated after applying the higher result: accept its
fresh proposal.
+ run_case(40003, 1, 6);
+}
+
+TEST(MetaServiceJobTest,
SerialCumuPointAdvanceIgnoresUnrelatedBaseCounterChange) {
+ auto meta_service = get_meta_service();
+
+ auto sp = SyncPoint::get_instance();
+ DORIS_CLOUD_DEFER {
+ SyncPoint::get_instance()->clear_all_call_backs();
+ };
+ sp->set_call_back("get_instance_id", [&](auto&& args) {
+ auto* ret = try_any_cast_ret<std::string>(args);
+ ret->first = instance_id;
+ ret->second = true;
+ });
+ sp->enable_processing();
+
+ constexpr int64_t table_id = 1;
+ constexpr int64_t index_id = 2;
+ constexpr int64_t partition_id = 3;
+ constexpr int64_t tablet_id = 40006;
+ ASSERT_NO_FATAL_FAILURE(
+ create_tablet(meta_service.get(), table_id, index_id,
partition_id, tablet_id, false));
+
+ std::vector<doris::RowsetMetaCloudPB> input_rowsets;
+ for (int64_t version = 2; version <= 4; ++version) {
+ input_rowsets.push_back(create_rowset(tablet_id, version, version));
+ }
+ insert_rowsets(meta_service->txn_kv().get(), table_id, index_id,
partition_id, tablet_id,
+ input_rowsets);
+
+ StartTabletJobResponse start_res;
+ start_compaction_job(meta_service.get(), tablet_id, "cumu", "BE1", 0, 0,
+ TabletCompactionJobPB::CUMULATIVE, start_res, {2, 4},
false);
+ ASSERT_EQ(start_res.status().code(), MetaServiceCode::OK);
+ start_res.Clear();
+ start_compaction_job(meta_service.get(), tablet_id, "base", "BE1", 0, 0,
+ TabletCompactionJobPB::BASE, start_res, {0, 1},
false);
+ ASSERT_EQ(start_res.status().code(), MetaServiceCode::OK);
+
+ auto cumu_output = create_rowset(tablet_id, 2, 4);
+ auto base_output = create_rowset(tablet_id, 0, 1);
+ for (const auto* output : {&cumu_output, &base_output}) {
+ CreateRowsetResponse rowset_res;
+ prepare_rowset(meta_service.get(), *output, rowset_res);
+ ASSERT_EQ(rowset_res.status().code(), MetaServiceCode::OK);
+ commit_rowset(meta_service.get(), *output, rowset_res);
+ ASSERT_EQ(rowset_res.status().code(), MetaServiceCode::OK);
+ }
+
+ FinishTabletJobResponse finish_res;
+ finish_rowset_compaction_job(meta_service.get(), tablet_id, "base",
TabletCompactionJobPB::BASE,
+ base_output, 1, 2, finish_res);
+ ASSERT_EQ(finish_res.status().code(), MetaServiceCode::OK);
+ ASSERT_EQ(finish_res.stats().base_compaction_cnt(), 1);
+ ASSERT_EQ(finish_res.stats().cumulative_point(), 2);
+
+ finish_res.Clear();
+ finish_rowset_compaction_job(meta_service.get(), tablet_id, "cumu",
+ TabletCompactionJobPB::CUMULATIVE,
cumu_output, 3, 5, finish_res);
+ ASSERT_EQ(finish_res.status().code(), MetaServiceCode::OK);
+ EXPECT_EQ(finish_res.stats().cumulative_point(), 5);
+}
+
+TEST(MetaServiceJobTest, BaseCompactionAdvancesPointPastOutput) {
+ auto meta_service = get_meta_service();
+
+ auto sp = SyncPoint::get_instance();
+ DORIS_CLOUD_DEFER {
+ SyncPoint::get_instance()->clear_all_call_backs();
+ };
+ sp->set_call_back("get_instance_id", [&](auto&& args) {
+ auto* ret = try_any_cast_ret<std::string>(args);
+ ret->first = instance_id;
+ ret->second = true;
+ });
+ sp->enable_processing();
+
+ constexpr int64_t table_id = 1;
+ constexpr int64_t index_id = 2;
+ constexpr int64_t partition_id = 3;
+ constexpr int64_t tablet_id = 40003;
+ ASSERT_NO_FATAL_FAILURE(
+ create_tablet(meta_service.get(), table_id, index_id,
partition_id, tablet_id, false));
+
+ std::vector<doris::RowsetMetaCloudPB> input_rowsets =
{create_rowset(tablet_id, 2, 4),
+
create_rowset(tablet_id, 5, 7)};
+ insert_rowsets(meta_service->txn_kv().get(), table_id, index_id,
partition_id, tablet_id,
+ input_rowsets);
+
+ std::unique_ptr<Transaction> txn;
+ ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), TxnErrorCode::TXN_OK);
+ auto stats_key = stats_tablet_key({instance_id, table_id, index_id,
partition_id, tablet_id});
+ std::string stats_value;
+ ASSERT_EQ(txn->get(stats_key, &stats_value), TxnErrorCode::TXN_OK);
+ TabletStatsPB stats;
+ ASSERT_TRUE(stats.ParseFromString(stats_value));
+ stats.set_cumulative_point(6);
+ txn->put(stats_key, stats.SerializeAsString());
+ ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
+
+ StartTabletJobResponse start_res;
+ start_compaction_job(meta_service.get(), tablet_id, "base", "BE1", 0, 0,
+ TabletCompactionJobPB::BASE, start_res, {2, 7});
+ ASSERT_EQ(start_res.status().code(), MetaServiceCode::OK);
+
+ auto output_rowset = create_rowset(tablet_id, 2, 7);
+ CreateRowsetResponse rowset_res;
+ prepare_rowset(meta_service.get(), output_rowset, rowset_res);
+ ASSERT_EQ(rowset_res.status().code(), MetaServiceCode::OK);
+ commit_rowset(meta_service.get(), output_rowset, rowset_res);
+ ASSERT_EQ(rowset_res.status().code(), MetaServiceCode::OK);
+
+ FinishTabletJobResponse finish_res;
+ finish_rowset_compaction_job(meta_service.get(), tablet_id, "base",
TabletCompactionJobPB::BASE,
+ output_rowset, 2, 6, finish_res);
+ ASSERT_EQ(finish_res.status().code(), MetaServiceCode::OK);
+ EXPECT_EQ(finish_res.stats().cumulative_point(), 8);
+
+ get_tablet_stats(meta_service.get(), tablet_id, stats);
+ EXPECT_EQ(stats.cumulative_point(), 8);
+}
+
// Plan A regression test: EMPTY_CUMULATIVE must be considered the same
conflict family as
// CUMULATIVE so that an EMPTY_CUMULATIVE submitted while a real CUMULATIVE is
still active on the
// same tablet is rejected with JOB_TABLET_BUSY. Otherwise EMPTY_CUMULATIVE
could advance
@@ -5198,8 +5520,8 @@ TEST(MetaServiceJobTest, IdempotentCompactionJob) {
compaction->set_segment_size_output_rowsets(dist(rng));
compaction->set_type(type);
- tablet_stats_pb.set_cumulative_compaction_cnt(dist(rng));
- tablet_stats_pb.set_base_compaction_cnt(dist(rng));
+ tablet_stats_pb.set_cumulative_compaction_cnt(19);
+ tablet_stats_pb.set_base_compaction_cnt(9);
tablet_stats_pb.set_cumulative_point(tablet_meta_pb.cumulative_layer_point());
// MUST let data stats be larger than input data size
tablet_stats_pb.set_num_rows(dist(rng) + compaction->num_input_rows());
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]