Add remote compaction public API (#8300)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/8300 Reviewed By: ajkr Differential Revision: D28464726 Pulled By: jay-zhuang fbshipit-source-id: 49e9f4fb791808a6cbf39a7b1a331373f645fc5emain
parent
311a544c2a
commit
3786181a90
@ -0,0 +1,454 @@ |
|||||||
|
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under both the GPLv2 (found in the
|
||||||
|
// COPYING file in the root directory) and Apache 2.0 License
|
||||||
|
// (found in the LICENSE.Apache file in the root directory).
|
||||||
|
|
||||||
|
#ifndef ROCKSDB_LITE |
||||||
|
|
||||||
|
#include "db/db_test_util.h" |
||||||
|
#include "port/stack_trace.h" |
||||||
|
|
||||||
|
namespace ROCKSDB_NAMESPACE { |
||||||
|
|
||||||
|
class MyTestCompactionService : public CompactionService { |
||||||
|
public: |
||||||
|
MyTestCompactionService(const std::string& db_path, |
||||||
|
std::shared_ptr<FileSystem> fs, Options& options) |
||||||
|
: db_path_(db_path), fs_(fs), options_(options) {} |
||||||
|
|
||||||
|
CompactionServiceJobStatus Start(const std::string& compaction_service_input, |
||||||
|
int job_id) override { |
||||||
|
InstrumentedMutexLock l(&mutex_); |
||||||
|
jobs_.emplace(job_id, compaction_service_input); |
||||||
|
CompactionServiceJobStatus s = CompactionServiceJobStatus::kSuccess; |
||||||
|
TEST_SYNC_POINT_CALLBACK("MyTestCompactionService::Start::End", &s); |
||||||
|
return s; |
||||||
|
} |
||||||
|
|
||||||
|
CompactionServiceJobStatus WaitForComplete( |
||||||
|
int job_id, std::string* compaction_service_result) override { |
||||||
|
std::string compaction_input; |
||||||
|
{ |
||||||
|
InstrumentedMutexLock l(&mutex_); |
||||||
|
auto i = jobs_.find(job_id); |
||||||
|
if (i == jobs_.end()) { |
||||||
|
return CompactionServiceJobStatus::kFailure; |
||||||
|
} |
||||||
|
compaction_input = std::move(i->second); |
||||||
|
jobs_.erase(i); |
||||||
|
} |
||||||
|
|
||||||
|
CompactionServiceOptionsOverride options_override; |
||||||
|
options_override.env = options_.env; |
||||||
|
options_override.file_checksum_gen_factory = |
||||||
|
options_.file_checksum_gen_factory; |
||||||
|
options_override.comparator = options_.comparator; |
||||||
|
options_override.merge_operator = options_.merge_operator; |
||||||
|
options_override.compaction_filter = options_.compaction_filter; |
||||||
|
options_override.compaction_filter_factory = |
||||||
|
options_.compaction_filter_factory; |
||||||
|
options_override.prefix_extractor = options_.prefix_extractor; |
||||||
|
options_override.table_factory = options_.table_factory; |
||||||
|
options_override.sst_partitioner_factory = options_.sst_partitioner_factory; |
||||||
|
|
||||||
|
Status s = DB::OpenAndCompact(db_path_, db_path_ + "/" + ToString(job_id), |
||||||
|
compaction_input, compaction_service_result, |
||||||
|
options_override); |
||||||
|
TEST_SYNC_POINT_CALLBACK("MyTestCompactionService::WaitForComplete::End", |
||||||
|
compaction_service_result); |
||||||
|
compaction_num_.fetch_add(1); |
||||||
|
if (s.ok()) { |
||||||
|
return CompactionServiceJobStatus::kSuccess; |
||||||
|
} else { |
||||||
|
return CompactionServiceJobStatus::kFailure; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
int GetCompactionNum() { return compaction_num_.load(); } |
||||||
|
|
||||||
|
private: |
||||||
|
InstrumentedMutex mutex_; |
||||||
|
std::atomic_int compaction_num_{0}; |
||||||
|
std::map<int, std::string> jobs_; |
||||||
|
const std::string db_path_; |
||||||
|
std::shared_ptr<FileSystem> fs_; |
||||||
|
Options options_; |
||||||
|
}; |
||||||
|
|
||||||
|
class CompactionServiceTest : public DBTestBase { |
||||||
|
public: |
||||||
|
explicit CompactionServiceTest() |
||||||
|
: DBTestBase("compaction_service_test", true) {} |
||||||
|
|
||||||
|
protected: |
||||||
|
void GenerateTestData() { |
||||||
|
// Generate 20 files @ L2
|
||||||
|
for (int i = 0; i < 20; i++) { |
||||||
|
for (int j = 0; j < 10; j++) { |
||||||
|
int key_id = i * 10 + j; |
||||||
|
ASSERT_OK(Put(Key(key_id), "value" + ToString(key_id))); |
||||||
|
} |
||||||
|
ASSERT_OK(Flush()); |
||||||
|
} |
||||||
|
MoveFilesToLevel(2); |
||||||
|
|
||||||
|
// Generate 10 files @ L1 overlap with all 20 files @ L2
|
||||||
|
for (int i = 0; i < 10; i++) { |
||||||
|
for (int j = 0; j < 10; j++) { |
||||||
|
int key_id = i * 20 + j * 2; |
||||||
|
ASSERT_OK(Put(Key(key_id), "value_new" + ToString(key_id))); |
||||||
|
} |
||||||
|
ASSERT_OK(Flush()); |
||||||
|
} |
||||||
|
MoveFilesToLevel(1); |
||||||
|
ASSERT_EQ(FilesPerLevel(), "0,10,20"); |
||||||
|
} |
||||||
|
|
||||||
|
void VerifyTestData() { |
||||||
|
for (int i = 0; i < 200; i++) { |
||||||
|
auto result = Get(Key(i)); |
||||||
|
if (i % 2) { |
||||||
|
ASSERT_EQ(result, "value" + ToString(i)); |
||||||
|
} else { |
||||||
|
ASSERT_EQ(result, "value_new" + ToString(i)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
TEST_F(CompactionServiceTest, BasicCompactions) { |
||||||
|
Options options = CurrentOptions(); |
||||||
|
options.env = env_; |
||||||
|
options.compaction_service = std::make_shared<MyTestCompactionService>( |
||||||
|
dbname_, env_->GetFileSystem(), options); |
||||||
|
|
||||||
|
DestroyAndReopen(options); |
||||||
|
|
||||||
|
for (int i = 0; i < 20; i++) { |
||||||
|
for (int j = 0; j < 10; j++) { |
||||||
|
int key_id = i * 10 + j; |
||||||
|
ASSERT_OK(Put(Key(key_id), "value" + ToString(key_id))); |
||||||
|
} |
||||||
|
ASSERT_OK(Flush()); |
||||||
|
} |
||||||
|
|
||||||
|
for (int i = 0; i < 10; i++) { |
||||||
|
for (int j = 0; j < 10; j++) { |
||||||
|
int key_id = i * 20 + j * 2; |
||||||
|
ASSERT_OK(Put(Key(key_id), "value_new" + ToString(key_id))); |
||||||
|
} |
||||||
|
ASSERT_OK(Flush()); |
||||||
|
} |
||||||
|
ASSERT_OK(dbfull()->TEST_WaitForCompact()); |
||||||
|
|
||||||
|
// verify result
|
||||||
|
for (int i = 0; i < 200; i++) { |
||||||
|
auto result = Get(Key(i)); |
||||||
|
if (i % 2) { |
||||||
|
ASSERT_EQ(result, "value" + ToString(i)); |
||||||
|
} else { |
||||||
|
ASSERT_EQ(result, "value_new" + ToString(i)); |
||||||
|
} |
||||||
|
} |
||||||
|
auto my_cs = |
||||||
|
dynamic_cast<MyTestCompactionService*>(options.compaction_service.get()); |
||||||
|
ASSERT_GE(my_cs->GetCompactionNum(), 1); |
||||||
|
|
||||||
|
// Test failed compaction
|
||||||
|
SyncPoint::GetInstance()->SetCallBack( |
||||||
|
"DBImplSecondary::CompactWithoutInstallation::End", [&](void* status) { |
||||||
|
// override job status
|
||||||
|
Status* s = static_cast<Status*>(status); |
||||||
|
*s = Status::Aborted("MyTestCompactionService failed to compact!"); |
||||||
|
}); |
||||||
|
SyncPoint::GetInstance()->EnableProcessing(); |
||||||
|
|
||||||
|
Status s; |
||||||
|
for (int i = 0; i < 10; i++) { |
||||||
|
for (int j = 0; j < 10; j++) { |
||||||
|
int key_id = i * 20 + j * 2; |
||||||
|
s = Put(Key(key_id), "value_new" + ToString(key_id)); |
||||||
|
if (s.IsAborted()) { |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
if (s.IsAborted()) { |
||||||
|
break; |
||||||
|
} |
||||||
|
s = Flush(); |
||||||
|
if (s.IsAborted()) { |
||||||
|
break; |
||||||
|
} |
||||||
|
s = dbfull()->TEST_WaitForCompact(); |
||||||
|
if (s.IsAborted()) { |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
ASSERT_TRUE(s.IsAborted()); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_F(CompactionServiceTest, ManualCompaction) { |
||||||
|
Options options = CurrentOptions(); |
||||||
|
options.env = env_; |
||||||
|
options.disable_auto_compactions = true; |
||||||
|
options.compaction_service = std::make_shared<MyTestCompactionService>( |
||||||
|
dbname_, env_->GetFileSystem(), options); |
||||||
|
DestroyAndReopen(options); |
||||||
|
GenerateTestData(); |
||||||
|
|
||||||
|
auto my_cs = |
||||||
|
dynamic_cast<MyTestCompactionService*>(options.compaction_service.get()); |
||||||
|
|
||||||
|
std::string start_str = Key(15); |
||||||
|
std::string end_str = Key(45); |
||||||
|
Slice start(start_str); |
||||||
|
Slice end(end_str); |
||||||
|
uint64_t comp_num = my_cs->GetCompactionNum(); |
||||||
|
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &start, &end)); |
||||||
|
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1); |
||||||
|
VerifyTestData(); |
||||||
|
|
||||||
|
start_str = Key(120); |
||||||
|
start = start_str; |
||||||
|
comp_num = my_cs->GetCompactionNum(); |
||||||
|
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &start, nullptr)); |
||||||
|
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1); |
||||||
|
VerifyTestData(); |
||||||
|
|
||||||
|
end_str = Key(92); |
||||||
|
end = end_str; |
||||||
|
comp_num = my_cs->GetCompactionNum(); |
||||||
|
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, &end)); |
||||||
|
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1); |
||||||
|
VerifyTestData(); |
||||||
|
|
||||||
|
comp_num = my_cs->GetCompactionNum(); |
||||||
|
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); |
||||||
|
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1); |
||||||
|
VerifyTestData(); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_F(CompactionServiceTest, FailedToStart) { |
||||||
|
Options options = CurrentOptions(); |
||||||
|
options.env = env_; |
||||||
|
options.disable_auto_compactions = true; |
||||||
|
options.compaction_service = std::make_shared<MyTestCompactionService>( |
||||||
|
dbname_, env_->GetFileSystem(), options); |
||||||
|
DestroyAndReopen(options); |
||||||
|
GenerateTestData(); |
||||||
|
|
||||||
|
SyncPoint::GetInstance()->SetCallBack( |
||||||
|
"MyTestCompactionService::Start::End", [&](void* status) { |
||||||
|
// override job status
|
||||||
|
auto s = static_cast<CompactionServiceJobStatus*>(status); |
||||||
|
*s = CompactionServiceJobStatus::kFailure; |
||||||
|
}); |
||||||
|
SyncPoint::GetInstance()->EnableProcessing(); |
||||||
|
|
||||||
|
std::string start_str = Key(15); |
||||||
|
std::string end_str = Key(45); |
||||||
|
Slice start(start_str); |
||||||
|
Slice end(end_str); |
||||||
|
Status s = db_->CompactRange(CompactRangeOptions(), &start, &end); |
||||||
|
ASSERT_TRUE(s.IsIncomplete()); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_F(CompactionServiceTest, InvalidResult) { |
||||||
|
Options options = CurrentOptions(); |
||||||
|
options.env = env_; |
||||||
|
options.disable_auto_compactions = true; |
||||||
|
options.compaction_service = std::make_shared<MyTestCompactionService>( |
||||||
|
dbname_, env_->GetFileSystem(), options); |
||||||
|
DestroyAndReopen(options); |
||||||
|
GenerateTestData(); |
||||||
|
|
||||||
|
SyncPoint::GetInstance()->SetCallBack( |
||||||
|
"MyTestCompactionService::WaitForComplete::End", [&](void* result) { |
||||||
|
// override job status
|
||||||
|
auto result_str = static_cast<std::string*>(result); |
||||||
|
*result_str = "Invalid Str"; |
||||||
|
}); |
||||||
|
SyncPoint::GetInstance()->EnableProcessing(); |
||||||
|
|
||||||
|
std::string start_str = Key(15); |
||||||
|
std::string end_str = Key(45); |
||||||
|
Slice start(start_str); |
||||||
|
Slice end(end_str); |
||||||
|
Status s = db_->CompactRange(CompactRangeOptions(), &start, &end); |
||||||
|
ASSERT_FALSE(s.ok()); |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: support sub-compaction
|
||||||
|
TEST_F(CompactionServiceTest, DISABLED_SubCompaction) { |
||||||
|
Options options = CurrentOptions(); |
||||||
|
options.env = env_; |
||||||
|
options.max_subcompactions = 10; |
||||||
|
options.target_file_size_base = 1 << 10; // 1KB
|
||||||
|
options.disable_auto_compactions = true; |
||||||
|
options.compaction_service = std::make_shared<MyTestCompactionService>( |
||||||
|
dbname_, env_->GetFileSystem(), options); |
||||||
|
|
||||||
|
DestroyAndReopen(options); |
||||||
|
GenerateTestData(); |
||||||
|
|
||||||
|
auto cro = CompactRangeOptions(); |
||||||
|
cro.max_subcompactions = 10; |
||||||
|
db_->CompactRange(cro, nullptr, nullptr); |
||||||
|
} |
||||||
|
|
||||||
|
class PartialDeleteCompactionFilter : public CompactionFilter { |
||||||
|
public: |
||||||
|
CompactionFilter::Decision FilterV2( |
||||||
|
int /*level*/, const Slice& key, ValueType /*value_type*/, |
||||||
|
const Slice& /*existing_value*/, std::string* /*new_value*/, |
||||||
|
std::string* /*skip_until*/) const override { |
||||||
|
int i = std::stoi(key.ToString().substr(3)); |
||||||
|
if (i > 5 && i <= 105) { |
||||||
|
return CompactionFilter::Decision::kRemove; |
||||||
|
} |
||||||
|
return CompactionFilter::Decision::kKeep; |
||||||
|
} |
||||||
|
|
||||||
|
const char* Name() const override { return "PartialDeleteCompactionFilter"; } |
||||||
|
}; |
||||||
|
|
||||||
|
TEST_F(CompactionServiceTest, CompactionFilter) { |
||||||
|
Options options = CurrentOptions(); |
||||||
|
options.env = env_; |
||||||
|
auto delete_comp_filter = PartialDeleteCompactionFilter(); |
||||||
|
options.compaction_filter = &delete_comp_filter; |
||||||
|
options.compaction_service = std::make_shared<MyTestCompactionService>( |
||||||
|
dbname_, env_->GetFileSystem(), options); |
||||||
|
|
||||||
|
DestroyAndReopen(options); |
||||||
|
|
||||||
|
for (int i = 0; i < 20; i++) { |
||||||
|
for (int j = 0; j < 10; j++) { |
||||||
|
int key_id = i * 10 + j; |
||||||
|
ASSERT_OK(Put(Key(key_id), "value" + ToString(key_id))); |
||||||
|
} |
||||||
|
ASSERT_OK(Flush()); |
||||||
|
} |
||||||
|
|
||||||
|
for (int i = 0; i < 10; i++) { |
||||||
|
for (int j = 0; j < 10; j++) { |
||||||
|
int key_id = i * 20 + j * 2; |
||||||
|
ASSERT_OK(Put(Key(key_id), "value_new" + ToString(key_id))); |
||||||
|
} |
||||||
|
ASSERT_OK(Flush()); |
||||||
|
} |
||||||
|
ASSERT_OK(dbfull()->TEST_WaitForCompact()); |
||||||
|
|
||||||
|
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); |
||||||
|
|
||||||
|
// verify result
|
||||||
|
for (int i = 0; i < 200; i++) { |
||||||
|
auto result = Get(Key(i)); |
||||||
|
if (i > 5 && i <= 105) { |
||||||
|
ASSERT_EQ(result, "NOT_FOUND"); |
||||||
|
} else if (i % 2) { |
||||||
|
ASSERT_EQ(result, "value" + ToString(i)); |
||||||
|
} else { |
||||||
|
ASSERT_EQ(result, "value_new" + ToString(i)); |
||||||
|
} |
||||||
|
} |
||||||
|
auto my_cs = |
||||||
|
dynamic_cast<MyTestCompactionService*>(options.compaction_service.get()); |
||||||
|
ASSERT_GE(my_cs->GetCompactionNum(), 1); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_F(CompactionServiceTest, Snapshot) { |
||||||
|
Options options = CurrentOptions(); |
||||||
|
options.env = env_; |
||||||
|
options.compaction_service = std::make_shared<MyTestCompactionService>( |
||||||
|
dbname_, env_->GetFileSystem(), options); |
||||||
|
|
||||||
|
DestroyAndReopen(options); |
||||||
|
|
||||||
|
ASSERT_OK(Put(Key(1), "value1")); |
||||||
|
ASSERT_OK(Put(Key(2), "value1")); |
||||||
|
const Snapshot* s1 = db_->GetSnapshot(); |
||||||
|
ASSERT_OK(Flush()); |
||||||
|
|
||||||
|
ASSERT_OK(Put(Key(1), "value2")); |
||||||
|
ASSERT_OK(Put(Key(3), "value2")); |
||||||
|
ASSERT_OK(Flush()); |
||||||
|
|
||||||
|
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); |
||||||
|
auto my_cs = |
||||||
|
dynamic_cast<MyTestCompactionService*>(options.compaction_service.get()); |
||||||
|
ASSERT_GE(my_cs->GetCompactionNum(), 1); |
||||||
|
ASSERT_EQ("value1", Get(Key(1), s1)); |
||||||
|
ASSERT_EQ("value2", Get(Key(1))); |
||||||
|
db_->ReleaseSnapshot(s1); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_F(CompactionServiceTest, ConcurrentCompaction) { |
||||||
|
Options options = CurrentOptions(); |
||||||
|
options.level0_file_num_compaction_trigger = 100; |
||||||
|
options.env = env_; |
||||||
|
options.compaction_service = std::make_shared<MyTestCompactionService>( |
||||||
|
dbname_, env_->GetFileSystem(), options); |
||||||
|
options.max_background_jobs = 20; |
||||||
|
|
||||||
|
DestroyAndReopen(options); |
||||||
|
GenerateTestData(); |
||||||
|
|
||||||
|
ColumnFamilyMetaData meta; |
||||||
|
db_->GetColumnFamilyMetaData(&meta); |
||||||
|
|
||||||
|
std::vector<std::thread> threads; |
||||||
|
for (const auto& file : meta.levels[1].files) { |
||||||
|
threads.push_back(std::thread([&]() { |
||||||
|
std::string fname = file.db_path + "/" + file.name; |
||||||
|
ASSERT_OK(db_->CompactFiles(CompactionOptions(), {fname}, 2)); |
||||||
|
})); |
||||||
|
} |
||||||
|
|
||||||
|
for (auto& thread : threads) { |
||||||
|
thread.join(); |
||||||
|
} |
||||||
|
ASSERT_OK(dbfull()->TEST_WaitForCompact()); |
||||||
|
|
||||||
|
// verify result
|
||||||
|
for (int i = 0; i < 200; i++) { |
||||||
|
auto result = Get(Key(i)); |
||||||
|
if (i % 2) { |
||||||
|
ASSERT_EQ(result, "value" + ToString(i)); |
||||||
|
} else { |
||||||
|
ASSERT_EQ(result, "value_new" + ToString(i)); |
||||||
|
} |
||||||
|
} |
||||||
|
auto my_cs = |
||||||
|
dynamic_cast<MyTestCompactionService*>(options.compaction_service.get()); |
||||||
|
ASSERT_EQ(my_cs->GetCompactionNum(), 10); |
||||||
|
ASSERT_EQ(FilesPerLevel(), "0,0,10"); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace ROCKSDB_NAMESPACE
|
||||||
|
|
||||||
|
#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS |
||||||
|
extern "C" { |
||||||
|
void RegisterCustomObjects(int argc, char** argv); |
||||||
|
} |
||||||
|
#else |
||||||
|
void RegisterCustomObjects(int /*argc*/, char** /*argv*/) {} |
||||||
|
#endif // !ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||||
|
|
||||||
|
int main(int argc, char** argv) { |
||||||
|
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); |
||||||
|
::testing::InitGoogleTest(&argc, argv); |
||||||
|
RegisterCustomObjects(argc, argv); |
||||||
|
return RUN_ALL_TESTS(); |
||||||
|
} |
||||||
|
|
||||||
|
#else |
||||||
|
#include <stdio.h> |
||||||
|
|
||||||
|
int main(int /*argc*/, char** /*argv*/) { |
||||||
|
fprintf(stderr, |
||||||
|
"SKIPPED as CompactionService is not supported in ROCKSDB_LITE\n"); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
#endif // ROCKSDB_LITE
|
Loading…
Reference in new issue