mirror of
https://github.com/facebook/rocksdb.git
synced 2026-07-07 14:47:40 +08:00
Compare commits
14 Commits
f2c0eb41ef
...
v9.6.2
| Author | SHA1 | Date | |
|---|---|---|---|
| ccfb1998b1 | |||
| ab03f7fb53 | |||
| fb6fa230c7 | |||
| 8d913b5b75 | |||
| 13d5230e5d | |||
| ca3418ca3d | |||
| daa63be3d6 | |||
| aaa570b21f | |||
| f172876964 | |||
| a2f796ff8f | |||
| 83a2e71b9a | |||
| 96315e9aa9 | |||
| becf8684e3 | |||
| 67bfca5117 |
+30
@@ -1,6 +1,36 @@
|
||||
# Rocksdb Change Log
|
||||
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
|
||||
|
||||
## 9.6.2 (10/31/2024)
|
||||
### Bug Fixes
|
||||
* Fix a leak of obsolete blob files left open until DB::Close(). This bug was introduced in version 9.4.0.
|
||||
|
||||
## 9.6.1 (08/24/2024)
|
||||
### Bug Fixes
|
||||
* Fix correctness of MultiGet across column families with user timestamp.
|
||||
|
||||
## 9.6.0 (08/19/2024)
|
||||
### New Features
|
||||
* *Best efforts recovery supports recovering to incomplete Version with a clean seqno cut that presents a valid point in time view from the user's perspective, if versioning history doesn't include atomic flush.
|
||||
* New option `BlockBasedTableOptions::decouple_partitioned_filters` should improve efficiency in serving read queries because filter and index partitions can consistently target the configured `metadata_block_size`. This option is currently opt-in.
|
||||
* Introduce a new mutable CF option `paranoid_memory_checks`. It enables additional validation on data integrity during reads/scanning. Currently, skip list based memtable will validate key ordering during look up and scans.
|
||||
|
||||
### Public API Changes
|
||||
* Add ticker stats to count file read retries due to checksum mismatch
|
||||
* Adds optional installation callback function for remote compaction
|
||||
|
||||
### Behavior Changes
|
||||
* There may be less intra-L0 compaction triggered by total L0 size being too small. We now use compensated file size (tombstones are assigned some value size) when calculating L0 size and reduce the threshold for L0 size limit. This is to avoid accumulating too much data/tombstones in L0.
|
||||
|
||||
### Bug Fixes
|
||||
* *Make DestroyDB supports slow deletion when it's configured in `SstFileManager`. The slow deletion is subject to the configured `rate_bytes_per_sec`, but not subject to the `max_trash_db_ratio`.
|
||||
* Fixed a bug where we set unprep_seqs_ even when WriteImpl() fails. This was caught by stress test write fault injection in WriteImpl(). This may have incorrectly caused iteration creation failure for unvalidated writes or returned wrong result for WriteUnpreparedTxn::GetUnpreparedSequenceNumbers().
|
||||
* Fixed a bug where successful write right after error recovery for last failed write finishes causes duplicate WAL entries
|
||||
* Fixed a data race involving the background error status in `unordered_write` mode.
|
||||
* *Fix a bug where file snapshot functions like backup, checkpoint may attempt to copy a non-existing manifest file. #12882
|
||||
* Fix a bug where per kv checksum corruption may be ignored in MultiGet().
|
||||
* Fix a race condition in pessimistic transactions that could allow multiple transactions with the same name to be registered simultaneously, resulting in a crash or other unpredictable behavior.
|
||||
|
||||
## 9.5.0 (07/19/2024)
|
||||
### Public API Changes
|
||||
* Introduced new C API function rocksdb_writebatch_iterate_cf for column family-aware iteration over the contents of a WriteBatch
|
||||
|
||||
@@ -42,6 +42,7 @@ Status BlobFileCache::GetBlobFileReader(
|
||||
assert(blob_file_reader);
|
||||
assert(blob_file_reader->IsEmpty());
|
||||
|
||||
// NOTE: sharing same Cache with table_cache
|
||||
const Slice key = GetSliceForKey(&blob_file_number);
|
||||
|
||||
assert(cache_);
|
||||
@@ -98,4 +99,13 @@ Status BlobFileCache::GetBlobFileReader(
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void BlobFileCache::Evict(uint64_t blob_file_number) {
|
||||
// NOTE: sharing same Cache with table_cache
|
||||
const Slice key = GetSliceForKey(&blob_file_number);
|
||||
|
||||
assert(cache_);
|
||||
|
||||
cache_.get()->Erase(key);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -36,6 +36,15 @@ class BlobFileCache {
|
||||
uint64_t blob_file_number,
|
||||
CacheHandleGuard<BlobFileReader>* blob_file_reader);
|
||||
|
||||
// Called when a blob file is obsolete to ensure it is removed from the cache
|
||||
// to avoid effectively leaking the open file and assicated memory
|
||||
void Evict(uint64_t blob_file_number);
|
||||
|
||||
// Used to identify cache entries for blob files (not normally useful)
|
||||
static const Cache::CacheItemHelper* GetHelper() {
|
||||
return CacheInterface::GetBasicHelper();
|
||||
}
|
||||
|
||||
private:
|
||||
using CacheInterface =
|
||||
BasicTypedCacheInterface<BlobFileReader, CacheEntryRole::kMisc>;
|
||||
|
||||
@@ -401,6 +401,7 @@ class ColumnFamilyData {
|
||||
SequenceNumber earliest_seq);
|
||||
|
||||
TableCache* table_cache() const { return table_cache_.get(); }
|
||||
BlobFileCache* blob_file_cache() const { return blob_file_cache_.get(); }
|
||||
BlobSource* blob_source() const { return blob_source_.get(); }
|
||||
|
||||
// See documentation in compaction_picker.h
|
||||
|
||||
@@ -552,7 +552,8 @@ class CompactionJobTestBase : public testing::Test {
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*read_only=*/false));
|
||||
compaction_job_stats_.Reset();
|
||||
ASSERT_OK(SetIdentityFile(WriteOptions(), env_, dbname_));
|
||||
ASSERT_OK(
|
||||
SetIdentityFile(WriteOptions(), env_, dbname_, Temperature::kUnknown));
|
||||
|
||||
VersionEdit new_db;
|
||||
new_db.SetLogNumber(0);
|
||||
@@ -575,7 +576,8 @@ class CompactionJobTestBase : public testing::Test {
|
||||
}
|
||||
ASSERT_OK(s);
|
||||
// Make "CURRENT" file that points to the new manifest file.
|
||||
s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr);
|
||||
s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1,
|
||||
Temperature::kUnknown, nullptr);
|
||||
|
||||
ASSERT_OK(s);
|
||||
|
||||
|
||||
+11
-4
@@ -654,8 +654,9 @@ Status DBImpl::CloseHelper() {
|
||||
// We need to release them before the block cache is destroyed. The block
|
||||
// cache may be destroyed inside versions_.reset(), when column family data
|
||||
// list is destroyed, so leaving handles in table cache after
|
||||
// versions_.reset() may cause issues.
|
||||
// Here we clean all unreferenced handles in table cache.
|
||||
// versions_.reset() may cause issues. Here we clean all unreferenced handles
|
||||
// in table cache, and (for certain builds/conditions) assert that no obsolete
|
||||
// files are hanging around unreferenced (leak) in the table/blob file cache.
|
||||
// Now we assume all user queries have finished, so only version set itself
|
||||
// can possibly hold the blocks from block cache. After releasing unreferenced
|
||||
// handles here, only handles held by version set left and inside
|
||||
@@ -663,6 +664,9 @@ Status DBImpl::CloseHelper() {
|
||||
// time a handle is released, we erase it from the cache too. By doing that,
|
||||
// we can guarantee that after versions_.reset(), table cache is empty
|
||||
// so the cache can be safely destroyed.
|
||||
#ifndef NDEBUG
|
||||
TEST_VerifyNoObsoleteFilesCached(/*db_mutex_already_held=*/true);
|
||||
#endif // !NDEBUG
|
||||
table_cache_->EraseUnRefEntries();
|
||||
|
||||
for (auto& txn_entry : recovered_transactions_) {
|
||||
@@ -3143,10 +3147,11 @@ Status DBImpl::MultiGetImpl(
|
||||
StopWatch sw(immutable_db_options_.clock, stats_, DB_MULTIGET);
|
||||
|
||||
assert(sorted_keys);
|
||||
assert(start_key + num_keys <= sorted_keys->size());
|
||||
// Clear the timestamps for returning results so that we can distinguish
|
||||
// between tombstone or key that has never been written
|
||||
for (auto* kctx : *sorted_keys) {
|
||||
assert(kctx);
|
||||
for (size_t i = start_key; i < start_key + num_keys; ++i) {
|
||||
KeyContext* kctx = (*sorted_keys)[i];
|
||||
if (kctx->timestamp) {
|
||||
kctx->timestamp->clear();
|
||||
}
|
||||
@@ -3209,6 +3214,8 @@ Status DBImpl::MultiGetImpl(
|
||||
s = Status::Aborted();
|
||||
break;
|
||||
}
|
||||
// This could be a long-running operation
|
||||
ROCKSDB_THREAD_YIELD_HOOK();
|
||||
}
|
||||
|
||||
// Post processing (decrement reference counts and record statistics)
|
||||
|
||||
@@ -1237,9 +1237,14 @@ class DBImpl : public DB {
|
||||
static Status TEST_ValidateOptions(const DBOptions& db_options) {
|
||||
return ValidateOptions(db_options);
|
||||
}
|
||||
|
||||
#endif // NDEBUG
|
||||
|
||||
// In certain configurations, verify that the table/blob file cache only
|
||||
// contains entries for live files, to check for effective leaks of open
|
||||
// files. This can only be called when purging of obsolete files has
|
||||
// "settled," such as during parts of DB Close().
|
||||
void TEST_VerifyNoObsoleteFilesCached(bool db_mutex_already_held) const;
|
||||
|
||||
// persist stats to column family "_persistent_stats"
|
||||
void PersistStats();
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#ifndef NDEBUG
|
||||
|
||||
#include "db/blob/blob_file_cache.h"
|
||||
#include "db/column_family.h"
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/error_handler.h"
|
||||
@@ -323,5 +324,49 @@ size_t DBImpl::TEST_EstimateInMemoryStatsHistorySize() const {
|
||||
InstrumentedMutexLock l(&const_cast<DBImpl*>(this)->stats_history_mutex_);
|
||||
return EstimateInMemoryStatsHistorySize();
|
||||
}
|
||||
|
||||
void DBImpl::TEST_VerifyNoObsoleteFilesCached(
|
||||
bool db_mutex_already_held) const {
|
||||
// This check is somewhat expensive and obscure to make a part of every
|
||||
// unit test in every build variety. Thus, we only enable it for ASAN builds.
|
||||
if (!kMustFreeHeapAllocations) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::optional<InstrumentedMutexLock> l;
|
||||
if (db_mutex_already_held) {
|
||||
mutex_.AssertHeld();
|
||||
} else {
|
||||
l.emplace(&mutex_);
|
||||
}
|
||||
|
||||
std::vector<uint64_t> live_files;
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (cfd->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
// Sneakily add both SST and blob files to the same list
|
||||
cfd->current()->AddLiveFiles(&live_files, &live_files);
|
||||
}
|
||||
std::sort(live_files.begin(), live_files.end());
|
||||
|
||||
auto fn = [&live_files](const Slice& key, Cache::ObjectPtr, size_t,
|
||||
const Cache::CacheItemHelper* helper) {
|
||||
if (helper != BlobFileCache::GetHelper()) {
|
||||
// Skip non-blob files for now
|
||||
// FIXME: diagnose and fix the leaks of obsolete SST files revealed in
|
||||
// unit tests.
|
||||
return;
|
||||
}
|
||||
// See TableCache and BlobFileCache
|
||||
assert(key.size() == sizeof(uint64_t));
|
||||
uint64_t file_number;
|
||||
GetUnaligned(reinterpret_cast<const uint64_t*>(key.data()), &file_number);
|
||||
// Assert file is in sorted live_files
|
||||
assert(
|
||||
std::binary_search(live_files.begin(), live_files.end(), file_number));
|
||||
};
|
||||
table_cache_->ApplyToAllEntries(fn, {});
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
#endif // NDEBUG
|
||||
|
||||
@@ -970,7 +970,9 @@ Status DBImpl::SetupDBId(const WriteOptions& write_options, bool read_only,
|
||||
}
|
||||
// Persist it to IDENTITY file if allowed
|
||||
if (!read_only) {
|
||||
s = SetIdentityFile(write_options, env_, dbname_, db_id_);
|
||||
s = SetIdentityFile(write_options, env_, dbname_,
|
||||
immutable_db_options_.metadata_write_temperature,
|
||||
db_id_);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -295,7 +295,8 @@ Status DBImpl::ValidateOptions(const DBOptions& db_options) {
|
||||
Status DBImpl::NewDB(std::vector<std::string>* new_filenames) {
|
||||
VersionEdit new_db;
|
||||
const WriteOptions write_options(Env::IOActivity::kDBOpen);
|
||||
Status s = SetIdentityFile(write_options, env_, dbname_);
|
||||
Status s = SetIdentityFile(write_options, env_, dbname_,
|
||||
immutable_db_options_.metadata_write_temperature);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -319,6 +320,12 @@ Status DBImpl::NewDB(std::vector<std::string>* new_filenames) {
|
||||
}
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
FileOptions file_options = fs_->OptimizeForManifestWrite(file_options_);
|
||||
// DB option takes precedence when not kUnknown
|
||||
if (immutable_db_options_.metadata_write_temperature !=
|
||||
Temperature::kUnknown) {
|
||||
file_options.temperature =
|
||||
immutable_db_options_.metadata_write_temperature;
|
||||
}
|
||||
s = NewWritableFile(fs_.get(), manifest, &file, file_options);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -344,6 +351,7 @@ Status DBImpl::NewDB(std::vector<std::string>* new_filenames) {
|
||||
if (s.ok()) {
|
||||
// Make "CURRENT" file that points to the new manifest file.
|
||||
s = SetCurrentFile(write_options, fs_.get(), dbname_, 1,
|
||||
immutable_db_options_.metadata_write_temperature,
|
||||
directories_.GetDbDir());
|
||||
if (new_filenames) {
|
||||
new_filenames->emplace_back(
|
||||
@@ -1936,6 +1944,10 @@ IOStatus DBImpl::CreateWAL(const WriteOptions& write_options,
|
||||
BuildDBOptions(immutable_db_options_, mutable_db_options_);
|
||||
FileOptions opt_file_options =
|
||||
fs_->OptimizeForLogWrite(file_options_, db_options);
|
||||
// DB option takes precedence when not kUnknown
|
||||
if (immutable_db_options_.wal_write_temperature != Temperature::kUnknown) {
|
||||
opt_file_options.temperature = immutable_db_options_.wal_write_temperature;
|
||||
}
|
||||
std::string wal_dir = immutable_db_options_.GetWalDir();
|
||||
std::string log_fname = LogFileName(wal_dir, log_file_num);
|
||||
|
||||
|
||||
@@ -895,6 +895,81 @@ TEST_P(DBIOCorruptionTest, ManifestCorruptionRetry) {
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_P(DBIOCorruptionTest, FooterReadCorruptionRetry) {
|
||||
Random rnd(300);
|
||||
bool retry = false;
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"ReadFooterFromFileInternal:0", [&](void* arg) {
|
||||
Slice* data = static_cast<Slice*>(arg);
|
||||
if (!retry) {
|
||||
std::memcpy(const_cast<char*>(data->data()),
|
||||
rnd.RandomString(static_cast<int>(data->size())).c_str(),
|
||||
data->size());
|
||||
retry = true;
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(Put("key1", "val1"));
|
||||
Status s = Flush();
|
||||
if (std::get<2>(GetParam())) {
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_COUNT), 1);
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_SUCCESS_COUNT),
|
||||
1);
|
||||
|
||||
std::string val;
|
||||
ReadOptions ro;
|
||||
ro.async_io = std::get<1>(GetParam());
|
||||
ASSERT_OK(dbfull()->Get(ro, "key1", &val));
|
||||
ASSERT_EQ(val, "val1");
|
||||
} else {
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_COUNT), 0);
|
||||
ASSERT_GT(stats()->getTickerCount(SST_FOOTER_CORRUPTION_COUNT), 0);
|
||||
}
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_P(DBIOCorruptionTest, TablePropertiesCorruptionRetry) {
|
||||
Random rnd(300);
|
||||
bool retry = false;
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"ReadTablePropertiesHelper:0", [&](void* arg) {
|
||||
Slice* data = static_cast<Slice*>(arg);
|
||||
if (!retry) {
|
||||
std::memcpy(const_cast<char*>(data->data()),
|
||||
rnd.RandomString(static_cast<int>(data->size())).c_str(),
|
||||
data->size());
|
||||
retry = true;
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(Put("key1", "val1"));
|
||||
Status s = Flush();
|
||||
if (std::get<2>(GetParam())) {
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_COUNT), 1);
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_SUCCESS_COUNT),
|
||||
1);
|
||||
|
||||
std::string val;
|
||||
ReadOptions ro;
|
||||
ro.async_io = std::get<1>(GetParam());
|
||||
ASSERT_OK(dbfull()->Get(ro, "key1", &val));
|
||||
ASSERT_EQ(val, "val1");
|
||||
} else {
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_COUNT), 0);
|
||||
}
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
// The parameters are - 1. Use FS provided buffer, 2. Use async IO ReadOption,
|
||||
// 3. Retry with verify_and_reconstruct_read IOOption
|
||||
INSTANTIATE_TEST_CASE_P(DBIOCorruptionTest, DBIOCorruptionTest,
|
||||
|
||||
@@ -538,6 +538,8 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
|
||||
} else {
|
||||
iter_.Next();
|
||||
}
|
||||
// This could be a long-running operation due to tombstones, etc.
|
||||
ROCKSDB_THREAD_YIELD_HOOK();
|
||||
} while (iter_.Valid());
|
||||
|
||||
valid_ = false;
|
||||
|
||||
+231
@@ -10,6 +10,7 @@
|
||||
#include <atomic>
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
@@ -26,6 +27,7 @@
|
||||
#include "rocksdb/utilities/replayer.h"
|
||||
#include "rocksdb/wal_filter.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/defer.h"
|
||||
#include "util/random.h"
|
||||
#include "utilities/fault_injection_env.h"
|
||||
|
||||
@@ -6544,6 +6546,235 @@ TEST_P(RenameCurrentTest, Compaction) {
|
||||
ASSERT_EQ("d_value", Get("d"));
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, VariousFileTemperatures) {
|
||||
constexpr size_t kNumberFileTypes = static_cast<size_t>(kBlobFile) + 1U;
|
||||
|
||||
struct MyTestFS : public FileTemperatureTestFS {
|
||||
explicit MyTestFS(const std::shared_ptr<FileSystem>& fs)
|
||||
: FileTemperatureTestFS(fs) {
|
||||
Reset();
|
||||
}
|
||||
|
||||
IOStatus NewWritableFile(const std::string& fname, const FileOptions& opts,
|
||||
std::unique_ptr<FSWritableFile>* result,
|
||||
IODebugContext* dbg) override {
|
||||
IOStatus ios =
|
||||
FileTemperatureTestFS::NewWritableFile(fname, opts, result, dbg);
|
||||
if (ios.ok()) {
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
if (ParseFileName(GetFileName(fname), &number, "LOG", &type)) {
|
||||
if (type == kTableFile) {
|
||||
// Not checked here
|
||||
} else if (type == kWalFile) {
|
||||
if (opts.temperature != expected_wal_temperature) {
|
||||
std::cerr << "Attempt to open " << fname << " with temperature "
|
||||
<< temperature_to_string[opts.temperature]
|
||||
<< " rather than "
|
||||
<< temperature_to_string[expected_wal_temperature]
|
||||
<< std::endl;
|
||||
assert(false);
|
||||
}
|
||||
} else if (type == kDescriptorFile) {
|
||||
if (opts.temperature != expected_manifest_temperature) {
|
||||
std::cerr << "Attempt to open " << fname << " with temperature "
|
||||
<< temperature_to_string[opts.temperature]
|
||||
<< " rather than "
|
||||
<< temperature_to_string[expected_wal_temperature]
|
||||
<< std::endl;
|
||||
assert(false);
|
||||
}
|
||||
} else if (opts.temperature != expected_other_metadata_temperature) {
|
||||
std::cerr << "Attempt to open " << fname << " with temperature "
|
||||
<< temperature_to_string[opts.temperature]
|
||||
<< " rather than "
|
||||
<< temperature_to_string[expected_wal_temperature]
|
||||
<< std::endl;
|
||||
assert(false);
|
||||
}
|
||||
UpdateCount(type, 1);
|
||||
}
|
||||
}
|
||||
return ios;
|
||||
}
|
||||
|
||||
IOStatus RenameFile(const std::string& src, const std::string& dst,
|
||||
const IOOptions& options,
|
||||
IODebugContext* dbg) override {
|
||||
IOStatus ios = FileTemperatureTestFS::RenameFile(src, dst, options, dbg);
|
||||
if (ios.ok()) {
|
||||
uint64_t number;
|
||||
FileType src_type;
|
||||
FileType dst_type;
|
||||
assert(ParseFileName(GetFileName(src), &number, "LOG", &src_type));
|
||||
assert(ParseFileName(GetFileName(dst), &number, "LOG", &dst_type));
|
||||
|
||||
UpdateCount(src_type, -1);
|
||||
UpdateCount(dst_type, 1);
|
||||
}
|
||||
return ios;
|
||||
}
|
||||
|
||||
void UpdateCount(FileType type, int delta) {
|
||||
size_t i = static_cast<size_t>(type);
|
||||
assert(i < kNumberFileTypes);
|
||||
counts[i].FetchAddRelaxed(delta);
|
||||
}
|
||||
|
||||
std::map<FileType, size_t> PopCounts() {
|
||||
std::map<FileType, size_t> ret;
|
||||
for (size_t i = 0; i < kNumberFileTypes; ++i) {
|
||||
int c = counts[i].ExchangeRelaxed(0);
|
||||
if (c > 0) {
|
||||
ret[static_cast<FileType>(i)] = c;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
FileOptions OptimizeForLogWrite(
|
||||
const FileOptions& file_options,
|
||||
const DBOptions& /*db_options*/) const override {
|
||||
FileOptions opts = file_options;
|
||||
if (optimize_wal_temperature != Temperature::kUnknown) {
|
||||
opts.temperature = optimize_wal_temperature;
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
|
||||
FileOptions OptimizeForManifestWrite(
|
||||
const FileOptions& file_options) const override {
|
||||
FileOptions opts = file_options;
|
||||
if (optimize_manifest_temperature != Temperature::kUnknown) {
|
||||
opts.temperature = optimize_manifest_temperature;
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
optimize_manifest_temperature = Temperature::kUnknown;
|
||||
optimize_wal_temperature = Temperature::kUnknown;
|
||||
expected_manifest_temperature = Temperature::kUnknown;
|
||||
expected_other_metadata_temperature = Temperature::kUnknown;
|
||||
expected_wal_temperature = Temperature::kUnknown;
|
||||
for (auto& c : counts) {
|
||||
c.StoreRelaxed(0);
|
||||
}
|
||||
}
|
||||
|
||||
Temperature optimize_manifest_temperature;
|
||||
Temperature optimize_wal_temperature;
|
||||
Temperature expected_manifest_temperature;
|
||||
Temperature expected_other_metadata_temperature;
|
||||
Temperature expected_wal_temperature;
|
||||
std::array<RelaxedAtomic<int>, kNumberFileTypes> counts;
|
||||
};
|
||||
|
||||
// We don't have enough non-unknown temps to confidently distinguish that
|
||||
// a specific setting caused a specific outcome, in a single run. This is a
|
||||
// reasonable work-around without blowing up test time. Only returns
|
||||
// non-unknown temperatures.
|
||||
auto RandomTemp = [] {
|
||||
static std::vector<Temperature> temps = {
|
||||
Temperature::kHot, Temperature::kWarm, Temperature::kCold};
|
||||
return temps[Random::GetTLSInstance()->Uniform(
|
||||
static_cast<int>(temps.size()))];
|
||||
};
|
||||
|
||||
auto test_fs = std::make_shared<MyTestFS>(env_->GetFileSystem());
|
||||
std::unique_ptr<Env> env(new CompositeEnvWrapper(env_, test_fs));
|
||||
for (bool use_optimize : {false, true}) {
|
||||
std::cerr << "use_optimize: " << std::to_string(use_optimize) << std::endl;
|
||||
for (bool use_temp_options : {false, true}) {
|
||||
std::cerr << "use_temp_options: " << std::to_string(use_temp_options)
|
||||
<< std::endl;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
// Currently require for last level temperature
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.env = env.get();
|
||||
test_fs->Reset();
|
||||
if (use_optimize) {
|
||||
test_fs->optimize_manifest_temperature = RandomTemp();
|
||||
test_fs->expected_manifest_temperature =
|
||||
test_fs->optimize_manifest_temperature;
|
||||
test_fs->optimize_wal_temperature = RandomTemp();
|
||||
test_fs->expected_wal_temperature = test_fs->optimize_wal_temperature;
|
||||
}
|
||||
if (use_temp_options) {
|
||||
options.metadata_write_temperature = RandomTemp();
|
||||
test_fs->expected_manifest_temperature =
|
||||
options.metadata_write_temperature;
|
||||
test_fs->expected_other_metadata_temperature =
|
||||
options.metadata_write_temperature;
|
||||
options.wal_write_temperature = RandomTemp();
|
||||
test_fs->expected_wal_temperature = options.wal_write_temperature;
|
||||
options.last_level_temperature = RandomTemp();
|
||||
options.default_write_temperature = RandomTemp();
|
||||
}
|
||||
|
||||
DestroyAndReopen(options);
|
||||
Defer closer([&] { Close(); });
|
||||
|
||||
using FTC = std::map<FileType, size_t>;
|
||||
// Files on DB startup
|
||||
ASSERT_EQ(test_fs->PopCounts(), FTC({{kWalFile, 1},
|
||||
{kDescriptorFile, 2},
|
||||
{kCurrentFile, 2},
|
||||
{kIdentityFile, 1},
|
||||
{kOptionsFile, 1}}));
|
||||
|
||||
// Temperature count map
|
||||
using TCM = std::map<Temperature, size_t>;
|
||||
ASSERT_EQ(test_fs->CountCurrentSstFilesByTemp(), TCM({}));
|
||||
|
||||
ASSERT_OK(Put("foo", "1"));
|
||||
ASSERT_OK(Put("bar", "1"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("foo", "2"));
|
||||
ASSERT_OK(Put("bar", "2"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_EQ(test_fs->CountCurrentSstFilesByTemp(),
|
||||
TCM({{options.default_write_temperature, 2}}));
|
||||
|
||||
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForBackgroundWork());
|
||||
|
||||
ASSERT_EQ(test_fs->CountCurrentSstFilesByTemp(),
|
||||
TCM({{options.last_level_temperature, 1}}));
|
||||
|
||||
ASSERT_OK(Put("foo", "3"));
|
||||
ASSERT_OK(Put("bar", "3"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Just in memtable/WAL
|
||||
ASSERT_OK(Put("dog", "3"));
|
||||
|
||||
{
|
||||
TCM expected;
|
||||
expected[options.default_write_temperature] += 1;
|
||||
expected[options.last_level_temperature] += 1;
|
||||
ASSERT_EQ(test_fs->CountCurrentSstFilesByTemp(), expected);
|
||||
}
|
||||
|
||||
// New files during operation
|
||||
ASSERT_EQ(test_fs->PopCounts(), FTC({{kWalFile, 3}, {kTableFile, 4}}));
|
||||
|
||||
Reopen(options);
|
||||
|
||||
// New files during re-open/recovery
|
||||
ASSERT_EQ(test_fs->PopCounts(), FTC({{kWalFile, 1},
|
||||
{kTableFile, 1},
|
||||
{kDescriptorFile, 1},
|
||||
{kCurrentFile, 1},
|
||||
{kOptionsFile, 1}}));
|
||||
|
||||
Destroy(options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, LastLevelTemperature) {
|
||||
class TestListener : public EventListener {
|
||||
public:
|
||||
|
||||
+10
-1
@@ -831,6 +831,15 @@ class FileTemperatureTestFS : public FileSystemWrapper {
|
||||
return count;
|
||||
}
|
||||
|
||||
std::map<Temperature, size_t> CountCurrentSstFilesByTemp() {
|
||||
MutexLock lock(&mu_);
|
||||
std::map<Temperature, size_t> ret;
|
||||
for (const auto& e : current_sst_file_temperatures_) {
|
||||
ret[e.second]++;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void OverrideSstFileTemperature(uint64_t number, Temperature temp) {
|
||||
MutexLock lock(&mu_);
|
||||
current_sst_file_temperatures_[number] = temp;
|
||||
@@ -842,7 +851,7 @@ class FileTemperatureTestFS : public FileSystemWrapper {
|
||||
requested_sst_file_temperatures_;
|
||||
std::map<uint64_t, Temperature> current_sst_file_temperatures_;
|
||||
|
||||
std::string GetFileName(const std::string& fname) {
|
||||
static std::string GetFileName(const std::string& fname) {
|
||||
auto filename = fname.substr(fname.find_last_of(kFilePathSeparator) + 1);
|
||||
// workaround only for Windows that the file path could contain both Windows
|
||||
// FilePathSeparator and '/'
|
||||
|
||||
@@ -172,6 +172,70 @@ TEST_F(DBBasicTestWithTimestamp, MixedCfs) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, MultiGetMultipleCfs) {
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.avoid_flush_during_shutdown = true;
|
||||
options.comparator = &test_cmp;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Options options1 = CurrentOptions();
|
||||
options1.env = env_;
|
||||
options1.comparator = &test_cmp;
|
||||
ColumnFamilyHandle* handle = nullptr;
|
||||
Status s = db_->CreateColumnFamily(options1, "data", &handle);
|
||||
ASSERT_OK(s);
|
||||
|
||||
std::string ts = Timestamp(1, 0);
|
||||
WriteBatch wb(0, 0, 0, kTimestampSize);
|
||||
ASSERT_OK(wb.Put("a", "value"));
|
||||
ASSERT_OK(wb.Put(handle, "a", "value"));
|
||||
const auto ts_sz_func = [kTimestampSize](uint32_t /*cf_id*/) {
|
||||
return kTimestampSize;
|
||||
};
|
||||
ASSERT_OK(wb.UpdateTimestamps(ts, ts_sz_func));
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &wb));
|
||||
|
||||
int num_keys = 2;
|
||||
std::vector<Slice> keys;
|
||||
std::vector<std::string> expected_values;
|
||||
for (int i = 0; i < num_keys; i++) {
|
||||
keys.push_back("a");
|
||||
expected_values.push_back("value");
|
||||
}
|
||||
std::vector<ColumnFamilyHandle*> handles;
|
||||
handles.push_back(db_->DefaultColumnFamily());
|
||||
handles.push_back(handle);
|
||||
|
||||
{
|
||||
Slice read_ts_slice(ts);
|
||||
ReadOptions read_opts;
|
||||
read_opts.timestamp = &read_ts_slice;
|
||||
|
||||
std::vector<PinnableSlice> values;
|
||||
values.resize(num_keys);
|
||||
std::vector<Status> statuses;
|
||||
statuses.resize(num_keys);
|
||||
std::vector<std::string> timestamps;
|
||||
timestamps.resize(num_keys);
|
||||
|
||||
db_->MultiGet(read_opts, num_keys, handles.data(), keys.data(),
|
||||
values.data(), timestamps.data(), statuses.data());
|
||||
|
||||
for (int i = 0; i < num_keys; i++) {
|
||||
ASSERT_OK(statuses[i]);
|
||||
ASSERT_EQ(expected_values[i], values[i].ToString());
|
||||
ASSERT_EQ(ts, timestamps[i]);
|
||||
}
|
||||
}
|
||||
|
||||
delete handle;
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, CompactRangeWithSpecifiedRange) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
|
||||
@@ -68,7 +68,8 @@ class FlushJobTestBase : public testing::Test {
|
||||
}
|
||||
|
||||
void NewDB() {
|
||||
ASSERT_OK(SetIdentityFile(WriteOptions(), env_, dbname_));
|
||||
ASSERT_OK(
|
||||
SetIdentityFile(WriteOptions(), env_, dbname_, Temperature::kUnknown));
|
||||
VersionEdit new_db;
|
||||
|
||||
new_db.SetLogNumber(0);
|
||||
@@ -114,7 +115,8 @@ class FlushJobTestBase : public testing::Test {
|
||||
}
|
||||
ASSERT_OK(s);
|
||||
// Make "CURRENT" file that points to the new manifest file.
|
||||
s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr);
|
||||
s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1,
|
||||
Temperature::kUnknown, nullptr);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
|
||||
@@ -164,6 +164,7 @@ Status TableCache::GetTableReader(
|
||||
}
|
||||
|
||||
Cache::Handle* TableCache::Lookup(Cache* cache, uint64_t file_number) {
|
||||
// NOTE: sharing same Cache with BlobFileCache
|
||||
Slice key = GetSliceForFileNumber(&file_number);
|
||||
return cache->Lookup(key);
|
||||
}
|
||||
@@ -179,6 +180,7 @@ Status TableCache::FindTable(
|
||||
size_t max_file_size_for_l0_meta_pin, Temperature file_temperature) {
|
||||
PERF_TIMER_GUARD_WITH_CLOCK(find_table_nanos, ioptions_.clock);
|
||||
uint64_t number = file_meta.fd.GetNumber();
|
||||
// NOTE: sharing same Cache with BlobFileCache
|
||||
Slice key = GetSliceForFileNumber(&number);
|
||||
*handle = cache_.Lookup(key);
|
||||
TEST_SYNC_POINT_CALLBACK("TableCache::FindTable:0",
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "cache/cache_reservation_manager.h"
|
||||
#include "db/blob/blob_file_cache.h"
|
||||
#include "db/blob/blob_file_meta.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/internal_stats.h"
|
||||
@@ -743,12 +744,9 @@ class VersionBuilder::Rep {
|
||||
return Status::Corruption("VersionBuilder", oss.str());
|
||||
}
|
||||
|
||||
// Note: we use C++11 for now but in C++14, this could be done in a more
|
||||
// elegant way using generalized lambda capture.
|
||||
VersionSet* const vs = version_set_;
|
||||
const ImmutableCFOptions* const ioptions = ioptions_;
|
||||
|
||||
auto deleter = [vs, ioptions](SharedBlobFileMetaData* shared_meta) {
|
||||
auto deleter = [vs = version_set_, ioptions = ioptions_,
|
||||
bc = cfd_ ? cfd_->blob_file_cache()
|
||||
: nullptr](SharedBlobFileMetaData* shared_meta) {
|
||||
if (vs) {
|
||||
assert(ioptions);
|
||||
assert(!ioptions->cf_paths.empty());
|
||||
@@ -757,6 +755,9 @@ class VersionBuilder::Rep {
|
||||
vs->AddObsoleteBlobFile(shared_meta->GetBlobFileNumber(),
|
||||
ioptions->cf_paths.front().path);
|
||||
}
|
||||
if (bc) {
|
||||
bc->Evict(shared_meta->GetBlobFileNumber());
|
||||
}
|
||||
|
||||
delete shared_meta;
|
||||
};
|
||||
@@ -765,7 +766,7 @@ class VersionBuilder::Rep {
|
||||
blob_file_number, blob_file_addition.GetTotalBlobCount(),
|
||||
blob_file_addition.GetTotalBlobBytes(),
|
||||
blob_file_addition.GetChecksumMethod(),
|
||||
blob_file_addition.GetChecksumValue(), deleter);
|
||||
blob_file_addition.GetChecksumValue(), std::move(deleter));
|
||||
|
||||
mutable_blob_file_metas_.emplace(
|
||||
blob_file_number, MutableBlobFileMetaData(std::move(shared_meta)));
|
||||
|
||||
+7
-3
@@ -5511,6 +5511,10 @@ Status VersionSet::ProcessManifestWrites(
|
||||
std::unique_ptr<log::Writer> new_desc_log_ptr;
|
||||
{
|
||||
FileOptions opt_file_opts = fs_->OptimizeForManifestWrite(file_options_);
|
||||
// DB option (in file_options_) takes precedence when not kUnknown
|
||||
if (file_options_.temperature != Temperature::kUnknown) {
|
||||
opt_file_opts.temperature = file_options_.temperature;
|
||||
}
|
||||
mu->Unlock();
|
||||
TEST_SYNC_POINT("VersionSet::LogAndApply:WriteManifestStart");
|
||||
TEST_SYNC_POINT_CALLBACK("VersionSet::LogAndApply:WriteManifest", nullptr);
|
||||
@@ -5637,9 +5641,9 @@ Status VersionSet::ProcessManifestWrites(
|
||||
assert(manifest_io_status.ok());
|
||||
}
|
||||
if (s.ok() && new_descriptor_log) {
|
||||
io_s = SetCurrentFile(write_options, fs_.get(), dbname_,
|
||||
pending_manifest_file_number_,
|
||||
dir_contains_current_file);
|
||||
io_s = SetCurrentFile(
|
||||
write_options, fs_.get(), dbname_, pending_manifest_file_number_,
|
||||
file_options_.temperature, dir_contains_current_file);
|
||||
if (!io_s.ok()) {
|
||||
s = io_s;
|
||||
// Quarantine old manifest file in case new manifest file's CURRENT file
|
||||
|
||||
@@ -1503,7 +1503,6 @@ class VersionSet {
|
||||
void GetLiveFilesMetaData(std::vector<LiveFileMetaData>* metadata);
|
||||
|
||||
void AddObsoleteBlobFile(uint64_t blob_file_number, std::string path) {
|
||||
// TODO: Erase file from BlobFileCache?
|
||||
obsolete_blob_files_.emplace_back(blob_file_number, std::move(path));
|
||||
}
|
||||
|
||||
|
||||
+44
-54
@@ -1415,16 +1415,22 @@ class VersionSetTestBase {
|
||||
}
|
||||
}
|
||||
|
||||
void CreateCurrentFile() {
|
||||
// Make "CURRENT" file point to the new manifest file.
|
||||
ASSERT_OK(SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1,
|
||||
Temperature::kUnknown,
|
||||
/* dir_contains_current_file */ nullptr));
|
||||
}
|
||||
|
||||
// Create DB with 3 column families.
|
||||
void NewDB() {
|
||||
SequenceNumber last_seqno;
|
||||
std::unique_ptr<log::Writer> log_writer;
|
||||
ASSERT_OK(SetIdentityFile(WriteOptions(), env_, dbname_));
|
||||
ASSERT_OK(
|
||||
SetIdentityFile(WriteOptions(), env_, dbname_, Temperature::kUnknown));
|
||||
PrepareManifest(&column_families_, &last_seqno, &log_writer);
|
||||
log_writer.reset();
|
||||
// Make "CURRENT" file point to the new manifest file.
|
||||
Status s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
|
||||
EXPECT_OK(versions_->Recover(column_families_, false));
|
||||
EXPECT_EQ(column_families_.size(),
|
||||
@@ -2600,7 +2606,7 @@ class VersionSetAtomicGroupTest : public VersionSetTestBase,
|
||||
edits_[i].MarkAtomicGroup(--remaining);
|
||||
edits_[i].SetLastSequence(last_seqno_++);
|
||||
}
|
||||
ASSERT_OK(SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr));
|
||||
CreateCurrentFile();
|
||||
}
|
||||
|
||||
void SetupIncompleteTrailingAtomicGroup(int atomic_group_size) {
|
||||
@@ -2612,7 +2618,7 @@ class VersionSetAtomicGroupTest : public VersionSetTestBase,
|
||||
edits_[i].MarkAtomicGroup(--remaining);
|
||||
edits_[i].SetLastSequence(last_seqno_++);
|
||||
}
|
||||
ASSERT_OK(SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr));
|
||||
CreateCurrentFile();
|
||||
}
|
||||
|
||||
void SetupCorruptedAtomicGroup(int atomic_group_size) {
|
||||
@@ -2626,7 +2632,7 @@ class VersionSetAtomicGroupTest : public VersionSetTestBase,
|
||||
}
|
||||
edits_[i].SetLastSequence(last_seqno_++);
|
||||
}
|
||||
ASSERT_OK(SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr));
|
||||
CreateCurrentFile();
|
||||
}
|
||||
|
||||
void SetupIncorrectAtomicGroup(int atomic_group_size) {
|
||||
@@ -2642,7 +2648,7 @@ class VersionSetAtomicGroupTest : public VersionSetTestBase,
|
||||
}
|
||||
edits_[i].SetLastSequence(last_seqno_++);
|
||||
}
|
||||
ASSERT_OK(SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr));
|
||||
CreateCurrentFile();
|
||||
}
|
||||
|
||||
void SetupTestSyncPoints() {
|
||||
@@ -3408,8 +3414,7 @@ TEST_P(VersionSetTestDropOneCF, HandleDroppedColumnFamilyInAtomicGroup) {
|
||||
SequenceNumber last_seqno;
|
||||
std::unique_ptr<log::Writer> log_writer;
|
||||
PrepareManifest(&column_families, &last_seqno, &log_writer);
|
||||
Status s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
|
||||
EXPECT_OK(versions_->Recover(column_families, false /* read_only */));
|
||||
EXPECT_EQ(column_families.size(),
|
||||
@@ -3431,7 +3436,7 @@ TEST_P(VersionSetTestDropOneCF, HandleDroppedColumnFamilyInAtomicGroup) {
|
||||
cfd_to_drop->Ref();
|
||||
drop_cf_edit.SetColumnFamily(cfd_to_drop->GetID());
|
||||
mutex_.Lock();
|
||||
s = versions_->LogAndApply(
|
||||
Status s = versions_->LogAndApply(
|
||||
cfd_to_drop, *cfd_to_drop->GetLatestMutableCFOptions(), read_options,
|
||||
write_options, &drop_cf_edit, &mutex_, nullptr);
|
||||
mutex_.Unlock();
|
||||
@@ -3541,9 +3546,7 @@ class EmptyDefaultCfNewManifest : public VersionSetTestBase,
|
||||
TEST_F(EmptyDefaultCfNewManifest, Recover) {
|
||||
PrepareManifest(nullptr, nullptr, &log_writer_);
|
||||
log_writer_.reset();
|
||||
Status s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1,
|
||||
/* dir_contains_current_file */ nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
@@ -3552,7 +3555,7 @@ TEST_F(EmptyDefaultCfNewManifest, Recover) {
|
||||
cf_options_);
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(
|
||||
Status s = versions_->TryRecoverFromOneManifest(
|
||||
manifest_path, column_families, false, &db_id, &has_missing_table_file);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_FALSE(has_missing_table_file);
|
||||
@@ -3573,7 +3576,8 @@ class VersionSetTestEmptyDb
|
||||
assert(nullptr != log_writer);
|
||||
VersionEdit new_db;
|
||||
if (db_options_.write_dbid_to_manifest) {
|
||||
ASSERT_OK(SetIdentityFile(WriteOptions(), env_, dbname_));
|
||||
ASSERT_OK(SetIdentityFile(WriteOptions(), env_, dbname_,
|
||||
Temperature::kUnknown));
|
||||
DBOptions tmp_db_options;
|
||||
tmp_db_options.env = env_;
|
||||
std::unique_ptr<DBImpl> impl(new DBImpl(tmp_db_options, dbname_));
|
||||
@@ -3606,9 +3610,7 @@ TEST_P(VersionSetTestEmptyDb, OpenFromIncompleteManifest0) {
|
||||
db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
|
||||
PrepareManifest(nullptr, nullptr, &log_writer_);
|
||||
log_writer_.reset();
|
||||
Status s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1,
|
||||
/* dir_contains_current_file */ nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
@@ -3623,9 +3625,9 @@ TEST_P(VersionSetTestEmptyDb, OpenFromIncompleteManifest0) {
|
||||
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(manifest_path, column_families,
|
||||
read_only, &db_id,
|
||||
&has_missing_table_file);
|
||||
Status s = versions_->TryRecoverFromOneManifest(
|
||||
manifest_path, column_families, read_only, &db_id,
|
||||
&has_missing_table_file);
|
||||
auto iter =
|
||||
std::find(cf_names.begin(), cf_names.end(), kDefaultColumnFamilyName);
|
||||
if (iter == cf_names.end()) {
|
||||
@@ -3651,9 +3653,7 @@ TEST_P(VersionSetTestEmptyDb, OpenFromIncompleteManifest1) {
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
log_writer_.reset();
|
||||
s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1,
|
||||
/* dir_contains_current_file */ nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
@@ -3699,9 +3699,7 @@ TEST_P(VersionSetTestEmptyDb, OpenFromInCompleteManifest2) {
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
log_writer_.reset();
|
||||
s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1,
|
||||
/* dir_contains_current_file */ nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
@@ -3758,9 +3756,7 @@ TEST_P(VersionSetTestEmptyDb, OpenManifestWithUnknownCF) {
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
log_writer_.reset();
|
||||
s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1,
|
||||
/* dir_contains_current_file */ nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
@@ -3816,9 +3812,7 @@ TEST_P(VersionSetTestEmptyDb, OpenCompleteManifest) {
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
log_writer_.reset();
|
||||
s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1,
|
||||
/* dir_contains_current_file */ nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
@@ -4025,15 +4019,14 @@ TEST_F(VersionSetTestMissingFiles, ManifestFarBehindSst) {
|
||||
WriteFileAdditionAndDeletionToManifest(
|
||||
/*cf=*/0, std::vector<std::pair<int, FileMetaData>>(), deleted_files);
|
||||
log_writer_.reset();
|
||||
Status s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(manifest_path, column_families_,
|
||||
/*read_only=*/false, &db_id,
|
||||
&has_missing_table_file);
|
||||
Status s = versions_->TryRecoverFromOneManifest(
|
||||
manifest_path, column_families_,
|
||||
/*read_only=*/false, &db_id, &has_missing_table_file);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_TRUE(has_missing_table_file);
|
||||
for (ColumnFamilyData* cfd : *(versions_->GetColumnFamilySet())) {
|
||||
@@ -4083,15 +4076,14 @@ TEST_F(VersionSetTestMissingFiles, ManifestAheadofSst) {
|
||||
WriteFileAdditionAndDeletionToManifest(
|
||||
/*cf=*/0, added_files, std::vector<std::pair<int, uint64_t>>());
|
||||
log_writer_.reset();
|
||||
Status s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(manifest_path, column_families_,
|
||||
/*read_only=*/false, &db_id,
|
||||
&has_missing_table_file);
|
||||
Status s = versions_->TryRecoverFromOneManifest(
|
||||
manifest_path, column_families_,
|
||||
/*read_only=*/false, &db_id, &has_missing_table_file);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_TRUE(has_missing_table_file);
|
||||
for (ColumnFamilyData* cfd : *(versions_->GetColumnFamilySet())) {
|
||||
@@ -4137,15 +4129,14 @@ TEST_F(VersionSetTestMissingFiles, NoFileMissing) {
|
||||
WriteFileAdditionAndDeletionToManifest(
|
||||
/*cf=*/0, std::vector<std::pair<int, FileMetaData>>(), deleted_files);
|
||||
log_writer_.reset();
|
||||
Status s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(manifest_path, column_families_,
|
||||
/*read_only=*/false, &db_id,
|
||||
&has_missing_table_file);
|
||||
Status s = versions_->TryRecoverFromOneManifest(
|
||||
manifest_path, column_families_,
|
||||
/*read_only=*/false, &db_id, &has_missing_table_file);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_FALSE(has_missing_table_file);
|
||||
for (ColumnFamilyData* cfd : *(versions_->GetColumnFamilySet())) {
|
||||
@@ -4266,15 +4257,14 @@ class BestEffortsRecoverIncompleteVersionTest
|
||||
/*cf=*/0, added_files, std::vector<std::pair<int, uint64_t>>(),
|
||||
blob_files);
|
||||
log_writer_.reset();
|
||||
Status s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(manifest_path, column_families_,
|
||||
/*read_only=*/false, &db_id,
|
||||
&has_missing_table_file);
|
||||
Status s = versions_->TryRecoverFromOneManifest(
|
||||
manifest_path, column_families_,
|
||||
/*read_only=*/false, &db_id, &has_missing_table_file);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_TRUE(has_missing_table_file);
|
||||
}
|
||||
|
||||
Vendored
+3
-3
@@ -181,10 +181,10 @@ FileOptions FileSystem::OptimizeForBlobFileRead(
|
||||
|
||||
IOStatus WriteStringToFile(FileSystem* fs, const Slice& data,
|
||||
const std::string& fname, bool should_sync,
|
||||
const IOOptions& io_options) {
|
||||
const IOOptions& io_options,
|
||||
const FileOptions& file_options) {
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
EnvOptions soptions;
|
||||
IOStatus s = fs->NewWritableFile(fname, soptions, &file, nullptr);
|
||||
IOStatus s = fs->NewWritableFile(fname, file_options, &file, nullptr);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
+11
-3
@@ -388,6 +388,7 @@ bool ParseFileName(const std::string& fname, uint64_t* number,
|
||||
|
||||
IOStatus SetCurrentFile(const WriteOptions& write_options, FileSystem* fs,
|
||||
const std::string& dbname, uint64_t descriptor_number,
|
||||
Temperature temp,
|
||||
FSDirectory* dir_contains_current_file) {
|
||||
// Remove leading "dbname/" and add newline to manifest file name
|
||||
std::string manifest = DescriptorFileName(dbname, descriptor_number);
|
||||
@@ -397,8 +398,11 @@ IOStatus SetCurrentFile(const WriteOptions& write_options, FileSystem* fs,
|
||||
std::string tmp = TempFileName(dbname, descriptor_number);
|
||||
IOOptions opts;
|
||||
IOStatus s = PrepareIOFromWriteOptions(write_options, opts);
|
||||
FileOptions file_opts;
|
||||
file_opts.temperature = temp;
|
||||
if (s.ok()) {
|
||||
s = WriteStringToFile(fs, contents.ToString() + "\n", tmp, true, opts);
|
||||
s = WriteStringToFile(fs, contents.ToString() + "\n", tmp, true, opts,
|
||||
file_opts);
|
||||
}
|
||||
TEST_SYNC_POINT_CALLBACK("SetCurrentFile:BeforeRename", &s);
|
||||
if (s.ok()) {
|
||||
@@ -423,7 +427,8 @@ IOStatus SetCurrentFile(const WriteOptions& write_options, FileSystem* fs,
|
||||
}
|
||||
|
||||
Status SetIdentityFile(const WriteOptions& write_options, Env* env,
|
||||
const std::string& dbname, const std::string& db_id) {
|
||||
const std::string& dbname, Temperature temp,
|
||||
const std::string& db_id) {
|
||||
std::string id;
|
||||
if (db_id.empty()) {
|
||||
id = env->GenerateUniqueId();
|
||||
@@ -437,8 +442,11 @@ Status SetIdentityFile(const WriteOptions& write_options, Env* env,
|
||||
Status s;
|
||||
IOOptions opts;
|
||||
s = PrepareIOFromWriteOptions(write_options, opts);
|
||||
FileOptions file_opts;
|
||||
file_opts.temperature = temp;
|
||||
if (s.ok()) {
|
||||
s = WriteStringToFile(env, id, tmp, true, &opts);
|
||||
s = WriteStringToFile(env->GetFileSystem().get(), id, tmp,
|
||||
/*should_sync=*/true, opts, file_opts);
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = env->RenameFile(tmp, identify_file_name);
|
||||
|
||||
+2
-1
@@ -161,11 +161,12 @@ bool ParseFileName(const std::string& filename, uint64_t* number,
|
||||
// when
|
||||
IOStatus SetCurrentFile(const WriteOptions& write_options, FileSystem* fs,
|
||||
const std::string& dbname, uint64_t descriptor_number,
|
||||
Temperature temp,
|
||||
FSDirectory* dir_contains_current_file);
|
||||
|
||||
// Make the IDENTITY file for the db
|
||||
Status SetIdentityFile(const WriteOptions& write_options, Env* env,
|
||||
const std::string& dbname,
|
||||
const std::string& dbname, Temperature temp,
|
||||
const std::string& db_id = {});
|
||||
|
||||
// Sync manifest file `file`.
|
||||
|
||||
@@ -61,18 +61,6 @@ enum CompactionPri : char {
|
||||
kRoundRobin = 0x4,
|
||||
};
|
||||
|
||||
// Temperature of a file. Used to pass to FileSystem for a different
|
||||
// placement and/or coding.
|
||||
// Reserve some numbers in the middle, in case we need to insert new tier
|
||||
// there.
|
||||
enum class Temperature : uint8_t {
|
||||
kUnknown = 0,
|
||||
kHot = 0x04,
|
||||
kWarm = 0x08,
|
||||
kCold = 0x0C,
|
||||
kLastTemperature,
|
||||
};
|
||||
|
||||
struct FileTemperatureAge {
|
||||
Temperature temperature = Temperature::kUnknown;
|
||||
uint64_t age = 0;
|
||||
@@ -813,7 +801,7 @@ struct AdvancedColumnFamilyOptions {
|
||||
// If this option is set, when creating the last level files, pass this
|
||||
// temperature to FileSystem used. Should be no-op for default FileSystem
|
||||
// and users need to plug in their own FileSystem to take advantage of it.
|
||||
// When using FIFO compaction, this option is ignored.
|
||||
// Currently only compatible with universal compaction.
|
||||
//
|
||||
// Dynamically changeable through the SetOptions() API
|
||||
Temperature last_level_temperature = Temperature::kUnknown;
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "rocksdb/port_defs.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/thread_status.h"
|
||||
#include "rocksdb/types.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
// Windows API macro interference
|
||||
@@ -159,6 +160,9 @@ class Env : public Customizable {
|
||||
|
||||
// Size of file in bytes
|
||||
uint64_t size_bytes;
|
||||
|
||||
// EXPERIMENTAL - only provided by some implementations
|
||||
Temperature temperature = Temperature::kUnknown;
|
||||
};
|
||||
|
||||
Env();
|
||||
|
||||
@@ -195,7 +195,9 @@ struct FileOptions : EnvOptions {
|
||||
FileOptions() : EnvOptions(), handoff_checksum_type(ChecksumType::kCRC32c) {}
|
||||
|
||||
FileOptions(const DBOptions& opts)
|
||||
: EnvOptions(opts), handoff_checksum_type(ChecksumType::kCRC32c) {}
|
||||
: EnvOptions(opts),
|
||||
temperature(opts.metadata_write_temperature),
|
||||
handoff_checksum_type(ChecksumType::kCRC32c) {}
|
||||
|
||||
FileOptions(const EnvOptions& opts)
|
||||
: EnvOptions(opts), handoff_checksum_type(ChecksumType::kCRC32c) {}
|
||||
@@ -1952,7 +1954,8 @@ class FSDirectoryWrapper : public FSDirectory {
|
||||
// A utility routine: write "data" to the named file.
|
||||
IOStatus WriteStringToFile(FileSystem* fs, const Slice& data,
|
||||
const std::string& fname, bool should_sync = false,
|
||||
const IOOptions& io_options = IOOptions());
|
||||
const IOOptions& io_options = IOOptions(),
|
||||
const FileOptions& file_options = FileOptions());
|
||||
|
||||
// A utility routine: read contents of named file into *data
|
||||
IOStatus ReadFileToString(FileSystem* fs, const std::string& fname,
|
||||
|
||||
@@ -1580,6 +1580,16 @@ struct DBOptions {
|
||||
// Default 100ms
|
||||
uint64_t follower_catchup_retry_wait_ms = 100;
|
||||
|
||||
// When DB files other than SST, blob and WAL files are created, use this
|
||||
// filesystem temperature. (See also `wal_write_temperature` and various
|
||||
// `*_temperature` CF options.) When not `kUnknown`, this overrides any
|
||||
// temperature set by OptimizeForManifestWrite functions.
|
||||
Temperature metadata_write_temperature = Temperature::kUnknown;
|
||||
|
||||
// Use this filesystem temperature when creating WAL files. When not
|
||||
// `kUnknown`, this overrides any temperature set by OptimizeForLogWrite
|
||||
// functions.
|
||||
Temperature wal_write_temperature = Temperature::kUnknown;
|
||||
// End EXPERIMENTAL
|
||||
};
|
||||
|
||||
|
||||
@@ -110,4 +110,16 @@ enum class WriteStallCondition {
|
||||
kNormal,
|
||||
};
|
||||
|
||||
// Temperature of a file. Used to pass to FileSystem for a different
|
||||
// placement and/or coding.
|
||||
// Reserve some numbers in the middle, in case we need to insert new tier
|
||||
// there.
|
||||
enum class Temperature : uint8_t {
|
||||
kUnknown = 0,
|
||||
kHot = 0x04,
|
||||
kWarm = 0x08,
|
||||
kCold = 0x0C,
|
||||
kLastTemperature,
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// minor or major version number planned for release.
|
||||
#define ROCKSDB_MAJOR 9
|
||||
#define ROCKSDB_MINOR 6
|
||||
#define ROCKSDB_PATCH 0
|
||||
#define ROCKSDB_PATCH 2
|
||||
|
||||
// Do not use these. We made the mistake of declaring macros starting with
|
||||
// double underscore. Now we have to live with our choice. We'll deprecate these
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/write_batch_base.h"
|
||||
|
||||
+15
-1
@@ -576,6 +576,14 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
{offsetof(struct ImmutableDBOptions, follower_catchup_retry_wait_ms),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"metadata_write_temperature",
|
||||
{offsetof(struct ImmutableDBOptions, metadata_write_temperature),
|
||||
OptionType::kTemperature, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"wal_write_temperature",
|
||||
{offsetof(struct ImmutableDBOptions, wal_write_temperature),
|
||||
OptionType::kTemperature, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
};
|
||||
|
||||
const std::string OptionsHelper::kDBOptionsName = "DBOptions";
|
||||
@@ -778,7 +786,9 @@ ImmutableDBOptions::ImmutableDBOptions(const DBOptions& options)
|
||||
follower_refresh_catchup_period_ms(
|
||||
options.follower_refresh_catchup_period_ms),
|
||||
follower_catchup_retry_count(options.follower_catchup_retry_count),
|
||||
follower_catchup_retry_wait_ms(options.follower_catchup_retry_wait_ms) {
|
||||
follower_catchup_retry_wait_ms(options.follower_catchup_retry_wait_ms),
|
||||
metadata_write_temperature(options.metadata_write_temperature),
|
||||
wal_write_temperature(options.wal_write_temperature) {
|
||||
fs = env->GetFileSystem();
|
||||
clock = env->GetSystemClock().get();
|
||||
logger = info_log.get();
|
||||
@@ -956,6 +966,10 @@ void ImmutableDBOptions::Dump(Logger* log) const {
|
||||
db_host_id.c_str());
|
||||
ROCKS_LOG_HEADER(log, " Options.enforce_single_del_contracts: %s",
|
||||
enforce_single_del_contracts ? "true" : "false");
|
||||
ROCKS_LOG_HEADER(log, " Options.metadata_write_temperature: %s",
|
||||
temperature_to_string[metadata_write_temperature].c_str());
|
||||
ROCKS_LOG_HEADER(log, " Options.wal_write_temperature: %s",
|
||||
temperature_to_string[wal_write_temperature].c_str());
|
||||
}
|
||||
|
||||
bool ImmutableDBOptions::IsWalDirSameAsDBPath() const {
|
||||
|
||||
@@ -103,6 +103,8 @@ struct ImmutableDBOptions {
|
||||
uint64_t follower_refresh_catchup_period_ms;
|
||||
uint64_t follower_catchup_retry_count;
|
||||
uint64_t follower_catchup_retry_wait_ms;
|
||||
Temperature metadata_write_temperature;
|
||||
Temperature wal_write_temperature;
|
||||
|
||||
// Beginning convenience/helper objects that are not part of the base
|
||||
// DBOptions
|
||||
|
||||
@@ -180,6 +180,15 @@ DBOptions BuildDBOptions(const ImmutableDBOptions& immutable_db_options,
|
||||
options.enforce_single_del_contracts =
|
||||
immutable_db_options.enforce_single_del_contracts;
|
||||
options.daily_offpeak_time_utc = mutable_db_options.daily_offpeak_time_utc;
|
||||
options.follower_refresh_catchup_period_ms =
|
||||
immutable_db_options.follower_refresh_catchup_period_ms;
|
||||
options.follower_catchup_retry_count =
|
||||
immutable_db_options.follower_catchup_retry_count;
|
||||
options.follower_catchup_retry_wait_ms =
|
||||
immutable_db_options.follower_catchup_retry_wait_ms;
|
||||
options.metadata_write_temperature =
|
||||
immutable_db_options.metadata_write_temperature;
|
||||
options.wal_write_temperature = immutable_db_options.wal_write_temperature;
|
||||
return options;
|
||||
}
|
||||
|
||||
|
||||
@@ -69,8 +69,9 @@ Status PersistRocksDBOptions(const WriteOptions& write_options,
|
||||
}
|
||||
std::unique_ptr<FSWritableFile> wf;
|
||||
|
||||
Status s =
|
||||
fs->NewWritableFile(file_name, FileOptions(), &wf, nullptr);
|
||||
FileOptions file_options;
|
||||
file_options.temperature = db_opt.metadata_write_temperature;
|
||||
Status s = fs->NewWritableFile(file_name, file_options, &wf, nullptr);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -367,7 +367,12 @@ TEST_F(OptionsSettableTest, DBOptionsAllFieldsSettable) {
|
||||
"lowest_used_cache_tier=kNonVolatileBlockTier;"
|
||||
"allow_data_in_errors=false;"
|
||||
"enforce_single_del_contracts=false;"
|
||||
"daily_offpeak_time_utc=08:30-19:00;",
|
||||
"daily_offpeak_time_utc=08:30-19:00;"
|
||||
"follower_refresh_catchup_period_ms=123;"
|
||||
"follower_catchup_retry_count=456;"
|
||||
"follower_catchup_retry_wait_ms=789;"
|
||||
"metadata_write_temperature=kCold;"
|
||||
"wal_write_temperature=kHot;",
|
||||
new_options));
|
||||
|
||||
ASSERT_EQ(unset_bytes_base, NumUnsetBytes(new_options_ptr, sizeof(DBOptions),
|
||||
|
||||
+16
@@ -19,3 +19,19 @@
|
||||
#elif defined(OS_WIN)
|
||||
#include "port/win/port_win.h"
|
||||
#endif
|
||||
|
||||
#ifdef OS_LINUX
|
||||
// A temporary hook into long-running RocksDB threads to support modifying their
|
||||
// priority etc. This should become a public API hook once the requirements
|
||||
// are better understood.
|
||||
extern "C" void RocksDbThreadYield() __attribute__((__weak__));
|
||||
#define ROCKSDB_THREAD_YIELD_HOOK() \
|
||||
{ \
|
||||
if (RocksDbThreadYield) { \
|
||||
RocksDbThreadYield(); \
|
||||
} \
|
||||
}
|
||||
#else
|
||||
#define ROCKSDB_THREAD_YIELD_HOOK() \
|
||||
{}
|
||||
#endif
|
||||
|
||||
@@ -680,26 +680,12 @@ Status BlockBasedTable::Open(
|
||||
if (s.ok()) {
|
||||
s = ReadFooterFromFile(opts, file.get(), *ioptions.fs,
|
||||
prefetch_buffer.get(), file_size, &footer,
|
||||
kBlockBasedTableMagicNumber);
|
||||
}
|
||||
// If the footer is corrupted and the FS supports checksum verification and
|
||||
// correction, try reading the footer again
|
||||
if (s.IsCorruption()) {
|
||||
RecordTick(ioptions.statistics.get(), SST_FOOTER_CORRUPTION_COUNT);
|
||||
if (CheckFSFeatureSupport(ioptions.fs.get(),
|
||||
FSSupportedOps::kVerifyAndReconstructRead)) {
|
||||
IOOptions retry_opts = opts;
|
||||
retry_opts.verify_and_reconstruct_read = true;
|
||||
s = ReadFooterFromFile(retry_opts, file.get(), *ioptions.fs,
|
||||
prefetch_buffer.get(), file_size, &footer,
|
||||
kBlockBasedTableMagicNumber);
|
||||
RecordTick(ioptions.stats, FILE_READ_CORRUPTION_RETRY_COUNT);
|
||||
if (s.ok()) {
|
||||
RecordTick(ioptions.stats, FILE_READ_CORRUPTION_RETRY_SUCCESS_COUNT);
|
||||
}
|
||||
}
|
||||
kBlockBasedTableMagicNumber, ioptions.stats);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
if (s.IsCorruption()) {
|
||||
RecordTick(ioptions.statistics.get(), SST_FOOTER_CORRUPTION_COUNT);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
if (!IsSupportedFormatVersion(footer.format_version())) {
|
||||
|
||||
+32
-4
@@ -475,10 +475,12 @@ std::string Footer::ToString() const {
|
||||
return result;
|
||||
}
|
||||
|
||||
Status ReadFooterFromFile(const IOOptions& opts, RandomAccessFileReader* file,
|
||||
FileSystem& fs, FilePrefetchBuffer* prefetch_buffer,
|
||||
uint64_t file_size, Footer* footer,
|
||||
uint64_t enforce_table_magic_number) {
|
||||
static Status ReadFooterFromFileInternal(const IOOptions& opts,
|
||||
RandomAccessFileReader* file,
|
||||
FileSystem& fs,
|
||||
FilePrefetchBuffer* prefetch_buffer,
|
||||
uint64_t file_size, Footer* footer,
|
||||
uint64_t enforce_table_magic_number) {
|
||||
if (file_size < Footer::kMinEncodedLength) {
|
||||
return Status::Corruption("file is too short (" +
|
||||
std::to_string(file_size) +
|
||||
@@ -516,6 +518,8 @@ Status ReadFooterFromFile(const IOOptions& opts, RandomAccessFileReader* file,
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK("ReadFooterFromFileInternal:0", &footer_input);
|
||||
|
||||
// Check that we actually read the whole footer from the file. It may be
|
||||
// that size isn't correct.
|
||||
if (footer_input.size() < Footer::kMinEncodedLength) {
|
||||
@@ -543,6 +547,30 @@ Status ReadFooterFromFile(const IOOptions& opts, RandomAccessFileReader* file,
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status ReadFooterFromFile(const IOOptions& opts, RandomAccessFileReader* file,
|
||||
FileSystem& fs, FilePrefetchBuffer* prefetch_buffer,
|
||||
uint64_t file_size, Footer* footer,
|
||||
uint64_t enforce_table_magic_number,
|
||||
Statistics* stats) {
|
||||
Status s =
|
||||
ReadFooterFromFileInternal(opts, file, fs, prefetch_buffer, file_size,
|
||||
footer, enforce_table_magic_number);
|
||||
if (s.IsCorruption() &&
|
||||
CheckFSFeatureSupport(&fs, FSSupportedOps::kVerifyAndReconstructRead)) {
|
||||
IOOptions new_opts = opts;
|
||||
new_opts.verify_and_reconstruct_read = true;
|
||||
footer->Reset();
|
||||
s = ReadFooterFromFileInternal(new_opts, file, fs, prefetch_buffer,
|
||||
file_size, footer,
|
||||
enforce_table_magic_number);
|
||||
RecordTick(stats, FILE_READ_CORRUPTION_RETRY_COUNT);
|
||||
if (s.ok()) {
|
||||
RecordTick(stats, FILE_READ_CORRUPTION_RETRY_SUCCESS_COUNT);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Custom handling for the last byte of a block, to avoid invoking streaming
|
||||
// API to get an effective block checksum. This function is its own inverse
|
||||
|
||||
+12
-1
@@ -186,6 +186,16 @@ class Footer {
|
||||
// Create empty. Populate using DecodeFrom.
|
||||
Footer() {}
|
||||
|
||||
void Reset() {
|
||||
table_magic_number_ = kNullTableMagicNumber;
|
||||
format_version_ = kInvalidFormatVersion;
|
||||
base_context_checksum_ = 0;
|
||||
metaindex_handle_ = BlockHandle::NullBlockHandle();
|
||||
index_handle_ = BlockHandle::NullBlockHandle();
|
||||
checksum_type_ = kInvalidChecksumType;
|
||||
block_trailer_size_ = 0;
|
||||
}
|
||||
|
||||
// Deserialize a footer (populate fields) from `input` and check for various
|
||||
// corruptions. `input_offset` is the offset within the target file of
|
||||
// `input` buffer, which is needed for verifying format_version >= 6 footer.
|
||||
@@ -304,7 +314,8 @@ class FooterBuilder {
|
||||
Status ReadFooterFromFile(const IOOptions& opts, RandomAccessFileReader* file,
|
||||
FileSystem& fs, FilePrefetchBuffer* prefetch_buffer,
|
||||
uint64_t file_size, Footer* footer,
|
||||
uint64_t enforce_table_magic_number = 0);
|
||||
uint64_t enforce_table_magic_number = 0,
|
||||
Statistics* stats = nullptr);
|
||||
|
||||
// Computes a checksum using the given ChecksumType. Sometimes we need to
|
||||
// include one more input byte logically at the end but not part of the main
|
||||
|
||||
+210
-162
@@ -259,182 +259,230 @@ Status ReadTablePropertiesHelper(
|
||||
MemoryAllocator* memory_allocator) {
|
||||
assert(table_properties);
|
||||
|
||||
// If this is an external SST file ingested with write_global_seqno set to
|
||||
// true, then we expect the checksum mismatch because checksum was written
|
||||
// by SstFileWriter, but its global seqno in the properties block may have
|
||||
// been changed during ingestion. For this reason, we initially read
|
||||
// and process without checksum verification, then later try checksum
|
||||
// verification so that if it fails, we can copy to a temporary buffer with
|
||||
// global seqno set to its original value, i.e. 0, and attempt checksum
|
||||
// verification again.
|
||||
ReadOptions modified_ro = ro;
|
||||
modified_ro.verify_checksums = false;
|
||||
BlockContents block_contents;
|
||||
BlockFetcher block_fetcher(file, prefetch_buffer, footer, modified_ro, handle,
|
||||
&block_contents, ioptions, false /* decompress */,
|
||||
false /*maybe_compressed*/, BlockType::kProperties,
|
||||
UncompressionDict::GetEmptyDict(),
|
||||
PersistentCacheOptions::kEmpty, memory_allocator);
|
||||
Status s = block_fetcher.ReadBlockContents();
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
// Unfortunately, Block::size() might not equal block_contents.data.size(),
|
||||
// and Block hides block_contents
|
||||
uint64_t block_size = block_contents.data.size();
|
||||
Block properties_block(std::move(block_contents));
|
||||
std::unique_ptr<MetaBlockIter> iter(properties_block.NewMetaIterator());
|
||||
|
||||
std::unique_ptr<TableProperties> new_table_properties{new TableProperties};
|
||||
// All pre-defined properties of type uint64_t
|
||||
std::unordered_map<std::string, uint64_t*> predefined_uint64_properties = {
|
||||
{TablePropertiesNames::kOriginalFileNumber,
|
||||
&new_table_properties->orig_file_number},
|
||||
{TablePropertiesNames::kDataSize, &new_table_properties->data_size},
|
||||
{TablePropertiesNames::kIndexSize, &new_table_properties->index_size},
|
||||
{TablePropertiesNames::kIndexPartitions,
|
||||
&new_table_properties->index_partitions},
|
||||
{TablePropertiesNames::kTopLevelIndexSize,
|
||||
&new_table_properties->top_level_index_size},
|
||||
{TablePropertiesNames::kIndexKeyIsUserKey,
|
||||
&new_table_properties->index_key_is_user_key},
|
||||
{TablePropertiesNames::kIndexValueIsDeltaEncoded,
|
||||
&new_table_properties->index_value_is_delta_encoded},
|
||||
{TablePropertiesNames::kFilterSize, &new_table_properties->filter_size},
|
||||
{TablePropertiesNames::kRawKeySize, &new_table_properties->raw_key_size},
|
||||
{TablePropertiesNames::kRawValueSize,
|
||||
&new_table_properties->raw_value_size},
|
||||
{TablePropertiesNames::kNumDataBlocks,
|
||||
&new_table_properties->num_data_blocks},
|
||||
{TablePropertiesNames::kNumEntries, &new_table_properties->num_entries},
|
||||
{TablePropertiesNames::kNumFilterEntries,
|
||||
&new_table_properties->num_filter_entries},
|
||||
{TablePropertiesNames::kDeletedKeys,
|
||||
&new_table_properties->num_deletions},
|
||||
{TablePropertiesNames::kMergeOperands,
|
||||
&new_table_properties->num_merge_operands},
|
||||
{TablePropertiesNames::kNumRangeDeletions,
|
||||
&new_table_properties->num_range_deletions},
|
||||
{TablePropertiesNames::kFormatVersion,
|
||||
&new_table_properties->format_version},
|
||||
{TablePropertiesNames::kFixedKeyLen,
|
||||
&new_table_properties->fixed_key_len},
|
||||
{TablePropertiesNames::kColumnFamilyId,
|
||||
&new_table_properties->column_family_id},
|
||||
{TablePropertiesNames::kCreationTime,
|
||||
&new_table_properties->creation_time},
|
||||
{TablePropertiesNames::kOldestKeyTime,
|
||||
&new_table_properties->oldest_key_time},
|
||||
{TablePropertiesNames::kFileCreationTime,
|
||||
&new_table_properties->file_creation_time},
|
||||
{TablePropertiesNames::kSlowCompressionEstimatedDataSize,
|
||||
&new_table_properties->slow_compression_estimated_data_size},
|
||||
{TablePropertiesNames::kFastCompressionEstimatedDataSize,
|
||||
&new_table_properties->fast_compression_estimated_data_size},
|
||||
{TablePropertiesNames::kTailStartOffset,
|
||||
&new_table_properties->tail_start_offset},
|
||||
{TablePropertiesNames::kUserDefinedTimestampsPersisted,
|
||||
&new_table_properties->user_defined_timestamps_persisted},
|
||||
};
|
||||
|
||||
std::string last_key;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
s = iter->status();
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
Status s;
|
||||
bool retry = false;
|
||||
while (true) {
|
||||
BlockContents block_contents;
|
||||
size_t len = handle.size() + footer.GetBlockTrailerSize();
|
||||
// If this is an external SST file ingested with write_global_seqno set to
|
||||
// true, then we expect the checksum mismatch because checksum was written
|
||||
// by SstFileWriter, but its global seqno in the properties block may have
|
||||
// been changed during ingestion. For this reason, we initially read
|
||||
// and process without checksum verification, then later try checksum
|
||||
// verification so that if it fails, we can copy to a temporary buffer with
|
||||
// global seqno set to its original value, i.e. 0, and attempt checksum
|
||||
// verification again.
|
||||
if (!retry) {
|
||||
ReadOptions modified_ro = ro;
|
||||
modified_ro.verify_checksums = false;
|
||||
BlockFetcher block_fetcher(
|
||||
file, prefetch_buffer, footer, modified_ro, handle, &block_contents,
|
||||
ioptions, false /* decompress */, false /*maybe_compressed*/,
|
||||
BlockType::kProperties, UncompressionDict::GetEmptyDict(),
|
||||
PersistentCacheOptions::kEmpty, memory_allocator);
|
||||
s = block_fetcher.ReadBlockContents();
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
assert(block_fetcher.GetBlockSizeWithTrailer() == len);
|
||||
TEST_SYNC_POINT_CALLBACK("ReadTablePropertiesHelper:0",
|
||||
&block_contents.data);
|
||||
} else {
|
||||
assert(s.IsCorruption());
|
||||
// If retrying, use a stronger file system read to check and correct
|
||||
// data corruption
|
||||
IOOptions opts;
|
||||
if (PrepareIOFromReadOptions(ro, ioptions.clock, opts) !=
|
||||
IOStatus::OK()) {
|
||||
return s;
|
||||
}
|
||||
opts.verify_and_reconstruct_read = true;
|
||||
std::unique_ptr<char[]> data(new char[len]);
|
||||
Slice result;
|
||||
IOStatus io_s =
|
||||
file->Read(opts, handle.offset(), len, &result, data.get(), nullptr);
|
||||
RecordTick(ioptions.stats, FILE_READ_CORRUPTION_RETRY_COUNT);
|
||||
if (!io_s.ok()) {
|
||||
ROCKS_LOG_INFO(ioptions.info_log,
|
||||
"Reading properties block failed - %s",
|
||||
io_s.ToString().c_str());
|
||||
// Return the original corruption error as that's more serious
|
||||
return s;
|
||||
}
|
||||
if (result.size() < len) {
|
||||
return Status::Corruption("Reading properties block failed - " +
|
||||
std::to_string(result.size()) +
|
||||
" bytes read");
|
||||
}
|
||||
RecordTick(ioptions.stats, FILE_READ_CORRUPTION_RETRY_SUCCESS_COUNT);
|
||||
block_contents = BlockContents(std::move(data), handle.size());
|
||||
}
|
||||
|
||||
auto key = iter->key().ToString();
|
||||
// properties block should be strictly sorted with no duplicate key.
|
||||
if (!last_key.empty() &&
|
||||
BytewiseComparator()->Compare(key, last_key) <= 0) {
|
||||
s = Status::Corruption("properties unsorted");
|
||||
break;
|
||||
}
|
||||
last_key = key;
|
||||
// Unfortunately, Block::size() might not equal block_contents.data.size(),
|
||||
// and Block hides block_contents
|
||||
uint64_t block_size = block_contents.data.size();
|
||||
Block properties_block(std::move(block_contents));
|
||||
std::unique_ptr<MetaBlockIter> iter(properties_block.NewMetaIterator());
|
||||
|
||||
auto raw_val = iter->value();
|
||||
auto pos = predefined_uint64_properties.find(key);
|
||||
std::unique_ptr<TableProperties> new_table_properties{new TableProperties};
|
||||
// All pre-defined properties of type uint64_t
|
||||
std::unordered_map<std::string, uint64_t*> predefined_uint64_properties = {
|
||||
{TablePropertiesNames::kOriginalFileNumber,
|
||||
&new_table_properties->orig_file_number},
|
||||
{TablePropertiesNames::kDataSize, &new_table_properties->data_size},
|
||||
{TablePropertiesNames::kIndexSize, &new_table_properties->index_size},
|
||||
{TablePropertiesNames::kIndexPartitions,
|
||||
&new_table_properties->index_partitions},
|
||||
{TablePropertiesNames::kTopLevelIndexSize,
|
||||
&new_table_properties->top_level_index_size},
|
||||
{TablePropertiesNames::kIndexKeyIsUserKey,
|
||||
&new_table_properties->index_key_is_user_key},
|
||||
{TablePropertiesNames::kIndexValueIsDeltaEncoded,
|
||||
&new_table_properties->index_value_is_delta_encoded},
|
||||
{TablePropertiesNames::kFilterSize, &new_table_properties->filter_size},
|
||||
{TablePropertiesNames::kRawKeySize,
|
||||
&new_table_properties->raw_key_size},
|
||||
{TablePropertiesNames::kRawValueSize,
|
||||
&new_table_properties->raw_value_size},
|
||||
{TablePropertiesNames::kNumDataBlocks,
|
||||
&new_table_properties->num_data_blocks},
|
||||
{TablePropertiesNames::kNumEntries, &new_table_properties->num_entries},
|
||||
{TablePropertiesNames::kNumFilterEntries,
|
||||
&new_table_properties->num_filter_entries},
|
||||
{TablePropertiesNames::kDeletedKeys,
|
||||
&new_table_properties->num_deletions},
|
||||
{TablePropertiesNames::kMergeOperands,
|
||||
&new_table_properties->num_merge_operands},
|
||||
{TablePropertiesNames::kNumRangeDeletions,
|
||||
&new_table_properties->num_range_deletions},
|
||||
{TablePropertiesNames::kFormatVersion,
|
||||
&new_table_properties->format_version},
|
||||
{TablePropertiesNames::kFixedKeyLen,
|
||||
&new_table_properties->fixed_key_len},
|
||||
{TablePropertiesNames::kColumnFamilyId,
|
||||
&new_table_properties->column_family_id},
|
||||
{TablePropertiesNames::kCreationTime,
|
||||
&new_table_properties->creation_time},
|
||||
{TablePropertiesNames::kOldestKeyTime,
|
||||
&new_table_properties->oldest_key_time},
|
||||
{TablePropertiesNames::kFileCreationTime,
|
||||
&new_table_properties->file_creation_time},
|
||||
{TablePropertiesNames::kSlowCompressionEstimatedDataSize,
|
||||
&new_table_properties->slow_compression_estimated_data_size},
|
||||
{TablePropertiesNames::kFastCompressionEstimatedDataSize,
|
||||
&new_table_properties->fast_compression_estimated_data_size},
|
||||
{TablePropertiesNames::kTailStartOffset,
|
||||
&new_table_properties->tail_start_offset},
|
||||
{TablePropertiesNames::kUserDefinedTimestampsPersisted,
|
||||
&new_table_properties->user_defined_timestamps_persisted},
|
||||
};
|
||||
|
||||
if (key == ExternalSstFilePropertyNames::kGlobalSeqno) {
|
||||
new_table_properties->external_sst_file_global_seqno_offset =
|
||||
handle.offset() + iter->ValueOffset();
|
||||
}
|
||||
std::string last_key;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
s = iter->status();
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (pos != predefined_uint64_properties.end()) {
|
||||
if (key == TablePropertiesNames::kDeletedKeys ||
|
||||
key == TablePropertiesNames::kMergeOperands) {
|
||||
// Insert in user-collected properties for API backwards compatibility
|
||||
auto key = iter->key().ToString();
|
||||
// properties block should be strictly sorted with no duplicate key.
|
||||
if (!last_key.empty() &&
|
||||
BytewiseComparator()->Compare(key, last_key) <= 0) {
|
||||
s = Status::Corruption("properties unsorted");
|
||||
break;
|
||||
}
|
||||
last_key = key;
|
||||
|
||||
auto raw_val = iter->value();
|
||||
auto pos = predefined_uint64_properties.find(key);
|
||||
|
||||
if (key == ExternalSstFilePropertyNames::kGlobalSeqno) {
|
||||
new_table_properties->external_sst_file_global_seqno_offset =
|
||||
handle.offset() + iter->ValueOffset();
|
||||
}
|
||||
|
||||
if (pos != predefined_uint64_properties.end()) {
|
||||
if (key == TablePropertiesNames::kDeletedKeys ||
|
||||
key == TablePropertiesNames::kMergeOperands) {
|
||||
// Insert in user-collected properties for API backwards compatibility
|
||||
new_table_properties->user_collected_properties.insert(
|
||||
{key, raw_val.ToString()});
|
||||
}
|
||||
// handle predefined rocksdb properties
|
||||
uint64_t val;
|
||||
if (!GetVarint64(&raw_val, &val)) {
|
||||
// skip malformed value
|
||||
auto error_msg =
|
||||
"Detect malformed value in properties meta-block:"
|
||||
"\tkey: " +
|
||||
key + "\tval: " + raw_val.ToString();
|
||||
ROCKS_LOG_ERROR(ioptions.logger, "%s", error_msg.c_str());
|
||||
continue;
|
||||
}
|
||||
*(pos->second) = val;
|
||||
} else if (key == TablePropertiesNames::kDbId) {
|
||||
new_table_properties->db_id = raw_val.ToString();
|
||||
} else if (key == TablePropertiesNames::kDbSessionId) {
|
||||
new_table_properties->db_session_id = raw_val.ToString();
|
||||
} else if (key == TablePropertiesNames::kDbHostId) {
|
||||
new_table_properties->db_host_id = raw_val.ToString();
|
||||
} else if (key == TablePropertiesNames::kFilterPolicy) {
|
||||
new_table_properties->filter_policy_name = raw_val.ToString();
|
||||
} else if (key == TablePropertiesNames::kColumnFamilyName) {
|
||||
new_table_properties->column_family_name = raw_val.ToString();
|
||||
} else if (key == TablePropertiesNames::kComparator) {
|
||||
new_table_properties->comparator_name = raw_val.ToString();
|
||||
} else if (key == TablePropertiesNames::kMergeOperator) {
|
||||
new_table_properties->merge_operator_name = raw_val.ToString();
|
||||
} else if (key == TablePropertiesNames::kPrefixExtractorName) {
|
||||
new_table_properties->prefix_extractor_name = raw_val.ToString();
|
||||
} else if (key == TablePropertiesNames::kPropertyCollectors) {
|
||||
new_table_properties->property_collectors_names = raw_val.ToString();
|
||||
} else if (key == TablePropertiesNames::kCompression) {
|
||||
new_table_properties->compression_name = raw_val.ToString();
|
||||
} else if (key == TablePropertiesNames::kCompressionOptions) {
|
||||
new_table_properties->compression_options = raw_val.ToString();
|
||||
} else if (key == TablePropertiesNames::kSequenceNumberTimeMapping) {
|
||||
new_table_properties->seqno_to_time_mapping = raw_val.ToString();
|
||||
} else {
|
||||
// handle user-collected properties
|
||||
new_table_properties->user_collected_properties.insert(
|
||||
{key, raw_val.ToString()});
|
||||
}
|
||||
// handle predefined rocksdb properties
|
||||
uint64_t val;
|
||||
if (!GetVarint64(&raw_val, &val)) {
|
||||
// skip malformed value
|
||||
auto error_msg =
|
||||
"Detect malformed value in properties meta-block:"
|
||||
"\tkey: " +
|
||||
key + "\tval: " + raw_val.ToString();
|
||||
ROCKS_LOG_ERROR(ioptions.logger, "%s", error_msg.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Modified version of BlockFetcher checksum verification
|
||||
// (See write_global_seqno comment above)
|
||||
if (s.ok() && footer.GetBlockTrailerSize() > 0) {
|
||||
s = VerifyBlockChecksum(footer, properties_block.data(), block_size,
|
||||
file->file_name(), handle.offset());
|
||||
if (s.IsCorruption()) {
|
||||
if (new_table_properties->external_sst_file_global_seqno_offset != 0) {
|
||||
std::string tmp_buf(properties_block.data(), len);
|
||||
uint64_t global_seqno_offset =
|
||||
new_table_properties->external_sst_file_global_seqno_offset -
|
||||
handle.offset();
|
||||
EncodeFixed64(&tmp_buf[static_cast<size_t>(global_seqno_offset)], 0);
|
||||
s = VerifyBlockChecksum(footer, tmp_buf.data(), block_size,
|
||||
file->file_name(), handle.offset());
|
||||
}
|
||||
}
|
||||
*(pos->second) = val;
|
||||
} else if (key == TablePropertiesNames::kDbId) {
|
||||
new_table_properties->db_id = raw_val.ToString();
|
||||
} else if (key == TablePropertiesNames::kDbSessionId) {
|
||||
new_table_properties->db_session_id = raw_val.ToString();
|
||||
} else if (key == TablePropertiesNames::kDbHostId) {
|
||||
new_table_properties->db_host_id = raw_val.ToString();
|
||||
} else if (key == TablePropertiesNames::kFilterPolicy) {
|
||||
new_table_properties->filter_policy_name = raw_val.ToString();
|
||||
} else if (key == TablePropertiesNames::kColumnFamilyName) {
|
||||
new_table_properties->column_family_name = raw_val.ToString();
|
||||
} else if (key == TablePropertiesNames::kComparator) {
|
||||
new_table_properties->comparator_name = raw_val.ToString();
|
||||
} else if (key == TablePropertiesNames::kMergeOperator) {
|
||||
new_table_properties->merge_operator_name = raw_val.ToString();
|
||||
} else if (key == TablePropertiesNames::kPrefixExtractorName) {
|
||||
new_table_properties->prefix_extractor_name = raw_val.ToString();
|
||||
} else if (key == TablePropertiesNames::kPropertyCollectors) {
|
||||
new_table_properties->property_collectors_names = raw_val.ToString();
|
||||
} else if (key == TablePropertiesNames::kCompression) {
|
||||
new_table_properties->compression_name = raw_val.ToString();
|
||||
} else if (key == TablePropertiesNames::kCompressionOptions) {
|
||||
new_table_properties->compression_options = raw_val.ToString();
|
||||
} else if (key == TablePropertiesNames::kSequenceNumberTimeMapping) {
|
||||
new_table_properties->seqno_to_time_mapping = raw_val.ToString();
|
||||
}
|
||||
|
||||
// If we detected a corruption and the file system supports verification
|
||||
// and reconstruction, retry the read
|
||||
if (s.IsCorruption() && !retry &&
|
||||
CheckFSFeatureSupport(ioptions.fs.get(),
|
||||
FSSupportedOps::kVerifyAndReconstructRead)) {
|
||||
retry = true;
|
||||
} else {
|
||||
// handle user-collected properties
|
||||
new_table_properties->user_collected_properties.insert(
|
||||
{key, raw_val.ToString()});
|
||||
}
|
||||
}
|
||||
|
||||
// Modified version of BlockFetcher checksum verification
|
||||
// (See write_global_seqno comment above)
|
||||
if (s.ok() && footer.GetBlockTrailerSize() > 0) {
|
||||
s = VerifyBlockChecksum(footer, properties_block.data(), block_size,
|
||||
file->file_name(), handle.offset());
|
||||
if (s.IsCorruption()) {
|
||||
if (new_table_properties->external_sst_file_global_seqno_offset != 0) {
|
||||
std::string tmp_buf(properties_block.data(),
|
||||
block_fetcher.GetBlockSizeWithTrailer());
|
||||
uint64_t global_seqno_offset =
|
||||
new_table_properties->external_sst_file_global_seqno_offset -
|
||||
handle.offset();
|
||||
EncodeFixed64(&tmp_buf[static_cast<size_t>(global_seqno_offset)], 0);
|
||||
s = VerifyBlockChecksum(footer, tmp_buf.data(), block_size,
|
||||
file->file_name(), handle.offset());
|
||||
if (s.ok()) {
|
||||
*table_properties = std::move(new_table_properties);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
*table_properties = std::move(new_table_properties);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
* There may be less intra-L0 compaction triggered by total L0 size being too small. We now use compensated file size (tombstones are assigned some value size) when calculating L0 size and reduce the threshold for L0 size limit. This is to avoid accumulating too much data/tombstones in L0.
|
||||
@@ -1 +0,0 @@
|
||||
*Make DestroyDB supports slow deletion when it's configured in `SstFileManager`. The slow deletion is subject to the configured `rate_bytes_per_sec`, but not subject to the `max_trash_db_ratio`.
|
||||
@@ -1 +0,0 @@
|
||||
Fixed a bug where we set unprep_seqs_ even when WriteImpl() fails. This was caught by stress test write fault injection in WriteImpl(). This may have incorrectly caused iteration creation failure for unvalidated writes or returned wrong result for WriteUnpreparedTxn::GetUnpreparedSequenceNumbers().
|
||||
@@ -1 +0,0 @@
|
||||
Fixed a bug where successful write right after error recovery for last failed write finishes causes duplicate WAL entries
|
||||
@@ -1 +0,0 @@
|
||||
Fixed a data race involving the background error status in `unordered_write` mode.
|
||||
@@ -1 +0,0 @@
|
||||
*Fix a bug where file snapshot functions like backup, checkpoint may attempt to copy a non-existing manifest file. #12882
|
||||
@@ -1 +0,0 @@
|
||||
* Fix a bug where per kv checksum corruption may be ignored in MultiGet().
|
||||
@@ -1 +0,0 @@
|
||||
Fix a race condition in pessimistic transactions that could allow multiple transactions with the same name to be registered simultaneously, resulting in a crash or other unpredictable behavior.
|
||||
@@ -1 +0,0 @@
|
||||
*Best efforts recovery supports recovering to incomplete Version with a clean seqno cut that presents a valid point in time view from the user's perspective, if versioning history doesn't include atomic flush.
|
||||
@@ -1 +0,0 @@
|
||||
* New option `BlockBasedTableOptions::decouple_partitioned_filters` should improve efficiency in serving read queries because filter and index partitions can consistently target the configured `metadata_block_size`. This option is currently opt-in.
|
||||
@@ -1 +0,0 @@
|
||||
* Introduce a new mutable CF option `paranoid_memory_checks`. It enables additional validation on data integrity during reads/scanning. Currently, skip list based memtable will validate key ordering during look up and scans.
|
||||
@@ -1,2 +0,0 @@
|
||||
Adds optional installation callback function for remote compaction
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Add ticker stats to count file read retries due to checksum mismatch
|
||||
Reference in New Issue
Block a user