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_job.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_job.h"
11
12
#include <algorithm>
13
#include <cinttypes>
14
#include <condition_variable>
15
#include <exception>
16
#include <memory>
17
#include <mutex>
18
#include <optional>
19
#include <set>
20
#include <utility>
21
#include <vector>
22
23
#include "db/blob/blob_counting_iterator.h"
24
#include "db/blob/blob_file_addition.h"
25
#include "db/blob/blob_file_builder.h"
26
#include "db/blob/missing_blob_repair_iterator.h"
27
#include "db/builder.h"
28
#include "db/compaction/clipping_iterator.h"
29
#include "db/compaction/compaction_state.h"
30
#include "db/db_impl/db_impl.h"
31
#include "db/dbformat.h"
32
#include "db/error_handler.h"
33
#include "db/event_helpers.h"
34
#include "db/history_trimming_iterator.h"
35
#include "db/log_writer.h"
36
#include "db/merge_helper.h"
37
#include "db/range_del_aggregator.h"
38
#include "db/version_edit.h"
39
#include "db/version_set.h"
40
#include "file/file_util.h"
41
#include "file/filename.h"
42
#include "file/read_write_util.h"
43
#include "file/sst_file_manager_impl.h"
44
#include "file/writable_file_writer.h"
45
#include "logging/log_buffer.h"
46
#include "logging/logging.h"
47
#include "monitoring/iostats_context_imp.h"
48
#include "monitoring/thread_status_util.h"
49
#include "options/configurable_helper.h"
50
#include "options/options_helper.h"
51
#include "port/port.h"
52
#include "rocksdb/db.h"
53
#include "rocksdb/env.h"
54
#include "rocksdb/options.h"
55
#include "rocksdb/statistics.h"
56
#include "rocksdb/status.h"
57
#include "rocksdb/table.h"
58
#include "rocksdb/utilities/options_type.h"
59
#include "table/format.h"
60
#include "table/merging_iterator.h"
61
#include "table/meta_blocks.h"
62
#include "table/table_builder.h"
63
#include "table/unique_id_impl.h"
64
#include "test_util/sync_point.h"
65
#include "util/defer.h"
66
#include "util/hash_containers.h"
67
#include "util/stop_watch.h"
68
69
namespace ROCKSDB_NAMESPACE {
70
71
namespace {
72
73
class SubcompactionCompletion {
74
 public:
75
0
  SubcompactionCompletion() = default;
76
  SubcompactionCompletion(const SubcompactionCompletion&) = delete;
77
  SubcompactionCompletion& operator=(const SubcompactionCompletion&) = delete;
78
  SubcompactionCompletion(SubcompactionCompletion&&) = delete;
79
  SubcompactionCompletion& operator=(SubcompactionCompletion&&) = delete;
80
81
  class Guard {
82
   public:
83
    explicit Guard(SubcompactionCompletion& completion)
84
0
        : completion_(&completion) {}
85
86
0
    ~Guard() {
87
0
      if (completion_ != nullptr) {
88
0
        completion_->Complete();
89
0
      }
90
0
    }
91
92
    Guard(const Guard&) = delete;
93
    Guard& operator=(const Guard&) = delete;
94
    Guard(Guard&&) = delete;
95
    Guard& operator=(Guard&&) = delete;
96
97
0
    void Release() { completion_ = nullptr; }
98
99
   private:
100
    SubcompactionCompletion* completion_;
101
  };
102
103
0
  void Add() {
104
0
    std::lock_guard<std::mutex> lock(mutex_);
105
0
    ++remaining_;
106
0
  }
107
108
0
  ~SubcompactionCompletion() {
109
0
    std::unique_lock<std::mutex> lock(mutex_);
110
0
    TEST_SYNC_POINT("CompactionJob::RunSubcompactions:Wait");
111
0
    cv_.wait(lock, [&] { return remaining_ == 0; });
112
0
  }
113
114
 private:
115
0
  void Complete() {
116
0
    std::lock_guard<std::mutex> lock(mutex_);
117
0
    assert(remaining_ > 0);
118
0
    --remaining_;
119
0
    cv_.notify_one();
120
0
  }
121
122
  std::mutex mutex_;
123
  std::condition_variable cv_;
124
  size_t remaining_ = 0;
125
};
126
127
}  // namespace
128
129
2.77k
const char* GetCompactionReasonString(CompactionReason compaction_reason) {
130
2.77k
  switch (compaction_reason) {
131
0
    case CompactionReason::kUnknown:
132
0
      return "Unknown";
133
1.15k
    case CompactionReason::kLevelL0FilesNum:
134
1.15k
      return "LevelL0FilesNum";
135
0
    case CompactionReason::kLevelMaxLevelSize:
136
0
      return "LevelMaxLevelSize";
137
0
    case CompactionReason::kUniversalSizeAmplification:
138
0
      return "UniversalSizeAmplification";
139
0
    case CompactionReason::kUniversalSizeRatio:
140
0
      return "UniversalSizeRatio";
141
0
    case CompactionReason::kUniversalSortedRunNum:
142
0
      return "UniversalSortedRunNum";
143
0
    case CompactionReason::kFIFOMaxSize:
144
0
      return "FIFOMaxSize";
145
0
    case CompactionReason::kFIFOReduceNumFiles:
146
0
      return "FIFOReduceNumFiles";
147
0
    case CompactionReason::kFIFOTtl:
148
0
      return "FIFOTtl";
149
840
    case CompactionReason::kManualCompaction:
150
840
      return "ManualCompaction";
151
0
    case CompactionReason::kFilesMarkedForCompaction:
152
0
      return "FilesMarkedForCompaction";
153
776
    case CompactionReason::kBottommostFiles:
154
776
      return "BottommostFiles";
155
0
    case CompactionReason::kTtl:
156
0
      return "Ttl";
157
0
    case CompactionReason::kFlush:
158
0
      return "Flush";
159
0
    case CompactionReason::kExternalSstIngestion:
160
0
      return "ExternalSstIngestion";
161
0
    case CompactionReason::kPeriodicCompaction:
162
0
      return "PeriodicCompaction";
163
0
    case CompactionReason::kChangeTemperature:
164
0
      return "ChangeTemperature";
165
0
    case CompactionReason::kForcedBlobGC:
166
0
      return "ForcedBlobGC";
167
0
    case CompactionReason::kRoundRobinTtl:
168
0
      return "RoundRobinTtl";
169
0
    case CompactionReason::kRefitLevel:
170
0
      return "RefitLevel";
171
0
    case CompactionReason::kReadTriggered:
172
0
      return "ReadTriggered";
173
0
    case CompactionReason::kNumOfReasons:
174
      // fall through
175
0
    default:
176
0
      assert(false);
177
0
      return "Invalid";
178
2.77k
  }
179
2.77k
}
180
181
const char* GetCompactionProximalOutputRangeTypeString(
182
0
    Compaction::ProximalOutputRangeType range_type) {
183
0
  switch (range_type) {
184
0
    case Compaction::ProximalOutputRangeType::kNotSupported:
185
0
      return "NotSupported";
186
0
    case Compaction::ProximalOutputRangeType::kFullRange:
187
0
      return "FullRange";
188
0
    case Compaction::ProximalOutputRangeType::kNonLastRange:
189
0
      return "NonLastRange";
190
0
    case Compaction::ProximalOutputRangeType::kDisabled:
191
0
      return "Disabled";
192
0
    default:
193
0
      assert(false);
194
0
      return "Invalid";
195
0
  }
196
0
}
197
198
// Static constant for compaction abort flag - always false, used for
199
// compaction service jobs that don't support abort signaling
200
const std::atomic<int> CompactionJob::kCompactionAbortedFalse{0};
201
202
CompactionJob::CompactionJob(
203
    int job_id, Compaction* compaction, const ImmutableDBOptions& db_options,
204
    const MutableDBOptions& mutable_db_options, const FileOptions& file_options,
205
    VersionSet* versions, const std::atomic<bool>* shutting_down,
206
    LogBuffer* log_buffer, FSDirectory* db_directory,
207
    FSDirectory* output_directory, FSDirectory* blob_output_directory,
208
    Statistics* stats, InstrumentedMutex* db_mutex,
209
    ErrorHandler* db_error_handler, JobContext* job_context,
210
    std::shared_ptr<Cache> table_cache, EventLogger* event_logger,
211
    bool paranoid_file_checks, bool measure_io_stats, const std::string& dbname,
212
    CompactionJobStats* compaction_job_stats, Env::Priority thread_pri,
213
    const std::shared_ptr<IOTracer>& io_tracer,
214
    const std::atomic<bool>& manual_compaction_canceled,
215
    const std::atomic<int>& compaction_aborted, const std::string& db_id,
216
    const std::string& db_session_id, std::string full_history_ts_low,
217
    std::string trim_ts, BlobFileCompletionCallback* blob_callback,
218
    int* bg_compaction_scheduled, int* bg_bottom_compaction_scheduled,
219
    std::atomic<int>* num_running_remote_compactions)
220
2.77k
    : compact_(new CompactionState(compaction)),
221
2.77k
      internal_stats_(compaction->compaction_reason(), 1),
222
2.77k
      db_options_(db_options),
223
2.77k
      mutable_db_options_copy_(mutable_db_options),
224
2.77k
      log_buffer_(log_buffer),
225
2.77k
      output_directory_(output_directory),
226
2.77k
      stats_(stats),
227
2.77k
      bottommost_level_(false),
228
2.77k
      write_hint_(Env::WLTH_NOT_SET),
229
2.77k
      job_stats_(compaction_job_stats),
230
2.77k
      job_id_(job_id),
231
2.77k
      dbname_(dbname),
232
2.77k
      db_id_(db_id),
233
2.77k
      db_session_id_(db_session_id),
234
2.77k
      file_options_(file_options),
235
2.77k
      env_(db_options.env),
236
2.77k
      io_tracer_(io_tracer),
237
2.77k
      fs_(db_options.fs, io_tracer),
238
      file_options_for_read_(
239
2.77k
          fs_->OptimizeForCompactionTableRead(file_options, db_options_)),
240
2.77k
      file_options_for_compaction_input_read_(file_options_for_read_),
241
2.77k
      versions_(versions),
242
2.77k
      shutting_down_(shutting_down),
243
2.77k
      manual_compaction_canceled_(manual_compaction_canceled),
244
2.77k
      compaction_aborted_(compaction_aborted),
245
2.77k
      db_directory_(db_directory),
246
2.77k
      blob_output_directory_(blob_output_directory),
247
2.77k
      db_mutex_(db_mutex),
248
2.77k
      db_error_handler_(db_error_handler),
249
      // job_context cannot be nullptr, but we will assert later in the body of
250
      // the constructor.
251
2.77k
      earliest_snapshot_(job_context
252
2.77k
                             ? job_context->GetEarliestSnapshotSequence()
253
2.77k
                             : kMaxSequenceNumber),
254
2.77k
      job_context_(job_context),
255
2.77k
      table_cache_(std::move(table_cache)),
256
2.77k
      event_logger_(event_logger),
257
2.77k
      paranoid_file_checks_(paranoid_file_checks),
258
2.77k
      measure_io_stats_(measure_io_stats),
259
2.77k
      thread_pri_(thread_pri),
260
2.77k
      full_history_ts_low_(std::move(full_history_ts_low)),
261
2.77k
      trim_ts_(std::move(trim_ts)),
262
2.77k
      blob_callback_(blob_callback),
263
2.77k
      extra_num_subcompaction_threads_reserved_(0),
264
2.77k
      bg_compaction_scheduled_(bg_compaction_scheduled),
265
2.77k
      bg_bottom_compaction_scheduled_(bg_bottom_compaction_scheduled),
266
2.77k
      num_running_remote_compactions_(num_running_remote_compactions) {
267
2.77k
  assert(job_stats_ != nullptr);
268
2.77k
  assert(log_buffer_ != nullptr);
269
2.77k
  assert(job_context);
270
2.77k
  assert(job_context->snapshot_context_initialized);
271
272
2.77k
  if (db_options_.use_direct_io_for_compaction_reads &&
273
0
      !db_options_.use_direct_reads) {
274
0
    file_options_for_compaction_input_read_.use_direct_reads = true;
275
0
  }
276
277
2.77k
  ReportStartedCompaction(compaction);
278
2.77k
  InitializeThreadStatus();
279
2.77k
}
280
281
2.77k
CompactionJob::~CompactionJob() {
282
2.77k
  assert(compact_ == nullptr);
283
2.77k
  ThreadStatusUtil::ResetThreadStatus();
284
2.77k
}
285
286
2.77k
void CompactionJob::InitializeThreadStatus() {
287
2.77k
  auto* compaction = compact_->compaction;
288
2.77k
  ThreadStatusUtil::SetEnableTracking(db_options_.enable_thread_tracking);
289
2.77k
  ThreadStatusUtil::SetColumnFamily(compaction->column_family_data());
290
2.77k
  ThreadStatusUtil::SetThreadOperationProperty(ThreadStatus::COMPACTION_JOB_ID,
291
2.77k
                                               job_id_);
292
293
2.77k
  ThreadStatusUtil::SetThreadOperationProperty(
294
2.77k
      ThreadStatus::COMPACTION_INPUT_OUTPUT_LEVEL,
295
2.77k
      (static_cast<uint64_t>(compact_->compaction->start_level()) << 32) +
296
2.77k
          compact_->compaction->output_level());
297
298
  // In the current design, a CompactionJob is always created
299
  // for non-trivial compaction.
300
2.77k
  assert(compaction->IsTrivialMove() == false ||
301
2.77k
         compaction->is_manual_compaction() == true);
302
303
2.77k
  ThreadStatusUtil::SetThreadOperationProperty(
304
2.77k
      ThreadStatus::COMPACTION_PROP_FLAGS,
305
2.77k
      compaction->is_manual_compaction() +
306
2.77k
          (compaction->deletion_compaction() << 1));
307
2.77k
  ThreadStatusUtil::SetThreadOperationProperty(
308
2.77k
      ThreadStatus::COMPACTION_TOTAL_INPUT_BYTES,
309
2.77k
      compaction->CalculateTotalInputSize());
310
311
2.77k
  IOSTATS_RESET(bytes_written);
312
2.77k
  IOSTATS_RESET(bytes_read);
313
2.77k
  ThreadStatusUtil::SetThreadOperationProperty(
314
2.77k
      ThreadStatus::COMPACTION_BYTES_WRITTEN, 0);
315
2.77k
  ThreadStatusUtil::SetThreadOperationProperty(
316
2.77k
      ThreadStatus::COMPACTION_BYTES_READ, 0);
317
318
  // Set the thread operation after operation properties
319
  // to ensure GetThreadList() can always show them all together.
320
2.77k
  ThreadStatusUtil::SetThreadOperation(ThreadStatus::OP_COMPACTION);
321
2.77k
}
322
323
2.77k
void CompactionJob::ReportStartedCompaction(Compaction* compaction) {
324
2.77k
  job_stats_->is_manual_compaction = compaction->is_manual_compaction();
325
2.77k
  job_stats_->is_full_compaction = compaction->is_full_compaction();
326
  // populate compaction stats num_input_files and total_num_of_bytes
327
2.77k
  size_t num_input_files = 0;
328
2.77k
  for (int input_level = 0;
329
7.19k
       input_level < static_cast<int>(compaction->num_input_levels());
330
4.41k
       ++input_level) {
331
4.41k
    const LevelFilesBrief* flevel = compaction->input_levels(input_level);
332
4.41k
    num_input_files += flevel->num_files;
333
4.41k
  }
334
2.77k
  job_stats_->CompactionJobStats::num_input_files = num_input_files;
335
2.77k
  job_stats_->total_input_bytes = compaction->CalculateTotalInputSize();
336
2.77k
}
337
338
void CompactionJob::Prepare(
339
    std::optional<std::pair<std::optional<Slice>, std::optional<Slice>>>
340
        known_single_subcompact,
341
    const CompactionProgress& compaction_progress,
342
2.77k
    log::Writer* compaction_progress_writer) {
343
2.77k
  db_mutex_->AssertHeld();
344
2.77k
  AutoThreadOperationStageUpdater stage_updater(
345
2.77k
      ThreadStatus::STAGE_COMPACTION_PREPARE);
346
347
  // Generate file_levels_ for compaction before making Iterator
348
2.77k
  auto* c = compact_->compaction;
349
2.77k
  [[maybe_unused]] ColumnFamilyData* cfd = c->column_family_data();
350
2.77k
  assert(cfd != nullptr);
351
2.77k
  const VersionStorageInfo* storage_info = c->input_version()->storage_info();
352
2.77k
  assert(storage_info);
353
2.77k
  assert(storage_info->NumLevelFiles(compact_->compaction->level()) > 0);
354
355
2.77k
  write_hint_ = storage_info->CalculateSSTWriteHint(
356
2.77k
      c->output_level(), db_options_.calculate_sst_write_lifetime_hint_set);
357
2.77k
  bottommost_level_ = c->bottommost_level();
358
359
2.77k
  if (!known_single_subcompact.has_value() && c->ShouldFormSubcompactions()) {
360
0
    StopWatch sw(db_options_.clock, stats_, SUBCOMPACTION_SETUP_TIME);
361
0
    GenSubcompactionBoundaries();
362
0
  }
363
2.77k
  if (boundaries_.size() >= 1) {
364
0
    assert(!known_single_subcompact.has_value());
365
0
    for (size_t i = 0; i <= boundaries_.size(); i++) {
366
0
      compact_->sub_compact_states.emplace_back(
367
0
          c, (i != 0) ? std::optional<Slice>(boundaries_[i - 1]) : std::nullopt,
368
0
          (i != boundaries_.size()) ? std::optional<Slice>(boundaries_[i])
369
0
                                    : std::nullopt,
370
0
          static_cast<uint32_t>(i));
371
      // assert to validate that boundaries don't have same user keys (without
372
      // timestamp part).
373
0
      assert(i == 0 || i == boundaries_.size() ||
374
0
             cfd->user_comparator()->CompareWithoutTimestamp(
375
0
                 boundaries_[i - 1], boundaries_[i]) < 0);
376
0
    }
377
0
    RecordInHistogram(stats_, NUM_SUBCOMPACTIONS_SCHEDULED,
378
0
                      compact_->sub_compact_states.size());
379
2.77k
  } else {
380
2.77k
    std::optional<Slice> start_key;
381
2.77k
    std::optional<Slice> end_key;
382
2.77k
    if (known_single_subcompact.has_value()) {
383
0
      start_key = known_single_subcompact.value().first;
384
0
      end_key = known_single_subcompact.value().second;
385
2.77k
    } else {
386
2.77k
      assert(!start_key.has_value() && !end_key.has_value());
387
2.77k
    }
388
2.77k
    compact_->sub_compact_states.emplace_back(c, start_key, end_key,
389
2.77k
                                              /*sub_job_id*/ 0);
390
2.77k
  }
391
392
2.77k
  MaybeAssignCompactionProgressAndWriter(compaction_progress,
393
2.77k
                                         compaction_progress_writer);
394
395
  // collect all seqno->time information from the input files which will be used
396
  // to encode seqno->time to the output files.
397
2.77k
  SequenceNumber preserve_time_min_seqno = kMaxSequenceNumber;
398
2.77k
  SequenceNumber preclude_last_level_min_seqno = kMaxSequenceNumber;
399
2.77k
  uint64_t preserve_time_duration =
400
2.77k
      MinAndMaxPreserveSeconds(c->mutable_cf_options()).max_preserve_seconds;
401
402
2.77k
  if (preserve_time_duration > 0) {
403
0
    const ReadOptions read_options(Env::IOActivity::kCompaction);
404
    // Setup seqno_to_time_mapping_ with relevant time range.
405
0
    seqno_to_time_mapping_.SetMaxTimeSpan(preserve_time_duration);
406
0
    for (const auto& each_level : *c->inputs()) {
407
0
      for (const auto& fmd : each_level.files) {
408
0
        std::shared_ptr<const TableProperties> tp;
409
0
        Status s = c->input_version()->GetTableProperties(read_options, &tp,
410
0
                                                          fmd, nullptr);
411
0
        if (s.ok()) {
412
0
          s = seqno_to_time_mapping_.DecodeFrom(tp->seqno_to_time_mapping);
413
0
        }
414
0
        if (!s.ok()) {
415
0
          ROCKS_LOG_WARN(
416
0
              db_options_.info_log,
417
0
              "Problem reading or processing seqno-to-time mapping: %s",
418
0
              s.ToString().c_str());
419
0
        }
420
0
      }
421
0
    }
422
423
0
    int64_t _current_time = 0;
424
0
    Status s = db_options_.clock->GetCurrentTime(&_current_time);
425
0
    if (!s.ok()) {
426
0
      ROCKS_LOG_WARN(db_options_.info_log,
427
0
                     "Failed to get current time in compaction: Status: %s",
428
0
                     s.ToString().c_str());
429
      // preserve all time information
430
0
      preserve_time_min_seqno = 0;
431
0
      preclude_last_level_min_seqno = 0;
432
0
      seqno_to_time_mapping_.Enforce();
433
0
    } else {
434
0
      seqno_to_time_mapping_.Enforce(_current_time);
435
0
      seqno_to_time_mapping_.GetCurrentTieringCutoffSeqnos(
436
0
          static_cast<uint64_t>(_current_time),
437
0
          c->mutable_cf_options().preserve_internal_time_seconds,
438
0
          c->mutable_cf_options().preclude_last_level_data_seconds,
439
0
          &preserve_time_min_seqno, &preclude_last_level_min_seqno);
440
0
    }
441
    // For accuracy of the GetProximalSeqnoBeforeTime queries above, we only
442
    // limit the capacity after them.
443
    // Here If we set capacity to the per-SST limit, we could be throwing away
444
    // fidelity when a compaction output file has a narrower seqno range than
445
    // all the inputs. If we only limit capacity for each compaction output, we
446
    // could be doing a lot of unnecessary recomputation in a large compaction
447
    // (up to quadratic in number of files). Thus, we do soemthing in the
448
    // middle: enforce a resonably large constant size limit substantially
449
    // larger than kMaxSeqnoTimePairsPerSST.
450
0
    seqno_to_time_mapping_.SetCapacity(kMaxSeqnoToTimeEntries);
451
0
  }
452
#ifndef NDEBUG
453
  assert(preserve_time_min_seqno <= preclude_last_level_min_seqno);
454
  TEST_SYNC_POINT_CALLBACK(
455
      "CompactionJob::PrepareTimes():preclude_last_level_min_seqno",
456
      static_cast<void*>(&preclude_last_level_min_seqno));
457
  // Restore the invariant asserted above, in case it was broken under the
458
  // callback
459
  preserve_time_min_seqno =
460
      std::min(preclude_last_level_min_seqno, preserve_time_min_seqno);
461
#endif
462
463
  // For a bottommost-file compaction, which exists only because the marker
464
  // (VersionStorageInfo::ComputeBottommostFilesMarkedForCompaction) decided the
465
  // file's largest sequence number could be zeroed, honor that same
466
  // version-carried preserve boundary here. The marker reads DBImpl's live,
467
  // DB-wide seqno->time mapping, whereas the value computed above is derived
468
  // only from the selected file's persisted mapping, which can be sparse or
469
  // empty (e.g. a file written before preserve/preclude was enabled). Taking
470
  // the max ensures this compaction can zero at least the sequence numbers the
471
  // marker considered zeroable, so a marked file makes progress instead of
472
  // being re-marked forever (infinite compaction loop). Other compaction
473
  // reasons keep the per-input-file boundary so their seqno-zeroing behavior is
474
  // unchanged. When preserve/preclude is inactive the version value is
475
  // kMaxSequenceNumber, making this a no-op. A remote compaction worker does
476
  // not know the reason or the live mapping; it gets this boundary from the
477
  // primary in CompactionServiceInput.
478
2.77k
  if (c->compaction_reason() == CompactionReason::kBottommostFiles) {
479
776
    preserve_time_min_seqno_floor_ = storage_info->GetPreserveTimeMinSeqno();
480
776
  }
481
2.77k
  preserve_time_min_seqno =
482
2.77k
      std::max(preserve_time_min_seqno, preserve_time_min_seqno_floor_);
483
484
  // Preserve sequence numbers for preserved write times and snapshots, though
485
  // the specific sequence number of the earliest snapshot can be zeroed.
486
2.77k
  preserve_seqno_after_ =
487
2.77k
      std::max(preserve_time_min_seqno, SequenceNumber{1}) - 1;
488
2.77k
  preserve_seqno_after_ = std::min(preserve_seqno_after_, earliest_snapshot_);
489
  // If using preclude feature, also preclude snapshots from last level, just
490
  // because they are heuristically more likely to be accessed than non-snapshot
491
  // data.
492
2.77k
  if (preclude_last_level_min_seqno < kMaxSequenceNumber &&
493
0
      earliest_snapshot_ < preclude_last_level_min_seqno) {
494
0
    preclude_last_level_min_seqno = earliest_snapshot_;
495
0
  }
496
  // Now combine what we would like to preclude from last level with what we
497
  // can safely support without dangerously moving data back up the LSM tree,
498
  // to get the final seqno threshold for proximal vs. last. In particular,
499
  // when the reserved output key range for the proximal level does not
500
  // include the entire last level input key range, we need to keep entries
501
  // already in the last level there. (Even allowing within-range entries to
502
  // move back up could cause problems with range tombstones. Perhaps it
503
  // would be better in some rare cases to keep entries in the last level
504
  // one-by-one rather than based on sequence number, but that would add extra
505
  // tracking and complexity to CompactionIterator that is probably not
506
  // worthwhile overall. Correctness is also more clear when splitting by
507
  // seqno threshold.)
508
2.77k
  proximal_after_seqno_ = std::max(preclude_last_level_min_seqno,
509
2.77k
                                   c->GetKeepInLastLevelThroughSeqno());
510
511
2.77k
  options_file_number_ = versions_->options_file_number();
512
513
  // For remote compaction hardening: capture the current MANIFEST position as a
514
  // floor for the worker's recovery. manifest_file_size() is a physical byte
515
  // offset that VersionSet::ProcessManifestWrites records under the DB mutex
516
  // only after the whole version-edit batch is written and sync'd, so it is a
517
  // durable, complete-batch offset -- never mid-record or mid-atomic-group --
518
  // at or past the compaction's (already committed) input version.
519
2.77k
  if (mutable_db_options_copy_.remote_compaction_manifest_floor) {
520
2.77k
    min_manifest_file_number_ = versions_->manifest_file_number();
521
2.77k
    min_manifest_file_size_ = versions_->manifest_file_size();
522
2.77k
  }
523
2.77k
}
524
525
void CompactionJob::MaybeAssignCompactionProgressAndWriter(
526
    const CompactionProgress& compaction_progress,
527
2.77k
    log::Writer* compaction_progress_writer) {
528
  // LIMITATION: Only supports resuming single subcompaction for now
529
2.77k
  if (compact_->sub_compact_states.size() != 1) {
530
0
    return;
531
0
  }
532
533
2.77k
  if (!compaction_progress.empty()) {
534
0
    assert(compaction_progress.size() == 1);
535
0
    SubcompactionState* sub_compact = &compact_->sub_compact_states[0];
536
0
    const SubcompactionProgress& subcompaction_progress =
537
0
        compaction_progress[0];
538
0
    sub_compact->SetSubcompactionProgress(subcompaction_progress);
539
0
  }
540
541
2.77k
  compaction_progress_writer_ = compaction_progress_writer;
542
2.77k
}
543
544
0
uint64_t CompactionJob::GetSubcompactionsLimit() {
545
0
  return extra_num_subcompaction_threads_reserved_ +
546
0
         std::max(
547
0
             std::uint64_t(1),
548
0
             static_cast<uint64_t>(compact_->compaction->max_subcompactions()));
549
0
}
550
551
void CompactionJob::AcquireSubcompactionResources(
552
0
    int num_extra_required_subcompactions) {
553
0
  if (remote_compaction_executor_) {
554
0
    return;
555
0
  }
556
0
  TEST_SYNC_POINT("CompactionJob::AcquireSubcompactionResources:0");
557
0
  TEST_SYNC_POINT("CompactionJob::AcquireSubcompactionResources:1");
558
0
  int max_db_compactions =
559
0
      DBImpl::GetBGJobLimits(
560
0
          mutable_db_options_copy_.max_background_flushes,
561
0
          mutable_db_options_copy_.max_background_compactions,
562
0
          mutable_db_options_copy_.max_background_jobs,
563
0
          versions_->GetColumnFamilySet()
564
0
              ->write_controller()
565
0
              ->NeedSpeedupCompaction())
566
0
          .max_compactions;
567
0
  InstrumentedMutexLock l(db_mutex_);
568
  // Apply min function first since We need to compute the extra subcompaction
569
  // against compaction limits. And then try to reserve threads for extra
570
  // subcompactions. The actual number of reserved threads could be less than
571
  // the desired number.
572
0
  int available_bg_compactions_against_db_limit =
573
0
      std::max(max_db_compactions - *bg_compaction_scheduled_ -
574
0
                   *bg_bottom_compaction_scheduled_,
575
0
               0);
576
  // Reservation only supports backgrdoun threads of which the priority is
577
  // between BOTTOM and HIGH. Need to degrade the priority to HIGH if the
578
  // origin thread_pri_ is higher than that. Similar to ReleaseThreads().
579
0
  extra_num_subcompaction_threads_reserved_ =
580
0
      env_->ReserveThreads(std::min(num_extra_required_subcompactions,
581
0
                                    available_bg_compactions_against_db_limit),
582
0
                           std::min(thread_pri_, Env::Priority::HIGH));
583
584
  // Update bg_compaction_scheduled_ or bg_bottom_compaction_scheduled_
585
  // depending on if this compaction has the bottommost priority
586
0
  if (thread_pri_ == Env::Priority::BOTTOM) {
587
0
    *bg_bottom_compaction_scheduled_ +=
588
0
        extra_num_subcompaction_threads_reserved_;
589
0
  } else {
590
0
    *bg_compaction_scheduled_ += extra_num_subcompaction_threads_reserved_;
591
0
  }
592
0
}
593
594
0
void CompactionJob::ShrinkSubcompactionResources(uint64_t num_extra_resources) {
595
  // Do nothing when we have zero resources to shrink
596
0
  if (num_extra_resources == 0) {
597
0
    return;
598
0
  }
599
0
  db_mutex_->Lock();
600
  // We cannot release threads more than what we reserved before
601
0
  int extra_num_subcompaction_threads_released = env_->ReleaseThreads(
602
0
      (int)num_extra_resources, std::min(thread_pri_, Env::Priority::HIGH));
603
  // Update the number of reserved threads and the number of background
604
  // scheduled compactions for this compaction job
605
0
  extra_num_subcompaction_threads_reserved_ -=
606
0
      extra_num_subcompaction_threads_released;
607
  // TODO (zichen): design a test case with new subcompaction partitioning
608
  // when the number of actual partitions is less than the number of planned
609
  // partitions
610
0
  assert(extra_num_subcompaction_threads_released == (int)num_extra_resources);
611
  // Update bg_compaction_scheduled_ or bg_bottom_compaction_scheduled_
612
  // depending on if this compaction has the bottommost priority
613
0
  if (thread_pri_ == Env::Priority::BOTTOM) {
614
0
    *bg_bottom_compaction_scheduled_ -=
615
0
        extra_num_subcompaction_threads_released;
616
0
  } else {
617
0
    *bg_compaction_scheduled_ -= extra_num_subcompaction_threads_released;
618
0
  }
619
0
  db_mutex_->Unlock();
620
0
  TEST_SYNC_POINT("CompactionJob::ShrinkSubcompactionResources:0");
621
0
}
622
623
2.77k
void CompactionJob::ReleaseSubcompactionResources() {
624
2.77k
  if (extra_num_subcompaction_threads_reserved_ == 0) {
625
2.77k
    return;
626
2.77k
  }
627
0
  {
628
0
    InstrumentedMutexLock l(db_mutex_);
629
    // The number of reserved threads becomes larger than 0 only if the
630
    // compaction prioity is round robin and there is no sufficient
631
    // sub-compactions available
632
633
    // The scheduled compaction must be no less than 1 + extra number
634
    // subcompactions using acquired resources since this compaction job has not
635
    // finished yet
636
0
    assert(*bg_bottom_compaction_scheduled_ >=
637
0
               1 + extra_num_subcompaction_threads_reserved_ ||
638
0
           *bg_compaction_scheduled_ >=
639
0
               1 + extra_num_subcompaction_threads_reserved_);
640
0
  }
641
0
  ShrinkSubcompactionResources(extra_num_subcompaction_threads_reserved_);
642
0
}
643
644
0
void CompactionJob::GenSubcompactionBoundaries() {
645
  // The goal is to find some boundary keys so that we can evenly partition
646
  // the compaction input data into max_subcompactions ranges.
647
  // For every input file, we ask TableReader to estimate 128 anchor points
648
  // that evenly partition the input file into 128 ranges and the range
649
  // sizes. This can be calculated by scanning index blocks of the file.
650
  // Once we have the anchor points for all the input files, we merge them
651
  // together and try to find keys dividing ranges evenly.
652
  // For example, if we have two input files, and each returns following
653
  // ranges:
654
  //   File1: (a1, 1000), (b1, 1200), (c1, 1100)
655
  //   File2: (a2, 1100), (b2, 1000), (c2, 1000)
656
  // We total sort the keys to following:
657
  //  (a1, 1000), (a2, 1100), (b1, 1200), (b2, 1000), (c1, 1100), (c2, 1000)
658
  // We calculate the total size by adding up all ranges' size, which is 6400.
659
  // If we would like to partition into 2 subcompactions, the target of the
660
  // range size is 3200. Based on the size, we take "b1" as the partition key
661
  // since the first three ranges would hit 3200.
662
  //
663
  // Note that the ranges are actually overlapping. For example, in the example
664
  // above, the range ending with "b1" is overlapping with the range ending with
665
  // "b2". So the size 1000+1100+1200 is an underestimation of data size up to
666
  // "b1". In extreme cases where we only compact N L0 files, a range can
667
  // overlap with N-1 other ranges. Since we requested a relatively large number
668
  // (128) of ranges from each input files, even N range overlapping would
669
  // cause relatively small inaccuracy.
670
0
  ReadOptions read_options(Env::IOActivity::kCompaction);
671
0
  read_options.rate_limiter_priority = GetRateLimiterPriority();
672
0
  auto* c = compact_->compaction;
673
0
  if (c->mutable_cf_options().table_factory->Name() ==
674
0
      TableFactory::kPlainTableName()) {
675
0
    return;
676
0
  }
677
678
0
  if (c->max_subcompactions() <= 1 &&
679
0
      !(c->immutable_options().compaction_pri == kRoundRobin &&
680
0
        c->immutable_options().compaction_style == kCompactionStyleLevel)) {
681
0
    return;
682
0
  }
683
0
  auto* cfd = c->column_family_data();
684
0
  const Comparator* cfd_comparator = cfd->user_comparator();
685
0
  const InternalKeyComparator& icomp = cfd->internal_comparator();
686
687
0
  auto* v = compact_->compaction->input_version();
688
0
  int base_level = v->storage_info()->base_level();
689
0
  InstrumentedMutexUnlock unlock_guard(db_mutex_);
690
691
0
  uint64_t total_size = 0;
692
0
  std::vector<TableReader::Anchor> all_anchors;
693
0
  int start_lvl = c->start_level();
694
0
  int out_lvl = c->output_level();
695
696
0
  for (size_t lvl_idx = 0; lvl_idx < c->num_input_levels(); lvl_idx++) {
697
0
    int lvl = c->level(lvl_idx);
698
0
    if (lvl >= start_lvl && lvl <= out_lvl) {
699
0
      const LevelFilesBrief* flevel = c->input_levels(lvl_idx);
700
0
      size_t num_files = flevel->num_files;
701
702
0
      if (num_files == 0) {
703
0
        continue;
704
0
      }
705
706
0
      for (size_t i = 0; i < num_files; i++) {
707
0
        FileMetaData* f = flevel->files[i].file_metadata;
708
0
        std::vector<TableReader::Anchor> my_anchors;
709
0
        Status s = cfd->table_cache()->ApproximateKeyAnchors(
710
0
            read_options, icomp, *f, c->mutable_cf_options(), my_anchors);
711
0
        if (!s.ok() || my_anchors.empty()) {
712
0
          my_anchors.emplace_back(f->largest.user_key(), f->fd.GetFileSize());
713
0
        }
714
0
        for (auto& ac : my_anchors) {
715
          // Can be optimize to avoid this loop.
716
0
          total_size += ac.range_size;
717
0
        }
718
719
0
        all_anchors.insert(all_anchors.end(), my_anchors.begin(),
720
0
                           my_anchors.end());
721
0
      }
722
0
    }
723
0
  }
724
  // Here we total sort all the anchor points across all files and go through
725
  // them in the sorted order to find partitioning boundaries.
726
  // Not the most efficient implementation. A much more efficient algorithm
727
  // probably exists. But they are more complex. If performance turns out to
728
  // be a problem, we can optimize.
729
0
  std::sort(
730
0
      all_anchors.begin(), all_anchors.end(),
731
0
      [cfd_comparator](TableReader::Anchor& a, TableReader::Anchor& b) -> bool {
732
0
        return cfd_comparator->CompareWithoutTimestamp(a.user_key, b.user_key) <
733
0
               0;
734
0
      });
735
736
  // Remove duplicated entries from boundaries.
737
0
  all_anchors.erase(
738
0
      std::unique(all_anchors.begin(), all_anchors.end(),
739
0
                  [cfd_comparator](TableReader::Anchor& a,
740
0
                                   TableReader::Anchor& b) -> bool {
741
0
                    return cfd_comparator->CompareWithoutTimestamp(
742
0
                               a.user_key, b.user_key) == 0;
743
0
                  }),
744
0
      all_anchors.end());
745
746
  // Get the number of planned subcompactions, may update reserve threads
747
  // and update extra_num_subcompaction_threads_reserved_ for round-robin
748
0
  uint64_t num_planned_subcompactions;
749
0
  if (c->immutable_options().compaction_pri == kRoundRobin &&
750
0
      c->immutable_options().compaction_style == kCompactionStyleLevel) {
751
    // For round-robin compaction prioity, we need to employ more
752
    // subcompactions (may exceed the max_subcompaction limit). The extra
753
    // subcompactions will be executed using reserved threads and taken into
754
    // account bg_compaction_scheduled or bg_bottom_compaction_scheduled.
755
756
    // Initialized by the number of input files
757
0
    num_planned_subcompactions = static_cast<uint64_t>(c->num_input_files(0));
758
0
    uint64_t max_subcompactions_limit = GetSubcompactionsLimit();
759
0
    if (max_subcompactions_limit < num_planned_subcompactions) {
760
      // Assert two pointers are not empty so that we can use extra
761
      // subcompactions against db compaction limits
762
0
      assert(bg_bottom_compaction_scheduled_ != nullptr);
763
0
      assert(bg_compaction_scheduled_ != nullptr);
764
      // Reserve resources when max_subcompaction is not sufficient
765
0
      AcquireSubcompactionResources(
766
0
          (int)(num_planned_subcompactions - max_subcompactions_limit));
767
      // Subcompactions limit changes after acquiring additional resources.
768
      // Need to call GetSubcompactionsLimit() again to update the number
769
      // of planned subcompactions
770
0
      num_planned_subcompactions =
771
0
          std::min(num_planned_subcompactions, GetSubcompactionsLimit());
772
0
    } else {
773
0
      num_planned_subcompactions = max_subcompactions_limit;
774
0
    }
775
0
  } else {
776
0
    num_planned_subcompactions = GetSubcompactionsLimit();
777
0
  }
778
779
0
  TEST_SYNC_POINT_CALLBACK("CompactionJob::GenSubcompactionBoundaries:0",
780
0
                           &num_planned_subcompactions);
781
0
  if (num_planned_subcompactions == 1) {
782
0
    return;
783
0
  }
784
785
  // Group the ranges into subcompactions
786
0
  uint64_t target_range_size = std::max(
787
0
      total_size / num_planned_subcompactions,
788
0
      MaxFileSizeForLevel(
789
0
          c->mutable_cf_options(), out_lvl,
790
0
          c->immutable_options().compaction_style, base_level,
791
0
          c->immutable_options().level_compaction_dynamic_level_bytes));
792
793
0
  if (target_range_size >= total_size) {
794
0
    return;
795
0
  }
796
797
0
  uint64_t next_threshold = target_range_size;
798
0
  uint64_t cumulative_size = 0;
799
0
  uint64_t num_actual_subcompactions = 1U;
800
0
  for (TableReader::Anchor& anchor : all_anchors) {
801
0
    cumulative_size += anchor.range_size;
802
0
    if (cumulative_size > next_threshold) {
803
0
      next_threshold += target_range_size;
804
0
      num_actual_subcompactions++;
805
0
      boundaries_.push_back(anchor.user_key);
806
0
    }
807
0
    if (num_actual_subcompactions == num_planned_subcompactions) {
808
0
      break;
809
0
    }
810
0
  }
811
0
  TEST_SYNC_POINT_CALLBACK("CompactionJob::GenSubcompactionBoundaries:1",
812
0
                           &num_actual_subcompactions);
813
  // Shrink extra subcompactions resources when extra resrouces are acquired
814
0
  ShrinkSubcompactionResources(
815
0
      std::min((int)(num_planned_subcompactions - num_actual_subcompactions),
816
0
               extra_num_subcompaction_threads_reserved_));
817
0
}
818
819
2.77k
void CompactionJob::InitializeCompactionRun() {
820
2.77k
  AutoThreadOperationStageUpdater stage_updater(
821
2.77k
      ThreadStatus::STAGE_COMPACTION_RUN);
822
2.77k
  TEST_SYNC_POINT("CompactionJob::Run():Start");
823
2.77k
  log_buffer_->FlushBufferToLog();
824
2.77k
  LogCompaction();
825
2.77k
}
826
827
2.77k
void CompactionJob::RunSubcompactions() {
828
2.77k
  TEST_SYNC_POINT("CompactionJob::RunSubcompactions:BeforeStart");
829
2.77k
  const size_t num_threads = compact_->sub_compact_states.size();
830
2.77k
  assert(num_threads > 0);
831
2.77k
  compact_->compaction->GetOrInitInputTableProperties();
832
833
2.77k
  if (remote_compaction_executor_) {
834
0
    const auto run_subcompaction = [this](SubcompactionState* sub_compact) {
835
0
      try {
836
0
        ProcessKeyValueCompaction(sub_compact);
837
0
      } catch (...) {
838
0
        sub_compact->status = Status::Aborted();
839
0
      }
840
0
    };
841
0
    SubcompactionCompletion completion;
842
0
    for (size_t i = 1; i < num_threads; ++i) {
843
0
      try {
844
0
        completion.Add();
845
0
        SubcompactionCompletion::Guard submission(completion);
846
0
        remote_compaction_executor_([&, i] {
847
0
          SubcompactionCompletion::Guard completed(completion);
848
0
          run_subcompaction(&compact_->sub_compact_states[i]);
849
0
        });
850
0
        submission.Release();
851
0
      } catch (...) {
852
0
        compact_->sub_compact_states[0].status = Status::Aborted();
853
0
        for (size_t j = i; j < num_threads; ++j) {
854
0
          compact_->sub_compact_states[j].status = Status::Aborted();
855
0
        }
856
0
        break;
857
0
      }
858
0
    }
859
0
    if (compact_->sub_compact_states[0].status.ok()) {
860
0
      run_subcompaction(compact_->sub_compact_states.data());
861
0
    }
862
2.77k
  } else {
863
    // Launch a thread for each of subcompactions 1...num_threads-1
864
2.77k
    std::vector<port::Thread> thread_pool;
865
2.77k
    thread_pool.reserve(num_threads - 1);
866
2.77k
    for (size_t i = 1; i < compact_->sub_compact_states.size(); i++) {
867
0
      thread_pool.emplace_back(&CompactionJob::ProcessKeyValueCompaction, this,
868
0
                               &compact_->sub_compact_states[i]);
869
0
    }
870
871
    // Always schedule the first subcompaction (whether or not there are also
872
    // others) in the current thread to be efficient with resources
873
2.77k
    ProcessKeyValueCompaction(compact_->sub_compact_states.data());
874
875
    // Wait for all other threads (if there are any) to finish execution
876
2.77k
    for (auto& thread : thread_pool) {
877
0
      thread.join();
878
0
    }
879
2.77k
  }
880
2.77k
  RecordRemoteCompactionInputBlobFileCount();
881
2.77k
  RemoveEmptyOutputs();
882
883
2.77k
  ReleaseSubcompactionResources();
884
2.77k
  TEST_SYNC_POINT("CompactionJob::ReleaseSubcompactionResources");
885
2.77k
}
886
887
2.77k
void CompactionJob::RecordRemoteCompactionInputBlobFileCount() {
888
2.77k
  const uint64_t oldest_blob_file_number =
889
2.77k
      remote_compaction_min_blob_file_number_.load(std::memory_order_relaxed);
890
2.77k
  if (oldest_blob_file_number == std::numeric_limits<uint64_t>::max()) {
891
2.77k
    return;
892
2.77k
  }
893
894
0
  uint64_t blob_file_count = 0;
895
0
  const VersionStorageInfo* const storage_info =
896
0
      compact_->compaction->input_version()->storage_info();
897
0
  for (const auto& blob_file : storage_info->GetBlobFiles()) {
898
0
    if (blob_file->GetBlobFileNumber() >= oldest_blob_file_number) {
899
0
      ++blob_file_count;
900
0
    }
901
0
  }
902
0
  RecordTick(stats_, REMOTE_COMPACT_INPUT_BLOB_FILE_COUNT, blob_file_count);
903
0
}
904
905
2.77k
void CompactionJob::UpdateTimingStats(uint64_t start_micros) {
906
2.77k
  internal_stats_.SetMicros(db_options_.clock->NowMicros() - start_micros);
907
908
2.77k
  for (auto& state : compact_->sub_compact_states) {
909
2.77k
    internal_stats_.AddCpuMicros(state.compaction_job_stats.cpu_micros);
910
2.77k
  }
911
912
2.77k
  RecordTimeToHistogram(stats_, COMPACTION_TIME,
913
2.77k
                        internal_stats_.output_level_stats.micros);
914
2.77k
  RecordTimeToHistogram(stats_, COMPACTION_CPU_TIME,
915
2.77k
                        internal_stats_.output_level_stats.cpu_micros);
916
2.77k
}
917
918
2.77k
void CompactionJob::RemoveEmptyOutputs() {
919
2.77k
  for (auto& state : compact_->sub_compact_states) {
920
2.77k
    state.RemoveLastEmptyOutput();
921
2.77k
  }
922
2.77k
}
923
924
0
void CompactionJob::CleanupAbortedSubcompactions() {
925
0
  ColumnFamilyData* cfd = compact_->compaction->column_family_data();
926
927
0
  uint64_t total_sst_files_deleted = 0;
928
0
  uint64_t total_blob_files_deleted = 0;
929
930
  // Track the first file deletion error to report at the end
931
0
  Status first_error;
932
0
  int deletion_errors = 0;
933
934
  // Mark all subcompactions as aborted and delete their output files
935
0
  for (auto& sub_compact : compact_->sub_compact_states) {
936
    // Mark this subcompaction as aborted
937
0
    sub_compact.status =
938
0
        Status::Incomplete(Status::SubCode::kCompactionAborted);
939
940
    // Delete all files (SST and blob) tracked during compaction.
941
    // GetOutputFilePaths() contains ALL file paths created, including
942
    // in-progress files that may have been removed from outputs_ or
943
    // blob_file_additions_.
944
0
    for (const bool is_proximal_level : {false, true}) {
945
0
      if (is_proximal_level &&
946
0
          !compact_->compaction->SupportsPerKeyPlacement()) {
947
0
        continue;
948
0
      }
949
0
      for (const std::string& file_path :
950
0
           sub_compact.Outputs(is_proximal_level)->GetOutputFilePaths()) {
951
0
        Status s = env_->DeleteFile(file_path);
952
0
        if (s.ok()) {
953
          // Count SST vs blob files by checking extension
954
0
          if (file_path.find(".sst") != std::string::npos) {
955
0
            total_sst_files_deleted++;
956
0
          } else if (file_path.find(".blob") != std::string::npos) {
957
0
            total_blob_files_deleted++;
958
0
          }
959
0
        } else if (!s.IsNotFound()) {
960
0
          if (first_error.ok()) {
961
0
            first_error = s;
962
0
          }
963
0
          deletion_errors++;
964
0
        }
965
0
      }
966
0
    }
967
0
    sub_compact.CleanupOutputs();
968
0
  }
969
970
0
  if (stats_) {
971
0
    RecordTick(stats_, COMPACTION_ABORTED);
972
0
  }
973
974
0
  ROCKS_LOG_INFO(db_options_.info_log,
975
0
                 "[%s] [JOB %d] Compaction aborted: deleted %" PRIu64
976
0
                 " SST files and %" PRIu64 " blob files",
977
0
                 cfd->GetName().c_str(), job_id_, total_sst_files_deleted,
978
0
                 total_blob_files_deleted);
979
980
0
  if (!first_error.ok()) {
981
0
    ROCKS_LOG_ERROR(db_options_.info_log,
982
0
                    "[%s] [JOB %d] Cleanup completed with %d file deletion "
983
0
                    "errors. First error: %s",
984
0
                    cfd->GetName().c_str(), job_id_, deletion_errors,
985
0
                    first_error.ToString().c_str());
986
0
  }
987
0
}
988
989
2.07k
bool CompactionJob::HasNewBlobFiles() const {
990
2.07k
  for (const auto& state : compact_->sub_compact_states) {
991
2.07k
    if (state.Current().HasBlobFileAdditions()) {
992
0
      return true;
993
0
    }
994
2.07k
  }
995
2.07k
  return false;
996
2.07k
}
997
998
2.77k
Status CompactionJob::CollectSubcompactionErrors() {
999
2.77k
  Status status;
1000
2.77k
  IOStatus io_s;
1001
1002
2.77k
  for (const auto& state : compact_->sub_compact_states) {
1003
2.77k
    if (!state.status.ok()) {
1004
700
      status = state.status;
1005
700
      io_s = state.io_status;
1006
700
      break;
1007
700
    }
1008
2.77k
  }
1009
1010
2.77k
  if (io_status_.ok()) {
1011
2.77k
    io_status_ = io_s;
1012
2.77k
  }
1013
1014
2.77k
  return status;
1015
2.77k
}
1016
1017
2.07k
Status CompactionJob::SyncOutputDirectories() {
1018
2.07k
  Status status;
1019
2.07k
  IOStatus io_s;
1020
2.07k
  constexpr IODebugContext* dbg = nullptr;
1021
2.07k
  const bool wrote_new_blob_files = HasNewBlobFiles();
1022
2.07k
  if (output_directory_) {
1023
2.07k
    io_s = output_directory_->FsyncWithDirOptions(
1024
2.07k
        IOOptions(), dbg,
1025
2.07k
        DirFsyncOptions(DirFsyncOptions::FsyncReason::kNewFileSynced));
1026
2.07k
  }
1027
1028
2.07k
  if (io_s.ok() && wrote_new_blob_files && blob_output_directory_ &&
1029
0
      blob_output_directory_ != output_directory_) {
1030
0
    io_s = blob_output_directory_->FsyncWithDirOptions(
1031
0
        IOOptions(), dbg,
1032
0
        DirFsyncOptions(DirFsyncOptions::FsyncReason::kNewFileSynced));
1033
0
  }
1034
1035
2.07k
  if (io_status_.ok()) {
1036
2.07k
    io_status_ = io_s;
1037
2.07k
  }
1038
2.07k
  if (status.ok()) {
1039
2.07k
    status = io_s;
1040
2.07k
  }
1041
1042
2.07k
  return status;
1043
2.07k
}
1044
1045
2.07k
Status CompactionJob::VerifyOutputFiles() {
1046
2.07k
  Status status;
1047
2.07k
  std::vector<port::Thread> thread_pool;
1048
2.07k
  ColumnFamilyData* cfd = compact_->compaction->column_family_data();
1049
2.07k
  VerifyOutputFlags verify_output_flags =
1050
2.07k
      compact_->compaction->mutable_cf_options().verify_output_flags;
1051
1052
  // For backward compatibility
1053
2.07k
  if (paranoid_file_checks_) {
1054
0
    verify_output_flags |= VerifyOutputFlags::kVerifyIteration;
1055
0
    verify_output_flags |= VerifyOutputFlags::kEnableForLocalCompaction;
1056
0
    verify_output_flags |= VerifyOutputFlags::kEnableForRemoteCompaction;
1057
0
  }
1058
1059
2.07k
  auto verify_table = [&](SubcompactionState& subcompaction_state) {
1060
    // Collect file open metadata during verification when fast_sst_open
1061
    // is enabled, keyed by file number.
1062
2.07k
    UnorderedMap<uint64_t, std::string> file_open_metadata_map;
1063
1064
2.07k
    for (const auto& output_file : subcompaction_state.GetOutputs()) {
1065
      // Verify that the table is usable
1066
      // We set for_compaction to false and don't
1067
      // OptimizeForCompactionTableRead here because this is a special case
1068
      // after we finish the table building No matter whether
1069
      // use_direct_io_for_flush_and_compaction is true, we will regard this
1070
      // verification as user reads since the goal is to cache it here for
1071
      // further user reads
1072
1.21k
      ReadOptions verification_read_options(Env::IOActivity::kCompaction);
1073
1.21k
      verification_read_options.verify_checksums = true;
1074
1.21k
      verification_read_options.readahead_size =
1075
1.21k
          file_options_for_read_.compaction_readahead_size;
1076
1077
1.21k
      std::unique_ptr<TableReader> table_reader_guard;
1078
1.21k
      TableReader* table_reader_ptr = table_reader_guard.get();
1079
1.21k
      verification_read_options.rate_limiter_priority =
1080
1.21k
          GetRateLimiterPriority();
1081
1.21k
      std::string file_open_metadata;
1082
1.21k
      std::string* file_open_metadata_ptr =
1083
1.21k
          mutable_db_options_copy_.fast_sst_open ? &file_open_metadata
1084
1.21k
                                                 : nullptr;
1085
1.21k
      InternalIterator* iter = cfd->table_cache()->NewIterator(
1086
1.21k
          verification_read_options, file_options_, cfd->internal_comparator(),
1087
1.21k
          output_file.meta,
1088
1.21k
          /*range_del_agg=*/nullptr, compact_->compaction->mutable_cf_options(),
1089
1.21k
          /*table_reader_ptr=*/&table_reader_ptr,
1090
1.21k
          cfd->internal_stats()->GetFileReadHist(
1091
1.21k
              compact_->compaction->output_level()),
1092
1.21k
          TableReaderCaller::kCompactionRefill, /*arena=*/nullptr,
1093
1.21k
          /*skip_filters=*/false, compact_->compaction->output_level(),
1094
1.21k
          MaxFileSizeForL0MetaPin(compact_->compaction->mutable_cf_options()),
1095
1.21k
          /*smallest_compaction_key=*/nullptr,
1096
1.21k
          /*largest_compaction_key=*/nullptr,
1097
1.21k
          /*allow_unprepared_value=*/false, /*seek_target=*/nullptr,
1098
1.21k
          /*prefix_filtered_table=*/nullptr,
1099
1.21k
          /*range_del_read_seqno=*/nullptr,
1100
1.21k
          /*range_del_iter=*/nullptr,
1101
1.21k
          /*maybe_pin_table_handle=*/false, file_open_metadata_ptr);
1102
1.21k
      auto s = iter->status();
1103
1.21k
      if (s.ok()) {
1104
        // Check for remote/local compaction and verify_output_flags flags
1105
1.21k
        const bool should_verify =
1106
1.21k
            (subcompaction_state.compaction_job_stats.is_remote_compaction &&
1107
0
             !!(verify_output_flags &
1108
0
                VerifyOutputFlags::kEnableForRemoteCompaction)) ||
1109
1.21k
            (!subcompaction_state.compaction_job_stats.is_remote_compaction &&
1110
1.21k
             !!(verify_output_flags &
1111
1.21k
                VerifyOutputFlags::kEnableForLocalCompaction));
1112
1113
1.21k
        if (should_verify) {
1114
0
          const bool should_verify_block_checksum =
1115
0
              !!(verify_output_flags & VerifyOutputFlags::kVerifyBlockChecksum);
1116
0
          const bool should_verify_iteration =
1117
0
              !!(verify_output_flags & VerifyOutputFlags::kVerifyIteration);
1118
0
          const bool should_verify_file_checksum =
1119
0
              !!(verify_output_flags &
1120
0
                 VerifyOutputFlags::kVerifyFileChecksum) &&
1121
0
              db_options_.file_checksum_gen_factory != nullptr &&
1122
0
              output_file.meta.file_checksum != kUnknownFileChecksum;
1123
0
          if (should_verify_block_checksum) {
1124
0
            assert(table_reader_ptr != nullptr);
1125
            // If verifying iteration as well, verify meta blocks here only to
1126
            // avoid redundant checks on data blocks
1127
0
            s = table_reader_ptr->VerifyChecksum(
1128
0
                verification_read_options, TableReaderCaller::kCompaction,
1129
0
                /*meta_blocks_only=*/should_verify_iteration);
1130
0
          }
1131
0
          if (s.ok() && should_verify_iteration) {
1132
0
            OutputValidator validator(cfd->internal_comparator(),
1133
0
                                      /*_enable_hash=*/true);
1134
0
            for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
1135
0
              s = validator.Add(iter->key(), iter->value());
1136
0
              if (!s.ok()) {
1137
0
                break;
1138
0
              }
1139
0
            }
1140
0
            if (s.ok()) {
1141
0
              s = iter->status();
1142
0
            }
1143
0
            if (s.ok() && !validator.CompareValidator(output_file.validator)) {
1144
0
              s = Status::Corruption(
1145
0
                  "Key-value checksum of compaction output doesn't match what "
1146
0
                  "was computed when written");
1147
0
            }
1148
0
          }
1149
0
          if (s.ok() && should_verify_file_checksum) {
1150
0
            std::string file_checksum;
1151
0
            std::string file_checksum_func_name;
1152
0
            std::string fname =
1153
0
                GetTableFileName(output_file.meta.fd.GetNumber());
1154
0
            s = GenerateOneFileChecksum(
1155
0
                fs_.get(), fname, db_options_.file_checksum_gen_factory.get(),
1156
0
                output_file.meta.file_checksum_func_name, &file_checksum,
1157
0
                &file_checksum_func_name,
1158
0
                verification_read_options.readahead_size,
1159
0
                db_options_.allow_mmap_reads, io_tracer_,
1160
0
                db_options_.rate_limiter.get(), verification_read_options,
1161
0
                stats_, db_options_.clock, file_options_for_read_);
1162
0
            if (s.ok() && file_checksum != output_file.meta.file_checksum) {
1163
0
              s = Status::Corruption(
1164
0
                  "File checksum mismatch for compaction output file " + fname);
1165
0
            }
1166
0
          }
1167
0
        }
1168
1.21k
      }
1169
1170
1.21k
      delete iter;
1171
1172
1.21k
      if (!s.ok()) {
1173
0
        subcompaction_state.status = s;
1174
0
        break;
1175
0
      }
1176
1177
1.21k
      if (!file_open_metadata.empty()) {
1178
0
        file_open_metadata_map[output_file.meta.fd.GetNumber()] =
1179
0
            std::move(file_open_metadata);
1180
0
      }
1181
1.21k
    }
1182
1183
    // Apply collected file open metadata to mutable outputs
1184
2.07k
    if (!file_open_metadata_map.empty()) {
1185
0
      auto apply_metadata =
1186
0
          [&file_open_metadata_map](
1187
0
              std::vector<CompactionOutputs::Output>& outputs) {
1188
0
            for (auto& output : outputs) {
1189
0
              auto it = file_open_metadata_map.find(output.meta.fd.GetNumber());
1190
0
              if (it != file_open_metadata_map.end()) {
1191
0
                output.meta.file_open_metadata = std::move(it->second);
1192
0
              }
1193
0
            }
1194
0
          };
1195
0
      apply_metadata(subcompaction_state.GetMutableCompactionOutputs());
1196
0
      apply_metadata(subcompaction_state.GetMutableProximalOutputs());
1197
0
    }
1198
2.07k
  };
1199
2.07k
  for (size_t i = 1; i < compact_->sub_compact_states.size(); i++) {
1200
0
    thread_pool.emplace_back(verify_table,
1201
0
                             std::ref(compact_->sub_compact_states[i]));
1202
0
  }
1203
2.07k
  verify_table(compact_->sub_compact_states[0]);
1204
2.07k
  for (auto& thread : thread_pool) {
1205
0
    thread.join();
1206
0
  }
1207
1208
2.07k
  for (const auto& state : compact_->sub_compact_states) {
1209
2.07k
    if (!state.status.ok()) {
1210
0
      status = state.status;
1211
0
      break;
1212
0
    }
1213
2.07k
  }
1214
1215
2.07k
  return status;
1216
2.07k
}
1217
1218
2.07k
void CompactionJob::SetOutputTableProperties() {
1219
2.07k
  for (const auto& state : compact_->sub_compact_states) {
1220
2.07k
    for (const auto& output : state.GetOutputs()) {
1221
1.21k
      auto fn =
1222
1.21k
          TableFileName(state.compaction->immutable_options().cf_paths,
1223
1.21k
                        output.meta.fd.GetNumber(), output.meta.fd.GetPathId());
1224
1.21k
      compact_->compaction->SetOutputTableProperties(fn,
1225
1.21k
                                                     output.table_properties);
1226
1.21k
    }
1227
2.07k
  }
1228
2.07k
}
1229
1230
2.77k
void CompactionJob::AggregateSubcompactionOutputAndJobStats() {
1231
  // Before the compaction starts, is_remote_compaction was set to true if
1232
  // compaction_service is set. We now know whether each sub_compaction was
1233
  // done remotely or not. Reset is_remote_compaction back to false and allow
1234
  // AggregateCompactionStats() to set the right value.
1235
2.77k
  job_stats_->is_remote_compaction = false;
1236
1237
  // Finish up all bookkeeping to unify the subcompaction results.
1238
2.77k
  compact_->AggregateCompactionStats(internal_stats_, *job_stats_);
1239
2.77k
}
1240
1241
Status CompactionJob::VerifyCompactionRecordCounts(
1242
2.07k
    bool stats_built_from_input_table_prop, uint64_t num_input_range_del) {
1243
2.07k
  Status status;
1244
2.07k
  if (stats_built_from_input_table_prop &&
1245
2.07k
      job_stats_->has_accurate_num_input_records) {
1246
2.07k
    status = VerifyInputRecordCount(num_input_range_del);
1247
2.07k
    if (!status.ok()) {
1248
0
      return status;
1249
0
    }
1250
2.07k
  }
1251
1252
2.07k
  const auto& mutable_cf_options = compact_->compaction->mutable_cf_options();
1253
2.07k
  if ((mutable_cf_options.table_factory->IsInstanceOf(
1254
2.07k
           TableFactory::kBlockBasedTableName()) ||
1255
0
       mutable_cf_options.table_factory->IsInstanceOf(
1256
2.07k
           TableFactory::kPlainTableName()))) {
1257
2.07k
    status = VerifyOutputRecordCount();
1258
2.07k
    if (!status.ok()) {
1259
0
      return status;
1260
0
    }
1261
2.07k
  }
1262
2.07k
  return status;
1263
2.07k
}
1264
1265
void CompactionJob::FinalizeCompactionRun(
1266
    const Status& input_status, bool stats_built_from_input_table_prop,
1267
2.77k
    uint64_t num_input_range_del) {
1268
2.77k
  if (stats_built_from_input_table_prop) {
1269
2.77k
    UpdateCompactionJobInputStatsFromInternalStats(internal_stats_,
1270
2.77k
                                                   num_input_range_del);
1271
2.77k
  }
1272
2.77k
  UpdateCompactionJobOutputStatsFromInternalStats(input_status,
1273
2.77k
                                                  internal_stats_);
1274
2.77k
  RecordCompactionIOStats();
1275
1276
2.77k
  LogFlush(db_options_.info_log);
1277
2.77k
  TEST_SYNC_POINT("CompactionJob::Run():End");
1278
2.77k
  compact_->status = input_status;
1279
2.77k
  TEST_SYNC_POINT_CALLBACK("CompactionJob::Run():EndStatusSet",
1280
2.77k
                           const_cast<Status*>(&input_status));
1281
2.77k
}
1282
1283
2.77k
Status CompactionJob::Run() {
1284
2.77k
  InitializeCompactionRun();
1285
1286
2.77k
  const uint64_t start_micros = db_options_.clock->NowMicros();
1287
1288
2.77k
  RunSubcompactions();
1289
1290
2.77k
  UpdateTimingStats(start_micros);
1291
1292
2.77k
  TEST_SYNC_POINT("CompactionJob::Run:BeforeVerify");
1293
1294
2.77k
  Status status = CollectSubcompactionErrors();
1295
1296
  // If compaction was aborted or manually paused, clean up any output files
1297
  // from completed subcompactions to prevent orphaned files on disk.
1298
  // Skip cleanup for resumable compaction (when progress writer is set)
1299
  // because the output files are needed for resumption.
1300
2.77k
  if ((status.IsCompactionAborted() || status.IsManualCompactionPaused()) &&
1301
0
      compaction_progress_writer_ == nullptr) {
1302
0
    CleanupAbortedSubcompactions();
1303
0
  }
1304
1305
2.77k
  if (status.ok()) {
1306
2.07k
    status = SyncOutputDirectories();
1307
2.07k
  }
1308
1309
2.77k
  if (status.ok()) {
1310
2.07k
    status = VerifyOutputFiles();
1311
2.07k
  }
1312
1313
2.77k
  TEST_SYNC_POINT_CALLBACK("CompactionJob::Run():AfterVerifyOutputFiles",
1314
2.77k
                           &status);
1315
1316
2.77k
  if (status.ok()) {
1317
2.07k
    SetOutputTableProperties();
1318
2.07k
  }
1319
1320
2.77k
  AggregateSubcompactionOutputAndJobStats();
1321
1322
2.77k
  uint64_t num_input_range_del = 0;
1323
2.77k
  bool stats_built_from_input_table_prop =
1324
2.77k
      UpdateInternalStatsFromInputFiles(&num_input_range_del);
1325
1326
2.77k
  if (status.ok()) {
1327
2.07k
    status = VerifyCompactionRecordCounts(stats_built_from_input_table_prop,
1328
2.07k
                                          num_input_range_del);
1329
2.07k
  }
1330
1331
2.77k
  FinalizeCompactionRun(status, stats_built_from_input_table_prop,
1332
2.77k
                        num_input_range_del);
1333
1334
2.77k
  return status;
1335
2.77k
}
1336
1337
2.77k
Status CompactionJob::Install(bool* compaction_released) {
1338
2.77k
  assert(compact_);
1339
1340
2.77k
  AutoThreadOperationStageUpdater stage_updater(
1341
2.77k
      ThreadStatus::STAGE_COMPACTION_INSTALL);
1342
2.77k
  db_mutex_->AssertHeld();
1343
2.77k
  Status status = compact_->status;
1344
1345
2.77k
  ColumnFamilyData* cfd = compact_->compaction->column_family_data();
1346
2.77k
  assert(cfd);
1347
1348
2.77k
  int output_level = compact_->compaction->output_level();
1349
2.77k
  cfd->internal_stats()->AddCompactionStats(output_level, thread_pri_,
1350
2.77k
                                            internal_stats_);
1351
1352
2.77k
  if (status.ok()) {
1353
2.07k
    status = InstallCompactionResults(compaction_released);
1354
2.07k
  }
1355
2.77k
  if (!versions_->io_status().ok()) {
1356
0
    io_status_ = versions_->io_status();
1357
0
  }
1358
1359
2.77k
  VersionStorageInfo::LevelSummaryStorage tmp;
1360
2.77k
  auto vstorage = cfd->current()->storage_info();
1361
2.77k
  const auto& stats = internal_stats_.output_level_stats;
1362
1363
2.77k
  double read_write_amp = 0.0;
1364
2.77k
  double write_amp = 0.0;
1365
2.77k
  double bytes_read_per_sec = 0;
1366
2.77k
  double bytes_written_per_sec = 0;
1367
1368
2.77k
  const uint64_t bytes_read_non_output_and_blob =
1369
2.77k
      stats.bytes_read_non_output_levels + stats.bytes_read_blob;
1370
2.77k
  const uint64_t bytes_read_all =
1371
2.77k
      stats.bytes_read_output_level + bytes_read_non_output_and_blob;
1372
2.77k
  const uint64_t bytes_written_all =
1373
2.77k
      stats.bytes_written + stats.bytes_written_blob;
1374
1375
2.77k
  if (bytes_read_non_output_and_blob > 0) {
1376
1.99k
    read_write_amp = (bytes_written_all + bytes_read_all) /
1377
1.99k
                     static_cast<double>(bytes_read_non_output_and_blob);
1378
1.99k
    write_amp =
1379
1.99k
        bytes_written_all / static_cast<double>(bytes_read_non_output_and_blob);
1380
1.99k
  }
1381
2.77k
  if (stats.micros > 0) {
1382
2.77k
    bytes_read_per_sec = bytes_read_all / static_cast<double>(stats.micros);
1383
2.77k
    bytes_written_per_sec =
1384
2.77k
        bytes_written_all / static_cast<double>(stats.micros);
1385
2.77k
  }
1386
1387
2.77k
  const std::string& column_family_name = cfd->GetName();
1388
1389
2.77k
  constexpr double kMB = 1048576.0;
1390
1391
2.77k
  ROCKS_LOG_BUFFER(
1392
2.77k
      log_buffer_,
1393
2.77k
      "[%s] compacted to: %s, MB/sec: %.1f rd, %.1f wr, level %d, "
1394
2.77k
      "files in(%d, %d) filtered(%d, %d) out(%d +%d blob) "
1395
2.77k
      "MB in(%.1f, %.1f +%.1f blob) filtered(%.1f, %.1f) out(%.1f +%.1f blob), "
1396
2.77k
      "read-write-amplify(%.1f) write-amplify(%.1f) %s, records in: %" PRIu64
1397
2.77k
      ", records dropped: %" PRIu64 " output_compression: %s\n",
1398
2.77k
      column_family_name.c_str(), vstorage->LevelSummary(&tmp),
1399
2.77k
      bytes_read_per_sec, bytes_written_per_sec,
1400
2.77k
      compact_->compaction->output_level(),
1401
2.77k
      stats.num_input_files_in_non_output_levels,
1402
2.77k
      stats.num_input_files_in_output_level,
1403
2.77k
      stats.num_filtered_input_files_in_non_output_levels,
1404
2.77k
      stats.num_filtered_input_files_in_output_level, stats.num_output_files,
1405
2.77k
      stats.num_output_files_blob, stats.bytes_read_non_output_levels / kMB,
1406
2.77k
      stats.bytes_read_output_level / kMB, stats.bytes_read_blob / kMB,
1407
2.77k
      stats.bytes_skipped_non_output_levels / kMB,
1408
2.77k
      stats.bytes_skipped_output_level / kMB, stats.bytes_written / kMB,
1409
2.77k
      stats.bytes_written_blob / kMB, read_write_amp, write_amp,
1410
2.77k
      status.ToString().c_str(), stats.num_input_records,
1411
2.77k
      stats.num_dropped_records,
1412
2.77k
      CompressionTypeToString(compact_->compaction->output_compression())
1413
2.77k
          .c_str());
1414
1415
2.77k
  const auto& blob_files = vstorage->GetBlobFiles();
1416
2.77k
  if (!blob_files.empty()) {
1417
0
    assert(blob_files.front());
1418
0
    assert(blob_files.back());
1419
1420
0
    ROCKS_LOG_BUFFER(
1421
0
        log_buffer_,
1422
0
        "[%s] Blob file summary: head=%" PRIu64 ", tail=%" PRIu64 "\n",
1423
0
        column_family_name.c_str(), blob_files.front()->GetBlobFileNumber(),
1424
0
        blob_files.back()->GetBlobFileNumber());
1425
0
  }
1426
1427
2.77k
  if (internal_stats_.has_proximal_level_output) {
1428
0
    ROCKS_LOG_BUFFER(log_buffer_,
1429
0
                     "[%s] has Proximal Level output: %" PRIu64
1430
0
                     ", level %d, number of files: %" PRIu64
1431
0
                     ", number of records: %" PRIu64,
1432
0
                     column_family_name.c_str(),
1433
0
                     internal_stats_.proximal_level_stats.bytes_written,
1434
0
                     compact_->compaction->GetProximalLevel(),
1435
0
                     internal_stats_.proximal_level_stats.num_output_files,
1436
0
                     internal_stats_.proximal_level_stats.num_output_records);
1437
0
  }
1438
1439
2.77k
  TEST_SYNC_POINT_CALLBACK(
1440
2.77k
      "CompactionJob::Install:AfterUpdateCompactionJobStats", job_stats_);
1441
1442
2.77k
  auto stream = event_logger_->LogToBuffer(log_buffer_, 8192);
1443
2.77k
  stream << "job" << job_id_ << "event" << "compaction_finished"
1444
2.77k
         << "compaction_time_micros" << stats.micros
1445
2.77k
         << "compaction_time_cpu_micros" << stats.cpu_micros << "output_level"
1446
2.77k
         << compact_->compaction->output_level() << "num_output_files"
1447
2.77k
         << stats.num_output_files << "total_output_size"
1448
2.77k
         << stats.bytes_written;
1449
1450
2.77k
  if (stats.num_output_files_blob > 0) {
1451
0
    stream << "num_blob_output_files" << stats.num_output_files_blob
1452
0
           << "total_blob_output_size" << stats.bytes_written_blob;
1453
0
  }
1454
1455
2.77k
  stream << "num_input_records" << stats.num_input_records
1456
2.77k
         << "num_output_records" << stats.num_output_records
1457
2.77k
         << "num_subcompactions" << compact_->sub_compact_states.size()
1458
2.77k
         << "output_compression"
1459
2.77k
         << CompressionTypeToString(compact_->compaction->output_compression());
1460
1461
2.77k
  stream << "num_single_delete_mismatches"
1462
2.77k
         << job_stats_->num_single_del_mismatch;
1463
2.77k
  stream << "num_single_delete_fallthrough"
1464
2.77k
         << job_stats_->num_single_del_fallthru;
1465
1466
2.77k
  if (measure_io_stats_) {
1467
0
    stream << "file_write_nanos" << job_stats_->file_write_nanos;
1468
0
    stream << "file_range_sync_nanos" << job_stats_->file_range_sync_nanos;
1469
0
    stream << "file_fsync_nanos" << job_stats_->file_fsync_nanos;
1470
0
    stream << "file_prepare_write_nanos"
1471
0
           << job_stats_->file_prepare_write_nanos;
1472
0
  }
1473
1474
2.77k
  stream << "lsm_state";
1475
2.77k
  stream.StartArray();
1476
22.1k
  for (int level = 0; level < vstorage->num_levels(); ++level) {
1477
19.4k
    stream << vstorage->NumLevelFiles(level);
1478
19.4k
  }
1479
2.77k
  stream.EndArray();
1480
1481
2.77k
  if (!blob_files.empty()) {
1482
0
    assert(blob_files.front());
1483
0
    stream << "blob_file_head" << blob_files.front()->GetBlobFileNumber();
1484
1485
0
    assert(blob_files.back());
1486
0
    stream << "blob_file_tail" << blob_files.back()->GetBlobFileNumber();
1487
0
  }
1488
1489
2.77k
  if (internal_stats_.has_proximal_level_output) {
1490
0
    InternalStats::CompactionStats& pl_stats =
1491
0
        internal_stats_.proximal_level_stats;
1492
0
    stream << "proximal_level_num_output_files" << pl_stats.num_output_files;
1493
0
    stream << "proximal_level_bytes_written" << pl_stats.bytes_written;
1494
0
    stream << "proximal_level_num_output_records"
1495
0
           << pl_stats.num_output_records;
1496
0
    stream << "proximal_level_num_output_files_blob"
1497
0
           << pl_stats.num_output_files_blob;
1498
0
    stream << "proximal_level_bytes_written_blob"
1499
0
           << pl_stats.bytes_written_blob;
1500
0
  }
1501
1502
  // Propagate Install failure to compact_->status so that
1503
  // CleanupCompaction() -> SubcompactionState::Cleanup() sees the failure and
1504
  // calls ReleaseObsolete on output files' table cache entries. Without this,
1505
  // if Run() succeeds but InstallCompactionResults() fails, Cleanup would see
1506
  // overall_status = OK and skip ReleaseObsolete, leaking entries for output
1507
  // files that were never installed into any Version.
1508
2.77k
  if (!status.ok() && compact_->status.ok()) {
1509
0
    compact_->status = status;
1510
0
  }
1511
1512
2.77k
  CleanupCompaction();
1513
2.77k
  return status;
1514
2.77k
}
1515
1516
void CompactionJob::NotifyOnSubcompactionBegin(
1517
2.77k
    SubcompactionState* sub_compact) {
1518
2.77k
  Compaction* c = compact_->compaction;
1519
1520
2.77k
  if (db_options_.listeners.empty()) {
1521
2.77k
    return;
1522
2.77k
  }
1523
0
  if (shutting_down_->load(std::memory_order_acquire)) {
1524
0
    return;
1525
0
  }
1526
0
  if (c->is_manual_compaction() &&
1527
0
      manual_compaction_canceled_.load(std::memory_order_acquire)) {
1528
0
    return;
1529
0
  }
1530
1531
0
  sub_compact->notify_on_subcompaction_completion = true;
1532
1533
0
  SubcompactionJobInfo info{};
1534
0
  sub_compact->BuildSubcompactionJobInfo(info);
1535
0
  info.job_id = static_cast<int>(job_id_);
1536
0
  info.separate_remote_compaction_pool_selected =
1537
0
      compact_->compaction->separate_remote_compaction_pool_selected();
1538
0
  info.thread_id = env_->GetThreadID();
1539
1540
0
  for (const auto& listener : db_options_.listeners) {
1541
0
    listener->OnSubcompactionBegin(info);
1542
0
  }
1543
0
  info.status.PermitUncheckedError();
1544
0
}
1545
1546
void CompactionJob::NotifyOnSubcompactionCompleted(
1547
2.77k
    SubcompactionState* sub_compact) {
1548
2.77k
  if (db_options_.listeners.empty()) {
1549
2.77k
    return;
1550
2.77k
  }
1551
0
  if (shutting_down_->load(std::memory_order_acquire)) {
1552
0
    return;
1553
0
  }
1554
1555
0
  if (sub_compact->notify_on_subcompaction_completion == false) {
1556
0
    return;
1557
0
  }
1558
0
  sub_compact->notify_on_subcompaction_completion = false;
1559
1560
0
  SubcompactionJobInfo info{};
1561
0
  sub_compact->BuildSubcompactionJobInfo(info);
1562
0
  info.job_id = static_cast<int>(job_id_);
1563
0
  info.separate_remote_compaction_pool_selected =
1564
0
      compact_->compaction->separate_remote_compaction_pool_selected();
1565
0
  info.thread_id = env_->GetThreadID();
1566
1567
0
  for (const auto& listener : db_options_.listeners) {
1568
0
    listener->OnSubcompactionCompleted(info);
1569
0
  }
1570
0
}
1571
1572
2.77k
bool CompactionJob::ShouldUseLocalCompaction(SubcompactionState* sub_compact) {
1573
2.77k
  if (sub_compact->compaction->RepairsMissingBlobReferences()) {
1574
    // Remote workers do not receive skip_missing_blob_files, and result
1575
    // validation rejects garbage updates for blob files absent from the
1576
    // primary's input Version, which repair produces. Keep repair local until
1577
    // the service supports this policy.
1578
0
    sub_compact->compaction_job_stats.is_remote_compaction = false;
1579
0
    return true;
1580
0
  }
1581
2.77k
  if (db_options_.compaction_service) {
1582
0
    const ImmutableCFOptions& options =
1583
0
        sub_compact->compaction->column_family_data()->ioptions();
1584
0
    const CompactionFilterFactory* factory = nullptr;
1585
0
    if (options.compaction_filter == nullptr &&
1586
0
        options.compaction_filter_factory &&
1587
0
        options.compaction_filter_factory->ShouldFilterTableFileCreation(
1588
0
            TableFileCreationReason::kCompaction)) {
1589
0
      factory = options.compaction_filter_factory.get();
1590
0
    }
1591
0
    CompactionFilter::MergeOrder order;
1592
0
    Status s = GetCompactionFilterMergeOrder(
1593
0
        options.compaction_filter, factory,
1594
0
        TableFileCreationReason::kCompaction, &order);
1595
0
    if (!s.ok()) {
1596
0
      sub_compact->status = s;
1597
0
      return false;
1598
0
    }
1599
0
    if (order == CompactionFilter::MergeOrder::kMergeBeforeFilter &&
1600
0
        !db_options_.compaction_service->SupportsMergeBeforeFilter()) {
1601
0
      TEST_SYNC_POINT(
1602
0
          "CompactionJob::ShouldUseLocalCompaction:MergeBeforeFilterLocal");
1603
0
      sub_compact->compaction_job_stats.is_remote_compaction = false;
1604
0
      return true;
1605
0
    }
1606
0
    CompactionServiceJobStatus comp_status =
1607
0
        ProcessKeyValueCompactionWithCompactionService(sub_compact, order);
1608
0
    if (comp_status != CompactionServiceJobStatus::kUseLocal) {
1609
0
      return false;
1610
0
    }
1611
    // fallback to local compaction
1612
0
    assert(comp_status == CompactionServiceJobStatus::kUseLocal);
1613
0
    sub_compact->compaction_job_stats.is_remote_compaction = false;
1614
0
  }
1615
2.77k
  return true;
1616
2.77k
}
1617
1618
2.77k
CompactionJob::CompactionIOStatsSnapshot CompactionJob::InitializeIOStats() {
1619
2.77k
  CompactionIOStatsSnapshot io_stats;
1620
1621
2.77k
  if (measure_io_stats_) {
1622
0
    io_stats.prev_perf_level = GetPerfLevel();
1623
0
    SetPerfLevel(PerfLevel::kEnableTimeAndCPUTimeExceptForMutex);
1624
0
    io_stats.prev_write_nanos = IOSTATS(write_nanos);
1625
0
    io_stats.prev_fsync_nanos = IOSTATS(fsync_nanos);
1626
0
    io_stats.prev_range_sync_nanos = IOSTATS(range_sync_nanos);
1627
0
    io_stats.prev_prepare_write_nanos = IOSTATS(prepare_write_nanos);
1628
0
    io_stats.prev_cpu_write_nanos = IOSTATS(cpu_write_nanos);
1629
0
    io_stats.prev_cpu_read_nanos = IOSTATS(cpu_read_nanos);
1630
0
  }
1631
1632
2.77k
  return io_stats;
1633
2.77k
}
1634
1635
Status CompactionJob::SetupAndValidateCompactionFilter(
1636
    SubcompactionState* sub_compact,
1637
    const CompactionFilter* configured_compaction_filter,
1638
    const CompactionFilter*& compaction_filter,
1639
    std::unique_ptr<CompactionFilter>& compaction_filter_from_factory,
1640
2.77k
    CompactionFilter::MergeOrder& merge_order) {
1641
2.77k
  compaction_filter = configured_compaction_filter;
1642
1643
2.77k
  if (compaction_filter == nullptr) {
1644
2.77k
    compaction_filter_from_factory =
1645
2.77k
        sub_compact->compaction->CreateCompactionFilter();
1646
2.77k
    compaction_filter = compaction_filter_from_factory.get();
1647
2.77k
  }
1648
1649
2.77k
  const CompactionFilterFactory* factory =
1650
2.77k
      configured_compaction_filter == nullptr
1651
2.77k
          ? sub_compact->compaction->column_family_data()
1652
2.77k
                ->ioptions()
1653
2.77k
                .compaction_filter_factory.get()
1654
2.77k
          : nullptr;
1655
2.77k
  Status s = GetCompactionFilterMergeOrder(compaction_filter, factory,
1656
2.77k
                                           TableFileCreationReason::kCompaction,
1657
2.77k
                                           &merge_order);
1658
2.77k
  if (!s.ok()) {
1659
0
    return s;
1660
0
  }
1661
2.77k
  if (compaction_filter == nullptr) {
1662
2.77k
    merge_order = CompactionFilter::MergeOrder::kFilterBeforeMerge;
1663
2.77k
  }
1664
2.77k
  if (compaction_filter &&
1665
0
      merge_order == CompactionFilter::MergeOrder::kMergeBeforeFilter &&
1666
0
      (job_context_->snapshot_checker != nullptr ||
1667
0
       sub_compact->compaction->column_family_data()
1668
0
               ->user_comparator()
1669
0
               ->timestamp_size() != 0 ||
1670
0
       compaction_filter->IsStackedBlobDbInternalCompactionFilter())) {
1671
0
    return Status::NotSupported(
1672
0
        "Merge-before-filter does not support SnapshotChecker, timestamps, "
1673
0
        "or stacked BlobDB");
1674
0
  }
1675
1676
2.77k
  if (compaction_filter != nullptr && !compaction_filter->IgnoreSnapshots()) {
1677
0
    return Status::NotSupported(
1678
0
        "CompactionFilter::IgnoreSnapshots() = false is not supported "
1679
0
        "anymore.");
1680
0
  }
1681
1682
2.77k
  return ValidateCompactionFilterMergeOrder(compaction_filter, merge_order);
1683
2.77k
}
1684
1685
void CompactionJob::InitializeReadOptionsAndBoundaries(
1686
    const size_t ts_sz, ReadOptions& read_options,
1687
2.77k
    SubcompactionKeyBoundaries& boundaries) {
1688
2.77k
  read_options.verify_checksums = true;
1689
2.77k
  read_options.fill_cache = false;
1690
2.77k
  read_options.rate_limiter_priority = GetRateLimiterPriority();
1691
2.77k
  read_options.io_activity = Env::IOActivity::kCompaction;
1692
  // Compaction iterators shouldn't be confined to a single prefix.
1693
  // Compactions use Seek() for
1694
  // (a) concurrent compactions,
1695
  // (b) CompactionFilter::Decision::kRemoveAndSkipUntil.
1696
2.77k
  read_options.total_order_seek = true;
1697
1698
  // Remove the timestamps from boundaries because boundaries created in
1699
  // GenSubcompactionBoundaries doesn't strip away the timestamp.
1700
2.77k
  if (boundaries.start.has_value()) {
1701
0
    read_options.iterate_lower_bound = &(*boundaries.start);
1702
0
    if (ts_sz > 0) {
1703
0
      boundaries.start_without_ts =
1704
0
          StripTimestampFromUserKey(*boundaries.start, ts_sz);
1705
0
      read_options.iterate_lower_bound = &(*boundaries.start_without_ts);
1706
0
    }
1707
0
  }
1708
2.77k
  if (boundaries.end.has_value()) {
1709
0
    read_options.iterate_upper_bound = &(*boundaries.end);
1710
0
    if (ts_sz > 0) {
1711
0
      boundaries.end_without_ts =
1712
0
          StripTimestampFromUserKey(*boundaries.end, ts_sz);
1713
0
      read_options.iterate_upper_bound = &(*boundaries.end_without_ts);
1714
0
    }
1715
0
  }
1716
1717
2.77k
  if (ts_sz > 0) {
1718
0
    if (ts_sz <= strlen(boundaries.kMaxTs)) {
1719
0
      boundaries.ts_slice = Slice(boundaries.kMaxTs, ts_sz);
1720
0
    } else {
1721
0
      boundaries.max_ts = std::string(ts_sz, '\xff');
1722
0
      boundaries.ts_slice = Slice(boundaries.max_ts);
1723
0
    }
1724
0
  }
1725
2.77k
  if (boundaries.start.has_value()) {
1726
0
    boundaries.start_ikey.SetInternalKey(*boundaries.start, kMaxSequenceNumber,
1727
0
                                         kValueTypeForSeek);
1728
0
    if (ts_sz > 0) {
1729
0
      boundaries.start_ikey.UpdateInternalKey(
1730
0
          kMaxSequenceNumber, kValueTypeForSeek, &boundaries.ts_slice);
1731
0
    }
1732
0
    boundaries.start_internal_key = boundaries.start_ikey.GetInternalKey();
1733
0
    boundaries.start_user_key = boundaries.start_ikey.GetUserKey();
1734
0
  }
1735
2.77k
  if (boundaries.end.has_value()) {
1736
0
    boundaries.end_ikey.SetInternalKey(*boundaries.end, kMaxSequenceNumber,
1737
0
                                       kValueTypeForSeek);
1738
0
    if (ts_sz > 0) {
1739
0
      boundaries.end_ikey.UpdateInternalKey(
1740
0
          kMaxSequenceNumber, kValueTypeForSeek, &boundaries.ts_slice);
1741
0
    }
1742
0
    boundaries.end_internal_key = boundaries.end_ikey.GetInternalKey();
1743
0
    boundaries.end_user_key = boundaries.end_ikey.GetUserKey();
1744
0
  }
1745
2.77k
}
1746
1747
InternalIterator* CompactionJob::CreateInputIterator(
1748
    SubcompactionState* sub_compact, ColumnFamilyData* cfd,
1749
    SubcompactionInternalIterators& iterators,
1750
2.77k
    SubcompactionKeyBoundaries& boundaries, ReadOptions& read_options) {
1751
2.77k
  const size_t ts_sz = cfd->user_comparator()->timestamp_size();
1752
2.77k
  InitializeReadOptionsAndBoundaries(ts_sz, read_options, boundaries);
1753
1754
  // This is assigned after creation of SubcompactionState to simplify that
1755
  // creation across both CompactionJob and CompactionServiceCompactionJob
1756
2.77k
  sub_compact->AssignRangeDelAggregator(
1757
2.77k
      std::make_unique<CompactionRangeDelAggregator>(
1758
2.77k
          &cfd->internal_comparator(), job_context_->snapshot_seqs,
1759
2.77k
          &full_history_ts_low_, &trim_ts_));
1760
1761
  // Although the v2 aggregator is what the level iterator(s) know about,
1762
  // the AddTombstones calls will be propagated down to the v1 aggregator.
1763
2.77k
  const bool open_ephemeral_table_reader =
1764
2.77k
      db_options_.use_direct_io_for_compaction_reads &&
1765
0
      !db_options_.use_direct_reads;
1766
2.77k
  FileOptions& input_file_options =
1767
2.77k
      open_ephemeral_table_reader ? file_options_for_compaction_input_read_
1768
2.77k
                                  : file_options_for_read_;
1769
2.77k
  TEST_SYNC_POINT_CALLBACK(
1770
2.77k
      "CompactionJob::CreateInputIterator:InputFileOptions",
1771
2.77k
      &input_file_options);
1772
2.77k
  iterators.raw_input =
1773
2.77k
      std::unique_ptr<InternalIterator>(versions_->MakeInputIterator(
1774
2.77k
          read_options, sub_compact->compaction, sub_compact->RangeDelAgg(),
1775
2.77k
          input_file_options, boundaries.start, boundaries.end,
1776
2.77k
          open_ephemeral_table_reader));
1777
2.77k
  InternalIterator* input = iterators.raw_input.get();
1778
1779
2.77k
  if (boundaries.start.has_value() || boundaries.end.has_value()) {
1780
0
    iterators.clip = std::make_unique<ClippingIterator>(
1781
0
        iterators.raw_input.get(),
1782
0
        boundaries.start.has_value() ? &boundaries.start_internal_key : nullptr,
1783
0
        boundaries.end.has_value() ? &boundaries.end_internal_key : nullptr,
1784
0
        &cfd->internal_comparator());
1785
0
    input = iterators.clip.get();
1786
0
  }
1787
1788
2.77k
  if (sub_compact->compaction->DoesInputReferenceBlobFiles() ||
1789
2.77k
      sub_compact->compaction->RepairsMissingBlobReferences()) {
1790
0
    BlobGarbageMeter* meter = sub_compact->Current().GetBlobGarbageMeter();
1791
0
    if (meter == nullptr) {
1792
0
      meter = sub_compact->Current().CreateBlobGarbageMeter();
1793
0
    }
1794
0
    iterators.blob_counter = std::make_unique<BlobCountingIterator>(
1795
0
        input, meter, compaction_progress_writer_ != nullptr);
1796
0
    input = iterators.blob_counter.get();
1797
0
  }
1798
1799
2.77k
  if (ts_sz > 0 && !trim_ts_.empty()) {
1800
0
    iterators.trim_history_iter = std::make_unique<HistoryTrimmingIterator>(
1801
0
        input, cfd->user_comparator(), trim_ts_);
1802
0
    input = iterators.trim_history_iter.get();
1803
0
  }
1804
1805
2.77k
  if (sub_compact->compaction->RepairsMissingBlobReferences()) {
1806
0
    iterators.missing_blob_repair = std::make_unique<MissingBlobRepairIterator>(
1807
0
        input, sub_compact->compaction->input_version()->storage_info(), ts_sz,
1808
        /*only_dropped_blob_files=*/
1809
0
        !sub_compact->compaction->skip_missing_blob_files());
1810
0
    input = iterators.missing_blob_repair.get();
1811
0
  }
1812
1813
2.77k
  return input;
1814
2.77k
}
1815
1816
void CompactionJob::CreateBlobFileBuilder(
1817
    SubcompactionState* sub_compact, ColumnFamilyData* cfd,
1818
    std::unique_ptr<BlobFileBuilder>& blob_file_builder,
1819
2.77k
    const WriteOptions& write_options) {
1820
2.77k
  const auto& mutable_cf_options =
1821
2.77k
      sub_compact->compaction->mutable_cf_options();
1822
1823
  // TODO: BlobDB to support output_to_proximal_level compaction, which needs
1824
  //  2 builders, so may need to move to `CompactionOutputs`
1825
2.77k
  if (mutable_cf_options.enable_blob_files &&
1826
0
      sub_compact->compaction->output_level() >=
1827
0
          mutable_cf_options.blob_file_starting_level) {
1828
0
    blob_file_builder = std::make_unique<BlobFileBuilder>(
1829
0
        [this]() { return NewBlobFileNumber(); }, fs_.get(),
1830
0
        &sub_compact->compaction->immutable_options(), &mutable_cf_options,
1831
0
        &file_options_, &write_options, db_id_, db_session_id_, job_id_,
1832
0
        cfd->GetID(), cfd->GetName(), write_hint_, io_tracer_, blob_callback_,
1833
0
        BlobFileCreationReason::kCompaction, GetBlobOutputPath(),
1834
0
        sub_compact->Current().GetOutputFilePathsPtr(),
1835
0
        sub_compact->Current().GetBlobFileAdditionsPtr());
1836
2.77k
  } else {
1837
2.77k
    blob_file_builder = nullptr;
1838
2.77k
  }
1839
2.77k
}
1840
1841
std::unique_ptr<CompactionIterator> CompactionJob::CreateCompactionIterator(
1842
    SubcompactionState* sub_compact, ColumnFamilyData* cfd,
1843
    InternalIterator* input, const CompactionFilter* compaction_filter,
1844
    MergeHelper& merge, std::unique_ptr<BlobFileBuilder>& blob_file_builder,
1845
2.77k
    const WriteOptions& write_options) {
1846
2.77k
  CreateBlobFileBuilder(sub_compact, cfd, blob_file_builder, write_options);
1847
1848
2.77k
  const std::string* const full_history_ts_low =
1849
2.77k
      full_history_ts_low_.empty() ? nullptr : &full_history_ts_low_;
1850
2.77k
  assert(job_context_);
1851
1852
2.77k
  return std::make_unique<CompactionIterator>(
1853
2.77k
      input, cfd->user_comparator(), &merge, versions_->LastSequence(),
1854
2.77k
      &(job_context_->snapshot_seqs), earliest_snapshot_,
1855
2.77k
      job_context_->earliest_write_conflict_snapshot,
1856
2.77k
      job_context_->GetJobSnapshotSequence(), job_context_->snapshot_checker,
1857
2.77k
      env_, ShouldReportDetailedTime(env_, stats_), sub_compact->RangeDelAgg(),
1858
2.77k
      blob_file_builder.get(), db_options_.allow_data_in_errors,
1859
2.77k
      db_options_.enforce_single_del_contracts, manual_compaction_canceled_,
1860
2.77k
      sub_compact->compaction->DoesInputReferenceBlobFiles() ||
1861
2.77k
          sub_compact->compaction
1862
2.77k
              ->RepairsMissingBlobReferences() /* must_count_input_entries */,
1863
2.77k
      sub_compact->compaction, compaction_filter, shutting_down_,
1864
2.77k
      db_options_.info_log, full_history_ts_low, preserve_seqno_after_,
1865
2.77k
      nullptr /* input_version */, Env::IOActivity::kCompaction,
1866
2.77k
      GetBlobGarbageCollectionCutoffFileNumber());
1867
2.77k
}
1868
1869
std::pair<CompactionFileOpenFunc, CompactionFileCloseFunc>
1870
CompactionJob::CreateFileHandlers(SubcompactionState* sub_compact,
1871
                                  SubcompactionKeyBoundaries& boundaries,
1872
                                  BlobFileBuilder* blob_file_builder,
1873
2.77k
                                  const BlobCountingIterator* blob_counter) {
1874
2.77k
  const CompactionFileOpenFunc open_file_func =
1875
2.77k
      [this, sub_compact](CompactionOutputs& outputs) {
1876
1.69k
        return this->OpenCompactionOutputFile(sub_compact, outputs);
1877
1.69k
      };
1878
1879
2.77k
  const Slice* start_user_key =
1880
2.77k
      sub_compact->start.has_value() ? &boundaries.start_user_key : nullptr;
1881
2.77k
  const Slice* end_user_key =
1882
2.77k
      sub_compact->end.has_value() ? &boundaries.end_user_key : nullptr;
1883
1884
2.77k
  const CompactionFileCloseFunc close_file_func =
1885
2.77k
      [this, sub_compact, start_user_key, end_user_key, blob_file_builder,
1886
2.77k
       blob_counter](const Status& status,
1887
2.77k
                     const ParsedInternalKey& prev_iter_output_internal_key,
1888
2.77k
                     const Slice& next_table_min_key,
1889
2.77k
                     const CompactionIterator* c_iter, bool post_prepare_retry,
1890
2.77k
                     CompactionOutputs& outputs) {
1891
1.69k
        return this->FinishCompactionOutputFile(
1892
1.69k
            status, prev_iter_output_internal_key, next_table_min_key,
1893
1.69k
            start_user_key, end_user_key, c_iter, post_prepare_retry,
1894
1.69k
            sub_compact, blob_file_builder, blob_counter, outputs);
1895
1.69k
      };
1896
1897
2.77k
  return {open_file_func, close_file_func};
1898
2.77k
}
1899
1900
Status CompactionJob::ProcessKeyValue(
1901
    SubcompactionState* sub_compact, ColumnFamilyData* cfd,
1902
    CompactionIterator* c_iter, const CompactionFileOpenFunc& open_file_func,
1903
    const CompactionFileCloseFunc& close_file_func,
1904
2.77k
    bool defer_blob_output_preparation, uint64_t& prev_cpu_micros) {
1905
  // Cron interval for periodic operations: stats update, abort check,
1906
  // and sync points. Uses 1024 (power of 2) for efficient bitwise check.
1907
2.77k
  const uint64_t kCronEveryMask = (1 << 10) - 1;
1908
2.77k
  [[maybe_unused]] const std::optional<const Slice> end = sub_compact->end;
1909
1910
  // Check for abort signal before starting key processing. The DB-wide
1911
  // (AbortAllCompactions) and per-CF (AbortCompactions) counters compose here;
1912
  // either aborts this job. See DB::AbortCompactions() in db.h for how the
1913
  // abort knobs relate to each other and to the other compaction controls.
1914
2.77k
  if (compaction_aborted_.load(std::memory_order_acquire) > 0 ||
1915
2.77k
      cfd->compaction_aborted() > 0) {
1916
0
    return Status::Incomplete(Status::SubCode::kCompactionAborted);
1917
0
  }
1918
1919
2.77k
  Status status;
1920
2.77k
  IterKey prev_iter_output_key;
1921
2.77k
  ParsedInternalKey prev_iter_output_internal_key;
1922
1923
2.77k
  TEST_SYNC_POINT_CALLBACK(
1924
2.77k
      "CompactionJob::ProcessKeyValueCompaction()::Processing",
1925
2.77k
      static_cast<void*>(const_cast<Compaction*>(sub_compact->compaction)));
1926
1927
4.66k
  while (status.ok() && !cfd->IsDropped() && c_iter->Valid() &&
1928
1.89k
         c_iter->status().ok()) {
1929
1.89k
    assert(!end.has_value() ||
1930
1.89k
           cfd->user_comparator()->Compare(c_iter->user_key(), *end) < 0);
1931
1932
1.89k
    const uint64_t num_records = c_iter->iter_stats().num_input_records;
1933
1934
    // Periodic cron operations: stats update, abort check.
1935
1.89k
    if ((num_records & kCronEveryMask) == kCronEveryMask) {
1936
      // Check for abort signal periodically
1937
0
      if (compaction_aborted_.load(std::memory_order_acquire) > 0 ||
1938
0
          cfd->compaction_aborted() > 0) {
1939
0
        status = Status::Incomplete(Status::SubCode::kCompactionAborted);
1940
0
        break;
1941
0
      }
1942
1943
0
      UpdateSubcompactionJobStatsIncrementally(
1944
0
          c_iter, &sub_compact->compaction_job_stats,
1945
0
          db_options_.clock->CPUMicros(), prev_cpu_micros);
1946
0
    }
1947
1948
1.89k
    const auto& ikey = c_iter->ikey();
1949
1.89k
    bool use_proximal_output = ikey.sequence > proximal_after_seqno_;
1950
1951
#ifndef NDEBUG
1952
    if (sub_compact->compaction->SupportsPerKeyPlacement()) {
1953
      PerKeyPlacementContext context(sub_compact->compaction->output_level(),
1954
                                     ikey.user_key, c_iter->value(),
1955
                                     ikey.sequence, use_proximal_output);
1956
      TEST_SYNC_POINT_CALLBACK("CompactionIterator::PrepareOutput.context",
1957
                               &context);
1958
      if (use_proximal_output) {
1959
        // Verify that entries sent to the proximal level are within the
1960
        // allowed range (because the input key range of the last level could
1961
        // be larger than the allowed output key range of the proximal
1962
        // level). This check uses user keys (ignores sequence numbers) because
1963
        // compaction boundaries are a "clean cut" between user keys (see
1964
        // CompactionPicker::ExpandInputsToCleanCut()), which is especially
1965
        // important when preferred sequence numbers has been swapped in for
1966
        // kTypeValuePreferredSeqno / TimedPut.
1967
        sub_compact->compaction->TEST_AssertWithinProximalLevelOutputRange(
1968
            c_iter->user_key());
1969
      }
1970
    } else {
1971
      assert(proximal_after_seqno_ == kMaxSequenceNumber);
1972
      assert(!use_proximal_output);
1973
    }
1974
#endif  // NDEBUG
1975
1976
    // Add current compaction_iterator key to target compaction output, if the
1977
    // output file needs to be close or open, it will call the `open_file_func`
1978
    // and `close_file_func`.
1979
    // TODO: it would be better to have the compaction file open/close moved
1980
    // into `CompactionOutputs` which has the output file information.
1981
1.89k
    status = sub_compact->AddToOutput(*c_iter, use_proximal_output,
1982
1.89k
                                      open_file_func, close_file_func,
1983
1.89k
                                      prev_iter_output_internal_key);
1984
1.89k
    if (!status.ok()) {
1985
0
      break;
1986
0
    }
1987
1.89k
    TEST_SYNC_POINT_CALLBACK("CompactionJob::Run():PausingManualCompaction:2",
1988
1.89k
                             static_cast<void*>(const_cast<std::atomic<bool>*>(
1989
1.89k
                                 &manual_compaction_canceled_)));
1990
1991
1.89k
    prev_iter_output_key.SetInternalKey(c_iter->key(),
1992
1.89k
                                        &prev_iter_output_internal_key);
1993
1.89k
    prev_iter_output_internal_key.sequence = ikey.sequence;
1994
1.89k
    prev_iter_output_internal_key.type = ikey.type;
1995
1.89k
    if (defer_blob_output_preparation) {
1996
0
      c_iter->NextWithoutPreparingOutput();
1997
1.89k
    } else {
1998
1.89k
      c_iter->Next();
1999
1.89k
    }
2000
2001
#ifndef NDEBUG
2002
    bool stop = false;
2003
    TEST_SYNC_POINT_CALLBACK("CompactionJob::ProcessKeyValueCompaction()::stop",
2004
                             static_cast<void*>(&stop));
2005
    if (stop) {
2006
      break;
2007
    }
2008
#endif  // NDEBUG
2009
1.89k
  }
2010
2011
2.77k
  return status;
2012
2.77k
}
2013
2014
void CompactionJob::UpdateSubcompactionJobStatsIncrementally(
2015
    CompactionIterator* c_iter, CompactionJobStats* compaction_job_stats,
2016
2.77k
    uint64_t cur_cpu_micros, uint64_t& prev_cpu_micros) {
2017
2.77k
  RecordDroppedKeys(c_iter->iter_stats(), compaction_job_stats);
2018
2.77k
  c_iter->ResetRecordCounts();
2019
2.77k
  RecordCompactionIOStats();
2020
2021
2.77k
  assert(cur_cpu_micros >= prev_cpu_micros);
2022
2.77k
  RecordTick(stats_, COMPACTION_CPU_TOTAL_TIME,
2023
2.77k
             cur_cpu_micros - prev_cpu_micros);
2024
2.77k
  prev_cpu_micros = cur_cpu_micros;
2025
2.77k
}
2026
2027
void CompactionJob::FinalizeSubcompactionJobStats(
2028
    SubcompactionState* sub_compact, CompactionIterator* c_iter,
2029
    uint64_t start_cpu_micros, uint64_t prev_cpu_micros,
2030
2.77k
    const CompactionIOStatsSnapshot& io_stats) {
2031
2.77k
  const CompactionIterationStats& c_iter_stats = c_iter->iter_stats();
2032
2033
2.77k
  assert(!sub_compact->compaction->DoesInputReferenceBlobFiles() ||
2034
2.77k
         c_iter->HasNumInputEntryScanned());
2035
2.77k
  sub_compact->compaction_job_stats.has_accurate_num_input_records &=
2036
2.77k
      c_iter->HasNumInputEntryScanned();
2037
2.77k
  sub_compact->compaction_job_stats.num_input_records +=
2038
2.77k
      c_iter->NumInputEntryScanned();
2039
2.77k
  sub_compact->compaction_job_stats.num_blobs_read =
2040
2.77k
      c_iter_stats.num_blobs_read;
2041
2.77k
  sub_compact->compaction_job_stats.total_blob_bytes_read =
2042
2.77k
      c_iter_stats.total_blob_bytes_read;
2043
2.77k
  sub_compact->compaction_job_stats.num_input_deletion_records =
2044
2.77k
      c_iter_stats.num_input_deletion_records;
2045
2.77k
  sub_compact->compaction_job_stats.num_corrupt_keys =
2046
2.77k
      c_iter_stats.num_input_corrupt_records;
2047
2.77k
  sub_compact->compaction_job_stats.num_single_del_fallthru =
2048
2.77k
      c_iter_stats.num_single_del_fallthru;
2049
2.77k
  sub_compact->compaction_job_stats.num_single_del_mismatch =
2050
2.77k
      c_iter_stats.num_single_del_mismatch;
2051
2.77k
  sub_compact->compaction_job_stats.total_input_raw_key_bytes +=
2052
2.77k
      c_iter_stats.total_input_raw_key_bytes;
2053
2.77k
  sub_compact->compaction_job_stats.total_input_raw_value_bytes +=
2054
2.77k
      c_iter_stats.total_input_raw_value_bytes;
2055
2056
2.77k
  RecordTick(stats_, FILTER_OPERATION_TOTAL_TIME,
2057
2.77k
             c_iter_stats.total_filter_time);
2058
2059
2.77k
  if (c_iter_stats.num_blobs_relocated > 0) {
2060
0
    RecordTick(stats_, BLOB_DB_GC_NUM_KEYS_RELOCATED,
2061
0
               c_iter_stats.num_blobs_relocated);
2062
0
  }
2063
2.77k
  if (c_iter_stats.total_blob_bytes_relocated > 0) {
2064
0
    RecordTick(stats_, BLOB_DB_GC_BYTES_RELOCATED,
2065
0
               c_iter_stats.total_blob_bytes_relocated);
2066
0
  }
2067
2068
2.77k
  uint64_t cur_cpu_micros = db_options_.clock->CPUMicros();
2069
2070
  // Record final compaction statistics including dropped keys, I/O stats,
2071
  // and CPU time delta from the last periodic measurement
2072
2.77k
  UpdateSubcompactionJobStatsIncrementally(c_iter,
2073
2.77k
                                           &sub_compact->compaction_job_stats,
2074
2.77k
                                           cur_cpu_micros, prev_cpu_micros);
2075
2076
  // Finalize timing and I/O statistics
2077
2.77k
  sub_compact->compaction_job_stats.cpu_micros =
2078
2.77k
      cur_cpu_micros - start_cpu_micros + sub_compact->GetWorkerCPUMicros();
2079
2080
2.77k
  if (measure_io_stats_) {
2081
0
    sub_compact->compaction_job_stats.file_write_nanos +=
2082
0
        IOSTATS(write_nanos) - io_stats.prev_write_nanos;
2083
0
    sub_compact->compaction_job_stats.file_fsync_nanos +=
2084
0
        IOSTATS(fsync_nanos) - io_stats.prev_fsync_nanos;
2085
0
    sub_compact->compaction_job_stats.file_range_sync_nanos +=
2086
0
        IOSTATS(range_sync_nanos) - io_stats.prev_range_sync_nanos;
2087
0
    sub_compact->compaction_job_stats.file_prepare_write_nanos +=
2088
0
        IOSTATS(prepare_write_nanos) - io_stats.prev_prepare_write_nanos;
2089
0
    sub_compact->compaction_job_stats.cpu_micros -=
2090
0
        (IOSTATS(cpu_write_nanos) - io_stats.prev_cpu_write_nanos +
2091
0
         IOSTATS(cpu_read_nanos) - io_stats.prev_cpu_read_nanos) /
2092
0
        1000;
2093
0
    if (io_stats.prev_perf_level !=
2094
0
        PerfLevel::kEnableTimeAndCPUTimeExceptForMutex) {
2095
0
      SetPerfLevel(io_stats.prev_perf_level);
2096
0
    }
2097
0
  }
2098
2.77k
}
2099
2100
Status CompactionJob::FinalizeProcessKeyValueStatus(
2101
    ColumnFamilyData* cfd, InternalIterator* input_iter,
2102
2.77k
    CompactionIterator* c_iter, Status status) {
2103
2.77k
  if (status.ok() && cfd->IsDropped()) {
2104
0
    status =
2105
0
        Status::ColumnFamilyDropped("Column family dropped during compaction");
2106
0
  }
2107
2.77k
  if (status.ok() && shutting_down_->load(std::memory_order_relaxed)) {
2108
700
    status = Status::ShutdownInProgress("Database shutdown");
2109
700
  }
2110
2.77k
  if (status.ok() &&
2111
2.07k
      (manual_compaction_canceled_.load(std::memory_order_relaxed))) {
2112
0
    status = Status::Incomplete(Status::SubCode::kManualCompactionPaused);
2113
0
  }
2114
2.77k
  if (status.ok()) {
2115
2.07k
    status = input_iter->status();
2116
2.07k
  }
2117
2.77k
  if (status.ok()) {
2118
2.07k
    status = c_iter->status();
2119
2.07k
  }
2120
2121
2.77k
  return status;
2122
2.77k
}
2123
2124
Status CompactionJob::CleanupCompactionFiles(
2125
    SubcompactionState* sub_compact, const Status& status,
2126
    const CompactionFileOpenFunc& open_file_func,
2127
2.77k
    const CompactionFileCloseFunc& close_file_func) {
2128
  // Call FinishCompactionOutputFile() even if status is not ok: it needs to
2129
  // close the output files. Open file function is also passed, in case there's
2130
  // only range-dels, no file was opened, to save the range-dels, it need to
2131
  // create a new output file.
2132
2.77k
  return sub_compact->CloseCompactionFiles(status, open_file_func,
2133
2.77k
                                           close_file_func);
2134
2.77k
}
2135
2136
Status CompactionJob::FinalizeBlobFiles(SubcompactionState* sub_compact,
2137
                                        BlobFileBuilder* blob_file_builder,
2138
2.77k
                                        Status status) {
2139
2.77k
  if (blob_file_builder) {
2140
0
    if (status.ok()) {
2141
0
      status = blob_file_builder->Finish();
2142
0
    } else {
2143
0
      blob_file_builder->Abandon(status);
2144
0
    }
2145
0
    sub_compact->Current().UpdateBlobStats();
2146
0
  }
2147
2148
2.77k
  return status;
2149
2.77k
}
2150
2151
2.77k
void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
2152
2.77k
  TEST_SYNC_POINT("CompactionJob::ProcessKeyValueCompaction:Start");
