Coverage Report

Created: 2026-11-12 08:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/rocksdb/db/db_impl/db_impl_files.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
#include <array>
10
#include <cinttypes>
11
#include <set>
12
#include <unordered_set>
13
14
#include "db/blob/blob_file_partition_manager.h"
15
#include "db/blob/blob_log_format.h"
16
#include "db/db_impl/db_impl.h"
17
#include "db/event_helpers.h"
18
#include "db/memtable_list.h"
19
#include "file/file_util.h"
20
#include "file/filename.h"
21
#include "file/sst_file_manager_impl.h"
22
#include "logging/logging.h"
23
#include "monitoring/thread_status_util.h"
24
#include "port/port.h"
25
#include "rocksdb/options.h"
26
#include "util/autovector.h"
27
#include "util/defer.h"
28
29
namespace ROCKSDB_NAMESPACE {
30
31
namespace {
32
33
0
Env::IOActivity GetCurrentThreadIOActivityForMetadataRead() {
34
0
  switch (ThreadStatusUtil::GetThreadOperation()) {
35
0
    case ThreadStatus::OperationType::OP_FLUSH:
36
0
      return Env::IOActivity::kFlush;
37
0
    case ThreadStatus::OperationType::OP_COMPACTION:
38
0
      return Env::IOActivity::kCompaction;
39
0
    case ThreadStatus::OperationType::OP_DBOPEN:
40
0
      return Env::IOActivity::kDBOpen;
41
0
    case ThreadStatus::OperationType::OP_GET:
42
0
      return Env::IOActivity::kGet;
43
0
    case ThreadStatus::OperationType::OP_MULTIGET:
44
0
      return Env::IOActivity::kMultiGet;
45
0
    case ThreadStatus::OperationType::OP_DBITERATOR:
46
0
      return Env::IOActivity::kDBIterator;
47
0
    case ThreadStatus::OperationType::OP_VERIFY_DB_CHECKSUM:
48
0
      return Env::IOActivity::kVerifyDBChecksum;
49
0
    case ThreadStatus::OperationType::OP_VERIFY_FILE_CHECKSUMS:
50
0
      return Env::IOActivity::kVerifyFileChecksums;
51
0
    case ThreadStatus::OperationType::OP_GETENTITY:
52
0
      return Env::IOActivity::kGetEntity;
53
0
    case ThreadStatus::OperationType::OP_MULTIGETENTITY:
54
0
      return Env::IOActivity::kMultiGetEntity;
55
0
    case ThreadStatus::OperationType::
56
0
        OP_GET_FILE_CHECKSUMS_FROM_CURRENT_MANIFEST:
57
0
      return Env::IOActivity::kGetFileChecksumsFromCurrentManifest;
58
0
    case ThreadStatus::OperationType::OP_LAZY_RESOLVE:
59
0
      return Env::IOActivity::kLazyResolve;
60
0
    case ThreadStatus::OperationType::OP_UNKNOWN:
61
0
    case ThreadStatus::OperationType::NUM_OP_TYPES:
62
0
    default:
63
0
      return Env::IOActivity::kUnknown;
64
0
  }
65
0
}
66
67
// A full-scan obsolete-file purge can observe a newly created direct-write
68
// blob file before it is added to the manager's active-file set. Those
69
// in-flight files are still missing their footer, so conservatively keep any
70
// footer-less blob file here rather than risk deleting a live write-path file.
71
bool ShouldKeepFooterlessBlobFile(FileSystem* fs,
72
                                  const FileOptions& file_options,
73
0
                                  const std::string& blob_file_path) {
74
0
  assert(fs != nullptr);
75
76
0
  constexpr IODebugContext* dbg = nullptr;
77
  // This purge path can run from DB open, flush/compaction cleanup, or
78
  // iterator-triggered obsolete-file cleanup. Tag the probe with the current
79
  // thread activity so db_stress keeps validating the read against the active
80
  // operation instead of seeing an unexpected kUnknown metadata read.
81
0
  IOOptions io_options;
82
0
  io_options.io_activity = GetCurrentThreadIOActivityForMetadataRead();
83
0
  uint64_t file_size = 0;
84
0
  IOStatus io_s = fs->GetFileSize(blob_file_path, io_options, &file_size, dbg);
85
0
  if (!io_s.ok()) {
86
0
    return !io_s.IsPathNotFound();
87
0
  }
88
89
0
  if (file_size < BlobLogHeader::kSize + BlobLogFooter::kSize) {
90
0
    return true;
91
0
  }
92
93
0
  FileOptions read_file_options = file_options;
94
0
  read_file_options.use_direct_reads = false;
95
96
0
  std::unique_ptr<FSRandomAccessFile> file;
97
0
  io_s = fs->NewRandomAccessFile(blob_file_path, read_file_options, &file, dbg);
98
0
  if (!io_s.ok()) {
99
0
    return !io_s.IsPathNotFound();
100
0
  }
101
102
0
  std::array<char, BlobLogFooter::kSize> scratch{};
103
0
  Slice footer_slice;
104
0
  io_s = file->Read(file_size - BlobLogFooter::kSize, BlobLogFooter::kSize,
105
0
                    io_options, &footer_slice, scratch.data(), dbg);
106
0
  if (!io_s.ok()) {
107
0
    return !io_s.IsPathNotFound();
108
0
  }
109
110
0
  if (footer_slice.size() != BlobLogFooter::kSize) {
111
0
    return true;
112
0
  }
113
114
0
  BlobLogFooter footer;
115
0
  return !footer.DecodeFrom(footer_slice).ok();
116
0
}
117
118
}  // namespace
119
120
149k
uint64_t DBImpl::MinLogNumberToKeep() {
121
149k
  return versions_->min_log_number_to_keep();
122
149k
}
123
124
0
uint64_t DBImpl::MinLogNumberToRecycle() { return min_wal_number_to_recycle_; }
125
126
0
bool DBImpl::IsWalInLogs(uint64_t wal_number) {
127
0
  InstrumentedMutexLock wal_lock(&wal_write_mutex_);
128
0
  for (const auto& wal : logs_) {
129
0
    if (wal.number == wal_number) {
130
0
      return true;
131
0
    }
132
0
  }
133
0
  return false;
134
0
}
135
136
113k
uint64_t DBImpl::MinObsoleteSstNumberToKeep() {
137
113k
  mutex_.AssertHeld();
138
113k
  if (!pending_outputs_.empty()) {
139
310
    return *pending_outputs_.begin();
140
310
  }
141
112k
  return std::numeric_limits<uint64_t>::max();
142
113k
}
143
144
0
uint64_t DBImpl::GetObsoleteSstFilesSize() {
145
0
  mutex_.AssertHeld();
146
0
  return versions_->GetObsoleteSstFilesSize();
147
0
}
148
149
113k
uint64_t DBImpl::MinOptionsFileNumberToKeep() {
150
113k
  mutex_.AssertHeld();
151
113k
  if (!min_options_file_numbers_.empty()) {
152
0
    return *min_options_file_numbers_.begin();
153
0
  }
154
113k
  return std::numeric_limits<uint64_t>::max();
155
113k
}
156
157
0
Status DBImpl::DisableFileDeletions() {
158
0
  Status s;
159
0
  int my_disable_delete_obsolete_files;
160
0
  {
161
0
    InstrumentedMutexLock l(&mutex_);
162
0
    s = DisableFileDeletionsWithLock();
163
0
    my_disable_delete_obsolete_files = disable_delete_obsolete_files_;
164
0
  }
165
0
  if (my_disable_delete_obsolete_files == 1) {
166
0
    ROCKS_LOG_INFO(immutable_db_options_.info_log, "File Deletions Disabled");
167
0
  } else {
168
0
    ROCKS_LOG_INFO(immutable_db_options_.info_log,
169
0
                   "File Deletions Disabled, but already disabled. Counter: %d",
170
0
                   my_disable_delete_obsolete_files);
171
0
  }
172
0
  return s;
173
0
}
174
175
// FIXME: can be inconsistent with DisableFileDeletions in cases like
176
// DBImplReadOnly
177
0
Status DBImpl::DisableFileDeletionsWithLock() {
178
0
  mutex_.AssertHeld();
179
0
  ++disable_delete_obsolete_files_;
180
0
  return Status::OK();
181
0
}
182
183
0
Status DBImpl::EnableFileDeletions() {
184
  // Job id == 0 means that this is not our background process, but rather
185
  // user thread
186
0
  JobContext job_context(0);
187
0
  int saved_counter;  // initialize on all paths
188
0
  {
189
0
    InstrumentedMutexLock l(&mutex_);
190
0
    if (disable_delete_obsolete_files_ > 0) {
191
0
      --disable_delete_obsolete_files_;
192
0
    }
193
0
    saved_counter = disable_delete_obsolete_files_;
194
0
    if (saved_counter == 0) {
195
0
      FindObsoleteFiles(&job_context, true);
196
0
      bg_cv_.SignalAll();
197
0
    }
198
0
  }
199
0
  if (saved_counter == 0) {
200
0
    ROCKS_LOG_INFO(immutable_db_options_.info_log, "File Deletions Enabled");
201
0
    if (job_context.HaveSomethingToDelete()) {
202
0
      PurgeObsoleteFiles(job_context);
203
0
    }
204
0
  } else {
205
0
    ROCKS_LOG_INFO(immutable_db_options_.info_log,
206
0
                   "File Deletions Enable, but not really enabled. Counter: %d",
207
0
                   saved_counter);
208
0
  }
209
0
  job_context.Clean();
210
0
  LogFlush(immutable_db_options_.info_log);
211
0
  return Status::OK();
212
0
}
213
214
0
bool DBImpl::IsFileDeletionsEnabled() const {
215
0
  return 0 == disable_delete_obsolete_files_;
216
0
}
217
218
// * Returns the list of live files in 'sst_live' and 'blob_live'.
219
// If it's doing full scan:
220
// * Returns the list of all files in the filesystem in
221
// 'full_scan_candidate_files'.
222
// Otherwise, gets obsolete files from VersionSet.
223
// no_full_scan = true -- never do the full scan using GetChildren()
224
// force = false -- don't force the full scan, except every
225
//  mutable_db_options_.delete_obsolete_files_period_micros
226
// force = true -- force the full scan
227
void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
228
113k
                               bool no_full_scan) {
229
113k
  mutex_.AssertHeld();
230
231
  // if deletion is disabled, do nothing
232
113k
  if (disable_delete_obsolete_files_ > 0) {
233
0
    return;
234
0
  }
235
236
113k
  bool doing_the_full_scan = false;
237
238
  // logic for figuring out if we're doing the full scan
239
113k
  if (no_full_scan) {
240
15.6k
    doing_the_full_scan = false;
241
97.5k
  } else if (force ||
242
89.7k
             mutable_db_options_.delete_obsolete_files_period_micros == 0) {
243
89.7k
    doing_the_full_scan = true;
244
89.7k
  } else {
245
7.79k
    const uint64_t now_micros = immutable_db_options_.clock->NowMicros();
246
7.79k
    if ((delete_obsolete_files_last_run_ +
247
7.79k
         mutable_db_options_.delete_obsolete_files_period_micros) <
248
7.79k
        now_micros) {
249
0
      doing_the_full_scan = true;
250
0
      delete_obsolete_files_last_run_ = now_micros;
251
0
    }
252
7.79k
  }
253
254
  // don't delete files that might be currently written to from compaction
255
  // threads
256
  // Since job_context->min_pending_output is set, until file scan finishes,
257
  // mutex_ cannot be released. Otherwise, we might see no min_pending_output
258
  // here but later find newer generated unfinalized files while scanning.
259
113k
  assert(job_context != nullptr);
260
113k
  job_context->min_pending_output = MinObsoleteSstNumberToKeep();
261
113k
  job_context->files_to_quarantine = error_handler_.GetFilesToQuarantine();
262
113k
  job_context->min_options_file_number = MinOptionsFileNumberToKeep();
263
113k
  job_context->manifest_options_file_number =
264
113k
      versions_->has_committed_options_file_number()
265
113k
          ? versions_->options_file_number()
266
113k
          : 0;
267
113k
  job_context->min_blob_file_number_to_keep =
268
113k
      versions_->current_next_file_number();
269
145k
  for (auto* cfd : *versions_->GetColumnFamilySet()) {
270
145k
    auto* mgr = cfd->blob_partition_manager();
271
145k
    if (mgr != nullptr) {
272
0
      mgr->GetActiveBlobFileNumbers(
273
0
          &job_context->active_blob_direct_write_files);
274
0
      mgr->GetProtectedBlobFileNumbers(
275
0
          &job_context->active_blob_direct_write_files);
276
0
    }
277
145k
  }
278
279
  // Get obsolete files.  This function will also update the list of
280
  // pending files in VersionSet().
281
113k
  assert(versions_);
282
113k
  versions_->GetObsoleteFiles(
283
113k
      &job_context->sst_delete_files, &job_context->blob_delete_files,
284
113k
      &job_context->manifest_delete_files, job_context->min_pending_output);
285
286
  // Mark the elements in job_context->sst_delete_files and
287
  // job_context->blob_delete_files as "grabbed for purge" so that other threads
288
  // calling FindObsoleteFiles with full_scan=true will not add these files to
289
  // candidate list for purge.
290
113k
  for (const auto& sst_to_del : job_context->sst_delete_files) {
291
11.9k
    MarkAsGrabbedForPurge(sst_to_del.metadata->fd.GetNumber());
292
11.9k
  }
293
294
113k
  for (const auto& blob_file : job_context->blob_delete_files) {
295
0
    MarkAsGrabbedForPurge(blob_file.GetBlobFileNumber());
296
0
  }
297
298
  // store the current filenum, lognum, etc
299
113k
  job_context->manifest_file_number = versions_->manifest_file_number();
300
113k
  job_context->pending_manifest_file_number =
301
113k
      versions_->pending_manifest_file_number();
302
113k
  job_context->log_number = MinLogNumberToKeep();
303
113k
  job_context->prev_log_number = versions_->prev_log_number();
304
305
113k
  if (doing_the_full_scan) {
306
89.7k
    versions_->AddLiveFiles(&job_context->sst_live, &job_context->blob_live);
307
    // Preserve uncommitted ingestion files in this purge snapshot.
308
89.7k
    for (const auto& [first_file_number, num_files] :
309
89.7k
         pending_ingestion_outputs_) {
310
0
      for (uint64_t i = 0; i < num_files; ++i) {
311
0
        job_context->sst_live.push_back(first_file_number + i);
312
0
      }
313
0
    }
314
89.7k
    InfoLogPrefix info_log_prefix(!immutable_db_options_.db_log_dir.empty(),
315
89.7k
                                  dbname_);
316
    // PurgeObsoleteFiles will dedupe duplicate files.
317
89.7k
    IOOptions io_opts;
318
89.7k
    io_opts.do_not_recurse = true;
319
89.7k
    for (auto& path : CollectAllDBPaths()) {
320
      // set of all files in the directory. We'll exclude files that are still
321
      // alive in the subsequent processings.
322
89.7k
      std::vector<std::string> files;
323
89.7k
      Status s = immutable_db_options_.fs->GetChildren(
324
89.7k
          path, io_opts, &files, /*IODebugContext*=*/nullptr);
325
89.7k
      s.PermitUncheckedError();  // TODO: What should we do on error?
326
1.56M
      for (const std::string& file : files) {
327
1.56M
        uint64_t number;
328
1.56M
        FileType type;
329
        // 1. If we cannot parse the file name, we skip;
330
        // 2. If the file with file_number equals number has already been
331
        // grabbed for purge by another compaction job, or it has already been
332
        // schedule for purge, we also skip it if we
333
        // are doing full scan in order to avoid double deletion of the same
334
        // file under race conditions. See
335
        // https://github.com/meta/rocksdb/issues/3573
336
1.56M
        if (!ParseFileName(file, &number, info_log_prefix.prefix, &type) ||
337
1.56M
            !ShouldPurge(number)) {
338
5.42k
          continue;
339
5.42k
        }
340
341
        // TODO(icanadi) clean up this mess to avoid having one-off "/"
342
        // prefixes
343
1.56M
        job_context->full_scan_candidate_files.emplace_back("/" + file, path);
344
1.56M
      }
345
89.7k
    }
346
347
    // Add log files in wal_dir
348
89.7k
    if (!immutable_db_options_.IsWalDirSameAsDBPath(dbname_)) {
349
0
      std::vector<std::string> log_files;
350
0
      Status s = immutable_db_options_.fs->GetChildren(
351
0
          immutable_db_options_.wal_dir, io_opts, &log_files,
352
0
          /*IODebugContext*=*/nullptr);
353
0
      s.PermitUncheckedError();  // TODO: What should we do on error?
354
0
      for (const std::string& log_file : log_files) {
355
0
        job_context->full_scan_candidate_files.emplace_back(
356
0
            log_file, immutable_db_options_.wal_dir);
357
0
      }
358
0
    }
359
360
    // Add info log files in db_log_dir
361
89.7k
    if (!immutable_db_options_.db_log_dir.empty() &&
362
0
        immutable_db_options_.db_log_dir != dbname_) {
363
0
      std::vector<std::string> info_log_files;
364
0
      Status s = immutable_db_options_.fs->GetChildren(
365
0
          immutable_db_options_.db_log_dir, io_opts, &info_log_files,
366
0
          /*IODebugContext*=*/nullptr);
367
0
      s.PermitUncheckedError();  // TODO: What should we do on error?
368
0
      for (std::string& log_file : info_log_files) {
369
0
        job_context->full_scan_candidate_files.emplace_back(
370
0
            log_file, immutable_db_options_.db_log_dir);
371
0
      }
372
0
    }
373
89.7k
  } else {
374
    // Instead of filling ob_context->sst_live and job_context->blob_live,
375
    // directly remove files that show up in any Version. This is because
376
    // candidate files tend to be a small percentage of all files, so it is
377
    // usually cheaper to check them against every version, compared to
378
    // building a map for all files.
379
23.4k
    versions_->RemoveLiveFiles(job_context->sst_delete_files,
380
23.4k
                               job_context->blob_delete_files);
381
23.4k
  }
382
383
  // Before potentially releasing mutex and waiting on condvar, increment
384
  // pending_purge_obsolete_files_ so that another thread executing
385
  // `GetSortedWals` will wait until this thread finishes execution since the
386
  // other thread will be waiting for `pending_purge_obsolete_files_`.
387
  // pending_purge_obsolete_files_ MUST be decremented if there is nothing to
388
  // delete.
389
113k
  ++pending_purge_obsolete_files_;
390
391
113k
  Defer cleanup([job_context, this]() {
392
113k
    assert(job_context != nullptr);
393
113k
    if (!job_context->HaveSomethingToDelete()) {
394
16.5k
      mutex_.AssertHeld();
395
16.5k
      --pending_purge_obsolete_files_;
396
16.5k
      if (pending_purge_obsolete_files_ == 0) {
397
16.5k
        bg_cv_.SignalAll();
398
16.5k
      }
399
16.5k
    }
400
113k
  });
401
402
  // logs_ is empty when called during recovery, in which case there can't yet
403
  // be any tracked obsolete logs
404
113k
  wal_write_mutex_.Lock();
405
406
113k
  if (alive_wal_files_.empty() || logs_.empty()) {
407
0
    mutex_.AssertHeld();
408
    // We may reach here if the db is DBImplSecondary
409
0
    wal_write_mutex_.Unlock();
410
0
    return;
411
0
  }
412
413
113k
  bool mutex_unlocked = false;
414
113k
  if (!alive_wal_files_.empty() && !logs_.empty()) {
415
113k
    uint64_t min_log_number = job_context->log_number;
416
113k
    size_t num_alive_wal_files = alive_wal_files_.size();
417
    // find newly obsoleted log files
418
115k
    while (alive_wal_files_.begin()->number < min_log_number) {
419
1.81k
      auto& earliest = *alive_wal_files_.begin();
420
1.81k
      if (immutable_db_options_.recycle_log_file_num >
421
1.81k
              wal_recycle_files_.size() &&
422
0
          earliest.number >= MinLogNumberToRecycle()) {
423
0
        ROCKS_LOG_INFO(immutable_db_options_.info_log,
424
0
                       "adding log %" PRIu64 " to recycle list\n",
425
0
                       earliest.number);
426
0
        wal_recycle_files_.push_back(earliest.number);
427
1.81k
      } else {
428
1.81k
        job_context->log_delete_files.push_back(earliest.number);
429
1.81k
      }
430
1.81k
      if (job_context->size_log_to_delete == 0) {
431
1.81k
        job_context->prev_wals_total_size = wals_total_size_.LoadRelaxed();
432
1.81k
        job_context->num_alive_wal_files = num_alive_wal_files;
433
1.81k
      }
434
1.81k
      job_context->size_log_to_delete += earliest.size;
435
1.81k
      wals_total_size_.FetchSubRelaxed(earliest.size);
436
1.81k
      alive_wal_files_.pop_front();
437
438
      // Current log should always stay alive since it can't have
439
      // number < MinLogNumber().
440
1.81k
      assert(alive_wal_files_.size());
441
1.81k
    }
442
113k
    wal_write_mutex_.Unlock();
443
113k
    mutex_.Unlock();
444
113k
    mutex_unlocked = true;
445
113k
    TEST_SYNC_POINT_CALLBACK("FindObsoleteFiles::PostMutexUnlock", nullptr);
446
113k
    wal_write_mutex_.Lock();
447
115k
    while (!logs_.empty() && logs_.front().number < min_log_number) {
448
1.81k
      auto& log = logs_.front();
449
1.81k
      if (log.IsSyncing()) {
450
0
        wal_sync_cv_.Wait();
451
        // logs_ could have changed while we were waiting.
452
0
        continue;
453
0
      }
454
      // This WAL file is not live, so it's OK if we never sync the rest of it.
455
      // If it's already closed, then it's been fully synced. If
456
      // !background_close_inactive_wals then we need to Close it before
457
      // removing from logs_ but not blocking while holding wal_write_mutex_.
458
      // A WAL that may be recycled must also be closed before it leaves
459
      // logs_, because it can be reused as soon as it is gone from there.
460
1.81k
      if ((!immutable_db_options_.background_close_inactive_wals ||
461
0
           immutable_db_options_.recycle_log_file_num > 0) &&
462
1.81k
          log.writer->file()) {
463
        // We are taking ownership of and pinning the front entry, so we can
464
        // expect it to be the same after releasing and re-acquiring the lock
465
1.81k
        log.PrepareForSync();
466
1.81k
        wal_write_mutex_.Unlock();
467
        // TODO: maybe check the return value of Close.
468
        // TODO: plumb Env::IOActivity, Env::IOPriority
469
1.81k
        auto s = log.writer->file()->Close({});
470
1.81k
        s.PermitUncheckedError();
471
1.81k
        wal_write_mutex_.Lock();
472
1.81k
        log.writer->PublishIfClosed();
473
1.81k
        assert(&log == &logs_.front());
474
1.81k
        log.FinishSync();
475
1.81k
        wal_sync_cv_.SignalAll();
476
1.81k
      }
477
1.81k
      wals_to_free_.push_back(log.ReleaseWriter());
478
1.81k
      logs_.pop_front();
479
1.81k
    }
480
    // Current log cannot be obsolete.
481
113k
    assert(!logs_.empty());
482
113k
  }
483
484
  // We're just cleaning up for DB::Write().
485
113k
  assert(job_context->wals_to_free.empty());
486
113k
  job_context->wals_to_free = wals_to_free_;
487
488
113k
  wals_to_free_.clear();
489
113k
  wal_write_mutex_.Unlock();
490
113k
  if (mutex_unlocked) {
491
113k
    mutex_.Lock();
492
113k
  }
493
113k
  job_context->log_recycle_files.assign(wal_recycle_files_.begin(),
494
113k
                                        wal_recycle_files_.end());
495
113k
}
496
497
// Delete obsolete files and log status and information of file deletion
498
void DBImpl::DeleteObsoleteFileImpl(int job_id, const std::string& fname,
499
                                    const std::string& path_to_sync,
500
89.7k
                                    FileType type, uint64_t number) {
501
89.7k
  TEST_SYNC_POINT_CALLBACK("DBImpl::DeleteObsoleteFileImpl::BeforeDeletion",
502
89.7k
                           const_cast<std::string*>(&fname));
503
89.7k
  IGNORE_STATUS_IF_ERROR(Status::IOError());
504
505
89.7k
  Status file_deletion_status;
506
89.7k
  if (type == kTableFile || type == kBlobFile || type == kWalFile) {
507
    // Rate limit WAL deletion only if its in the DB dir
508
44.8k
    file_deletion_status = DeleteDBFile(
509
44.8k
        &immutable_db_options_, fname, path_to_sync,
510
44.8k
        /*force_bg=*/false,
511
44.8k
        /*force_fg=*/(type == kWalFile) ? !wal_in_db_path_ : false);
512
44.8k
  } else {
513
44.8k
    file_deletion_status = env_->DeleteFile(fname);
514
44.8k
  }
515
89.7k
  TEST_SYNC_POINT_CALLBACK("DBImpl::DeleteObsoleteFileImpl:AfterDeletion",
516
89.7k
                           &file_deletion_status);
517
89.7k
  if (file_deletion_status.ok()) {
518
89.7k
    ROCKS_LOG_DEBUG(immutable_db_options_.info_log,
519
89.7k
                    "[JOB %d] [db_session_id=%s] Delete %s type=%d #%" PRIu64
520
89.7k
                    " -- %s\n",
521
89.7k
                    job_id, db_session_id_.c_str(), fname.c_str(), type, number,
522
89.7k
                    file_deletion_status.ToString().c_str());
523
89.7k
  } else if (env_->FileExists(fname).IsNotFound()) {
524
0
    ROCKS_LOG_INFO(
525
0
        immutable_db_options_.info_log,
526
0
        "[JOB %d] [db_session_id=%s] Tried to delete a non-existing file %s"
527
0
        " type=%d #%" PRIu64 " -- %s\n",
528
0
        job_id, db_session_id_.c_str(), fname.c_str(), type, number,
529
0
        file_deletion_status.ToString().c_str());
530
0
  } else {
531
0
    ROCKS_LOG_ERROR(immutable_db_options_.info_log,
532
0
                    "[JOB %d] [db_session_id=%s] Failed to delete %s type=%d"
533
0
                    " #%" PRIu64 " -- %s\n",
534
0
                    job_id, db_session_id_.c_str(), fname.c_str(), type, number,
535
0
                    file_deletion_status.ToString().c_str());
536
0
  }
537
89.7k
  if (type == kTableFile) {
538
6.36k
    EventHelpers::LogAndNotifyTableFileDeletion(
539
6.36k
        &event_logger_, job_id, number, fname, file_deletion_status, GetName(),
540
6.36k
        db_session_id_, immutable_db_options_.listeners);
541
6.36k
  }
542
89.7k
  if (type == kBlobFile) {
543
0
    EventHelpers::LogAndNotifyBlobFileDeletion(
544
0
        &event_logger_, immutable_db_options_.listeners, job_id, number, fname,
545
0
        file_deletion_status, GetName(), db_session_id_);
546
0
  }
547
89.7k
}
548
549
// Diffs the files listed in filenames and those that do not
550
// belong to live files are possibly removed. Also, removes all the
551
// files in sst_delete_files and log_delete_files.
552
// It is not necessary to hold the mutex when invoking this method.
553
96.6k
void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
554
96.6k
  TEST_SYNC_POINT("DBImpl::PurgeObsoleteFiles:Begin");
