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 "cloud/cloud_cumulative_compaction_policy.h" |
19 | | |
20 | | #include <algorithm> |
21 | | #include <iterator> |
22 | | #include <list> |
23 | | #include <ostream> |
24 | | #include <string> |
25 | | |
26 | | #include "cloud/config.h" |
27 | | #include "common/config.h" |
28 | | #include "common/logging.h" |
29 | | #include "cpp/sync_point.h" |
30 | | #include "storage/compaction/cumulative_compaction_time_series_policy.h" |
31 | | #include "storage/olap_common.h" |
32 | | #include "storage/tablet/tablet.h" |
33 | | #include "storage/tablet/tablet_meta.h" |
34 | | #include "util/defer_op.h" |
35 | | |
36 | | namespace doris { |
37 | | |
38 | | int64_t CloudCumulativeCompactionPolicy::calculate_cumulative_point( |
39 | | CloudTablet* tablet, const std::vector<RowsetSharedPtr>& rowsets, |
40 | | const RowsetSharedPtr& output_rowset, Version& last_delete_version, |
41 | 17 | int64_t input_cumulative_point) { |
42 | 17 | DORIS_CHECK(std::find(rowsets.begin(), rowsets.end(), output_rowset) != rowsets.end()); |
43 | 17 | int64_t cumulative_point = input_cumulative_point; |
44 | 17 | Version no_delete_version {-1, -1}; |
45 | 30 | for (const auto& rowset : rowsets) { |
46 | 30 | DORIS_CHECK_EQ(rowset->start_version(), cumulative_point); |
47 | 30 | if (rowset->rowset_meta()->has_delete_predicate()) { |
48 | 2 | cumulative_point = rowset->end_version() + 1; |
49 | 2 | continue; |
50 | 2 | } |
51 | 28 | if (rowset->rowset_meta()->is_segments_overlapping()) { |
52 | 3 | return cumulative_point; |
53 | 3 | } |
54 | 25 | Version& delete_version = rowset == output_rowset ? last_delete_version : no_delete_version; |
55 | 25 | int64_t candidate_cumulative_point = |
56 | 25 | new_cumulative_point(tablet, rowset, delete_version, cumulative_point); |
57 | 25 | if (candidate_cumulative_point != rowset->end_version() + 1) { |
58 | 9 | return cumulative_point; |
59 | 9 | } |
60 | 16 | cumulative_point = candidate_cumulative_point; |
61 | 16 | } |
62 | 5 | return cumulative_point; |
63 | 17 | } |
64 | | |
65 | | CloudSizeBasedCumulativeCompactionPolicy::CloudSizeBasedCumulativeCompactionPolicy( |
66 | | int64_t promotion_size, double promotion_ratio, int64_t promotion_min_size, |
67 | | int64_t compaction_min_size) |
68 | 265 | : _promotion_size(promotion_size), |
69 | 265 | _promotion_ratio(promotion_ratio), |
70 | 265 | _promotion_min_size(promotion_min_size), |
71 | 265 | _compaction_min_size(compaction_min_size) {} |
72 | | |
73 | 44 | int64_t CloudSizeBasedCumulativeCompactionPolicy::_level_size(const int64_t size) { |
74 | 44 | if (size < 1024) return 0; |
75 | 29 | int64_t max_level = (int64_t)1 |
76 | 29 | << (sizeof(_promotion_size) * 8 - 1 - __builtin_clzl(_promotion_size / 2)); |
77 | 29 | if (size >= max_level) return max_level; |
78 | 23 | return (int64_t)1 << (sizeof(size) * 8 - 1 - __builtin_clzl(size)); |
79 | 29 | } |
80 | | |
81 | | void find_longest_consecutive_empty_rowsets(std::vector<RowsetSharedPtr>* result, |
82 | 23 | const std::vector<RowsetSharedPtr>& candidate_rowsets) { |
83 | 23 | std::vector<RowsetSharedPtr> current_sequence; |
84 | 23 | std::vector<RowsetSharedPtr> longest_sequence; |
85 | | |
86 | 331 | for (size_t i = 0; i < candidate_rowsets.size(); ++i) { |
87 | 308 | auto& rowset = candidate_rowsets[i]; |
88 | | |
89 | | // Check if rowset is empty and has no delete predicate |
90 | 308 | if (rowset->num_segments() == 0 && !rowset->rowset_meta()->has_delete_predicate()) { |
91 | | // Check if this is consecutive with previous rowset |
92 | 196 | if (current_sequence.empty() || |
93 | 196 | (current_sequence.back()->end_version() == rowset->start_version() - 1)) { |
94 | 196 | current_sequence.push_back(rowset); |
95 | 196 | } else { |
96 | | // Start new sequence if not consecutive |
97 | 0 | if (current_sequence.size() > longest_sequence.size()) { |
98 | 0 | longest_sequence = current_sequence; |
99 | 0 | } |
100 | 0 | current_sequence.clear(); |
101 | 0 | current_sequence.push_back(rowset); |
102 | 0 | } |
103 | 196 | } else { |
104 | | // Non-empty rowset, check if we have a sequence to compare |
105 | 112 | if (current_sequence.size() > longest_sequence.size()) { |
106 | 11 | longest_sequence = current_sequence; |
107 | 11 | } |
108 | 112 | current_sequence.clear(); |
109 | 112 | } |
110 | 308 | } |
111 | | |
112 | | // Check final sequence |
113 | 23 | if (current_sequence.size() > longest_sequence.size()) { |
114 | 3 | longest_sequence = current_sequence; |
115 | 3 | } |
116 | | |
117 | 23 | *result = longest_sequence; |
118 | 23 | } |
119 | | |
120 | | int64_t CloudSizeBasedCumulativeCompactionPolicy::pick_input_rowsets( |
121 | | CloudTablet* tablet, const std::vector<RowsetSharedPtr>& candidate_rowsets, |
122 | | const int64_t max_compaction_score, const int64_t min_compaction_score, |
123 | | std::vector<RowsetSharedPtr>* input_rowsets, Version* last_delete_version, |
124 | 21 | size_t* compaction_score, bool allow_delete) { |
125 | 21 | DBUG_EXECUTE_IF( |
126 | 21 | "CloudSizeBasedCumulativeCompactionPolicy::pick_input_rowsets.set_input_rowsets", { |
127 | 21 | auto target_tablet_id = dp->param<int64_t>("tablet_id", -1); |
128 | 21 | if (target_tablet_id == tablet->tablet_id()) { |
129 | 21 | auto start_version = dp->param<int64_t>("start_version", -1); |
130 | 21 | auto end_version = dp->param<int64_t>("end_version", -1); |
131 | 21 | for (auto& rowset : candidate_rowsets) { |
132 | 21 | if (rowset->start_version() >= start_version && |
133 | 21 | rowset->end_version() <= end_version) { |
134 | 21 | input_rowsets->push_back(rowset); |
135 | 21 | } |
136 | 21 | } |
137 | 21 | LOG_INFO( |
138 | 21 | "[CloudSizeBasedCumulativeCompactionPolicy::pick_input_rowsets.set_" |
139 | 21 | "input_rowsets] tablet_id={}, start={}, end={}, " |
140 | 21 | "input_rowsets->size()={}", |
141 | 21 | target_tablet_id, start_version, end_version, input_rowsets->size()); |
142 | 21 | return input_rowsets->size(); |
143 | 21 | } |
144 | 21 | }) |
145 | | |
146 | 21 | size_t promotion_size = cloud_promotion_size(tablet); |
147 | 21 | auto max_version = tablet->max_version().first; |
148 | 21 | int transient_size = 0; |
149 | 21 | *compaction_score = 0; |
150 | 21 | int64_t total_size = 0; |
151 | 21 | bool skip_trim = false; // Skip trim for Empty Rowset Compaction |
152 | 21 | RowsetSharedPtr last_popped; |
153 | | |
154 | | // DEFER: trim input_rowsets from back if score > max_compaction_score |
155 | | // This ensures we don't return more rowsets than allowed by max_compaction_score, |
156 | | // while still collecting enough rowsets to pass min_compaction_score check after level_size removal. |
157 | | // Must be placed after variable initialization and before collection loop. |
158 | 21 | DEFER({ |
159 | 21 | if (skip_trim) { |
160 | 21 | return; |
161 | 21 | } |
162 | | // Keep at least 1 rowset to avoid removing the only rowset (consistent with fallback branch) |
163 | 21 | while (input_rowsets->size() > 1 && |
164 | 21 | *compaction_score > static_cast<size_t>(max_compaction_score)) { |
165 | 21 | last_popped = std::move(input_rowsets->back()); |
166 | 21 | *compaction_score -= last_popped->rowset_meta()->get_compaction_score(); |
167 | 21 | total_size -= last_popped->rowset_meta()->total_disk_size(); |
168 | 21 | input_rowsets->pop_back(); |
169 | 21 | } |
170 | | // A single non-overlapping rowset cannot be compacted by itself. Restore the direct |
171 | | // successor and accept a one-off max-score overshoot to keep the input mergeable. |
172 | 21 | if (input_rowsets->size() == 1 && last_popped != nullptr && |
173 | 21 | !input_rowsets->front()->rowset_meta()->is_segments_overlapping()) { |
174 | 21 | *compaction_score += last_popped->rowset_meta()->get_compaction_score(); |
175 | 21 | total_size += last_popped->rowset_meta()->total_disk_size(); |
176 | 21 | input_rowsets->push_back(std::move(last_popped)); |
177 | 21 | } |
178 | 21 | }); |
179 | | |
180 | 316 | for (auto& rowset : candidate_rowsets) { |
181 | | // check whether this rowset is delete version |
182 | 316 | if (!allow_delete && rowset->rowset_meta()->has_delete_predicate()) { |
183 | 0 | *last_delete_version = rowset->version(); |
184 | 0 | if (!input_rowsets->empty()) { |
185 | | // we meet a delete version, and there were other versions before. |
186 | | // we should compact those version before handling them over to base compaction |
187 | 0 | break; |
188 | 0 | } else { |
189 | | // we meet a delete version, and no other versions before, skip it and continue |
190 | 0 | input_rowsets->clear(); |
191 | 0 | *compaction_score = 0; |
192 | 0 | transient_size = 0; |
193 | 0 | continue; |
194 | 0 | } |
195 | 0 | } |
196 | 316 | if (tablet->tablet_state() == TABLET_NOTREADY) { |
197 | | // If tablet under alter, keep latest 10 version so that base tablet max version |
198 | | // not merged in new tablet, and then we can copy data from base tablet |
199 | 19 | if (rowset->version().second > max_version - 10) { |
200 | 10 | continue; |
201 | 10 | } |
202 | 19 | } |
203 | | // Removed: max_compaction_score check here |
204 | | // We now collect all candidate rowsets and trim from back at return time via DEFER |
205 | 306 | *compaction_score += rowset->rowset_meta()->get_compaction_score(); |
206 | 306 | total_size += rowset->rowset_meta()->total_disk_size(); |
207 | | |
208 | 306 | transient_size += 1; |
209 | 306 | input_rowsets->push_back(rowset); |
210 | 306 | } |
211 | | |
212 | | // if there is delete version, do compaction directly |
213 | 21 | if (last_delete_version->first != -1) { |
214 | 0 | if (input_rowsets->size() == 1) { |
215 | 0 | auto rs_meta = input_rowsets->front()->rowset_meta(); |
216 | | // if there is only one rowset and not overlapping, |
217 | | // we do not need to do cumulative compaction |
218 | 0 | if (!rs_meta->is_segments_overlapping()) { |
219 | 0 | input_rowsets->clear(); |
220 | 0 | *compaction_score = 0; |
221 | 0 | } |
222 | 0 | } |
223 | 0 | return transient_size; |
224 | 0 | } |
225 | | |
226 | | // Check if empty rowset compaction strategy is enabled |
227 | 21 | if (config::enable_empty_rowset_compaction && !input_rowsets->empty()) { |
228 | | // Check if input_rowsets contain consecutive empty rowsets that meet criteria |
229 | 20 | std::vector<RowsetSharedPtr> consecutive_empty_rowsets; |
230 | 20 | find_longest_consecutive_empty_rowsets(&consecutive_empty_rowsets, *input_rowsets); |
231 | | |
232 | 20 | if (!consecutive_empty_rowsets.empty() && |
233 | 20 | consecutive_empty_rowsets.size() >= config::empty_rowset_compaction_min_count && |
234 | 20 | static_cast<double>(consecutive_empty_rowsets.size()) / |
235 | 5 | static_cast<double>(input_rowsets->size()) >= |
236 | 5 | config::empty_rowset_compaction_min_ratio) { |
237 | | // Prioritize consecutive empty rowset compaction |
238 | | // Skip trim: empty rowset compaction has very low cost and the goal is to reduce rowset count |
239 | 4 | *input_rowsets = consecutive_empty_rowsets; |
240 | 4 | *compaction_score = consecutive_empty_rowsets.size(); |
241 | 4 | skip_trim = true; |
242 | 4 | return consecutive_empty_rowsets.size(); |
243 | 4 | } |
244 | 20 | } |
245 | | |
246 | 17 | auto rs_begin = input_rowsets->begin(); |
247 | 17 | size_t new_compaction_score = *compaction_score; |
248 | 17 | const bool can_handle_exhausted_input = |
249 | 17 | (config::prioritize_query_perf_in_compaction && tablet->keys_type() != DUP_KEYS) || |
250 | 17 | *compaction_score >= static_cast<size_t>(max_compaction_score); |
251 | 23 | while (rs_begin != input_rowsets->end()) { |
252 | 22 | auto& rs_meta = (*rs_begin)->rowset_meta(); |
253 | 22 | int64_t current_level = _level_size(rs_meta->total_disk_size()); |
254 | 22 | int64_t remain_level = _level_size(total_size - rs_meta->total_disk_size()); |
255 | | // if current level less then remain level, input rowsets contain current rowset |
256 | | // and process return; otherwise, input rowsets do not contain current rowset. |
257 | 22 | if (current_level <= remain_level) { |
258 | 11 | break; |
259 | 11 | } |
260 | | |
261 | 11 | auto next = std::next(rs_begin); |
262 | | // Keep the last suffix rowset for the singleton checks unless the exhausted-input |
263 | | // fallback below can select a useful input. |
264 | 11 | if (next == input_rowsets->end() && !can_handle_exhausted_input) { |
265 | 5 | break; |
266 | 5 | } |
267 | 6 | total_size -= rs_meta->total_disk_size(); |
268 | 6 | new_compaction_score -= rs_meta->get_compaction_score(); |
269 | 6 | rs_begin = next; |
270 | 6 | } |
271 | 17 | if (rs_begin == input_rowsets->end()) { // No suitable level size found in `input_rowsets` |
272 | 1 | if (config::prioritize_query_perf_in_compaction && tablet->keys_type() != DUP_KEYS) { |
273 | | // While tablet's key type is not `DUP_KEYS`, compacting rowset in such tablets has a significant |
274 | | // positive impact on queries and reduces space amplification, so we ignore level limitation and |
275 | | // pick candidate rowsets as input rowsets. |
276 | 1 | return transient_size; |
277 | 1 | } else if (*compaction_score >= max_compaction_score) { |
278 | | // Score of `input_rowsets` exceed max compaction score, which means `input_rowsets` will never change and |
279 | | // this tablet will never execute cumulative compaction. MUST execute compaction on these `input_rowsets` |
280 | | // to reduce compaction score. |
281 | 0 | RowsetSharedPtr rs_with_max_score; |
282 | 0 | uint32_t max_score = 1; |
283 | 0 | for (auto& rs : *input_rowsets) { |
284 | 0 | if (rs->rowset_meta()->get_compaction_score() > max_score) { |
285 | 0 | max_score = rs->rowset_meta()->get_compaction_score(); |
286 | 0 | rs_with_max_score = rs; |
287 | 0 | } |
288 | 0 | } |
289 | 0 | if (rs_with_max_score) { |
290 | 0 | input_rowsets->clear(); |
291 | 0 | input_rowsets->push_back(std::move(rs_with_max_score)); |
292 | 0 | *compaction_score = max_score; |
293 | 0 | return transient_size; |
294 | 0 | } |
295 | | // no rowset is OVERLAPPING, return all input rowsets (DEFER will trim to max_compaction_score) |
296 | 0 | return transient_size; |
297 | 0 | } |
298 | 1 | } |
299 | 16 | input_rowsets->erase(input_rowsets->begin(), rs_begin); |
300 | 16 | *compaction_score = new_compaction_score; |
301 | | |
302 | 16 | VLOG_CRITICAL << "cumulative compaction size_based policy, compaction_score = " |
303 | 0 | << *compaction_score << ", total_size = " << total_size |
304 | 0 | << ", calc promotion size value = " << promotion_size |
305 | 0 | << ", tablet = " << tablet->tablet_id() << ", input_rowset size " |
306 | 0 | << input_rowsets->size(); |
307 | | |
308 | | // empty return |
309 | 16 | if (input_rowsets->empty()) { |
310 | 0 | return transient_size; |
311 | 0 | } |
312 | | |
313 | | // if we have a sufficient number of segments, we should process the compaction. |
314 | | // otherwise, we check number of segments and total_size whether can do compaction. |
315 | 16 | if (total_size < _compaction_min_size && *compaction_score < min_compaction_score) { |
316 | 2 | input_rowsets->clear(); |
317 | 2 | *compaction_score = 0; |
318 | 14 | } else if (total_size >= _compaction_min_size && input_rowsets->size() == 1) { |
319 | 3 | auto rs_meta = input_rowsets->front()->rowset_meta(); |
320 | | // if there is only one rowset and not overlapping, |
321 | | // we do not need to do compaction |
322 | 3 | if (!rs_meta->is_segments_overlapping()) { |
323 | 1 | input_rowsets->clear(); |
324 | 1 | *compaction_score = 0; |
325 | 1 | } |
326 | 3 | } |
327 | 16 | return transient_size; |
328 | 16 | } |
329 | | |
330 | 41 | int64_t CloudSizeBasedCumulativeCompactionPolicy::cloud_promotion_size(CloudTablet* t) const { |
331 | 41 | int64_t promotion_size = int64_t(cast_set<double>(t->base_size()) * _promotion_ratio); |
332 | | // promotion_size is between _size_based_promotion_size and _size_based_promotion_min_size |
333 | 41 | return promotion_size > _promotion_size ? _promotion_size |
334 | 41 | : promotion_size < _promotion_min_size ? _promotion_min_size |
335 | 41 | : promotion_size; |
336 | 41 | } |
337 | | |
338 | | int64_t CloudSizeBasedCumulativeCompactionPolicy::new_cumulative_point( |
339 | | CloudTablet* tablet, const RowsetSharedPtr& output_rowset, Version& last_delete_version, |
340 | 22 | int64_t last_cumulative_point) { |
341 | 22 | TEST_INJECTION_POINT_RETURN_WITH_VALUE("new_cumulative_point", int64_t(0), output_rowset.get(), |
342 | 22 | last_cumulative_point); |
343 | 22 | DBUG_EXECUTE_IF("CloudSizeBasedCumulativeCompactionPolicy::new_cumulative_point", { |
344 | 22 | auto target_tablet_id = dp->param<int64_t>("tablet_id", -1); |
345 | 22 | auto cumu_point = dp->param<int64_t>("cumu_point", -1); |
346 | 22 | if (target_tablet_id == tablet->tablet_id() && cumu_point != -1) { |
347 | 22 | LOG_INFO( |
348 | 22 | "[CloudSizeBasedCumulativeCompactionPolicy::new_cumulative_point] " |
349 | 22 | "tablet_id={}, cumu_point={}", |
350 | 22 | target_tablet_id, cumu_point); |
351 | 22 | return cumu_point; |
352 | 22 | } |
353 | 22 | }); |
354 | | // for MoW table, if there's too many versions, the delete bitmap will grow to |
355 | | // a very big size, which may cause the tablet meta too big and the `save_meta` |
356 | | // operation too slow. |
357 | | // if the rowset should not promotion according to it's disk size, we should also |
358 | | // consider it's version count here. |
359 | 22 | bool satisfy_promotion_version = tablet->enable_unique_key_merge_on_write() && |
360 | 22 | output_rowset->end_version() - output_rowset->start_version() > |
361 | 2 | config::compaction_promotion_version_count; |
362 | | // if rowsets have delete version, move to the last directly. |
363 | | // if rowsets have no delete version, check output_rowset total disk size satisfies promotion size. |
364 | 22 | return (last_delete_version.first != -1 || |
365 | 22 | output_rowset->total_disk_size() >= cloud_promotion_size(tablet) || |
366 | 22 | satisfy_promotion_version) |
367 | 22 | ? output_rowset->end_version() + 1 |
368 | 22 | : last_cumulative_point; |
369 | 22 | } |
370 | | |
371 | | int64_t CloudTimeSeriesCumulativeCompactionPolicy::pick_input_rowsets( |
372 | | CloudTablet* tablet, const std::vector<RowsetSharedPtr>& candidate_rowsets, |
373 | | const int64_t max_compaction_score, const int64_t min_compaction_score, |
374 | | std::vector<RowsetSharedPtr>* input_rowsets, Version* last_delete_version, |
375 | 0 | size_t* compaction_score, bool allow_delete) { |
376 | 0 | int64_t last_cumu = tablet->last_cumu_compaction_success_time(); |
377 | 0 | return TimeSeriesCumulativeCompactionPolicy::pick_input_rowsets( |
378 | 0 | tablet, last_cumu, candidate_rowsets, max_compaction_score, min_compaction_score, |
379 | 0 | input_rowsets, last_delete_version, compaction_score, allow_delete); |
380 | 0 | } |
381 | | |
382 | | int64_t CloudTimeSeriesCumulativeCompactionPolicy::get_compaction_level( |
383 | | CloudTablet* tablet, const std::vector<RowsetSharedPtr>& input_rowsets, |
384 | 0 | RowsetSharedPtr output_rowset) { |
385 | 0 | return TimeSeriesCumulativeCompactionPolicy::get_compaction_level((BaseTablet*)tablet, |
386 | 0 | input_rowsets, output_rowset); |
387 | 0 | } |
388 | | |
389 | | int64_t CloudTimeSeriesCumulativeCompactionPolicy::new_cumulative_point( |
390 | | CloudTablet* tablet, const RowsetSharedPtr& output_rowset, Version& last_delete_version, |
391 | 6 | int64_t last_cumulative_point) { |
392 | 6 | if (tablet->tablet_state() != TABLET_RUNNING || output_rowset->num_segments() == 0) { |
393 | 2 | return last_cumulative_point; |
394 | 2 | } |
395 | | |
396 | 4 | if (tablet->tablet_meta()->time_series_compaction_level_threshold() >= 2 && |
397 | 4 | output_rowset->rowset_meta()->compaction_level() < 2) { |
398 | 1 | return last_cumulative_point; |
399 | 1 | } |
400 | | |
401 | 3 | return output_rowset->end_version() + 1; |
402 | 4 | } |
403 | | |
404 | | } // namespace doris |