Coverage Report

Created: 2026-08-07 16:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/storage/compaction/cumulative_compaction_policy.cpp
Line
Count
Source
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
#include "storage/compaction/cumulative_compaction_policy.h"
19
20
#include <algorithm>
21
#include <iterator>
22
#include <list>
23
#include <ostream>
24
#include <string>
25
26
#include "common/config.h"
27
#include "common/logging.h"
28
#include "storage/compaction/cumulative_compaction_binlog_policy.h"
29
#include "storage/compaction/cumulative_compaction_time_series_policy.h"
30
#include "storage/olap_common.h"
31
#include "storage/rowset/rowset.h"
32
#include "storage/rowset/rowset_meta.h"
33
#include "storage/tablet/tablet.h"
34
#include "storage/tablet/tablet_meta.h"
35
#include "util/debug_points.h"
36
#include "util/defer_op.h"
37
38
namespace doris {
39
40
SizeBasedCumulativeCompactionPolicy::SizeBasedCumulativeCompactionPolicy(
41
        int64_t promotion_size, double promotion_ratio, int64_t promotion_min_size,
42
        int64_t promotion_version_count, int64_t compaction_min_size)
43
585
        : _promotion_size(promotion_size),
44
585
          _promotion_ratio(promotion_ratio),
45
585
          _promotion_min_size(promotion_min_size),
46
585
          _promotion_version_count(promotion_version_count),
47
585
          _compaction_min_size(compaction_min_size) {}
48
49
void SizeBasedCumulativeCompactionPolicy::calculate_cumulative_point(
50
        Tablet* tablet, const RowsetMetaMapContainer& all_metas, int64_t current_cumulative_point,
51
76
        int64_t* ret_cumulative_point) {
52
76
    *ret_cumulative_point = Tablet::K_INVALID_CUMULATIVE_POINT;
53
76
    if (current_cumulative_point != Tablet::K_INVALID_CUMULATIVE_POINT) {
54
        // only calculate the point once.
55
        // after that, cumulative point will be updated along with compaction process.
56
21
        return;
57
21
    }
58
    // empty return
59
55
    if (all_metas.empty()) {
60
0
        return;
61
0
    }
62
63
55
    std::list<RowsetMetaSharedPtr> existing_rss;
64
3.12k
    for (const auto& [_, rs] : all_metas) {
65
3.12k
        existing_rss.emplace_back(rs);
66
3.12k
    }
67
68
    // sort the existing rowsets by version in ascending order
69
18.7k
    existing_rss.sort([](const RowsetMetaSharedPtr& a, const RowsetMetaSharedPtr& b) {
70
        // simple because 2 versions are certainly not overlapping
71
18.7k
        return a->version().first < b->version().first;
72
18.7k
    });
73
74
    // calculate promotion size
75
55
    auto base_rowset_meta = existing_rss.begin();
76
77
55
    if (tablet->tablet_state() == TABLET_RUNNING) {
78
        // check base rowset first version must be zero
79
        // for tablet which state is not TABLET_RUNNING, there may not have base version.
80
54
        CHECK((*base_rowset_meta)->start_version() == 0);
81
82
54
        int64_t promotion_size = 0;
83
54
        _calc_promotion_size(tablet, *base_rowset_meta, &promotion_size);
84
85
54
        int64_t prev_version = -1;
86
110
        for (const RowsetMetaSharedPtr& rs : existing_rss) {
87
110
            if (rs->version().first > prev_version + 1) {
88
                // There is a hole, do not continue
89
0
                break;
90
0
            }
91
92
110
            bool is_delete = rs->has_delete_predicate();
93
94
            // break the loop if segments in this rowset is overlapping.
95
110
            if (!is_delete && rs->is_segments_overlapping()) {
96
14
                *ret_cumulative_point = rs->version().first;
97
14
                break;
98
14
            }
99
100
            // check the rowset is whether less than promotion size
101
96
            if (!is_delete && rs->version().first != 0 && rs->total_disk_size() < promotion_size) {
102
38
                *ret_cumulative_point = rs->version().first;
103
38
                break;
104
38
            }
105
106
            // include one situation: When the segment is not deleted, and is singleton delta, and is NONOVERLAPPING, ret_cumulative_point increase
107
58
            prev_version = rs->version().second;
108
58
            *ret_cumulative_point = prev_version + 1;
109
58
        }
110
54
        VLOG_NOTICE
111
0
                << "cumulative compaction size_based policy, calculate cumulative point value = "
112
0
                << *ret_cumulative_point << ", calc promotion size value = " << promotion_size
113
0
                << " tablet = " << tablet->tablet_id();
114
54
    } else if (tablet->tablet_state() == TABLET_NOTREADY) {
115
        // tablet under alter process
116
        // we choose version next to the base version as cumulative point
117
2
        for (const RowsetMetaSharedPtr& rs : existing_rss) {
118
2
            if (rs->version().first > 0) {
119
1
                *ret_cumulative_point = rs->version().first;
120
1
                break;
121
1
            }
122
2
        }
123
1
    }
124
55
}
125
126
void SizeBasedCumulativeCompactionPolicy::_calc_promotion_size(Tablet* tablet,
127
                                                               RowsetMetaSharedPtr base_rowset_meta,
128
70
                                                               int64_t* promotion_size) {
129
70
    int64_t base_size = base_rowset_meta->total_disk_size();
130
70
    *promotion_size = int64_t(cast_set<double>(base_size) * _promotion_ratio);
131
132
    // promotion_size is between _promotion_size and _promotion_min_size
133
70
    if (*promotion_size >= _promotion_size) {
134
12
        *promotion_size = _promotion_size;
135
58
    } else if (*promotion_size <= _promotion_min_size) {
136
58
        *promotion_size = _promotion_min_size;
137
58
    }
138
70
    _refresh_tablet_promotion_size(tablet, *promotion_size);
139
70
}
140
141
void SizeBasedCumulativeCompactionPolicy::_refresh_tablet_promotion_size(Tablet* tablet,
142
70
                                                                         int64_t promotion_size) {
143
70
    tablet->set_cumulative_promotion_size(promotion_size);
144
70
}
145
146
void SizeBasedCumulativeCompactionPolicy::update_cumulative_point(
147
        Tablet* tablet, const std::vector<RowsetSharedPtr>& input_rowsets,
148
1
        RowsetSharedPtr output_rowset, Version& last_delete_version) {
149
1
    if (tablet->tablet_state() != TABLET_RUNNING) {
150
        // if tablet under alter process, do not update cumulative point
151
0
        return;
152
0
    }
153
    // if rowsets have delete version, move to the last directly
154
1
    if (last_delete_version.first != -1) {
155
0
        tablet->set_cumulative_layer_point(output_rowset->end_version() + 1);
156
1
    } else {
157
        // if rowsets have no delete version, check output_rowset total disk size
158
        // satisfies promotion size.
159
1
        size_t total_size = output_rowset->rowset_meta()->total_disk_size();
160
1
        if (total_size >= tablet->cumulative_promotion_size()) {
161
0
            tablet->set_cumulative_layer_point(output_rowset->end_version() + 1);
162
1
        } else if (tablet->enable_unique_key_merge_on_write() &&
163
1
                   output_rowset->end_version() - output_rowset->start_version() >
164
0
                           _promotion_version_count) {
165
            // for MoW table, if there's too many versions, the delete bitmap will grow to
166
            // a very big size, which may cause the tablet meta too big and the `save_meta`
167
            // operation too slow.
168
            // if the rowset should not promotion according to it's disk size, we should also
169
            // consider it's version count here.
170
0
            tablet->set_cumulative_layer_point(output_rowset->end_version() + 1);
171
0
        }
172
1
    }
173
1
}
174
175
16
uint32_t SizeBasedCumulativeCompactionPolicy::calc_cumulative_compaction_score(Tablet* tablet) {
176
16
    uint32_t score = 0;
177
16
    bool base_rowset_exist = false;
178
16
    const int64_t point = tablet->cumulative_layer_point();
179
16
    int64_t promotion_size = 0;
180
181
16
    std::vector<RowsetMetaSharedPtr> rowset_to_compact;
182
16
    int64_t total_size = 0;
183
184
16
    RowsetMetaSharedPtr first_meta;
185
16
    int64_t first_version = INT64_MAX;
186
    // NOTE: tablet._meta_lock is hold
187
16
    auto& rs_metas = tablet->tablet_meta()->all_rs_metas();
188
    // check the base rowset and collect the rowsets of cumulative part
189
1.00k
    for (const auto& [_, rs_meta] : rs_metas) {
190
1.00k
        if (rs_meta->start_version() < first_version) {
191
263
            first_version = rs_meta->start_version();
192
263
            first_meta = rs_meta;
193
263
        }
194
        // check base rowset
195
1.00k
        if (rs_meta->start_version() == 0) {
196
16
            base_rowset_exist = true;
197
16
        }
198
1.00k
        if (rs_meta->end_version() < point || !rs_meta->is_local()) {
199
            // all_rs_metas() is not sorted, so we use _continue_ other than _break_ here.
200
16
            continue;
201
993
        } else {
202
            // collect the rowsets of cumulative part
203
993
            total_size += rs_meta->total_disk_size();
204
993
            score += rs_meta->get_compaction_score();
205
993
            rowset_to_compact.push_back(rs_meta);
206
993
        }
207
1.00k
    }
208
209
16
    if (first_meta == nullptr) {
210
0
        return 0;
211
0
    }
212
213
    // Use "first"(not base) version to calc promotion size
214
    // because some tablet do not have base version(under alter operation)
215
16
    _calc_promotion_size(tablet, first_meta, &promotion_size);
216
217
    // If base version does not exist, but its state is RUNNING.
218
    // It is abnormal, do not select it and set *score = 0
219
16
    if (!base_rowset_exist && tablet->tablet_state() == TABLET_RUNNING) {
220
0
        LOG(WARNING) << "tablet state is running but have no base version";
221
0
        return 0;
222
0
    }
223
224
    // if total_size is greater than promotion_size, return total score
225
16
    if (total_size >= promotion_size) {
226
0
        return score;
227
0
    }
228
229
    // sort the rowsets of cumulative part
230
16
    std::sort(rowset_to_compact.begin(), rowset_to_compact.end(), RowsetMeta::comparator);
231
232
    // calculate the rowsets to do cumulative compaction
233
    // eg: size of rowset_to_compact are:
234
    // 128, 16, 16, 16
235
    // we will choose [16,16,16] to compact.
236
16
    for (auto& rs_meta : rowset_to_compact) {
237
16
        int64_t current_level = _level_size(rs_meta->total_disk_size());
238
16
        int64_t remain_level = _level_size(total_size - rs_meta->total_disk_size());
239
        // if current level less then remain level, score contains current rowset
240
        // and process return; otherwise, score does not contains current rowset.
241
16
        if (current_level <= remain_level) {
242
16
            return score;
243
16
        }
244
0
        total_size -= rs_meta->total_disk_size();
245
0
        score -= rs_meta->get_compaction_score();
246
0
    }
247
0
    return score;
248
16
}
249
250
int SizeBasedCumulativeCompactionPolicy::pick_input_rowsets(
251
        Tablet* tablet, const std::vector<RowsetSharedPtr>& candidate_rowsets,
252
        const int64_t max_compaction_score, const int64_t min_compaction_score,
253
        std::vector<RowsetSharedPtr>* input_rowsets, Version* last_delete_version,
254
69
        size_t* compaction_score, bool allow_delete) {
255
69
    DBUG_EXECUTE_IF("SizeBasedCumulativeCompactionPolicy::pick_input_rowsets.set_input_rowsets", {
256
69
        auto target_tablet_id = dp->param<int64_t>("tablet_id", -1);
257
69
        if (target_tablet_id == tablet->tablet_id()) {
258
69
            auto start_version = dp->param<int64_t>("start_version", -1);
259
69
            auto end_version = dp->param<int64_t>("end_version", -1);
260
69
            for (auto& rowset : candidate_rowsets) {
261
69
                if (rowset->start_version() >= start_version &&
262
69
                    rowset->end_version() <= end_version) {
263
69
                    input_rowsets->push_back(rowset);
264
69
                }
265
69
            }
266
69
        }
267
69
        return cast_set<uint32_t>(input_rowsets->size());
268
69
    })
269
270
69
    size_t promotion_size = tablet->cumulative_promotion_size();
271
69
    auto max_version = tablet->max_version().first;
272
69
    int transient_size = 0;
273
69
    *compaction_score = 0;
274
69
    int64_t total_size = 0;
275
69
    RowsetSharedPtr last_popped;
276
277
    // DEFER: trim input_rowsets from back if score > max_compaction_score
278
    // This ensures we don't return more rowsets than allowed by max_compaction_score,
279
    // while still collecting enough rowsets to pass min_compaction_score check after level_size removal.
280
    // Must be placed after variable initialization and before collection loop.
281
69
    DEFER({
282
        // Keep at least 1 rowset to avoid removing the only rowset (consistent with fallback branch)
283
69
        while (input_rowsets->size() > 1 &&
284
69
               *compaction_score > static_cast<size_t>(max_compaction_score)) {
285
69
            last_popped = std::move(input_rowsets->back());
286
69
            *compaction_score -= last_popped->rowset_meta()->get_compaction_score();
287
69
            total_size -= last_popped->rowset_meta()->total_disk_size();
288
69
            input_rowsets->pop_back();
289
69
        }
290
        // A single non-overlapping rowset cannot be compacted by itself. Restore the direct
291
        // successor and accept a one-off max-score overshoot to keep the input mergeable.
292
69
        if (input_rowsets->size() == 1 && last_popped != nullptr &&
293
69
            !input_rowsets->front()->rowset_meta()->is_segments_overlapping()) {
294
69
            *compaction_score += last_popped->rowset_meta()->get_compaction_score();
295
69
            total_size += last_popped->rowset_meta()->total_disk_size();
296
69
            input_rowsets->push_back(std::move(last_popped));
297
69
        }
298
69
    });
299
300
3.61k
    for (auto& rowset : candidate_rowsets) {
301
        // check whether this rowset is delete version
302
3.61k
        if (!allow_delete && rowset->rowset_meta()->has_delete_predicate()) {
303
3
            *last_delete_version = rowset->version();
304
3
            if (!input_rowsets->empty()) {
305
                // we meet a delete version, and there were other versions before.
306
                // we should compact those version before handling them over to base compaction
307
3
                break;
308
3
            } else {
309
                // we meet a delete version, and no other versions before, skip it and continue
310
0
                input_rowsets->clear();
311
0
                *compaction_score = 0;
312
0
                transient_size = 0;
313
0
                continue;
314
0
            }
315
3
        }
316
3.61k
        if (tablet->tablet_state() == TABLET_NOTREADY) {
317
            // If tablet under alter, keep latest 10 version so that base tablet max version
318
            // not merged in new tablet, and then we can copy data from base tablet
319
19
            if (rowset->version().second > max_version - 10) {
320
10
                continue;
321
10
            }
322
19
        }
323
        // Removed: max_compaction_score check here
324
        // We now collect all candidate rowsets and trim from back at return time via DEFER
325
3.60k
        *compaction_score += rowset->rowset_meta()->get_compaction_score();
326
3.60k
        total_size += rowset->rowset_meta()->total_disk_size();
327
328
3.60k
        transient_size += 1;
329
3.60k
        input_rowsets->push_back(rowset);
330
3.60k
    }
331
69
    DBUG_EXECUTE_IF("SizeBaseCumulativeCompactionPolicy.pick_input_rowsets.return_input_rowsets",
332
69
                    { return transient_size; })
333
334
    // if there is delete version, do compaction directly
335
69
    if (last_delete_version->first != -1) {
336
3
        if (input_rowsets->size() == 1) {
337
0
            auto rs_meta = input_rowsets->front()->rowset_meta();
338
            // if there is only one rowset and not overlapping,
339
            // we do not need to do cumulative compaction
340
0
            if (!rs_meta->is_segments_overlapping()) {
341
0
                input_rowsets->clear();
342
0
                *compaction_score = 0;
343
0
            }
344
0
        }
345
3
        return transient_size;
346
3
    }
347
348
66
    auto rs_begin = input_rowsets->begin();
349
66
    size_t new_compaction_score = *compaction_score;
350
66
    const bool can_handle_exhausted_input =
351
66
            *compaction_score >= static_cast<size_t>(max_compaction_score);
352
93
    while (rs_begin != input_rowsets->end()) {
353
88
        auto& rs_meta = (*rs_begin)->rowset_meta();
354
88
        int64_t current_level = _level_size(rs_meta->total_disk_size());
355
88
        int64_t remain_level = _level_size(total_size - rs_meta->total_disk_size());
356
        // if current level less then remain level, input rowsets contain current rowset
357
        // and process return; otherwise, input rowsets do not contain current rowset.
358
88
        if (current_level <= remain_level) {
359
56
            break;
360
56
        }
361
362
32
        auto next = std::next(rs_begin);
363
        // Keep the last suffix rowset for the singleton checks unless the exhausted-input
364
        // fallback below can select a useful input.
365
32
        if (next == input_rowsets->end() && !can_handle_exhausted_input) {
366
5
            break;
367
5
        }
368
27
        total_size -= rs_meta->total_disk_size();
369
27
        new_compaction_score -= rs_meta->get_compaction_score();
370
27
        rs_begin = next;
371
27
    }
372
66
    if (rs_begin == input_rowsets->end() && *compaction_score >= max_compaction_score) {
373
        // No suitable level size found in `input_rowsets` but score of `input_rowsets` exceed max compaction score,
374
        // which means `input_rowsets` will never change and this tablet will never execute cumulative compaction.
375
        // MUST execute compaction on these `input_rowsets` to reduce compaction score.
376
4
        RowsetSharedPtr rs_with_max_score;
377
4
        uint32_t max_score = 1;
378
9
        for (auto& rs : *input_rowsets) {
379
9
            if (rs->rowset_meta()->get_compaction_score() > max_score) {
380
3
                max_score = rs->rowset_meta()->get_compaction_score();
381
3
                rs_with_max_score = rs;
382
3
            }
383
9
        }
384
4
        if (rs_with_max_score) {
385
3
            input_rowsets->clear();
386
3
            input_rowsets->push_back(std::move(rs_with_max_score));
387
3
            *compaction_score = max_score;
388
3
            return transient_size;
389
3
        }
390
        // no rowset is OVERLAPPING, return all input rowsets (DEFER will trim to max_compaction_score)
391
1
        return transient_size;
392
4
    }
393
62
    input_rowsets->erase(input_rowsets->begin(), rs_begin);
394
62
    *compaction_score = new_compaction_score;
395
396
62
    VLOG_CRITICAL << "cumulative compaction size_based policy, compaction_score = "
397
0
                  << *compaction_score << ", total_size = " << total_size
398
0
                  << ", calc promotion size value = " << promotion_size
399
0
                  << ", tablet = " << tablet->tablet_id() << ", input_rowset size "
400
0
                  << input_rowsets->size();
401
402
    // empty return
403
62
    if (input_rowsets->empty()) {
404
1
        return transient_size;
405
1
    }
406
407
    // if we have a sufficient number of segments, we should process the compaction.
408
    // otherwise, we check number of segments and total_size whether can do compaction.
409
61
    if (total_size < _compaction_min_size && *compaction_score < min_compaction_score) {
410
5
        input_rowsets->clear();
411
5
        *compaction_score = 0;
412
56
    } else if (total_size >= _compaction_min_size && input_rowsets->size() == 1) {
413
4
        auto rs_meta = input_rowsets->front()->rowset_meta();
414
        // if there is only one rowset and not overlapping,
415
        // we do not need to do compaction
416
4
        if (!rs_meta->is_segments_overlapping()) {
417
1
            input_rowsets->clear();
418
1
            *compaction_score = 0;
419
1
        }
420
4
    }
421
61
    return transient_size;
422
62
}
423
424
212
int64_t SizeBasedCumulativeCompactionPolicy::_level_size(const int64_t size) {
425
212
    if (size < 1024) return 0;
426
153
    int64_t max_level = (int64_t)1
427
153
                        << (sizeof(_promotion_size) * 8 - 1 - __builtin_clzl(_promotion_size / 2));
428
153
    if (size >= max_level) return max_level;
429
146
    return (int64_t)1 << (sizeof(size) * 8 - 1 - __builtin_clzl(size));
430
153
}
431
432
std::shared_ptr<CumulativeCompactionPolicy>
433
CumulativeCompactionPolicyFactory::create_cumulative_compaction_policy(
434
611
        const std::string_view& compaction_policy) {
435
611
    if (compaction_policy == CUMULATIVE_TIME_SERIES_POLICY) {
436
17
        return std::make_shared<TimeSeriesCumulativeCompactionPolicy>();
437
594
    } else if (compaction_policy == CUMULATIVE_SIZE_BASED_POLICY) {
438
456
        return std::make_shared<SizeBasedCumulativeCompactionPolicy>();
439
456
    } else if (compaction_policy == CUMULATIVE_BINLOG_POLICY) {
440
9
        return std::make_shared<BinlogCumulativeCompactionPolicy>();
441
9
    }
442
129
    return std::make_shared<SizeBasedCumulativeCompactionPolicy>();
443
611
}
444
} // namespace doris