555
  // we'd better have sth to delete
556
96.6k
  assert(state.HaveSomethingToDelete());
557
558
  // FindObsoleteFiles() should've populated this so nonzero
559
96.6k
  assert(state.manifest_file_number != 0);
560
561
96.6k
  IGNORE_STATUS_IF_ERROR(Status::IOError());
562
563
  // Now, convert lists to unordered sets, WITHOUT mutex held; set is slow.
564
96.6k
  std::unordered_set<uint64_t> sst_live_set(state.sst_live.begin(),
565
96.6k
                                            state.sst_live.end());
566
96.6k
  std::unordered_set<uint64_t> blob_live_set(state.blob_live.begin(),
567
96.6k
                                             state.blob_live.end());
568
96.6k
  std::unordered_set<uint64_t> wal_recycle_files_set(
569
96.6k
      state.log_recycle_files.begin(), state.log_recycle_files.end());
570
96.6k
  std::unordered_set<uint64_t> quarantine_files_set(
571
96.6k
      state.files_to_quarantine.begin(), state.files_to_quarantine.end());
572
573
96.6k
  auto candidate_files = state.full_scan_candidate_files;
574
96.6k
  candidate_files.reserve(
575
96.6k
      candidate_files.size() + state.sst_delete_files.size() +
576
96.6k
      state.blob_delete_files.size() + state.log_delete_files.size() +
577
96.6k
      state.manifest_delete_files.size());