2153
2.77k
  assert(sub_compact);
2154
2.77k
  assert(sub_compact->compaction);
2155
2156
2.77k
  if (!ShouldUseLocalCompaction(sub_compact)) {
2157
0
    return;
2158
0
  }
2159
2.77k
  if (local_compaction_executor_) {
2160
0
    Status status = local_compaction_executor_(
2161
0
        [this, sub_compact] {
2162
0
          if (thread_pri_ != Env::Priority::USER) {
2163
0
            InitializeThreadStatus();
2164
0
          }
2165
0
          Defer reset_thread_status([this] {
2166
0
            if (thread_pri_ != Env::Priority::USER) {
2167
0
              ThreadStatusUtil::ResetThreadStatus();
2168
0
            }
2169
0
          });
2170
0
          try {
2171
0
            ProcessLocalKeyValueCompaction(sub_compact);
2172
0
            return;
2173
0
          } catch (const std::exception& e) {
2174
0
            sub_compact->status =
2175
0
                Status::Aborted("Local subcompaction exception", e.what());
2176
0
          } catch (...) {
2177
0
            sub_compact->status =
2178
0
                Status::Aborted("Unknown local subcompaction exception");
2179
0
          }
2180
0
          sub_compact->CleanupOutputs();
2181
0
          NotifyOnSubcompactionCompleted(sub_compact);
2182
0
        },
2183
0
        thread_pri_);
2184
0
    if (!status.ok()) {
2185
0
      sub_compact->status = status;
2186
0
    }
2187
2.77k
  } else {
2188
2.77k
    ProcessLocalKeyValueCompaction(sub_compact);
2189
2.77k
  }
