Add a blob file builder class that can be used in background jobs (#7306)
Summary: The patch adds a class called `BlobFileBuilder` that can be used to build and cut blob files in background jobs (flushes/compactions). The class enforces a value size threshold (`min_blob_size`; smaller blobs will be inlined in the LSM tree itself), and supports specifying a blob file size limit (`blob_file_size`), as well as compression (`blob_compression_type`) and checksums for blob files. It also keeps track of the generated blob files and their associated `BlobFileAddition` metadata, which can be applied as part of the background job's `VersionEdit`. Pull Request resolved: https://github.com/facebook/rocksdb/pull/7306 Test Plan: `make check` Reviewed By: riversand963 Differential Revision: D23298817 Pulled By: ltamasi fbshipit-source-id: 38f35d81dab1ba81f15236240612ec173d7f21b5main
parent
8e0df9050c
commit
5043960623
@ -0,0 +1,291 @@ |
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/blob/blob_file_builder.h" |
||||
|
||||
#include <cassert> |
||||
|
||||
#include "db/blob/blob_file_addition.h" |
||||
#include "db/blob/blob_index.h" |
||||
#include "db/blob/blob_log_format.h" |
||||
#include "db/blob/blob_log_writer.h" |
||||
#include "db/version_set.h" |
||||
#include "file/filename.h" |
||||
#include "file/read_write_util.h" |
||||
#include "file/writable_file_writer.h" |
||||
#include "options/cf_options.h" |
||||
#include "rocksdb/slice.h" |
||||
#include "rocksdb/status.h" |
||||
#include "test_util/sync_point.h" |
||||
#include "util/compression.h" |
||||
|
||||
namespace ROCKSDB_NAMESPACE { |
||||
|
||||
BlobFileBuilder::BlobFileBuilder( |
||||
VersionSet* versions, Env* env, FileSystem* fs, |
||||
const ImmutableCFOptions* immutable_cf_options, |
||||
const MutableCFOptions* mutable_cf_options, const FileOptions* file_options, |
||||
uint32_t column_family_id, Env::IOPriority io_priority, |
||||
Env::WriteLifeTimeHint write_hint, |
||||
std::vector<BlobFileAddition>* blob_file_additions) |
||||
: BlobFileBuilder([versions]() { return versions->NewFileNumber(); }, env, |
||||
fs, immutable_cf_options, mutable_cf_options, |
||||
file_options, column_family_id, io_priority, write_hint, |
||||
blob_file_additions) {} |
||||
|
||||
BlobFileBuilder::BlobFileBuilder( |
||||
std::function<uint64_t()> file_number_generator, Env* env, FileSystem* fs, |
||||
const ImmutableCFOptions* immutable_cf_options, |
||||
const MutableCFOptions* mutable_cf_options, const FileOptions* file_options, |
||||
uint32_t column_family_id, Env::IOPriority io_priority, |
||||
Env::WriteLifeTimeHint write_hint, |
||||
std::vector<BlobFileAddition>* blob_file_additions) |
||||
: file_number_generator_(std::move(file_number_generator)), |
||||
env_(env), |
||||
fs_(fs), |
||||
immutable_cf_options_(immutable_cf_options), |
||||
min_blob_size_(mutable_cf_options->min_blob_size), |
||||
blob_file_size_(mutable_cf_options->blob_file_size), |
||||
blob_compression_type_(mutable_cf_options->blob_compression_type), |
||||
file_options_(file_options), |
||||
column_family_id_(column_family_id), |
||||
io_priority_(io_priority), |
||||
write_hint_(write_hint), |
||||
blob_file_additions_(blob_file_additions), |
||||
blob_count_(0), |
||||
blob_bytes_(0) { |
||||
assert(file_number_generator_); |
||||
assert(env_); |
||||
assert(fs_); |
||||
assert(immutable_cf_options_); |
||||
assert(file_options_); |
||||
assert(blob_file_additions_); |
||||
} |
||||
|
||||
BlobFileBuilder::~BlobFileBuilder() = default; |
||||
|
||||
Status BlobFileBuilder::Add(const Slice& key, const Slice& value, |
||||
std::string* blob_index) { |
||||
assert(blob_index); |
||||
assert(blob_index->empty()); |
||||
|
||||
if (value.size() < min_blob_size_) { |
||||
return Status::OK(); |
||||
} |
||||
|
||||
{ |
||||
const Status s = OpenBlobFileIfNeeded(); |
||||
if (!s.ok()) { |
||||
return s; |
||||
} |
||||
} |
||||
|
||||
Slice blob = value; |
||||
std::string compressed_blob; |
||||
|
||||
{ |
||||
const Status s = CompressBlobIfNeeded(&blob, &compressed_blob); |
||||
if (!s.ok()) { |
||||
return s; |
||||
} |
||||
} |
||||
|
||||
uint64_t blob_file_number = 0; |
||||
uint64_t blob_offset = 0; |
||||
|
||||
{ |
||||
const Status s = |
||||
WriteBlobToFile(key, blob, &blob_file_number, &blob_offset); |
||||
if (!s.ok()) { |
||||
return s; |
||||
} |
||||
} |
||||
|
||||
{ |
||||
const Status s = CloseBlobFileIfNeeded(); |
||||
if (!s.ok()) { |
||||
return s; |
||||
} |
||||
} |
||||
|
||||
BlobIndex::EncodeBlob(blob_index, blob_file_number, blob_offset, blob.size(), |
||||
blob_compression_type_); |
||||
|
||||
return Status::OK(); |
||||
} |
||||
|
||||
Status BlobFileBuilder::Finish() { |
||||
if (!IsBlobFileOpen()) { |
||||
return Status::OK(); |
||||
} |
||||
|
||||
return CloseBlobFile(); |
||||
} |
||||
|
||||
bool BlobFileBuilder::IsBlobFileOpen() const { return !!writer_; } |
||||
|
||||
Status BlobFileBuilder::OpenBlobFileIfNeeded() { |
||||
if (IsBlobFileOpen()) { |
||||
return Status::OK(); |
||||
} |
||||
|
||||
assert(!blob_count_); |
||||
assert(!blob_bytes_); |
||||
|
||||
assert(file_number_generator_); |
||||
const uint64_t blob_file_number = file_number_generator_(); |
||||
|
||||
assert(immutable_cf_options_); |
||||
assert(!immutable_cf_options_->cf_paths.empty()); |
||||
const std::string blob_file_path = BlobFileName( |
||||
immutable_cf_options_->cf_paths.front().path, blob_file_number); |
||||
|
||||
std::unique_ptr<FSWritableFile> file; |
||||
|
||||
{ |
||||
TEST_SYNC_POINT("BlobFileBuilder::OpenBlobFileIfNeeded:NewWritableFile"); |
||||
|
||||
assert(file_options_); |
||||
const Status s = |
||||
NewWritableFile(fs_, blob_file_path, &file, *file_options_); |
||||
if (!s.ok()) { |
||||
return s; |
||||
} |
||||
} |
||||
|
||||
assert(file); |
||||
file->SetIOPriority(io_priority_); |
||||
file->SetWriteLifeTimeHint(write_hint_); |
||||
|
||||
Statistics* const statistics = immutable_cf_options_->statistics; |
||||
|
||||
std::unique_ptr<WritableFileWriter> file_writer( |
||||
new WritableFileWriter(std::move(file), blob_file_path, *file_options_, |
||||
env_, statistics, immutable_cf_options_->listeners, |
||||
immutable_cf_options_->file_checksum_gen_factory)); |
||||
|
||||
std::unique_ptr<BlobLogWriter> blob_log_writer( |
||||
new BlobLogWriter(std::move(file_writer), env_, statistics, |
||||
blob_file_number, immutable_cf_options_->use_fsync)); |
||||
|
||||
constexpr bool has_ttl = false; |
||||
constexpr ExpirationRange expiration_range; |
||||
|
||||
BlobLogHeader header(column_family_id_, blob_compression_type_, has_ttl, |
||||
expiration_range); |
||||
|
||||
{ |
||||
TEST_SYNC_POINT("BlobFileBuilder::OpenBlobFileIfNeeded:WriteHeader"); |
||||
|
||||
const Status s = blob_log_writer->WriteHeader(header); |
||||
if (!s.ok()) { |
||||
return s; |
||||
} |
||||
} |
||||
|
||||
writer_ = std::move(blob_log_writer); |
||||
|
||||
assert(IsBlobFileOpen()); |
||||
|
||||
return Status::OK(); |
||||
} |
||||
|
||||
Status BlobFileBuilder::CompressBlobIfNeeded( |
||||
Slice* blob, std::string* compressed_blob) const { |
||||
assert(blob); |
||||
assert(compressed_blob); |
||||
assert(compressed_blob->empty()); |
||||
|
||||
if (blob_compression_type_ == kNoCompression) { |
||||
return Status::OK(); |
||||
} |
||||
|
||||
CompressionOptions opts; |
||||
CompressionContext context(blob_compression_type_); |
||||
constexpr uint64_t sample_for_compression = 0; |
||||
|
||||
CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(), |
||||
blob_compression_type_, sample_for_compression); |
||||
|
||||
constexpr uint32_t compression_format_version = 2; |
||||
|
||||
if (!CompressData(*blob, info, compression_format_version, compressed_blob)) { |
||||
return Status::Corruption("Error compressing blob"); |
||||
} |
||||
|
||||
*blob = Slice(*compressed_blob); |
||||
|
||||
return Status::OK(); |
||||
} |
||||
|
||||
Status BlobFileBuilder::WriteBlobToFile(const Slice& key, const Slice& blob, |
||||
uint64_t* blob_file_number, |
||||
uint64_t* blob_offset) { |
||||
assert(IsBlobFileOpen()); |
||||
assert(blob_file_number); |
||||
assert(blob_offset); |
||||
|
||||
uint64_t key_offset = 0; |
||||
|
||||
TEST_SYNC_POINT("BlobFileBuilder::WriteBlobToFile:AddRecord"); |
||||
|
||||
const Status s = writer_->AddRecord(key, blob, &key_offset, blob_offset); |
||||
if (!s.ok()) { |
||||
return s; |
||||
} |
||||
|
||||
*blob_file_number = writer_->get_log_number(); |
||||
|
||||
++blob_count_; |
||||
blob_bytes_ += BlobLogRecord::kHeaderSize + key.size() + blob.size(); |
||||
|
||||
return Status::OK(); |
||||
} |
||||
|
||||
Status BlobFileBuilder::CloseBlobFile() { |
||||
assert(IsBlobFileOpen()); |
||||
|
||||
BlobLogFooter footer; |
||||
footer.blob_count = blob_count_; |
||||
|
||||
std::string checksum_method; |
||||
std::string checksum_value; |
||||
|
||||
TEST_SYNC_POINT("BlobFileBuilder::WriteBlobToFile:AppendFooter"); |
||||
|
||||
const Status s = |
||||
writer_->AppendFooter(footer, &checksum_method, &checksum_value); |
||||
if (!s.ok()) { |
||||
return s; |
||||
} |
||||
|
||||
const uint64_t blob_file_number = writer_->get_log_number(); |
||||
|
||||
assert(blob_file_additions_); |
||||
blob_file_additions_->emplace_back(blob_file_number, blob_count_, blob_bytes_, |
||||
std::move(checksum_method), |
||||
std::move(checksum_value)); |
||||
|
||||
writer_.reset(); |
||||
blob_count_ = 0; |
||||
blob_bytes_ = 0; |
||||
|
||||
return Status::OK(); |
||||
} |
||||
|
||||
Status BlobFileBuilder::CloseBlobFileIfNeeded() { |
||||
assert(IsBlobFileOpen()); |
||||
|
||||
const WritableFileWriter* const file_writer = writer_->file(); |
||||
assert(file_writer); |
||||
|
||||
if (file_writer->GetFileSize() < blob_file_size_) { |
||||
return Status::OK(); |
||||
} |
||||
|
||||
return CloseBlobFile(); |
||||
} |
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
@ -0,0 +1,82 @@ |
||||
// 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).
|
||||
#pragma once |
||||
|
||||
#include <cinttypes> |
||||
#include <functional> |
||||
#include <memory> |
||||
#include <string> |
||||
#include <vector> |
||||
|
||||
#include "rocksdb/compression_type.h" |
||||
#include "rocksdb/env.h" |
||||
#include "rocksdb/rocksdb_namespace.h" |
||||
|
||||
namespace ROCKSDB_NAMESPACE { |
||||
|
||||
class VersionSet; |
||||
class FileSystem; |
||||
struct ImmutableCFOptions; |
||||
struct MutableCFOptions; |
||||
struct FileOptions; |
||||
class BlobFileAddition; |
||||
class Status; |
||||
class Slice; |
||||
class BlobLogWriter; |
||||
|
||||
class BlobFileBuilder { |
||||
public: |
||||
BlobFileBuilder(VersionSet* versions, Env* env, FileSystem* fs, |
||||
const ImmutableCFOptions* immutable_cf_options, |
||||
const MutableCFOptions* mutable_cf_options, |
||||
const FileOptions* file_options, uint32_t column_family_id, |
||||
Env::IOPriority io_priority, |
||||
Env::WriteLifeTimeHint write_hint, |
||||
std::vector<BlobFileAddition>* blob_file_additions); |
||||
|
||||
BlobFileBuilder(std::function<uint64_t()> file_number_generator, Env* env, |
||||
FileSystem* fs, |
||||
const ImmutableCFOptions* immutable_cf_options, |
||||
const MutableCFOptions* mutable_cf_options, |
||||
const FileOptions* file_options, uint32_t column_family_id, |
||||
Env::IOPriority io_priority, |
||||
Env::WriteLifeTimeHint write_hint, |
||||
std::vector<BlobFileAddition>* blob_file_additions); |
||||
|
||||
BlobFileBuilder(const BlobFileBuilder&) = delete; |
||||
BlobFileBuilder& operator=(const BlobFileBuilder&) = delete; |
||||
|
||||
~BlobFileBuilder(); |
||||
|
||||
Status Add(const Slice& key, const Slice& value, std::string* blob_index); |
||||
Status Finish(); |
||||
|
||||
private: |
||||
bool IsBlobFileOpen() const; |
||||
Status OpenBlobFileIfNeeded(); |
||||
Status CompressBlobIfNeeded(Slice* blob, std::string* compressed_blob) const; |
||||
Status WriteBlobToFile(const Slice& key, const Slice& blob, |
||||
uint64_t* blob_file_number, uint64_t* blob_offset); |
||||
Status CloseBlobFile(); |
||||
Status CloseBlobFileIfNeeded(); |
||||
|
||||
std::function<uint64_t()> file_number_generator_; |
||||
Env* env_; |
||||
FileSystem* fs_; |
||||
const ImmutableCFOptions* immutable_cf_options_; |
||||
uint64_t min_blob_size_; |
||||
uint64_t blob_file_size_; |
||||
CompressionType blob_compression_type_; |
||||
const FileOptions* file_options_; |
||||
uint32_t column_family_id_; |
||||
Env::IOPriority io_priority_; |
||||
Env::WriteLifeTimeHint write_hint_; |
||||
std::vector<BlobFileAddition>* blob_file_additions_; |
||||
std::unique_ptr<BlobLogWriter> writer_; |
||||
uint64_t blob_count_; |
||||
uint64_t blob_bytes_; |
||||
}; |
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
@ -0,0 +1,579 @@ |
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/blob/blob_file_builder.h" |
||||
|
||||
#include <cassert> |
||||
#include <cinttypes> |
||||
#include <string> |
||||
#include <utility> |
||||
#include <vector> |
||||
|
||||
#include "db/blob/blob_file_addition.h" |
||||
#include "db/blob/blob_index.h" |
||||
#include "db/blob/blob_log_format.h" |
||||
#include "db/blob/blob_log_reader.h" |
||||
#include "env/composite_env_wrapper.h" |
||||
#include "env/mock_env.h" |
||||
#include "file/filename.h" |
||||
#include "file/random_access_file_reader.h" |
||||
#include "options/cf_options.h" |
||||
#include "rocksdb/env.h" |
||||
#include "rocksdb/file_checksum.h" |
||||
#include "rocksdb/options.h" |
||||
#include "test_util/sync_point.h" |
||||
#include "test_util/testharness.h" |
||||
#include "util/compression.h" |
||||
#include "utilities/fault_injection_env.h" |
||||
|
||||
namespace ROCKSDB_NAMESPACE { |
||||
|
||||
class TestFileNumberGenerator { |
||||
public: |
||||
uint64_t operator()() { return ++next_file_number_; } |
||||
|
||||
private: |
||||
uint64_t next_file_number_ = 1; |
||||
}; |
||||
|
||||
class BlobFileBuilderTest : public testing::Test { |
||||
protected: |
||||
BlobFileBuilderTest() : mock_env_(Env::Default()), fs_(&mock_env_) {} |
||||
|
||||
void VerifyBlobFile(const ImmutableCFOptions& immutable_cf_options, |
||||
uint64_t blob_file_number, uint32_t column_family_id, |
||||
CompressionType blob_compression_type, |
||||
const std::vector<std::pair<std::string, std::string>>& |
||||
expected_key_value_pairs, |
||||
const std::vector<std::string>& blob_indexes) { |
||||
assert(expected_key_value_pairs.size() == blob_indexes.size()); |
||||
|
||||
const std::string blob_file_path = BlobFileName( |
||||
immutable_cf_options.cf_paths.front().path, blob_file_number); |
||||
|
||||
std::unique_ptr<FSRandomAccessFile> file; |
||||
constexpr IODebugContext* dbg = nullptr; |
||||
ASSERT_OK( |
||||
fs_.NewRandomAccessFile(blob_file_path, file_options_, &file, dbg)); |
||||
|
||||
std::unique_ptr<RandomAccessFileReader> file_reader( |
||||
new RandomAccessFileReader(std::move(file), blob_file_path, |
||||
&mock_env_)); |
||||
|
||||
constexpr Statistics* statistics = nullptr; |
||||
BlobLogReader blob_log_reader(std::move(file_reader), &mock_env_, |
||||
statistics); |
||||
|
||||
BlobLogHeader header; |
||||
ASSERT_OK(blob_log_reader.ReadHeader(&header)); |
||||
ASSERT_EQ(header.version, kVersion1); |
||||
ASSERT_EQ(header.column_family_id, column_family_id); |
||||
ASSERT_EQ(header.compression, blob_compression_type); |
||||
ASSERT_FALSE(header.has_ttl); |
||||
ASSERT_EQ(header.expiration_range, ExpirationRange()); |
||||
|
||||
for (size_t i = 0; i < expected_key_value_pairs.size(); ++i) { |
||||
BlobLogRecord record; |
||||
uint64_t blob_offset = 0; |
||||
|
||||
ASSERT_OK(blob_log_reader.ReadRecord( |
||||
&record, BlobLogReader::kReadHeaderKeyBlob, &blob_offset)); |
||||
|
||||
// Check the contents of the blob file
|
||||
const auto& expected_key_value = expected_key_value_pairs[i]; |
||||
const auto& key = expected_key_value.first; |
||||
const auto& value = expected_key_value.second; |
||||
|
||||
ASSERT_EQ(record.key_size, key.size()); |
||||
ASSERT_EQ(record.value_size, value.size()); |
||||
ASSERT_EQ(record.expiration, 0); |
||||
ASSERT_EQ(record.key, key); |
||||
ASSERT_EQ(record.value, value); |
||||
|
||||
// Make sure the blob reference returned by the builder points to the
|
||||
// right place
|
||||
BlobIndex blob_index; |
||||
ASSERT_OK(blob_index.DecodeFrom(blob_indexes[i])); |
||||
ASSERT_FALSE(blob_index.IsInlined()); |
||||
ASSERT_FALSE(blob_index.HasTTL()); |
||||
ASSERT_EQ(blob_index.file_number(), blob_file_number); |
||||
ASSERT_EQ(blob_index.offset(), blob_offset); |
||||
ASSERT_EQ(blob_index.size(), value.size()); |
||||
} |
||||
|
||||
BlobLogFooter footer; |
||||
ASSERT_OK(blob_log_reader.ReadFooter(&footer)); |
||||
ASSERT_EQ(footer.blob_count, expected_key_value_pairs.size()); |
||||
ASSERT_EQ(footer.expiration_range, ExpirationRange()); |
||||
} |
||||
|
||||
MockEnv mock_env_; |
||||
LegacyFileSystemWrapper fs_; |
||||
FileOptions file_options_; |
||||
}; |
||||
|
||||
TEST_F(BlobFileBuilderTest, BuildAndCheckOneFile) { |
||||
// Build a single blob file
|
||||
constexpr size_t number_of_blobs = 10; |
||||
constexpr size_t key_size = 1; |
||||
constexpr size_t value_size = 4; |
||||
constexpr size_t value_offset = 1234; |
||||
|
||||
Options options; |
||||
options.cf_paths.emplace_back( |
||||
test::PerThreadDBPath(&mock_env_, |
||||
"BlobFileBuilderTest_BuildAndCheckOneFile"), |
||||
0); |
||||
options.enable_blob_files = true; |
||||
|
||||
ImmutableCFOptions immutable_cf_options(options); |
||||
MutableCFOptions mutable_cf_options(options); |
||||
|
||||
constexpr uint32_t column_family_id = 123; |
||||
constexpr Env::IOPriority io_priority = Env::IO_HIGH; |
||||
constexpr Env::WriteLifeTimeHint write_hint = Env::WLTH_MEDIUM; |
||||
|
||||
std::vector<BlobFileAddition> blob_file_additions; |
||||
|
||||
BlobFileBuilder builder(TestFileNumberGenerator(), &mock_env_, &fs_, |
||||
&immutable_cf_options, &mutable_cf_options, |
||||
&file_options_, column_family_id, io_priority, |
||||
write_hint, &blob_file_additions); |
||||
|
||||
std::vector<std::pair<std::string, std::string>> expected_key_value_pairs( |
||||
number_of_blobs); |
||||
std::vector<std::string> blob_indexes(number_of_blobs); |
||||
|
||||
for (size_t i = 0; i < number_of_blobs; ++i) { |
||||
auto& expected_key_value = expected_key_value_pairs[i]; |
||||
|
||||
auto& key = expected_key_value.first; |
||||
key = std::to_string(i); |
||||
assert(key.size() == key_size); |
||||
|
||||
auto& value = expected_key_value.second; |
||||
value = std::to_string(i + value_offset); |
||||
assert(value.size() == value_size); |
||||
|
||||
auto& blob_index = blob_indexes[i]; |
||||
|
||||
ASSERT_OK(builder.Add(key, value, &blob_index)); |
||||
ASSERT_FALSE(blob_index.empty()); |
||||
} |
||||
|
||||
ASSERT_OK(builder.Finish()); |
||||
|
||||
// Check the metadata generated
|
||||
ASSERT_EQ(blob_file_additions.size(), 1); |
||||
|
||||
const auto& blob_file_addition = blob_file_additions[0]; |
||||
|
||||
constexpr uint64_t blob_file_number = 2; |
||||
|
||||
ASSERT_EQ(blob_file_addition.GetBlobFileNumber(), blob_file_number); |
||||
ASSERT_EQ(blob_file_addition.GetTotalBlobCount(), number_of_blobs); |
||||
ASSERT_EQ( |
||||
blob_file_addition.GetTotalBlobBytes(), |
||||
number_of_blobs * (BlobLogRecord::kHeaderSize + key_size + value_size)); |
||||
|
||||
// Verify the contents of the new blob file as well as the blob references
|
||||
VerifyBlobFile(immutable_cf_options, blob_file_number, column_family_id, |
||||
kNoCompression, expected_key_value_pairs, blob_indexes); |
||||
} |
||||
|
||||
TEST_F(BlobFileBuilderTest, BuildAndCheckMultipleFiles) { |
||||
// Build multiple blob files: file size limit is set to the size of a single
|
||||
// value, so each blob ends up in a file of its own
|
||||
constexpr size_t number_of_blobs = 10; |
||||
constexpr size_t key_size = 1; |
||||
constexpr size_t value_size = 10; |
||||
constexpr size_t value_offset = 1234567890; |
||||
|
||||
Options options; |
||||
options.cf_paths.emplace_back( |
||||
test::PerThreadDBPath(&mock_env_, |
||||
"BlobFileBuilderTest_BuildAndCheckMultipleFiles"), |
||||
0); |
||||
options.enable_blob_files = true; |
||||
options.blob_file_size = value_size; |
||||
|
||||
ImmutableCFOptions immutable_cf_options(options); |
||||
MutableCFOptions mutable_cf_options(options); |
||||
|
||||
constexpr uint32_t column_family_id = 123; |
||||
constexpr Env::IOPriority io_priority = Env::IO_HIGH; |
||||
constexpr Env::WriteLifeTimeHint write_hint = Env::WLTH_MEDIUM; |
||||
|
||||
std::vector<BlobFileAddition> blob_file_additions; |
||||
|
||||
BlobFileBuilder builder(TestFileNumberGenerator(), &mock_env_, &fs_, |
||||
&immutable_cf_options, &mutable_cf_options, |
||||
&file_options_, column_family_id, io_priority, |
||||
write_hint, &blob_file_additions); |
||||
|
||||
std::vector<std::pair<std::string, std::string>> expected_key_value_pairs( |
||||
number_of_blobs); |
||||
std::vector<std::string> blob_indexes(number_of_blobs); |
||||
|
||||
for (size_t i = 0; i < number_of_blobs; ++i) { |
||||
auto& expected_key_value = expected_key_value_pairs[i]; |
||||
|
||||
auto& key = expected_key_value.first; |
||||
key = std::to_string(i); |
||||
assert(key.size() == key_size); |
||||
|
||||
auto& value = expected_key_value.second; |
||||
value = std::to_string(i + value_offset); |
||||
assert(value.size() == value_size); |
||||
|
||||
auto& blob_index = blob_indexes[i]; |
||||
|
||||
ASSERT_OK(builder.Add(key, value, &blob_index)); |
||||
ASSERT_FALSE(blob_index.empty()); |
||||
} |
||||
|
||||
ASSERT_OK(builder.Finish()); |
||||
|
||||
// Check the metadata generated
|
||||
ASSERT_EQ(blob_file_additions.size(), number_of_blobs); |
||||
|
||||
for (size_t i = 0; i < number_of_blobs; ++i) { |
||||
const auto& blob_file_addition = blob_file_additions[i]; |
||||
|
||||
ASSERT_EQ(blob_file_addition.GetBlobFileNumber(), i + 2); |
||||
ASSERT_EQ(blob_file_addition.GetTotalBlobCount(), 1); |
||||
ASSERT_EQ(blob_file_addition.GetTotalBlobBytes(), |
||||
BlobLogRecord::kHeaderSize + key_size + value_size); |
||||
} |
||||
|
||||
// Verify the contents of the new blob files as well as the blob references
|
||||
for (size_t i = 0; i < number_of_blobs; ++i) { |
||||
std::vector<std::pair<std::string, std::string>> expected_key_value_pair{ |
||||
expected_key_value_pairs[i]}; |
||||
std::vector<std::string> blob_index{blob_indexes[i]}; |
||||
|
||||
VerifyBlobFile(immutable_cf_options, i + 2, column_family_id, |
||||
kNoCompression, expected_key_value_pair, blob_index); |
||||
} |
||||
} |
||||
|
||||
TEST_F(BlobFileBuilderTest, InlinedValues) { |
||||
// All values are below the min_blob_size threshold; no blob files get written
|
||||
constexpr size_t number_of_blobs = 10; |
||||
constexpr size_t key_size = 1; |
||||
constexpr size_t value_size = 10; |
||||
constexpr size_t value_offset = 1234567890; |
||||
|
||||
Options options; |
||||
options.cf_paths.emplace_back( |
||||
test::PerThreadDBPath(&mock_env_, "BlobFileBuilderTest_InlinedValues"), |
||||
0); |
||||
options.enable_blob_files = true; |
||||
options.min_blob_size = 1024; |
||||
|
||||
ImmutableCFOptions immutable_cf_options(options); |
||||
MutableCFOptions mutable_cf_options(options); |
||||
|
||||
constexpr uint32_t column_family_id = 123; |
||||
constexpr Env::IOPriority io_priority = Env::IO_HIGH; |
||||
constexpr Env::WriteLifeTimeHint write_hint = Env::WLTH_MEDIUM; |
||||
|
||||
std::vector<BlobFileAddition> blob_file_additions; |
||||
|
||||
BlobFileBuilder builder(TestFileNumberGenerator(), &mock_env_, &fs_, |
||||
&immutable_cf_options, &mutable_cf_options, |
||||
&file_options_, column_family_id, io_priority, |
||||
write_hint, &blob_file_additions); |
||||
|
||||
for (size_t i = 0; i < number_of_blobs; ++i) { |
||||
const std::string key = std::to_string(i); |
||||
assert(key.size() == key_size); |
||||
|
||||
const std::string value = std::to_string(i + value_offset); |
||||
assert(value.size() == value_size); |
||||
|
||||
std::string blob_index; |
||||
ASSERT_OK(builder.Add(key, value, &blob_index)); |
||||
ASSERT_TRUE(blob_index.empty()); |
||||
} |
||||
|
||||
ASSERT_OK(builder.Finish()); |
||||
|
||||
// Check the metadata generated
|
||||
ASSERT_TRUE(blob_file_additions.empty()); |
||||
} |
||||
|
||||
TEST_F(BlobFileBuilderTest, Compression) { |
||||
// Build a blob file with a compressed blob
|
||||
if (!Snappy_Supported()) { |
||||
return; |
||||
} |
||||
|
||||
constexpr size_t key_size = 1; |
||||
constexpr size_t value_size = 100; |
||||
|
||||
Options options; |
||||
options.cf_paths.emplace_back( |
||||
test::PerThreadDBPath(&mock_env_, "BlobFileBuilderTest_Compression"), 0); |
||||
options.enable_blob_files = true; |
||||
options.blob_compression_type = kSnappyCompression; |
||||
|
||||
ImmutableCFOptions immutable_cf_options(options); |
||||
MutableCFOptions mutable_cf_options(options); |
||||
|
||||
constexpr uint32_t column_family_id = 123; |
||||
constexpr Env::IOPriority io_priority = Env::IO_HIGH; |
||||
constexpr Env::WriteLifeTimeHint write_hint = Env::WLTH_MEDIUM; |
||||
|
||||
std::vector<BlobFileAddition> blob_file_additions; |
||||
|
||||
BlobFileBuilder builder(TestFileNumberGenerator(), &mock_env_, &fs_, |
||||
&immutable_cf_options, &mutable_cf_options, |
||||
&file_options_, column_family_id, io_priority, |
||||
write_hint, &blob_file_additions); |
||||
|
||||
const std::string key("1"); |
||||
const std::string uncompressed_value(value_size, 'x'); |
||||
|
||||
std::string blob_index; |
||||
|
||||
ASSERT_OK(builder.Add(key, uncompressed_value, &blob_index)); |
||||
ASSERT_FALSE(blob_index.empty()); |
||||
|
||||
ASSERT_OK(builder.Finish()); |
||||
|
||||
// Check the metadata generated
|
||||
ASSERT_EQ(blob_file_additions.size(), 1); |
||||
|
||||
const auto& blob_file_addition = blob_file_additions[0]; |
||||
|
||||
constexpr uint64_t blob_file_number = 2; |
||||
|
||||
ASSERT_EQ(blob_file_addition.GetBlobFileNumber(), blob_file_number); |
||||
ASSERT_EQ(blob_file_addition.GetTotalBlobCount(), 1); |
||||
|
||||
CompressionOptions opts; |
||||
CompressionContext context(kSnappyCompression); |
||||
constexpr uint64_t sample_for_compression = 0; |
||||
|
||||
CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(), |
||||
kSnappyCompression, sample_for_compression); |
||||
|
||||
std::string compressed_value; |
||||
ASSERT_TRUE(Snappy_Compress(info, uncompressed_value.data(), |
||||
uncompressed_value.size(), &compressed_value)); |
||||
|
||||
ASSERT_EQ(blob_file_addition.GetTotalBlobBytes(), |
||||
BlobLogRecord::kHeaderSize + key_size + compressed_value.size()); |
||||
|
||||
// Verify the contents of the new blob file as well as the blob reference
|
||||
std::vector<std::pair<std::string, std::string>> expected_key_value_pairs{ |
||||
{key, compressed_value}}; |
||||
std::vector<std::string> blob_indexes{blob_index}; |
||||
|
||||
VerifyBlobFile(immutable_cf_options, blob_file_number, column_family_id, |
||||
kSnappyCompression, expected_key_value_pairs, blob_indexes); |
||||
} |
||||
|
||||
TEST_F(BlobFileBuilderTest, CompressionError) { |
||||
// Simulate an error during compression
|
||||
if (!Snappy_Supported()) { |
||||
return; |
||||
} |
||||
|
||||
Options options; |
||||
options.cf_paths.emplace_back( |
||||
test::PerThreadDBPath(&mock_env_, "BlobFileBuilderTest_CompressionError"), |
||||
0); |
||||
options.enable_blob_files = true; |
||||
options.blob_compression_type = kSnappyCompression; |
||||
|
||||
ImmutableCFOptions immutable_cf_options(options); |
||||
MutableCFOptions mutable_cf_options(options); |
||||
|
||||
constexpr uint32_t column_family_id = 123; |
||||
constexpr Env::IOPriority io_priority = Env::IO_HIGH; |
||||
constexpr Env::WriteLifeTimeHint write_hint = Env::WLTH_MEDIUM; |
||||
|
||||
std::vector<BlobFileAddition> blob_file_additions; |
||||
|
||||
BlobFileBuilder builder(TestFileNumberGenerator(), &mock_env_, &fs_, |
||||
&immutable_cf_options, &mutable_cf_options, |
||||
&file_options_, column_family_id, io_priority, |
||||
write_hint, &blob_file_additions); |
||||
|
||||
SyncPoint::GetInstance()->SetCallBack("CompressData:TamperWithReturnValue", |
||||
[](void* arg) { |
||||
bool* ret = static_cast<bool*>(arg); |
||||
*ret = false; |
||||
}); |
||||
SyncPoint::GetInstance()->EnableProcessing(); |
||||
|
||||
constexpr char key[] = "1"; |
||||
constexpr char value[] = "deadbeef"; |
||||
|
||||
std::string blob_index; |
||||
|
||||
ASSERT_TRUE(builder.Add(key, value, &blob_index).IsCorruption()); |
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing(); |
||||
SyncPoint::GetInstance()->ClearAllCallBacks(); |
||||
} |
||||
|
||||
TEST_F(BlobFileBuilderTest, Checksum) { |
||||
// Build a blob file with checksum
|
||||
|
||||
class DummyFileChecksumGenerator : public FileChecksumGenerator { |
||||
public: |
||||
void Update(const char* /* data */, size_t /* n */) override {} |
||||
|
||||
void Finalize() override {} |
||||
|
||||
std::string GetChecksum() const override { return std::string("dummy"); } |
||||
|
||||
const char* Name() const override { return "DummyFileChecksum"; } |
||||
}; |
||||
|
||||
class DummyFileChecksumGenFactory : public FileChecksumGenFactory { |
||||
public: |
||||
std::unique_ptr<FileChecksumGenerator> CreateFileChecksumGenerator( |
||||
const FileChecksumGenContext& /* context */) override { |
||||
return std::unique_ptr<FileChecksumGenerator>( |
||||
new DummyFileChecksumGenerator); |
||||
} |
||||
|
||||
const char* Name() const override { return "DummyFileChecksumGenFactory"; } |
||||
}; |
||||
|
||||
Options options; |
||||
options.cf_paths.emplace_back( |
||||
test::PerThreadDBPath(&mock_env_, "BlobFileBuilderTest_Checksum"), 0); |
||||
options.enable_blob_files = true; |
||||
options.file_checksum_gen_factory = |
||||
std::make_shared<DummyFileChecksumGenFactory>(); |
||||
|
||||
ImmutableCFOptions immutable_cf_options(options); |
||||
MutableCFOptions mutable_cf_options(options); |
||||
|
||||
constexpr uint32_t column_family_id = 123; |
||||
constexpr Env::IOPriority io_priority = Env::IO_HIGH; |
||||
constexpr Env::WriteLifeTimeHint write_hint = Env::WLTH_MEDIUM; |
||||
|
||||
std::vector<BlobFileAddition> blob_file_additions; |
||||
|
||||
BlobFileBuilder builder(TestFileNumberGenerator(), &mock_env_, &fs_, |
||||
&immutable_cf_options, &mutable_cf_options, |
||||
&file_options_, column_family_id, io_priority, |
||||
write_hint, &blob_file_additions); |
||||
|
||||
const std::string key("1"); |
||||
const std::string value("deadbeef"); |
||||
|
||||
std::string blob_index; |
||||
|
||||
ASSERT_OK(builder.Add(key, value, &blob_index)); |
||||
ASSERT_FALSE(blob_index.empty()); |
||||
|
||||
ASSERT_OK(builder.Finish()); |
||||
|
||||
// Check the metadata generated
|
||||
ASSERT_EQ(blob_file_additions.size(), 1); |
||||
|
||||
const auto& blob_file_addition = blob_file_additions[0]; |
||||
|
||||
constexpr uint64_t blob_file_number = 2; |
||||
|
||||
ASSERT_EQ(blob_file_addition.GetBlobFileNumber(), blob_file_number); |
||||
ASSERT_EQ(blob_file_addition.GetTotalBlobCount(), 1); |
||||
ASSERT_EQ(blob_file_addition.GetTotalBlobBytes(), |
||||
BlobLogRecord::kHeaderSize + key.size() + value.size()); |
||||
ASSERT_EQ(blob_file_addition.GetChecksumMethod(), "DummyFileChecksum"); |
||||
ASSERT_EQ(blob_file_addition.GetChecksumValue(), "dummy"); |
||||
|
||||
// Verify the contents of the new blob file as well as the blob reference
|
||||
std::vector<std::pair<std::string, std::string>> expected_key_value_pairs{ |
||||
{key, value}}; |
||||
std::vector<std::string> blob_indexes{blob_index}; |
||||
|
||||
VerifyBlobFile(immutable_cf_options, blob_file_number, column_family_id, |
||||
kNoCompression, expected_key_value_pairs, blob_indexes); |
||||
} |
||||
|
||||
class BlobFileBuilderIOErrorTest |
||||
: public testing::Test, |
||||
public testing::WithParamInterface<std::string> { |
||||
protected: |
||||
BlobFileBuilderIOErrorTest() |
||||
: mock_env_(Env::Default()), |
||||
fault_injection_env_(&mock_env_), |
||||
fs_(&fault_injection_env_), |
||||
sync_point_(GetParam()) {} |
||||
|
||||
MockEnv mock_env_; |
||||
FaultInjectionTestEnv fault_injection_env_; |
||||
LegacyFileSystemWrapper fs_; |
||||
FileOptions file_options_; |
||||
std::string sync_point_; |
||||
}; |
||||
|
||||
INSTANTIATE_TEST_CASE_P( |
||||
BlobFileBuilderTest, BlobFileBuilderIOErrorTest, |
||||
::testing::ValuesIn(std::vector<std::string>{ |
||||
"BlobFileBuilder::OpenBlobFileIfNeeded:NewWritableFile", |
||||
"BlobFileBuilder::OpenBlobFileIfNeeded:WriteHeader", |
||||
"BlobFileBuilder::WriteBlobToFile:AddRecord", |
||||
"BlobFileBuilder::WriteBlobToFile:AppendFooter"})); |
||||
|
||||
TEST_P(BlobFileBuilderIOErrorTest, IOError) { |
||||
// Simulate an I/O error during the specified step of Add()
|
||||
// Note: blob_file_size will be set to value_size in order for the first blob
|
||||
// to trigger close
|
||||
constexpr size_t value_size = 8; |
||||
|
||||
Options options; |
||||
options.cf_paths.emplace_back( |
||||
test::PerThreadDBPath(&fault_injection_env_, |
||||
"BlobFileBuilderIOErrorTest_IOError"), |
||||
0); |
||||
options.enable_blob_files = true; |
||||
options.blob_file_size = value_size; |
||||
|
||||
ImmutableCFOptions immutable_cf_options(options); |
||||
MutableCFOptions mutable_cf_options(options); |
||||
|
||||
constexpr uint32_t column_family_id = 123; |
||||
constexpr Env::IOPriority io_priority = Env::IO_HIGH; |
||||
constexpr Env::WriteLifeTimeHint write_hint = Env::WLTH_MEDIUM; |
||||
|
||||
std::vector<BlobFileAddition> blob_file_additions; |
||||
|
||||
BlobFileBuilder builder(TestFileNumberGenerator(), &fault_injection_env_, |
||||
&fs_, &immutable_cf_options, &mutable_cf_options, |
||||
&file_options_, column_family_id, io_priority, |
||||
write_hint, &blob_file_additions); |
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(sync_point_, [this](void* /* arg */) { |
||||
fault_injection_env_.SetFilesystemActive(false, |
||||
Status::IOError(sync_point_)); |
||||
}); |
||||
SyncPoint::GetInstance()->EnableProcessing(); |
||||
|
||||
constexpr char key[] = "1"; |
||||
constexpr char value[] = "deadbeef"; |
||||
|
||||
std::string blob_index; |
||||
|
||||
ASSERT_TRUE(builder.Add(key, value, &blob_index).IsIOError()); |
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing(); |
||||
SyncPoint::GetInstance()->ClearAllCallBacks(); |
||||
} |
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) { |
||||
::testing::InitGoogleTest(&argc, argv); |
||||
return RUN_ALL_TESTS(); |
||||
} |
Loading…
Reference in new issue