2022-03-18 23:35:51 +00:00
|
|
|
// Copyright (c) Facebook, Inc. and its affiliates. 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).
|
|
|
|
|
|
|
|
#pragma once
|
|
|
|
|
|
|
|
#include "db/version_set.h"
|
|
|
|
|
|
|
|
namespace ROCKSDB_NAMESPACE {
|
|
|
|
|
|
|
|
// Instead of opening a `DB` to perform certain manifest updates, this
|
|
|
|
// uses the underlying `VersionSet` API to read and modify the MANIFEST. This
|
|
|
|
// allows us to use the user's real options, while not having to worry about
|
|
|
|
// the DB persisting new SST files via flush/compaction or attempting to read/
|
|
|
|
// compact files which may fail, particularly for the file we intend to remove
|
|
|
|
// (the user may want to remove an already deleted file from MANIFEST).
|
|
|
|
class OfflineManifestWriter {
|
|
|
|
public:
|
|
|
|
OfflineManifestWriter(const DBOptions& options, const std::string& db_path)
|
|
|
|
: wc_(options.delayed_write_rate),
|
|
|
|
wb_(options.db_write_buffer_size),
|
|
|
|
immutable_db_options_(WithDbPath(options, db_path)),
|
|
|
|
tc_(NewLRUCache(1 << 20 /* capacity */,
|
|
|
|
options.table_cache_numshardbits)),
|
|
|
|
versions_(db_path, &immutable_db_options_, sopt_, tc_.get(), &wb_, &wc_,
|
|
|
|
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
2022-06-21 03:58:11 +00:00
|
|
|
/*db_id*/ "", /*db_session_id*/ "") {}
|
2022-03-18 23:35:51 +00:00
|
|
|
|
|
|
|
Status Recover(const std::vector<ColumnFamilyDescriptor>& column_families) {
|
2022-10-11 00:59:17 +00:00
|
|
|
return versions_.Recover(column_families, /*read_only*/ false,
|
|
|
|
/*db_id*/ nullptr,
|
|
|
|
/*no_error_if_files_missing*/ true);
|
2022-03-18 23:35:51 +00:00
|
|
|
}
|
|
|
|
|
2023-04-21 16:07:18 +00:00
|
|
|
Status LogAndApply(const ReadOptions& read_options, ColumnFamilyData* cfd,
|
|
|
|
VersionEdit* edit,
|
Sync dir containing CURRENT after RenameFile on CURRENT as much as possible (#10573)
Summary:
**Context:**
Below crash test revealed a bug that directory containing CURRENT file (short for `dir_contains_current_file` below) was not always get synced after a new CURRENT is created and being called with `RenameFile` as part of the creation.
This bug exposes a risk that such un-synced directory containing the updated CURRENT can’t survive a host crash (e.g, power loss) hence get corrupted. This then will be followed by a recovery from a corrupted CURRENT that we don't want.
The root-cause is that a nullptr `FSDirectory* dir_contains_current_file` sometimes gets passed-down to `SetCurrentFile()` hence in those case `dir_contains_current_file->FSDirectory::FsyncWithDirOptions()` will be skipped (which otherwise will internally call`Env/FS::SyncDic()` )
```
./db_stress --acquire_snapshot_one_in=10000 --adaptive_readahead=1 --allow_data_in_errors=True --avoid_unnecessary_blocking_io=0 --backup_max_size=104857600 --backup_one_in=100000 --batch_protection_bytes_per_key=8 --block_size=16384 --bloom_bits=134.8015470676662 --bottommost_compression_type=disable --cache_size=8388608 --checkpoint_one_in=1000000 --checksum_type=kCRC32c --clear_column_family_one_in=0 --compact_files_one_in=1000000 --compact_range_one_in=1000000 --compaction_pri=2 --compaction_ttl=100 --compression_max_dict_buffer_bytes=511 --compression_max_dict_bytes=16384 --compression_type=zstd --compression_use_zstd_dict_trainer=1 --compression_zstd_max_train_bytes=65536 --continuous_verification_interval=0 --data_block_index_type=0 --db=$db --db_write_buffer_size=1048576 --delpercent=5 --delrangepercent=0 --destroy_db_initially=0 --disable_wal=0 --enable_compaction_filter=0 --enable_pipelined_write=1 --expected_values_dir=$exp --fail_if_options_file_error=1 --file_checksum_impl=none --flush_one_in=1000000 --get_current_wal_file_one_in=0 --get_live_files_one_in=1000000 --get_property_one_in=1000000 --get_sorted_wal_files_one_in=0 --index_block_restart_interval=4 --ingest_external_file_one_in=0 --iterpercent=10 --key_len_percent_dist=1,30,69 --level_compaction_dynamic_level_bytes=True --mark_for_compaction_one_file_in=10 --max_background_compactions=20 --max_bytes_for_level_base=10485760 --max_key=10000 --max_key_len=3 --max_manifest_file_size=16384 --max_write_batch_group_size_bytes=64 --max_write_buffer_number=3 --max_write_buffer_size_to_maintain=0 --memtable_prefix_bloom_size_ratio=0.001 --memtable_protection_bytes_per_key=1 --memtable_whole_key_filtering=1 --mmap_read=1 --nooverwritepercent=1 --open_metadata_write_fault_one_in=0 --open_read_fault_one_in=0 --open_write_fault_one_in=0 --ops_per_thread=100000000 --optimize_filters_for_memory=1 --paranoid_file_checks=1 --partition_pinning=2 --pause_background_one_in=1000000 --periodic_compaction_seconds=0 --prefix_size=5 --prefixpercent=5 --prepopulate_block_cache=1 --progress_reports=0 --read_fault_one_in=1000 --readpercent=45 --recycle_log_file_num=0 --reopen=0 --ribbon_starting_level=999 --secondary_cache_fault_one_in=32 --secondary_cache_uri=compressed_secondary_cache://capacity=8388608 --set_options_one_in=10000 --snapshot_hold_ops=100000 --sst_file_manager_bytes_per_sec=0 --sst_file_manager_bytes_per_truncate=0 --subcompactions=3 --sync_fault_injection=1 --target_file_size_base=2097 --target_file_size_multiplier=2 --test_batches_snapshots=1 --top_level_index_pinning=1 --use_full_merge_v1=1 --use_merge=1 --value_size_mult=32 --verify_checksum=1 --verify_checksum_one_in=1000000 --verify_db_one_in=100000 --verify_sst_unique_id_in_manifest=1 --wal_bytes_per_sync=524288 --write_buffer_size=4194 --writepercent=35
```
```
stderr:
WARNING: prefix_size is non-zero but memtablerep != prefix_hash
db_stress: utilities/fault_injection_fs.cc:748: virtual rocksdb::IOStatus rocksdb::FaultInjectionTestFS::RenameFile(const std::string &, const std::string &, const rocksdb::IOOptions &, rocksdb::IODebugContext *): Assertion `tlist.find(tdn.second) == tlist.end()' failed.`
```
**Summary:**
The PR ensured the non-test path pass down a non-null dir containing CURRENT (which is by current RocksDB assumption just db_dir) by doing the following:
- Renamed `directory_to_fsync` as `dir_contains_current_file` in `SetCurrentFile()` to tighten the association between this directory and CURRENT file
- Changed `SetCurrentFile()` API to require `dir_contains_current_file` being passed-in, instead of making it by default nullptr.
- Because `SetCurrentFile()`'s `dir_contains_current_file` is passed down from `VersionSet::LogAndApply()` then `VersionSet::ProcessManifestWrites()` (i.e, think about this as a chain of 3 functions related to MANIFEST update), these 2 functions also got refactored to require `dir_contains_current_file`
- Updated the non-test-path callers of these 3 functions to obtain and pass in non-nullptr `dir_contains_current_file`, which by current assumption of RocksDB, is the `FSDirectory* db_dir`.
- `db_impl` path will obtain `DBImpl::directories_.getDbDir()` while others with no access to such `directories_` are obtained on the fly by creating such object `FileSystem::NewDirectory(..)` and manage it by unique pointers to ensure short life time.
Pull Request resolved: https://github.com/facebook/rocksdb/pull/10573
Test Plan:
- `make check`
- Passed the repro db_stress command
- For future improvement, since we currently don't assert dir containing CURRENT to be non-nullptr due to https://github.com/facebook/rocksdb/pull/10573#pullrequestreview-1087698899, there is still chances that future developers mistakenly pass down nullptr dir containing CURRENT thus resulting skipped sync dir and cause the bug again. Therefore a smarter test (e.g, such as quoted from ajkr "(make) unsynced data loss to be dropping files corresponding to unsynced directory entries") is still needed.
Reviewed By: ajkr
Differential Revision: D39005886
Pulled By: hx235
fbshipit-source-id: 336fb9090d0cfa6ca3dd580db86268007dde7f5a
2022-08-30 00:35:21 +00:00
|
|
|
FSDirectory* dir_contains_current_file) {
|
2022-03-18 23:35:51 +00:00
|
|
|
// Use `mutex` to imitate a locked DB mutex when calling `LogAndApply()`.
|
|
|
|
InstrumentedMutex mutex;
|
|
|
|
mutex.Lock();
|
2023-04-21 16:07:18 +00:00
|
|
|
Status s = versions_.LogAndApply(
|
|
|
|
cfd, *cfd->GetLatestMutableCFOptions(), read_options, edit, &mutex,
|
|
|
|
dir_contains_current_file, false /* new_descriptor_log */);
|
2022-03-18 23:35:51 +00:00
|
|
|
mutex.Unlock();
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
VersionSet& Versions() { return versions_; }
|
|
|
|
const ImmutableDBOptions& IOptions() { return immutable_db_options_; }
|
|
|
|
|
|
|
|
private:
|
|
|
|
WriteController wc_;
|
|
|
|
WriteBufferManager wb_;
|
|
|
|
ImmutableDBOptions immutable_db_options_;
|
|
|
|
std::shared_ptr<Cache> tc_;
|
|
|
|
EnvOptions sopt_;
|
|
|
|
VersionSet versions_;
|
|
|
|
|
|
|
|
static ImmutableDBOptions WithDbPath(const DBOptions& options,
|
|
|
|
const std::string& db_path) {
|
|
|
|
ImmutableDBOptions rv(options);
|
|
|
|
if (rv.db_paths.empty()) {
|
|
|
|
// `VersionSet` expects options that have been through
|
|
|
|
// `SanitizeOptions()`, which would sanitize an empty `db_paths`.
|
|
|
|
rv.db_paths.emplace_back(db_path, 0 /* target_size */);
|
|
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
} // namespace ROCKSDB_NAMESPACE
|