Summary: Allow EventListener::OnCompactionCompleted to return CompactionJobStats, which contains useful information about a compaction. Example CompactionJobStats returned by OnCompactionCompleted(): smallest_output_key_prefix 05000000 largest_output_key_prefix 06990000 elapsed_time 42419 num_input_records 300 num_input_files 3 num_input_files_at_output_level 2 num_output_records 200 num_output_files 1 actual_bytes_input 167200 actual_bytes_output 110688 total_input_raw_key_bytes 5400 total_input_raw_value_bytes 300000 num_records_replaced 100 is_manual_compaction 1 Test Plan: Developed a mega test in db_test which covers 20 variables in CompactionJobStats. Reviewers: rven, igor, anthony, sdong Reviewed By: sdong Subscribers: tnovak, dhruba, leveldb Differential Revision: https://reviews.facebook.net/D38463main
parent
3083ed2129
commit
fe5c6321cb
@ -0,0 +1,691 @@ |
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include <algorithm> |
||||
#include <iostream> |
||||
#include <mutex> |
||||
#include <queue> |
||||
#include <set> |
||||
#include <thread> |
||||
#include <unordered_set> |
||||
#include <utility> |
||||
|
||||
#include "db/db_impl.h" |
||||
#include "db/dbformat.h" |
||||
#include "db/filename.h" |
||||
#include "db/job_context.h" |
||||
#include "db/version_set.h" |
||||
#include "db/write_batch_internal.h" |
||||
#include "port/stack_trace.h" |
||||
#include "rocksdb/cache.h" |
||||
#include "rocksdb/compaction_filter.h" |
||||
#include "rocksdb/db.h" |
||||
#include "rocksdb/env.h" |
||||
#include "rocksdb/experimental.h" |
||||
#include "rocksdb/filter_policy.h" |
||||
#include "rocksdb/options.h" |
||||
#include "rocksdb/perf_context.h" |
||||
#include "rocksdb/slice.h" |
||||
#include "rocksdb/slice_transform.h" |
||||
#include "rocksdb/table.h" |
||||
#include "rocksdb/table_properties.h" |
||||
#include "rocksdb/thread_status.h" |
||||
#include "rocksdb/utilities/checkpoint.h" |
||||
#include "rocksdb/utilities/convenience.h" |
||||
#include "rocksdb/utilities/write_batch_with_index.h" |
||||
#include "table/block_based_table_factory.h" |
||||
#include "table/mock_table.h" |
||||
#include "table/plain_table_factory.h" |
||||
#include "util/compression.h" |
||||
#include "util/hash.h" |
||||
#include "util/hash_linklist_rep.h" |
||||
#include "util/logging.h" |
||||
#include "util/mock_env.h" |
||||
#include "util/mutexlock.h" |
||||
#include "util/rate_limiter.h" |
||||
#include "util/scoped_arena_iterator.h" |
||||
#include "util/statistics.h" |
||||
#include "util/string_util.h" |
||||
#include "util/sync_point.h" |
||||
#include "util/testharness.h" |
||||
#include "util/testutil.h" |
||||
#include "util/thread_status_util.h" |
||||
#include "util/xfunc.h" |
||||
#include "utilities/merge_operators.h" |
||||
|
||||
#if !defined(IOS_CROSS_COMPILE) |
||||
#ifndef ROCKSDB_LITE |
||||
namespace rocksdb { |
||||
|
||||
static std::string RandomString(Random* rnd, int len, double ratio) { |
||||
std::string r; |
||||
test::CompressibleString(rnd, ratio, len, &r); |
||||
return r; |
||||
} |
||||
|
||||
std::string Key(uint64_t key, int length) { |
||||
const int kBufSize = 1000; |
||||
char buf[kBufSize]; |
||||
if (length > kBufSize) { |
||||
length = kBufSize; |
||||
} |
||||
snprintf(buf, kBufSize, "%0*lu", length, key); |
||||
return std::string(buf); |
||||
} |
||||
|
||||
class CompactionJobStatsTest : public testing::Test { |
||||
public: |
||||
std::string dbname_; |
||||
std::string alternative_wal_dir_; |
||||
Env* env_; |
||||
DB* db_; |
||||
std::vector<ColumnFamilyHandle*> handles_; |
||||
|
||||
Options last_options_; |
||||
|
||||
CompactionJobStatsTest() : env_(Env::Default()) { |
||||
env_->SetBackgroundThreads(1, Env::LOW); |
||||
env_->SetBackgroundThreads(1, Env::HIGH); |
||||
dbname_ = test::TmpDir(env_) + "/compaction_job_stats_test"; |
||||
alternative_wal_dir_ = dbname_ + "/wal"; |
||||
Options options; |
||||
options.create_if_missing = true; |
||||
auto delete_options = options; |
||||
delete_options.wal_dir = alternative_wal_dir_; |
||||
EXPECT_OK(DestroyDB(dbname_, delete_options)); |
||||
// Destroy it for not alternative WAL dir is used.
|
||||
EXPECT_OK(DestroyDB(dbname_, options)); |
||||
db_ = nullptr; |
||||
Reopen(options); |
||||
} |
||||
|
||||
~CompactionJobStatsTest() { |
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing(); |
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency({}); |
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks(); |
||||
Close(); |
||||
Options options; |
||||
options.db_paths.emplace_back(dbname_, 0); |
||||
options.db_paths.emplace_back(dbname_ + "_2", 0); |
||||
options.db_paths.emplace_back(dbname_ + "_3", 0); |
||||
options.db_paths.emplace_back(dbname_ + "_4", 0); |
||||
EXPECT_OK(DestroyDB(dbname_, options)); |
||||
} |
||||
|
||||
DBImpl* dbfull() { |
||||
return reinterpret_cast<DBImpl*>(db_); |
||||
} |
||||
|
||||
void CreateColumnFamilies(const std::vector<std::string>& cfs, |
||||
const Options& options) { |
||||
ColumnFamilyOptions cf_opts(options); |
||||
size_t cfi = handles_.size(); |
||||
handles_.resize(cfi + cfs.size()); |
||||
for (auto cf : cfs) { |
||||
ASSERT_OK(db_->CreateColumnFamily(cf_opts, cf, &handles_[cfi++])); |
||||
} |
||||
} |
||||
|
||||
void CreateAndReopenWithCF(const std::vector<std::string>& cfs, |
||||
const Options& options) { |
||||
CreateColumnFamilies(cfs, options); |
||||
std::vector<std::string> cfs_plus_default = cfs; |
||||
cfs_plus_default.insert(cfs_plus_default.begin(), kDefaultColumnFamilyName); |
||||
ReopenWithColumnFamilies(cfs_plus_default, options); |
||||
} |
||||
|
||||
void ReopenWithColumnFamilies(const std::vector<std::string>& cfs, |
||||
const std::vector<Options>& options) { |
||||
ASSERT_OK(TryReopenWithColumnFamilies(cfs, options)); |
||||
} |
||||
|
||||
void ReopenWithColumnFamilies(const std::vector<std::string>& cfs, |
||||
const Options& options) { |
||||
ASSERT_OK(TryReopenWithColumnFamilies(cfs, options)); |
||||
} |
||||
|
||||
Status TryReopenWithColumnFamilies( |
||||
const std::vector<std::string>& cfs, |
||||
const std::vector<Options>& options) { |
||||
Close(); |
||||
EXPECT_EQ(cfs.size(), options.size()); |
||||
std::vector<ColumnFamilyDescriptor> column_families; |
||||
for (size_t i = 0; i < cfs.size(); ++i) { |
||||
column_families.push_back(ColumnFamilyDescriptor(cfs[i], options[i])); |
||||
} |
||||
DBOptions db_opts = DBOptions(options[0]); |
||||
return DB::Open(db_opts, dbname_, column_families, &handles_, &db_); |
||||
} |
||||
|
||||
Status TryReopenWithColumnFamilies(const std::vector<std::string>& cfs, |
||||
const Options& options) { |
||||
Close(); |
||||
std::vector<Options> v_opts(cfs.size(), options); |
||||
return TryReopenWithColumnFamilies(cfs, v_opts); |
||||
} |
||||
|
||||
void Reopen(const Options& options) { |
||||
ASSERT_OK(TryReopen(options)); |
||||
} |
||||
|
||||
void Close() { |
||||
for (auto h : handles_) { |
||||
delete h; |
||||
} |
||||
handles_.clear(); |
||||
delete db_; |
||||
db_ = nullptr; |
||||
} |
||||
|
||||
void DestroyAndReopen(const Options& options) { |
||||
// Destroy using last options
|
||||
Destroy(last_options_); |
||||
ASSERT_OK(TryReopen(options)); |
||||
} |
||||
|
||||
void Destroy(const Options& options) { |
||||
Close(); |
||||
ASSERT_OK(DestroyDB(dbname_, options)); |
||||
} |
||||
|
||||
Status ReadOnlyReopen(const Options& options) { |
||||
return DB::OpenForReadOnly(options, dbname_, &db_); |
||||
} |
||||
|
||||
Status TryReopen(const Options& options) { |
||||
Close(); |
||||
last_options_ = options; |
||||
return DB::Open(options, dbname_, &db_); |
||||
} |
||||
|
||||
Status Flush(int cf = 0) { |
||||
if (cf == 0) { |
||||
return db_->Flush(FlushOptions()); |
||||
} else { |
||||
return db_->Flush(FlushOptions(), handles_[cf]); |
||||
} |
||||
} |
||||
|
||||
Status Put(const Slice& k, const Slice& v, WriteOptions wo = WriteOptions()) { |
||||
return db_->Put(wo, k, v); |
||||
} |
||||
|
||||
Status Put(int cf, const Slice& k, const Slice& v, |
||||
WriteOptions wo = WriteOptions()) { |
||||
return db_->Put(wo, handles_[cf], k, v); |
||||
} |
||||
|
||||
Status Delete(const std::string& k) { |
||||
return db_->Delete(WriteOptions(), k); |
||||
} |
||||
|
||||
Status Delete(int cf, const std::string& k) { |
||||
return db_->Delete(WriteOptions(), handles_[cf], k); |
||||
} |
||||
|
||||
std::string Get(const std::string& k, const Snapshot* snapshot = nullptr) { |
||||
ReadOptions options; |
||||
options.verify_checksums = true; |
||||
options.snapshot = snapshot; |
||||
std::string result; |
||||
Status s = db_->Get(options, k, &result); |
||||
if (s.IsNotFound()) { |
||||
result = "NOT_FOUND"; |
||||
} else if (!s.ok()) { |
||||
result = s.ToString(); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
std::string Get(int cf, const std::string& k, |
||||
const Snapshot* snapshot = nullptr) { |
||||
ReadOptions options; |
||||
options.verify_checksums = true; |
||||
options.snapshot = snapshot; |
||||
std::string result; |
||||
Status s = db_->Get(options, handles_[cf], k, &result); |
||||
if (s.IsNotFound()) { |
||||
result = "NOT_FOUND"; |
||||
} else if (!s.ok()) { |
||||
result = s.ToString(); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
int NumTableFilesAtLevel(int level, int cf = 0) { |
||||
std::string property; |
||||
if (cf == 0) { |
||||
// default cfd
|
||||
EXPECT_TRUE(db_->GetProperty( |
||||
"rocksdb.num-files-at-level" + NumberToString(level), &property)); |
||||
} else { |
||||
EXPECT_TRUE(db_->GetProperty( |
||||
handles_[cf], "rocksdb.num-files-at-level" + NumberToString(level), |
||||
&property)); |
||||
} |
||||
return atoi(property.c_str()); |
||||
} |
||||
|
||||
// Return spread of files per level
|
||||
std::string FilesPerLevel(int cf = 0) { |
||||
int num_levels = |
||||
(cf == 0) ? db_->NumberLevels() : db_->NumberLevels(handles_[1]); |
||||
std::string result; |
||||
size_t last_non_zero_offset = 0; |
||||
for (int level = 0; level < num_levels; level++) { |
||||
int f = NumTableFilesAtLevel(level, cf); |
||||
char buf[100]; |
||||
snprintf(buf, sizeof(buf), "%s%d", (level ? "," : ""), f); |
||||
result += buf; |
||||
if (f > 0) { |
||||
last_non_zero_offset = result.size(); |
||||
} |
||||
} |
||||
result.resize(last_non_zero_offset); |
||||
return result; |
||||
} |
||||
|
||||
uint64_t Size(const Slice& start, const Slice& limit, int cf = 0) { |
||||
Range r(start, limit); |
||||
uint64_t size; |
||||
if (cf == 0) { |
||||
db_->GetApproximateSizes(&r, 1, &size); |
||||
} else { |
||||
db_->GetApproximateSizes(handles_[1], &r, 1, &size); |
||||
} |
||||
return size; |
||||
} |
||||
|
||||
void Compact(int cf, const Slice& start, const Slice& limit, |
||||
uint32_t target_path_id) { |
||||
ASSERT_OK(db_->CompactRange(handles_[cf], &start, &limit, false, -1, |
||||
target_path_id)); |
||||
} |
||||
|
||||
void Compact(int cf, const Slice& start, const Slice& limit) { |
||||
ASSERT_OK(db_->CompactRange(handles_[cf], &start, &limit)); |
||||
} |
||||
|
||||
void Compact(const Slice& start, const Slice& limit) { |
||||
ASSERT_OK(db_->CompactRange(&start, &limit)); |
||||
} |
||||
|
||||
// Do n memtable compactions, each of which produces an sstable
|
||||
// covering the range [small,large].
|
||||
void MakeTables(int n, const std::string& small, const std::string& large, |
||||
int cf = 0) { |
||||
for (int i = 0; i < n; i++) { |
||||
ASSERT_OK(Put(cf, small, "begin")); |
||||
ASSERT_OK(Put(cf, large, "end")); |
||||
ASSERT_OK(Flush(cf)); |
||||
} |
||||
} |
||||
|
||||
void MakeTableWithKeyValues( |
||||
Random* rnd, uint64_t smallest, uint64_t largest, |
||||
int key_size, int value_size, uint64_t interval, |
||||
double ratio, int cf = 0) { |
||||
for (auto key = smallest; key < largest; key += interval) { |
||||
ASSERT_OK(Put(cf, Slice(Key(key, key_size)), |
||||
Slice(RandomString(rnd, value_size, ratio)))); |
||||
} |
||||
ASSERT_OK(Flush(cf)); |
||||
} |
||||
}; |
||||
|
||||
// An EventListener which helps verify the compaction results in
|
||||
// test CompactionJobStatsTest.
|
||||
class CompactionJobStatsChecker : public EventListener { |
||||
public: |
||||
CompactionJobStatsChecker() : compression_enabled_(false) {} |
||||
|
||||
size_t NumberOfUnverifiedStats() { return expected_stats_.size(); } |
||||
|
||||
// Once a compaction completed, this functionw will verify the returned
|
||||
// CompactionJobInfo with the oldest CompactionJobInfo added earlier
|
||||
// in "expected_stats_" which has not yet being used for verification.
|
||||
virtual void OnCompactionCompleted(DB *db, const CompactionJobInfo& ci) { |
||||
std::lock_guard<std::mutex> lock(mutex_); |
||||
|
||||
if (expected_stats_.size()) { |
||||
Verify(ci.stats, expected_stats_.front()); |
||||
expected_stats_.pop(); |
||||
} |
||||
} |
||||
|
||||
// A helper function which verifies whether two CompactionJobStats
|
||||
// match. The verification of all compaction stats are done by
|
||||
// ASSERT_EQ except the following stats, which we use ASSERT_GE
|
||||
// and ASSERT_LE with a reasonable (< 15%) bias:
|
||||
// 1. write-amplication
|
||||
// 2. actual bytes input and output, which relies on the compression
|
||||
// ratio and the implementation of table formats.
|
||||
void Verify(const CompactionJobStats& current_stats, |
||||
const CompactionJobStats& stats) { |
||||
// time
|
||||
ASSERT_GT(current_stats.elapsed_micros, 0U); |
||||
|
||||
ASSERT_EQ(current_stats.num_input_records, |
||||
stats.num_input_records); |
||||
ASSERT_EQ(current_stats.num_input_files, |
||||
stats.num_input_files); |
||||
ASSERT_EQ(current_stats.num_input_files_at_output_level, |
||||
stats.num_input_files_at_output_level); |
||||
|
||||
ASSERT_EQ(current_stats.num_output_records, |
||||
stats.num_output_records); |
||||
ASSERT_EQ(current_stats.num_output_files, |
||||
stats.num_output_files); |
||||
|
||||
ASSERT_EQ(current_stats.is_manual_compaction, |
||||
stats.is_manual_compaction); |
||||
|
||||
// file size
|
||||
double kFileSizeBias = 0.15; |
||||
ASSERT_GE(current_stats.total_input_bytes * (1.00 + kFileSizeBias), |
||||
stats.total_input_bytes); |
||||
ASSERT_LE(current_stats.total_input_bytes, |
||||
stats.total_input_bytes * (1.00 + kFileSizeBias)); |
||||
ASSERT_GE(current_stats.total_output_bytes * (1.00 + kFileSizeBias), |
||||
stats.total_output_bytes); |
||||
ASSERT_LE(current_stats.total_output_bytes, |
||||
stats.total_output_bytes * (1.00 + kFileSizeBias)); |
||||
ASSERT_EQ(current_stats.total_input_raw_key_bytes, |
||||
stats.total_input_raw_key_bytes); |
||||
ASSERT_EQ(current_stats.total_input_raw_value_bytes, |
||||
stats.total_input_raw_value_bytes); |
||||
|
||||
ASSERT_EQ(current_stats.num_records_replaced, |
||||
stats.num_records_replaced); |
||||
|
||||
ASSERT_EQ( |
||||
std::string(current_stats.smallest_output_key_prefix), |
||||
std::string(stats.smallest_output_key_prefix)); |
||||
ASSERT_EQ( |
||||
std::string(current_stats.largest_output_key_prefix), |
||||
std::string(stats.largest_output_key_prefix)); |
||||
} |
||||
|
||||
// Add an expected compaction stats, which will be used to
|
||||
// verify the CompactionJobStats returned by the OnCompactionCompleted()
|
||||
// callback.
|
||||
void AddExpectedStats(const CompactionJobStats& stats) { |
||||
std::lock_guard<std::mutex> lock(mutex_); |
||||
expected_stats_.push(stats); |
||||
} |
||||
|
||||
void EnableCompression(bool flag) { |
||||
compression_enabled_ = flag; |
||||
} |
||||
|
||||
private: |
||||
std::mutex mutex_; |
||||
std::queue<CompactionJobStats> expected_stats_; |
||||
bool compression_enabled_; |
||||
}; |
||||
|
||||
namespace { |
||||
|
||||
uint64_t EstimatedFileSize( |
||||
uint64_t num_records, size_t key_size, size_t value_size, |
||||
double compression_ratio = 1.0, |
||||
size_t block_size = 4096, |
||||
int bloom_bits_per_key = 10) { |
||||
const size_t kPerKeyOverhead = 8; |
||||
const size_t kFooterSize = 512; |
||||
|
||||
uint64_t data_size = |
||||
num_records * (key_size + value_size * compression_ratio + |
||||
kPerKeyOverhead); |
||||
|
||||
return data_size + kFooterSize |
||||
+ num_records * bloom_bits_per_key / 8 // filter block
|
||||
+ data_size * (key_size + 8) / block_size; // index block
|
||||
} |
||||
|
||||
namespace { |
||||
|
||||
void CopyPrefix( |
||||
char* dst, size_t dst_length, const Slice& src) { |
||||
assert(dst_length > 0); |
||||
size_t length = src.size() > dst_length - 1 ? dst_length - 1 : src.size(); |
||||
memcpy(dst, src.data(), length); |
||||
dst[length] = 0; |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
CompactionJobStats NewManualCompactionJobStats( |
||||
const std::string& smallest_key, const std::string& largest_key, |
||||
size_t num_input_files, size_t num_input_files_at_output_level, |
||||
uint64_t num_input_records, size_t key_size, size_t value_size, |
||||
size_t num_output_files, uint64_t num_output_records, |
||||
double compression_ratio, uint64_t num_records_replaced) { |
||||
CompactionJobStats stats; |
||||
stats.Reset(); |
||||
|
||||
stats.num_input_records = num_input_records; |
||||
stats.num_input_files = num_input_files; |
||||
stats.num_input_files_at_output_level = num_input_files_at_output_level; |
||||
|
||||
stats.num_output_records = num_output_records; |
||||
stats.num_output_files = num_output_files; |
||||
|
||||
stats.total_input_bytes = |
||||
EstimatedFileSize( |
||||
num_input_records / num_input_files, |
||||
key_size, value_size, compression_ratio) * num_input_files; |
||||
stats.total_output_bytes = |
||||
EstimatedFileSize( |
||||
num_output_records / num_output_files, |
||||
key_size, value_size, compression_ratio) * num_output_files; |
||||
stats.total_input_raw_key_bytes = |
||||
num_input_records * (key_size + 8); |
||||
stats.total_input_raw_value_bytes = |
||||
num_input_records * value_size; |
||||
|
||||
stats.is_manual_compaction = true; |
||||
|
||||
stats.num_records_replaced = num_records_replaced; |
||||
|
||||
CopyPrefix(stats.smallest_output_key_prefix, |
||||
sizeof(stats.smallest_output_key_prefix), |
||||
smallest_key); |
||||
CopyPrefix(stats.largest_output_key_prefix, |
||||
sizeof(stats.largest_output_key_prefix), |
||||
largest_key); |
||||
|
||||
return stats; |
||||
} |
||||
|
||||
CompressionType GetAnyCompression() { |
||||
if (Snappy_Supported()) { |
||||
return kSnappyCompression; |
||||
} else if (Zlib_Supported()) { |
||||
return kZlibCompression; |
||||
} else if (BZip2_Supported()) { |
||||
return kBZip2Compression; |
||||
} else if (LZ4_Supported()) { |
||||
return kLZ4Compression; |
||||
} |
||||
return kNoCompression; |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
TEST_F(CompactionJobStatsTest, CompactionJobStatsTest) { |
||||
Random rnd(301); |
||||
const int kBufSize = 100; |
||||
char buf[kBufSize]; |
||||
uint64_t key_base = 100000000l; |
||||
// Note: key_base must be multiple of num_keys_per_L0_file
|
||||
int num_keys_per_L0_file = 100; |
||||
const int kTestScale = 8; |
||||
const int kKeySize = 10; |
||||
const int kValueSize = 1000; |
||||
const double kCompressionRatio = 0.5; |
||||
double compression_ratio = 1.0; |
||||
uint64_t key_interval = key_base / num_keys_per_L0_file; |
||||
|
||||
// Whenever a compaction completes, this listener will try to
|
||||
// verify whether the returned CompactionJobStats matches
|
||||
// what we expect. The expected CompactionJobStats is added
|
||||
// via AddExpectedStats().
|
||||
auto* stats_checker = new CompactionJobStatsChecker(); |
||||
Options options; |
||||
options.listeners.emplace_back(stats_checker); |
||||
options.create_if_missing = true; |
||||
options.max_background_flushes = 0; |
||||
options.max_mem_compaction_level = 0; |
||||
// just enough setting to hold off auto-compaction.
|
||||
options.level0_file_num_compaction_trigger = kTestScale + 1; |
||||
options.num_levels = 3; |
||||
options.compression = kNoCompression; |
||||
|
||||
for (int test = 0; test < 2; ++test) { |
||||
DestroyAndReopen(options); |
||||
CreateAndReopenWithCF({"pikachu"}, options); |
||||
|
||||
// 1st Phase: generate "num_L0_files" L0 files.
|
||||
int num_L0_files = 0; |
||||
for (uint64_t start_key = key_base; |
||||
start_key <= key_base * kTestScale; |
||||
start_key += key_base) { |
||||
MakeTableWithKeyValues( |
||||
&rnd, start_key, start_key + key_base - 1, |
||||
kKeySize, kValueSize, key_interval, |
||||
kCompressionRatio, 1); |
||||
snprintf(buf, kBufSize, "%d", ++num_L0_files); |
||||
ASSERT_EQ(std::string(buf), FilesPerLevel(1)); |
||||
} |
||||
ASSERT_EQ(std::to_string(num_L0_files), FilesPerLevel(1)); |
||||
|
||||
// 2nd Phase: perform L0 -> L1 compaction.
|
||||
int L0_compaction_count = 6; |
||||
int count = 1; |
||||
std::string smallest_key; |
||||
std::string largest_key; |
||||
for (uint64_t start_key = key_base; |
||||
start_key <= key_base * L0_compaction_count; |
||||
start_key += key_base, count++) { |
||||
smallest_key = Key(start_key, 10); |
||||
largest_key = Key(start_key + key_base - key_interval, 10); |
||||
stats_checker->AddExpectedStats( |
||||
NewManualCompactionJobStats( |
||||
smallest_key, largest_key, |
||||
1, 0, num_keys_per_L0_file, |
||||
kKeySize, kValueSize, |
||||
1, num_keys_per_L0_file, |
||||
compression_ratio, 0)); |
||||
ASSERT_EQ(stats_checker->NumberOfUnverifiedStats(), 1U); |
||||
Compact(1, smallest_key, largest_key); |
||||
snprintf(buf, kBufSize, "%d,%d", num_L0_files - count, count); |
||||
ASSERT_EQ(std::string(buf), FilesPerLevel(1)); |
||||
} |
||||
|
||||
// compact two files into one in the last L0 -> L1 compaction
|
||||
int num_remaining_L0 = num_L0_files - L0_compaction_count; |
||||
smallest_key = Key(key_base * (L0_compaction_count + 1), 10); |
||||
largest_key = Key(key_base * (kTestScale + 1) - key_interval, 10); |
||||
stats_checker->AddExpectedStats( |
||||
NewManualCompactionJobStats( |
||||
smallest_key, largest_key, |
||||
num_remaining_L0, |
||||
0, num_keys_per_L0_file * num_remaining_L0, |
||||
kKeySize, kValueSize, |
||||
1, num_keys_per_L0_file * num_remaining_L0, |
||||
compression_ratio, 0)); |
||||
ASSERT_EQ(stats_checker->NumberOfUnverifiedStats(), 1U); |
||||
Compact(1, smallest_key, largest_key); |
||||
|
||||
int num_L1_files = num_L0_files - num_remaining_L0 + 1; |
||||
num_L0_files = 0; |
||||
snprintf(buf, kBufSize, "%d,%d", num_L0_files, num_L1_files); |
||||
ASSERT_EQ(std::string(buf), FilesPerLevel(1)); |
||||
|
||||
// 3rd Phase: generate sparse L0 files (wider key-range, same num of keys)
|
||||
int sparseness = 2; |
||||
for (uint64_t start_key = key_base; |
||||
start_key <= key_base * kTestScale; |
||||
start_key += key_base * sparseness) { |
||||
MakeTableWithKeyValues( |
||||
&rnd, start_key, start_key + key_base * sparseness - 1, |
||||
kKeySize, kValueSize, |
||||
key_base * sparseness / num_keys_per_L0_file, |
||||
kCompressionRatio, 1); |
||||
snprintf(buf, kBufSize, "%d,%d", ++num_L0_files, num_L1_files); |
||||
ASSERT_EQ(std::string(buf), FilesPerLevel(1)); |
||||
} |
||||
|
||||
// 4th Phase: perform L0 -> L1 compaction again, expect higher write amp
|
||||
for (uint64_t start_key = key_base; |
||||
num_L0_files > 1; |
||||
start_key += key_base * sparseness) { |
||||
smallest_key = Key(start_key, 10); |
||||
largest_key = |
||||
Key(start_key + key_base * sparseness - key_interval, 10); |
||||
stats_checker->AddExpectedStats( |
||||
NewManualCompactionJobStats( |
||||
smallest_key, largest_key, |
||||
3, 2, num_keys_per_L0_file * 3, |
||||
kKeySize, kValueSize, |
||||
1, num_keys_per_L0_file * 2, // 1/3 of the data will be updated.
|
||||
compression_ratio, |
||||
num_keys_per_L0_file)); |
||||
ASSERT_EQ(stats_checker->NumberOfUnverifiedStats(), 1U); |
||||
Compact(1, smallest_key, largest_key); |
||||
snprintf(buf, kBufSize, "%d,%d", |
||||
--num_L0_files, --num_L1_files); |
||||
ASSERT_EQ(std::string(buf), FilesPerLevel(1)); |
||||
} |
||||
|
||||
// 5th Phase: Do a full compaction, which involves in two sub-compactions.
|
||||
// Here we expect to have 1 L0 files and 4 L1 files
|
||||
// In the first sub-compaction, we expect L0 compaction.
|
||||
smallest_key = Key(key_base, 10); |
||||
largest_key = Key(key_base * (kTestScale + 1) - key_interval, 10); |
||||
stats_checker->AddExpectedStats( |
||||
NewManualCompactionJobStats( |
||||
Key(key_base * (kTestScale + 1 - sparseness), 10), largest_key, |
||||
2, 1, num_keys_per_L0_file * 3, |
||||
kKeySize, kValueSize, |
||||
1, num_keys_per_L0_file * 2, |
||||
compression_ratio, |
||||
num_keys_per_L0_file)); |
||||
// In the second sub-compaction, we expect L1 compaction.
|
||||
stats_checker->AddExpectedStats( |
||||
NewManualCompactionJobStats( |
||||
smallest_key, largest_key, |
||||
4, 0, num_keys_per_L0_file * 8, |
||||
kKeySize, kValueSize, |
||||
1, num_keys_per_L0_file * 8, |
||||
compression_ratio, 0)); |
||||
ASSERT_EQ(stats_checker->NumberOfUnverifiedStats(), 2U); |
||||
Compact(1, smallest_key, largest_key); |
||||
ASSERT_EQ("0,1", FilesPerLevel(1)); |
||||
options.compression = GetAnyCompression(); |
||||
if (options.compression == kNoCompression) { |
||||
break; |
||||
} |
||||
stats_checker->EnableCompression(true); |
||||
compression_ratio = kCompressionRatio; |
||||
} |
||||
ASSERT_EQ(stats_checker->NumberOfUnverifiedStats(), 0U); |
||||
} |
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) { |
||||
rocksdb::port::InstallStackTraceHandler(); |
||||
::testing::InitGoogleTest(&argc, argv); |
||||
return RUN_ALL_TESTS(); |
||||
} |
||||
|
||||
#endif // !ROCKSDB_LITE
|
||||
#endif // !defined(IOS_CROSS_COMPILE)
|
@ -0,0 +1,51 @@ |
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#pragma once |
||||
#include <stddef.h> |
||||
#include <stdint.h> |
||||
|
||||
namespace rocksdb { |
||||
struct CompactionJobStats { |
||||
CompactionJobStats() { Reset(); } |
||||
void Reset(); |
||||
|
||||
// the elapsed time in micro of this compaction.
|
||||
uint64_t elapsed_micros; |
||||
|
||||
// the number of compaction input records.
|
||||
uint64_t num_input_records; |
||||
// the number of compaction input files.
|
||||
size_t num_input_files; |
||||
// the number of compaction input files at the output level.
|
||||
size_t num_input_files_at_output_level; |
||||
|
||||
// the number of compaction output records.
|
||||
uint64_t num_output_records; |
||||
// the number of compaction output files.
|
||||
size_t num_output_files; |
||||
|
||||
// true if the compaction is a manual compaction
|
||||
bool is_manual_compaction; |
||||
|
||||
// the size of the compaction input in bytes.
|
||||
uint64_t total_input_bytes; |
||||
// the size of the compaction output in bytes.
|
||||
uint64_t total_output_bytes; |
||||
|
||||
// number of records being replaced by newer record associated with same key
|
||||
uint64_t num_records_replaced; |
||||
|
||||
// the sum of the uncompressed input keys in bytes.
|
||||
uint64_t total_input_raw_key_bytes; |
||||
// the sum of the uncompressed input values in bytes.
|
||||
uint64_t total_input_raw_value_bytes; |
||||
|
||||
// 0-terminated strings storing the first 8 bytes of the smallest and
|
||||
// largest key in the output.
|
||||
char smallest_output_key_prefix[9]; |
||||
char largest_output_key_prefix[9]; |
||||
}; |
||||
} // namespace rocksdb
|
@ -0,0 +1,44 @@ |
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#include <cstring> |
||||
#include "include/rocksdb/compaction_job_stats.h" |
||||
|
||||
namespace rocksdb { |
||||
|
||||
#ifndef ROCKSDB_LITE |
||||
|
||||
void CompactionJobStats::Reset() { |
||||
elapsed_micros = 0; |
||||
|
||||
num_input_files = 0; |
||||
num_input_files_at_output_level = 0; |
||||
num_output_files = 0; |
||||
|
||||
num_input_records = 0; |
||||
num_output_records = 0; |
||||
|
||||
total_input_bytes = 0; |
||||
total_output_bytes = 0; |
||||
|
||||
total_input_raw_key_bytes = 0; |
||||
total_input_raw_value_bytes = 0; |
||||
|
||||
num_records_replaced = 0; |
||||
|
||||
is_manual_compaction = 0; |
||||
|
||||
smallest_output_key_prefix[0] = 0; |
||||
largest_output_key_prefix[0] = 0; |
||||
} |
||||
|
||||
#else |
||||
|
||||
void CompactionJobStats::Reset() { |
||||
} |
||||
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
Loading…
Reference in new issue