mirror of
https://github.com/facebook/rocksdb.git
synced 2026-07-07 14:47:40 +08:00
4707775ae9
Summary: - propagate lower-level read and merge failures through `GetContext` via `read_status`, so `Get` and `GetEntity` preserve the original error instead of synthesizing `Corruption` when blob-backed reads or merge resolution fail - teach `GetMergeOperands` to resolve blob-backed default columns from wide-column entities, covering both the direct base-value path and the merge-plus-base path - add regression coverage for blob-read IO errors during `Get`/`GetEntity` merge resolution and for `GetMergeOperands` on blob-backed wide-column entities - fix the `DBFlushTest.MemPurgeCorrectLogNumberAndSSTFileCreation` test race by waiting for flush callbacks and cleaning up sync points ## Testing - `make db_blob_basic_test -j14` - `/usr/bin/perl -e 'alarm shift; exec ARGV' 60 ./db_blob_basic_test --gtest_filter='DBBlobBasicTest/DBBlobBasicIOErrorTest.GetBlob_IOError/*:DBBlobBasicTest/DBBlobBasicIOErrorTest.GetEntityMergeWithBlobBaseIOError/*'` ## Task T265824017, T265415808 Pull Request resolved: https://github.com/facebook/rocksdb/pull/14640 Reviewed By: anand1976 Differential Revision: D101690700 Pulled By: xingbowang fbshipit-source-id: 2b6fc357b37a01efa72a2d54dcff55be8992f42a
1796 lines
66 KiB
C++
1796 lines
66 KiB
C++
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
|
// This source code is licensed under both the GPLv2 (found in the
|
|
// COPYING file in the root directory) and Apache 2.0 License
|
|
// (found in the LICENSE.Apache file in the root directory).
|
|
|
|
#include "db/db_impl/db_impl_secondary.h"
|
|
|
|
#include <cinttypes>
|
|
#include <optional>
|
|
|
|
#include "db/arena_wrapped_db_iter.h"
|
|
#include "db/blob/blob_fetcher.h"
|
|
#include "db/log_reader.h"
|
|
#include "db/log_writer.h"
|
|
#include "db/merge_context.h"
|
|
#include "db/version_edit.h"
|
|
#include "file/filename.h"
|
|
#include "file/writable_file_writer.h"
|
|
#include "logging/auto_roll_logger.h"
|
|
#include "logging/logging.h"
|
|
#include "monitoring/perf_context_imp.h"
|
|
#include "rocksdb/convenience.h"
|
|
#include "rocksdb/utilities/options_util.h"
|
|
#include "util/cast_util.h"
|
|
#include "util/write_batch_util.h"
|
|
|
|
namespace ROCKSDB_NAMESPACE {
|
|
|
|
DBImplSecondary::DBImplSecondary(const DBOptions& db_options,
|
|
const std::string& dbname,
|
|
std::string secondary_path)
|
|
: DBImpl(db_options, dbname, false, true, true),
|
|
secondary_path_(std::move(secondary_path)) {
|
|
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
|
"Opening the db in secondary mode");
|
|
LogFlush(immutable_db_options_.info_log);
|
|
}
|
|
|
|
DBImplSecondary::~DBImplSecondary() = default;
|
|
|
|
Status DBImplSecondary::Recover(
|
|
const std::vector<ColumnFamilyDescriptor>& column_families,
|
|
bool /*readonly*/, bool /*error_if_wal_file_exists*/,
|
|
bool /*error_if_data_exists_in_wals*/, bool /*is_retry*/, uint64_t*,
|
|
RecoveryContext* /*recovery_ctx*/, bool* /*can_retry*/) {
|
|
mutex_.AssertHeld();
|
|
|
|
Status s;
|
|
s = static_cast<ReactiveVersionSet*>(versions_.get())
|
|
->Recover(column_families, &manifest_reader_, &manifest_reporter_,
|
|
&manifest_reader_status_);
|
|
if (!s.ok()) {
|
|
if (manifest_reader_status_) {
|
|
manifest_reader_status_->PermitUncheckedError();
|
|
}
|
|
return s;
|
|
}
|
|
// Initial max_total_in_memory_state_ before recovery logs.
|
|
max_total_in_memory_state_ = 0;
|
|
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
|
const auto& mutable_cf_options = cfd->GetLatestMutableCFOptions();
|
|
max_total_in_memory_state_ += mutable_cf_options.write_buffer_size *
|
|
mutable_cf_options.max_write_buffer_number;
|
|
}
|
|
default_cf_handle_ = new ColumnFamilyHandleImpl(
|
|
versions_->GetColumnFamilySet()->GetDefault(), this, &mutex_);
|
|
default_cf_internal_stats_ = default_cf_handle_->cfd()->internal_stats();
|
|
|
|
// TODO: update options_file_number_ needed?
|
|
|
|
return s;
|
|
}
|
|
|
|
// find new WAL and apply them in order to the secondary instance
|
|
Status DBImplSecondary::FindAndRecoverLogFiles(
|
|
std::unordered_set<ColumnFamilyData*>* cfds_changed,
|
|
JobContext* job_context) {
|
|
assert(nullptr != cfds_changed);
|
|
assert(nullptr != job_context);
|
|
TEST_SYNC_POINT("DBImplSecondary::FindAndRecoverLogFiles:Begin");
|
|
Status s;
|
|
std::vector<uint64_t> logs;
|
|
s = FindNewLogNumbers(&logs);
|
|
if (s.ok() && !logs.empty()) {
|
|
SequenceNumber next_sequence(kMaxSequenceNumber);
|
|
s = RecoverLogFiles(logs, &next_sequence, cfds_changed, job_context);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
// List wal_dir and find all new WALs, return these log numbers
|
|
Status DBImplSecondary::FindNewLogNumbers(std::vector<uint64_t>* logs) {
|
|
assert(logs != nullptr);
|
|
std::vector<std::string> filenames;
|
|
Status s;
|
|
IOOptions io_opts;
|
|
io_opts.do_not_recurse = true;
|
|
s = immutable_db_options_.fs->GetChildren(immutable_db_options_.GetWalDir(),
|
|
io_opts, &filenames,
|
|
/*IODebugContext*=*/nullptr);
|
|
if (s.IsNotFound()) {
|
|
return Status::InvalidArgument("Failed to open wal_dir",
|
|
immutable_db_options_.GetWalDir());
|
|
} else if (!s.ok()) {
|
|
return s;
|
|
}
|
|
|
|
// if log_readers_ is non-empty, it means we have applied all logs with log
|
|
// numbers smaller than the smallest log in log_readers_, so there is no
|
|
// need to pass these logs to RecoverLogFiles
|
|
uint64_t log_number_min = 0;
|
|
if (!log_readers_.empty()) {
|
|
log_number_min = log_readers_.begin()->first;
|
|
}
|
|
for (size_t i = 0; i < filenames.size(); i++) {
|
|
uint64_t number;
|
|
FileType type;
|
|
if (ParseFileName(filenames[i], &number, &type) && type == kWalFile &&
|
|
number >= log_number_min) {
|
|
logs->push_back(number);
|
|
}
|
|
}
|
|
// Recover logs in the order that they were generated
|
|
if (!logs->empty()) {
|
|
std::sort(logs->begin(), logs->end());
|
|
}
|
|
return s;
|
|
}
|
|
|
|
Status DBImplSecondary::MaybeInitLogReader(
|
|
uint64_t log_number, log::FragmentBufferedReader** log_reader) {
|
|
auto iter = log_readers_.find(log_number);
|
|
// make sure the log file is still present
|
|
if (iter == log_readers_.end() ||
|
|
iter->second->reader_->GetLogNumber() != log_number) {
|
|
// delete the obsolete log reader if log number mismatch
|
|
if (iter != log_readers_.end()) {
|
|
log_readers_.erase(iter);
|
|
}
|
|
// initialize log reader from log_number
|
|
// TODO: min_log_number_to_keep_2pc check needed?
|
|
// Open the log file
|
|
std::string fname =
|
|
LogFileName(immutable_db_options_.GetWalDir(), log_number);
|
|
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
|
"Recovering log #%" PRIu64 " mode %d", log_number,
|
|
static_cast<int>(immutable_db_options_.wal_recovery_mode));
|
|
|
|
std::unique_ptr<SequentialFileReader> file_reader;
|
|
{
|
|
std::unique_ptr<FSSequentialFile> file;
|
|
Status status = fs_->NewSequentialFile(
|
|
fname, fs_->OptimizeForLogRead(file_options_), &file, nullptr);
|
|
if (!status.ok()) {
|
|
*log_reader = nullptr;
|
|
return status;
|
|
}
|
|
file_reader.reset(new SequentialFileReader(
|
|
std::move(file), fname, immutable_db_options_.log_readahead_size,
|
|
io_tracer_));
|
|
}
|
|
|
|
// Create the log reader.
|
|
LogReaderContainer* log_reader_container = new LogReaderContainer(
|
|
env_, immutable_db_options_.info_log, std::move(fname),
|
|
std::move(file_reader), log_number);
|
|
log_readers_.insert(std::make_pair(
|
|
log_number, std::unique_ptr<LogReaderContainer>(log_reader_container)));
|
|
}
|
|
iter = log_readers_.find(log_number);
|
|
assert(iter != log_readers_.end());
|
|
*log_reader = iter->second->reader_;
|
|
return Status::OK();
|
|
}
|
|
|
|
// After manifest recovery, replay WALs and refresh log_readers_ if necessary
|
|
// REQUIRES: log_numbers are sorted in ascending order
|
|
Status DBImplSecondary::RecoverLogFiles(
|
|
const std::vector<uint64_t>& log_numbers, SequenceNumber* next_sequence,
|
|
std::unordered_set<ColumnFamilyData*>* cfds_changed,
|
|
JobContext* job_context) {
|
|
assert(nullptr != cfds_changed);
|
|
assert(nullptr != job_context);
|
|
mutex_.AssertHeld();
|
|
Status status;
|
|
for (auto log_number : log_numbers) {
|
|
log::FragmentBufferedReader* reader = nullptr;
|
|
status = MaybeInitLogReader(log_number, &reader);
|
|
if (!status.ok()) {
|
|
return status;
|
|
}
|
|
assert(reader != nullptr);
|
|
}
|
|
|
|
const UnorderedMap<uint32_t, size_t>& running_ts_sz =
|
|
versions_->GetRunningColumnFamiliesTimestampSize();
|
|
for (auto log_number : log_numbers) {
|
|
auto it = log_readers_.find(log_number);
|
|
assert(it != log_readers_.end());
|
|
log::FragmentBufferedReader* reader = it->second->reader_;
|
|
Status* wal_read_status = it->second->status_;
|
|
assert(wal_read_status);
|
|
// Manually update the file number allocation counter in VersionSet.
|
|
versions_->MarkFileNumberUsed(log_number);
|
|
|
|
// Determine if we should tolerate incomplete records at the tail end of the
|
|
// Read all the records and add to a memtable
|
|
std::string scratch;
|
|
Slice record;
|
|
WriteBatch batch;
|
|
|
|
while (reader->ReadRecord(&record, &scratch,
|
|
immutable_db_options_.wal_recovery_mode) &&
|
|
wal_read_status->ok() && status.ok()) {
|
|
if (record.size() < WriteBatchInternal::kHeader) {
|
|
reader->GetReporter()->Corruption(
|
|
record.size(), Status::Corruption("log record too small"));
|
|
continue;
|
|
}
|
|
status = WriteBatchInternal::SetContents(&batch, record);
|
|
if (!status.ok()) {
|
|
break;
|
|
}
|
|
const UnorderedMap<uint32_t, size_t>& record_ts_sz =
|
|
reader->GetRecordedTimestampSize();
|
|
status = HandleWriteBatchTimestampSizeDifference(
|
|
&batch, running_ts_sz, record_ts_sz,
|
|
TimestampSizeConsistencyMode::kVerifyConsistency, seq_per_batch_,
|
|
batch_per_txn_);
|
|
if (!status.ok()) {
|
|
break;
|
|
}
|
|
SequenceNumber seq_of_batch = WriteBatchInternal::Sequence(&batch);
|
|
std::vector<uint32_t> column_family_ids;
|
|
status = CollectColumnFamilyIdsFromWriteBatch(batch, &column_family_ids);
|
|
if (status.ok()) {
|
|
for (const auto id : column_family_ids) {
|
|
ColumnFamilyData* cfd =
|
|
versions_->GetColumnFamilySet()->GetColumnFamily(id);
|
|
if (cfd == nullptr) {
|
|
continue;
|
|
}
|
|
cfds_changed->insert(cfd);
|
|
const std::vector<FileMetaData*>& l0_files =
|
|
cfd->current()->storage_info()->LevelFiles(0);
|
|
SequenceNumber seq =
|
|
l0_files.empty() ? 0 : l0_files.back()->fd.largest_seqno;
|
|
// If the write batch's sequence number is smaller than the last
|
|
// sequence number of the largest sequence persisted for this column
|
|
// family, then its data must reside in an SST that has already been
|
|
// added in the prior MANIFEST replay.
|
|
if (seq_of_batch <= seq) {
|
|
continue;
|
|
}
|
|
auto curr_log_num = std::numeric_limits<uint64_t>::max();
|
|
if (cfd_to_current_log_.count(cfd) > 0) {
|
|
curr_log_num = cfd_to_current_log_[cfd];
|
|
}
|
|
// If the active memtable contains records added by replaying an
|
|
// earlier WAL, then we need to seal the memtable, add it to the
|
|
// immutable memtable list and create a new active memtable.
|
|
if (!cfd->mem()->IsEmpty() &&
|
|
(curr_log_num == std::numeric_limits<uint64_t>::max() ||
|
|
curr_log_num != log_number)) {
|
|
MemTable* new_mem = cfd->ConstructNewMemtable(
|
|
cfd->GetLatestMutableCFOptions(), seq_of_batch);
|
|
cfd->mem()->SetNextLogNumber(log_number);
|
|
cfd->mem()->ConstructFragmentedRangeTombstones();
|
|
cfd->imm()->Add(cfd->mem(), &job_context->memtables_to_free);
|
|
new_mem->Ref();
|
|
cfd->SetMemtable(new_mem);
|
|
}
|
|
}
|
|
bool has_valid_writes = false;
|
|
status = WriteBatchInternal::InsertInto(
|
|
&batch, column_family_memtables_.get(),
|
|
nullptr /* flush_scheduler */, nullptr /* trim_history_scheduler*/,
|
|
true, log_number, this, false /* concurrent_memtable_writes */,
|
|
next_sequence, &has_valid_writes, seq_per_batch_, batch_per_txn_);
|
|
}
|
|
// If column family was not found, it might mean that the WAL write
|
|
// batch references to the column family that was dropped after the
|
|
// insert. We don't want to fail the whole write batch in that case --
|
|
// we just ignore the update.
|
|
// That's why we set ignore missing column families to true
|
|
// passing null flush_scheduler will disable memtable flushing which is
|
|
// needed for secondary instances
|
|
if (status.ok()) {
|
|
for (const auto id : column_family_ids) {
|
|
ColumnFamilyData* cfd =
|
|
versions_->GetColumnFamilySet()->GetColumnFamily(id);
|
|
if (cfd == nullptr) {
|
|
continue;
|
|
}
|
|
std::unordered_map<ColumnFamilyData*, uint64_t>::iterator iter =
|
|
cfd_to_current_log_.find(cfd);
|
|
if (iter == cfd_to_current_log_.end()) {
|
|
cfd_to_current_log_.insert({cfd, log_number});
|
|
} else if (log_number > iter->second) {
|
|
iter->second = log_number;
|
|
}
|
|
}
|
|
auto last_sequence = *next_sequence - 1;
|
|
if ((*next_sequence != kMaxSequenceNumber) &&
|
|
(versions_->LastSequence() <= last_sequence)) {
|
|
versions_->SetLastAllocatedSequence(last_sequence);
|
|
versions_->SetLastPublishedSequence(last_sequence);
|
|
versions_->SetLastSequence(last_sequence);
|
|
}
|
|
} else {
|
|
// We are treating this as a failure while reading since we read valid
|
|
// blocks that do not form coherent data
|
|
reader->GetReporter()->Corruption(record.size(), status);
|
|
}
|
|
}
|
|
if (status.ok() && !wal_read_status->ok()) {
|
|
status = *wal_read_status;
|
|
}
|
|
if (!status.ok()) {
|
|
wal_read_status->PermitUncheckedError();
|
|
return status;
|
|
}
|
|
}
|
|
// remove logreaders from map after successfully recovering the WAL
|
|
if (log_readers_.size() > 1) {
|
|
auto erase_iter = log_readers_.begin();
|
|
std::advance(erase_iter, log_readers_.size() - 1);
|
|
log_readers_.erase(log_readers_.begin(), erase_iter);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
Status DBImplSecondary::GetImpl(const ReadOptions& read_options,
|
|
const Slice& key,
|
|
GetImplOptions& get_impl_options) {
|
|
assert(get_impl_options.value != nullptr ||
|
|
get_impl_options.columns != nullptr ||
|
|
get_impl_options.merge_operands != nullptr);
|
|
assert(get_impl_options.column_family);
|
|
|
|
Status s;
|
|
|
|
if (read_options.timestamp) {
|
|
s = FailIfTsMismatchCf(get_impl_options.column_family,
|
|
*(read_options.timestamp));
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
} else {
|
|
s = FailIfCfHasTs(get_impl_options.column_family);
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
}
|
|
|
|
// Clear the timestamps for returning results so that we can distinguish
|
|
// between tombstone or key that has never been written
|
|
if (get_impl_options.timestamp) {
|
|
get_impl_options.timestamp->clear();
|
|
}
|
|
|
|
PERF_CPU_TIMER_GUARD(get_cpu_nanos, immutable_db_options_.clock);
|
|
StopWatch sw(immutable_db_options_.clock, stats_, DB_GET);
|
|
PERF_TIMER_GUARD(get_snapshot_time);
|
|
|
|
const Comparator* ucmp = get_impl_options.column_family->GetComparator();
|
|
assert(ucmp);
|
|
SequenceNumber snapshot = versions_->LastSequence();
|
|
GetWithTimestampReadCallback read_cb(snapshot);
|
|
auto cfh = static_cast_with_check<ColumnFamilyHandleImpl>(
|
|
get_impl_options.column_family);
|
|
auto cfd = cfh->cfd();
|
|
bool is_blob_index = false;
|
|
bool* is_blob_ptr = get_impl_options.is_blob_index;
|
|
std::string timestamp_storage;
|
|
std::string* ts = nullptr;
|
|
if (ucmp->timestamp_size() > 0) {
|
|
ts = get_impl_options.timestamp != nullptr
|
|
? get_impl_options.timestamp
|
|
: (get_impl_options.get_value ? ×tamp_storage : nullptr);
|
|
}
|
|
if (!is_blob_ptr && get_impl_options.get_value) {
|
|
is_blob_ptr = &is_blob_index;
|
|
}
|
|
const bool resolve_blob_backed_memtable_value =
|
|
get_impl_options.get_value && (is_blob_ptr == &is_blob_index);
|
|
if (tracer_) {
|
|
InstrumentedMutexLock lock(&trace_mutex_);
|
|
if (tracer_) {
|
|
tracer_->Get(get_impl_options.column_family, key);
|
|
}
|
|
}
|
|
|
|
// Acquire SuperVersion
|
|
SuperVersion* super_version = GetAndRefSuperVersion(cfd);
|
|
if (read_options.timestamp && read_options.timestamp->size() > 0) {
|
|
s = FailIfReadCollapsedHistory(cfd, super_version,
|
|
*(read_options.timestamp));
|
|
if (!s.ok()) {
|
|
ReturnAndCleanupSuperVersion(cfd, super_version);
|
|
return s;
|
|
}
|
|
}
|
|
MergeContext merge_context;
|
|
// TODO - Large Result Optimization for Secondary DB
|
|
// (https://github.com/facebook/rocksdb/pull/10458)
|
|
|
|
SequenceNumber max_covering_tombstone_seq = 0;
|
|
LookupKey lkey(key, snapshot, read_options.timestamp);
|
|
PERF_TIMER_STOP(get_snapshot_time);
|
|
bool done = false;
|
|
std::optional<BlobFetcher> memtable_blob_fetcher;
|
|
if (cfd->ioptions().enable_blob_direct_write ||
|
|
cfd->GetLatestMutableCFOptions().enable_blob_files) {
|
|
// Catch-up can rebuild older blob references into memtables after mutable
|
|
// blob-file settings change, so keep blob resolution available whenever
|
|
// either blob knob indicates it may be needed.
|
|
memtable_blob_fetcher.emplace(super_version->current, read_options,
|
|
cfd->blob_file_cache(),
|
|
/*allow_write_path_fallback=*/true);
|
|
}
|
|
const BlobFetcher* memtable_blob_fetcher_ptr =
|
|
memtable_blob_fetcher ? &*memtable_blob_fetcher : nullptr;
|
|
|
|
// Look up starts here
|
|
if (get_impl_options.get_value) {
|
|
if (super_version->mem->Get(
|
|
lkey,
|
|
get_impl_options.value ? get_impl_options.value->GetSelf()
|
|
: nullptr,
|
|
get_impl_options.columns, ts, &s, &merge_context,
|
|
&max_covering_tombstone_seq, read_options,
|
|
false /* immutable_memtable */, &read_cb, is_blob_ptr,
|
|
/*do_merge=*/true, memtable_blob_fetcher_ptr)) {
|
|
done = true;
|
|
DBImpl::PostprocessMemtableValueRead(
|
|
key, ts, resolve_blob_backed_memtable_value,
|
|
memtable_blob_fetcher_ptr, get_impl_options.value,
|
|
get_impl_options.columns, &s, &is_blob_index,
|
|
get_impl_options.value_found);
|
|
RecordTick(stats_, MEMTABLE_HIT);
|
|
} else if ((s.ok() || s.IsMergeInProgress()) &&
|
|
super_version->imm->Get(
|
|
lkey,
|
|
get_impl_options.value ? get_impl_options.value->GetSelf()
|
|
: nullptr,
|
|
get_impl_options.columns, ts, &s, &merge_context,
|
|
&max_covering_tombstone_seq, read_options, &read_cb,
|
|
is_blob_ptr, memtable_blob_fetcher_ptr)) {
|
|
done = true;
|
|
DBImpl::PostprocessMemtableValueRead(
|
|
key, ts, resolve_blob_backed_memtable_value,
|
|
memtable_blob_fetcher_ptr, get_impl_options.value,
|
|
get_impl_options.columns, &s, &is_blob_index,
|
|
get_impl_options.value_found);
|
|
RecordTick(stats_, MEMTABLE_HIT);
|
|
}
|
|
} else {
|
|
// GetMergeOperands
|
|
if (super_version->mem->Get(
|
|
lkey,
|
|
get_impl_options.value ? get_impl_options.value->GetSelf()
|
|
: nullptr,
|
|
get_impl_options.columns, ts, &s, &merge_context,
|
|
&max_covering_tombstone_seq, read_options,
|
|
false /* immutable_memtable */, &read_cb,
|
|
/*is_blob_index=*/nullptr, /*do_merge=*/false,
|
|
memtable_blob_fetcher_ptr)) {
|
|
done = true;
|
|
RecordTick(stats_, MEMTABLE_HIT);
|
|
} else if ((s.ok() || s.IsMergeInProgress()) &&
|
|
super_version->imm->GetMergeOperands(
|
|
lkey, &s, &merge_context, &max_covering_tombstone_seq,
|
|
read_options, memtable_blob_fetcher_ptr)) {
|
|
done = true;
|
|
RecordTick(stats_, MEMTABLE_HIT);
|
|
}
|
|
}
|
|
if (!s.ok() && !s.IsMergeInProgress() && !s.IsNotFound()) {
|
|
assert(done);
|
|
ReturnAndCleanupSuperVersion(cfd, super_version);
|
|
return s;
|
|
}
|
|
if (!done) {
|
|
PERF_TIMER_GUARD(get_from_output_files_time);
|
|
PinnedIteratorsManager pinned_iters_mgr;
|
|
super_version->current->Get(
|
|
read_options, lkey, get_impl_options.value, get_impl_options.columns,
|
|
ts, &s, &merge_context, &max_covering_tombstone_seq, &pinned_iters_mgr,
|
|
/*value_found*/ nullptr,
|
|
/*key_exists*/ nullptr, /*seq*/ nullptr, &read_cb, /*is_blob*/ nullptr,
|
|
/*do_merge=*/get_impl_options.get_value);
|
|
RecordTick(stats_, MEMTABLE_MISS);
|
|
}
|
|
{
|
|
PERF_TIMER_GUARD(get_post_process_time);
|
|
ReturnAndCleanupSuperVersion(cfd, super_version);
|
|
RecordTick(stats_, NUMBER_KEYS_READ);
|
|
size_t size = 0;
|
|
if (get_impl_options.value) {
|
|
size = get_impl_options.value->size();
|
|
} else if (get_impl_options.columns) {
|
|
size = get_impl_options.columns->serialized_size();
|
|
} else if (get_impl_options.merge_operands) {
|
|
*get_impl_options.number_of_operands =
|
|
static_cast<int>(merge_context.GetNumOperands());
|
|
for (const Slice& sl : merge_context.GetOperands()) {
|
|
size += sl.size();
|
|
get_impl_options.merge_operands->PinSelf(sl);
|
|
get_impl_options.merge_operands++;
|
|
}
|
|
}
|
|
RecordTick(stats_, BYTES_READ, size);
|
|
RecordTimeToHistogram(stats_, BYTES_PER_READ, size);
|
|
PERF_COUNTER_ADD(get_read_bytes, size);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
Iterator* DBImplSecondary::NewIterator(const ReadOptions& _read_options,
|
|
ColumnFamilyHandle* column_family) {
|
|
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
|
_read_options.io_activity != Env::IOActivity::kDBIterator) {
|
|
return NewErrorIterator(Status::InvalidArgument(
|
|
"Can only call NewIterator with `ReadOptions::io_activity` is "
|
|
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kDBIterator`"));
|
|
}
|
|
ReadOptions read_options(_read_options);
|
|
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
|
read_options.io_activity = Env::IOActivity::kDBIterator;
|
|
}
|
|
if (read_options.read_tier == kPersistedTier) {
|
|
return NewErrorIterator(Status::NotSupported(
|
|
"ReadTier::kPersistedData is not yet supported in iterators."));
|
|
}
|
|
|
|
assert(column_family);
|
|
if (read_options.timestamp) {
|
|
const Status s =
|
|
FailIfTsMismatchCf(column_family, *(read_options.timestamp));
|
|
if (!s.ok()) {
|
|
return NewErrorIterator(s);
|
|
}
|
|
} else {
|
|
const Status s = FailIfCfHasTs(column_family);
|
|
if (!s.ok()) {
|
|
return NewErrorIterator(s);
|
|
}
|
|
}
|
|
|
|
Iterator* result = nullptr;
|
|
auto cfh = static_cast_with_check<ColumnFamilyHandleImpl>(column_family);
|
|
assert(cfh != nullptr);
|
|
auto cfd = cfh->cfd();
|
|
if (read_options.tailing) {
|
|
return NewErrorIterator(Status::NotSupported(
|
|
"tailing iterator not supported in secondary mode"));
|
|
} else if (read_options.snapshot != nullptr) {
|
|
// TODO (yanqin) support snapshot.
|
|
return NewErrorIterator(
|
|
Status::NotSupported("snapshot not supported in secondary mode"));
|
|
} else {
|
|
SequenceNumber snapshot(kMaxSequenceNumber);
|
|
SuperVersion* sv = cfd->GetReferencedSuperVersion(this);
|
|
if (read_options.timestamp && read_options.timestamp->size() > 0) {
|
|
const Status s =
|
|
FailIfReadCollapsedHistory(cfd, sv, *(read_options.timestamp));
|
|
if (!s.ok()) {
|
|
CleanupSuperVersion(sv);
|
|
return NewErrorIterator(s);
|
|
}
|
|
}
|
|
result = NewIteratorImpl(read_options, cfh, sv, snapshot,
|
|
nullptr /*read_callback*/);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
ArenaWrappedDBIter* DBImplSecondary::NewIteratorImpl(
|
|
const ReadOptions& read_options, ColumnFamilyHandleImpl* cfh,
|
|
SuperVersion* super_version, SequenceNumber snapshot,
|
|
ReadCallback* read_callback, bool expose_blob_index, bool allow_refresh) {
|
|
assert(nullptr != cfh);
|
|
assert(snapshot == kMaxSequenceNumber);
|
|
snapshot = versions_->LastSequence();
|
|
assert(snapshot != kMaxSequenceNumber);
|
|
return NewArenaWrappedDbIterator(env_, read_options, cfh, super_version,
|
|
snapshot, read_callback, this,
|
|
expose_blob_index, allow_refresh,
|
|
/*allow_mark_memtable_for_flush=*/false);
|
|
}
|
|
|
|
Status DBImplSecondary::NewIterators(
|
|
const ReadOptions& _read_options,
|
|
const std::vector<ColumnFamilyHandle*>& column_families,
|
|
std::vector<Iterator*>* iterators) {
|
|
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
|
_read_options.io_activity != Env::IOActivity::kDBIterator) {
|
|
return Status::InvalidArgument(
|
|
"Can only call NewIterators with `ReadOptions::io_activity` is "
|
|
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kDBIterator`");
|
|
}
|
|
ReadOptions read_options(_read_options);
|
|
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
|
read_options.io_activity = Env::IOActivity::kDBIterator;
|
|
}
|
|
if (read_options.read_tier == kPersistedTier) {
|
|
return Status::NotSupported(
|
|
"ReadTier::kPersistedData is not yet supported in iterators.");
|
|
}
|
|
ReadCallback* read_callback = nullptr; // No read callback provided.
|
|
if (iterators == nullptr) {
|
|
return Status::InvalidArgument("iterators not allowed to be nullptr");
|
|
}
|
|
|
|
if (read_options.timestamp) {
|
|
for (auto* cf : column_families) {
|
|
assert(cf);
|
|
const Status s = FailIfTsMismatchCf(cf, *(read_options.timestamp));
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
}
|
|
} else {
|
|
for (auto* cf : column_families) {
|
|
assert(cf);
|
|
const Status s = FailIfCfHasTs(cf);
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
}
|
|
}
|
|
iterators->clear();
|
|
iterators->reserve(column_families.size());
|
|
if (read_options.tailing) {
|
|
return Status::NotSupported(
|
|
"tailing iterator not supported in secondary mode");
|
|
} else if (read_options.snapshot != nullptr) {
|
|
// TODO (yanqin) support snapshot.
|
|
return Status::NotSupported("snapshot not supported in secondary mode");
|
|
} else {
|
|
SequenceNumber read_seq(kMaxSequenceNumber);
|
|
autovector<std::tuple<ColumnFamilyHandleImpl*, SuperVersion*>> cfh_to_sv;
|
|
const bool check_read_ts =
|
|
read_options.timestamp && read_options.timestamp->size() > 0;
|
|
for (auto cf : column_families) {
|
|
auto cfh = static_cast_with_check<ColumnFamilyHandleImpl>(cf);
|
|
auto cfd = cfh->cfd();
|
|
SuperVersion* sv = cfd->GetReferencedSuperVersion(this);
|
|
cfh_to_sv.emplace_back(cfh, sv);
|
|
if (check_read_ts) {
|
|
const Status s =
|
|
FailIfReadCollapsedHistory(cfd, sv, *(read_options.timestamp));
|
|
if (!s.ok()) {
|
|
for (auto prev_entry : cfh_to_sv) {
|
|
CleanupSuperVersion(std::get<1>(prev_entry));
|
|
}
|
|
return s;
|
|
}
|
|
}
|
|
}
|
|
assert(cfh_to_sv.size() == column_families.size());
|
|
for (auto [cfh, sv] : cfh_to_sv) {
|
|
iterators->push_back(
|
|
NewIteratorImpl(read_options, cfh, sv, read_seq, read_callback));
|
|
}
|
|
}
|
|
return Status::OK();
|
|
}
|
|
|
|
Status DBImplSecondary::TryCatchUpWithPrimary() {
|
|
assert(versions_.get() != nullptr);
|
|
Status s;
|
|
// read the manifest and apply new changes to the secondary instance
|
|
std::unordered_set<ColumnFamilyData*> cfds_changed;
|
|
JobContext job_context(0, true /*create_superversion*/);
|
|
{
|
|
InstrumentedMutexLock lock_guard(&mutex_);
|
|
assert(manifest_reader_.get() != nullptr);
|
|
s = static_cast_with_check<ReactiveVersionSet>(versions_.get())
|
|
->ReadAndApply(&mutex_, &manifest_reader_,
|
|
manifest_reader_status_.get(), &cfds_changed,
|
|
/*files_to_delete=*/nullptr);
|
|
|
|
ROCKS_LOG_INFO(immutable_db_options_.info_log, "Last sequence is %" PRIu64,
|
|
static_cast<uint64_t>(versions_->LastSequence()));
|
|
for (ColumnFamilyData* cfd : cfds_changed) {
|
|
if (cfd->IsDropped()) {
|
|
ROCKS_LOG_DEBUG(immutable_db_options_.info_log, "[%s] is dropped\n",
|
|
cfd->GetName().c_str());
|
|
continue;
|
|
}
|
|
VersionStorageInfo::LevelSummaryStorage tmp;
|
|
ROCKS_LOG_DEBUG(immutable_db_options_.info_log,
|
|
"[%s] Level summary: %s\n", cfd->GetName().c_str(),
|
|
cfd->current()->storage_info()->LevelSummary(&tmp));
|
|
}
|
|
|
|
// list wal_dir to discover new WALs and apply new changes to the secondary
|
|
// instance
|
|
if (s.ok()) {
|
|
s = FindAndRecoverLogFiles(&cfds_changed, &job_context);
|
|
if (s.IsPathNotFound()) {
|
|
ROCKS_LOG_INFO(
|
|
immutable_db_options_.info_log,
|
|
"Secondary tries to read WAL, but WAL file(s) have already "
|
|
"been purged by primary.");
|
|
s = Status::OK();
|
|
}
|
|
}
|
|
if (s.ok()) {
|
|
for (auto cfd : cfds_changed) {
|
|
cfd->imm()->RemoveOldMemTables(cfd->GetLogNumber(),
|
|
&job_context.memtables_to_free);
|
|
auto& sv_context = job_context.superversion_contexts.back();
|
|
cfd->InstallSuperVersion(&sv_context, &mutex_);
|
|
sv_context.NewSuperVersion();
|
|
}
|
|
}
|
|
}
|
|
job_context.Clean();
|
|
|
|
// Cleanup unused, obsolete files.
|
|
JobContext purge_files_job_context(0);
|
|
{
|
|
InstrumentedMutexLock lock_guard(&mutex_);
|
|
// Currently, secondary instance does not own the database files, thus it
|
|
// is unnecessary for the secondary to force full scan.
|
|
FindObsoleteFiles(&purge_files_job_context, /*force=*/false);
|
|
}
|
|
if (purge_files_job_context.HaveSomethingToDelete()) {
|
|
PurgeObsoleteFiles(purge_files_job_context);
|
|
}
|
|
purge_files_job_context.Clean();
|
|
return s;
|
|
}
|
|
|
|
Status DB::OpenAsSecondary(const Options& options, const std::string& dbname,
|
|
const std::string& secondary_path,
|
|
std::unique_ptr<DB>* dbptr) {
|
|
*dbptr = nullptr;
|
|
|
|
DBOptions db_options(options);
|
|
ColumnFamilyOptions cf_options(options);
|
|
std::vector<ColumnFamilyDescriptor> column_families;
|
|
column_families.emplace_back(kDefaultColumnFamilyName, cf_options);
|
|
std::vector<ColumnFamilyHandle*> handles;
|
|
|
|
Status s = DB::OpenAsSecondary(db_options, dbname, secondary_path,
|
|
column_families, &handles, dbptr);
|
|
if (s.ok()) {
|
|
assert(handles.size() == 1);
|
|
delete handles[0];
|
|
}
|
|
return s;
|
|
}
|
|
|
|
Status DB::OpenAsSecondary(
|
|
const DBOptions& db_options, const std::string& dbname,
|
|
const std::string& secondary_path,
|
|
const std::vector<ColumnFamilyDescriptor>& column_families,
|
|
std::vector<ColumnFamilyHandle*>* handles, std::unique_ptr<DB>* dbptr) {
|
|
return DBImplSecondary::OpenAsSecondaryImpl(
|
|
db_options, dbname, secondary_path, column_families, handles, dbptr,
|
|
/*recover_wal=*/true);
|
|
}
|
|
|
|
Status DBImplSecondary::OpenAsSecondaryImpl(
|
|
const DBOptions& db_options, const std::string& dbname,
|
|
const std::string& secondary_path,
|
|
const std::vector<ColumnFamilyDescriptor>& column_families,
|
|
std::vector<ColumnFamilyHandle*>* handles, std::unique_ptr<DB>* dbptr,
|
|
bool recover_wal) {
|
|
*dbptr = nullptr;
|
|
|
|
DBOptions tmp_opts(db_options);
|
|
Status s;
|
|
if (nullptr == tmp_opts.info_log) {
|
|
s = CreateLoggerFromOptions(secondary_path, tmp_opts, &tmp_opts.info_log);
|
|
if (!s.ok()) {
|
|
tmp_opts.info_log = nullptr;
|
|
return s;
|
|
}
|
|
}
|
|
|
|
assert(tmp_opts.info_log != nullptr);
|
|
if (db_options.max_open_files != -1) {
|
|
std::ostringstream oss;
|
|
oss << "The primary instance may delete all types of files after they "
|
|
"become obsolete. The application can coordinate the primary and "
|
|
"secondary so that primary does not delete/rename files that are "
|
|
"currently being used by the secondary. Alternatively, a custom "
|
|
"Env/FS can be provided such that files become inaccessible only "
|
|
"after all primary and secondaries indicate that they are obsolete "
|
|
"and deleted. If the above two are not possible, you can open the "
|
|
"secondary instance with `max_open_files==-1` so that secondary "
|
|
"will eagerly keep all table files open. Even if a file is deleted, "
|
|
"its content can still be accessed via a prior open file "
|
|
"descriptor. This is a hacky workaround for only table files. If "
|
|
"none of the above is done, then point lookup or "
|
|
"range scan via the secondary instance can result in IOError: file "
|
|
"not found. This can be resolved by retrying "
|
|
"TryCatchUpWithPrimary().";
|
|
ROCKS_LOG_WARN(tmp_opts.info_log, "%s", oss.str().c_str());
|
|
}
|
|
|
|
handles->clear();
|
|
DBImplSecondary* impl = new DBImplSecondary(tmp_opts, dbname, secondary_path);
|
|
impl->versions_.reset(new ReactiveVersionSet(
|
|
dbname, &impl->immutable_db_options_, impl->mutable_db_options_,
|
|
impl->file_options_, impl->table_cache_.get(),
|
|
impl->write_buffer_manager_, &impl->write_controller_, impl->io_tracer_,
|
|
impl->db_id_, impl->db_session_id_));
|
|
impl->column_family_memtables_.reset(
|
|
new ColumnFamilyMemTablesImpl(impl->versions_->GetColumnFamilySet()));
|
|
impl->wal_in_db_path_ = impl->immutable_db_options_.IsWalDirSameAsDBPath();
|
|
|
|
impl->mutex_.Lock();
|
|
JobContext job_context(0);
|
|
s = impl->Recover(column_families, true, false, false);
|
|
// WAL recovery is optional: DB::OpenAsSecondary() needs it to replay
|
|
// memtable data, while DB::OpenAndCompact() skips it since remote
|
|
// compaction only needs LSM state from MANIFEST.
|
|
if (s.ok() && recover_wal) {
|
|
std::unordered_set<ColumnFamilyData*> cfds_changed;
|
|
s = impl->FindAndRecoverLogFiles(&cfds_changed, &job_context);
|
|
if (s.IsPathNotFound()) {
|
|
ROCKS_LOG_INFO(impl->immutable_db_options_.info_log,
|
|
"Secondary tries to read WAL, but WAL file(s) have "
|
|
"already been purged by primary.");
|
|
s = Status::OK();
|
|
}
|
|
}
|
|
if (s.ok()) {
|
|
for (const auto& cf : column_families) {
|
|
auto cfd =
|
|
impl->versions_->GetColumnFamilySet()->GetColumnFamily(cf.name);
|
|
if (nullptr == cfd) {
|
|
s = Status::InvalidArgument("Column family not found", cf.name);
|
|
break;
|
|
}
|
|
handles->push_back(new ColumnFamilyHandleImpl(cfd, impl, &impl->mutex_));
|
|
}
|
|
}
|
|
SuperVersionContext sv_context(true /* create_superversion */);
|
|
if (s.ok()) {
|
|
for (auto cfd : *impl->versions_->GetColumnFamilySet()) {
|
|
sv_context.NewSuperVersion();
|
|
cfd->InstallSuperVersion(&sv_context, &impl->mutex_);
|
|
}
|
|
impl->MarkAsyncFileOpenNotNeeded();
|
|
}
|
|
impl->mutex_.Unlock();
|
|
sv_context.Clean();
|
|
job_context.Clean();
|
|
if (s.ok()) {
|
|
dbptr->reset(impl);
|
|
for (auto h : *handles) {
|
|
impl->NewThreadStatusCfInfo(
|
|
static_cast_with_check<ColumnFamilyHandleImpl>(h)->cfd());
|
|
}
|
|
} else {
|
|
for (auto h : *handles) {
|
|
delete h;
|
|
}
|
|
handles->clear();
|
|
delete impl;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
Status DBImplSecondary::ScanCompactionProgressFiles(
|
|
CompactionProgressFilesScan* scan_result) {
|
|
assert(scan_result != nullptr);
|
|
scan_result->Clear();
|
|
|
|
WriteOptions write_options(Env::IOActivity::kCompaction);
|
|
IOOptions opts;
|
|
Status s = WritableFileWriter::PrepareIOOptions(write_options, opts);
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
|
|
std::vector<std::string> all_filenames;
|
|
s = fs_->GetChildren(secondary_path_, opts, &all_filenames, nullptr /* dbg*/);
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
|
|
for (const auto& filename : all_filenames) {
|
|
if (filename == "." || filename == "..") {
|
|
continue;
|
|
}
|
|
|
|
uint64_t number;
|
|
FileType type;
|
|
|
|
if (!ParseFileName(filename, &number, &type)) {
|
|
continue;
|
|
}
|
|
|
|
// Categorize compaction progress files
|
|
if (type == kCompactionProgressFile) {
|
|
if (number > scan_result->latest_progress_timestamp) {
|
|
// Found a newer progress file
|
|
if (scan_result->HasLatestProgressFile()) {
|
|
// Previous "latest" becomes "old"
|
|
scan_result->old_progress_filenames.push_back(
|
|
scan_result->latest_progress_filename.value());
|
|
}
|
|
scan_result->latest_progress_timestamp = number;
|
|
scan_result->latest_progress_filename = filename;
|
|
} else {
|
|
// This is an older progress file
|
|
scan_result->old_progress_filenames.push_back(filename);
|
|
}
|
|
} else if (type == kTempFile &&
|
|
filename.find(kCompactionProgressFileNamePrefix) == 0) {
|
|
// Temporary progress files
|
|
scan_result->temp_progress_filenames.push_back(filename);
|
|
} else if (type == kTableFile) {
|
|
// Collect table file numbers for CleanupPhysicalCompactionOutputFiles
|
|
scan_result->table_file_numbers.push_back(number);
|
|
}
|
|
}
|
|
|
|
return Status::OK();
|
|
}
|
|
|
|
Status DBImplSecondary::DeleteCompactionProgressFiles(
|
|
const std::vector<std::string>& filenames) {
|
|
WriteOptions write_options(Env::IOActivity::kCompaction);
|
|
IOOptions opts;
|
|
Status s = WritableFileWriter::PrepareIOOptions(write_options, opts);
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
|
|
for (const auto& filename : filenames) {
|
|
std::string file_path = secondary_path_ + "/" + filename;
|
|
Status delete_status = fs_->DeleteFile(file_path, opts, nullptr /* dbg */);
|
|
if (!delete_status.ok()) {
|
|
return delete_status;
|
|
}
|
|
}
|
|
|
|
return Status::OK();
|
|
}
|
|
|
|
Status DBImplSecondary::CleanupOldAndTemporaryCompactionProgressFiles(
|
|
bool preserve_latest, const CompactionProgressFilesScan& scan_result) {
|
|
std::vector<std::string> filenames_to_delete;
|
|
|
|
// Always delete old progress files
|
|
filenames_to_delete.insert(filenames_to_delete.end(),
|
|
scan_result.old_progress_filenames.begin(),
|
|
scan_result.old_progress_filenames.end());
|
|
|
|
// Always delete temp files
|
|
filenames_to_delete.insert(filenames_to_delete.end(),
|
|
scan_result.temp_progress_filenames.begin(),
|
|
scan_result.temp_progress_filenames.end());
|
|
|
|
// Conditionally delete latest file
|
|
if (!preserve_latest && scan_result.HasLatestProgressFile()) {
|
|
filenames_to_delete.push_back(scan_result.latest_progress_filename.value());
|
|
}
|
|
|
|
return DeleteCompactionProgressFiles(filenames_to_delete);
|
|
}
|
|
|
|
// Loads compaction progress from a file and cleans up extra output
|
|
// files. After loading the progress, this function identifies and deletes any
|
|
// SST files in the output folder that are NOT tracked in the
|
|
// progress. This ensures consistency between the progress file and
|
|
// actual output files on disk.
|
|
Status DBImplSecondary::LoadCompactionProgressAndCleanupExtraOutputFiles(
|
|
const std::string& compaction_progress_file_path,
|
|
const CompactionProgressFilesScan& scan_result) {
|
|
Status s = ParseCompactionProgressFile(compaction_progress_file_path,
|
|
&compaction_progress_);
|
|
if (s.ok()) {
|
|
s = CleanupPhysicalCompactionOutputFiles(true /* preserve_tracked_files */,
|
|
scan_result);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
Status DBImplSecondary::ParseCompactionProgressFile(
|
|
const std::string& compaction_progress_file_path,
|
|
CompactionProgress* compaction_progress) {
|
|
std::unique_ptr<FSSequentialFile> file;
|
|
Status s = fs_->NewSequentialFile(compaction_progress_file_path,
|
|
FileOptions(), &file, nullptr /* dbg */);
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
|
|
std::unique_ptr<SequentialFileReader> file_reader(new SequentialFileReader(
|
|
std::move(file), compaction_progress_file_path,
|
|
immutable_db_options_.log_readahead_size, io_tracer_, {} /* listeners */,
|
|
immutable_db_options_.rate_limiter.get()));
|
|
|
|
Status reader_status;
|
|
|
|
struct CompactionProgressReaderReporter : public log::Reader::Reporter {
|
|
Status* status;
|
|
explicit CompactionProgressReaderReporter(Status* s) : status(s) {}
|
|
|
|
void Corruption(size_t /*bytes*/, const Status& s,
|
|
uint64_t /*log_number*/) override {
|
|
if (status->ok()) {
|
|
*status = s;
|
|
}
|
|
}
|
|
|
|
void OldLogRecord(size_t /*bytes*/) override {
|
|
// Ignore old records
|
|
}
|
|
} progress_reporter(&reader_status);
|
|
|
|
log::Reader compaction_progress_reader(
|
|
immutable_db_options_.info_log, std::move(file_reader),
|
|
&progress_reporter, true /* checksum */, 0 /* log_num */);
|
|
|
|
// LIMITATION: Only supports resuming single subcompaction
|
|
SubcompactionProgressBuilder progress_builder;
|
|
Slice slice;
|
|
std::string record;
|
|
|
|
while (compaction_progress_reader.ReadRecord(&slice, &record) &&
|
|
reader_status.ok()) {
|
|
VersionEdit edit;
|
|
s = edit.DecodeFrom(slice);
|
|
if (!s.ok()) {
|
|
break;
|
|
}
|
|
|
|
bool res = progress_builder.ProcessVersionEdit(edit);
|
|
if (!res) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!reader_status.ok()) {
|
|
return reader_status;
|
|
}
|
|
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
|
|
if (progress_builder.HasAccumulatedSubcompactionProgress()) {
|
|
compaction_progress->clear();
|
|
compaction_progress->push_back(
|
|
progress_builder.GetAccumulatedSubcompactionProgress());
|
|
} else {
|
|
s = Status::NotFound("No compaction progress was persisted yet");
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
Status DBImplSecondary::RenameCompactionProgressFile(
|
|
const std::string& temp_file_path, std::string* final_file_path) {
|
|
uint64_t current_time = env_->NowMicros();
|
|
*final_file_path = CompactionProgressFileName(secondary_path_, current_time);
|
|
|
|
WriteOptions write_options(Env::IOActivity::kCompaction);
|
|
IOOptions opts;
|
|
Status s = WritableFileWriter::PrepareIOOptions(write_options, opts);
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
|
|
s = fs_->RenameFile(temp_file_path, *final_file_path, opts,
|
|
nullptr /* dbg */);
|
|
|
|
return s;
|
|
}
|
|
|
|
Status DBImplSecondary::CleanupPhysicalCompactionOutputFiles(
|
|
bool preserve_tracked_files,
|
|
const CompactionProgressFilesScan& scan_result) {
|
|
std::unordered_set<uint64_t> files_to_preserve;
|
|
|
|
if (preserve_tracked_files) {
|
|
for (const auto& subcompaction_progress : compaction_progress_) {
|
|
for (const auto& file_metadata :
|
|
subcompaction_progress.output_level_progress.GetOutputFiles()) {
|
|
files_to_preserve.insert(file_metadata.fd.GetNumber());
|
|
}
|
|
for (const auto& file_metadata :
|
|
subcompaction_progress.proximal_output_level_progress
|
|
.GetOutputFiles()) {
|
|
files_to_preserve.insert(file_metadata.fd.GetNumber());
|
|
}
|
|
}
|
|
}
|
|
|
|
WriteOptions write_options(Env::IOActivity::kCompaction);
|
|
IOOptions opts;
|
|
Status s = WritableFileWriter::PrepareIOOptions(write_options, opts);
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
|
|
for (uint64_t file_number : scan_result.table_file_numbers) {
|
|
bool should_delete =
|
|
!preserve_tracked_files ||
|
|
(files_to_preserve.find(file_number) == files_to_preserve.end());
|
|
|
|
if (should_delete) {
|
|
std::string file_path = MakeTableFileName(secondary_path_, file_number);
|
|
Status delete_status =
|
|
fs_->DeleteFile(file_path, opts, nullptr /* dbg */);
|
|
if (!delete_status.ok()) {
|
|
return delete_status;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Status::OK();
|
|
}
|
|
|
|
Status DBImplSecondary::InitializeCompactionWorkspace(
|
|
bool allow_resumption, std::unique_ptr<FSDirectory>* output_dir,
|
|
std::unique_ptr<log::Writer>* compaction_progress_writer) {
|
|
// Create output directory if it doest exist yet
|
|
Status s = CreateAndNewDirectory(fs_.get(), secondary_path_, output_dir);
|
|
if (!s.ok() || !allow_resumption) {
|
|
return s;
|
|
}
|
|
|
|
s = PrepareCompactionProgressState();
|
|
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
|
|
s = FinalizeCompactionProgressWriter(compaction_progress_writer);
|
|
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
|
|
return Status::OK();
|
|
}
|
|
|
|
// PrepareCompactionProgressState() manages compaction progress files and output
|
|
// files to ensure a clean, consistent state for resuming or starting fresh
|
|
// compaction.
|
|
//
|
|
// PRECONDITION:
|
|
// - This function is ONLY called when allow_resumption = true
|
|
// - The caller wants resumption support for this compaction attempt
|
|
//
|
|
// FILE SYSTEM STATE (before entering this function):
|
|
// - 0 or more compaction progress files may exist in `secondary_path_`:
|
|
// * Latest progress file (from the most recent compaction attempt)
|
|
// * Older progress files (left by crashing during a previous
|
|
// InitializeCompactionWorkspace() call)
|
|
// * Temporary progress files (left by crashing during a previous
|
|
// InitializeCompactionWorkspace() call)
|
|
// - 0 or more compaction output files may exist in `secondary_path_`
|
|
//
|
|
// POSTCONDITIONS (after this function):
|
|
// - IF the latest progress file exists AND it parses successfully AND
|
|
// actually contains valid compaction progress:
|
|
// * Exactly one latest progress file remains
|
|
// * All older and temporary compaction progress files are deleted
|
|
// * All corresponding compaction output files are preserved
|
|
// * All extra compaction output files are deleted (files left by
|
|
// compaction
|
|
// crashing before persisting the progress)
|
|
// * Result: Ready to resume compaction from the saved progress
|
|
// - OTHERWISE (no latest progress file OR it fails to parse OR it's
|
|
// invalid):
|
|
// * ALL compaction progress files are deleted (latest + older +
|
|
// temporary)
|
|
// * ALL compaction output files are deleted
|
|
// * Result: Ready to start fresh compaction (despite allow_resumption =
|
|
// true, we cannot resume because there's no valid progress to resume from)
|
|
//
|
|
// ERROR HANDLING:
|
|
// - ON ERROR (if any of the postconditions cannot be achieved):
|
|
// * Function returns error status
|
|
// * File system may be left in a partially modified state
|
|
// * Caller should manually clean up secondary_path_ before retrying
|
|
// * Subsequent OpenAndCompact() calls to this clean secondary_path_ will
|
|
// effectively start fresh compaction
|
|
Status DBImplSecondary::PrepareCompactionProgressState() {
|
|
Status s;
|
|
|
|
// STEP 1: Scan directory ONCE (includes progress files + table files)
|
|
CompactionProgressFilesScan scan_result;
|
|
s = ScanCompactionProgressFiles(&scan_result);
|
|
if (!s.ok()) {
|
|
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
|
|
"Encountered error when scanning for compaction "
|
|
"progress files: %s",
|
|
s.ToString().c_str());
|
|
return s;
|
|
}
|
|
|
|
std::optional<std::string> latest_progress_file =
|
|
scan_result.latest_progress_filename;
|
|
|
|
// STEP 2: Determine if we should resume
|
|
bool should_resume = false;
|
|
if (latest_progress_file.has_value()) {
|
|
should_resume = true;
|
|
} else {
|
|
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
|
"Did not find any latest compaction progress file. "
|
|
"Will perform clean up to start fresh compaction");
|
|
}
|
|
|
|
// STEP 3: Cleanup using pre-scanned results
|
|
if (should_resume) {
|
|
// Keep latest, delete old/temp
|
|
s = CleanupOldAndTemporaryCompactionProgressFiles(
|
|
true /* preserve_latest */, scan_result);
|
|
} else {
|
|
// Delete everything including latest
|
|
s = CleanupOldAndTemporaryCompactionProgressFiles(
|
|
false /* preserve_latest */, scan_result);
|
|
latest_progress_file.reset();
|
|
}
|
|
|
|
if (!s.ok()) {
|
|
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
|
|
"Failed to clean up compaction progress file(s): %s. "
|
|
"Will fail the compaction",
|
|
s.ToString().c_str());
|
|
return s;
|
|
}
|
|
|
|
// STEP 4: Load progress if resuming
|
|
if (latest_progress_file.has_value()) {
|
|
uint64_t timestamp = scan_result.latest_progress_timestamp;
|
|
|
|
std::string compaction_progress_file_path =
|
|
CompactionProgressFileName(secondary_path_, timestamp);
|
|
|
|
s = LoadCompactionProgressAndCleanupExtraOutputFiles(
|
|
compaction_progress_file_path, scan_result);
|
|
|
|
if (!s.ok()) {
|
|
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
|
"Failed to load the latest compaction "
|
|
"progress from %s: %s. Will perform clean up "
|
|
"to start fresh compaction",
|
|
latest_progress_file.value().c_str(),
|
|
s.ToString().c_str());
|
|
return HandleInvalidOrNoCompactionProgress(compaction_progress_file_path,
|
|
scan_result);
|
|
}
|
|
|
|
ROCKS_LOG_DEBUG(
|
|
immutable_db_options_.info_log,
|
|
"Loaded compaction progress with %zu subcompaction(s) from %s",
|
|
compaction_progress_.size(), compaction_progress_file_path.c_str());
|
|
return s;
|
|
} else {
|
|
return HandleInvalidOrNoCompactionProgress(
|
|
std::nullopt /* compaction_progress_file_path */, scan_result);
|
|
}
|
|
}
|
|
|
|
uint64_t DBImplSecondary::CalculateResumedCompactionBytes(
|
|
const CompactionProgress& compaction_progress) const {
|
|
uint64_t total_resumed_bytes = 0;
|
|
|
|
for (const auto& subcompaction_progress : compaction_progress) {
|
|
for (const auto& file_meta :
|
|
subcompaction_progress.output_level_progress.GetOutputFiles()) {
|
|
total_resumed_bytes += file_meta.fd.file_size;
|
|
}
|
|
|
|
for (const auto& file_meta :
|
|
subcompaction_progress.proximal_output_level_progress
|
|
.GetOutputFiles()) {
|
|
total_resumed_bytes += file_meta.fd.file_size;
|
|
}
|
|
}
|
|
|
|
return total_resumed_bytes;
|
|
}
|
|
|
|
Status DBImplSecondary::HandleInvalidOrNoCompactionProgress(
|
|
const std::optional<std::string>& compaction_progress_file_path,
|
|
const CompactionProgressFilesScan& scan_result) {
|
|
compaction_progress_.clear();
|
|
|
|
Status s;
|
|
if (compaction_progress_file_path.has_value()) {
|
|
WriteOptions write_options(Env::IOActivity::kCompaction);
|
|
IOOptions opts;
|
|
s = WritableFileWriter::PrepareIOOptions(write_options, opts);
|
|
if (s.ok()) {
|
|
s = fs_->DeleteFile(compaction_progress_file_path.value(), opts,
|
|
nullptr /* dbg */);
|
|
}
|
|
if (!s.ok()) {
|
|
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
|
|
"Failed to remove invalid progress file: %s",
|
|
s.ToString().c_str());
|
|
return s;
|
|
}
|
|
}
|
|
|
|
s = CleanupPhysicalCompactionOutputFiles(false /* preserve_tracked_files */,
|
|
scan_result);
|
|
if (!s.ok()) {
|
|
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
|
|
"Failed to cleanup existing compaction output files: %s",
|
|
s.ToString().c_str());
|
|
return s;
|
|
}
|
|
|
|
return Status::OK();
|
|
}
|
|
|
|
Status DBImplSecondary::CompactWithoutInstallation(
|
|
const OpenAndCompactOptions& options, ColumnFamilyHandle* cfh,
|
|
const CompactionServiceInput& input, CompactionServiceResult* result) {
|
|
if (options.canceled && options.canceled->load(std::memory_order_acquire)) {
|
|
return Status::Incomplete(Status::SubCode::kManualCompactionPaused);
|
|
}
|
|
|
|
std::unique_ptr<FSDirectory> output_dir;
|
|
std::unique_ptr<log::Writer> compaction_progress_writer;
|
|
|
|
InstrumentedMutexLock l(&mutex_);
|
|
|
|
auto cfd = static_cast_with_check<ColumnFamilyHandleImpl>(cfh)->cfd();
|
|
if (!cfd) {
|
|
return Status::InvalidArgument("Cannot find column family" +
|
|
cfh->GetName());
|
|
}
|
|
Status s;
|
|
|
|
const auto& mutable_cf_options = cfd->GetLatestMutableCFOptions();
|
|
|
|
// TODO(hx235): Resuming compaction is currently incompatible with
|
|
// output hash verification (enabled via paranoid_file_checks=true or
|
|
// verify_output_flags containing kVerifyIteration) because resumed compaction
|
|
// will lose the hash computed before interruption.
|
|
// Potential solutions:
|
|
// 1. Persist the hash state: Before interruption, save the current hash value
|
|
// of each output file to disk, allowing validation to continue correctly
|
|
// after resumption.
|
|
// 2. Immediate verification: Move output verification to happen
|
|
// immediately after each output file is created and closed, eliminating
|
|
// the need to maintain hash state across resumption boundaries.
|
|
bool output_hash_verification_enabled =
|
|
mutable_cf_options.paranoid_file_checks ||
|
|
!!(mutable_cf_options.verify_output_flags &
|
|
VerifyOutputFlags::kVerifyIteration);
|
|
|
|
bool allow_resumption =
|
|
options.allow_resumption && !output_hash_verification_enabled;
|
|
|
|
if (options.allow_resumption && output_hash_verification_enabled) {
|
|
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
|
"Resume compaction configured but disabled due to "
|
|
"incompatibility with output hash verification "
|
|
"(paranoid_file_checks=true or verify_output_flags "
|
|
"containing kVerifyIteration)");
|
|
}
|
|
|
|
mutex_.Unlock();
|
|
|
|
s = InitializeCompactionWorkspace(allow_resumption, &output_dir,
|
|
&compaction_progress_writer);
|
|
|
|
mutex_.Lock();
|
|
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
|
|
std::unordered_set<uint64_t> input_set;
|
|
for (const auto& file_name : input.input_files) {
|
|
input_set.insert(TableFileNameToNumber(file_name));
|
|
}
|
|
|
|
auto* version = cfd->current();
|
|
|
|
ColumnFamilyMetaData cf_meta;
|
|
version->GetColumnFamilyMetaData(&cf_meta);
|
|
|
|
VersionStorageInfo* vstorage = version->storage_info();
|
|
|
|
CompactionOptions comp_options;
|
|
comp_options.compression = kDisableCompressionOption;
|
|
comp_options.output_file_size_limit = MaxFileSizeForLevel(
|
|
mutable_cf_options, input.output_level, cfd->ioptions().compaction_style,
|
|
vstorage->base_level(),
|
|
cfd->ioptions().level_compaction_dynamic_level_bytes);
|
|
|
|
std::vector<CompactionInputFiles> input_files;
|
|
s = cfd->compaction_picker()->GetCompactionInputsFromFileNumbers(
|
|
&input_files, &input_set, vstorage, comp_options);
|
|
if (!s.ok()) {
|
|
ROCKS_LOG_ERROR(
|
|
immutable_db_options_.info_log,
|
|
"GetCompactionInputsFromFileNumbers() failed - %s.\n DebugString: %s",
|
|
s.ToString().c_str(), version->DebugString(/*hex=*/true).c_str());
|
|
return s;
|
|
}
|
|
|
|
const int job_id = next_job_id_.fetch_add(1);
|
|
JobContext job_context(job_id, true /*create_superversion*/);
|
|
std::vector<SequenceNumber> snapshots = input.snapshots;
|
|
|
|
// TODO - snapshot_checker support in Remote Compaction
|
|
job_context.InitSnapshotContext(/*checker=*/nullptr,
|
|
/*managed_snapshot=*/nullptr,
|
|
kMaxSequenceNumber, std::move(snapshots));
|
|
|
|
// TODO - consider serializing the entire Compaction object and using it as
|
|
// input instead of recreating it in the remote worker
|
|
std::unique_ptr<Compaction> c;
|
|
assert(cfd->compaction_picker());
|
|
std::optional<SequenceNumber> earliest_snapshot = std::nullopt;
|
|
// Standalone Range Deletion Optimization is only supported in Universal
|
|
// Compactions - https://github.com/facebook/rocksdb/pull/13078
|
|
if (cfd->GetLatestCFOptions().compaction_style ==
|
|
CompactionStyle::kCompactionStyleUniversal) {
|
|
earliest_snapshot = !job_context.snapshot_seqs.empty()
|
|
? job_context.snapshot_seqs.front()
|
|
: kMaxSequenceNumber;
|
|
}
|
|
c.reset(cfd->compaction_picker()->PickCompactionForCompactFiles(
|
|
comp_options, input_files, input.output_level, vstorage,
|
|
mutable_cf_options, mutable_db_options_, 0, earliest_snapshot,
|
|
job_context.snapshot_checker));
|
|
assert(c != nullptr);
|
|
c->FinalizeInputInfo(version);
|
|
|
|
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL,
|
|
immutable_db_options_.info_log.get());
|
|
|
|
// use primary host's db_id for running the compaction, but db_session_id is
|
|
// using the local one, which is to make sure the unique id is unique from
|
|
// the remote compactors. Because the id is generated from db_id,
|
|
// db_session_id and orig_file_number, unlike the local compaction, remote
|
|
// compaction cannot guarantee the uniqueness of orig_file_number, the file
|
|
// number is only assigned when compaction is done.
|
|
CompactionServiceCompactionJob compaction_job(
|
|
job_id, c.get(), immutable_db_options_, mutable_db_options_,
|
|
file_options_for_compaction_, versions_.get(), &shutting_down_,
|
|
&log_buffer, output_dir.get(), stats_, &mutex_, &error_handler_,
|
|
&job_context, table_cache_, &event_logger_, dbname_, io_tracer_,
|
|
options.canceled ? *options.canceled : kManualCompactionCanceledFalse_,
|
|
input.db_id, db_session_id_, secondary_path_, input, result);
|
|
|
|
compaction_job.Prepare(compaction_progress_,
|
|
compaction_progress_writer.get());
|
|
|
|
mutex_.Unlock();
|
|
s = compaction_job.Run();
|
|
mutex_.Lock();
|
|
|
|
// These cleanup functions handle metadata and state cleanup only and
|
|
// not the physical files
|
|
compaction_job.io_status().PermitUncheckedError();
|
|
compaction_job.CleanupCompaction();
|
|
c->ReleaseCompactionFiles(s);
|
|
c.reset();
|
|
|
|
TEST_SYNC_POINT_CALLBACK("DBImplSecondary::CompactWithoutInstallation::End",
|
|
&s);
|
|
|
|
if (!compaction_progress_.empty() && s.ok()) {
|
|
uint64_t total_resumed_bytes =
|
|
CalculateResumedCompactionBytes(compaction_progress_);
|
|
|
|
if (total_resumed_bytes > 0 &&
|
|
immutable_db_options_.statistics != nullptr) {
|
|
RecordTick(immutable_db_options_.statistics.get(),
|
|
REMOTE_COMPACT_RESUMED_BYTES, total_resumed_bytes);
|
|
}
|
|
}
|
|
|
|
result->status = s;
|
|
return s;
|
|
}
|
|
|
|
Status DB::OpenAndCompact(
|
|
const OpenAndCompactOptions& options, const std::string& name,
|
|
const std::string& output_directory, const std::string& input,
|
|
std::string* output,
|
|
const CompactionServiceOptionsOverride& override_options) {
|
|
// Check for cancellation
|
|
if (options.canceled && options.canceled->load(std::memory_order_acquire)) {
|
|
return Status::Incomplete(Status::SubCode::kManualCompactionPaused);
|
|
}
|
|
|
|
// 1. Deserialize Compaction Input
|
|
CompactionServiceInput compaction_input;
|
|
Status s = CompactionServiceInput::Read(input, &compaction_input);
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
|
|
// 2. Load the options
|
|
DBOptions base_db_options;
|
|
ConfigOptions config_options;
|
|
config_options.env = override_options.env;
|
|
config_options.ignore_unknown_options = true;
|
|
std::vector<ColumnFamilyDescriptor> all_column_families;
|
|
|
|
TEST_SYNC_POINT_CALLBACK(
|
|
"DBImplSecondary::OpenAndCompact::BeforeLoadingOptions:0",
|
|
&compaction_input.options_file_number);
|
|
TEST_SYNC_POINT("DBImplSecondary::OpenAndCompact::BeforeLoadingOptions:1");
|
|
std::string options_file_name =
|
|
OptionsFileName(name, compaction_input.options_file_number);
|
|
|
|
s = LoadOptionsFromFile(config_options, options_file_name, &base_db_options,
|
|
&all_column_families);
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
|
|
// 3. Options to Override
|
|
// Override serializable configurations from override_options.options_map
|
|
DBOptions db_options;
|
|
s = GetDBOptionsFromMap(config_options, base_db_options,
|
|
override_options.options_map, &db_options);
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
|
|
// Override options that are directly set as shared ptrs in
|
|
// CompactionServiceOptionsOverride
|
|
db_options.env = override_options.env;
|
|
db_options.file_checksum_gen_factory =
|
|
override_options.file_checksum_gen_factory;
|
|
db_options.statistics = override_options.statistics;
|
|
db_options.listeners = override_options.listeners;
|
|
db_options.compaction_service = nullptr;
|
|
// We will close the DB after the compaction anyway.
|
|
// Open as many files as needed for the compaction.
|
|
db_options.max_open_files = -1;
|
|
db_options.info_log = override_options.info_log;
|
|
|
|
// 4. Filter CFs that are needed for OpenAndCompact()
|
|
// We do not need to open all column families for the remote compaction.
|
|
// Only open default CF + target CF. If target CF == default CF, we will open
|
|
// just the default CF (Due to current limitation, DB cannot open without the
|
|
// default CF)
|
|
std::vector<ColumnFamilyDescriptor> column_families;
|
|
for (auto& cf : all_column_families) {
|
|
if (cf.name == compaction_input.cf_name) {
|
|
ColumnFamilyOptions cf_options;
|
|
// Override serializable configurations from override_options.options_map
|
|
s = GetColumnFamilyOptionsFromMap(config_options, cf.options,
|
|
override_options.options_map,
|
|
&cf_options);
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
cf.options = std::move(cf_options);
|
|
|
|
// Override options that are directly set as shared ptrs in
|
|
// CompactionServiceOptionsOverride
|
|
cf.options.comparator = override_options.comparator;
|
|
cf.options.merge_operator = override_options.merge_operator;
|
|
cf.options.compaction_filter = override_options.compaction_filter;
|
|
cf.options.compaction_filter_factory =
|
|
override_options.compaction_filter_factory;
|
|
cf.options.prefix_extractor = override_options.prefix_extractor;
|
|
cf.options.table_factory = override_options.table_factory;
|
|
cf.options.sst_partitioner_factory =
|
|
override_options.sst_partitioner_factory;
|
|
cf.options.table_properties_collector_factories =
|
|
override_options.table_properties_collector_factories;
|
|
|
|
column_families.emplace_back(cf);
|
|
} else if (cf.name == kDefaultColumnFamilyName) {
|
|
column_families.emplace_back(cf);
|
|
}
|
|
}
|
|
|
|
// 5. Open db As Secondary (skip WAL recovery -- remote compaction only
|
|
// needs LSM state from MANIFEST, not memtable data from WAL replay)
|
|
std::unique_ptr<DB> db;
|
|
std::vector<ColumnFamilyHandle*> handles;
|
|
s = DBImplSecondary::OpenAsSecondaryImpl(db_options, name, output_directory,
|
|
column_families, &handles, &db,
|
|
/*recover_wal=*/false);
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
assert(db);
|
|
|
|
TEST_SYNC_POINT_CALLBACK(
|
|
"DBImplSecondary::OpenAndCompact::AfterOpenAsSecondary:0", db.get());
|
|
|
|
// 6. Find the handle of the Column Family that this will compact
|
|
ColumnFamilyHandle* cfh = nullptr;
|
|
for (auto* handle : handles) {
|
|
if (compaction_input.cf_name == handle->GetName()) {
|
|
cfh = handle;
|
|
break;
|
|
}
|
|
}
|
|
assert(cfh);
|
|
|
|
// 7. Run the compaction without installation.
|
|
// Output will be stored in the directory specified by output_directory
|
|
CompactionServiceResult compaction_result;
|
|
DBImplSecondary* db_secondary =
|
|
static_cast_with_check<DBImplSecondary>(db.get());
|
|
s = db_secondary->CompactWithoutInstallation(options, cfh, compaction_input,
|
|
&compaction_result);
|
|
|
|
// 8. Serialize the result
|
|
Status serialization_status = compaction_result.Write(output);
|
|
|
|
// 9. Close the db and return
|
|
for (auto& handle : handles) {
|
|
delete handle;
|
|
}
|
|
db.reset();
|
|
if (s.ok()) {
|
|
return serialization_status;
|
|
} else {
|
|
serialization_status.PermitUncheckedError();
|
|
}
|
|
return s;
|
|
}
|
|
|
|
Status DB::OpenAndCompact(
|
|
const std::string& name, const std::string& output_directory,
|
|
const std::string& input, std::string* output,
|
|
const CompactionServiceOptionsOverride& override_options) {
|
|
return OpenAndCompact(OpenAndCompactOptions(), name, output_directory, input,
|
|
output, override_options);
|
|
}
|
|
|
|
Status DBImplSecondary::CreateCompactionProgressWriter(
|
|
const std::string& file_path,
|
|
std::unique_ptr<log::Writer>* compaction_progress_writer) {
|
|
std::unique_ptr<FSWritableFile> file;
|
|
Status s =
|
|
fs_->NewWritableFile(file_path, FileOptions(), &file, nullptr /* dbg */);
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
|
|
std::unique_ptr<WritableFileWriter> file_writer(
|
|
new WritableFileWriter(std::move(file), file_path, FileOptions()));
|
|
|
|
compaction_progress_writer->reset(
|
|
new log::Writer(std::move(file_writer), 0 /* log_number */,
|
|
false /* recycle_log_files */));
|
|
|
|
return Status::OK();
|
|
}
|
|
|
|
Status DBImplSecondary::PersistInitialCompactionProgress(
|
|
log::Writer* compaction_progress_writer,
|
|
const CompactionProgress& compaction_progress) {
|
|
assert(compaction_progress_writer);
|
|
|
|
// LIMITATION: Only supports resuming single subcompaction
|
|
assert(compaction_progress.size() == 1);
|
|
const SubcompactionProgress& subcompaction_progress = compaction_progress[0];
|
|
|
|
VersionEdit edit;
|
|
edit.SetSubcompactionProgress(subcompaction_progress);
|
|
|
|
std::string record;
|
|
if (!edit.EncodeTo(&record)) {
|
|
return Status::IOError("Failed to encode the initial compaction progress");
|
|
}
|
|
|
|
WriteOptions write_options(Env::IOActivity::kCompaction);
|
|
Status s = compaction_progress_writer->AddRecord(write_options, record);
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
IOOptions opts;
|
|
s = WritableFileWriter::PrepareIOOptions(write_options, opts);
|
|
if (!s.ok()) {
|
|
return s;
|
|
}
|
|
|
|
s = compaction_progress_writer->file()->Sync(opts,
|
|
immutable_db_options_.use_fsync);
|
|
|
|
return s;
|
|
}
|
|
|
|
Status DBImplSecondary::HandleCompactionProgressWriterCreationFailure(
|
|
const std::string& temp_file_path, const std::string& final_file_path,
|
|
std::unique_ptr<log::Writer>* compaction_progress_writer) {
|
|
compaction_progress_writer->reset();
|
|
|
|
const std::vector<std::string> paths_to_delete = {final_file_path,
|
|
temp_file_path};
|
|
|
|
Status s;
|
|
for (const auto& file_path : paths_to_delete) {
|
|
WriteOptions write_options(Env::IOActivity::kCompaction);
|
|
IOOptions opts;
|
|
s = WritableFileWriter::PrepareIOOptions(write_options, opts);
|
|
if (s.ok()) {
|
|
s = fs_->DeleteFile(file_path, opts, nullptr /* dbg */);
|
|
}
|
|
|
|
if (!s.ok()) {
|
|
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
|
|
"Failed to cleanup the compaction progress file "
|
|
"during writer creation failure: %s",
|
|
s.ToString().c_str());
|
|
return s;
|
|
}
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
Status DBImplSecondary::FinalizeCompactionProgressWriter(
|
|
std::unique_ptr<log::Writer>* compaction_progress_writer) {
|
|
uint64_t timestamp = env_->NowMicros();
|
|
const std::string temp_file_path =
|
|
TempCompactionProgressFileName(secondary_path_, timestamp);
|
|
|
|
Status s = CreateCompactionProgressWriter(temp_file_path,
|
|
compaction_progress_writer);
|
|
if (!s.ok()) {
|
|
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
|
"Failed to create compaction progress writer at "
|
|
"temp path %s: %s. Will perform clean up "
|
|
"to start compaction without progress persistence",
|
|
temp_file_path.c_str(), s.ToString().c_str());
|
|
return HandleCompactionProgressWriterCreationFailure(
|
|
temp_file_path, "" /* final_file_path */, compaction_progress_writer);
|
|
}
|
|
|
|
if (!compaction_progress_.empty()) {
|
|
s = PersistInitialCompactionProgress(compaction_progress_writer->get(),
|
|
compaction_progress_);
|
|
if (!s.ok()) {
|
|
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
|
"Failed to persist the initial copmaction "
|
|
"progress: %s. Will perform clean up "
|
|
"to start compaction without progress persistence",
|
|
s.ToString().c_str());
|
|
return HandleCompactionProgressWriterCreationFailure(
|
|
temp_file_path, "" /* final_file_path */, compaction_progress_writer);
|
|
}
|
|
}
|
|
|
|
compaction_progress_writer->reset();
|
|
|
|
std::string final_file_path;
|
|
s = RenameCompactionProgressFile(temp_file_path, &final_file_path);
|
|
|
|
if (!s.ok()) {
|
|
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
|
"Failed to rename temporary compaction progress "
|
|
"file from %s to %s: %s. Will perform clean up "
|
|
"to start compaction without progress persistence",
|
|
temp_file_path.c_str(), final_file_path.c_str(),
|
|
s.ToString().c_str());
|
|
return HandleCompactionProgressWriterCreationFailure(
|
|
temp_file_path, final_file_path, compaction_progress_writer);
|
|
}
|
|
|
|
s = CreateCompactionProgressWriter(final_file_path,
|
|
compaction_progress_writer);
|
|
if (!s.ok()) {
|
|
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
|
"Failed to create the final compaction progress "
|
|
"writer: %s. Will attempt clean to start the compaction "
|
|
"without progress persistence",
|
|
s.ToString().c_str());
|
|
return HandleCompactionProgressWriterCreationFailure(
|
|
"" /* temp_file_path */, final_file_path, compaction_progress_writer);
|
|
}
|
|
|
|
ROCKS_LOG_DEBUG(immutable_db_options_.info_log,
|
|
"Finalized compaction progress writer onto %s",
|
|
final_file_path.c_str());
|
|
|
|
return Status::OK();
|
|
}
|
|
} // namespace ROCKSDB_NAMESPACE
|