578
  // We may ignore the dbname when generating the file names.
579
96.6k
  for (auto& file : state.sst_delete_files) {
580
11.9k
    auto* handle = file.metadata->fd.pinned_reader.GetCacheHandle();
581
11.9k
    if (file.only_delete_metadata) {
582
6.08k
      if (handle) {
583
        // Simply release handle of file that is not being deleted
584
6.08k
        table_cache_->Release(handle);
585
6.08k
      }
586
6.08k
    } else {
587
      // File is being deleted (actually obsolete)
588
5.89k
      auto number = file.metadata->fd.GetNumber();
589
5.89k
      candidate_files.emplace_back(MakeTableFileName(number), file.path);
590
5.89k
      TableCache::ReleaseObsolete(table_cache_.get(), number, handle,
591
5.89k
                                  file.uncache_aggressiveness);
592
5.89k
    }
593
11.9k
    file.DeleteMetadata();
594
11.9k
  }
595
596
96.6k
  for (const auto& blob_file : state.blob_delete_files) {
597
0
    candidate_files.emplace_back(BlobFileName(blob_file.GetBlobFileNumber()),
598
0
                                 blob_file.GetPath());
599
0
  }
600
601
96.6k
  auto wal_dir = immutable_db_options_.GetWalDir();
602
96.6k
  for (auto file_num : state.log_delete_files) {
603
1.81k
    if (file_num > 0) {
604
1.81k
      candidate_files.emplace_back(LogFileName(file_num), wal_dir);
605
1.81k
    }
606
1.81k
  }
