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

luwei16 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 54300f922fd [fix](compaction) Keep cumulative inputs mergeable after 
score trimming (#65470)
54300f922fd is described below

commit 54300f922fddbeac6e679a83d7829444d4a72f77
Author: Jamie <[email protected]>
AuthorDate: Mon Aug 3 11:57:42 2026 +0800

    [fix](compaction) Keep cumulative inputs mergeable after score trimming 
(#65470)
    
    ### What problem does this PR solve?
    
    Issue Number: None
    
    Related PR: #59268
    
    Problem Summary:
    
    When the cumulative compaction max score is reduced, back trimming can
    leave a single non-overlapping rowset. It cannot be compacted alone, so
    compaction repeatedly returns `CUMULATIVE_NO_SUITABLE_VERSION` without
    advancing the cumulative point.
    
    This change remembers the last trimmed rowset in both cloud and local
    size-based policies. If trimming strands a single non-overlapping
    rowset, it restores the direct successor and allows one max-score
    overshoot so the input remains mergeable. Existing overlapping-singleton
    and cloud empty-rowset behavior is unchanged.
    
    ### Release note
    
    Prevent max-score trimming from leaving an unmergeable cumulative
    compaction input.
---
 .../cloud/cloud_cumulative_compaction_policy.cpp   | 15 +++-
 .../compaction/cumulative_compaction_policy.cpp    | 15 +++-
 .../cloud_cumulative_compaction_policy_test.cpp    | 62 ++++++++++++++++
 .../cumulative_compaction_policy_test.cpp          | 84 ++++++++++++++++++++++
 4 files changed, 170 insertions(+), 6 deletions(-)

diff --git a/be/src/cloud/cloud_cumulative_compaction_policy.cpp 
b/be/src/cloud/cloud_cumulative_compaction_policy.cpp
index ca422be9b24..465f2c2c31e 100644
--- a/be/src/cloud/cloud_cumulative_compaction_policy.cpp
+++ b/be/src/cloud/cloud_cumulative_compaction_policy.cpp
@@ -122,6 +122,7 @@ int64_t 
CloudSizeBasedCumulativeCompactionPolicy::pick_input_rowsets(
     *compaction_score = 0;
     int64_t total_size = 0;
     bool skip_trim = false; // Skip trim for Empty Rowset Compaction
+    RowsetSharedPtr last_popped;
 
     // DEFER: trim input_rowsets from back if score > max_compaction_score
     // This ensures we don't return more rowsets than allowed by 
max_compaction_score,
@@ -134,11 +135,19 @@ int64_t 
CloudSizeBasedCumulativeCompactionPolicy::pick_input_rowsets(
         // Keep at least 1 rowset to avoid removing the only rowset 
(consistent with fallback branch)
         while (input_rowsets->size() > 1 &&
                *compaction_score > static_cast<size_t>(max_compaction_score)) {
-            auto& last_rowset = input_rowsets->back();
-            *compaction_score -= 
last_rowset->rowset_meta()->get_compaction_score();
-            total_size -= last_rowset->rowset_meta()->total_disk_size();
+            last_popped = std::move(input_rowsets->back());
+            *compaction_score -= 
last_popped->rowset_meta()->get_compaction_score();
+            total_size -= last_popped->rowset_meta()->total_disk_size();
             input_rowsets->pop_back();
         }
+        // A single non-overlapping rowset cannot be compacted by itself. 
Restore the direct
+        // successor and accept a one-off max-score overshoot to keep the 
input mergeable.
+        if (input_rowsets->size() == 1 && last_popped != nullptr &&
+            !input_rowsets->front()->rowset_meta()->is_segments_overlapping()) 
{
+            *compaction_score += 
last_popped->rowset_meta()->get_compaction_score();
+            total_size += last_popped->rowset_meta()->total_disk_size();
+            input_rowsets->push_back(std::move(last_popped));
+        }
     });
 
     for (auto& rowset : candidate_rowsets) {
diff --git a/be/src/storage/compaction/cumulative_compaction_policy.cpp 
b/be/src/storage/compaction/cumulative_compaction_policy.cpp
index a120d941e6f..7ab7a8e34ef 100644
--- a/be/src/storage/compaction/cumulative_compaction_policy.cpp
+++ b/be/src/storage/compaction/cumulative_compaction_policy.cpp
@@ -269,6 +269,7 @@ int SizeBasedCumulativeCompactionPolicy::pick_input_rowsets(
     int transient_size = 0;
     *compaction_score = 0;
     int64_t total_size = 0;
+    RowsetSharedPtr last_popped;
 
     // DEFER: trim input_rowsets from back if score > max_compaction_score
     // This ensures we don't return more rowsets than allowed by 
max_compaction_score,
@@ -278,11 +279,19 @@ int 
SizeBasedCumulativeCompactionPolicy::pick_input_rowsets(
         // Keep at least 1 rowset to avoid removing the only rowset 
(consistent with fallback branch)
         while (input_rowsets->size() > 1 &&
                *compaction_score > static_cast<size_t>(max_compaction_score)) {
-            auto& last_rowset = input_rowsets->back();
-            *compaction_score -= 
last_rowset->rowset_meta()->get_compaction_score();
-            total_size -= last_rowset->rowset_meta()->total_disk_size();
+            last_popped = std::move(input_rowsets->back());
+            *compaction_score -= 
last_popped->rowset_meta()->get_compaction_score();
+            total_size -= last_popped->rowset_meta()->total_disk_size();
             input_rowsets->pop_back();
         }
+        // A single non-overlapping rowset cannot be compacted by itself. 
Restore the direct
+        // successor and accept a one-off max-score overshoot to keep the 
input mergeable.
+        if (input_rowsets->size() == 1 && last_popped != nullptr &&
+            !input_rowsets->front()->rowset_meta()->is_segments_overlapping()) 
{
+            *compaction_score += 
last_popped->rowset_meta()->get_compaction_score();
+            total_size += last_popped->rowset_meta()->total_disk_size();
+            input_rowsets->push_back(std::move(last_popped));
+        }
     });
 
     for (auto& rowset : candidate_rowsets) {
diff --git a/be/test/cloud/cloud_cumulative_compaction_policy_test.cpp 
b/be/test/cloud/cloud_cumulative_compaction_policy_test.cpp
index 08799c900d2..2a20b5b3cba 100644
--- a/be/test/cloud/cloud_cumulative_compaction_policy_test.cpp
+++ b/be/test/cloud/cloud_cumulative_compaction_policy_test.cpp
@@ -145,6 +145,22 @@ static int64_t total_disk_size(const 
std::vector<RowsetSharedPtr>& rowsets) {
     return total_size;
 }
 
+static std::vector<RowsetSharedPtr> create_max_score_trim_candidates(bool 
include_stranded_head) {
+    std::vector<RowsetSharedPtr> candidate_rowsets;
+    if (include_stranded_head) {
+        candidate_rowsets.push_back(create_rowset(Version(13, 56), 0, false, 
0));
+    }
+    candidate_rowsets.push_back(create_rowset(Version(57, 57), 192, true, 256 
* 1024 * 1024));
+    candidate_rowsets.push_back(create_rowset(Version(58, 58), 0, false, 0));
+    candidate_rowsets.push_back(create_rowset(Version(59, 59), 0, false, 0));
+    candidate_rowsets.push_back(create_rowset(Version(60, 60), 150, true, 256 
* 1024 * 1024));
+    for (int64_t version = 61; version <= 67; ++version) {
+        candidate_rowsets.push_back(
+                create_rowset(Version(version, version), 1, false, 1024 * 
1024));
+    }
+    return candidate_rowsets;
+}
+
 TEST_F(TestCloudSizeBasedCumulativeCompactionPolicy, new_cumulative_point) {
     std::vector<RowsetMetaSharedPtr> rs_metas;
     init_rs_meta_small_base(&rs_metas);
@@ -267,6 +283,52 @@ TEST_F(TestCloudSizeBasedCumulativeCompactionPolicy,
     EXPECT_EQ(0, compaction_score);
 }
 
+TEST_F(TestCloudSizeBasedCumulativeCompactionPolicy,
+       pick_input_rowsets_restores_successor_for_non_overlapping_singleton) {
+    CloudTablet tablet(_engine, _tablet_meta);
+    tablet._base_size = 1024L * 1024 * 1024;
+    tablet._tablet_meta->_enable_unique_key_merge_on_write = true;
+    auto candidate_rowsets = create_max_score_trim_candidates(true);
+    ASSERT_EQ(12, candidate_rowsets.size());
+
+    std::vector<RowsetSharedPtr> input_rowsets;
+    Version last_delete_version {-1, -1};
+    size_t compaction_score = 0;
+    CloudSizeBasedCumulativeCompactionPolicy policy;
+    policy.pick_input_rowsets(&tablet, candidate_rowsets, 100, 5, 
&input_rowsets,
+                              &last_delete_version, &compaction_score, true);
+
+    ASSERT_EQ(2, input_rowsets.size());
+    EXPECT_EQ(Version(13, 56), input_rowsets[0]->version());
+    EXPECT_EQ(Version(57, 57), input_rowsets[1]->version());
+    EXPECT_EQ(193, compaction_score);
+
+    auto output_rowset = create_rowset(Version(13, 57), 1, false, 256 * 1024 * 
1024);
+    ASSERT_NE(nullptr, output_rowset);
+    EXPECT_EQ(58, policy.new_cumulative_point(&tablet, output_rowset, 
last_delete_version, 13));
+}
+
+TEST_F(TestCloudSizeBasedCumulativeCompactionPolicy,
+       pick_input_rowsets_keeps_single_overlapping_rowset_after_trim) {
+    CloudTablet tablet(_engine, _tablet_meta);
+    tablet._base_size = 1024L * 1024 * 1024;
+    auto candidate_rowsets = create_max_score_trim_candidates(false);
+    ASSERT_EQ(11, candidate_rowsets.size());
+
+    std::vector<RowsetSharedPtr> input_rowsets;
+    Version last_delete_version {-1, -1};
+    size_t compaction_score = 0;
+    CloudSizeBasedCumulativeCompactionPolicy policy;
+    policy.pick_input_rowsets(&tablet, candidate_rowsets, 100, 5, 
&input_rowsets,
+                              &last_delete_version, &compaction_score, true);
+
+    // The tail was trimmed, but the overlapping head remains mergeable by 
itself.
+    ASSERT_EQ(1, input_rowsets.size());
+    EXPECT_GT(candidate_rowsets.size(), input_rowsets.size());
+    EXPECT_EQ(Version(57, 57), input_rowsets.front()->version());
+    EXPECT_EQ(192, compaction_score);
+}
+
 // Test case: Empty rowset compaction with skip_trim
 TEST_F(TestCloudSizeBasedCumulativeCompactionPolicy, 
pick_input_rowsets_empty_rowset_compaction) {
     // Save original config values
diff --git a/be/test/storage/compaction/cumulative_compaction_policy_test.cpp 
b/be/test/storage/compaction/cumulative_compaction_policy_test.cpp
index 58b18128b86..ce722df19b6 100644
--- a/be/test/storage/compaction/cumulative_compaction_policy_test.cpp
+++ b/be/test/storage/compaction/cumulative_compaction_policy_test.cpp
@@ -329,6 +329,32 @@ public:
         rs_metas->push_back(ptr5);
     }
 
+    std::vector<RowsetMetaSharedPtr> create_rs_meta_max_score_trim(bool 
include_stranded_head) {
+        std::vector<RowsetMetaSharedPtr> rs_metas;
+        auto add_rowset = [&](int64_t start_version, int64_t end_version, int 
num_segments,
+                              bool overlapping, int64_t total_disk_size) {
+            RowsetMetaSharedPtr rowset_meta(new RowsetMeta());
+            init_rs_meta(rowset_meta, start_version, end_version);
+            rowset_meta->set_num_segments(num_segments);
+            rowset_meta->set_segments_overlap(overlapping ? OVERLAPPING : 
NONOVERLAPPING);
+            rowset_meta->set_total_disk_size(total_disk_size);
+            rs_metas.push_back(rowset_meta);
+        };
+
+        add_rowset(0, include_stranded_head ? 12 : 56, 1, false, 1024L * 1024 
* 1024);
+        if (include_stranded_head) {
+            add_rowset(13, 56, 0, false, 0);
+        }
+        add_rowset(57, 57, 192, true, 256L * 1024 * 1024);
+        add_rowset(58, 58, 0, false, 0);
+        add_rowset(59, 59, 0, false, 0);
+        add_rowset(60, 60, 150, true, 256L * 1024 * 1024);
+        for (int64_t version = 61; version <= 67; ++version) {
+            add_rowset(version, version, 1, false, 1024L * 1024);
+        }
+        return rs_metas;
+    }
+
 protected:
     std::string _json_rowset_meta;
     TabletMetaSharedPtr _tablet_meta;
@@ -1301,6 +1327,64 @@ TEST_F(TestSizeBasedCumulativeCompactionPolicy,
     EXPECT_EQ(0, compaction_score);
 }
 
+TEST_F(TestSizeBasedCumulativeCompactionPolicy,
+       pick_input_rowsets_restores_successor_for_non_overlapping_singleton) {
+    auto rs_metas = create_rs_meta_max_score_trim(true);
+    for (auto& rowset : rs_metas) {
+        static_cast<void>(_tablet_meta->add_rs_meta(rowset));
+    }
+
+    TabletSharedPtr tablet(
+            new Tablet(_engine, _tablet_meta, nullptr, 
CUMULATIVE_SIZE_BASED_POLICY));
+    static_cast<void>(tablet->init());
+    tablet->calculate_cumulative_point();
+    EXPECT_EQ(13, tablet->cumulative_layer_point());
+    EXPECT_EQ(64L * 1024 * 1024, tablet->cumulative_promotion_size());
+
+    auto candidate_rowsets = 
tablet->pick_candidate_rowsets_to_cumulative_compaction();
+    ASSERT_EQ(12, candidate_rowsets.size());
+    std::vector<RowsetSharedPtr> input_rowsets;
+    Version last_delete_version {-1, -1};
+    size_t compaction_score = 0;
+    tablet->_cumulative_compaction_policy->pick_input_rowsets(
+            tablet.get(), candidate_rowsets, 100, 5, &input_rowsets, 
&last_delete_version,
+            &compaction_score, config::enable_delete_when_cumu_compaction);
+
+    ASSERT_EQ(2, input_rowsets.size());
+    EXPECT_EQ(Version(13, 56), input_rowsets[0]->version());
+    EXPECT_EQ(Version(57, 57), input_rowsets[1]->version());
+    EXPECT_EQ(193, compaction_score);
+}
+
+TEST_F(TestSizeBasedCumulativeCompactionPolicy,
+       pick_input_rowsets_keeps_single_overlapping_rowset_after_trim) {
+    auto rs_metas = create_rs_meta_max_score_trim(false);
+    for (auto& rowset : rs_metas) {
+        static_cast<void>(_tablet_meta->add_rs_meta(rowset));
+    }
+
+    TabletSharedPtr tablet(
+            new Tablet(_engine, _tablet_meta, nullptr, 
CUMULATIVE_SIZE_BASED_POLICY));
+    static_cast<void>(tablet->init());
+    tablet->calculate_cumulative_point();
+    EXPECT_EQ(57, tablet->cumulative_layer_point());
+
+    auto candidate_rowsets = 
tablet->pick_candidate_rowsets_to_cumulative_compaction();
+    ASSERT_EQ(11, candidate_rowsets.size());
+    std::vector<RowsetSharedPtr> input_rowsets;
+    Version last_delete_version {-1, -1};
+    size_t compaction_score = 0;
+    tablet->_cumulative_compaction_policy->pick_input_rowsets(
+            tablet.get(), candidate_rowsets, 100, 5, &input_rowsets, 
&last_delete_version,
+            &compaction_score, config::enable_delete_when_cumu_compaction);
+
+    // The tail was trimmed, but the overlapping head remains mergeable by 
itself.
+    ASSERT_EQ(1, input_rowsets.size());
+    EXPECT_GT(candidate_rowsets.size(), input_rowsets.size());
+    EXPECT_EQ(Version(57, 57), input_rowsets.front()->version());
+    EXPECT_EQ(192, compaction_score);
+}
+
 // Test case: Trim with varying scores (high score rowsets at tail)
 TEST_F(TestSizeBasedCumulativeCompactionPolicy, 
pick_input_rowsets_trim_high_score_tail) {
     std::vector<RowsetMetaSharedPtr> rs_metas;


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

Reply via email to