/src/rocksdb/db/compaction/compaction_picker_universal.cc
Line | Count | Source |
1 | | // Copyright (c) 2011-present, Facebook, Inc. All rights reserved. |
2 | | // This source code is licensed under both the GPLv2 (found in the |
3 | | // COPYING file in the root directory) and Apache 2.0 License |
4 | | // (found in the LICENSE.Apache file in the root directory). |
5 | | // |
6 | | // Copyright (c) 2011 The LevelDB Authors. All rights reserved. |
7 | | // Use of this source code is governed by a BSD-style license that can be |
8 | | // found in the LICENSE file. See the AUTHORS file for names of contributors. |
9 | | |
10 | | #include "db/compaction/compaction_picker_universal.h" |
11 | | |
12 | | #include <cstdint> |
13 | | #include <limits> |
14 | | #include <queue> |
15 | | #include <string> |
16 | | #include <utility> |
17 | | |
18 | | #include "db/column_family.h" |
19 | | #include "file/filename.h" |
20 | | #include "logging/log_buffer.h" |
21 | | #include "logging/logging.h" |
22 | | #include "monitoring/statistics_impl.h" |
23 | | #include "test_util/sync_point.h" |
24 | | #include "util/random.h" |
25 | | #include "util/string_util.h" |
26 | | |
27 | | namespace ROCKSDB_NAMESPACE { |
28 | | namespace { |
29 | | // A helper class that form universal compactions. The class is used by |
30 | | // UniversalCompactionPicker::PickCompaction(). |
31 | | // The usage is to create the class, and get the compaction object by calling |
32 | | // PickCompaction(). |
33 | | class UniversalCompactionBuilder { |
34 | | public: |
35 | | UniversalCompactionBuilder( |
36 | | const ImmutableOptions& ioptions, const InternalKeyComparator* icmp, |
37 | | const std::string& cf_name, const MutableCFOptions& mutable_cf_options, |
38 | | const MutableDBOptions& mutable_db_options, |
39 | | const std::vector<SequenceNumber>& existing_snapshots, |
40 | | const SnapshotChecker* snapshot_checker, VersionStorageInfo* vstorage, |
41 | | UniversalCompactionPicker* picker, LogBuffer* log_buffer, |
42 | | bool require_max_output_level, const std::string& full_history_ts_low) |
43 | 0 | : ioptions_(ioptions), |
44 | 0 | icmp_(icmp), |
45 | 0 | cf_name_(cf_name), |
46 | 0 | mutable_cf_options_(mutable_cf_options), |
47 | 0 | mutable_db_options_(mutable_db_options), |
48 | 0 | vstorage_(vstorage), |
49 | 0 | picker_(picker), |
50 | 0 | log_buffer_(log_buffer), |
51 | 0 | require_max_output_level_(require_max_output_level), |
52 | 0 | allow_ingest_behind_(ioptions.cf_allow_ingest_behind || |
53 | 0 | ioptions.allow_ingest_behind), |
54 | 0 | full_history_ts_low_(full_history_ts_low) { |
55 | 0 | assert(icmp_); |
56 | 0 | const auto* ucmp = icmp_->user_comparator(); |
57 | 0 | assert(ucmp); |
58 | | // These parameters are only passed when user-defined timestamp is not |
59 | | // enabled. |
60 | 0 | if (ucmp->timestamp_size() == 0) { |
61 | 0 | earliest_snapshot_ = existing_snapshots.empty() |
62 | 0 | ? kMaxSequenceNumber |
63 | 0 | : existing_snapshots.at(0); |
64 | 0 | snapshot_checker_ = snapshot_checker; |
65 | 0 | } |
66 | 0 | } |
67 | | |
68 | | // Form and return the compaction object. The caller owns return object. |
69 | | Compaction* PickCompaction(); |
70 | | |
71 | | private: |
72 | | struct SortedRun { |
73 | | SortedRun(int _level, FileMetaData* _file, uint64_t _size, |
74 | | uint64_t _compensated_file_size, bool _being_compacted, |
75 | | bool _level_has_marked_standalone_rangedel) |
76 | 0 | : level(_level), |
77 | 0 | file(_file), |
78 | 0 | size(_size), |
79 | 0 | compensated_file_size(_compensated_file_size), |
80 | 0 | being_compacted(_being_compacted), |
81 | | level_has_marked_standalone_rangedel( |
82 | 0 | _level_has_marked_standalone_rangedel) { |
83 | 0 | assert(compensated_file_size > 0); |
84 | 0 | assert(level != 0 || file != nullptr); |
85 | 0 | } |
86 | | |
87 | | void Dump(char* out_buf, size_t out_buf_size, |
88 | | bool print_path = false) const; |
89 | | |
90 | | // sorted_run_count is added into the string to print |
91 | | void DumpSizeInfo(char* out_buf, size_t out_buf_size, |
92 | | size_t sorted_run_count) const; |
93 | | |
94 | | int level; |
95 | | // `file` Will be null for level > 0. For level = 0, the sorted run is |
96 | | // for this file. |
97 | | FileMetaData* file; |
98 | | // For level > 0, `size` and `compensated_file_size` are sum of sizes all |
99 | | // files in the level. `being_compacted` should be the same for all files |
100 | | // in a non-zero level. Use the value here. |
101 | | uint64_t size; |
102 | | uint64_t compensated_file_size; |
103 | | bool being_compacted; |
104 | | // True if this level has any file that is a standalone range deletion file |
105 | | // marked for compaction. Best effort is made to make only deletion |
106 | | // triggered compaction pick this type of file. |
107 | | bool level_has_marked_standalone_rangedel; |
108 | | }; |
109 | | |
110 | | unsigned int GetMaxNumFilesToCompactBasedOnMaxReadAmp( |
111 | | const int file_num_compaction_trigger, const unsigned int ratio, |
112 | 0 | int* num_sr_not_compacted_output, int* max_num_runs_output) const { |
113 | 0 | assert(num_sr_not_compacted_output); |
114 | 0 | assert(max_num_runs_output); |
115 | 0 | int max_num_runs = |
116 | 0 | mutable_cf_options_.compaction_options_universal.max_read_amp; |
117 | 0 | if (max_num_runs < 0) { |
118 | | // any value < -1 is not valid |
119 | 0 | assert(max_num_runs == -1); |
120 | | // By default, fall back to `level0_file_num_compaction_trigger` |
121 | 0 | max_num_runs = file_num_compaction_trigger; |
122 | 0 | } else if (max_num_runs == 0) { |
123 | 0 | if (mutable_cf_options_.compaction_options_universal.stop_style == |
124 | 0 | kCompactionStopStyleTotalSize) { |
125 | | // 0 means auto-tuning by RocksDB. We estimate max num run based on |
126 | | // max_run_size, size_ratio and write buffer size: |
127 | | // Assume the size of the lowest level size is equal to |
128 | | // write_buffer_size. Each subsequent level is the max size without |
129 | | // triggering size_ratio compaction. `max_num_runs` is the minimum |
130 | | // number of levels required such that the target size of the |
131 | | // largest level is at least `max_run_size_`. |
132 | 0 | max_num_runs = 1; |
133 | 0 | double cur_level_max_size = |
134 | 0 | static_cast<double>(mutable_cf_options_.write_buffer_size); |
135 | 0 | double total_run_size = 0; |
136 | 0 | while (cur_level_max_size < static_cast<double>(max_run_size_)) { |
137 | | // This loop should not take too many iterations since |
138 | | // cur_level_max_size at least doubles each iteration. |
139 | 0 | total_run_size += cur_level_max_size; |
140 | 0 | cur_level_max_size = (100.0 + ratio) / 100.0 * total_run_size; |
141 | 0 | ++max_num_runs; |
142 | 0 | } |
143 | 0 | } else { |
144 | | // TODO: implement the auto-tune logic for this stop style |
145 | 0 | max_num_runs = file_num_compaction_trigger; |
146 | 0 | } |
147 | 0 | } else { |
148 | | // max_num_runs > 0, it's the limit on the number of sorted run |
149 | 0 | } |
150 | | |
151 | | // Get the total number of sorted runs that are not being compacted |
152 | 0 | int num_sr_not_compacted = 0; |
153 | 0 | for (size_t i = 0; i < sorted_runs_.size(); i++) { |
154 | 0 | if (sorted_runs_[i].being_compacted == false && |
155 | 0 | !sorted_runs_[i].level_has_marked_standalone_rangedel) { |
156 | 0 | num_sr_not_compacted++; |
157 | 0 | } |
158 | 0 | } |
159 | |
|
160 | 0 | *num_sr_not_compacted_output = num_sr_not_compacted; |
161 | 0 | *max_num_runs_output = max_num_runs; |
162 | |
|
163 | 0 | if (num_sr_not_compacted > max_num_runs) { |
164 | 0 | return num_sr_not_compacted - max_num_runs + 1; |
165 | 0 | } else { |
166 | 0 | return 0; |
167 | 0 | } |
168 | 0 | } |
169 | | |
170 | 0 | Compaction* MaybePickPeriodicCompaction(Compaction* const prev_picked_c) { |
171 | 0 | if (prev_picked_c != nullptr || |
172 | 0 | vstorage_->FilesMarkedForPeriodicCompaction().empty()) { |
173 | 0 | return prev_picked_c; |
174 | 0 | } |
175 | | // Always need to do a full compaction for periodic compaction. |
176 | 0 | Compaction* c = PickPeriodicCompaction(); |
177 | 0 | TEST_SYNC_POINT_CALLBACK("PostPickPeriodicCompaction", c); |
178 | 0 | if (c != nullptr) { |
179 | 0 | ROCKS_LOG_BUFFER(log_buffer_, |
180 | 0 | "[%s] Universal: picked for periodic compaction\n", |
181 | 0 | cf_name_.c_str()); |
182 | 0 | } |
183 | 0 | return c; |
184 | 0 | } |
185 | | |
186 | | Compaction* MaybePickSizeAmpCompaction(Compaction* const prev_picked_c, |
187 | 0 | int file_num_compaction_trigger) { |
188 | 0 | if (prev_picked_c != nullptr || |
189 | 0 | sorted_runs_.size() < |
190 | 0 | static_cast<size_t>(file_num_compaction_trigger)) { |
191 | 0 | return prev_picked_c; |
192 | 0 | } |
193 | 0 | Compaction* c = PickCompactionToReduceSizeAmp(); |
194 | 0 | if (c != nullptr) { |
195 | 0 | TEST_SYNC_POINT("PickCompactionToReduceSizeAmpReturnNonnullptr"); |
196 | 0 | ROCKS_LOG_BUFFER(log_buffer_, |
197 | 0 | "[%s] Universal: picked for size amp compaction \n", |
198 | 0 | cf_name_.c_str()); |
199 | 0 | } |
200 | 0 | return c; |
201 | 0 | } |
202 | | |
203 | | Compaction* MaybePickCompactionToReduceSortedRunsBasedFileRatio( |
204 | | Compaction* const prev_picked_c, int file_num_compaction_trigger, |
205 | 0 | unsigned int ratio) { |
206 | 0 | if (prev_picked_c != nullptr || |
207 | 0 | sorted_runs_.size() < |
208 | 0 | static_cast<size_t>(file_num_compaction_trigger)) { |
209 | 0 | return prev_picked_c; |
210 | 0 | } |
211 | 0 | Compaction* c = PickCompactionToReduceSortedRuns(ratio, UINT_MAX); |
212 | 0 | if (c != nullptr) { |
213 | 0 | TEST_SYNC_POINT("PickCompactionToReduceSortedRunsReturnNonnullptr"); |
214 | 0 | ROCKS_LOG_BUFFER(log_buffer_, |
215 | 0 | "[%s] Universal: picked for size ratio compaction to " |
216 | 0 | "reduce sorted run\n", |
217 | 0 | cf_name_.c_str()); |
218 | 0 | } |
219 | 0 | return c; |
220 | 0 | } |
221 | | |
222 | | Compaction* MaybePickCompactionToReduceSortedRuns( |
223 | | Compaction* const prev_picked_c, int file_num_compaction_trigger, |
224 | 0 | unsigned int ratio) { |
225 | 0 | if (prev_picked_c != nullptr || |
226 | 0 | sorted_runs_.size() < |
227 | 0 | static_cast<size_t>(file_num_compaction_trigger)) { |
228 | 0 | return prev_picked_c; |
229 | 0 | } |
230 | | |
231 | 0 | int num_sr_not_compacted = 0; |
232 | 0 | int max_num_runs = 0; |
233 | 0 | const unsigned int max_num_files_to_compact = |
234 | 0 | GetMaxNumFilesToCompactBasedOnMaxReadAmp(file_num_compaction_trigger, |
235 | 0 | ratio, &num_sr_not_compacted, |
236 | 0 | &max_num_runs); |
237 | 0 | if (max_num_files_to_compact == 0) { |
238 | 0 | ROCKS_LOG_BUFFER( |
239 | 0 | log_buffer_, |
240 | 0 | "[%s] Universal: skipping compaction to reduce sorted run, num " |
241 | 0 | "sorted runs not " |
242 | 0 | "being compacted -- %u, max num runs allowed -- %d, max_run_size " |
243 | 0 | "-- %" PRIu64 "\n", |
244 | 0 | cf_name_.c_str(), num_sr_not_compacted, max_num_runs, max_run_size_); |
245 | 0 | return nullptr; |
246 | 0 | } |
247 | | |
248 | 0 | Compaction* c = |
249 | 0 | PickCompactionToReduceSortedRuns(UINT_MAX, max_num_files_to_compact); |
250 | 0 | if (c != nullptr) { |
251 | 0 | ROCKS_LOG_BUFFER(log_buffer_, |
252 | 0 | "[%s] Universal: picked for sorted run num compaction " |
253 | 0 | "to reduce sorted run, to " |
254 | 0 | "compact file num -- %u, max num runs allowed" |
255 | 0 | "-- %d, max_run_size -- %" PRIu64 "\n", |
256 | 0 | cf_name_.c_str(), max_num_files_to_compact, max_num_runs, |
257 | 0 | max_run_size_); |
258 | 0 | } |
259 | 0 | return c; |
260 | 0 | } |
261 | | |
262 | | Compaction* MaybePickDeleteTriggeredCompaction( |
263 | 0 | Compaction* const prev_picked_c) { |
264 | 0 | if (prev_picked_c != nullptr) { |
265 | 0 | return prev_picked_c; |
266 | 0 | } |
267 | 0 | Compaction* c = PickDeleteTriggeredCompaction(); |
268 | 0 | if (c != nullptr) { |
269 | 0 | TEST_SYNC_POINT("PickDeleteTriggeredCompactionReturnNonnullptr"); |
270 | 0 | ROCKS_LOG_BUFFER( |
271 | 0 | log_buffer_, |
272 | 0 | "[%s] Universal: picked for delete triggered compaction\n", |
273 | 0 | cf_name_.c_str()); |
274 | 0 | } |
275 | 0 | return c; |
276 | 0 | } |
277 | | |
278 | | Compaction* MaybePickReadTriggeredCompaction( |
279 | 0 | Compaction* const prev_picked_c) { |
280 | 0 | if (prev_picked_c != nullptr || |
281 | 0 | vstorage_->ReadTriggeredCompactionFiles().empty()) { |
282 | 0 | return prev_picked_c; |
283 | 0 | } |
284 | 0 | Compaction* c = PickReadTriggeredCompaction(); |
285 | 0 | if (c != nullptr) { |
286 | 0 | ROCKS_LOG_BUFFER(log_buffer_, |
287 | 0 | "[%s] Universal: picked for read triggered compaction\n", |
288 | 0 | cf_name_.c_str()); |
289 | 0 | } |
290 | 0 | return c; |
291 | 0 | } |
292 | | |
293 | | // Pick Universal compaction to limit read amplification |
294 | | Compaction* PickCompactionToReduceSortedRuns( |
295 | | unsigned int ratio, unsigned int max_number_of_files_to_compact); |
296 | | |
297 | | // Pick Universal compaction to limit space amplification. |
298 | | Compaction* PickCompactionToReduceSizeAmp(); |
299 | | |
300 | | // Try to pick incremental compaction to reduce space amplification. |
301 | | // It will return null if it cannot find a fanout within the threshold. |
302 | | // Fanout is defined as |
303 | | // total size of files to compact at output level |
304 | | // -------------------------------------------------- |
305 | | // total size of files to compact at other levels |
306 | | Compaction* PickIncrementalForReduceSizeAmp(double fanout_threshold); |
307 | | |
308 | | Compaction* PickDeleteTriggeredCompaction(); |
309 | | |
310 | | Compaction* PickReadTriggeredCompaction(); |
311 | | |
312 | | // Given already-selected start_level_inputs, find the first non-empty output |
313 | | // level, compute overlapping inputs, and create a Compaction. Can produce |
314 | | // intra-L0 compaction if there is only level 0. |
315 | | Compaction* BuildCompactionToNextLevel( |
316 | | CompactionInputFiles& start_level_inputs, CompactionReason reason); |
317 | | |
318 | | // Returns true if this given file (that is marked be compaction) should be |
319 | | // skipped from being picked for now. We do this to best use standalone range |
320 | | // tombstone files. |
321 | | bool ShouldSkipMarkedFile(const FileMetaData* file) const; |
322 | | |
323 | | // Form a compaction from the sorted run indicated by start_index to the |
324 | | // oldest sorted run. |
325 | | // The caller is responsible for making sure that those files are not in |
326 | | // compaction. |
327 | | Compaction* PickCompactionToOldest(size_t start_index, |
328 | | CompactionReason compaction_reason); |
329 | | |
330 | | Compaction* PickCompactionWithSortedRunRange( |
331 | | size_t start_index, size_t end_index, CompactionReason compaction_reason); |
332 | | |
333 | | // Try to pick periodic compaction. The caller should only call it |
334 | | // if there is at least one file marked for periodic compaction. |
335 | | // null will be returned if no such a compaction can be formed |
336 | | // because some files are being compacted. |
337 | | Compaction* PickPeriodicCompaction(); |
338 | | |
339 | 0 | bool ShouldSkipLastSortedRunForSizeAmpCompaction() const { |
340 | 0 | assert(!sorted_runs_.empty()); |
341 | 0 | return mutable_cf_options_.preclude_last_level_data_seconds > 0 && |
342 | 0 | ioptions_.num_levels > 2 && |
343 | 0 | sorted_runs_.back().level == ioptions_.num_levels - 1 && |
344 | 0 | sorted_runs_.size() > 1; |
345 | 0 | } |
346 | | // Used in universal compaction when the allow_trivial_move |
347 | | // option is set. Checks whether there are any overlapping files |
348 | | // in the input. Returns true if the input files are non |
349 | | // overlapping. |
350 | | bool IsInputFilesNonOverlapping(Compaction* c); |
351 | | |
352 | | uint64_t GetMaxOverlappingBytes() const; |
353 | | |
354 | | // To conditionally exclude some of the newest L0 files |
355 | | // from a size amp compaction. This is to prevent a large number of L0 |
356 | | // files from being locked by a size amp compaction, potentially leading to |
357 | | // write stop with a few more flushes. |
358 | | // |
359 | | // Such exclusion is based on `num_l0_input_pre_exclusion`, |
360 | | // `level0_stop_writes_trigger`, `max/min_merge_width` and the pre-exclusion |
361 | | // compaction score. Noted that it will not make the size amp compaction of |
362 | | // interest invalid from running as a size amp compaction as long as its |
363 | | // pre-exclusion compaction score satisfies the condition to run. |
364 | | // |
365 | | // @param `num_l0_input_pre_exclusion` Number of L0 input files prior to |
366 | | // exclusion |
367 | | // @param `end_index` Index of the last sorted run selected as compaction |
368 | | // input. Will not be affected by this exclusion. |
369 | | // @param `start_index` Index of the first input sorted run prior to |
370 | | // exclusion. Will be modified as output based on the exclusion. |
371 | | // @param `candidate_size` Total size of all except for the last input sorted |
372 | | // runs prior to exclusion. Will be modified as output based on the exclusion. |
373 | | // |
374 | | // @return Number of L0 files to exclude. `start_index` and |
375 | | // `candidate_size` will be modified accordingly |
376 | | std::size_t MightExcludeNewL0sToReduceWriteStop( |
377 | | std::size_t num_l0_input_pre_exclusion, std::size_t end_index, |
378 | 0 | std::size_t& start_index, uint64_t& candidate_size) const { |
379 | 0 | if (num_l0_input_pre_exclusion == 0) { |
380 | 0 | return 0; |
381 | 0 | } |
382 | | |
383 | 0 | assert(start_index <= end_index && sorted_runs_.size() > end_index); |
384 | 0 | assert(mutable_cf_options_.level0_stop_writes_trigger > 0); |
385 | 0 | const std::size_t level0_stop_writes_trigger = static_cast<std::size_t>( |
386 | 0 | mutable_cf_options_.level0_stop_writes_trigger); |
387 | 0 | const std::size_t max_merge_width = static_cast<std::size_t>( |
388 | 0 | mutable_cf_options_.compaction_options_universal.max_merge_width); |
389 | 0 | const std::size_t min_merge_width = static_cast<std::size_t>( |
390 | 0 | mutable_cf_options_.compaction_options_universal.min_merge_width); |
391 | 0 | const uint64_t max_size_amplification_percent = |
392 | 0 | mutable_cf_options_.compaction_options_universal |
393 | 0 | .max_size_amplification_percent; |
394 | 0 | const uint64_t base_sr_size = sorted_runs_[end_index].size; |
395 | | |
396 | | // Leave at least 1 L0 file and 2 input sorted runs after exclusion |
397 | 0 | const std::size_t max_num_l0_to_exclude = |
398 | 0 | std::min(num_l0_input_pre_exclusion - 1, end_index - start_index - 1); |
399 | | // In universal compaction, sorted runs from non L0 levels are counted |
400 | | // toward `level0_stop_writes_trigger`. Therefore we need to subtract the |
401 | | // total number of sorted runs picked originally for this compaction from |
402 | | // `level0_stop_writes_trigger` to calculate |
403 | | // `num_extra_l0_before_write_stop` |
404 | 0 | const std::size_t num_extra_l0_before_write_stop = |
405 | 0 | level0_stop_writes_trigger - |
406 | 0 | std::min(level0_stop_writes_trigger, end_index - start_index + 1); |
407 | 0 | const std::size_t num_l0_to_exclude_for_max_merge_width = |
408 | 0 | std::min(max_merge_width - |
409 | 0 | std::min(max_merge_width, num_extra_l0_before_write_stop), |
410 | 0 | max_num_l0_to_exclude); |
411 | 0 | const std::size_t num_l0_to_exclude_for_min_merge_width = |
412 | 0 | std::min(min_merge_width - |
413 | 0 | std::min(min_merge_width, num_extra_l0_before_write_stop), |
414 | 0 | max_num_l0_to_exclude); |
415 | |
|
416 | 0 | std::size_t num_l0_to_exclude = 0; |
417 | 0 | uint64_t candidate_size_post_exclusion = candidate_size; |
418 | |
|
419 | 0 | for (std::size_t possible_num_l0_to_exclude = |
420 | 0 | num_l0_to_exclude_for_min_merge_width; |
421 | 0 | possible_num_l0_to_exclude <= num_l0_to_exclude_for_max_merge_width; |
422 | 0 | ++possible_num_l0_to_exclude) { |
423 | 0 | uint64_t current_candidate_size = candidate_size_post_exclusion; |
424 | 0 | for (std::size_t j = num_l0_to_exclude; j < possible_num_l0_to_exclude; |
425 | 0 | ++j) { |
426 | 0 | current_candidate_size -= |
427 | 0 | sorted_runs_.at(start_index + j).compensated_file_size; |
428 | 0 | } |
429 | | |
430 | | // To ensure the compaction score before and after exclusion is similar |
431 | | // so this exclusion will not make the size amp compaction of |
432 | | // interest invalid from running as a size amp compaction as long as its |
433 | | // pre-exclusion compaction score satisfies the condition to run. |
434 | 0 | if (current_candidate_size * 100 < |
435 | 0 | max_size_amplification_percent * base_sr_size || |
436 | 0 | current_candidate_size < candidate_size * 9 / 10) { |
437 | 0 | break; |
438 | 0 | } |
439 | 0 | num_l0_to_exclude = possible_num_l0_to_exclude; |
440 | 0 | candidate_size_post_exclusion = current_candidate_size; |
441 | 0 | } |
442 | |
|
443 | 0 | start_index += num_l0_to_exclude; |
444 | 0 | candidate_size = candidate_size_post_exclusion; |
445 | 0 | return num_l0_to_exclude; |
446 | 0 | } |
447 | | |
448 | 0 | bool MeetsOutputLevelRequirements(int output_level) const { |
449 | 0 | return !require_max_output_level_ || |
450 | 0 | Compaction::OutputToNonZeroMaxOutputLevel( |
451 | 0 | output_level, vstorage_->MaxOutputLevel(allow_ingest_behind_)); |
452 | 0 | } |
453 | | |
454 | | const ImmutableOptions& ioptions_; |
455 | | const InternalKeyComparator* icmp_; |
456 | | double score_; |
457 | | std::vector<SortedRun> sorted_runs_; |
458 | | uint64_t max_run_size_; |
459 | | const std::string& cf_name_; |
460 | | const MutableCFOptions& mutable_cf_options_; |
461 | | const MutableDBOptions& mutable_db_options_; |
462 | | VersionStorageInfo* vstorage_; |
463 | | UniversalCompactionPicker* picker_; |
464 | | LogBuffer* log_buffer_; |
465 | | // Optional earliest snapshot at time of compaction picking. This is only |
466 | | // provided if the column family doesn't enable user-defined timestamps. |
467 | | // And this information is only passed to `Compaction` picked by deletion |
468 | | // triggered compaction for possible optimizations. |
469 | | std::optional<SequenceNumber> earliest_snapshot_; |
470 | | const SnapshotChecker* snapshot_checker_ = nullptr; |
471 | | // Mapping from file id to its index in the sorted run for the files that are |
472 | | // marked for compaction. This is only populated when snapshot info is |
473 | | // populated. |
474 | | std::map<uint64_t, size_t> file_marked_for_compaction_to_sorted_run_index_; |
475 | | bool require_max_output_level_; |
476 | | bool allow_ingest_behind_; |
477 | | const std::string& full_history_ts_low_; |
478 | | |
479 | | std::vector<UniversalCompactionBuilder::SortedRun> CalculateSortedRuns( |
480 | | const VersionStorageInfo& vstorage, int last_level, |
481 | | uint64_t* max_run_size); |
482 | | |
483 | | // Pick a path ID to place a newly generated file, with its estimated file |
484 | | // size. |
485 | | static uint32_t GetPathId(const ImmutableCFOptions& ioptions, |
486 | | const MutableCFOptions& mutable_cf_options, |
487 | | uint64_t file_size); |
488 | | }; |
489 | | |
490 | | // Used in universal compaction when trivial move is enabled. |
491 | | // This structure is used for the construction of min heap |
492 | | // that contains the file meta data, the level of the file |
493 | | // and the index of the file in that level |
494 | | |
495 | | struct InputFileInfo { |
496 | 0 | InputFileInfo() : InputFileInfo(nullptr, 0, 0) {} |
497 | | InputFileInfo(FileMetaData* file_meta, size_t l, size_t i) |
498 | 0 | : f(file_meta), level(l), index(i) {} |
499 | | |
500 | | FileMetaData* f; |
501 | | size_t level; |
502 | | size_t index; |
503 | | }; |
504 | | |
505 | | // Used in universal compaction when trivial move is enabled. |
506 | | // This comparator is used for the construction of min heap |
507 | | // based on the smallest key of the file. |
508 | | struct SmallestKeyHeapComparator { |
509 | 0 | explicit SmallestKeyHeapComparator(const Comparator* ucmp) { ucmp_ = ucmp; } |
510 | | |
511 | 0 | bool operator()(InputFileInfo i1, InputFileInfo i2) const { |
512 | 0 | return (ucmp_->CompareWithoutTimestamp(i1.f->smallest.user_key(), |
513 | 0 | i2.f->smallest.user_key()) > 0); |
514 | 0 | } |
515 | | |
516 | | private: |
517 | | const Comparator* ucmp_; |
518 | | }; |
519 | | |
520 | | using SmallestKeyHeap = |
521 | | std::priority_queue<InputFileInfo, std::vector<InputFileInfo>, |
522 | | SmallestKeyHeapComparator>; |
523 | | |
524 | | // This function creates the heap that is used to find if the files are |
525 | | // overlapping during universal compaction when the allow_trivial_move |
526 | | // is set. |
527 | 0 | SmallestKeyHeap create_level_heap(Compaction* c, const Comparator* ucmp) { |
528 | 0 | SmallestKeyHeap smallest_key_priority_q = |
529 | 0 | SmallestKeyHeap(SmallestKeyHeapComparator(ucmp)); |
530 | |
|
531 | 0 | for (size_t l = 0; l < c->num_input_levels(); l++) { |
532 | 0 | if (c->num_input_files(l) != 0) { |
533 | 0 | if (l == 0 && c->start_level() == 0) { |
534 | 0 | for (size_t i = 0; i < c->num_input_files(0); i++) { |
535 | 0 | smallest_key_priority_q.emplace(c->input(0, i), 0, i); |
536 | 0 | } |
537 | 0 | } else { |
538 | 0 | smallest_key_priority_q.emplace(c->input(l, 0), l, 0); |
539 | 0 | } |
540 | 0 | } |
541 | 0 | } |
542 | 0 | return smallest_key_priority_q; |
543 | 0 | } |
544 | | |
545 | | #ifndef NDEBUG |
546 | | // smallest_seqno and largest_seqno are set iff. `files` is not empty. |
547 | | void GetSmallestLargestSeqno(const std::vector<FileMetaData*>& files, |
548 | | SequenceNumber* smallest_seqno, |
549 | | SequenceNumber* largest_seqno) { |
550 | | bool is_first = true; |
551 | | for (FileMetaData* f : files) { |
552 | | assert(f->fd.smallest_seqno <= f->fd.largest_seqno); |
553 | | if (is_first) { |
554 | | is_first = false; |
555 | | *smallest_seqno = f->fd.smallest_seqno; |
556 | | *largest_seqno = f->fd.largest_seqno; |
557 | | } else { |
558 | | if (f->fd.smallest_seqno < *smallest_seqno) { |
559 | | *smallest_seqno = f->fd.smallest_seqno; |
560 | | } |
561 | | if (f->fd.largest_seqno > *largest_seqno) { |
562 | | *largest_seqno = f->fd.largest_seqno; |
563 | | } |
564 | | } |
565 | | } |
566 | | } |
567 | | #endif |
568 | | } // namespace |
569 | | |
570 | | // Algorithm that checks to see if there are any overlapping |
571 | | // files in the input |
572 | 0 | bool UniversalCompactionBuilder::IsInputFilesNonOverlapping(Compaction* c) { |
573 | 0 | auto comparator = icmp_->user_comparator(); |
574 | 0 | int first_iter = 1; |
575 | |
|
576 | 0 | InputFileInfo prev; |
577 | 0 | InputFileInfo curr; |
578 | |
|
579 | 0 | SmallestKeyHeap smallest_key_priority_q = |
580 | 0 | create_level_heap(c, icmp_->user_comparator()); |
581 | |
|
582 | 0 | while (!smallest_key_priority_q.empty()) { |
583 | 0 | curr = smallest_key_priority_q.top(); |
584 | 0 | smallest_key_priority_q.pop(); |
585 | |
|
586 | 0 | if (first_iter) { |
587 | 0 | prev = curr; |
588 | 0 | first_iter = 0; |
589 | 0 | } else { |
590 | 0 | if (comparator->CompareWithoutTimestamp( |
591 | 0 | prev.f->largest.user_key(), curr.f->smallest.user_key()) >= 0) { |
592 | | // found overlapping files, return false |
593 | 0 | return false; |
594 | 0 | } |
595 | 0 | assert(comparator->CompareWithoutTimestamp( |
596 | 0 | curr.f->largest.user_key(), prev.f->largest.user_key()) > 0); |
597 | 0 | prev = curr; |
598 | 0 | } |
599 | | |
600 | 0 | if (c->level(curr.level) != 0 && |
601 | 0 | curr.index < c->num_input_files(curr.level) - 1) { |
602 | 0 | smallest_key_priority_q.emplace(c->input(curr.level, curr.index + 1), |
603 | 0 | curr.level, curr.index + 1); |
604 | 0 | } |
605 | 0 | } |
606 | 0 | return true; |
607 | 0 | } |
608 | | |
609 | | bool UniversalCompactionPicker::NeedsCompaction( |
610 | 0 | const VersionStorageInfo* vstorage) const { |
611 | 0 | const int kLevel0 = 0; |
612 | 0 | if (vstorage->CompactionScore(kLevel0) >= 1) { |
613 | 0 | return true; |
614 | 0 | } |
615 | 0 | if (!vstorage->FilesMarkedForPeriodicCompaction().empty()) { |
616 | 0 | return true; |
617 | 0 | } |
618 | 0 | if (!vstorage->FilesMarkedForCompaction().empty()) { |
619 | 0 | return true; |
620 | 0 | } |
621 | 0 | if (!vstorage->ReadTriggeredCompactionFiles().empty()) { |
622 | 0 | return true; |
623 | 0 | } |
624 | 0 | return false; |
625 | 0 | } |
626 | | |
627 | | // TODO leverage full_history_ts_low in universal compaction picking. It could |
628 | | // help reduce the same infinite compaction loop issue found in level |
629 | | // compaction. |
630 | | Compaction* UniversalCompactionPicker::PickCompaction( |
631 | | const std::string& cf_name, const MutableCFOptions& mutable_cf_options, |
632 | | const MutableDBOptions& mutable_db_options, |
633 | | const std::vector<SequenceNumber>& existing_snapshots, |
634 | | const SnapshotChecker* snapshot_checker, VersionStorageInfo* vstorage, |
635 | | LogBuffer* log_buffer, const std::string& full_history_ts_low, |
636 | 0 | bool require_max_output_level) { |
637 | 0 | UniversalCompactionBuilder builder( |
638 | 0 | ioptions_, icmp_, cf_name, mutable_cf_options, mutable_db_options, |
639 | 0 | existing_snapshots, snapshot_checker, vstorage, this, log_buffer, |
640 | 0 | require_max_output_level, full_history_ts_low); |
641 | 0 | return builder.PickCompaction(); |
642 | 0 | } |
643 | | |
644 | | void UniversalCompactionBuilder::SortedRun::Dump(char* out_buf, |
645 | | size_t out_buf_size, |
646 | 0 | bool print_path) const { |
647 | 0 | if (level == 0) { |
648 | 0 | assert(file != nullptr); |
649 | 0 | if (file->fd.GetPathId() == 0 || !print_path) { |
650 | 0 | snprintf(out_buf, out_buf_size, "file %" PRIu64, file->fd.GetNumber()); |
651 | 0 | } else { |
652 | 0 | snprintf(out_buf, out_buf_size, |
653 | 0 | "file %" PRIu64 |
654 | 0 | "(path " |
655 | 0 | "%" PRIu32 ")", |
656 | 0 | file->fd.GetNumber(), file->fd.GetPathId()); |
657 | 0 | } |
658 | 0 | } else { |
659 | 0 | snprintf(out_buf, out_buf_size, "level %d", level); |
660 | 0 | } |
661 | 0 | } |
662 | | |
663 | | void UniversalCompactionBuilder::SortedRun::DumpSizeInfo( |
664 | 0 | char* out_buf, size_t out_buf_size, size_t sorted_run_count) const { |
665 | 0 | if (level == 0) { |
666 | 0 | assert(file != nullptr); |
667 | 0 | snprintf(out_buf, out_buf_size, |
668 | 0 | "file %" PRIu64 "[%" ROCKSDB_PRIszt |
669 | 0 | "] " |
670 | 0 | "with size %" PRIu64 " (compensated size %" PRIu64 ")", |
671 | 0 | file->fd.GetNumber(), sorted_run_count, file->fd.GetFileSize(), |
672 | 0 | file->compensated_file_size); |
673 | 0 | } else { |
674 | 0 | snprintf(out_buf, out_buf_size, |
675 | 0 | "level %d[%" ROCKSDB_PRIszt |
676 | 0 | "] " |
677 | 0 | "with size %" PRIu64 " (compensated size %" PRIu64 ")", |
678 | 0 | level, sorted_run_count, size, compensated_file_size); |
679 | 0 | } |
680 | 0 | } |
681 | | |
682 | | std::vector<UniversalCompactionBuilder::SortedRun> |
683 | | UniversalCompactionBuilder::CalculateSortedRuns( |
684 | | const VersionStorageInfo& vstorage, int last_level, |
685 | 0 | uint64_t* max_run_size) { |
686 | 0 | assert(max_run_size); |
687 | 0 | *max_run_size = 0; |
688 | 0 | std::vector<UniversalCompactionBuilder::SortedRun> ret; |
689 | 0 | for (FileMetaData* f : vstorage.LevelFiles(0)) { |
690 | 0 | if (earliest_snapshot_.has_value() && f->marked_for_compaction) { |
691 | 0 | file_marked_for_compaction_to_sorted_run_index_.emplace(f->fd.GetNumber(), |
692 | 0 | ret.size()); |
693 | 0 | } |
694 | 0 | ret.emplace_back( |
695 | 0 | 0, f, f->fd.GetFileSize(), f->compensated_file_size, f->being_compacted, |
696 | 0 | f->marked_for_compaction && f->FileIsStandAloneRangeTombstone()); |
697 | 0 | *max_run_size = std::max(*max_run_size, f->fd.GetFileSize()); |
698 | 0 | } |
699 | 0 | for (int level = 1; level <= last_level; level++) { |
700 | 0 | uint64_t total_compensated_size = 0U; |
701 | 0 | uint64_t total_size = 0U; |
702 | 0 | bool being_compacted = false; |
703 | 0 | bool level_has_marked_standalone_rangedel = false; |
704 | 0 | for (FileMetaData* f : vstorage.LevelFiles(level)) { |
705 | 0 | total_compensated_size += f->compensated_file_size; |
706 | 0 | total_size += f->fd.GetFileSize(); |
707 | | // Size amp, read amp and periodic compactions always include all files |
708 | | // for a non-zero level. However, a delete triggered compaction and |
709 | | // a trivial move might pick a subset of files in a sorted run. So |
710 | | // always check all files in a sorted run and mark the entire run as |
711 | | // being compacted if one or more files are being compacted |
712 | 0 | if (f->being_compacted) { |
713 | 0 | being_compacted = f->being_compacted; |
714 | 0 | } |
715 | 0 | level_has_marked_standalone_rangedel = |
716 | 0 | level_has_marked_standalone_rangedel || |
717 | 0 | (f->marked_for_compaction && f->FileIsStandAloneRangeTombstone()); |
718 | 0 | if (earliest_snapshot_.has_value() && f->marked_for_compaction) { |
719 | 0 | file_marked_for_compaction_to_sorted_run_index_.emplace( |
720 | 0 | f->fd.GetNumber(), ret.size()); |
721 | 0 | } |
722 | 0 | } |
723 | 0 | if (total_compensated_size > 0) { |
724 | 0 | ret.emplace_back(level, nullptr, total_size, total_compensated_size, |
725 | 0 | being_compacted, level_has_marked_standalone_rangedel); |
726 | 0 | } |
727 | 0 | *max_run_size = std::max(*max_run_size, total_size); |
728 | 0 | } |
729 | 0 | return ret; |
730 | 0 | } |
731 | | |
732 | | bool UniversalCompactionBuilder::ShouldSkipMarkedFile( |
733 | 0 | const FileMetaData* file) const { |
734 | 0 | assert(file->marked_for_compaction); |
735 | 0 | if (!earliest_snapshot_.has_value()) { |
736 | 0 | return false; |
737 | 0 | } |
738 | 0 | if (!file->FileIsStandAloneRangeTombstone()) { |
739 | 0 | return false; |
740 | 0 | } |
741 | | // Skip until earliest snapshot advances at or above this standalone range |
742 | | // tombstone file. `DB::ReleaseSnapshot` will re-examine and schedule |
743 | | // compaction for it. |
744 | 0 | if (!DataIsDefinitelyInSnapshot(file->fd.largest_seqno, |
745 | 0 | earliest_snapshot_.value(), |
746 | 0 | snapshot_checker_)) { |
747 | 0 | return true; |
748 | 0 | } |
749 | | |
750 | 0 | auto iter = file_marked_for_compaction_to_sorted_run_index_.find( |
751 | 0 | file->fd.GetNumber()); |
752 | 0 | assert(iter != file_marked_for_compaction_to_sorted_run_index_.end()); |
753 | 0 | size_t idx = iter->second; |
754 | 0 | const SortedRun* succeeding_sorted_run = |
755 | 0 | idx < sorted_runs_.size() - 1 ? &sorted_runs_[idx + 1] : nullptr; |
756 | | // Marked standalone range tombstone file is best used if it's in the start |
757 | | // input level. Skip to let that compaction happen first. |
758 | 0 | if (succeeding_sorted_run && |
759 | 0 | succeeding_sorted_run->level_has_marked_standalone_rangedel) { |
760 | 0 | return true; |
761 | 0 | } |
762 | | |
763 | 0 | return false; |
764 | 0 | } |
765 | | |
766 | | // Universal style of compaction. Pick files that are contiguous in |
767 | | // time-range to compact. |
768 | 0 | Compaction* UniversalCompactionBuilder::PickCompaction() { |
769 | 0 | const int kLevel0 = 0; |
770 | 0 | score_ = vstorage_->CompactionScore(kLevel0); |
771 | 0 | const int max_output_level = vstorage_->MaxOutputLevel(allow_ingest_behind_); |
772 | 0 | const int file_num_compaction_trigger = |
773 | 0 | mutable_cf_options_.level0_file_num_compaction_trigger; |
774 | 0 | const unsigned int ratio = |
775 | 0 | mutable_cf_options_.compaction_options_universal.size_ratio; |
776 | |
|
777 | 0 | if (max_output_level == 0 && |
778 | 0 | !MeetsOutputLevelRequirements(0 /* output_level */)) { |
779 | 0 | return nullptr; |
780 | 0 | } |
781 | | |
782 | 0 | max_run_size_ = 0; |
783 | 0 | sorted_runs_ = |
784 | 0 | CalculateSortedRuns(*vstorage_, max_output_level, &max_run_size_); |
785 | |
|
786 | 0 | if (sorted_runs_.size() == 0 || |
787 | 0 | (vstorage_->FilesMarkedForPeriodicCompaction().empty() && |
788 | 0 | vstorage_->FilesMarkedForCompaction().empty() && |
789 | 0 | vstorage_->ReadTriggeredCompactionFiles().empty() && |
790 | 0 | sorted_runs_.size() < (unsigned int)file_num_compaction_trigger)) { |
791 | 0 | ROCKS_LOG_BUFFER(log_buffer_, "[%s] Universal: nothing to do\n", |
792 | 0 | cf_name_.c_str()); |
793 | 0 | TEST_SYNC_POINT_CALLBACK( |
794 | 0 | "UniversalCompactionBuilder::PickCompaction:Return", nullptr); |
795 | 0 | return nullptr; |
796 | 0 | } |
797 | | |
798 | 0 | VersionStorageInfo::LevelSummaryStorage tmp; |
799 | 0 | ROCKS_LOG_BUFFER_MAX_SZ( |
800 | 0 | log_buffer_, 3072, |
801 | 0 | "[%s] Universal: sorted runs: %" ROCKSDB_PRIszt " files: %s\n", |
802 | 0 | cf_name_.c_str(), sorted_runs_.size(), vstorage_->LevelSummary(&tmp)); |
803 | |
|
804 | 0 | Compaction* c = nullptr; |
805 | |
|
806 | 0 | c = MaybePickPeriodicCompaction(c); |
807 | 0 | c = MaybePickSizeAmpCompaction(c, file_num_compaction_trigger); |
808 | 0 | c = MaybePickCompactionToReduceSortedRunsBasedFileRatio( |
809 | 0 | c, file_num_compaction_trigger, ratio); |
810 | 0 | c = MaybePickCompactionToReduceSortedRuns(c, file_num_compaction_trigger, |
811 | 0 | ratio); |
812 | 0 | c = MaybePickDeleteTriggeredCompaction(c); |
813 | 0 | c = MaybePickReadTriggeredCompaction(c); |
814 | |
|
815 | 0 | if (c == nullptr) { |
816 | 0 | TEST_SYNC_POINT_CALLBACK( |
817 | 0 | "UniversalCompactionBuilder::PickCompaction:Return", nullptr); |
818 | 0 | return nullptr; |
819 | 0 | } |
820 | 0 | assert(c->output_level() <= vstorage_->MaxOutputLevel(allow_ingest_behind_)); |
821 | 0 | assert(MeetsOutputLevelRequirements(c->output_level())); |
822 | |
|
823 | 0 | if (mutable_cf_options_.compaction_options_universal.allow_trivial_move == |
824 | 0 | true && |
825 | 0 | c->compaction_reason() != CompactionReason::kPeriodicCompaction) { |
826 | 0 | c->set_is_trivial_move(IsInputFilesNonOverlapping(c)); |
827 | 0 | } |
828 | | |
829 | | // validate that all the chosen files of L0 are non overlapping in time |
830 | | #ifndef NDEBUG |
831 | | bool is_first = true; |
832 | | |
833 | | size_t level_index = 0U; |
834 | | if (c->start_level() == 0) { |
835 | | for (auto f : *c->inputs(0)) { |
836 | | assert(f->fd.smallest_seqno <= f->fd.largest_seqno); |
837 | | if (is_first) { |
838 | | is_first = false; |
839 | | } |
840 | | } |
841 | | level_index = 1U; |
842 | | } |
843 | | for (; level_index < c->num_input_levels(); level_index++) { |
844 | | if (c->num_input_files(level_index) != 0) { |
845 | | SequenceNumber smallest_seqno = 0U; |
846 | | SequenceNumber largest_seqno = 0U; |
847 | | GetSmallestLargestSeqno(*(c->inputs(level_index)), &smallest_seqno, |
848 | | &largest_seqno); |
849 | | if (is_first) { |
850 | | is_first = false; |
851 | | } |
852 | | } |
853 | | } |
854 | | #endif |
855 | | // update statistics |
856 | 0 | size_t num_files = 0; |
857 | 0 | for (auto& each_level : *c->inputs()) { |
858 | 0 | num_files += each_level.files.size(); |
859 | 0 | } |
860 | 0 | RecordInHistogram(ioptions_.stats, NUM_FILES_IN_SINGLE_COMPACTION, num_files); |
861 | |
|
862 | 0 | picker_->RegisterCompaction(c); |
863 | 0 | vstorage_->ComputeCompactionScore(ioptions_, mutable_cf_options_, |
864 | 0 | full_history_ts_low_); |
865 | |
|
866 | 0 | TEST_SYNC_POINT_CALLBACK("UniversalCompactionBuilder::PickCompaction:Return", |
867 | 0 | c); |
868 | 0 | return c; |
869 | 0 | } |
870 | | |
871 | | uint32_t UniversalCompactionBuilder::GetPathId( |
872 | | const ImmutableCFOptions& ioptions, |
873 | 0 | const MutableCFOptions& mutable_cf_options, uint64_t file_size) { |
874 | | // Two conditions need to be satisfied: |
875 | | // (1) the target path needs to be able to hold the file's size |
876 | | // (2) Total size left in this and previous paths need to be not |
877 | | // smaller than expected future file size before this new file is |
878 | | // compacted, which is estimated based on size_ratio. |
879 | | // For example, if now we are compacting files of size (1, 1, 2, 4, 8), |
880 | | // we will make sure the target file, probably with size of 16, will be |
881 | | // placed in a path so that eventually when new files are generated and |
882 | | // compacted to (1, 1, 2, 4, 8, 16), all those files can be stored in or |
883 | | // before the path we chose. |
884 | | // |
885 | | // TODO(sdong): now the case of multiple column families is not |
886 | | // considered in this algorithm. So the target size can be violated in |
887 | | // that case. We need to improve it. |
888 | 0 | uint64_t accumulated_size = 0; |
889 | 0 | uint64_t future_size = |
890 | 0 | file_size * |
891 | 0 | (100 - mutable_cf_options.compaction_options_universal.size_ratio) / 100; |
892 | 0 | uint32_t p = 0; |
893 | 0 | assert(!ioptions.cf_paths.empty()); |
894 | 0 | for (; p < ioptions.cf_paths.size() - 1; p++) { |
895 | 0 | uint64_t target_size = ioptions.cf_paths[p].target_size; |
896 | 0 | if (target_size > file_size && |
897 | 0 | accumulated_size + (target_size - file_size) > future_size) { |
898 | 0 | return p; |
899 | 0 | } |
900 | 0 | accumulated_size += target_size; |
901 | 0 | } |
902 | 0 | return p; |
903 | 0 | } |
904 | | |
905 | | // |
906 | | // Consider compaction files based on their size differences with |
907 | | // the next file in time order. |
908 | | // |
909 | | Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns( |
910 | 0 | unsigned int ratio, unsigned int max_number_of_files_to_compact) { |
911 | 0 | unsigned int min_merge_width = |
912 | 0 | mutable_cf_options_.compaction_options_universal.min_merge_width; |
913 | 0 | unsigned int max_merge_width = |
914 | 0 | mutable_cf_options_.compaction_options_universal.max_merge_width; |
915 | |
|
916 | 0 | const SortedRun* sr = nullptr; |
917 | 0 | bool done = false; |
918 | 0 | size_t start_index = 0; |
919 | 0 | unsigned int candidate_count = 0; |
920 | |
|
921 | 0 | unsigned int max_files_to_compact = |
922 | 0 | std::min(max_merge_width, max_number_of_files_to_compact); |
923 | 0 | min_merge_width = std::max(min_merge_width, 2U); |
924 | | |
925 | | // Caller checks the size before executing this function. This invariant is |
926 | | // important because otherwise we may have a possible integer underflow when |
927 | | // dealing with unsigned types. |
928 | 0 | assert(sorted_runs_.size() > 0); |
929 | | |
930 | | // Considers a candidate file only if it is smaller than the |
931 | | // total size accumulated so far. |
932 | 0 | for (size_t loop = 0; loop < sorted_runs_.size(); loop++) { |
933 | 0 | candidate_count = 0; |
934 | | |
935 | | // Skip files that are already being compacted |
936 | 0 | for (sr = nullptr; loop < sorted_runs_.size(); loop++) { |
937 | 0 | sr = &sorted_runs_[loop]; |
938 | |
|
939 | 0 | if (!sr->being_compacted && !sr->level_has_marked_standalone_rangedel) { |
940 | 0 | candidate_count = 1; |
941 | 0 | break; |
942 | 0 | } |
943 | 0 | char file_num_buf[kFormatFileNumberBufSize]; |
944 | 0 | sr->Dump(file_num_buf, sizeof(file_num_buf)); |
945 | 0 | if (sr->being_compacted) { |
946 | 0 | ROCKS_LOG_BUFFER(log_buffer_, |
947 | 0 | "[%s] Universal: %s" |
948 | 0 | "[%" ROCKSDB_PRIszt |
949 | 0 | "] being compacted, skipping for compaction to " |
950 | 0 | "reduce sorted runs", |
951 | 0 | cf_name_.c_str(), file_num_buf, loop); |
952 | 0 | } else if (sr->level_has_marked_standalone_rangedel) { |
953 | 0 | ROCKS_LOG_BUFFER( |
954 | 0 | log_buffer_, |
955 | 0 | "[%s] Universal: %s" |
956 | 0 | "[%" ROCKSDB_PRIszt |
957 | 0 | "] has standalone range tombstone files marked for " |
958 | 0 | "compaction, skipping for compaction to reduce sorted runs", |
959 | 0 | cf_name_.c_str(), file_num_buf, loop); |
960 | 0 | } |
961 | |
|
962 | 0 | sr = nullptr; |
963 | 0 | } |
964 | | |
965 | | // This file is not being compacted. Consider it as the |
966 | | // first candidate to be compacted. |
967 | 0 | uint64_t candidate_size = sr != nullptr ? sr->compensated_file_size : 0; |
968 | 0 | if (sr != nullptr) { |
969 | 0 | char file_num_buf[kFormatFileNumberBufSize]; |
970 | 0 | sr->Dump(file_num_buf, sizeof(file_num_buf), true); |
971 | 0 | ROCKS_LOG_BUFFER(log_buffer_, |
972 | 0 | "[%s] Universal: Possible candidate for compaction to " |
973 | 0 | "reduce sorted runs %s[%" ROCKSDB_PRIszt "].", |
974 | 0 | cf_name_.c_str(), file_num_buf, loop); |
975 | 0 | } |
976 | | |
977 | | // Check if the succeeding files need compaction. |
978 | 0 | for (size_t i = loop + 1; |
979 | 0 | candidate_count < max_files_to_compact && i < sorted_runs_.size(); |
980 | 0 | i++) { |
981 | 0 | const SortedRun* succeeding_sr = &sorted_runs_[i]; |
982 | 0 | if (succeeding_sr->being_compacted || |
983 | 0 | succeeding_sr->level_has_marked_standalone_rangedel) { |
984 | 0 | break; |
985 | 0 | } |
986 | | // Pick files if the total/last candidate file size (increased by the |
987 | | // specified ratio) is still larger than the next candidate file. |
988 | | // candidate_size is the total size of files picked so far with the |
989 | | // default kCompactionStopStyleTotalSize; with |
990 | | // kCompactionStopStyleSimilarSize, it's simply the size of the last |
991 | | // picked file. |
992 | 0 | double sz = candidate_size * (100.0 + ratio) / 100.0; |
993 | 0 | if (sz < static_cast<double>(succeeding_sr->size)) { |
994 | 0 | break; |
995 | 0 | } |
996 | 0 | if (mutable_cf_options_.compaction_options_universal.stop_style == |
997 | 0 | kCompactionStopStyleSimilarSize) { |
998 | | // Similar-size stopping rule: also check the last picked file isn't |
999 | | // far larger than the next candidate file. |
1000 | 0 | sz = (succeeding_sr->size * (100.0 + ratio)) / 100.0; |
1001 | 0 | if (sz < static_cast<double>(candidate_size)) { |
1002 | | // If the small file we've encountered begins a run of similar-size |
1003 | | // files, we'll pick them up on a future iteration of the outer |
1004 | | // loop. If it's some lonely straggler, it'll eventually get picked |
1005 | | // by the last-resort read amp strategy which disregards size ratios. |
1006 | 0 | break; |
1007 | 0 | } |
1008 | 0 | candidate_size = succeeding_sr->compensated_file_size; |
1009 | 0 | } else { // default kCompactionStopStyleTotalSize |
1010 | 0 | candidate_size += succeeding_sr->compensated_file_size; |
1011 | 0 | } |
1012 | 0 | candidate_count++; |
1013 | 0 | } |
1014 | | |
1015 | | // Found a series of consecutive files that need compaction. |
1016 | 0 | if (candidate_count >= (unsigned int)min_merge_width) { |
1017 | 0 | start_index = loop; |
1018 | 0 | done = true; |
1019 | 0 | break; |
1020 | 0 | } else { |
1021 | 0 | for (size_t i = loop; |
1022 | 0 | i < loop + candidate_count && i < sorted_runs_.size(); i++) { |
1023 | 0 | const SortedRun* skipping_sr = &sorted_runs_[i]; |
1024 | 0 | char file_num_buf[256]; |
1025 | 0 | skipping_sr->DumpSizeInfo(file_num_buf, sizeof(file_num_buf), loop); |
1026 | 0 | ROCKS_LOG_BUFFER(log_buffer_, "[%s] Universal: Skipping %s", |
1027 | 0 | cf_name_.c_str(), file_num_buf); |
1028 | 0 | } |
1029 | 0 | } |
1030 | 0 | } |
1031 | 0 | if (!done || candidate_count <= 1) { |
1032 | 0 | return nullptr; |
1033 | 0 | } |
1034 | 0 | size_t first_index_after = start_index + candidate_count; |
1035 | | // Compression is enabled if files compacted earlier already reached |
1036 | | // size ratio of compression. |
1037 | 0 | bool enable_compression = true; |
1038 | 0 | int ratio_to_compress = |
1039 | 0 | mutable_cf_options_.compaction_options_universal.compression_size_percent; |
1040 | 0 | if (ratio_to_compress >= 0) { |
1041 | 0 | uint64_t total_size = 0; |
1042 | 0 | for (auto& sorted_run : sorted_runs_) { |
1043 | 0 | total_size += sorted_run.compensated_file_size; |
1044 | 0 | } |
1045 | |
|
1046 | 0 | uint64_t older_file_size = 0; |
1047 | 0 | for (size_t i = sorted_runs_.size() - 1; i >= first_index_after; i--) { |
1048 | 0 | older_file_size += sorted_runs_[i].size; |
1049 | 0 | if (older_file_size * 100L >= total_size * (long)ratio_to_compress) { |
1050 | 0 | enable_compression = false; |
1051 | 0 | break; |
1052 | 0 | } |
1053 | 0 | } |
1054 | 0 | } |
1055 | |
|
1056 | 0 | uint64_t estimated_total_size = 0; |
1057 | 0 | for (unsigned int i = 0; i < first_index_after; i++) { |
1058 | 0 | estimated_total_size += sorted_runs_[i].size; |
1059 | 0 | } |
1060 | 0 | uint32_t path_id = |
1061 | 0 | GetPathId(ioptions_, mutable_cf_options_, estimated_total_size); |
1062 | 0 | int start_level = sorted_runs_[start_index].level; |
1063 | 0 | int output_level; |
1064 | | // last level is reserved for the files ingested behind |
1065 | 0 | int max_output_level = vstorage_->MaxOutputLevel(allow_ingest_behind_); |
1066 | 0 | if (first_index_after == sorted_runs_.size()) { |
1067 | 0 | output_level = max_output_level; |
1068 | 0 | } else if (sorted_runs_[first_index_after].level == 0) { |
1069 | 0 | output_level = 0; |
1070 | 0 | } else { |
1071 | 0 | output_level = sorted_runs_[first_index_after].level - 1; |
1072 | 0 | } |
1073 | |
|
1074 | 0 | if (!MeetsOutputLevelRequirements(output_level)) { |
1075 | 0 | return nullptr; |
1076 | 0 | } |
1077 | | |
1078 | 0 | std::vector<CompactionInputFiles> inputs(max_output_level + 1); |
1079 | 0 | for (size_t i = 0; i < inputs.size(); ++i) { |
1080 | 0 | inputs[i].level = start_level + static_cast<int>(i); |
1081 | 0 | } |
1082 | 0 | for (size_t i = start_index; i < first_index_after; i++) { |
1083 | 0 | auto& picking_sr = sorted_runs_[i]; |
1084 | 0 | if (picking_sr.level == 0) { |
1085 | 0 | FileMetaData* picking_file = picking_sr.file; |
1086 | 0 | inputs[0].files.push_back(picking_file); |
1087 | 0 | } else { |
1088 | 0 | auto& files = inputs[picking_sr.level - start_level].files; |
1089 | 0 | for (auto* f : vstorage_->LevelFiles(picking_sr.level)) { |
1090 | 0 | files.push_back(f); |
1091 | 0 | } |
1092 | 0 | } |
1093 | 0 | char file_num_buf[256]; |
1094 | 0 | picking_sr.DumpSizeInfo(file_num_buf, sizeof(file_num_buf), i); |
1095 | 0 | ROCKS_LOG_BUFFER(log_buffer_, "[%s] Universal: Picking %s", |
1096 | 0 | cf_name_.c_str(), file_num_buf); |
1097 | 0 | } |
1098 | |
|
1099 | 0 | std::vector<FileMetaData*> grandparents; |
1100 | | // Include grandparents for potential file cutting in incremental |
1101 | | // mode. It is for aligning file cutting boundaries across levels, |
1102 | | // so that subsequent compactions can pick files with aligned |
1103 | | // buffer. |
1104 | | // Single files are only picked up in incremental mode, so that |
1105 | | // there is no need for full range. |
1106 | 0 | if (mutable_cf_options_.compaction_options_universal.incremental && |
1107 | 0 | first_index_after < sorted_runs_.size() && |
1108 | 0 | sorted_runs_[first_index_after].level > 1) { |
1109 | 0 | grandparents = vstorage_->LevelFiles(sorted_runs_[first_index_after].level); |
1110 | 0 | } |
1111 | |
|
1112 | 0 | if (output_level != 0 && picker_->FilesRangeOverlapWithCompaction( |
1113 | 0 | inputs, output_level, |
1114 | 0 | Compaction::EvaluateProximalLevel( |
1115 | 0 | vstorage_, mutable_cf_options_, ioptions_, |
1116 | 0 | start_level, output_level))) { |
1117 | 0 | return nullptr; |
1118 | 0 | } |
1119 | 0 | CompactionReason compaction_reason; |
1120 | 0 | if (max_number_of_files_to_compact == UINT_MAX) { |
1121 | 0 | compaction_reason = CompactionReason::kUniversalSizeRatio; |
1122 | 0 | } else { |
1123 | 0 | compaction_reason = CompactionReason::kUniversalSortedRunNum; |
1124 | 0 | } |
1125 | 0 | return new Compaction(vstorage_, ioptions_, mutable_cf_options_, |
1126 | 0 | mutable_db_options_, std::move(inputs), output_level, |
1127 | 0 | MaxFileSizeForLevel(mutable_cf_options_, output_level, |
1128 | 0 | kCompactionStyleUniversal), |
1129 | 0 | GetMaxOverlappingBytes(), path_id, |
1130 | 0 | GetCompressionType(vstorage_, mutable_cf_options_, |
1131 | 0 | output_level, 1, enable_compression), |
1132 | 0 | GetCompressionOptions(mutable_cf_options_, vstorage_, |
1133 | 0 | output_level, enable_compression), |
1134 | 0 | Temperature::kUnknown, |
1135 | 0 | /* max_subcompactions */ 0, grandparents, |
1136 | 0 | /* earliest_snapshot */ std::nullopt, |
1137 | 0 | /* snapshot_checker */ nullptr, compaction_reason, |
1138 | 0 | /* trim_ts */ "", score_, |
1139 | 0 | /* l0_files_might_overlap */ true); |
1140 | 0 | } |
1141 | | |
1142 | | // Look at overall size amplification. If size amplification |
1143 | | // exceeds the configured value, then do a compaction |
1144 | | // on longest span of candidate files without conflict with other compactions |
1145 | | // ending at the earliest base file (overriding configured values of file-size |
1146 | | // ratios, min_merge_width and max_merge_width). |
1147 | 0 | Compaction* UniversalCompactionBuilder::PickCompactionToReduceSizeAmp() { |
1148 | 0 | assert(!sorted_runs_.empty()); |
1149 | |
|
1150 | 0 | const size_t end_index = ShouldSkipLastSortedRunForSizeAmpCompaction() |
1151 | 0 | ? sorted_runs_.size() - 2 |
1152 | 0 | : sorted_runs_.size() - 1; |
1153 | 0 | if (sorted_runs_[end_index].being_compacted || |
1154 | 0 | sorted_runs_[end_index].level_has_marked_standalone_rangedel) { |
1155 | 0 | return nullptr; |
1156 | 0 | } |
1157 | 0 | const uint64_t base_sr_size = sorted_runs_[end_index].size; |
1158 | 0 | size_t start_index = end_index; |
1159 | 0 | uint64_t candidate_size = 0; |
1160 | 0 | size_t num_l0_files = 0; |
1161 | | |
1162 | | // Get longest span (i.e, [start_index, end_index]) of available sorted runs |
1163 | 0 | while (start_index > 0) { |
1164 | 0 | const SortedRun* sr = &sorted_runs_[start_index - 1]; |
1165 | 0 | if (sr->being_compacted || sr->level_has_marked_standalone_rangedel) { |
1166 | 0 | char file_num_buf[kFormatFileNumberBufSize]; |
1167 | 0 | sr->Dump(file_num_buf, sizeof(file_num_buf), true); |
1168 | 0 | if (sr->being_compacted) { |
1169 | 0 | ROCKS_LOG_BUFFER(log_buffer_, |
1170 | 0 | "[%s] Universal: stopping for size amp compaction at " |
1171 | 0 | "sorted run undergoing compaction: " |
1172 | 0 | "%s[%" ROCKSDB_PRIszt "]", |
1173 | 0 | cf_name_.c_str(), file_num_buf, start_index - 1); |
1174 | 0 | } else if (sr->level_has_marked_standalone_rangedel) { |
1175 | 0 | ROCKS_LOG_BUFFER(log_buffer_, |
1176 | 0 | "[%s] Universal: stopping for size amp compaction at " |
1177 | 0 | "sorted run that has " |
1178 | 0 | "standalone range " |
1179 | 0 | "tombstone files marked for compaction: " |
1180 | 0 | "%s[%" ROCKSDB_PRIszt "]", |
1181 | 0 | cf_name_.c_str(), file_num_buf, start_index - 1); |
1182 | 0 | } |
1183 | 0 | break; |
1184 | 0 | } |
1185 | 0 | candidate_size += sr->compensated_file_size; |
1186 | 0 | num_l0_files += sr->level == 0 ? 1 : 0; |
1187 | 0 | --start_index; |
1188 | 0 | } |
1189 | |
|
1190 | 0 | if (start_index == end_index) { |
1191 | 0 | return nullptr; |
1192 | 0 | } |
1193 | | |
1194 | 0 | { |
1195 | 0 | const size_t num_l0_to_exclude = MightExcludeNewL0sToReduceWriteStop( |
1196 | 0 | num_l0_files, end_index, start_index, candidate_size); |
1197 | 0 | ROCKS_LOG_BUFFER( |
1198 | 0 | log_buffer_, |
1199 | 0 | "[%s] Universal: Excluding for size amp compaction %" ROCKSDB_PRIszt |
1200 | 0 | " latest L0 files to reduce potential write stop " |
1201 | 0 | "triggered by `level0_stop_writes_trigger`", |
1202 | 0 | cf_name_.c_str(), num_l0_to_exclude); |
1203 | 0 | } |
1204 | |
|
1205 | 0 | { |
1206 | 0 | char file_num_buf[kFormatFileNumberBufSize]; |
1207 | 0 | sorted_runs_[start_index].Dump(file_num_buf, sizeof(file_num_buf), true); |
1208 | 0 | ROCKS_LOG_BUFFER( |
1209 | 0 | log_buffer_, |
1210 | 0 | "[%s] Universal: First candidate %s[%" ROCKSDB_PRIszt "] %s", |
1211 | 0 | cf_name_.c_str(), file_num_buf, start_index, " to reduce size amp.\n"); |
1212 | 0 | } |
1213 | | |
1214 | | // percentage flexibility while reducing size amplification |
1215 | 0 | const uint64_t ratio = mutable_cf_options_.compaction_options_universal |
1216 | 0 | .max_size_amplification_percent; |
1217 | | |
1218 | | // size amplification = percentage of additional size |
1219 | 0 | if (candidate_size * 100 < ratio * base_sr_size) { |
1220 | 0 | ROCKS_LOG_BUFFER(log_buffer_, |
1221 | 0 | "[%s] Universal: size amp compction not needed. " |
1222 | 0 | "newer-files-total-size %" PRIu64 |
1223 | 0 | " earliest-file-size %" PRIu64, |
1224 | 0 | cf_name_.c_str(), candidate_size, base_sr_size); |
1225 | 0 | return nullptr; |
1226 | 0 | } else { |
1227 | 0 | ROCKS_LOG_BUFFER(log_buffer_, |
1228 | 0 | "[%s] Universal: size amp compaction needed. " |
1229 | 0 | "newer-files-total-size %" PRIu64 |
1230 | 0 | " earliest-file-size %" PRIu64, |
1231 | 0 | cf_name_.c_str(), candidate_size, base_sr_size); |
1232 | 0 | } |
1233 | | // Since incremental compaction can't include more than second last |
1234 | | // level, it can introduce penalty, compared to full compaction. We |
1235 | | // hard code the pentalty to be 80%. If we end up with a compaction |
1236 | | // fanout higher than 80% of full level compactions, we fall back |
1237 | | // to full level compaction. |
1238 | | // The 80% threshold is arbitrary and can be adjusted or made |
1239 | | // configurable in the future. |
1240 | | // This also prevent the case when compaction falls behind and we |
1241 | | // need to compact more levels for compactions to catch up. |
1242 | 0 | if (mutable_cf_options_.compaction_options_universal.incremental) { |
1243 | 0 | double fanout_threshold = static_cast<double>(base_sr_size) / |
1244 | 0 | static_cast<double>(candidate_size) * 1.8; |
1245 | 0 | Compaction* picked = PickIncrementalForReduceSizeAmp(fanout_threshold); |
1246 | 0 | if (picked != nullptr) { |
1247 | | // As the feature is still incremental, picking incremental compaction |
1248 | | // might fail and we will fall bck to compacting full level. |
1249 | 0 | return picked; |
1250 | 0 | } |
1251 | 0 | } |
1252 | 0 | return PickCompactionWithSortedRunRange( |
1253 | 0 | start_index, end_index, CompactionReason::kUniversalSizeAmplification); |
1254 | 0 | } |
1255 | | |
1256 | | Compaction* UniversalCompactionBuilder::PickIncrementalForReduceSizeAmp( |
1257 | 0 | double fanout_threshold) { |
1258 | | // Try find all potential compactions with total size just over |
1259 | | // options.max_compaction_size / 2, and take the one with the lowest |
1260 | | // fanout (defined in declaration of the function). |
1261 | | // This is done by having a sliding window of the files at the second |
1262 | | // lowest level, and keep expanding while finding overlapping in the |
1263 | | // last level. Once total size exceeds the size threshold, calculate |
1264 | | // the fanout value. And then shrinking from the small side of the |
1265 | | // window. Keep doing it until the end. |
1266 | | // Finally, we try to include upper level files if they fall into |
1267 | | // the range. |
1268 | | // |
1269 | | // Note that it is a similar problem as leveled compaction's |
1270 | | // kMinOverlappingRatio priority, but instead of picking single files |
1271 | | // we expand to a target compaction size. The reason is that in |
1272 | | // leveled compaction, actual fanout value tends to high, e.g. 10, so |
1273 | | // even with single file in down merging level, the extra size |
1274 | | // compacted in boundary files is at a lower ratio. But here users |
1275 | | // often have size of second last level size to be 1/4, 1/3 or even |
1276 | | // 1/2 of the bottommost level, so picking single file in second most |
1277 | | // level will cause significant waste, which is not desirable. |
1278 | | // |
1279 | | // This algorithm has lots of room to improve to pick more efficient |
1280 | | // compactions. |
1281 | 0 | assert(sorted_runs_.size() >= 2); |
1282 | 0 | int second_last_level = sorted_runs_[sorted_runs_.size() - 2].level; |
1283 | 0 | if (second_last_level == 0) { |
1284 | | // Can't split Level 0. |
1285 | 0 | return nullptr; |
1286 | 0 | } |
1287 | 0 | int output_level = sorted_runs_.back().level; |
1288 | 0 | const std::vector<FileMetaData*>& bottom_files = |
1289 | 0 | vstorage_->LevelFiles(output_level); |
1290 | 0 | const std::vector<FileMetaData*>& files = |
1291 | 0 | vstorage_->LevelFiles(second_last_level); |
1292 | 0 | assert(!bottom_files.empty()); |
1293 | 0 | assert(!files.empty()); |
1294 | | |
1295 | | // std::unordered_map<uint64_t, uint64_t> file_to_order; |
1296 | |
|
1297 | 0 | int picked_start_idx = 0; |
1298 | 0 | int picked_end_idx = 0; |
1299 | 0 | double picked_fanout = fanout_threshold; |
1300 | | |
1301 | | // Use half target compaction bytes as anchor to stop growing second most |
1302 | | // level files, and reserve growing space for more overlapping bottom level, |
1303 | | // clean cut, files from other levels, etc. |
1304 | 0 | uint64_t comp_thres_size = mutable_cf_options_.max_compaction_bytes / 2; |
1305 | 0 | int start_idx = 0; |
1306 | 0 | int bottom_end_idx = 0; |
1307 | 0 | int bottom_start_idx = 0; |
1308 | 0 | uint64_t non_bottom_size = 0; |
1309 | 0 | uint64_t bottom_size = 0; |
1310 | 0 | bool end_bottom_size_counted = false; |
1311 | 0 | for (int end_idx = 0; end_idx < static_cast<int>(files.size()); end_idx++) { |
1312 | 0 | FileMetaData* end_file = files[end_idx]; |
1313 | | |
1314 | | // Include bottom most level files smaller than the current second |
1315 | | // last level file. |
1316 | 0 | int num_skipped = 0; |
1317 | 0 | while (bottom_end_idx < static_cast<int>(bottom_files.size()) && |
1318 | 0 | icmp_->Compare(bottom_files[bottom_end_idx]->largest, |
1319 | 0 | end_file->smallest) < 0) { |
1320 | 0 | if (!end_bottom_size_counted) { |
1321 | 0 | bottom_size += bottom_files[bottom_end_idx]->fd.file_size; |
1322 | 0 | } |
1323 | 0 | bottom_end_idx++; |
1324 | 0 | end_bottom_size_counted = false; |
1325 | 0 | num_skipped++; |
1326 | 0 | } |
1327 | |
|
1328 | 0 | if (num_skipped > 1) { |
1329 | | // At least a file in the bottom most level falls into the file gap. No |
1330 | | // reason to include the file. We cut the range and start a new sliding |
1331 | | // window. |
1332 | 0 | start_idx = end_idx; |
1333 | 0 | } |
1334 | |
|
1335 | 0 | if (start_idx == end_idx) { |
1336 | | // new sliding window. |
1337 | 0 | non_bottom_size = 0; |
1338 | 0 | bottom_size = 0; |
1339 | 0 | bottom_start_idx = bottom_end_idx; |
1340 | 0 | end_bottom_size_counted = false; |
1341 | 0 | } |
1342 | |
|
1343 | 0 | non_bottom_size += end_file->fd.file_size; |
1344 | | |
1345 | | // Include all overlapping files in bottom level. |
1346 | 0 | while (bottom_end_idx < static_cast<int>(bottom_files.size()) && |
1347 | 0 | icmp_->Compare(bottom_files[bottom_end_idx]->smallest, |
1348 | 0 | end_file->largest) < 0) { |
1349 | 0 | if (!end_bottom_size_counted) { |
1350 | 0 | bottom_size += bottom_files[bottom_end_idx]->fd.file_size; |
1351 | 0 | end_bottom_size_counted = true; |
1352 | 0 | } |
1353 | 0 | if (icmp_->Compare(bottom_files[bottom_end_idx]->largest, |
1354 | 0 | end_file->largest) > 0) { |
1355 | | // next level file cross large boundary of current file. |
1356 | 0 | break; |
1357 | 0 | } |
1358 | 0 | bottom_end_idx++; |
1359 | 0 | end_bottom_size_counted = false; |
1360 | 0 | } |
1361 | |
|
1362 | 0 | if ((non_bottom_size + bottom_size > comp_thres_size || |
1363 | 0 | end_idx == static_cast<int>(files.size()) - 1) && |
1364 | 0 | non_bottom_size > 0) { // Do we alow 0 size file at all? |
1365 | | // If it is a better compaction, remember it in picked* variables. |
1366 | 0 | double fanout = static_cast<double>(bottom_size) / |
1367 | 0 | static_cast<double>(non_bottom_size); |
1368 | 0 | if (fanout < picked_fanout) { |
1369 | 0 | picked_start_idx = start_idx; |
1370 | 0 | picked_end_idx = end_idx; |
1371 | 0 | picked_fanout = fanout; |
1372 | 0 | } |
1373 | | // Shrink from the start end to under comp_thres_size |
1374 | 0 | while (non_bottom_size + bottom_size > comp_thres_size && |
1375 | 0 | start_idx <= end_idx) { |
1376 | 0 | non_bottom_size -= files[start_idx]->fd.file_size; |
1377 | 0 | start_idx++; |
1378 | 0 | if (start_idx < static_cast<int>(files.size())) { |
1379 | 0 | while (bottom_start_idx <= bottom_end_idx && |
1380 | 0 | icmp_->Compare(bottom_files[bottom_start_idx]->largest, |
1381 | 0 | files[start_idx]->smallest) < 0) { |
1382 | 0 | bottom_size -= bottom_files[bottom_start_idx]->fd.file_size; |
1383 | 0 | bottom_start_idx++; |
1384 | 0 | } |
1385 | 0 | } |
1386 | 0 | } |
1387 | 0 | } |
1388 | 0 | } |
1389 | |
|
1390 | 0 | if (picked_fanout >= fanout_threshold) { |
1391 | 0 | assert(picked_fanout == fanout_threshold); |
1392 | 0 | return nullptr; |
1393 | 0 | } |
1394 | | |
1395 | 0 | std::vector<CompactionInputFiles> inputs; |
1396 | 0 | CompactionInputFiles bottom_level_inputs; |
1397 | 0 | CompactionInputFiles second_last_level_inputs; |
1398 | 0 | second_last_level_inputs.level = second_last_level; |
1399 | 0 | bottom_level_inputs.level = output_level; |
1400 | 0 | for (int i = picked_start_idx; i <= picked_end_idx; i++) { |
1401 | 0 | if (files[i]->being_compacted) { |
1402 | 0 | return nullptr; |
1403 | 0 | } |
1404 | 0 | second_last_level_inputs.files.push_back(files[i]); |
1405 | 0 | } |
1406 | 0 | assert(!second_last_level_inputs.empty()); |
1407 | 0 | if (!picker_->ExpandInputsToCleanCut(cf_name_, vstorage_, |
1408 | 0 | &second_last_level_inputs, |
1409 | 0 | /*next_smallest=*/nullptr)) { |
1410 | 0 | return nullptr; |
1411 | 0 | } |
1412 | | // We might be able to avoid this binary search if we save and expand |
1413 | | // from bottom_start_idx and bottom_end_idx, but for now, we use |
1414 | | // SetupOtherInputs() for simplicity. |
1415 | 0 | int parent_index = -1; // Create and use bottom_start_idx? |
1416 | 0 | if (!picker_->SetupOtherInputs(cf_name_, mutable_cf_options_, vstorage_, |
1417 | 0 | &second_last_level_inputs, |
1418 | 0 | &bottom_level_inputs, &parent_index, |
1419 | 0 | /*base_index=*/-1)) { |
1420 | 0 | return nullptr; |
1421 | 0 | } |
1422 | | |
1423 | | // Try to include files in upper levels if they fall into the range. |
1424 | | // Since we need to go from lower level up and this is in the reverse |
1425 | | // order, compared to level order, we first write to an reversed |
1426 | | // data structure and finally copy them to compaction inputs. |
1427 | 0 | InternalKey smallest; |
1428 | 0 | InternalKey largest; |
1429 | 0 | picker_->GetRange(second_last_level_inputs, &smallest, &largest); |
1430 | 0 | std::vector<CompactionInputFiles> inputs_reverse; |
1431 | 0 | for (auto it = ++(++sorted_runs_.rbegin()); it != sorted_runs_.rend(); it++) { |
1432 | 0 | SortedRun& sr = *it; |
1433 | 0 | if (sr.level == 0) { |
1434 | 0 | break; |
1435 | 0 | } |
1436 | 0 | std::vector<FileMetaData*> level_inputs; |
1437 | 0 | vstorage_->GetCleanInputsWithinInterval(sr.level, &smallest, &largest, |
1438 | 0 | &level_inputs); |
1439 | 0 | if (!level_inputs.empty()) { |
1440 | 0 | inputs_reverse.push_back({}); |
1441 | 0 | inputs_reverse.back().level = sr.level; |
1442 | 0 | inputs_reverse.back().files = level_inputs; |
1443 | 0 | picker_->GetRange(inputs_reverse.back(), &smallest, &largest); |
1444 | 0 | } |
1445 | 0 | } |
1446 | 0 | for (auto it = inputs_reverse.rbegin(); it != inputs_reverse.rend(); it++) { |
1447 | 0 | inputs.push_back(*it); |
1448 | 0 | } |
1449 | |
|
1450 | 0 | inputs.push_back(second_last_level_inputs); |
1451 | 0 | inputs.push_back(bottom_level_inputs); |
1452 | |
|
1453 | 0 | int start_level = Compaction::kInvalidLevel; |
1454 | 0 | for (const auto& in : inputs) { |
1455 | 0 | if (!in.empty()) { |
1456 | | // inputs should already be sorted by level |
1457 | 0 | start_level = in.level; |
1458 | 0 | break; |
1459 | 0 | } |
1460 | 0 | } |
1461 | | |
1462 | | // intra L0 compactions outputs could have overlap |
1463 | 0 | if (output_level != 0 && picker_->FilesRangeOverlapWithCompaction( |
1464 | 0 | inputs, output_level, |
1465 | 0 | Compaction::EvaluateProximalLevel( |
1466 | 0 | vstorage_, mutable_cf_options_, ioptions_, |
1467 | 0 | start_level, output_level))) { |
1468 | 0 | return nullptr; |
1469 | 0 | } |
1470 | | |
1471 | | // TODO support multi paths? |
1472 | 0 | uint32_t path_id = 0; |
1473 | 0 | return new Compaction( |
1474 | 0 | vstorage_, ioptions_, mutable_cf_options_, mutable_db_options_, |
1475 | 0 | std::move(inputs), output_level, |
1476 | 0 | MaxFileSizeForLevel(mutable_cf_options_, output_level, |
1477 | 0 | kCompactionStyleUniversal), |
1478 | 0 | GetMaxOverlappingBytes(), path_id, |
1479 | 0 | GetCompressionType(vstorage_, mutable_cf_options_, output_level, 1, |
1480 | 0 | true /* enable_compression */), |
1481 | 0 | GetCompressionOptions(mutable_cf_options_, vstorage_, output_level, |
1482 | 0 | true /* enable_compression */), |
1483 | 0 | Temperature::kUnknown, |
1484 | 0 | /* max_subcompactions */ 0, /* grandparents */ {}, |
1485 | 0 | /* earliest_snapshot */ std::nullopt, |
1486 | 0 | /* snapshot_checker */ nullptr, |
1487 | 0 | CompactionReason::kUniversalSizeAmplification, |
1488 | 0 | /* trim_ts */ "", score_, |
1489 | 0 | /* l0_files_might_overlap */ true); |
1490 | 0 | } |
1491 | | |
1492 | | // Pick files marked for compaction. Typically, files are marked by |
1493 | | // CompactOnDeleteCollector due to the presence of tombstones. |
1494 | 0 | Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() { |
1495 | 0 | CompactionInputFiles start_level_inputs; |
1496 | |
|
1497 | 0 | if (vstorage_->num_levels() == 1) { |
1498 | | // This is single level universal. Since we're basically trying to reclaim |
1499 | | // space by processing files marked for compaction due to high tombstone |
1500 | | // density, let's do the same thing as compaction to reduce size amp which |
1501 | | // has the same goals. |
1502 | 0 | int start_index = -1; |
1503 | |
|
1504 | 0 | start_level_inputs.level = 0; |
1505 | 0 | start_level_inputs.files.clear(); |
1506 | | // Find the first file marked for compaction. Ignore the last file |
1507 | 0 | for (size_t loop = 0; loop + 1 < sorted_runs_.size(); loop++) { |
1508 | 0 | SortedRun* sr = &sorted_runs_[loop]; |
1509 | 0 | if (sr->being_compacted) { |
1510 | 0 | continue; |
1511 | 0 | } |
1512 | 0 | FileMetaData* f = vstorage_->LevelFiles(0)[loop]; |
1513 | 0 | if (f->marked_for_compaction && !ShouldSkipMarkedFile(f)) { |
1514 | 0 | start_level_inputs.files.push_back(f); |
1515 | 0 | start_index = |
1516 | 0 | static_cast<int>(loop); // Consider this as the first candidate. |
1517 | 0 | break; |
1518 | 0 | } |
1519 | 0 | } |
1520 | 0 | if (start_index < 0) { |
1521 | | // Either no file marked, or they're already being compacted |
1522 | 0 | return nullptr; |
1523 | 0 | } |
1524 | | |
1525 | 0 | for (size_t loop = start_index + 1; loop < sorted_runs_.size(); loop++) { |
1526 | 0 | SortedRun* sr = &sorted_runs_[loop]; |
1527 | 0 | if (sr->being_compacted || sr->level_has_marked_standalone_rangedel) { |
1528 | 0 | break; |
1529 | 0 | } |
1530 | | |
1531 | 0 | FileMetaData* f = vstorage_->LevelFiles(0)[loop]; |
1532 | 0 | start_level_inputs.files.push_back(f); |
1533 | 0 | } |
1534 | 0 | } else { |
1535 | 0 | int start_level; |
1536 | 0 | int output_level; |
1537 | | |
1538 | | // For multi-level universal, the strategy is to make this look more like |
1539 | | // leveled. We pick one of the files marked for compaction and compact with |
1540 | | // overlapping files in the adjacent level. |
1541 | 0 | picker_->PickFilesMarkedForCompaction(cf_name_, vstorage_, &start_level, |
1542 | 0 | &output_level, &start_level_inputs, |
1543 | 0 | [this](const FileMetaData* file) { |
1544 | 0 | return ShouldSkipMarkedFile(file); |
1545 | 0 | }); |
1546 | 0 | if (start_level_inputs.empty()) { |
1547 | 0 | return nullptr; |
1548 | 0 | } |
1549 | 0 | } |
1550 | | |
1551 | 0 | return BuildCompactionToNextLevel( |
1552 | 0 | start_level_inputs, CompactionReason::kFilesMarkedForCompaction); |
1553 | 0 | } |
1554 | | |
1555 | | Compaction* UniversalCompactionBuilder::PickCompactionToOldest( |
1556 | 0 | size_t start_index, CompactionReason compaction_reason) { |
1557 | 0 | return PickCompactionWithSortedRunRange(start_index, sorted_runs_.size() - 1, |
1558 | 0 | compaction_reason); |
1559 | 0 | } |
1560 | | |
1561 | | Compaction* UniversalCompactionBuilder::PickCompactionWithSortedRunRange( |
1562 | 0 | size_t start_index, size_t end_index, CompactionReason compaction_reason) { |
1563 | 0 | assert(start_index < sorted_runs_.size()); |
1564 | | |
1565 | | // Estimate total file size |
1566 | 0 | uint64_t estimated_total_size = 0; |
1567 | 0 | for (size_t loop = start_index; loop <= end_index; loop++) { |
1568 | 0 | estimated_total_size += sorted_runs_[loop].size; |
1569 | 0 | } |
1570 | 0 | uint32_t path_id = |
1571 | 0 | GetPathId(ioptions_, mutable_cf_options_, estimated_total_size); |
1572 | 0 | int start_level = sorted_runs_[start_index].level; |
1573 | 0 | int max_output_level = vstorage_->MaxOutputLevel(allow_ingest_behind_); |
1574 | 0 | std::vector<CompactionInputFiles> inputs(max_output_level + 1); |
1575 | 0 | for (size_t i = 0; i < inputs.size(); ++i) { |
1576 | 0 | inputs[i].level = start_level + static_cast<int>(i); |
1577 | 0 | } |
1578 | 0 | for (size_t loop = start_index; loop <= end_index; loop++) { |
1579 | 0 | auto& picking_sr = sorted_runs_[loop]; |
1580 | 0 | if (picking_sr.level == 0) { |
1581 | 0 | FileMetaData* f = picking_sr.file; |
1582 | 0 | inputs[0].files.push_back(f); |
1583 | 0 | } else { |
1584 | 0 | auto& files = inputs[picking_sr.level - start_level].files; |
1585 | 0 | for (auto* f : vstorage_->LevelFiles(picking_sr.level)) { |
1586 | 0 | files.push_back(f); |
1587 | 0 | } |
1588 | 0 | } |
1589 | 0 | std::string comp_reason_print_string; |
1590 | 0 | if (compaction_reason == CompactionReason::kPeriodicCompaction) { |
1591 | 0 | comp_reason_print_string = "periodic compaction"; |
1592 | 0 | } else if (compaction_reason == |
1593 | 0 | CompactionReason::kUniversalSizeAmplification) { |
1594 | 0 | comp_reason_print_string = "size amp"; |
1595 | 0 | } else { |
1596 | 0 | assert(false); |
1597 | 0 | comp_reason_print_string = "unknown: "; |
1598 | 0 | comp_reason_print_string.append( |
1599 | 0 | std::to_string(static_cast<int>(compaction_reason))); |
1600 | 0 | } |
1601 | |
|
1602 | 0 | char file_num_buf[256]; |
1603 | 0 | picking_sr.DumpSizeInfo(file_num_buf, sizeof(file_num_buf), loop); |
1604 | 0 | ROCKS_LOG_BUFFER(log_buffer_, "[%s] Universal: %s picking %s", |
1605 | 0 | cf_name_.c_str(), comp_reason_print_string.c_str(), |
1606 | 0 | file_num_buf); |
1607 | 0 | } |
1608 | |
|
1609 | 0 | int output_level; |
1610 | 0 | if (end_index == sorted_runs_.size() - 1) { |
1611 | 0 | output_level = max_output_level; |
1612 | 0 | } else { |
1613 | | // if it's not including all sorted_runs, it can only output to the level |
1614 | | // above the `end_index + 1` sorted_run. |
1615 | 0 | output_level = sorted_runs_[end_index + 1].level - 1; |
1616 | 0 | } |
1617 | |
|
1618 | 0 | if (!MeetsOutputLevelRequirements(output_level)) { |
1619 | 0 | return nullptr; |
1620 | 0 | } |
1621 | | |
1622 | | // intra L0 compactions outputs could have overlap |
1623 | 0 | if (output_level != 0 && picker_->FilesRangeOverlapWithCompaction( |
1624 | 0 | inputs, output_level, |
1625 | 0 | Compaction::EvaluateProximalLevel( |
1626 | 0 | vstorage_, mutable_cf_options_, ioptions_, |
1627 | 0 | start_level, output_level))) { |
1628 | 0 | return nullptr; |
1629 | 0 | } |
1630 | | |
1631 | | // We never check size for |
1632 | | // compaction_options_universal.compression_size_percent, |
1633 | | // because we always compact all the files, so always compress. |
1634 | 0 | return new Compaction( |
1635 | 0 | vstorage_, ioptions_, mutable_cf_options_, mutable_db_options_, |
1636 | 0 | std::move(inputs), output_level, |
1637 | 0 | MaxFileSizeForLevel(mutable_cf_options_, output_level, |
1638 | 0 | kCompactionStyleUniversal), |
1639 | 0 | GetMaxOverlappingBytes(), path_id, |
1640 | 0 | GetCompressionType(vstorage_, mutable_cf_options_, output_level, 1, |
1641 | 0 | true /* enable_compression */), |
1642 | 0 | GetCompressionOptions(mutable_cf_options_, vstorage_, output_level, |
1643 | 0 | true /* enable_compression */), |
1644 | 0 | Temperature::kUnknown, |
1645 | 0 | /* max_subcompactions */ 0, /* grandparents */ {}, |
1646 | 0 | /* earliest_snapshot */ std::nullopt, |
1647 | 0 | /* snapshot_checker */ nullptr, compaction_reason, |
1648 | 0 | /* trim_ts */ "", score_, |
1649 | 0 | /* l0_files_might_overlap */ true); |
1650 | 0 | } |
1651 | | |
1652 | 0 | Compaction* UniversalCompactionBuilder::PickPeriodicCompaction() { |
1653 | 0 | ROCKS_LOG_BUFFER(log_buffer_, "[%s] Universal: Periodic Compaction", |
1654 | 0 | cf_name_.c_str()); |
1655 | | |
1656 | | // In universal compaction, sorted runs contain older data are almost always |
1657 | | // generated earlier too. To simplify the problem, we just try to trigger |
1658 | | // a full compaction. We start from the oldest sorted run and include |
1659 | | // all sorted runs, until we hit a sorted already being compacted. |
1660 | | // Since usually the largest (which is usually the oldest) sorted run is |
1661 | | // included anyway, doing a full compaction won't increase write |
1662 | | // amplification much. |
1663 | | |
1664 | | // Get some information from marked files to check whether a file is |
1665 | | // included in the compaction. |
1666 | |
|
1667 | 0 | size_t start_index = sorted_runs_.size(); |
1668 | 0 | while (start_index > 0 && !sorted_runs_[start_index - 1].being_compacted && |
1669 | 0 | !sorted_runs_[start_index - 1].level_has_marked_standalone_rangedel) { |
1670 | 0 | start_index--; |
1671 | 0 | } |
1672 | 0 | if (start_index == sorted_runs_.size()) { |
1673 | 0 | return nullptr; |
1674 | 0 | } |
1675 | | |
1676 | | // There is a rare corner case where we can't pick up all the files |
1677 | | // because some files are being compacted and we end up with picking files |
1678 | | // but none of them need periodic compaction. Unless we simply recompact |
1679 | | // the last sorted run (either the last level or last L0 file), we would just |
1680 | | // execute the compaction, in order to simplify the logic. |
1681 | 0 | if (start_index == sorted_runs_.size() - 1) { |
1682 | 0 | bool included_file_marked = false; |
1683 | 0 | int start_level = sorted_runs_[start_index].level; |
1684 | 0 | FileMetaData* start_file = sorted_runs_[start_index].file; |
1685 | 0 | for (const std::pair<int, FileMetaData*>& level_file_pair : |
1686 | 0 | vstorage_->FilesMarkedForPeriodicCompaction()) { |
1687 | 0 | if (start_level != 0) { |
1688 | | // Last sorted run is a level |
1689 | 0 | if (start_level == level_file_pair.first) { |
1690 | 0 | included_file_marked = true; |
1691 | 0 | break; |
1692 | 0 | } |
1693 | 0 | } else { |
1694 | | // Last sorted run is a L0 file. |
1695 | 0 | if (start_file == level_file_pair.second) { |
1696 | 0 | included_file_marked = true; |
1697 | 0 | break; |
1698 | 0 | } |
1699 | 0 | } |
1700 | 0 | } |
1701 | 0 | if (!included_file_marked) { |
1702 | 0 | ROCKS_LOG_BUFFER(log_buffer_, |
1703 | 0 | "[%s] Universal: Cannot form a compaction covering file " |
1704 | 0 | "marked for periodic compaction", |
1705 | 0 | cf_name_.c_str()); |
1706 | 0 | return nullptr; |
1707 | 0 | } |
1708 | 0 | } |
1709 | | |
1710 | 0 | Compaction* c = PickCompactionToOldest(start_index, |
1711 | 0 | CompactionReason::kPeriodicCompaction); |
1712 | |
|
1713 | 0 | TEST_SYNC_POINT_CALLBACK( |
1714 | 0 | "UniversalCompactionPicker::PickPeriodicCompaction:Return", c); |
1715 | |
|
1716 | 0 | return c; |
1717 | 0 | } |
1718 | | |
1719 | 0 | Compaction* UniversalCompactionBuilder::PickReadTriggeredCompaction() { |
1720 | 0 | ROCKS_LOG_BUFFER(log_buffer_, "[%s] Universal: Read Triggered Compaction", |
1721 | 0 | cf_name_.c_str()); |
1722 | |
|
1723 | 0 | if (vstorage_->ReadTriggeredCompactionFiles().empty()) { |
1724 | 0 | return nullptr; |
1725 | 0 | } |
1726 | | |
1727 | 0 | CompactionInputFiles start_level_inputs; |
1728 | 0 | int start_level = -1; |
1729 | |
|
1730 | 0 | for (const auto& level_file : vstorage_->ReadTriggeredCompactionFiles()) { |
1731 | 0 | assert(!level_file.second->being_compacted); |
1732 | 0 | start_level = level_file.first; |
1733 | |
|
1734 | 0 | if (start_level == 0 && |
1735 | 0 | !picker_->level0_compactions_in_progress()->empty()) { |
1736 | 0 | continue; |
1737 | 0 | } |
1738 | | |
1739 | 0 | start_level_inputs.files = {level_file.second}; |
1740 | 0 | start_level_inputs.level = start_level; |
1741 | 0 | if (picker_->ExpandInputsToCleanCut(cf_name_, vstorage_, |
1742 | 0 | &start_level_inputs)) { |
1743 | 0 | break; |
1744 | 0 | } |
1745 | 0 | start_level_inputs.files.clear(); |
1746 | 0 | } |
1747 | |
|
1748 | 0 | if (start_level_inputs.empty()) { |
1749 | 0 | return nullptr; |
1750 | 0 | } |
1751 | | |
1752 | | // For intra L0 compactions, pick up all overlapping files |
1753 | 0 | if (vstorage_->num_levels() == 1 && start_level_inputs.level == 0) { |
1754 | 0 | if (!picker_->GetOverlappingL0Files(vstorage_, &start_level_inputs, |
1755 | 0 | /*output_level=*/0, nullptr, nullptr)) { |
1756 | 0 | return nullptr; |
1757 | 0 | } |
1758 | 0 | } |
1759 | | |
1760 | 0 | return BuildCompactionToNextLevel(start_level_inputs, |
1761 | 0 | CompactionReason::kReadTriggered); |
1762 | 0 | } |
1763 | | |
1764 | | Compaction* UniversalCompactionBuilder::BuildCompactionToNextLevel( |
1765 | 0 | CompactionInputFiles& start_level_inputs, CompactionReason reason) { |
1766 | 0 | int start_level = start_level_inputs.level; |
1767 | 0 | int max_output_level = vstorage_->MaxOutputLevel(allow_ingest_behind_); |
1768 | | |
1769 | | // Pick the first non-empty level after start_level as output. |
1770 | 0 | int output_level; |
1771 | 0 | for (output_level = start_level + 1; output_level <= max_output_level; |
1772 | 0 | output_level++) { |
1773 | 0 | if (vstorage_->NumLevelFiles(output_level) != 0) { |
1774 | 0 | break; |
1775 | 0 | } |
1776 | 0 | } |
1777 | | |
1778 | | // If all higher levels are empty, pick the highest level as output level |
1779 | | // for L0 start. For non-L0 start, a trivial move doesn't help reclaim |
1780 | | // space, so skip. |
1781 | 0 | if (output_level > max_output_level) { |
1782 | 0 | if (start_level == 0) { |
1783 | 0 | output_level = max_output_level; |
1784 | 0 | } else { |
1785 | 0 | return nullptr; |
1786 | 0 | } |
1787 | 0 | } |
1788 | | |
1789 | 0 | if (start_level_inputs.size() <= 1 && output_level == 0) { |
1790 | | // If only the last file in L0 is marked for compaction, ignore it |
1791 | 0 | return nullptr; |
1792 | 0 | } |
1793 | | |
1794 | 0 | std::vector<CompactionInputFiles> inputs; |
1795 | 0 | std::vector<FileMetaData*> grandparents; |
1796 | |
|
1797 | 0 | if (output_level != 0) { |
1798 | 0 | if (!MeetsOutputLevelRequirements(output_level)) { |
1799 | 0 | return nullptr; |
1800 | 0 | } |
1801 | | |
1802 | | // For standalone range deletion, we don't want to compact it with newer |
1803 | | // L0 files that it doesn't cover. |
1804 | 0 | const FileMetaData* starting_l0_file = |
1805 | 0 | (start_level == 0 && start_level_inputs.size() == 1 && |
1806 | 0 | start_level_inputs.files[0]->FileIsStandAloneRangeTombstone()) |
1807 | 0 | ? start_level_inputs.files[0] |
1808 | 0 | : nullptr; |
1809 | |
|
1810 | 0 | if (start_level == 0) { |
1811 | 0 | if (!picker_->GetOverlappingL0Files(vstorage_, &start_level_inputs, |
1812 | 0 | output_level, nullptr, |
1813 | 0 | starting_l0_file)) { |
1814 | 0 | return nullptr; |
1815 | 0 | } |
1816 | 0 | } |
1817 | | |
1818 | 0 | CompactionInputFiles output_level_inputs; |
1819 | 0 | int parent_index = -1; |
1820 | |
|
1821 | 0 | output_level_inputs.level = output_level; |
1822 | 0 | if (!picker_->SetupOtherInputs( |
1823 | 0 | cf_name_, mutable_cf_options_, vstorage_, &start_level_inputs, |
1824 | 0 | &output_level_inputs, &parent_index, -1, false, starting_l0_file)) { |
1825 | 0 | return nullptr; |
1826 | 0 | } |
1827 | 0 | inputs.push_back(start_level_inputs); |
1828 | 0 | if (!output_level_inputs.empty()) { |
1829 | 0 | inputs.push_back(output_level_inputs); |
1830 | 0 | } |
1831 | 0 | if (picker_->FilesRangeOverlapWithCompaction( |
1832 | 0 | inputs, output_level, |
1833 | 0 | Compaction::EvaluateProximalLevel(vstorage_, mutable_cf_options_, |
1834 | 0 | ioptions_, start_level, |
1835 | 0 | output_level))) { |
1836 | 0 | return nullptr; |
1837 | 0 | } |
1838 | | |
1839 | 0 | picker_->GetGrandparents(vstorage_, start_level_inputs, output_level_inputs, |
1840 | 0 | &grandparents); |
1841 | 0 | } else { |
1842 | 0 | inputs.push_back(start_level_inputs); |
1843 | 0 | } |
1844 | | |
1845 | 0 | uint64_t estimated_total_size = 0; |
1846 | 0 | for (FileMetaData* f : vstorage_->LevelFiles(output_level)) { |
1847 | 0 | estimated_total_size += f->fd.GetFileSize(); |
1848 | 0 | } |
1849 | 0 | uint32_t path_id = |
1850 | 0 | GetPathId(ioptions_, mutable_cf_options_, estimated_total_size); |
1851 | 0 | return new Compaction( |
1852 | 0 | vstorage_, ioptions_, mutable_cf_options_, mutable_db_options_, |
1853 | 0 | std::move(inputs), output_level, |
1854 | 0 | MaxFileSizeForLevel(mutable_cf_options_, output_level, |
1855 | 0 | kCompactionStyleUniversal), |
1856 | 0 | /* max_grandparent_overlap_bytes */ GetMaxOverlappingBytes(), path_id, |
1857 | 0 | GetCompressionType(vstorage_, mutable_cf_options_, output_level, 1), |
1858 | 0 | GetCompressionOptions(mutable_cf_options_, vstorage_, output_level), |
1859 | 0 | Temperature::kUnknown, |
1860 | 0 | /* max_subcompactions */ 0, grandparents, earliest_snapshot_, |
1861 | 0 | snapshot_checker_, reason, |
1862 | 0 | /* trim_ts */ "", score_, |
1863 | 0 | /* l0_files_might_overlap */ true); |
1864 | 0 | } |
1865 | | |
1866 | 0 | uint64_t UniversalCompactionBuilder::GetMaxOverlappingBytes() const { |
1867 | 0 | if (!mutable_cf_options_.compaction_options_universal.incremental) { |
1868 | 0 | return std::numeric_limits<uint64_t>::max(); |
1869 | 0 | } else { |
1870 | | // Try to align cutting boundary with files at the next level if the |
1871 | | // file isn't end up with 1/2 of target size, or it would overlap |
1872 | | // with two full size files at the next level. |
1873 | 0 | return mutable_cf_options_.target_file_size_base / 2 * 3; |
1874 | 0 | } |
1875 | 0 | } |
1876 | | } // namespace ROCKSDB_NAMESPACE |