607
96.6k
  for (const auto& filename : state.manifest_delete_files) {
608
44.8k
    candidate_files.emplace_back(filename, dbname_);
609
44.8k
  }
610
611
  // dedup state.candidate_files so we don't try to delete the same
612
  // file twice
613
96.6k
  std::sort(candidate_files.begin(), candidate_files.end(),
614
96.6k
            [](const JobContext::CandidateFileInfo& lhs,
615
8.01M
               const JobContext::CandidateFileInfo& rhs) {
616
8.01M
              if (lhs.file_name < rhs.file_name) {
617
6.31M
                return true;
618
6.31M
              } else if (lhs.file_name > rhs.file_name) {
619
1.65M
                return false;
620
1.65M
              } else {
621
46.4k
                return (lhs.file_path < rhs.file_path);
622
46.4k
              }
623
8.01M
            });
624
96.6k
  candidate_files.erase(
625
96.6k
      std::unique(candidate_files.begin(), candidate_files.end()),
626
96.6k
      candidate_files.end());
627
628
96.6k
  if (state.prev_wals_total_size > 0) {
629
1.81k
    ROCKS_LOG_INFO(immutable_db_options_.info_log,
630
1.81k
                   "[JOB %d] Try to delete WAL files size %" PRIu64
631
1.81k
                   ", prev total WAL file size %" PRIu64
632
1.81k
                   ", number of live WAL files %" ROCKSDB_PRIszt ".\n",
633
1.81k
                   state.job_id, state.size_log_to_delete,
634
1.81k
                   state.prev_wals_total_size, state.num_alive_wal_files);
635
1.81k
  }
636
637
96.6k
  std::vector<std::string> old_info_log_files;
638
96.6k
  InfoLogPrefix info_log_prefix(!immutable_db_options_.db_log_dir.empty(),
639
96.6k
                                dbname_);
640
641
  // File numbers of most recent two OPTIONS file in candidate_files (found in
642
  // previous FindObsoleteFiles(full_scan=true))
643
  // At this point, there must not be any duplicate file numbers in
644
  // candidate_files.
645
96.6k
  uint64_t optsfile_num1 = std::numeric_limits<uint64_t>::min();