2190
2.77k
}
2191
2192
void CompactionJob::ProcessLocalKeyValueCompaction(
2193
2.77k
    SubcompactionState* sub_compact) {
2194
2.77k
  AutoThreadOperationStageUpdater stage_updater(
2195
2.77k
      ThreadStatus::STAGE_COMPACTION_PROCESS_KV);
2196
2197
2.77k
  const uint64_t start_cpu_micros = db_options_.clock->CPUMicros();
2198
2.77k
  uint64_t prev_cpu_micros = start_cpu_micros;
2199
2.77k
  const CompactionIOStatsSnapshot io_stats = InitializeIOStats();
2200
2.77k
  ColumnFamilyData* cfd = sub_compact->compaction->column_family_data();
2201
2.77k
  const CompactionFilter* compaction_filter;
2202
2.77k
  std::unique_ptr<CompactionFilter> compaction_filter_from_factory = nullptr;
2203
2.77k
  CompactionFilter::MergeOrder merge_order;
2204
2.77k
  Status filter_status = SetupAndValidateCompactionFilter(
2205
2.77k
      sub_compact, cfd->ioptions().compaction_filter, compaction_filter,
2206
2.77k
      compaction_filter_from_factory, merge_order);
2207
2.77k
  if (!filter_status.ok()) {
2208
0
    sub_compact->status = filter_status;
2209
0
    return;
2210
0
  }
2211
2212
2.77k
  NotifyOnSubcompactionBegin(sub_compact);
2213
2214
2.77k
  SubcompactionKeyBoundaries boundaries(sub_compact->start, sub_compact->end);
2215
2.77k
  SubcompactionInternalIterators iterators;
2216
2.77k
  ReadOptions read_options;
2217
2.77k
  const WriteOptions write_options(Env::IOPriority::IO_LOW,
2218
2.77k
                                   Env::IOActivity::kCompaction);
2219
2220
2.77k
  InternalIterator* input_iter = CreateInputIterator(
2221
2.77k
      sub_compact, cfd, iterators, boundaries, read_options);
2222
2223
2.77k
  assert(input_iter);
2224
2225
2.77k
  Status status =
2226
2.77k
      MaybeResumeSubcompactionProgressOnInputIterator(sub_compact, input_iter);
2227
2228
2.77k
  if (status.IsNotFound()) {
2229
2.77k
    input_iter->SeekToFirst();
2230
2.77k
  } else if (!status.ok()) {
2231
0
    sub_compact->status = status;
2232
0
    NotifyOnSubcompactionCompleted(sub_compact);
2233
0
    return;
2234
0
  }
2235
2236
2.77k
  MergeHelper merge(
2237
2.77k
      env_, cfd->user_comparator(), cfd->ioptions().merge_operator.get(),
2238
2.77k
      compaction_filter, merge_order, db_options_.info_log.get(),
2239
2.77k
      false /* internal key corruption is expected */,
2240
2.77k
      job_context_->GetLatestSnapshotSequence(), job_context_->snapshot_checker,
2241
2.77k
      compact_->compaction->level(), db_options_.stats);
2242
2.77k
  std::unique_ptr<BlobFileBuilder> blob_file_builder;
2243
2244
2.77k
  auto c_iter =
2245
2.77k
      CreateCompactionIterator(sub_compact, cfd, input_iter, compaction_filter,
2246
2.77k
                               merge, blob_file_builder, write_options);
2247
2.77k
  assert(c_iter);
2248
2.77k
  c_iter->SetMissingBlobRepairIterator(iterators.missing_blob_repair.get());
2249
2.77k
  const bool defer_blob_output_preparation =
2250
2.77k
      compaction_progress_writer_ != nullptr &&
2251
0
      !sub_compact->compaction->SupportsPerKeyPlacement() &&
2252
0
      (blob_file_builder != nullptr ||
2253
0
       sub_compact->Current().GetBlobGarbageMeter() != nullptr);
2254
2.77k
  if (defer_blob_output_preparation) {
2255
0
    c_iter->SeekToFirstWithoutPreparingOutput();
2256
2.77k
  } else {
2257
2.77k
    c_iter->SeekToFirst();
2258
2.77k
  }
2259
2260
2.77k
  TEST_SYNC_POINT("CompactionJob::Run():Inprogress");
2261
2.77k
  TEST_SYNC_POINT_CALLBACK("CompactionJob::Run():PausingManualCompaction:1",
2262
2.77k
                           static_cast<void*>(const_cast<std::atomic<bool>*>(
2263
2.77k
                               &manual_compaction_canceled_)));
2264
2265
2.77k
  auto [open_file_func, close_file_func] =
2266
2.77k
      CreateFileHandlers(sub_compact, boundaries, blob_file_builder.get(),
2267
2.77k
                         iterators.blob_counter.get());
2268
2269
2.77k
  status = ProcessKeyValue(sub_compact, cfd, c_iter.get(), open_file_func,
2270
2.77k
                           close_file_func, defer_blob_output_preparation,
2271
2.77k
                           prev_cpu_micros);
2272
2273
2.77k
  status = FinalizeProcessKeyValueStatus(cfd, input_iter, c_iter.get(), status);
2274
2275
2.77k
  FinalizeSubcompaction(sub_compact, status, open_file_func, close_file_func,
2276
2.77k
                        blob_file_builder.get(), c_iter.get(), input_iter,
2277
2.77k
                        start_cpu_micros, prev_cpu_micros, io_stats);
2278
2279
2.77k
  NotifyOnSubcompactionCompleted(sub_compact);
2280
2.77k
}
2281
2282
void CompactionJob::FinalizeSubcompaction(
2283
    SubcompactionState* sub_compact, Status status,
2284
    const CompactionFileOpenFunc& open_file_func,
2285
    const CompactionFileCloseFunc& close_file_func,
2286
    BlobFileBuilder* blob_file_builder, CompactionIterator* c_iter,
2287
    [[maybe_unused]] InternalIterator* input_iter, uint64_t start_cpu_micros,
2288
2.77k
    uint64_t prev_cpu_micros, const CompactionIOStatsSnapshot& io_stats) {
2289
2.77k
  status = CleanupCompactionFiles(sub_compact, status, open_file_func,
2290
2.77k
                                  close_file_func);
2291
2.77k
  status = FinalizeBlobFiles(sub_compact, blob_file_builder, status);
2292
2293
2.77k
  FinalizeSubcompactionJobStats(sub_compact, c_iter, start_cpu_micros,
2294
2.77k
                                prev_cpu_micros, io_stats);
2295
2296
#ifdef ROCKSDB_ASSERT_STATUS_CHECKED
2297
  if (!status.ok()) {
2298
    if (c_iter) {
2299
      c_iter->status().PermitUncheckedError();
2300
    }
2301
    if (input_iter) {
2302
      input_iter->status().PermitUncheckedError();
2303
    }
2304
  }
2305
#endif  // ROCKSDB_ASSERT_STATUS_CHECKED
2306
2307
2.77k
  sub_compact->status = status;
2308
2.77k
}
2309
2310
0
uint64_t CompactionJob::GetCompactionId(SubcompactionState* sub_compact) const {
2311
0
  return (uint64_t)job_id_ << 32 | sub_compact->sub_job_id;
2312
0
}
2313
2314
void CompactionJob::RecordDroppedKeys(
2315
    const CompactionIterationStats& c_iter_stats,
2316
3.98k
    CompactionJobStats* compaction_job_stats) {
2317
3.98k
  if (c_iter_stats.num_record_drop_user > 0) {
2318
0
    RecordTick(stats_, COMPACTION_KEY_DROP_USER,
2319
0
               c_iter_stats.num_record_drop_user);
2320
0
  }
2321
3.98k
  if (c_iter_stats.num_record_drop_hidden > 0) {
2322
1.59k
    RecordTick(stats_, COMPACTION_KEY_DROP_NEWER_ENTRY,
2323
1.59k
               c_iter_stats.num_record_drop_hidden);
2324
1.59k
    if (compaction_job_stats) {
2325
1.59k
      compaction_job_stats->num_records_replaced +=
2326
1.59k
          c_iter_stats.num_record_drop_hidden;
2327
1.59k
    }
2328
1.59k
  }
2329
3.98k
  if (c_iter_stats.num_record_drop_obsolete > 0) {
2330
1.29k
    RecordTick(stats_, COMPACTION_KEY_DROP_OBSOLETE,
2331
1.29k
               c_iter_stats.num_record_drop_obsolete);
2332
1.29k
    if (compaction_job_stats) {
2333
1.29k
      compaction_job_stats->num_expired_deletion_records +=
2334
1.29k
          c_iter_stats.num_record_drop_obsolete;
2335
1.29k
    }
2336
1.29k
  }
2337
3.98k
  if (c_iter_stats.num_record_drop_range_del > 0) {
2338
0
    RecordTick(stats_, COMPACTION_KEY_DROP_RANGE_DEL,
2339
0
               c_iter_stats.num_record_drop_range_del);
2340
0
  }
2341
3.98k
  if (c_iter_stats.num_range_del_drop_obsolete > 0) {
2342
0
    RecordTick(stats_, COMPACTION_RANGE_DEL_DROP_OBSOLETE,
2343
0
               c_iter_stats.num_range_del_drop_obsolete);
2344
0
  }
2345
3.98k
  if (c_iter_stats.num_optimized_del_drop_obsolete > 0) {
2346
0
    RecordTick(stats_, COMPACTION_OPTIMIZED_DEL_DROP_OBSOLETE,
2347
0
               c_iter_stats.num_optimized_del_drop_obsolete);
2348
0
  }
2349
3.98k
}
2350
2351
Status CompactionJob::FinishCompactionOutputFile(
2352
    const Status& input_status,
2353
    const ParsedInternalKey& prev_iter_output_internal_key,
2354
    const Slice& next_table_min_key, const Slice* comp_start_user_key,
2355
    const Slice* comp_end_user_key, const CompactionIterator* c_iter,
2356
    bool post_prepare_retry, SubcompactionState* sub_compact,
2357
    BlobFileBuilder* blob_file_builder,
2358
1.69k
    const BlobCountingIterator* blob_counter, CompactionOutputs& outputs) {
2359
1.69k
  AutoThreadOperationStageUpdater stage_updater(
2360
1.69k
      ThreadStatus::STAGE_COMPACTION_SYNC_FILE);
2361
1.69k
  assert(sub_compact != nullptr);
2362
1.69k
  assert(outputs.HasBuilder());
2363
2364
1.69k
  FileMetaData* meta = outputs.GetMetaData();
2365
1.69k
  uint64_t output_number = meta->fd.GetNumber();
2366
1.69k
  assert(output_number != 0);
2367
2368
1.69k
  ColumnFamilyData* cfd = sub_compact->compaction->column_family_data();
2369
1.69k
  std::string file_checksum = kUnknownFileChecksum;
2370
1.69k
  std::string file_checksum_func_name = kUnknownFileChecksumFuncName;
2371
2372
  // Check for iterator errors
2373
1.69k
  Status s = input_status;
2374
2375
  // Add range tombstones
2376
1.69k
  if (s.ok()) {
2377
    // Inclusive lower bound, exclusive upper bound
2378
1.21k
    std::pair<SequenceNumber, SequenceNumber> keep_seqno_range{
2379
1.21k
        0, kMaxSequenceNumber};
2380
1.21k
    if (sub_compact->compaction->SupportsPerKeyPlacement()) {
2381
      // Point entries are routed to proximal output only when their seqno is
2382
      // strictly greater than `proximal_after_seqno_`. Range tombstones use a
2383
      // [lower, upper) filter, so split them at the next seqno to preserve the
2384
      // same boundary.
2385
0
      SequenceNumber range_del_split_seqno = proximal_after_seqno_;
2386
0
      if (range_del_split_seqno < kMaxSequenceNumber) {
2387
0
        range_del_split_seqno++;
2388
0
      }
2389
0
      if (outputs.IsProximalLevel()) {
2390
0
        keep_seqno_range.first = range_del_split_seqno;
2391
0
      } else {
2392
0
        keep_seqno_range.second = range_del_split_seqno;
2393
0
      }
2394
0
    }
2395
1.21k
    CompactionIterationStats range_del_out_stats;
2396
    // NOTE1: Use `bottommost_level_ = true` for both bottommost and
2397
    // output_to_proximal_level compaction here, as it's only used to decide
2398
    // if range dels could be dropped. (Logically, we are taking a single sorted
2399
    // run returned from CompactionIterator and physically splitting it between
2400
    // two output levels.)
2401
    // NOTE2: with per-key placement, range tombstones will be filtered on
2402
    // each output level based on sequence number (traversed twice). This is
2403
    // CPU-inefficient for a large number of range tombstones, but that would
2404
    // be an unusual work load.
2405
1.21k
    if (sub_compact->HasRangeDel()) {
2406
0
      s = outputs.AddRangeDels(*sub_compact->RangeDelAgg(), comp_start_user_key,
2407
0
                               comp_end_user_key, range_del_out_stats,
2408
0
                               bottommost_level_, cfd->internal_comparator(),
2409
0
                               earliest_snapshot_, keep_seqno_range,
2410
0
                               next_table_min_key, full_history_ts_low_);
2411
0
    }
2412
1.21k
    RecordDroppedKeys(range_del_out_stats, &sub_compact->compaction_job_stats);
2413
1.21k
    TEST_SYNC_POINT("CompactionJob::FinishCompactionOutputFile1");
2414
1.21k
  }
2415
2416
1.69k
  const uint64_t current_entries = outputs.NumEntries();
2417
2418
1.69k
  s = outputs.Finish(s, seqno_to_time_mapping_);
2419
1.69k
  TEST_SYNC_POINT_CALLBACK(
2420
1.69k
      "CompactionJob::FinishCompactionOutputFile()::AfterFinish", &s);
2421
2422
1.69k
  if (s.ok()) {
2423
    // With accurate smallest and largest key, we can get a slightly more
2424
    // accurate oldest ancestor time.
2425
    // This makes oldest ancestor time in manifest more accurate than in
2426
    // table properties. Not sure how to resolve it.
2427
1.21k
    if (meta->smallest.size() > 0 && meta->largest.size() > 0) {
2428
1.21k
      uint64_t refined_oldest_ancestor_time;
2429
1.21k
      Slice new_smallest = meta->smallest.user_key();
2430
1.21k
      Slice new_largest = meta->largest.user_key();
2431
1.21k
      if (!new_largest.empty() && !new_smallest.empty()) {
2432
351
        refined_oldest_ancestor_time =
2433
351
            sub_compact->compaction->MinInputFileOldestAncestorTime(
2434
351
                &(meta->smallest), &(meta->largest));
2435
351
        if (refined_oldest_ancestor_time !=
2436
351
            std::numeric_limits<uint64_t>::max()) {
2437
101
          meta->oldest_ancestor_time = refined_oldest_ancestor_time;
2438
101
        }
2439
351
      }
2440
1.21k
    }
2441
1.21k
  }
2442
2443
  // Finish and check for file errors
2444
1.69k
  IOStatus io_s = outputs.WriterSyncClose(s, db_options_.clock, stats_,
2445
1.69k
                                          db_options_.use_fsync);
2446
2447
1.69k
  if (s.ok() && io_s.ok()) {
2448
1.21k
    file_checksum = meta->file_checksum;
2449
1.21k
    file_checksum_func_name = meta->file_checksum_func_name;
2450
1.21k
  }
2451
2452
1.69k
  if (s.ok()) {
2453
1.21k
    s = io_s;
2454
1.21k
  }
2455
1.69k
  if (sub_compact->io_status.ok()) {
2456
1.69k
    sub_compact->io_status = io_s;
2457
    // Since this error is really a copy of the
2458
    // "normal" status, it does not also need to be checked
2459
1.69k
    sub_compact->io_status.PermitUncheckedError();
2460
1.69k
  }
2461
2462
1.69k
  TableProperties tp;
2463
1.69k
  if (s.ok()) {
2464
1.21k
    tp = outputs.GetTableProperties();
2465
1.21k
  }
2466
1.69k
  if (s.ok() && current_entries == 0 && tp.num_range_deletions == 0) {
2467
    // If there is nothing to output, no necessary to generate a sst file.
2468
    // This happens when the output level is bottom level, at the same time
2469
    // the sub_compact output nothing.
2470
0
    std::string fname = GetTableFileName(meta->fd.GetNumber());
2471
2472
    // TODO(AR) it is not clear if there are any larger implications if
2473
    // DeleteFile fails here
2474
0
    Status ds = env_->DeleteFile(fname);
2475
0
    if (!ds.ok()) {
2476
0
      ROCKS_LOG_WARN(
2477
0
          db_options_.info_log,
2478
0
          "[%s] [JOB %d] Unable to remove SST file for table #%" PRIu64
2479
0
          " at bottom level%s",
2480
0
          cfd->GetName().c_str(), job_id_, output_number,
2481
0
          meta->marked_for_compaction ? " (need compaction)" : "");
2482
0
    }
2483
2484
    // Also need to remove the file from outputs, or it will be added to the
2485
    // VersionEdit.
2486
0
    outputs.RemoveLastOutput();
2487
0
    meta = nullptr;
2488
0
  }
2489
2490
1.69k
  if (s.ok() && (current_entries > 0 || tp.num_range_deletions > 0)) {
2491
    // Output to event logger and fire events.
2492
1.21k
    outputs.UpdateTableProperties();
2493
1.21k
    ROCKS_LOG_INFO(db_options_.info_log,
2494
1.21k
                   "[%s] [JOB %d] Generated table #%" PRIu64 ": %" PRIu64
2495
1.21k
                   " keys, %" PRIu64 " bytes%s, temperature: %s",
2496
1.21k
                   cfd->GetName().c_str(), job_id_, output_number,
2497
1.21k
                   current_entries, meta->fd.file_size,
2498
1.21k
                   meta->marked_for_compaction ? " (need compaction)" : "",
2499
1.21k
                   temperature_to_string[meta->temperature].c_str());
2500
1.21k
  }
2501
1.69k
  std::string fname;
2502
1.69k
  FileDescriptor output_fd;
2503
1.69k
  uint64_t oldest_blob_file_number = kInvalidBlobFileNumber;
2504
1.69k
  Status status_for_listener = s;
2505
1.69k
  if (meta != nullptr) {
2506
1.69k
    fname = GetTableFileName(meta->fd.GetNumber());
2507
1.69k
    output_fd = meta->fd;
2508
1.69k
    oldest_blob_file_number = meta->oldest_blob_file_number;
2509
1.69k
  } else {
2510
0
    fname = "(nil)";
2511
0
    if (s.ok()) {
2512
0
      status_for_listener = Status::Aborted("Empty SST file not kept");
2513
0
    }
2514
0
  }
2515
1.69k
  EventHelpers::LogAndNotifyTableFileCreationFinished(
2516
1.69k
      event_logger_, cfd->ioptions().listeners, dbname_, cfd->GetName(), fname,
2517
1.69k
      job_id_, output_fd, oldest_blob_file_number, tp,
2518
1.69k
      TableFileCreationReason::kCompaction, status_for_listener, file_checksum,
2519
1.69k
      file_checksum_func_name);
2520
2521
  // Report new file to SstFileManagerImpl
2522
1.69k
  auto sfm =
2523
1.69k
      static_cast<SstFileManagerImpl*>(db_options_.sst_file_manager.get());
2524
1.69k
  if (sfm && meta != nullptr && meta->fd.GetPathId() == 0) {
2525
1.69k
    Status add_s = sfm->OnAddFile(fname);
2526
1.69k
    if (!add_s.ok() && s.ok()) {
2527
0
      s = add_s;
2528
0
    }
2529
1.69k
    if (sfm->IsMaxAllowedSpaceReached()) {
2530
      // TODO(ajkr): should we return OK() if max space was reached by the final
2531
      // compaction output file (similarly to how flush works when full)?
2532
0
      s = Status::SpaceLimit("Max allowed space was reached");
2533
0
      TEST_SYNC_POINT(
2534
0
          "CompactionJob::FinishCompactionOutputFile:MaxAllowedSpaceReached");
2535
0
      InstrumentedMutexLock l(db_mutex_);
2536
0
      db_error_handler_->SetBGError(s, BackgroundErrorReason::kCompaction);
2537
0
    }
2538
1.69k
  }
2539
2540
1.69k
  bool should_update_progress =
2541
1.69k
      s.ok() && ShouldUpdateSubcompactionProgress(
2542
1.21k
                    sub_compact, c_iter, prev_iter_output_internal_key,
2543
1.21k
                    next_table_min_key, meta, blob_file_builder, blob_counter,
2544
1.21k
                    post_prepare_retry);
2545
1.69k
  if (post_prepare_retry) {
2546
0
    TEST_SYNC_POINT_CALLBACK(
2547
0
        "CompactionJob::FinishCompactionOutputFile:"
2548
0
        "PostPrepareRetryShouldUpdateProgress",
2549
0
        &should_update_progress);
2550
0
  }
2551
1.69k
  if (should_update_progress) {
2552
0
    s = sub_compact->UpdateBlobGarbageCheckpoint(
2553
0
        blob_counter == nullptr || !blob_counter->TracksCurrentKeyInflow()
2554
0
            ? nullptr
2555
0
            : &blob_counter->GetCurrentKeyInflow());
2556
0
  }
2557
1.69k
  if (s.ok() && should_update_progress && blob_file_builder != nullptr) {
2558
    // An SST checkpoint must never reference a blob file that is still open or
2559
    // absent from the persisted BlobFileAddition state. Closing here also
2560
    // makes a crash before the progress record harmless: workspace cleanup
2561
    // removes the uncheckpointed blob file and SST together.
2562
0
    s = blob_file_builder->Finish();
2563
0
  }
2564
1.69k
  if (s.ok() && should_update_progress) {
2565
0
    UpdateSubcompactionProgress(c_iter, next_table_min_key, sub_compact);
2566
0
    s = PersistSubcompactionProgress(sub_compact);
2567
0
  }
2568
1.69k
  outputs.ResetBuilder();
2569
1.69k
  return s;
2570
1.69k
}
2571
2572
bool CompactionJob::ShouldUpdateSubcompactionProgress(
2573
    const SubcompactionState* sub_compact, const CompactionIterator* c_iter,
2574
    const ParsedInternalKey& prev_iter_output_internal_key,
2575
    const Slice& next_table_min_internal_key, const FileMetaData* meta,
2576
    const BlobFileBuilder* blob_file_builder,
2577
1.21k
    const BlobCountingIterator* blob_counter, bool post_prepare_retry) const {
2578
1.21k
  if (post_prepare_retry) {
2579
0
    return false;
2580
0
  }
2581
1.21k
  const auto* cfd = sub_compact->compaction->column_family_data();
2582
  // No need to update when the progress will not get persisted
2583
1.21k
  if (compaction_progress_writer_ == nullptr) {
2584
1.21k
    return false;
2585
1.21k
  }
2586
2587
0
  bool supports_per_key_placement =
2588
0
      sub_compact->compaction->SupportsPerKeyPlacement();
2589
0
  TEST_SYNC_POINT_CALLBACK(
2590
0
      "CompactionJob::ShouldUpdateSubcompactionProgress:"
2591
0
      "SupportsPerKeyPlacement",
2592
0
      &supports_per_key_placement);
2593
0
  if (supports_per_key_placement &&
2594
0
      (blob_file_builder != nullptr || blob_counter != nullptr)) {
2595
    // Per-key placement prepares blob output before reaching this boundary.
2596
    // Checkpointing would retain an unreferenced blob record when the current
2597
    // key is processed again after resumption.
2598
0
    return false;
2599
0
  }
2600
2601
  // LIMITATION: With per-key placement, the file that just closed can belong
2602
  // to one output level while the other level still has an open file with
2603
  // keys before this boundary. A resume can restore only finished files, so it
2604
  // would lose those keys.
2605
0
  if (sub_compact->HasUnfinishedOutputFile()) {
2606
0
    return false;
2607
0
  }
2608
2609
  // No need to update for a new empty output
2610
0
  if (meta == nullptr) {
2611
0
    return false;
2612
0
  }
2613
2614
  // TODO(hx235): save progress even on the last output file
2615
0
  if (next_table_min_internal_key.empty()) {
2616
0
    return false;
2617
0
  }
2618
0
  assert(c_iter != nullptr);
2619
2620
  // LIMITATION: Persisting compaction progress with timestamp
2621
  // is not supported since the feature of persisting timestamp of the key in
2622
  // SST files itself is still experimental
2623
0
  size_t ts_sz = cfd->user_comparator()->timestamp_size();
2624
0
  if (ts_sz > 0) {
2625
0
    return false;
2626
0
  }
2627
2628
  // LIMITATION: Compaction progress persistence disabled for file boundaries
2629
  // containing range deletions. Range deletions can span file boundaries,
2630
  // making it difficult to ensure adjacent output tables have different user
2631
  // keys. See the last check for why different users keys of adjacent output
2632
  // tables are needed
2633
0
  const ValueType next_table_min_internal_key_type =
2634
0
      ExtractValueType(next_table_min_internal_key);
2635
0
  const ValueType prev_iter_output_internal_key_type =
2636
0
      prev_iter_output_internal_key.user_key.empty()
2637
0
          ? ValueType::kTypeValue
2638
0
          : prev_iter_output_internal_key.type;
2639
2640
  // Range deletes truncated to align with file boundaries may be output by the
2641
  // compaction iterator with `ValueType::kTypeMaxValid` instead of the original
2642
  // type.
2643
0
  if ((next_table_min_internal_key_type == ValueType::kTypeRangeDeletion ||
2644
0
       next_table_min_internal_key_type == ValueType::kTypeMaxValid) ||
2645
0
      (prev_iter_output_internal_key_type == ValueType::kTypeRangeDeletion ||
2646
0
       prev_iter_output_internal_key_type == ValueType::kTypeMaxValid)) {
2647
0
    return false;
2648
0
  }
2649
2650
  // LIMITATION: Compaction progress persistence disabled when adjacent output
2651
  // tables share the same user key at boundaries. This ensures a simple Seek()
2652
  // of the next key when resuming can process all versions of a user key
2653
0
  const Slice next_table_min_user_key =
2654
0
      ExtractUserKey(next_table_min_internal_key);
2655
0
  const Slice prev_table_last_user_key =
2656
0
      prev_iter_output_internal_key.user_key.empty()
2657
0
          ? Slice()
2658
0
          : prev_iter_output_internal_key.user_key;
2659
2660
0
  if (cfd->user_comparator()->EqualWithoutTimestamp(next_table_min_user_key,
2661
0
                                                    prev_table_last_user_key)) {
2662
0
    return false;
2663
0
  }
2664
2665
  // LIMITATION: Don't save progress when the current output required looking
2666
  // ahead or consuming an earlier input entry of the same user key. Resuming
2667
  // through Seek() would process those entries again and double-count them.
2668
  // A future implementation could persist enough iterator state to allow
2669
  // checkpoints at these boundaries.
2670
0
  if (c_iter->IsCurrentKeyAlreadyScanned() ||
2671
0
      c_iter->HasConsumedEarlierEntriesOfCurrentKey()) {
2672
0
    if (!logged_unsafe_resume_boundary_.exchange(true,
2673
0
                                                 std::memory_order_relaxed)) {
2674
0
      ROCKS_LOG_INFO(db_options_.info_log,
2675
0
                     "[%s] [JOB %d] Skipping resumable compaction checkpoint "
2676
0
                     "at an input-consumed user-key boundary",
2677
0
                     cfd->GetName().c_str(), job_id_);
2678
0
    }
2679
0
    return false;
2680
0
  }
2681
2682
0
  return true;
2683
0
}
2684
2685
2.07k
Status CompactionJob::InstallCompactionResults(bool* compaction_released) {
2686
2.07k
  assert(compact_);
2687
2688
2.07k
  db_mutex_->AssertHeld();
2689
2690
2.07k
  const ReadOptions read_options(Env::IOActivity::kCompaction);
2691
2.07k
  const WriteOptions write_options(Env::IOActivity::kCompaction);
2692
2693
2.07k
  auto* compaction = compact_->compaction;
2694
2.07k
  assert(compaction);
2695
2696
2.07k
  {
2697
2.07k
    Compaction::InputLevelSummaryBuffer inputs_summary;
2698
2.07k
    if (internal_stats_.has_proximal_level_output) {
2699
0
      ROCKS_LOG_BUFFER(
2700
0
          log_buffer_,
2701
0
          "[%s] [JOB %d] Compacted %s => output_to_proximal_level: %" PRIu64
2702
0
          " bytes + last: %" PRIu64 " bytes. Total: %" PRIu64 " bytes",
2703
0
          compaction->column_family_data()->GetName().c_str(), job_id_,
2704
0
          compaction->InputLevelSummary(&inputs_summary),
2705
0
          internal_stats_.proximal_level_stats.bytes_written,
2706
0
          internal_stats_.output_level_stats.bytes_written,
2707
0
          internal_stats_.TotalBytesWritten());
2708
2.07k
    } else {
2709
2.07k
      ROCKS_LOG_BUFFER(log_buffer_,
2710
2.07k
                       "[%s] [JOB %d] Compacted %s => %" PRIu64 " bytes",
2711
2.07k
                       compaction->column_family_data()->GetName().c_str(),
2712
2.07k
                       job_id_, compaction->InputLevelSummary(&inputs_summary),
2713
2.07k
                       internal_stats_.TotalBytesWritten());
2714
2.07k
    }
2715
2.07k
  }
2716
2717
2.07k
  VersionEdit* const edit = compaction->edit();
2718
2.07k
  assert(edit);
2719
2720
2.07k
  struct BlobFileState {
2721
2.07k
    uint64_t total_blob_count;
2722
2.07k
    uint64_t total_blob_bytes;
2723
2.07k
    uint64_t garbage_blob_count;
2724
2.07k
    uint64_t garbage_blob_bytes;
2725
2.07k
  };
2726
2.07k
  std::unordered_map<uint64_t, BlobFileState> blob_files;
2727
2.07k
  const VersionStorageInfo* const storage_info =
2728
2.07k
      compaction->input_version()->storage_info();
2729
2.07k
  for (const auto& blob_file : storage_info->GetBlobFiles()) {
2730
0
    assert(blob_file);
2731
0
    blob_files.emplace(blob_file->GetBlobFileNumber(),
2732
0
                       BlobFileState{blob_file->GetTotalBlobCount(),
2733
0
                                     blob_file->GetTotalBlobBytes(),
2734
0
                                     blob_file->GetGarbageBlobCount(),
2735
0
                                     blob_file->GetGarbageBlobBytes()});
2736
0
  }
2737
2.07k
  for (const auto& sub_compact : compact_->sub_compact_states) {
2738
2.07k
    for (const auto& blob : sub_compact.Current().GetBlobFileAdditions()) {
2739
0
      if (!blob_files
2740
0
               .emplace(blob.GetBlobFileNumber(),
2741
0
                        BlobFileState{blob.GetTotalBlobCount(),
2742
0
                                      blob.GetTotalBlobBytes(), 0, 0})
2743
0
               .second) {
2744
0
        return Status::Corruption(
2745
0
            "Compaction produced duplicate blob file additions");
2746
0
      }
2747
0
    }
2748
2.07k
  }
2749
2750
2.07k
  std::unordered_map<uint64_t, BlobGarbageMeter::BlobStats> blob_total_garbage;
2751
2.07k
  uint64_t skipped_missing_blob_flows = 0;
2752
2.07k
  for (const auto& sub_compact : compact_->sub_compact_states) {
2753
2.07k
    if (sub_compact.Current().GetBlobGarbageMeter()) {
2754
0
      const auto& flows = sub_compact.Current().GetBlobGarbageMeter()->flows();
2755
2756
0
      for (const auto& pair : flows) {
2757
0
        const uint64_t blob_file_number = pair.first;
2758
0
        const BlobGarbageMeter::BlobInOutFlow& flow = pair.second;
2759
2760
0
        assert(flow.IsValid());
2761
0
        if (flow.HasGarbage()) {
2762
0
          const std::unordered_map<uint64_t, BlobFileState>::const_iterator
2763
0
              blob_file = blob_files.find(blob_file_number);
2764
0
          if (blob_file == blob_files.end()) {
2765
0
            if (blob_file_number != kInvalidBlobFileNumber &&
2766
0
                (compaction->skip_missing_blob_files() ||
2767
0
                 storage_info->IsDroppedBlobFile(blob_file_number))) {
2768
0
              ++skipped_missing_blob_flows;
2769
0
              continue;
2770
0
            }
2771
0
            return Status::Corruption(
2772
0
                "Compaction produced garbage for an unknown blob file");
2773
0
          }
2774
0
          const BlobFileState& state = blob_file->second;
2775
0
          if (state.garbage_blob_count > state.total_blob_count ||
2776
0
              state.garbage_blob_bytes > state.total_blob_bytes) {
2777
0
            return Status::Corruption(
2778
0
                "Compaction encountered invalid existing blob garbage");
2779
0
          }
2780
0
          BlobGarbageMeter::BlobStats& aggregate =
2781
0
              blob_total_garbage[blob_file_number];
2782
0
          const uint64_t available_blob_count =
2783
0
              state.total_blob_count - state.garbage_blob_count;
2784
0
          const uint64_t available_blob_bytes =
2785
0
              state.total_blob_bytes - state.garbage_blob_bytes;
2786
0
          if (aggregate.GetCount() > available_blob_count ||
2787
0
              aggregate.GetBytes() > available_blob_bytes ||
2788
0
              flow.GetGarbageCount() >
2789
0
                  available_blob_count - aggregate.GetCount() ||
2790
0
              flow.GetGarbageBytes() >
2791
0
                  available_blob_bytes - aggregate.GetBytes()) {
2792
0
            return Status::Corruption(
2793
0
                "Compaction blob garbage exceeds file totals");
2794
0
          }
2795
0
          aggregate.Add(flow.GetGarbageCount(), flow.GetGarbageBytes());
2796
0
        }
2797
0
      }
2798
0
    }
2799
2.07k
  }
2800
2801
2.07k
  if (skipped_missing_blob_flows != 0) {
2802
0
    ROCKS_LOG_BUFFER(
2803
0
        log_buffer_,
2804
0
        "[%s] [JOB %d] Repair omitted %" PRIu64
2805
0
        " garbage flows for blob files absent from the input Version",
2806
0
        compaction->column_family_data()->GetName().c_str(), job_id_,
2807
0
        skipped_missing_blob_flows);
2808
0
  }
2809
2810
  // Add compaction inputs and outputs only after all blob metadata has been
2811
  // validated across subcompactions.
2812
2.07k
  compaction->AddInputDeletions(edit);
2813
2.07k
  for (const auto& sub_compact : compact_->sub_compact_states) {
2814
2.07k
    sub_compact.AddOutputsEdit(edit);
2815
2816
2.07k
    for (const auto& blob : sub_compact.Current().GetBlobFileAdditions()) {
2817
0
      edit->AddBlobFile(blob);
2818
0
    }
2819
2.07k
  }
2820
2821
2.07k
  for (const auto& pair : blob_total_garbage) {
2822
0
    const uint64_t blob_file_number = pair.first;
2823
0
    const BlobGarbageMeter::BlobStats& stats = pair.second;
2824
2825
0
    edit->AddBlobFileGarbage(blob_file_number, stats.GetCount(),
2826
0
                             stats.GetBytes());
2827
0
  }
2828
2829
2.07k
  if ((compaction->compaction_reason() ==
2830
2.07k
           CompactionReason::kLevelMaxLevelSize ||
2831
2.07k
       compaction->compaction_reason() == CompactionReason::kRoundRobinTtl) &&
2832
0
      compaction->immutable_options().compaction_pri == kRoundRobin) {
2833
0
    int start_level = compaction->start_level();
2834
0
    if (start_level > 0) {
2835
0
      auto vstorage = compaction->input_version()->storage_info();
2836
0
      edit->AddCompactCursor(start_level,
2837
0
                             vstorage->GetNextCompactCursor(
2838
0
                                 start_level, compaction->num_input_files(0)));
2839
0
    }
2840
0
  }
2841
2842
2.07k
  auto manifest_wcb = [&compaction, &compaction_released](const Status& s) {
2843
2.07k
    compaction->ReleaseCompactionFiles(s);
2844
2.07k
    *compaction_released = true;
2845
2.07k
  };
2846
2847
2.07k
  Status s;
2848
2.07k
  TEST_SYNC_POINT_CALLBACK(
2849
2.07k
      "CompactionJob::InstallCompactionResults:BeforeLogAndApply", &s);
2850
2.07k
  if (s.ok()) {
2851
2.07k
    s = versions_->LogAndApply(compaction->column_family_data(), read_options,
2852
2.07k
                               write_options, edit, db_mutex_, db_directory_,
2853
2.07k
                               /*new_descriptor_log=*/false,
2854
2.07k
                               /*column_family_options=*/nullptr, manifest_wcb);
2855
2.07k
  }
2856
2.07k
  return s;
2857
2.07k
}
2858
2859
5.54k
void CompactionJob::RecordCompactionIOStats() {
2860
5.54k
  RecordTick(stats_, COMPACT_READ_BYTES, IOSTATS(bytes_read));
2861
5.54k
  RecordTick(stats_, COMPACT_WRITE_BYTES, IOSTATS(bytes_written));
2862
5.54k
  CompactionReason compaction_reason =
2863
5.54k
      compact_->compaction->compaction_reason();
2864
5.54k
  if (compaction_reason == CompactionReason::kFilesMarkedForCompaction) {
2865
0
    RecordTick(stats_, COMPACT_READ_BYTES_MARKED, IOSTATS(bytes_read));
2866
0
    RecordTick(stats_, COMPACT_WRITE_BYTES_MARKED, IOSTATS(bytes_written));
2867
5.54k
  } else if (compaction_reason == CompactionReason::kPeriodicCompaction) {
2868
0
    RecordTick(stats_, COMPACT_READ_BYTES_PERIODIC, IOSTATS(bytes_read));
2869
0
    RecordTick(stats_, COMPACT_WRITE_BYTES_PERIODIC, IOSTATS(bytes_written));
2870
5.54k
  } else if (compaction_reason == CompactionReason::kTtl) {
2871
0
    RecordTick(stats_, COMPACT_READ_BYTES_TTL, IOSTATS(bytes_read));
2872
0
    RecordTick(stats_, COMPACT_WRITE_BYTES_TTL, IOSTATS(bytes_written));
2873
0
  }
2874
5.54k
  ThreadStatusUtil::IncreaseThreadOperationProperty(
2875
5.54k
      ThreadStatus::COMPACTION_BYTES_READ, IOSTATS(bytes_read));
2876
5.54k
  IOSTATS_RESET(bytes_read);
2877
5.54k
  ThreadStatusUtil::IncreaseThreadOperationProperty(
2878
5.54k
      ThreadStatus::COMPACTION_BYTES_WRITTEN, IOSTATS(bytes_written));
2879
5.54k
  IOSTATS_RESET(bytes_written);
2880
5.54k
}
2881
2882
Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact,
2883
1.69k
                                               CompactionOutputs& outputs) {
2884
1.69k
  assert(sub_compact != nullptr);
2885
2886
  // no need to lock because VersionSet::next_file_number_ is atomic
2887
1.69k
  uint64_t file_number = versions_->NewFileNumber();
2888
#ifndef NDEBUG
2889
  TEST_SYNC_POINT_CALLBACK(
2890
      "CompactionJob::OpenCompactionOutputFile::NewFileNumber", &file_number);
2891
#endif
2892
1.69k
  std::string fname = GetTableFileName(file_number);
2893
  // Fire events.
2894
1.69k
  ColumnFamilyData* cfd = sub_compact->compaction->column_family_data();
2895
1.69k
  EventHelpers::NotifyTableFileCreationStarted(
2896
1.69k
      cfd->ioptions().listeners, dbname_, cfd->GetName(), fname, job_id_,
2897
1.69k
      TableFileCreationReason::kCompaction);
2898
  // Make the output file
2899
1.69k
  std::unique_ptr<FSWritableFile> writable_file;
2900
#ifndef NDEBUG
2901
  bool syncpoint_arg = file_options_.use_direct_writes;
2902
  TEST_SYNC_POINT_CALLBACK("CompactionJob::OpenCompactionOutputFile",
2903
                           &syncpoint_arg);
2904
#endif
2905
2906
  // Pass temperature of the last level files to FileSystem.
2907
1.69k
  FileOptions fo_copy = file_options_;
2908
1.69k
  auto temperature =
2909
1.69k
      sub_compact->compaction->GetOutputTemperature(outputs.IsProximalLevel());
2910
1.69k
  fo_copy.temperature = temperature;
2911
1.69k
  fo_copy.open_contract = FileOpenContract::kNoReopenForWrite |
2912
1.69k
                          FileOpenContract::kNoReadersWhileOpenForWrite;
2913
1.69k
  fo_copy.write_hint = write_hint_;
2914
2915
1.69k
  Status s;
2916
1.69k
  IOStatus io_s = NewWritableFile(fs_.get(), fname, &writable_file, fo_copy);
2917
1.69k
  s = io_s;
2918
1.69k
  if (io_s.ok()) {
2919
    // Track the SST file path for cleanup on abort.
2920
1.69k
    outputs.AddOutputFilePath(fname);
2921
1.69k
  }
2922
1.69k
  if (sub_compact->io_status.ok()) {
2923
1.69k
    sub_compact->io_status = io_s;
2924
    // Since this error is really a copy of the io_s that is checked below as s,
2925
    // it does not also need to be checked.
2926
1.69k
    sub_compact->io_status.PermitUncheckedError();
2927
1.69k
  }
2928
1.69k
  if (!s.ok()) {
2929
0
    ROCKS_LOG_ERROR(
2930
0
        db_options_.info_log,
2931
0
        "[%s] [JOB %d] OpenCompactionOutputFiles for table #%" PRIu64
2932
0
        " fails at NewWritableFile with status %s",
2933
0
        sub_compact->compaction->column_family_data()->GetName().c_str(),
2934
0
        job_id_, file_number, s.ToString().c_str());
2935
0
    LogFlush(db_options_.info_log);
2936
0
    EventHelpers::LogAndNotifyTableFileCreationFinished(
2937
0
        event_logger_, cfd->ioptions().listeners, dbname_, cfd->GetName(),
2938
0
        fname, job_id_, FileDescriptor(), kInvalidBlobFileNumber,
2939
0
        TableProperties(), TableFileCreationReason::kCompaction, s,
2940
0
        kUnknownFileChecksum, kUnknownFileChecksumFuncName);
2941
0
    return s;
2942
0
  }
2943
2944
  // Try to figure out the output file's oldest ancestor time.
2945
1.69k
  int64_t temp_current_time = 0;
2946
1.69k
  auto get_time_status = db_options_.clock->GetCurrentTime(&temp_current_time);
2947
  // Safe to proceed even if GetCurrentTime fails. So, log and proceed.
2948
1.69k
  if (!get_time_status.ok()) {
2949
0
    ROCKS_LOG_WARN(db_options_.info_log,
2950
0
                   "Failed to get current time. Status: %s",
2951
0
                   get_time_status.ToString().c_str());
2952
0
  }
2953
1.69k
  uint64_t current_time = static_cast<uint64_t>(temp_current_time);
2954
1.69k
  InternalKey tmp_start;
2955
1.69k
  InternalKey tmp_end;
2956
1.69k
  if (sub_compact->start.has_value()) {
2957
0
    tmp_start.SetMinPossibleForUserKey(*(sub_compact->start));
2958
0
  }
2959
1.69k
  if (sub_compact->end.has_value()) {
2960
0
    tmp_end.SetMinPossibleForUserKey(*(sub_compact->end));
2961
0
  }
2962
1.69k
  uint64_t oldest_ancestor_time =
2963
1.69k
      sub_compact->compaction->MinInputFileOldestAncestorTime(
2964
1.69k
          sub_compact->start.has_value() ? &tmp_start : nullptr,
2965
1.69k
          sub_compact->end.has_value() ? &tmp_end : nullptr);
2966
1.69k
  if (oldest_ancestor_time == std::numeric_limits<uint64_t>::max()) {
2967
    // TODO: fix DBSSTTest.GetTotalSstFilesSize and use
2968
    //  kUnknownOldestAncestorTime
2969
0
    oldest_ancestor_time = current_time;
2970
0
  }
2971
2972
1.69k
  uint64_t newest_key_time = sub_compact->compaction->MaxInputFileNewestKeyTime(
2973
1.69k
      sub_compact->start.has_value() ? &tmp_start : nullptr,
2974
1.69k
      sub_compact->end.has_value() ? &tmp_end : nullptr);
2975
2976
  // Initialize a SubcompactionState::Output and add it to sub_compact->outputs
2977
1.69k
  uint64_t epoch_number = sub_compact->compaction->MinInputFileEpochNumber();
2978
1.69k
  {
2979
1.69k
    FileMetaData meta;
2980
1.69k
    meta.fd = FileDescriptor(file_number,
2981
1.69k
                             sub_compact->compaction->output_path_id(), 0);
2982
1.69k
    meta.oldest_ancestor_time = oldest_ancestor_time;
2983
1.69k
    meta.file_creation_time = current_time;
2984
1.69k
    meta.db_ownership_start_time = current_time;
2985
1.69k
    meta.data_file_provenance =
2986
1.69k
        DataFileProvenanceForOrigin(DataFileProvenance::Origin::kCompaction);
2987
1.69k
    meta.epoch_number = epoch_number;
2988
1.69k
    meta.temperature = temperature;
2989
1.69k
    assert(!db_id_.empty());
2990
1.69k
    assert(!db_session_id_.empty());
2991
1.69k
    s = GetSstInternalUniqueId(db_id_, db_session_id_, meta.fd.GetNumber(),
2992
1.69k
                               &meta.unique_id);
2993
1.69k
    if (!s.ok()) {
2994
0
      ROCKS_LOG_ERROR(db_options_.info_log,
2995
0
                      "[%s] [JOB %d] file #%" PRIu64
2996
0
                      " failed to generate unique id: %s.",
2997
0
                      cfd->GetName().c_str(), job_id_, meta.fd.GetNumber(),
2998
0
                      s.ToString().c_str());
2999
0
      return s;
3000
0
    }
3001
3002
    // Enable hash computation if paranoid_file_checks is on or if
3003
    // verify_output_flags includes kVerifyIteration, so that
3004
    // VerifyOutputFiles() can compare the hash of the written data
3005
    // against a re-read of the output file.
3006
1.69k
    bool enable_output_hash =
3007
1.69k
        paranoid_file_checks_ ||
3008
1.69k
        !!(sub_compact->compaction->mutable_cf_options().verify_output_flags &
3009
1.69k
           VerifyOutputFlags::kVerifyIteration);
3010
1.69k
    outputs.AddOutput(std::move(meta), cfd->internal_comparator(),
3011
1.69k
                      enable_output_hash);
3012
1.69k
  }
3013
3014
0
  writable_file->SetIOPriority(GetRateLimiterPriority());
3015
  // Subsequent attempts to override the hint via SetWriteLifeTimeHint
3016
  // with the very same value will be ignored by the fs.
3017
1.69k
  writable_file->SetWriteLifeTimeHint(fo_copy.write_hint);
3018
1.69k
  FileTypeSet tmp_set = db_options_.checksum_handoff_file_types;
3019
1.69k
  writable_file->SetPreallocationBlockSize(static_cast<size_t>(
3020
1.69k
      sub_compact->compaction->OutputFilePreallocationSize()));
3021
1.69k
  const auto& listeners =
3022
1.69k
      sub_compact->compaction->immutable_options().listeners;
3023
1.69k
  outputs.AssignFileWriter(new WritableFileWriter(
3024
1.69k
      std::move(writable_file), fname, fo_copy, db_options_.clock, io_tracer_,
3025
1.69k
      db_options_.stats, Histograms::SST_WRITE_MICROS, listeners,
3026
1.69k
      db_options_.file_checksum_gen_factory.get(),
3027
1.69k
      tmp_set.Contains(FileType::kTableFile),
3028
1.69k
      tmp_set.Contains(FileType::kTableFile)));
3029
3030
  // TODO(hx235): pass in the correct `oldest_key_time` instead of `0`
3031
1.69k
  const ReadOptions read_options(Env::IOActivity::kCompaction);
3032
1.69k
  const WriteOptions write_options(Env::IOActivity::kCompaction);
3033
1.69k
  TableBuilderOptions tboptions(
3034
1.69k
      cfd->ioptions(), sub_compact->compaction->mutable_cf_options(),
3035
1.69k
      read_options, write_options, cfd->internal_comparator(),
3036
1.69k
      cfd->internal_tbl_prop_coll_factories(),
3037
1.69k
      sub_compact->compaction->output_compression(),
3038
1.69k
      sub_compact->compaction->output_compression_opts(), cfd->GetID(),
3039
1.69k
      cfd->GetName(), sub_compact->compaction->output_level(), newest_key_time,
3040
1.69k
      bottommost_level_, TableFileCreationReason::kCompaction,
3041
1.69k
      0 /* oldest_key_time */, db_id_, db_session_id_,
3042
1.69k
      sub_compact->compaction->max_output_file_size(), file_number,
3043
1.69k
      proximal_after_seqno_ /*last_level_inclusive_max_seqno_threshold*/,
3044
1.69k
      dbname_ /*db_name*/, IsRemoteCompaction() /*is_remote_compaction*/);
3045
3046
1.69k
  outputs.NewBuilder(tboptions);
3047
3048
1.69k
  LogFlush(db_options_.info_log);
3049
1.69k
  return s;
3050
1.69k
}
3051
3052
2.77k
void CompactionJob::CleanupCompaction() {
3053
2.77k
  for (SubcompactionState& sub_compact : compact_->sub_compact_states) {
3054
2.77k
    sub_compact.Cleanup(table_cache_.get(), compact_->status);
3055
2.77k
  }
3056
2.77k
  delete compact_;
3057
2.77k
  compact_ = nullptr;
3058
2.77k
}
3059
3060
namespace {
3061
2.42k
void CopyPrefix(const Slice& src, size_t prefix_length, std::string* dst) {
3062
2.42k
  assert(prefix_length > 0);
3063
2.42k
  size_t length = src.size() > prefix_length ? prefix_length : src.size();
3064
2.42k
  dst->assign(src.data(), length);
3065
2.42k
}
3066
}  // namespace
3067
3068
bool CompactionJob::UpdateInternalStatsFromInputFiles(
3069
2.77k
    uint64_t* num_input_range_del) {
3070
2.77k
  assert(compact_);
3071
3072
2.77k
  Compaction* compaction = compact_->compaction;
3073
2.77k
  internal_stats_.output_level_stats.num_input_files_in_non_output_levels = 0;
3074
2.77k
  internal_stats_.output_level_stats.num_input_files_in_output_level = 0;
3075
3076
2.77k
  bool has_error = false;
3077
2.77k
  const ReadOptions read_options(Env::IOActivity::kCompaction);
3078
2.77k
  const auto& input_table_properties = compaction->GetInputTableProperties();
3079
3080
  // Check all input files for old block-based SST format_version. Why? Old
3081
  // block-based SST files from roughly version 5.0 to 5.18 could produce
3082
  // inaccurate num_entries counts due to the evolution of its handling along
3083
  // with num_range_deletions. We have to disable some paranoid checks when
3084
  // compacting files from such an old release. However, we don't have great
3085
  // information to identify those files, so we heuristically over-approximate
3086
  // that set of files using
3087
  // (a) format_version < 5, which will be true for any files from RocksDB <
3088
  // 6.6.0 and should not be true for any recent production files
3089
  // (b) to avoid including non-block-based SST files (which still use older
3090
  // format_version markers, and do not support DeleteRange), we also require
3091
  // the presence of the user property "rocksdb.block.based.table.index.type",
3092
  // which was added in RocksDB 2.8 and is always present in block-based tables.
3093
10.8k
  for (const auto& tp_pair : input_table_properties) {
3094
10.8k
    if (tp_pair.second && tp_pair.second->format_version < 5) {
3095
      // Check for block-based table by looking for its index type property
3096
0
      const auto& user_props = tp_pair.second->user_collected_properties;
3097
0
      if (user_props.find(BlockBasedTablePropertyNames::kIndexType) !=
3098
0
          user_props.end()) {
3099
0
        job_stats_->has_accurate_num_input_records = false;
3100
0
        break;
3101
0
      }
3102
0
    }
3103
10.8k
  }
3104
3105
2.77k
  for (int input_level = 0;
3106
7.19k
       input_level < static_cast<int>(compaction->num_input_levels());
3107
4.41k
       ++input_level) {
3108
4.41k
    const LevelFilesBrief* flevel = compaction->input_levels(input_level);
3109
4.41k
    size_t num_input_files = flevel->num_files;
3110
4.41k
    uint64_t* bytes_read;
3111
4.41k
    if (compaction->level(input_level) != compaction->output_level()) {
3112
1.99k
      internal_stats_.output_level_stats.num_input_files_in_non_output_levels +=
3113
1.99k
          static_cast<int>(num_input_files);
3114
1.99k
      bytes_read =
3115
1.99k
          &internal_stats_.output_level_stats.bytes_read_non_output_levels;
3116
2.41k
    } else {
3117
2.41k
      internal_stats_.output_level_stats.num_input_files_in_output_level +=
3118
2.41k
          static_cast<int>(num_input_files);
3119
2.41k
      bytes_read = &internal_stats_.output_level_stats.bytes_read_output_level;
3120
2.41k
    }
3121
15.2k
    for (size_t i = 0; i < num_input_files; ++i) {
3122
10.8k
      const FileMetaData* file_meta = flevel->files[i].file_metadata;
3123
10.8k
      *bytes_read += file_meta->fd.GetFileSize();
3124
10.8k
      uint64_t file_input_entries = file_meta->num_entries;
3125
10.8k
      uint64_t file_num_range_del = file_meta->num_range_deletions;
3126
10.8k
      if (file_input_entries == 0) {
3127
0
        uint64_t file_number = file_meta->fd.GetNumber();
3128
        // Try getting info from table property
3129
0
        std::string fn = TableFileName(compaction->immutable_options().cf_paths,
3130
0
                                       file_number, file_meta->fd.GetPathId());
3131
0
        const auto& tp = input_table_properties.find(fn);
3132
0
        if (tp != input_table_properties.end()) {
3133
0
          file_input_entries = tp->second->num_entries;
3134
0
          file_num_range_del = tp->second->num_range_deletions;
3135
0
        } else {
3136
0
          has_error = true;
3137
0
        }
3138
0
      }
3139
10.8k
      internal_stats_.output_level_stats.num_input_records +=
3140
10.8k
          file_input_entries;
3141
10.8k
      if (num_input_range_del) {
3142
10.8k
        *num_input_range_del += file_num_range_del;
3143
10.8k
      }
3144
10.8k
    }
3145
3146
4.41k
    const std::vector<FileMetaData*>& filtered_flevel =
3147
4.41k
        compaction->filtered_input_levels(input_level);
3148
4.41k
    size_t num_filtered_input_files = filtered_flevel.size();
3149
4.41k
    uint64_t* bytes_skipped;
3150
4.41k
    if (compaction->level(input_level) != compaction->output_level()) {
3151
1.99k
      internal_stats_.output_level_stats
3152
1.99k
          .num_filtered_input_files_in_non_output_levels +=
3153
1.99k
          static_cast<int>(num_filtered_input_files);
3154
1.99k
      bytes_skipped =
3155
1.99k
          &internal_stats_.output_level_stats.bytes_skipped_non_output_levels;
3156
2.41k
    } else {
3157
2.41k
      internal_stats_.output_level_stats
3158
2.41k
          .num_filtered_input_files_in_output_level +=
3159
2.41k
          static_cast<int>(num_filtered_input_files);
3160
2.41k
      bytes_skipped =
3161
2.41k
          &internal_stats_.output_level_stats.bytes_skipped_output_level;
3162
2.41k
    }
3163
4.41k
    for (const FileMetaData* filtered_file_meta : filtered_flevel) {
3164
0
      *bytes_skipped += filtered_file_meta->fd.GetFileSize();
3165
0
    }
3166
4.41k
  }
3167
3168
  // TODO - find a better place to set these two
3169
2.77k
  assert(job_stats_);
3170
2.77k
  internal_stats_.output_level_stats.bytes_read_blob =
3171
2.77k
      job_stats_->total_blob_bytes_read;
3172
2.77k
  internal_stats_.output_level_stats.num_dropped_records =
3173
2.77k
      internal_stats_.DroppedRecords();
3174
2.77k
  return !has_error;
3175
2.77k
}
3176
3177
void CompactionJob::UpdateCompactionJobInputStatsFromInternalStats(
3178
    const InternalStats::CompactionStatsFull& internal_stats,
3179
2.77k
    uint64_t num_input_range_del) const {
3180
2.77k
  assert(job_stats_);
3181
  // input information
3182
2.77k
  job_stats_->total_input_bytes =
3183
2.77k
      internal_stats.output_level_stats.bytes_read_non_output_levels +
3184
2.77k
      internal_stats.output_level_stats.bytes_read_output_level;
3185
2.77k
  job_stats_->num_input_records =
3186
2.77k
      internal_stats.output_level_stats.num_input_records - num_input_range_del;
3187
2.77k
  job_stats_->num_input_files =
3188
2.77k
      internal_stats.output_level_stats.num_input_files_in_non_output_levels +
3189
2.77k
      internal_stats.output_level_stats.num_input_files_in_output_level;
3190
2.77k
  job_stats_->num_input_files_at_output_level =
3191
2.77k
      internal_stats.output_level_stats.num_input_files_in_output_level;
3192
2.77k
  job_stats_->num_filtered_input_files =
3193
2.77k
      internal_stats.output_level_stats
3194
2.77k
          .num_filtered_input_files_in_non_output_levels +
3195
2.77k
      internal_stats.output_level_stats
3196
2.77k
          .num_filtered_input_files_in_output_level;
3197
2.77k
  job_stats_->num_filtered_input_files_at_output_level =
3198
2.77k
      internal_stats.output_level_stats
3199
2.77k
          .num_filtered_input_files_in_output_level;
3200
2.77k
  job_stats_->total_skipped_input_bytes =
3201
2.77k
      internal_stats.output_level_stats.bytes_skipped_non_output_levels +
3202
2.77k
      internal_stats.output_level_stats.bytes_skipped_output_level;
3203
3204
2.77k
  if (internal_stats.has_proximal_level_output) {
3205
0
    job_stats_->total_input_bytes +=
3206
0
        internal_stats.proximal_level_stats.bytes_read_non_output_levels +
3207
0
        internal_stats.proximal_level_stats.bytes_read_output_level;
3208
0
    job_stats_->num_input_records +=
3209
0
        internal_stats.proximal_level_stats.num_input_records;
3210
0
    job_stats_->num_input_files +=
3211
0
        internal_stats.proximal_level_stats
3212
0
            .num_input_files_in_non_output_levels +
3213
0
        internal_stats.proximal_level_stats.num_input_files_in_output_level;
3214
0
    job_stats_->num_input_files_at_output_level +=
3215
0
        internal_stats.proximal_level_stats.num_input_files_in_output_level;
3216
0
    job_stats_->num_filtered_input_files +=
3217
0
        internal_stats.proximal_level_stats
3218
0
            .num_filtered_input_files_in_non_output_levels +
3219
0
        internal_stats.proximal_level_stats
3220
0
            .num_filtered_input_files_in_output_level;
3221
0
    job_stats_->num_filtered_input_files_at_output_level +=
3222
0
        internal_stats.proximal_level_stats
3223
0
            .num_filtered_input_files_in_output_level;
3224
0
    job_stats_->total_skipped_input_bytes +=
3225
0
        internal_stats.proximal_level_stats.bytes_skipped_non_output_levels +
3226
0
        internal_stats.proximal_level_stats.bytes_skipped_output_level;
3227
0
  }
3228
2.77k
}
3229
3230
void CompactionJob::UpdateCompactionJobOutputStatsFromInternalStats(
3231
    const Status& status,
3232
2.77k
    const InternalStats::CompactionStatsFull& internal_stats) const {
3233
2.77k
  assert(job_stats_);
3234
2.77k
  job_stats_->elapsed_micros = internal_stats.output_level_stats.micros;
3235
2.77k
  job_stats_->cpu_micros = internal_stats.output_level_stats.cpu_micros;
3236
3237
  // output information
3238
2.77k
  job_stats_->total_output_bytes =
3239
2.77k
      internal_stats.output_level_stats.bytes_written;
3240
2.77k
  job_stats_->total_output_bytes_blob =
3241
2.77k
      internal_stats.output_level_stats.bytes_written_blob;
3242
2.77k
  job_stats_->num_output_records =
3243
2.77k
      internal_stats.output_level_stats.num_output_records;
3244
2.77k
  job_stats_->num_output_files =
3245
2.77k
      internal_stats.output_level_stats.num_output_files;
3246
2.77k
  job_stats_->num_output_files_blob =
3247
2.77k
      internal_stats.output_level_stats.num_output_files_blob;
3248
3249
2.77k
  if (internal_stats.has_proximal_level_output) {
3250
0
    job_stats_->total_output_bytes +=
3251
0
        internal_stats.proximal_level_stats.bytes_written;
3252
0
    job_stats_->total_output_bytes_blob +=
3253
0
        internal_stats.proximal_level_stats.bytes_written_blob;
3254
0
    job_stats_->num_output_records +=
3255
0
        internal_stats.proximal_level_stats.num_output_records;
3256
0
    job_stats_->num_output_files +=
3257
0
        internal_stats.proximal_level_stats.num_output_files;
3258
0
    job_stats_->num_output_files_blob +=
3259
0
        internal_stats.proximal_level_stats.num_output_files_blob;
3260
0
  }
3261
3262
2.77k
  if (status.ok() && job_stats_->num_output_files > 0) {
3263
1.21k
    CopyPrefix(compact_->SmallestUserKey(),
3264
1.21k
               CompactionJobStats::kMaxPrefixLength,
3265
1.21k
               &job_stats_->smallest_output_key_prefix);
3266
1.21k
    CopyPrefix(compact_->LargestUserKey(), CompactionJobStats::kMaxPrefixLength,
3267
1.21k
               &job_stats_->largest_output_key_prefix);
3268
1.21k
  }
3269
2.77k
}
3270
3271
2.77k
void CompactionJob::LogCompaction() {
3272
2.77k
  Compaction* compaction = compact_->compaction;
3273
2.77k
  ColumnFamilyData* cfd = compaction->column_family_data();
3274
  // Let's check if anything will get logged. Don't prepare all the info if
3275
  // we're not logging
3276
2.77k
  if (db_options_.info_log_level <= InfoLogLevel::INFO_LEVEL) {
3277
2.77k
    Compaction::InputLevelSummaryBuffer inputs_summary;
3278
2.77k
    ROCKS_LOG_INFO(
3279
2.77k
        db_options_.info_log, "[%s] [JOB %d] Compacting %s, score %.2f",
3280
2.77k
        cfd->GetName().c_str(), job_id_,
3281
2.77k
        compaction->InputLevelSummary(&inputs_summary), compaction->score());
3282
2.77k
    char scratch[2345];
3283
2.77k
    compaction->Summary(scratch, sizeof(scratch));
3284
2.77k
    ROCKS_LOG_INFO(db_options_.info_log, "[%s]: Compaction start summary: %s\n",
3285
2.77k
                   cfd->GetName().c_str(), scratch);
3286
    // build event logger report
3287
2.77k
    auto stream = event_logger_->Log();
3288
2.77k
    stream << "job" << job_id_ << "event" << "compaction_started" << "cf_name"
3289
2.77k
           << cfd->GetName() << "compaction_reason"
3290
2.77k
           << GetCompactionReasonString(compaction->compaction_reason());
3291
7.19k
    for (size_t i = 0; i < compaction->num_input_levels(); ++i) {
3292
4.41k
      stream << ("files_L" + std::to_string(compaction->level(i)));
3293
4.41k
      stream.StartArray();
3294
10.8k
      for (auto f : *compaction->inputs(i)) {
3295
10.8k
        stream << f->fd.GetNumber();
3296
10.8k
      }
3297
4.41k
      stream.EndArray();
3298
4.41k
    }
3299
2.77k
    stream << "score" << compaction->score() << "input_data_size"
3300
2.77k
           << compaction->CalculateTotalInputSize() << "oldest_snapshot_seqno"
3301
2.77k
           << (job_context_->snapshot_seqs.empty()
3302
2.77k
                   ? int64_t{-1}  // Use -1 for "none"
3303
2.77k
                   : static_cast<int64_t>(
3304
0
                         job_context_->GetEarliestSnapshotSequence()));
3305
2.77k
    if (compaction->SupportsPerKeyPlacement()) {
3306
0
      stream << "proximal_after_seqno" << proximal_after_seqno_;
3307
0
      stream << "preserve_seqno_after" << preserve_seqno_after_;
3308
0
      stream << "proximal_output_level" << compaction->GetProximalLevel();
3309
0
      stream << "proximal_output_range"
3310
0
             << GetCompactionProximalOutputRangeTypeString(
3311
0
                    compaction->GetProximalOutputRangeType());
3312
3313
0
      if (compaction->GetProximalOutputRangeType() ==
3314
0
          Compaction::ProximalOutputRangeType::kDisabled) {
3315
0
        ROCKS_LOG_WARN(
3316
0
            db_options_.info_log,
3317
0
            "[%s] [JOB %d] Proximal level output is disabled, likely "
3318
0
            "because of the range conflict in the proximal level",
3319
0
            cfd->GetName().c_str(), job_id_);
3320
0
      }
3321
0
    }
3322
2.77k
  }
3323
2.77k
}
3324
3325
3.38k
std::string CompactionJob::GetTableFileName(uint64_t file_number) {
3326
3.38k
  return TableFileName(compact_->compaction->immutable_options().cf_paths,
3327
3.38k
                       file_number, compact_->compaction->output_path_id());
3328
3.38k
}
3329
3330
0
const std::string& CompactionJob::GetBlobOutputPath() const {
3331
0
  return compact_->compaction->immutable_options().cf_paths.front().path;
3332
0
}
3333
3334
0
uint64_t CompactionJob::NewBlobFileNumber() {
3335
0
  return versions_->NewFileNumber();
3336
0
}
3337
3338
0
uint64_t CompactionJob::GetNextBlobFileNumber() const {
3339
0
  return versions_->current_next_file_number();
3340
0
}
3341
3342
Status CompactionJob::RestoreNextBlobFileNumber(
3343
0
    const SubcompactionProgress& subcompaction_progress) {
3344
0
  if (!subcompaction_progress.has_blob_compaction_progress) {
3345
0
    return Status::OK();
3346
0
  }
3347
3348
0
  const uint64_t next_blob_file_number =
3349
0
      subcompaction_progress.next_blob_file_number;
3350
0
  if (next_blob_file_number == kInvalidBlobFileNumber) {
3351
0
    return Status::Corruption("Invalid next blob file number in progress");
3352
0
  }
3353
0
  const uint64_t current_next_file_number =
3354
0
      versions_->current_next_file_number();
3355
0
  if (current_next_file_number < next_blob_file_number) {
3356
0
    versions_->FetchAddFileNumber(next_blob_file_number -
3357
0
                                  current_next_file_number);
3358
0
  }
3359
0
  return Status::OK();
3360
0
}
3361
3362
5.68k
Env::IOPriority CompactionJob::GetRateLimiterPriority() {
3363
5.68k
  if (versions_ && versions_->GetColumnFamilySet() &&
3364
5.68k
      versions_->GetColumnFamilySet()->write_controller()) {
3365
5.68k
    WriteController* write_controller =
3366
5.68k
        versions_->GetColumnFamilySet()->write_controller();
3367
5.68k
    if (write_controller->NeedsDelay() || write_controller->IsStopped()) {
3368
8
      return Env::IO_USER;
3369
8
    }
3370
5.68k
  }
3371
3372
5.67k
  return Env::IO_LOW;
3373
5.68k
}
3374
3375
Status CompactionJob::ReadTablePropertiesDirectly(
3376
    const ImmutableOptions& ioptions, const MutableCFOptions& moptions,
3377
    const FileMetaData* file_meta, const ReadOptions& read_options,
3378
0
    std::shared_ptr<const TableProperties>* tp) {
3379
0
  std::unique_ptr<FSRandomAccessFile> file;
3380
0
  std::string file_name = GetTableFileName(file_meta->fd.GetNumber());
3381
0
  FileOptions fopts = file_options_;
3382
0
  fopts.file_checksum = file_meta->file_checksum;
3383
0
  fopts.file_checksum_func_name = file_meta->file_checksum_func_name;
3384
0
  Status s = ioptions.fs->NewRandomAccessFile(file_name, fopts, &file,
3385
0
                                              nullptr /* dbg */);
3386
0
  if (!s.ok()) {
3387
0
    return s;
3388
0
  }
3389
3390
0
  std::unique_ptr<RandomAccessFileReader> file_reader(
3391
0
      new RandomAccessFileReader(
3392
0
          std::move(file), file_name, ioptions.clock, io_tracer_,
3393
0
          ioptions.stats, Histograms::SST_READ_MICROS /* hist_type */,
3394
0
          nullptr /* file_read_hist */, ioptions.rate_limiter.get(),
3395
0
          ioptions.listeners));
3396
3397
0
  std::unique_ptr<TableProperties> props;
3398
3399
0
  uint64_t magic_number = kBlockBasedTableMagicNumber;
3400
3401
0
  const auto* table_factory = moptions.table_factory.get();
3402
0
  if (table_factory == nullptr) {
3403
0
    return Status::Incomplete("Table factory is not set");
3404
0
  } else {
3405
0
    const auto& table_factory_name = table_factory->Name();
3406
0
    if (table_factory_name == TableFactory::kPlainTableName()) {
3407
0
      magic_number = kPlainTableMagicNumber;
3408
0
    } else if (table_factory_name == TableFactory::kCuckooTableName()) {
3409
0
      magic_number = kCuckooTableMagicNumber;
3410
0
    }
3411
0
  }
3412
3413
0
  s = ReadTableProperties(file_reader.get(), file_meta->fd.GetFileSize(),
3414
0
                          magic_number, ioptions, read_options, &props);
3415
0
  if (!s.ok()) {
3416
0
    return s;
3417
0
  }
3418
3419
0
  *tp = std::move(props);
3420
0
  return s;
3421
0
}
3422
3423
Status CompactionJob::ReadOutputFilesTableProperties(
3424
    const autovector<FileMetaData>& output_files,
3425
    const ReadOptions& read_options,
3426
    std::vector<std::shared_ptr<const TableProperties>>&
3427
        output_files_table_properties,
3428
0
    bool is_proximal_level) {
3429
  // `output_files` can be empty: with per-key placement, one of the two output
3430
  // levels may have no output file yet when the progress was saved.
3431
0
  const char* level_type = is_proximal_level ? "proximal output" : "output";
3432
3433
0
  output_files_table_properties.reserve(output_files.size());
3434
3435
0
  Status s;
3436
3437
0
  for (const FileMetaData& metadata : output_files) {
3438
0
    std::shared_ptr<const TableProperties> tp;
3439
0
    s = ReadTablePropertiesDirectly(compact_->compaction->immutable_options(),
3440
0
                                    compact_->compaction->mutable_cf_options(),
3441
0
                                    &metadata, read_options, &tp);
3442
0
    if (!s.ok()) {
3443
0
      ROCKS_LOG_ERROR(
3444
0
          db_options_.info_log,
3445
0
          "Failed to read table properties for %s level output file #%" PRIu64
3446
0
          ": %s",
3447
0
          level_type, metadata.fd.GetNumber(), s.ToString().c_str());
3448
0
      return s;
3449
0
    }
3450
3451
0
    if (tp == nullptr) {
3452
0
      ROCKS_LOG_ERROR(db_options_.info_log,
3453
0
                      "Empty table property for %s level output file #%" PRIu64
3454
0
                      "",
3455
0
                      level_type, metadata.fd.GetNumber());
3456
3457
0
      s = Status::Corruption("Empty table property for " +
3458
0
                             std::string(level_type) +
3459
0
                             " level output files during resuming");
3460
0
      return s;
3461
0
    }
3462
0
    output_files_table_properties.push_back(tp);
3463
0
  }
3464
0
  return s;
3465
0
}
3466
3467
void CompactionJob::RestoreCompactionOutputs(
3468
    const ColumnFamilyData* cfd,
3469
    const std::vector<std::shared_ptr<const TableProperties>>&
3470
        output_files_table_properties,
3471
    SubcompactionProgressPerLevel& subcompaction_progress_per_level,
3472
0
    CompactionOutputs* outputs_to_restore) {
3473
0
  assert(outputs_to_restore->GetOutputs().size() == 0);
3474
3475
0
  const auto& output_files = subcompaction_progress_per_level.GetOutputFiles();
3476
3477
0
  const bool enable_output_hash =
3478
0
      paranoid_file_checks_ ||
3479
0
      !!(compact_->compaction->mutable_cf_options().verify_output_flags &
3480
0
         VerifyOutputFlags::kVerifyIteration);
3481
3482
0
  uint64_t restored_bytes = 0;
3483
0
  for (size_t i = 0; i < output_files.size(); i++) {
3484
0
    FileMetaData file_copy = output_files[i];
3485
0
    restored_bytes += file_copy.fd.GetFileSize();
3486
3487
0
    outputs_to_restore->AddOutput(std::move(file_copy),
3488
0
                                  cfd->internal_comparator(),
3489
0
                                  enable_output_hash, true /* finished */);
3490
3491
0
    outputs_to_restore->UpdateTableProperties(
3492
0
        *output_files_table_properties[i]);
3493
0
  }
3494
3495
0
  outputs_to_restore->SetNumOutputRecords(
3496
0
      subcompaction_progress_per_level.GetNumProcessedOutputRecords());
3497
0
  outputs_to_restore->AddRestoredBytesWritten(restored_bytes);
3498
0
}
3499
3500
Status CompactionJob::RestoreBlobCompactionProgress(
3501
    SubcompactionState* sub_compact,
3502
0
    const SubcompactionProgress& subcompaction_progress) {
3503
0
  if (!subcompaction_progress.has_blob_compaction_progress) {
3504
0
    return RestoreNextBlobFileNumber(subcompaction_progress);
3505
0
  }
3506
3507
0
  CompactionOutputs* const outputs = sub_compact->Outputs(false);
3508
0
  assert(outputs);
3509
0
  assert(outputs->GetBlobFileAdditions().empty());
3510
0
  *outputs->GetBlobFileAdditionsPtr() =
3511
0
      subcompaction_progress.blob_file_additions;
3512
3513
0
  if (!subcompaction_progress.blob_file_garbages.empty()) {
3514
0
    BlobGarbageMeter* meter = outputs->GetBlobGarbageMeter();
3515
0
    if (meter == nullptr) {
3516
0
      meter = outputs->CreateBlobGarbageMeter();
3517
0
    }
3518
0
    for (const auto& garbage : subcompaction_progress.blob_file_garbages) {
3519
0
      meter->AddGarbage(garbage.GetBlobFileNumber(),
3520
0
                        garbage.GetGarbageBlobCount(),
3521
0
                        garbage.GetGarbageBlobBytes());
3522
0
    }
3523
0
  }
3524
3525
0
  return RestoreNextBlobFileNumber(subcompaction_progress);
3526
0
}
3527
3528
// Attempt to resume compaction from a previously persisted compaction progress.
3529
//
3530
// RETURNS:
3531
// - Status::OK():
3532
// * Input iterator positioned at next unprocessed key
3533
// * CompactionOutputs objects fully restored for both output and proximal
3534
// output levels in SubcompactionState, including BlobDB additions and garbage
3535
// * Compaction job statistics accurately reflect input and output records
3536
// processed for record count verification
3537
// * SST and blob file-number generation advanced to prevent conflicts with
3538
// existing outputs
3539
// - Status::NotFound(): No valid progress to resume from
3540
// - Status::Incomplete(): Blob state cannot be resumed safely
3541
// - Status::Corruption(): Resume key is invalid, beyond input range, or output
3542
// restoration failed
3543
Status CompactionJob::MaybeResumeSubcompactionProgressOnInputIterator(
3544
2.77k
    SubcompactionState* sub_compact, InternalIterator* input_iter) {
3545
2.77k
  const ReadOptions read_options(Env::IOActivity::kCompaction);
3546
2.77k
  ColumnFamilyData* cfd = sub_compact->compaction->column_family_data();
3547
2.77k
  SubcompactionProgress& subcompaction_progress =
3548
2.77k
      sub_compact->GetSubcompactionProgressRef();
3549
3550
2.77k
  if (subcompaction_progress.output_level_progress
3551
2.77k
              .GetNumProcessedOutputRecords() == 0 &&
3552
2.77k
      subcompaction_progress.proximal_output_level_progress
3553
2.77k
              .GetNumProcessedOutputRecords() == 0) {
3554
2.77k
    return Status::NotFound("No subcompaction progress to resume");
3555
2.77k
  }
3556
3557
0
  ROCKS_LOG_INFO(db_options_.info_log, "[%s] [JOB %d] Resuming compaction : %s",
3558
0
                 cfd->GetName().c_str(), job_id_,
3559
0
                 subcompaction_progress.ToString().c_str());
3560
3561
0
  input_iter->Seek(subcompaction_progress.next_internal_key_to_compact);
3562
3563
0
  if (!input_iter->Valid()) {
3564
0
    ROCKS_LOG_ERROR(db_options_.info_log,
3565
0
                    "[%s] [JOB %d] Iterator is invalid after "
3566
0
                    "seeking to the key to resume. This indicates the key is "
3567
0
                    "incorrectly beyond the input data range.",
3568
0
                    cfd->GetName().c_str(), job_id_);
3569
0
    return Status::Corruption(
3570
0
        "The key to resume is beyond the input data range");
3571
0
  } else if (!input_iter->status().ok()) {
3572
0
    ROCKS_LOG_ERROR(db_options_.info_log,
3573
0
                    "[%s] [JOB %d] Iterator has error after seeking to "
3574
0
                    "the key to resume: %s",
3575
0
                    cfd->GetName().c_str(), job_id_,
3576
0
                    input_iter->status().ToString().c_str());
3577
0
    return Status::Corruption(
3578
0
        "Iterator has error status after seeking to the key: " +
3579
0
        input_iter->status().ToString());
3580
0
  }
3581
3582
0
  sub_compact->compaction_job_stats.has_accurate_num_input_records =
3583
0
      subcompaction_progress.num_processed_input_records != 0;
3584
3585
0
  sub_compact->compaction_job_stats.num_input_records =
3586
0
      subcompaction_progress.num_processed_input_records;
3587
3588
0
  for (const bool& is_proximal_level : {false, true}) {
3589
0
    if (is_proximal_level &&
3590
0
        !sub_compact->compaction->SupportsPerKeyPlacement()) {
3591
0
      continue;
3592
0
    }
3593
3594
0
    Status s;
3595
0
    SubcompactionProgressPerLevel& subcompaction_progress_per_level =
3596
0
        is_proximal_level
3597
0
            ? subcompaction_progress.proximal_output_level_progress
3598
0
            : subcompaction_progress.output_level_progress;
3599
3600
0
    const auto& output_files =
3601
0
        subcompaction_progress_per_level.GetOutputFiles();
3602
3603
0
    std::vector<std::shared_ptr<const TableProperties>>
3604
0
        output_files_table_properties;
3605
3606
    // TODO(hx235): investigate if we can skip reading properties to save read
3607
    // IO
3608
0
    s = ReadOutputFilesTableProperties(output_files, read_options,
3609
0
                                       output_files_table_properties,
3610
0
                                       is_proximal_level);
3611
0
    if (!s.ok()) {
3612
0
      ROCKS_LOG_ERROR(
3613
0
          db_options_.info_log,
3614
0
          "[%s] [JOB %d] Failed to read table properties for %s level output "
3615
0
          "files during resume: %s.",
3616
0
          cfd->GetName().c_str(), job_id_,
3617
0
          is_proximal_level ? "proximal output" : "output",
3618
0
          s.ToString().c_str());
3619
0
      return Status::Corruption(
3620
0
          "Not able to resume due to table property reading error " +
3621
0
          s.ToString());
3622
0
    }
3623
3624
0
    RestoreCompactionOutputs(cfd, output_files_table_properties,
3625
0
                             subcompaction_progress_per_level,
3626
0
                             sub_compact->Outputs(is_proximal_level));
3627
3628
    // Skip past all the used file numbers to avoid creating new output files
3629
    // after resumption that conflict with the existing output files
3630
0
    for (const auto& file_meta : output_files) {
3631
0
      uint64_t file_number = file_meta.fd.GetNumber();
3632
0
      while (versions_->NewFileNumber() <= file_number) {
3633
0
        versions_->FetchAddFileNumber(1);
3634
0
      }
3635
0
    }
3636
0
  }
3637
3638
0
  Status blob_status =
3639
0
      RestoreBlobCompactionProgress(sub_compact, subcompaction_progress);
3640
0
  if (!blob_status.ok()) {
3641
0
    return blob_status;
3642
0
  }
3643
3644
0
  return Status::OK();
3645
0
}
3646
3647
void CompactionJob::UpdateSubcompactionProgress(
3648
    const CompactionIterator* c_iter, const Slice next_table_min_key,
3649
0
    SubcompactionState* sub_compact) {
3650
0
  assert(c_iter);
3651
0
  SubcompactionProgress& subcompaction_progress =
3652
0
      sub_compact->GetSubcompactionProgressRef();
3653
3654
0
  IterKey next_ikey_to_compact;
3655
0
  next_ikey_to_compact.SetInternalKey(ExtractUserKey(next_table_min_key),
3656
0
                                      kMaxSequenceNumber, kValueTypeForSeek);
3657
0
  subcompaction_progress.next_internal_key_to_compact =
3658
0
      next_ikey_to_compact.GetInternalKey().ToString();
3659
3660
  // Track total processed input records for progress reporting by combining:
3661
  // - Resumed count: records already processed before compaction was
3662
  // interrupted
3663
  // - Current count: records scanned in the current compaction session
3664
  // Only update when both tracking mechanisms provide accurate counts to ensure
3665
  // reliability.
3666
0
  subcompaction_progress.num_processed_input_records =
3667
0
      c_iter->HasNumInputEntryScanned() &&
3668
0
              sub_compact->compaction_job_stats.has_accurate_num_input_records
3669
0
          ? c_iter->NumInputEntryScanned() +
3670
0
                sub_compact->compaction_job_stats.num_input_records
3671
0
          : 0;
3672
3673
0
  UpdateSubcompactionProgressPerLevel(
3674
0
      sub_compact, false /* is_proximal_level */, subcompaction_progress);
3675
3676
0
  if (sub_compact->compaction->SupportsPerKeyPlacement()) {
3677
0
    UpdateSubcompactionProgressPerLevel(
3678
0
        sub_compact, true /* is_proximal_level */, subcompaction_progress);
3679
0
  }
3680
3681
0
  UpdateBlobCompactionProgress(sub_compact, subcompaction_progress);
3682
0
}
3683
3684
void CompactionJob::UpdateBlobCompactionProgress(
3685
    SubcompactionState* sub_compact,
3686
0
    SubcompactionProgress& subcompaction_progress) {
3687
0
  CompactionOutputs* const outputs = sub_compact->Outputs(false);
3688
0
  assert(outputs);
3689
3690
0
  const MutableCFOptions& mutable_cf_options =
3691
0
      sub_compact->compaction->mutable_cf_options();
3692
0
  const bool can_create_blob_files =
3693
0
      mutable_cf_options.enable_blob_files &&
3694
0
      sub_compact->compaction->output_level() >=
3695
0
          mutable_cf_options.blob_file_starting_level;
3696
0
  const BlobGarbageMeter* garbage_meter =
3697
0
      sub_compact->GetBlobGarbageCheckpoint();
3698
0
  if (garbage_meter == nullptr) {
3699
0
    garbage_meter = outputs->GetBlobGarbageMeter();
3700
0
  }
3701
0
  if (!can_create_blob_files && garbage_meter == nullptr) {
3702
0
    return;
3703
0
  }
3704
3705
0
  subcompaction_progress.has_blob_compaction_progress = true;
3706
0
  subcompaction_progress.blob_file_additions = outputs->GetBlobFileAdditions();
3707
0
  subcompaction_progress.blob_file_garbages.clear();
3708
0
  if (garbage_meter != nullptr) {
3709
0
    for (const auto& [file_number, flow] : garbage_meter->flows()) {
3710
0
      assert(flow.IsValid());
3711
0
      if (flow.HasGarbage()) {
3712
0
        subcompaction_progress.blob_file_garbages.emplace_back(
3713
0
            file_number, flow.GetGarbageCount(), flow.GetGarbageBytes());
3714
0
      }
3715
0
    }
3716
0
    std::sort(subcompaction_progress.blob_file_garbages.begin(),
3717
0
              subcompaction_progress.blob_file_garbages.end(),
3718
0
              [](const BlobFileGarbage& lhs, const BlobFileGarbage& rhs) {
3719
0
                return lhs.GetBlobFileNumber() < rhs.GetBlobFileNumber();
3720
0
              });
3721
0
  }
3722
0
  subcompaction_progress.next_blob_file_number = GetNextBlobFileNumber();
3723
0
}
3724
3725
void CompactionJob::UpdateSubcompactionProgressPerLevel(
3726
    SubcompactionState* sub_compact, bool is_proximal_level,
3727
0
    SubcompactionProgress& subcompaction_progress) {
3728
0
  SubcompactionProgressPerLevel& subcompaction_progress_per_level =
3729
0
      is_proximal_level ? subcompaction_progress.proximal_output_level_progress
3730
0
                        : subcompaction_progress.output_level_progress;
3731
3732
0
  subcompaction_progress_per_level.SetNumProcessedOutputRecords(
3733
0
      sub_compact->OutputStats(is_proximal_level)->num_output_records);
3734
3735
0
  const auto& prev_output_files =
3736
0
      subcompaction_progress_per_level.GetOutputFiles();
3737
3738
0
  const auto& current_output_files =
3739
0
      sub_compact->Outputs(is_proximal_level)->GetOutputs();
3740
3741
0
  for (size_t i = prev_output_files.size(); i < current_output_files.size();
3742
0
       i++) {
3743
0
    subcompaction_progress_per_level.AddToOutputFiles(
3744
0
        current_output_files[i].meta);
3745
0
  }
3746
0
}
3747
3748
Status CompactionJob::PersistSubcompactionProgress(
3749
0
    SubcompactionState* sub_compact) {
3750
0
  SubcompactionProgress& subcompaction_progress =
3751
0
      sub_compact->GetSubcompactionProgressRef();
3752
3753
0
  assert(compaction_progress_writer_);
3754
3755
0
  VersionEdit edit;
3756
0
  edit.SetSubcompactionProgress(subcompaction_progress);
3757
3758
0
  std::string record;
3759
0
  if (!edit.EncodeTo(&record)) {
3760
0
    ROCKS_LOG_ERROR(
3761
0
        db_options_.info_log,
3762
0
        "[%s] [JOB %d] Failed to encode subcompaction "
3763
0
        "progress",
3764
0
        compact_->compaction->column_family_data()->GetName().c_str(), job_id_);
3765
0
    return Status::Corruption("Failed to encode subcompaction progress");
3766
0
  }
3767
3768
0
  WriteOptions write_options(Env::IOActivity::kCompaction);
3769
0
  Status s = compaction_progress_writer_->AddRecord(write_options, record);
3770
0
  IOOptions opts;
3771
0
  if (s.ok()) {
3772
0
    s = WritableFileWriter::PrepareIOOptions(write_options, opts);
3773
0
  }
3774
0
  if (s.ok()) {
3775
0
    s = db_options_.use_fsync ? compaction_progress_writer_->file()->Fsync(opts)
3776
0
                              : compaction_progress_writer_->file()->Sync(opts);
3777
0
  }
3778
3779
0
  if (!s.ok()) {
3780
0
    ROCKS_LOG_ERROR(
3781
0
        db_options_.info_log,
3782
0
        "[%s] [JOB %d] Failed to persist subcompaction "
3783
0
        "progress: %s",
3784
0
        compact_->compaction->column_family_data()->GetName().c_str(), job_id_,
3785
0
        s.ToString().c_str());
3786
0
    return s;
3787
0
  }
3788
3789
0
  TEST_SYNC_POINT("CompactionJob::PersistSubcompactionProgress:Complete");
3790
3791
0
  subcompaction_progress.output_level_progress
3792
0
      .UpdateLastPersistedOutputFilesCount();
3793
3794
0
  subcompaction_progress.proximal_output_level_progress
3795
0
      .UpdateLastPersistedOutputFilesCount();
3796
3797
0
  subcompaction_progress.UpdateLastPersistedBlobFileAdditionsCount();
3798
3799
0
  return Status::OK();
3800
0
}
3801
3802
Status CompactionJob::VerifyInputRecordCount(
3803
2.07k
    uint64_t num_input_range_del) const {
3804
2.07k
  size_t ts_sz = compact_->compaction->column_family_data()
3805
2.07k
                     ->user_comparator()
3806
2.07k
                     ->timestamp_size();
3807
  // When trim_ts_ is non-empty, CompactionIterator takes
3808
  // HistoryTrimmingIterator as input iterator and sees a trimmed view of
3809
  // input keys. So the number of keys it processed is not suitable for
3810
  // verification here.
3811
  // TODO: support verification when trim_ts_ is non-empty.
3812
2.07k
  if (!(ts_sz > 0 && !trim_ts_.empty())) {
3813
2.07k
    assert(internal_stats_.output_level_stats.num_input_records > 0);
3814
    // TODO: verify the number of range deletion entries.
3815
2.07k
    uint64_t expected = internal_stats_.output_level_stats.num_input_records -
3816
2.07k
                        num_input_range_del;
3817
2.07k
    uint64_t actual = job_stats_->num_input_records;
3818
2.07k
    TEST_SYNC_POINT_CALLBACK("CompactionJob::VerifyInputRecordCount:Actual",
3819
2.07k
                             &actual);
3820
2.07k
    if (expected != actual) {
3821
0
      char scratch[2345];
3822
0
      compact_->compaction->Summary(scratch, sizeof(scratch));
3823
0
      std::string msg =
3824
0
          "Compaction number of input keys does not match "
3825
0
          "number of keys processed. Expected " +
3826
0
          std::to_string(expected) + " but processed " +
3827
0
          std::to_string(actual) + ". Compaction summary: " + scratch;
3828
0
      ROCKS_LOG_WARN(
3829
0
          db_options_.info_log,
3830
0
          "[%s] [JOB %d] VerifyInputRecordCount() Status: %s",
3831
0
          compact_->compaction->column_family_data()->GetName().c_str(),
3832
0
          job_context_->job_id, msg.c_str());
3833
0
      if (db_options_.compaction_verify_record_count) {
3834
0
        return Status::Corruption(msg);
3835
0
      }
3836
0
    }
3837
2.07k
  }
3838
2.07k
  return Status::OK();
3839
2.07k
}
3840
3841
2.07k
Status CompactionJob::VerifyOutputRecordCount() const {
3842
2.07k
  uint64_t total_output_num = 0;
3843
2.07k
  for (const auto& state : compact_->sub_compact_states) {
3844
2.07k
    for (const auto& output : state.GetOutputs()) {
3845
1.21k
      total_output_num += output.table_properties->num_entries -
3846
1.21k
                          output.table_properties->num_range_deletions;
3847
1.21k
    }
3848
2.07k
  }
3849
3850
2.07k
  uint64_t expected = internal_stats_.output_level_stats.num_output_records;
3851
2.07k
  if (internal_stats_.has_proximal_level_output) {
3852
0
    expected += internal_stats_.proximal_level_stats.num_output_records;
3853
0
  }
3854
2.07k
  if (expected != total_output_num) {
3855
0
    char scratch[2345];
3856
0
    compact_->compaction->Summary(scratch, sizeof(scratch));
3857
0
    std::string msg =
3858
0
        "Number of keys in compaction output SST files does not match "
3859
0
        "number of keys added. Expected " +
3860
0
        std::to_string(expected) + " but there are " +
3861
0
        std::to_string(total_output_num) +
3862
0
        " in output SST files. Compaction summary: " + scratch;
3863
0
    ROCKS_LOG_WARN(
3864
0
        db_options_.info_log,
3865
0
        "[%s] [JOB %d] VerifyOutputRecordCount() status: %s",
3866
0
        compact_->compaction->column_family_data()->GetName().c_str(),
3867
0
        job_context_->job_id, msg.c_str());
3868
0
    if (db_options_.compaction_verify_record_count) {
3869
0
      return Status::Corruption(msg);
3870
0
    }
3871
0
  }
3872
2.07k
  return Status::OK();
3873
2.07k
}
3874
}  // namespace ROCKSDB_NAMESPACE