Coverage Report

Created: 2026-11-12 08:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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