646
96.6k
  uint64_t optsfile_num2 = std::numeric_limits<uint64_t>::min();
647
1.56M
  for (const auto& candidate_file : candidate_files) {
648
1.56M
    const std::string& fname = candidate_file.file_name;
649
1.56M
    uint64_t number;
650
1.56M
    FileType type;
651
1.56M
    if (!ParseFileName(fname, &number, info_log_prefix.prefix, &type) ||
652
1.56M
        type != kOptionsFile) {
653
1.40M
      continue;
654
1.40M
    }
655
164k
    if (number > optsfile_num1) {
656
164k
      optsfile_num2 = optsfile_num1;
657
164k
      optsfile_num1 = number;
658
164k
    } else if (number > optsfile_num2) {
659
0
      optsfile_num2 = number;
660
0
    }
661
164k
  }
662
663
  // For remote compactions, we need to keep OPTIONS file that may get
664
  // referenced by the remote worker
665
666
96.6k
  optsfile_num2 = std::min(optsfile_num2, state.min_options_file_number);
667
668
  // Close WALs before trying to delete them.
669
96.6k
  for (const auto w : state.wals_to_free) {
670
    // TODO: maybe check the return value of Close.
671
    // TODO: plumb Env::IOActivity, Env::IOPriority
672
1.81k
    auto s = w->Close({});
673
1.81k
    s.PermitUncheckedError();
674
1.81k
  }
675
676
96.6k
  bool own_files = OwnTablesAndLogs();
677
96.6k
  std::unordered_set<uint64_t> files_to_del;
678
1.56M
  for (const auto& candidate_file : candidate_files) {
679
1.56M
    const std::string& to_delete = candidate_file.file_name;
680
1.56M
    uint64_t number;
681
1.56M
    FileType type;
682
    // Ignore file if we cannot recognize it.
683
1.56M
    if (!ParseFileName(to_delete, &number, info_log_prefix.prefix, &type)) {
684
0
      continue;
685
0
    }
686
687
1.56M
    if (quarantine_files_set.find(number) != quarantine_files_set.end()) {
688
0
      continue;
689
0
    }
690
691
1.56M
    bool keep = true;
692
1.56M
    switch (type) {
693
128k
      case kWalFile:
694
128k
        keep = ((number >= state.log_number) ||
695
38.5k
                (number == state.prev_log_number) ||
696
38.5k
                (wal_recycle_files_set.find(number) !=
697
38.5k
                 wal_recycle_files_set.end()));
698
128k
        break;
699
134k
      case kDescriptorFile:
700
        // Keep my manifest file, and any newer incarnations'
701
        // (can happen during manifest roll)
702
134k
        keep = (number >= state.manifest_file_number);
703
134k
        break;
704
167k
      case kTableFile:
705
        // If the second condition is not there, this makes
706
        // DontDeletePendingOutputs fail
707
        // FIXME: but should NOT keep if it came from sst_delete_files?
708
167k
        keep = (sst_live_set.find(number) != sst_live_set.end()) ||
709
6.36k
               number >= state.min_pending_output;
710
167k
        if (!keep) {
711
          // NOTE: sometimes redundant (if came from sst_delete_files)
712
          // We don't know which column family is applicable here so we don't
713
          // know what uncache_aggressiveness would be used with
714
          // ReleaseObsolete(). Anyway, obsolete files ideally go into
715
          // sst_delete_files for better/quicker handling, and this is just a
716
          // backstop.
717
6.36k
          TableCache::Evict(table_cache_.get(), number);
718
6.36k
          files_to_del.insert(number);
719
6.36k
        }
720
167k
        break;
721
0
      case kBlobFile: {
722
0
        keep = number >= state.min_pending_output ||
723
0
               number >= state.min_blob_file_number_to_keep ||
724
0
               (blob_live_set.find(number) != blob_live_set.end()) ||
725
0
               (state.active_blob_direct_write_files.find(number) !=
726
0
                state.active_blob_direct_write_files.end());
727
0
        if (!keep) {
728
0
          const std::string blob_file_path =
729
0
              BlobFileName(candidate_file.file_path, number);
730
0
          if (ShouldKeepBlobFileDuringPurge(number, blob_file_path)) {
731
0
            keep = true;
732
0
            break;
733
0
          }
734
0
          TableCache::Evict(table_cache_.get(), number);
735
0
          files_to_del.insert(number);
736
0
        }
737
0
        break;
738
0
      }
739
0
      case kTempFile:
740
        // Any temp files that are currently being written to must
741
        // be recorded in pending_outputs_, which is inserted into "live".
742
        // Also, SetCurrentFile creates a temp file when writing out new
743
        // manifest, which is equal to state.pending_manifest_file_number. We
744
        // should not delete that file
745
        //
746
        // TODO(yhchiang): carefully modify the third condition to safely
747
        //                 remove the temp options files.
748
0
        keep = (sst_live_set.find(number) != sst_live_set.end()) ||
749
0
               (blob_live_set.find(number) != blob_live_set.end()) ||
750
0
               (number == state.pending_manifest_file_number) ||
751
0
               (to_delete.find(kOptionsFileNamePrefix) != std::string::npos);
752
0
        break;
753
704k
      case kInfoLogFile:
754
704k
        keep = true;
755
704k
        if (number != 0) {
756
614k
          old_info_log_files.push_back(to_delete);
757
614k
        }
758
704k
        break;
759
164k
      case kOptionsFile:
760
164k
        keep = (number >= optsfile_num2) ||
761
0
               (number == state.manifest_options_file_number);
762
164k
        break;
763
0
      case kCompactionProgressFile:
764
        // Keep compaction progress files - they are managed
765
        // separately by DBImplSecondary for now
766
0
        keep = true;
767
0
        break;
768
89.7k
      case kCurrentFile:
769
179k
      case kDBLockFile:
770
269k
      case kIdentityFile:
771
269k
      case kMetaDatabase:
772
269k
        keep = true;
773
269k
        break;
774
1.56M
    }
775
776
1.56M
    if (keep) {
777
1.47M
      continue;
778
1.47M
    }
779
780
89.7k
    std::string fname;
781
89.7k
    std::string dir_to_sync;
782
89.7k
    if (type == kTableFile) {
783
6.36k
      fname = MakeTableFileName(candidate_file.file_path, number);
784
6.36k
      dir_to_sync = candidate_file.file_path;
785
83.3k
    } else if (type == kBlobFile) {
786
0
      fname = BlobFileName(candidate_file.file_path, number);
787
0
      dir_to_sync = candidate_file.file_path;
788
83.3k
    } else {
789
83.3k
      dir_to_sync = (type == kWalFile) ? wal_dir : dbname_;
790
83.3k
      fname = dir_to_sync +
791
83.3k
              ((!dir_to_sync.empty() && dir_to_sync.back() == '/') ||
792
83.3k
                       (!to_delete.empty() && to_delete.front() == '/')
793
83.3k
                   ? ""
794
83.3k
                   : "/") +
795
83.3k
              to_delete;
796
83.3k
    }
797
798
89.7k
    if (type == kWalFile && (immutable_db_options_.WAL_ttl_seconds > 0 ||
799
38.5k
                             immutable_db_options_.WAL_size_limit_MB > 0)) {
800
0
      wal_manager_.ArchiveWALFile(fname, number);
801
0
      continue;
802
0
    }
803
804
    // If I do not own these files, e.g. secondary instance with max_open_files
805
    // = -1, then no need to delete or schedule delete these files since they
806
    // will be removed by their owner, e.g. the primary instance.
807
89.7k
    if (!own_files) {
808
0
      continue;
809
0
    }
810
89.7k
    if (schedule_only) {
811
0
      InstrumentedMutexLock guard_lock(&mutex_);
812
0
      SchedulePendingPurge(fname, dir_to_sync, type, number, state.job_id);
813
89.7k
    } else {
814
89.7k
      DeleteObsoleteFileImpl(state.job_id, fname, dir_to_sync, type, number);
815
89.7k
    }
816
89.7k
  }
817
818
96.6k
  {
819
    // After purging obsolete files, remove them from files_grabbed_for_purge_.
820
96.6k
    InstrumentedMutexLock guard_lock(&mutex_);
821
96.6k
    autovector<uint64_t> to_be_removed;
822
96.6k
    for (auto fn : files_grabbed_for_purge_) {
823
18.1k
      if (files_to_del.count(fn) != 0) {
824
5.88k
        to_be_removed.emplace_back(fn);
825
5.88k
      }
826
18.1k
    }
827
96.6k
    for (auto fn : to_be_removed) {
828
5.88k
      files_grabbed_for_purge_.erase(fn);
829
5.88k
    }
830
96.6k
  }
831
832
  // Delete old info log files.
833
96.6k
  size_t old_info_log_file_count = old_info_log_files.size();
834
96.6k
  if (old_info_log_file_count != 0 &&
835
73.4k
      old_info_log_file_count >= immutable_db_options_.keep_log_file_num) {
836
0
    std::sort(old_info_log_files.begin(), old_info_log_files.end());
837
0
    size_t end =
838
0
        old_info_log_file_count - immutable_db_options_.keep_log_file_num;
839
0
    for (unsigned int i = 0; i <= end; i++) {
840
0
      std::string& to_delete = old_info_log_files.at(i);
841
0
      std::string full_path_to_delete =
842
0
          (immutable_db_options_.db_log_dir.empty()
843
0
               ? dbname_
844
0
               : immutable_db_options_.db_log_dir) +
845
0
          "/" + to_delete;
846
0
      ROCKS_LOG_INFO(immutable_db_options_.info_log,
847
0
                     "[JOB %d] Delete info log file %s\n", state.job_id,
848
0
                     full_path_to_delete.c_str());
849
0
      Status s = env_->DeleteFile(full_path_to_delete);
850
0
      if (!s.ok()) {
851
0
        if (env_->FileExists(full_path_to_delete).IsNotFound()) {
852
0
          ROCKS_LOG_INFO(
853
0
              immutable_db_options_.info_log,
854
0
              "[JOB %d] Tried to delete non-existing info log file %s FAILED "
855
0
              "-- %s\n",
856
0
              state.job_id, to_delete.c_str(), s.ToString().c_str());
857
0
        } else {
858
0
          ROCKS_LOG_ERROR(immutable_db_options_.info_log,
859
0
                          "[JOB %d] Delete info log file %s FAILED -- %s\n",
860
0
                          state.job_id, to_delete.c_str(),
861
0
                          s.ToString().c_str());
862
0
        }
863
0
      }
864
0
    }
865
0
  }
866
96.6k
  wal_manager_.PurgeObsoleteWALFiles();
867
96.6k
  LogFlush(immutable_db_options_.info_log);
868
96.6k
  TEST_SYNC_POINT("DBImpl::PurgeObsoleteFiles:BeforePendingPurgeFinished");
869
96.6k
  InstrumentedMutexLock l(&mutex_);
870
96.6k
  --pending_purge_obsolete_files_;
871
96.6k
  assert(pending_purge_obsolete_files_ >= 0);
872
96.6k
  if (schedule_only) {
873
    // Must change from pending_purge_obsolete_files_ to bg_purge_scheduled_
874
    // while holding mutex (for GetSortedWalFiles() etc.)
875
0
    SchedulePurge();
876
0
  }
877
96.6k
  if (pending_purge_obsolete_files_ == 0) {
878
96.6k
    bg_cv_.SignalAll();
879
96.6k
  }
880
96.6k
  TEST_SYNC_POINT("DBImpl::PurgeObsoleteFiles:End");
881
96.6k
}
882
883
44.8k
void DBImpl::DeleteObsoleteFiles() {
884
44.8k
  mutex_.AssertHeld();
885
44.8k
  JobContext job_context(next_job_id_.fetch_add(1));
886
44.8k
  FindObsoleteFiles(&job_context, true);
887
888
44.8k
  mutex_.Unlock();
889
44.8k
  if (job_context.HaveSomethingToDelete()) {
890
44.8k
    bool defer_purge = immutable_db_options_.avoid_unnecessary_blocking_io;
891
44.8k
    PurgeObsoleteFiles(job_context, defer_purge);
892
44.8k
  }
893
44.8k
  job_context.Clean();
894
44.8k
  mutex_.Lock();
895
44.8k
}
896
897
bool DBImpl::ShouldKeepBlobFileDuringPurge(uint64_t number,
898
0
                                           const std::string& blob_file_path) {
899
0
  {
900
0
    InstrumentedMutexLock lock(&mutex_);
901
0
    for (auto* cfd : *versions_->GetColumnFamilySet()) {
902
0
      auto* mgr = cfd->blob_partition_manager();
903
0
      if (mgr != nullptr && mgr->IsTrackedBlobFileNumber(number)) {
904
0
        return true;
905
0
      }
906
0
    }
907
0
  }
908
909
0
  return ShouldKeepFooterlessBlobFile(fs_.get(), file_options_, blob_file_path);
910
0
}
911
912
VersionEdit GetDBRecoveryEditForObsoletingMemTables(
913
    VersionSet* vset, const ColumnFamilyData& cfd,
914
    const autovector<VersionEdit*>& edit_list,
915
    const autovector<ReadOnlyMemTable*>& memtables,
916
1.81k
    LogsWithPrepTracker* prep_tracker) {
917
1.81k
  VersionEdit wal_deletion_edit;
918
1.81k
  uint64_t min_wal_number_to_keep = 0;
919
1.81k
  assert(edit_list.size() > 0);
920
1.81k
  if (vset->db_options()->allow_2pc) {
921
    // Note that if mempurge is successful, the edit_list will
922
    // not be applicable (contains info of new min_log number to keep,
923
    // and level 0 file path of SST file created during normal flush,
924
    // so both pieces of information are irrelevant after a successful
925
    // mempurge operation).
926
0
    min_wal_number_to_keep = PrecomputeMinLogNumberToKeep2PC(
927
0
        vset, cfd, edit_list, memtables, prep_tracker);
928
929
    // We piggyback the information of earliest log file to keep in the
930
    // manifest entry for the last file flushed.
931
1.81k
  } else {
932
1.81k
    min_wal_number_to_keep =
933
1.81k
        PrecomputeMinLogNumberToKeepNon2PC(vset, cfd, edit_list);
934
1.81k
  }
935
936
1.81k
  wal_deletion_edit.SetMinLogNumberToKeep(min_wal_number_to_keep);
937
1.81k
  if (vset->db_options()->track_and_verify_wals_in_manifest) {
938
0
    if (min_wal_number_to_keep > vset->GetWalSet().GetMinWalNumberToKeep()) {
939
0
      wal_deletion_edit.DeleteWalsBefore(min_wal_number_to_keep);
940
0
    }
941
0
  }
942
1.81k
  return wal_deletion_edit;
943
1.81k
}
944
945
uint64_t FindMinPrepLogReferencedByMemTable(
946
0
    VersionSet* vset, const autovector<ReadOnlyMemTable*>& memtables_to_flush) {
947
0
  uint64_t min_log = 0;
948
949
  // we must look through the memtables for two phase transactions
950
  // that have been committed but not yet flushed
951
0
  std::unordered_set<ReadOnlyMemTable*> memtables_to_flush_set(
952
0
      memtables_to_flush.begin(), memtables_to_flush.end());
953
0
  for (auto loop_cfd : *vset->GetColumnFamilySet()) {
954
0
    if (loop_cfd->IsDropped()) {
955
0
      continue;
956
0
    }
957
958
0
    auto log = loop_cfd->imm()->PrecomputeMinLogContainingPrepSection(
959
0
        &memtables_to_flush_set);
960
961
0
    if (log > 0 && (min_log == 0 || log < min_log)) {
962
0
      min_log = log;
963
0
    }
964
965
0
    log = loop_cfd->mem()->GetMinLogContainingPrepSection();
966
967
0
    if (log > 0 && (min_log == 0 || log < min_log)) {
968
0
      min_log = log;
969
0
    }
970
0
  }
971
972
0
  return min_log;
973
0
}
974
975
uint64_t FindMinPrepLogReferencedByMemTable(
976
    VersionSet* vset, const autovector<const autovector<ReadOnlyMemTable*>*>&
977
0
                          memtables_to_flush) {
978
0
  uint64_t min_log = 0;
979
980
0
  std::unordered_set<ReadOnlyMemTable*> memtables_to_flush_set;
981
0
  for (const autovector<ReadOnlyMemTable*>* memtables : memtables_to_flush) {
982
0
    memtables_to_flush_set.insert(memtables->begin(), memtables->end());
983
0
  }
984
0
  for (auto loop_cfd : *vset->GetColumnFamilySet()) {
985
0
    if (loop_cfd->IsDropped()) {
986
0
      continue;
987
0
    }
988
989
0
    auto log = loop_cfd->imm()->PrecomputeMinLogContainingPrepSection(
990
0
        &memtables_to_flush_set);
991
0
    if (log > 0 && (min_log == 0 || log < min_log)) {
992
0
      min_log = log;
993
0
    }
994
995
0
    log = loop_cfd->mem()->GetMinLogContainingPrepSection();
996
0
    if (log > 0 && (min_log == 0 || log < min_log)) {
997
0
      min_log = log;
998
0
    }
999
0
  }
1000
1001
0
  return min_log;
1002
0
}
1003
1004
uint64_t PrecomputeMinLogNumberToKeepNon2PC(
1005
    VersionSet* vset, const ColumnFamilyData& cfd_to_flush,
1006
1.81k
    const autovector<VersionEdit*>& edit_list) {
1007
1.81k
  assert(vset != nullptr);
1008
1009
  // Precompute the min log number containing unflushed data for the column
1010
  // family being flushed (`cfd_to_flush`).
1011
1.81k
  uint64_t cf_min_log_number_to_keep = 0;
1012
1.81k
  for (auto& e : edit_list) {
1013
1.81k
    if (e->HasLogNumber()) {
1014
1.81k
      cf_min_log_number_to_keep =
1015
1.81k
          std::max(cf_min_log_number_to_keep, e->GetLogNumber());
1016
1.81k
    }
1017
1.81k
  }
1018
1.81k
  if (cf_min_log_number_to_keep == 0) {
1019
    // No version edit contains information on log number. The log number
1020
    // for this column family should stay the same as it is.
1021
0
    cf_min_log_number_to_keep = cfd_to_flush.GetLogNumber();
1022
0
  }
1023
1024
  // Get min log number containing unflushed data for other column families.
1025
1.81k
  uint64_t min_log_number_to_keep =
1026
1.81k
      vset->PreComputeMinLogNumberWithUnflushedData(&cfd_to_flush);
1027
1.81k
  if (cf_min_log_number_to_keep != 0) {
1028
1.81k
    min_log_number_to_keep =
1029
1.81k
        std::min(cf_min_log_number_to_keep, min_log_number_to_keep);
1030
1.81k
  }
1031
1.81k
  return min_log_number_to_keep;
1032
1.81k
}
1033
1034
uint64_t PrecomputeMinLogNumberToKeepNon2PC(
1035
    VersionSet* vset, const autovector<ColumnFamilyData*>& cfds_to_flush,
1036
0
    const autovector<autovector<VersionEdit*>>& edit_lists) {
1037
0
  assert(vset != nullptr);
1038
0
  assert(!cfds_to_flush.empty());
1039
0
  assert(cfds_to_flush.size() == edit_lists.size());
1040
1041
0
  uint64_t min_log_number_to_keep = std::numeric_limits<uint64_t>::max();
1042
0
  for (const auto& edit_list : edit_lists) {
1043
0
    uint64_t log = 0;
1044
0
    for (const auto& e : edit_list) {
1045
0
      if (e->HasLogNumber()) {
1046
0
        log = std::max(log, e->GetLogNumber());
1047
0
      }
1048
0
    }
1049
0
    if (log != 0) {
1050
0
      min_log_number_to_keep = std::min(min_log_number_to_keep, log);
1051
0
    }
1052
0
  }
1053
0
  if (min_log_number_to_keep == std::numeric_limits<uint64_t>::max()) {
1054
0
    min_log_number_to_keep = cfds_to_flush[0]->GetLogNumber();
1055
0
    for (size_t i = 1; i < cfds_to_flush.size(); i++) {
1056
0
      min_log_number_to_keep =
1057
0
          std::min(min_log_number_to_keep, cfds_to_flush[i]->GetLogNumber());
1058
0
    }
1059
0
  }
1060
1061
0
  std::unordered_set<const ColumnFamilyData*> flushed_cfds(
1062
0
      cfds_to_flush.begin(), cfds_to_flush.end());
1063
0
  min_log_number_to_keep =
1064
0
      std::min(min_log_number_to_keep,
1065
0
               vset->PreComputeMinLogNumberWithUnflushedData(flushed_cfds));
1066
1067
0
  return min_log_number_to_keep;
1068
0
}
1069
1070
uint64_t PrecomputeMinLogNumberToKeep2PC(
1071
    VersionSet* vset, const ColumnFamilyData& cfd_to_flush,
1072
    const autovector<VersionEdit*>& edit_list,
1073
    const autovector<ReadOnlyMemTable*>& memtables_to_flush,
1074
0
    LogsWithPrepTracker* prep_tracker) {
1075
0
  assert(vset != nullptr);
1076
0
  assert(prep_tracker != nullptr);
1077
  // Calculate updated min_log_number_to_keep
1078
  // Since the function should only be called in 2pc mode, log number in
1079
  // the version edit should be sufficient.
1080
1081
0
  uint64_t min_log_number_to_keep =
1082
0
      PrecomputeMinLogNumberToKeepNon2PC(vset, cfd_to_flush, edit_list);
1083
1084
  // if are 2pc we must consider logs containing prepared
1085
  // sections of outstanding transactions.
1086
  //
1087
  // We must check min logs with outstanding prep before we check
1088
  // logs references by memtables because a log referenced by the
1089
  // first data structure could transition to the second under us.
1090
  //
1091
  // TODO: iterating over all column families under db mutex.
1092
  // should find more optimal solution
1093
0
  auto min_log_in_prep_heap =
1094
0
      prep_tracker->FindMinLogContainingOutstandingPrep();
1095
1096
0
  if (min_log_in_prep_heap != 0 &&
1097
0
      min_log_in_prep_heap < min_log_number_to_keep) {
1098
0
    min_log_number_to_keep = min_log_in_prep_heap;
1099
0
  }
1100
1101
0
  uint64_t min_log_refed_by_mem =
1102
0
      FindMinPrepLogReferencedByMemTable(vset, memtables_to_flush);
1103
1104
0
  if (min_log_refed_by_mem != 0 &&
1105
0
      min_log_refed_by_mem < min_log_number_to_keep) {
1106
0
    min_log_number_to_keep = min_log_refed_by_mem;
1107
0
  }
1108
0
  return min_log_number_to_keep;
1109
0
}
1110
1111
uint64_t PrecomputeMinLogNumberToKeep2PC(
1112
    VersionSet* vset, const autovector<ColumnFamilyData*>& cfds_to_flush,
1113
    const autovector<autovector<VersionEdit*>>& edit_lists,
1114
    const autovector<const autovector<ReadOnlyMemTable*>*>& memtables_to_flush,
1115
0
    LogsWithPrepTracker* prep_tracker) {
1116
0
  assert(vset != nullptr);
1117
0
  assert(prep_tracker != nullptr);
1118
0
  assert(cfds_to_flush.size() == edit_lists.size());
1119
0
  assert(cfds_to_flush.size() == memtables_to_flush.size());
1120
1121
0
  uint64_t min_log_number_to_keep =
1122
0
      PrecomputeMinLogNumberToKeepNon2PC(vset, cfds_to_flush, edit_lists);
1123
1124
0
  uint64_t min_log_in_prep_heap =
1125
0
      prep_tracker->FindMinLogContainingOutstandingPrep();
1126
1127
0
  if (min_log_in_prep_heap != 0 &&
1128
0
      min_log_in_prep_heap < min_log_number_to_keep) {
1129
0
    min_log_number_to_keep = min_log_in_prep_heap;
1130
0
  }
1131
1132
0
  uint64_t min_log_refed_by_mem =
1133
0
      FindMinPrepLogReferencedByMemTable(vset, memtables_to_flush);
1134
1135
0
  if (min_log_refed_by_mem != 0 &&
1136
0
      min_log_refed_by_mem < min_log_number_to_keep) {
1137
0
    min_log_number_to_keep = min_log_refed_by_mem;
1138
0
  }
1139
1140
0
  return min_log_number_to_keep;
1141
0
}
1142
1143
void DBImpl::SetDBId(std::string&& id, bool read_only,
1144
8.13k
                     VersionEdit* version_edit) {
1145
8.13k
  assert(db_id_.empty());
1146
8.13k
  assert(!id.empty());
1147
8.13k
  db_id_ = std::move(id);
1148
8.13k
  if (!read_only && version_edit) {
1149
8.13k
    assert(version_edit != nullptr);
1150
8.13k
    assert(versions_->GetColumnFamilySet() != nullptr);
1151
8.13k
    version_edit->SetDBId(db_id_);
1152
8.13k
    versions_->db_id_ = db_id_;
1153
8.13k
  }
1154
8.13k
}
1155
1156
Status DBImpl::SetupDBId(const WriteOptions& write_options, bool read_only,
1157
                         bool is_new_db, bool is_retry,
1158
44.8k
                         VersionEdit* version_edit) {
1159
44.8k
  Status s;
1160
44.8k
  if (!is_new_db) {
1161
    // Check for the IDENTITY file and create it if not there or
1162
    // broken or not matching manifest
1163
36.7k
    std::string db_id_in_file;
1164
36.7k
    s = fs_->FileExists(IdentityFileName(dbname_), IOOptions(), nullptr);
1165
36.7k
    if (s.ok()) {
1166
36.7k
      IOOptions opts;
1167
36.7k
      if (is_retry) {
1168
0
        opts.verify_and_reconstruct_read = true;
1169
0
      }
1170
36.7k
      s = GetDbIdentityFromIdentityFile(opts, &db_id_in_file);
1171
36.7k
      if (s.ok() && !db_id_in_file.empty()) {
1172
36.7k
        if (db_id_.empty()) {
1173
          // Loaded from file and wasn't already known from manifest
1174
0
          SetDBId(std::move(db_id_in_file), read_only, version_edit);
1175
0
          return s;
1176
36.7k
        } else if (db_id_ == db_id_in_file) {
1177
          // Loaded from file and matches manifest
1178
36.7k
          return s;
1179
36.7k
        }
1180
36.7k
      }
1181
36.7k
    }
1182
0
    if (s.IsNotFound()) {
1183
0
      s = Status::OK();
1184
0
    }
1185
0
    if (!s.ok()) {
1186
0
      assert(s.IsIOError());
1187
0
      return s;
1188
0
    }
1189
0
  }
1190
  // Otherwise IDENTITY file is missing or no good.
1191
  // Generate new id if needed
1192
8.13k
  if (db_id_.empty()) {
1193
8.13k
    SetDBId(env_->GenerateUniqueId(), read_only, version_edit);
1194
8.13k
  }
1195
  // Persist it to IDENTITY file if allowed
1196
8.13k
  if (!read_only && immutable_db_options_.write_identity_file) {
1197
8.13k
    s = SetIdentityFile(write_options, env_, dbname_,
1198
8.13k
                        immutable_db_options_.metadata_write_temperature,
1199
8.13k
                        db_id_);
1200
8.13k
  }
1201
  // NOTE: an obsolete IDENTITY file with write_identity_file=false is handled
1202
  // elsewhere, so that it's only deleted after successful recovery
1203
8.13k
  return s;
1204
44.8k
}
1205
1206
179k
std::set<std::string> DBImpl::CollectAllDBPaths() {
1207
179k
  std::set<std::string> all_db_paths;
1208
179k
  all_db_paths.insert(NormalizePath(dbname_));
1209
179k
  for (const auto& db_path : immutable_db_options_.db_paths) {
1210
179k
    all_db_paths.insert(NormalizePath(db_path.path));
1211
179k
  }
1212
242k
  for (const auto* cfd : *versions_->GetColumnFamilySet()) {
1213
242k
    for (const auto& cf_path : cfd->ioptions().cf_paths) {
1214
242k
      all_db_paths.insert(NormalizePath(cf_path.path));
1215
242k
    }
1216
242k
  }
1217
179k
  return all_db_paths;
1218
179k
}
1219
1220
44.8k
Status DBImpl::MaybeUpdateNextFileNumber(RecoveryContext* recovery_ctx) {
1221
44.8k
  mutex_.AssertHeld();
1222
44.8k
  const uint64_t prev_next_file_number = versions_->current_next_file_number();
1223
44.8k
  uint64_t largest_file_number = prev_next_file_number;
1224
44.8k
  bool on_disk_file_advanced_counter = false;
1225
44.8k
  bool manifest_relevant_file_advanced_counter = false;
1226
44.8k
  Status s;
1227
44.8k
  for (const auto& path : CollectAllDBPaths()) {
1228
44.8k
    std::vector<std::string> files;
1229
44.8k
    s = env_->GetChildren(path, &files);
1230
44.8k
    if (!s.ok()) {
1231
0
      break;
1232
0
    }
1233
44.8k
    if (path == NormalizePath(dbname_)) {
1234
44.8k
      recovery_ctx->existing_db_files_ = files;
1235
44.8k
    }
1236
704k
    for (const auto& fname : files) {
1237
704k
      uint64_t number = 0;
1238
704k
      FileType type;
1239
704k
      if (!ParseFileName(fname, &number, &type)) {
1240
352k
        continue;
1241
352k
      }
1242
351k
      std::string normalized_fpath = path;
1243
351k
      normalized_fpath += kFilePathSeparator;
1244
351k
      normalized_fpath.append(fname);
1245
      // Use >= so a crashed-mid-allocation file at exactly
1246
      // prev_next_file_number triggers the advance -- otherwise the next
1247
      // NewFileNumber() would collide with it.
1248
351k
      if (number >= prev_next_file_number) {
1249
34.5k
        on_disk_file_advanced_counter = true;
1250
34.5k
        if (type != kOptionsFile && type != kTempFile) {
1251
0
          manifest_relevant_file_advanced_counter = true;
1252
0
        }
1253
34.5k
      }
1254
351k
      largest_file_number = std::max(largest_file_number, number);
1255
351k
      if ((type == kTableFile || type == kBlobFile)) {
1256
68.7k
        recovery_ctx->existing_data_files_.push_back(normalized_fpath);
1257
68.7k
      }
1258
351k
    }
1259
44.8k
  }
1260
44.8k
  if (!s.ok()) {
1261
0
    return s;
1262
0
  }
1263
1264
  // Legacy bug: initializing largest_file_number from prev_next_file_number
1265
  // made the post-loop check trivially true and emitted a noop SetNextFile
1266
  // on every recovery.
1267
  // Preserved verbatim by default (some tests pin file numbers); skipped
1268
  // only when the option is on AND we're not in salvage mode (which
1269
  // requires the MANIFEST rewrite this emit triggers).
1270
44.8k
  const bool optimize_manifest_for_recovery =
1271
44.8k
      mutable_db_options_.optimize_manifest_for_recovery &&
1272
0
      !immutable_db_options_.UsesBestEffortsRecoveryPath();
1273
1274
44.8k
  if (!optimize_manifest_for_recovery ||
1275
44.8k
      manifest_relevant_file_advanced_counter) {
1276
44.8k
    if (largest_file_number >= prev_next_file_number) {
1277
44.8k
      versions_->next_file_number_.store(largest_file_number + 1);
1278
44.8k
    }
1279
44.8k
    TEST_SYNC_POINT("DBImpl::MaybeUpdateNextFileNumber:EmitEdit");
1280
44.8k
    VersionEdit edit;
1281
44.8k
    edit.SetNextFile(versions_->next_file_number_.load());
1282
44.8k
    assert(versions_->GetColumnFamilySet());
1283
44.8k
    ColumnFamilyData* default_cfd =
1284
44.8k
        versions_->GetColumnFamilySet()->GetDefault();
1285
44.8k
    assert(default_cfd);
1286
44.8k
    recovery_ctx->UpdateVersionEdits(default_cfd, edit);
1287
44.8k
  } else {
1288
0
    if (on_disk_file_advanced_counter) {
1289
0
      versions_->next_file_number_.store(largest_file_number + 1);
1290
0
    }
1291
0
    TEST_SYNC_POINT("DBImpl::Recovery:SkippedNoopEdit:NextFileNumber");
1292
0
  }
1293
44.8k
  return s;
1294
44.8k
}
1295
}  // namespace ROCKSDB_NAMESPACE