mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 02:52:35 -05:00
Compare commits
40
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a00304c7b | ||
|
|
7d971cbd33 | ||
|
|
9072f5db09 | ||
|
|
49b0cb64df | ||
|
|
82794e0a4f | ||
|
|
934cf2d40d | ||
|
|
0b815cf3b3 | ||
|
|
7f3ee34cdf | ||
|
|
d0374a0a72 | ||
|
|
12829883d7 | ||
|
|
2e175124d8 | ||
|
|
0a43d8a261 | ||
|
|
cc487ba367 | ||
|
|
17ac19f2c4 | ||
|
|
24952ff088 | ||
|
|
6ac13a5f0a | ||
|
|
0cc943c067 | ||
|
|
ca7367a003 | ||
|
|
c5921df3d7 | ||
|
|
8e16f8fecf | ||
|
|
22ca6e5e68 | ||
|
|
60c266658d | ||
|
|
b9c7481fc2 | ||
|
|
5d1c0a8832 | ||
|
|
d033c6a849 | ||
|
|
68b2d941be | ||
|
|
8e6d431153 | ||
|
|
14c949df8b | ||
|
|
f6bff87b92 | ||
|
|
ec8f1452f5 | ||
|
|
15873b1fdd | ||
|
|
da8eba8b49 | ||
|
|
5f9b7ccce3 | ||
|
|
0c7e5bd2f0 | ||
|
|
7e272d2032 | ||
|
|
1d6c33d2a5 | ||
|
|
ebaeb03648 | ||
|
|
d1f383b8eb | ||
|
|
3af905aa68 | ||
|
|
3740fccc4b |
No files matched your search
+18
-2
@@ -1,11 +1,27 @@
|
||||
# Rocksdb Change Log
|
||||
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
|
||||
|
||||
## 10.0.1 (03/05/2025)
|
||||
## 10.1.0 (03/24/2025)
|
||||
### New Features
|
||||
* Added a new `DBOptions.calculate_sst_write_lifetime_hint_set` setting that allows to customize which compaction styles SST write lifetime hint calculation is allowed on. Today RocksDB supports only two modes `kCompactionStyleLevel` and `kCompactionStyleUniversal`.
|
||||
* Add a new field `num_l0_files` in `CompactionJobInfo` about the number of L0 files in the CF right before and after the compaction
|
||||
* Added per-key-placement feature in Remote Compaction
|
||||
* Implemented API DB::GetPropertiesOfTablesByLevel that retrieves table properties for files in each LSM tree level
|
||||
|
||||
### Public API Changes
|
||||
* `GetAllKeyVersions()` now interprets empty slices literally, as valid keys, and uses new `OptSlice` type default value for extreme upper and lower range limits.
|
||||
* `DeleteFilesInRanges()` now takes `RangeOpt` which is based on `OptSlice`. The overload taking `RangePtr` is deprecated.
|
||||
* Add an unordered map of name/value pairs, ReadOptions::property_bag, to pass opaque options through to an external table when creating an Iterator.
|
||||
* Introduced CompactionServiceJobStatus::kAborted to allow handling aborted scenario in Schedule(), Wait() or OnInstallation() APIs in Remote Compactions.
|
||||
* Added a column family option disallow_memtable_writes to safely fail any attempts to write to a non-default column family. This can be used for column families that are ingest only.
|
||||
* format\_version < 2 in BlockBasedTableOptions is no longer supported for writing new files. Support for reading such files is deprecated and might be removed in the future. `CompressedSecondaryCacheOptions::compress_format_version == 1` is also deprecated.
|
||||
|
||||
### Behavior Changes
|
||||
* `ldb` now returns an error if the specified `--compression_type` is not supported in the build.
|
||||
* MultiGet with snapshot and ReadOptions::read_tier = kPersistedTier will now read a consistent view across CFs (instead of potentially reading some CF before and some CF after a flush).
|
||||
* CreateColumnFamily() is no longer allowed on a read-only DB (OpenForReadOnly())
|
||||
|
||||
### Bug Fixes
|
||||
* Fixed stats for Tiered Storage with preclude_last_level feature
|
||||
|
||||
## 10.0.0 (02/21/2025)
|
||||
### New Features
|
||||
|
||||
@@ -2489,7 +2489,7 @@ checkout_folly:
|
||||
fi
|
||||
@# Pin to a particular version for public CI, so that PR authors don't
|
||||
@# need to worry about folly breaking our integration. Update periodically
|
||||
cd third-party/folly && git reset --hard f1c1542fb071e9d88181279ec24043400222aef1
|
||||
cd third-party/folly && git reset --hard 78286282478e1ae05b2e8cbcf0e2139eab283bea
|
||||
@# NOTE: this hack is required for clang in some cases
|
||||
perl -pi -e 's/int rv = syscall/int rv = (int)syscall/' third-party/folly/folly/detail/Futex.cpp
|
||||
@# NOTE: this hack is required for gcc in some cases
|
||||
|
||||
@@ -188,10 +188,12 @@ Status BlobFileBuilder::OpenBlobFileIfNeeded() {
|
||||
}
|
||||
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
|
||||
FileOptions fo_copy;
|
||||
{
|
||||
assert(file_options_);
|
||||
Status s = NewWritableFile(fs_, blob_file_path, &file, *file_options_);
|
||||
fo_copy = *file_options_;
|
||||
fo_copy.write_hint = write_hint_;
|
||||
Status s = NewWritableFile(fs_, blob_file_path, &file, fo_copy);
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"BlobFileBuilder::OpenBlobFileIfNeeded:NewWritableFile", &s);
|
||||
@@ -209,7 +211,9 @@ Status BlobFileBuilder::OpenBlobFileIfNeeded() {
|
||||
|
||||
assert(file);
|
||||
file->SetIOPriority(write_options_->rate_limiter_priority);
|
||||
file->SetWriteLifeTimeHint(write_hint_);
|
||||
// Subsequent attempts to override the hint via SetWriteLifeTimeHint
|
||||
// with the very same value will be ignored by the fs.
|
||||
file->SetWriteLifeTimeHint(fo_copy.write_hint);
|
||||
FileTypeSet tmp_set = immutable_options_->checksum_handoff_file_types;
|
||||
Statistics* const statistics = immutable_options_->stats;
|
||||
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
|
||||
|
||||
+6
-2
@@ -145,7 +145,9 @@ Status BuildTable(
|
||||
bool use_direct_writes = file_options.use_direct_writes;
|
||||
TEST_SYNC_POINT_CALLBACK("BuildTable:create_file", &use_direct_writes);
|
||||
#endif // !NDEBUG
|
||||
IOStatus io_s = NewWritableFile(fs, fname, &file, file_options);
|
||||
FileOptions fo_copy = file_options;
|
||||
fo_copy.write_hint = write_hint;
|
||||
IOStatus io_s = NewWritableFile(fs, fname, &file, fo_copy);
|
||||
assert(s.ok());
|
||||
s = io_s;
|
||||
if (io_status->ok()) {
|
||||
@@ -163,7 +165,9 @@ Status BuildTable(
|
||||
table_file_created = true;
|
||||
FileTypeSet tmp_set = ioptions.checksum_handoff_file_types;
|
||||
file->SetIOPriority(tboptions.write_options.rate_limiter_priority);
|
||||
file->SetWriteLifeTimeHint(write_hint);
|
||||
// Subsequent attempts to override the hint via SetWriteLifeTimeHint
|
||||
// with the very same value will be ignored by the fs.
|
||||
file->SetWriteLifeTimeHint(fo_copy.write_hint);
|
||||
file_writer.reset(new WritableFileWriter(
|
||||
std::move(file), fname, file_options, ioptions.clock, io_tracer,
|
||||
ioptions.stats, Histograms::SST_WRITE_MICROS, ioptions.listeners,
|
||||
|
||||
+44
-21
@@ -200,8 +200,9 @@ const uint64_t kDefaultTtl = 0xfffffffffffffffe;
|
||||
const uint64_t kDefaultPeriodicCompSecs = 0xfffffffffffffffe;
|
||||
} // anonymous namespace
|
||||
|
||||
ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
|
||||
const ColumnFamilyOptions& src) {
|
||||
ColumnFamilyOptions SanitizeCfOptions(const ImmutableDBOptions& db_options,
|
||||
bool read_only,
|
||||
const ColumnFamilyOptions& src) {
|
||||
ColumnFamilyOptions result = src;
|
||||
size_t clamp_max = std::conditional<
|
||||
sizeof(size_t) == 4, std::integral_constant<size_t, 0xffffffff>,
|
||||
@@ -440,6 +441,18 @@ ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
|
||||
result.periodic_compaction_seconds = 0;
|
||||
}
|
||||
|
||||
if (read_only && (result.preserve_internal_time_seconds > 0 ||
|
||||
result.preclude_last_level_data_seconds > 0)) {
|
||||
// With no writes coming in, we don't need periodic SeqnoToTime entries.
|
||||
// Existing SST files may or may not have that info associated with them.
|
||||
ROCKS_LOG_WARN(
|
||||
db_options.info_log.get(),
|
||||
"preserve_internal_time_seconds and preclude_last_level_data_seconds "
|
||||
"are ignored in read-only DB");
|
||||
result.preserve_internal_time_seconds = 0;
|
||||
result.preclude_last_level_data_seconds = 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -497,6 +510,17 @@ void SuperVersion::Init(
|
||||
imm->Ref();
|
||||
current->Ref();
|
||||
refs.store(1, std::memory_order_relaxed);
|
||||
|
||||
// There should be at least one mapping entry iff time tracking is enabled.
|
||||
#ifndef NDEBUG
|
||||
MinAndMaxPreserveSeconds preserve_info{mutable_cf_options};
|
||||
if (preserve_info.IsEnabled()) {
|
||||
assert(seqno_to_time_mapping);
|
||||
assert(!seqno_to_time_mapping->Empty());
|
||||
} else {
|
||||
assert(seqno_to_time_mapping == nullptr);
|
||||
}
|
||||
#endif // NDEBUG
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -535,7 +559,7 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
const FileOptions* file_options, ColumnFamilySet* column_family_set,
|
||||
BlockCacheTracer* const block_cache_tracer,
|
||||
const std::shared_ptr<IOTracer>& io_tracer, const std::string& db_id,
|
||||
const std::string& db_session_id)
|
||||
const std::string& db_session_id, bool read_only)
|
||||
: id_(id),
|
||||
name_(name),
|
||||
dummy_versions_(_dummy_versions),
|
||||
@@ -545,7 +569,7 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
dropped_(false),
|
||||
flush_skip_reschedule_(false),
|
||||
internal_comparator_(cf_options.comparator),
|
||||
initial_cf_options_(SanitizeOptions(db_options, cf_options)),
|
||||
initial_cf_options_(SanitizeCfOptions(db_options, read_only, cf_options)),
|
||||
ioptions_(db_options, initial_cf_options_),
|
||||
mutable_cf_options_(initial_cf_options_),
|
||||
is_delete_range_supported_(
|
||||
@@ -1344,20 +1368,17 @@ bool ColumnFamilyData::ReturnThreadLocalSuperVersion(SuperVersion* sv) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void ColumnFamilyData::InstallSuperVersion(SuperVersionContext* sv_context,
|
||||
InstrumentedMutex* db_mutex) {
|
||||
db_mutex->AssertHeld();
|
||||
return InstallSuperVersion(sv_context, mutable_cf_options_);
|
||||
}
|
||||
|
||||
void ColumnFamilyData::InstallSuperVersion(
|
||||
SuperVersionContext* sv_context,
|
||||
const MutableCFOptions& mutable_cf_options) {
|
||||
SuperVersionContext* sv_context, InstrumentedMutex* db_mutex,
|
||||
std::optional<std::shared_ptr<SeqnoToTimeMapping>>
|
||||
new_seqno_to_time_mapping) {
|
||||
db_mutex->AssertHeld();
|
||||
|
||||
SuperVersion* new_superversion = sv_context->new_superversion.release();
|
||||
new_superversion->mutable_cf_options = mutable_cf_options;
|
||||
new_superversion->mutable_cf_options = GetLatestMutableCFOptions();
|
||||
new_superversion->Init(this, mem_, imm_.current(), current_,
|
||||
sv_context->new_seqno_to_time_mapping
|
||||
? std::move(sv_context->new_seqno_to_time_mapping)
|
||||
new_seqno_to_time_mapping.has_value()
|
||||
? std::move(new_seqno_to_time_mapping.value())
|
||||
: super_version_
|
||||
? super_version_->ShareSeqnoToTimeMapping()
|
||||
: nullptr);
|
||||
@@ -1370,7 +1391,7 @@ void ColumnFamilyData::InstallSuperVersion(
|
||||
// currently RecalculateWriteStallConditions() treats it as further slowing
|
||||
// down is needed.
|
||||
super_version_->write_stall_condition =
|
||||
RecalculateWriteStallConditions(mutable_cf_options);
|
||||
RecalculateWriteStallConditions(new_superversion->mutable_cf_options);
|
||||
} else {
|
||||
super_version_->write_stall_condition =
|
||||
old_superversion->write_stall_condition;
|
||||
@@ -1383,8 +1404,9 @@ void ColumnFamilyData::InstallSuperVersion(
|
||||
ResetThreadLocalSuperVersions();
|
||||
|
||||
if (old_superversion->mutable_cf_options.write_buffer_size !=
|
||||
mutable_cf_options.write_buffer_size) {
|
||||
mem_->UpdateWriteBufferSize(mutable_cf_options.write_buffer_size);
|
||||
new_superversion->mutable_cf_options.write_buffer_size) {
|
||||
mem_->UpdateWriteBufferSize(
|
||||
new_superversion->mutable_cf_options.write_buffer_size);
|
||||
}
|
||||
if (old_superversion->write_stall_condition !=
|
||||
new_superversion->write_stall_condition) {
|
||||
@@ -1685,7 +1707,8 @@ ColumnFamilySet::ColumnFamilySet(const std::string& dbname,
|
||||
dummy_cfd_(new ColumnFamilyData(
|
||||
ColumnFamilyData::kDummyColumnFamilyDataId, "", nullptr, nullptr,
|
||||
nullptr, ColumnFamilyOptions(), *db_options, &file_options_, nullptr,
|
||||
block_cache_tracer, io_tracer, db_id, db_session_id)),
|
||||
block_cache_tracer, io_tracer, db_id, db_session_id,
|
||||
/*read_only*/ true)),
|
||||
default_cfd_cache_(nullptr),
|
||||
db_name_(dbname),
|
||||
db_options_(db_options),
|
||||
@@ -1757,12 +1780,12 @@ size_t ColumnFamilySet::NumberOfColumnFamilies() const {
|
||||
// under a DB mutex AND write thread
|
||||
ColumnFamilyData* ColumnFamilySet::CreateColumnFamily(
|
||||
const std::string& name, uint32_t id, Version* dummy_versions,
|
||||
const ColumnFamilyOptions& options) {
|
||||
const ColumnFamilyOptions& options, bool read_only) {
|
||||
assert(column_families_.find(name) == column_families_.end());
|
||||
ColumnFamilyData* new_cfd = new ColumnFamilyData(
|
||||
id, name, dummy_versions, table_cache_, write_buffer_manager_, options,
|
||||
*db_options_, &file_options_, this, block_cache_tracer_, io_tracer_,
|
||||
db_id_, db_session_id_);
|
||||
db_id_, db_session_id_, read_only);
|
||||
column_families_.insert({name, id});
|
||||
column_family_data_.insert({id, new_cfd});
|
||||
auto ucmp = new_cfd->user_comparator();
|
||||
|
||||
+21
-25
@@ -281,8 +281,9 @@ Status CheckConcurrentWritesSupported(const ColumnFamilyOptions& cf_options);
|
||||
Status CheckCFPathsSupported(const DBOptions& db_options,
|
||||
const ColumnFamilyOptions& cf_options);
|
||||
|
||||
ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
|
||||
const ColumnFamilyOptions& src);
|
||||
ColumnFamilyOptions SanitizeCfOptions(const ImmutableDBOptions& db_options,
|
||||
bool read_only,
|
||||
const ColumnFamilyOptions& src);
|
||||
// Wrap user defined table properties collector factories `from cf_options`
|
||||
// into internal ones in internal_tbl_prop_coll_factories. Add a system internal
|
||||
// one too.
|
||||
@@ -384,10 +385,10 @@ class ColumnFamilyData {
|
||||
Version* dummy_versions() { return dummy_versions_; }
|
||||
Version* current() { return current_; } // REQUIRE: DB mutex held
|
||||
void SetCurrent(Version* _current); // REQUIRE: DB mutex held
|
||||
uint64_t GetNumLiveVersions() const; // REQUIRE: DB mutex held
|
||||
uint64_t GetTotalSstFilesSize() const; // REQUIRE: DB mutex held
|
||||
uint64_t GetLiveSstFilesSize() const; // REQUIRE: DB mutex held
|
||||
uint64_t GetTotalBlobFileSize() const; // REQUIRE: DB mutex held
|
||||
uint64_t GetNumLiveVersions() const; // REQUIRE: DB mutex held
|
||||
uint64_t GetTotalSstFilesSize() const; // REQUIRE: DB mutex held
|
||||
uint64_t GetLiveSstFilesSize() const; // REQUIRE: DB mutex held
|
||||
uint64_t GetTotalBlobFileSize() const; // REQUIRE: DB mutex held
|
||||
// REQUIRE: DB mutex held
|
||||
void SetMemtable(MemTable* new_mem) {
|
||||
AssignMemtableID(new_mem);
|
||||
@@ -490,15 +491,11 @@ class ColumnFamilyData {
|
||||
uint64_t GetSuperVersionNumberRelaxed() const {
|
||||
return super_version_number_.load(std::memory_order_relaxed);
|
||||
}
|
||||
// will return a pointer to SuperVersion* if previous SuperVersion
|
||||
// if its reference count is zero and needs deletion or nullptr if not
|
||||
// As argument takes a pointer to allocated SuperVersion to enable
|
||||
// the clients to allocate SuperVersion outside of mutex.
|
||||
// IMPORTANT: Only call this from DBImpl::InstallSuperVersion()
|
||||
// Only intended for use by DBImpl::InstallSuperVersion() and variants
|
||||
void InstallSuperVersion(SuperVersionContext* sv_context,
|
||||
const MutableCFOptions& mutable_cf_options);
|
||||
void InstallSuperVersion(SuperVersionContext* sv_context,
|
||||
InstrumentedMutex* db_mutex);
|
||||
InstrumentedMutex* db_mutex,
|
||||
std::optional<std::shared_ptr<SeqnoToTimeMapping>>
|
||||
new_seqno_to_time_mapping = {});
|
||||
|
||||
void ResetThreadLocalSuperVersions();
|
||||
|
||||
@@ -593,16 +590,14 @@ class ColumnFamilyData {
|
||||
|
||||
private:
|
||||
friend class ColumnFamilySet;
|
||||
ColumnFamilyData(uint32_t id, const std::string& name,
|
||||
Version* dummy_versions, Cache* table_cache,
|
||||
WriteBufferManager* write_buffer_manager,
|
||||
const ColumnFamilyOptions& options,
|
||||
const ImmutableDBOptions& db_options,
|
||||
const FileOptions* file_options,
|
||||
ColumnFamilySet* column_family_set,
|
||||
BlockCacheTracer* const block_cache_tracer,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::string& db_id, const std::string& db_session_id);
|
||||
ColumnFamilyData(
|
||||
uint32_t id, const std::string& name, Version* dummy_versions,
|
||||
Cache* table_cache, WriteBufferManager* write_buffer_manager,
|
||||
const ColumnFamilyOptions& options, const ImmutableDBOptions& db_options,
|
||||
const FileOptions* file_options, ColumnFamilySet* column_family_set,
|
||||
BlockCacheTracer* const block_cache_tracer,
|
||||
const std::shared_ptr<IOTracer>& io_tracer, const std::string& db_id,
|
||||
const std::string& db_session_id, bool read_only);
|
||||
|
||||
std::vector<std::string> GetDbPaths() const;
|
||||
|
||||
@@ -764,7 +759,8 @@ class ColumnFamilySet {
|
||||
|
||||
ColumnFamilyData* CreateColumnFamily(const std::string& name, uint32_t id,
|
||||
Version* dummy_version,
|
||||
const ColumnFamilyOptions& options);
|
||||
const ColumnFamilyOptions& options,
|
||||
bool read_only);
|
||||
|
||||
const UnorderedMap<uint32_t, size_t>& GetRunningColumnFamiliesTimestampSize()
|
||||
const {
|
||||
|
||||
@@ -271,7 +271,8 @@ class ColumnFamilyTestBase : public testing::Test {
|
||||
// them.
|
||||
ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(
|
||||
ConfigOptions(), desc.options,
|
||||
SanitizeOptions(dbfull()->immutable_db_options(), current_cf_opt)));
|
||||
SanitizeCfOptions(dbfull()->immutable_db_options(),
|
||||
/*read_only*/ false, current_cf_opt)));
|
||||
cfi++;
|
||||
}
|
||||
}
|
||||
@@ -2232,7 +2233,7 @@ TEST_P(ColumnFamilyTest, CreateMissingColumnFamilies) {
|
||||
ASSERT_EQ(my_fs->options_files_created.load(), 2);
|
||||
}
|
||||
|
||||
TEST_P(ColumnFamilyTest, SanitizeOptions) {
|
||||
TEST_P(ColumnFamilyTest, SanitizeCfOptions) {
|
||||
DBOptions db_options;
|
||||
for (int s = kCompactionStyleLevel; s <= kCompactionStyleUniversal; ++s) {
|
||||
for (int l = 0; l <= 2; l++) {
|
||||
@@ -2248,8 +2249,8 @@ TEST_P(ColumnFamilyTest, SanitizeOptions) {
|
||||
original.write_buffer_size =
|
||||
l * 4 * 1024 * 1024 + i * 1024 * 1024 + j * 1024 + k;
|
||||
|
||||
ColumnFamilyOptions result =
|
||||
SanitizeOptions(ImmutableDBOptions(db_options), original);
|
||||
ColumnFamilyOptions result = SanitizeCfOptions(
|
||||
ImmutableDBOptions(db_options), /*read_only*/ false, original);
|
||||
ASSERT_TRUE(result.level0_stop_writes_trigger >=
|
||||
result.level0_slowdown_writes_trigger);
|
||||
ASSERT_TRUE(result.level0_slowdown_writes_trigger >=
|
||||
|
||||
+50
-54
@@ -338,16 +338,16 @@ Compaction::Compaction(
|
||||
_blob_garbage_collection_age_cutoff > 1
|
||||
? mutable_cf_options().blob_garbage_collection_age_cutoff
|
||||
: _blob_garbage_collection_age_cutoff),
|
||||
penultimate_level_(
|
||||
// For simplicity, we don't support the concept of "penultimate level"
|
||||
proximal_level_(
|
||||
// For simplicity, we don't support the concept of "proximal level"
|
||||
// with `CompactionReason::kExternalSstIngestion` and
|
||||
// `CompactionReason::kRefitLevel`
|
||||
_compaction_reason == CompactionReason::kExternalSstIngestion ||
|
||||
_compaction_reason == CompactionReason::kRefitLevel
|
||||
? Compaction::kInvalidLevel
|
||||
: EvaluatePenultimateLevel(vstorage, mutable_cf_options_,
|
||||
immutable_options_, start_level_,
|
||||
output_level_)) {
|
||||
: EvaluateProximalLevel(vstorage, mutable_cf_options_,
|
||||
immutable_options_, start_level_,
|
||||
output_level_)) {
|
||||
MarkFilesBeingCompacted(true);
|
||||
if (is_manual_compaction_) {
|
||||
compaction_reason_ = CompactionReason::kManualCompaction;
|
||||
@@ -405,10 +405,10 @@ Compaction::Compaction(
|
||||
}
|
||||
}
|
||||
|
||||
PopulatePenultimateLevelOutputRange();
|
||||
PopulateProximalLevelOutputRange();
|
||||
}
|
||||
|
||||
void Compaction::PopulatePenultimateLevelOutputRange() {
|
||||
void Compaction::PopulateProximalLevelOutputRange() {
|
||||
if (!SupportsPerKeyPlacement()) {
|
||||
assert(keep_in_last_level_through_seqno_ == kMaxSequenceNumber);
|
||||
return;
|
||||
@@ -417,46 +417,42 @@ void Compaction::PopulatePenultimateLevelOutputRange() {
|
||||
// exclude the last level, the range of all input levels is the safe range
|
||||
// of keys that can be moved up.
|
||||
int exclude_level = number_levels_ - 1;
|
||||
penultimate_output_range_type_ = PenultimateOutputRangeType::kNonLastRange;
|
||||
proximal_output_range_type_ = ProximalOutputRangeType::kNonLastRange;
|
||||
|
||||
// For universal compaction, the penultimate_output_range could be extended if
|
||||
// all penultimate level files are included in the compaction (which includes
|
||||
// the case that the penultimate level is empty).
|
||||
// For universal compaction, the proximal_output_range could be extended if
|
||||
// all proximal level files are included in the compaction (which includes
|
||||
// the case that the proximal level is empty).
|
||||
if (immutable_options_.compaction_style == kCompactionStyleUniversal) {
|
||||
exclude_level = kInvalidLevel;
|
||||
penultimate_output_range_type_ = PenultimateOutputRangeType::kFullRange;
|
||||
std::set<uint64_t> penultimate_inputs;
|
||||
proximal_output_range_type_ = ProximalOutputRangeType::kFullRange;
|
||||
std::set<uint64_t> proximal_inputs;
|
||||
for (const auto& input_lvl : inputs_) {
|
||||
if (input_lvl.level == penultimate_level_) {
|
||||
if (input_lvl.level == proximal_level_) {
|
||||
for (const auto& file : input_lvl.files) {
|
||||
penultimate_inputs.emplace(file->fd.GetNumber());
|
||||
proximal_inputs.emplace(file->fd.GetNumber());
|
||||
}
|
||||
}
|
||||
}
|
||||
auto penultimate_files = input_vstorage_->LevelFiles(penultimate_level_);
|
||||
for (const auto& file : penultimate_files) {
|
||||
if (penultimate_inputs.find(file->fd.GetNumber()) ==
|
||||
penultimate_inputs.end()) {
|
||||
auto proximal_files = input_vstorage_->LevelFiles(proximal_level_);
|
||||
for (const auto& file : proximal_files) {
|
||||
if (proximal_inputs.find(file->fd.GetNumber()) == proximal_inputs.end()) {
|
||||
exclude_level = number_levels_ - 1;
|
||||
penultimate_output_range_type_ =
|
||||
PenultimateOutputRangeType::kNonLastRange;
|
||||
proximal_output_range_type_ = ProximalOutputRangeType::kNonLastRange;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: should make use of `penultimate_output_range_type_`.
|
||||
// FIXME: should make use of `proximal_output_range_type_`.
|
||||
// FIXME: when last level's input range does not overlap with
|
||||
// penultimate level, and penultimate level input is empty,
|
||||
// this call will not set penultimate_level_smallest_ or
|
||||
// penultimate_level_largest_. No keys will be compacted up.
|
||||
GetBoundaryInternalKeys(input_vstorage_, inputs_,
|
||||
&penultimate_level_smallest_,
|
||||
&penultimate_level_largest_, exclude_level);
|
||||
// proximal level, and proximal level input is empty,
|
||||
// this call will not set proximal_level_smallest_ or
|
||||
// proximal_level_largest_. No keys will be compacted up.
|
||||
GetBoundaryInternalKeys(input_vstorage_, inputs_, &proximal_level_smallest_,
|
||||
&proximal_level_largest_, exclude_level);
|
||||
|
||||
if (penultimate_output_range_type_ !=
|
||||
PenultimateOutputRangeType::kFullRange) {
|
||||
// If not full range in penultimate level, must keep everything already
|
||||
if (proximal_output_range_type_ != ProximalOutputRangeType::kFullRange) {
|
||||
// If not full range in proximal level, must keep everything already
|
||||
// in the last level there, because moving it back up might cause
|
||||
// overlap/placement issues that are difficult to resolve properly in the
|
||||
// presence of range deletes
|
||||
@@ -486,23 +482,23 @@ Compaction::~Compaction() {
|
||||
}
|
||||
|
||||
bool Compaction::SupportsPerKeyPlacement() const {
|
||||
return penultimate_level_ != kInvalidLevel;
|
||||
return proximal_level_ != kInvalidLevel;
|
||||
}
|
||||
|
||||
int Compaction::GetPenultimateLevel() const { return penultimate_level_; }
|
||||
int Compaction::GetProximalLevel() const { return proximal_level_; }
|
||||
|
||||
// smallest_key and largest_key include timestamps if user-defined timestamp is
|
||||
// enabled.
|
||||
bool Compaction::OverlapPenultimateLevelOutputRange(
|
||||
bool Compaction::OverlapProximalLevelOutputRange(
|
||||
const Slice& smallest_key, const Slice& largest_key) const {
|
||||
if (!SupportsPerKeyPlacement()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// See FIXME in Compaction::PopulatePenultimateLevelOutputRange().
|
||||
// See FIXME in Compaction::PopulateProximalLevelOutputRange().
|
||||
// We do not compact any key up in this case.
|
||||
if (penultimate_level_smallest_.size() == 0 ||
|
||||
penultimate_level_largest_.size() == 0) {
|
||||
if (proximal_level_smallest_.size() == 0 ||
|
||||
proximal_level_largest_.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -510,13 +506,13 @@ bool Compaction::OverlapPenultimateLevelOutputRange(
|
||||
input_vstorage_->InternalComparator()->user_comparator();
|
||||
|
||||
return ucmp->CompareWithoutTimestamp(
|
||||
smallest_key, penultimate_level_largest_.user_key()) <= 0 &&
|
||||
smallest_key, proximal_level_largest_.user_key()) <= 0 &&
|
||||
ucmp->CompareWithoutTimestamp(
|
||||
largest_key, penultimate_level_smallest_.user_key()) >= 0;
|
||||
largest_key, proximal_level_smallest_.user_key()) >= 0;
|
||||
}
|
||||
|
||||
// key includes timestamp if user-defined timestamp is enabled.
|
||||
void Compaction::TEST_AssertWithinPenultimateLevelOutputRange(
|
||||
void Compaction::TEST_AssertWithinProximalLevelOutputRange(
|
||||
const Slice& user_key, bool expect_failure) const {
|
||||
#ifdef NDEBUG
|
||||
(void)user_key;
|
||||
@@ -524,15 +520,15 @@ void Compaction::TEST_AssertWithinPenultimateLevelOutputRange(
|
||||
#else
|
||||
assert(SupportsPerKeyPlacement());
|
||||
|
||||
assert(penultimate_level_smallest_.size() > 0);
|
||||
assert(penultimate_level_largest_.size() > 0);
|
||||
assert(proximal_level_smallest_.size() > 0);
|
||||
assert(proximal_level_largest_.size() > 0);
|
||||
|
||||
auto* cmp = input_vstorage_->user_comparator();
|
||||
|
||||
// op_type of a key can change during compaction, e.g. Merge -> Put.
|
||||
if (!(cmp->Compare(user_key, penultimate_level_smallest_.user_key()) >= 0)) {
|
||||
if (!(cmp->Compare(user_key, proximal_level_smallest_.user_key()) >= 0)) {
|
||||
assert(expect_failure);
|
||||
} else if (!(cmp->Compare(user_key, penultimate_level_largest_.user_key()) <=
|
||||
} else if (!(cmp->Compare(user_key, proximal_level_largest_.user_key()) <=
|
||||
0)) {
|
||||
assert(expect_failure);
|
||||
} else {
|
||||
@@ -1018,7 +1014,7 @@ uint64_t Compaction::MinInputFileEpochNumber() const {
|
||||
return min_epoch_number;
|
||||
}
|
||||
|
||||
int Compaction::EvaluatePenultimateLevel(
|
||||
int Compaction::EvaluateProximalLevel(
|
||||
const VersionStorageInfo* vstorage,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const ImmutableOptions& immutable_options, const int start_level,
|
||||
@@ -1033,21 +1029,21 @@ int Compaction::EvaluatePenultimateLevel(
|
||||
return kInvalidLevel;
|
||||
}
|
||||
|
||||
int penultimate_level = output_level - 1;
|
||||
assert(penultimate_level < immutable_options.num_levels);
|
||||
if (penultimate_level <= 0) {
|
||||
int proximal_level = output_level - 1;
|
||||
assert(proximal_level < immutable_options.num_levels);
|
||||
if (proximal_level <= 0) {
|
||||
return kInvalidLevel;
|
||||
}
|
||||
|
||||
// If the penultimate level is not within input level -> output level range
|
||||
// check if the penultimate output level is empty, if it's empty, it could
|
||||
// also be locked for the penultimate output.
|
||||
// If the proximal level is not within input level -> output level range
|
||||
// check if the proximal output level is empty, if it's empty, it could
|
||||
// also be locked for the proximal output.
|
||||
// TODO: ideally, it only needs to check if there's a file within the
|
||||
// compaction output key range. For simplicity, it just check if there's any
|
||||
// file on the penultimate level.
|
||||
// file on the proximal level.
|
||||
if (start_level == immutable_options.num_levels - 1 &&
|
||||
(immutable_options.compaction_style != kCompactionStyleUniversal ||
|
||||
!vstorage->LevelFiles(penultimate_level).empty())) {
|
||||
!vstorage->LevelFiles(proximal_level).empty())) {
|
||||
return kInvalidLevel;
|
||||
}
|
||||
|
||||
@@ -1061,7 +1057,7 @@ int Compaction::EvaluatePenultimateLevel(
|
||||
return kInvalidLevel;
|
||||
}
|
||||
|
||||
return penultimate_level;
|
||||
return proximal_level;
|
||||
}
|
||||
|
||||
void Compaction::FilterInputsForCompactionIterator() {
|
||||
|
||||
+43
-43
@@ -102,13 +102,13 @@ class Compaction {
|
||||
BlobGarbageCollectionPolicy::kUseDefault,
|
||||
double blob_garbage_collection_age_cutoff = -1);
|
||||
|
||||
// The type of the penultimate level output range
|
||||
enum class PenultimateOutputRangeType : int {
|
||||
kNotSupported, // it cannot output to the penultimate level
|
||||
kFullRange, // any data could be output to the penultimate level
|
||||
// The type of the proximal level output range
|
||||
enum class ProximalOutputRangeType : int {
|
||||
kNotSupported, // it cannot output to the proximal level
|
||||
kFullRange, // any data could be output to the proximal level
|
||||
kNonLastRange, // only the keys within non_last_level compaction inputs can
|
||||
// be outputted to the penultimate level
|
||||
kDisabled, // no data can be outputted to the penultimate level
|
||||
// be outputted to the proximal level
|
||||
kDisabled, // no data can be outputted to the proximal level
|
||||
};
|
||||
|
||||
// No copying allowed
|
||||
@@ -370,29 +370,29 @@ class Compaction {
|
||||
|
||||
Slice GetLargestUserKey() const { return largest_user_key_; }
|
||||
|
||||
PenultimateOutputRangeType GetPenultimateOutputRangeType() const {
|
||||
return penultimate_output_range_type_;
|
||||
ProximalOutputRangeType GetProximalOutputRangeType() const {
|
||||
return proximal_output_range_type_;
|
||||
}
|
||||
|
||||
// Return true if the compaction supports per_key_placement
|
||||
bool SupportsPerKeyPlacement() const;
|
||||
|
||||
// Get per_key_placement penultimate output level, which is `last_level - 1`
|
||||
// Get per_key_placement proximal output level, which is `last_level - 1`
|
||||
// if per_key_placement feature is supported. Otherwise, return -1.
|
||||
int GetPenultimateLevel() const;
|
||||
int GetProximalLevel() const;
|
||||
|
||||
// Return true if the given range is overlap with penultimate level output
|
||||
// Return true if the given range is overlap with proximal level output
|
||||
// range.
|
||||
// Both smallest_key and largest_key include timestamps if user-defined
|
||||
// timestamp is enabled.
|
||||
bool OverlapPenultimateLevelOutputRange(const Slice& smallest_key,
|
||||
const Slice& largest_key) const;
|
||||
bool OverlapProximalLevelOutputRange(const Slice& smallest_key,
|
||||
const Slice& largest_key) const;
|
||||
|
||||
// For testing purposes, check that a key is within penultimate level
|
||||
// For testing purposes, check that a key is within proximal level
|
||||
// output range for per_key_placement feature, which is safe to place the key
|
||||
// to the penultimate level. Different compaction strategies have different
|
||||
// to the proximal level. Different compaction strategies have different
|
||||
// rules. `user_key` includes timestamp if user-defined timestamp is enabled.
|
||||
void TEST_AssertWithinPenultimateLevelOutputRange(
|
||||
void TEST_AssertWithinProximalLevelOutputRange(
|
||||
const Slice& user_key, bool expect_failure = false) const;
|
||||
|
||||
CompactionReason compaction_reason() const { return compaction_reason_; }
|
||||
@@ -441,20 +441,20 @@ class Compaction {
|
||||
|
||||
static constexpr int kInvalidLevel = -1;
|
||||
|
||||
// Evaluate penultimate output level. If the compaction supports
|
||||
// per_key_placement feature, it returns the penultimate level number.
|
||||
// Evaluate proximal output level. If the compaction supports
|
||||
// per_key_placement feature, it returns the proximal level number.
|
||||
// Otherwise, it's set to kInvalidLevel (-1), which means
|
||||
// output_to_penultimate_level is not supported.
|
||||
// Note: even the penultimate level output is supported (PenultimateLevel !=
|
||||
// output_to_proximal_level is not supported.
|
||||
// Note: even the proximal level output is supported (ProximalLevel !=
|
||||
// kInvalidLevel), some key range maybe unsafe to be outputted to the
|
||||
// penultimate level. The safe key range is populated by
|
||||
// `PopulatePenultimateLevelOutputRange()`.
|
||||
// Which could potentially disable all penultimate level output.
|
||||
static int EvaluatePenultimateLevel(
|
||||
const VersionStorageInfo* vstorage,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const ImmutableOptions& immutable_options, const int start_level,
|
||||
const int output_level);
|
||||
// proximal level. The safe key range is populated by
|
||||
// `PopulateProximalLevelOutputRange()`.
|
||||
// Which could potentially disable all proximal level output.
|
||||
static int EvaluateProximalLevel(const VersionStorageInfo* vstorage,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const ImmutableOptions& immutable_options,
|
||||
const int start_level,
|
||||
const int output_level);
|
||||
|
||||
// If some data cannot be safely migrated "up" the LSM tree due to a change
|
||||
// in the preclude_last_level_data_seconds setting, this indicates a sequence
|
||||
@@ -482,10 +482,10 @@ class Compaction {
|
||||
InternalKey* smallest_key, InternalKey* largest_key,
|
||||
int exclude_level = -1);
|
||||
|
||||
// populate penultimate level output range, which will be used to determine if
|
||||
// a key is safe to output to the penultimate level (details see
|
||||
// `Compaction::WithinPenultimateLevelOutputRange()`.
|
||||
void PopulatePenultimateLevelOutputRange();
|
||||
// populate proximal level output range, which will be used to determine if
|
||||
// a key is safe to output to the proximal level (details see
|
||||
// `Compaction::WithinProximalLevelOutputRange()`.
|
||||
void PopulateProximalLevelOutputRange();
|
||||
|
||||
// If oldest snapshot is specified at Compaction construction time, we have
|
||||
// an opportunity to optimize inputs for compaction iterator for this case:
|
||||
@@ -616,20 +616,20 @@ class Compaction {
|
||||
|
||||
// only set when per_key_placement feature is enabled, -1 (kInvalidLevel)
|
||||
// means not supported.
|
||||
const int penultimate_level_;
|
||||
const int proximal_level_;
|
||||
|
||||
// Key range for penultimate level output
|
||||
// Key range for proximal level output
|
||||
// includes timestamp if user-defined timestamp is enabled.
|
||||
// penultimate_output_range_type_ shows the range type
|
||||
InternalKey penultimate_level_smallest_;
|
||||
InternalKey penultimate_level_largest_;
|
||||
PenultimateOutputRangeType penultimate_output_range_type_ =
|
||||
PenultimateOutputRangeType::kNotSupported;
|
||||
// proximal_output_range_type_ shows the range type
|
||||
InternalKey proximal_level_smallest_;
|
||||
InternalKey proximal_level_largest_;
|
||||
ProximalOutputRangeType proximal_output_range_type_ =
|
||||
ProximalOutputRangeType::kNotSupported;
|
||||
};
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Helper struct only for tests, which contains the data to decide if a key
|
||||
// should be output to the penultimate level.
|
||||
// should be output to the proximal level.
|
||||
// TODO: remove this when the public feature knob is available
|
||||
struct PerKeyPlacementContext {
|
||||
const int level;
|
||||
@@ -637,16 +637,16 @@ struct PerKeyPlacementContext {
|
||||
const Slice value;
|
||||
const SequenceNumber seq_num;
|
||||
|
||||
bool& output_to_penultimate_level;
|
||||
bool& output_to_proximal_level;
|
||||
|
||||
PerKeyPlacementContext(int _level, Slice _key, Slice _value,
|
||||
SequenceNumber _seq_num,
|
||||
bool& _output_to_penultimate_level)
|
||||
bool& _output_to_proximal_level)
|
||||
: level(_level),
|
||||
key(_key),
|
||||
value(_value),
|
||||
seq_num(_seq_num),
|
||||
output_to_penultimate_level(_output_to_penultimate_level) {}
|
||||
output_to_proximal_level(_output_to_proximal_level) {}
|
||||
};
|
||||
#endif /* !NDEBUG */
|
||||
|
||||
|
||||
+269
-160
@@ -109,16 +109,16 @@ const char* GetCompactionReasonString(CompactionReason compaction_reason) {
|
||||
}
|
||||
}
|
||||
|
||||
const char* GetCompactionPenultimateOutputRangeTypeString(
|
||||
Compaction::PenultimateOutputRangeType range_type) {
|
||||
const char* GetCompactionProximalOutputRangeTypeString(
|
||||
Compaction::ProximalOutputRangeType range_type) {
|
||||
switch (range_type) {
|
||||
case Compaction::PenultimateOutputRangeType::kNotSupported:
|
||||
case Compaction::ProximalOutputRangeType::kNotSupported:
|
||||
return "NotSupported";
|
||||
case Compaction::PenultimateOutputRangeType::kFullRange:
|
||||
case Compaction::ProximalOutputRangeType::kFullRange:
|
||||
return "FullRange";
|
||||
case Compaction::PenultimateOutputRangeType::kNonLastRange:
|
||||
case Compaction::ProximalOutputRangeType::kNonLastRange:
|
||||
return "NonLastRange";
|
||||
case Compaction::PenultimateOutputRangeType::kDisabled:
|
||||
case Compaction::ProximalOutputRangeType::kDisabled:
|
||||
return "Disabled";
|
||||
default:
|
||||
assert(false);
|
||||
@@ -147,7 +147,7 @@ CompactionJob::CompactionJob(
|
||||
BlobFileCompletionCallback* blob_callback, int* bg_compaction_scheduled,
|
||||
int* bg_bottom_compaction_scheduled)
|
||||
: compact_(new CompactionState(compaction)),
|
||||
compaction_stats_(compaction->compaction_reason(), 1),
|
||||
internal_stats_(compaction->compaction_reason(), 1),
|
||||
db_options_(db_options),
|
||||
mutable_db_options_copy_(mutable_db_options),
|
||||
log_buffer_(log_buffer),
|
||||
@@ -155,7 +155,7 @@ CompactionJob::CompactionJob(
|
||||
stats_(stats),
|
||||
bottommost_level_(false),
|
||||
write_hint_(Env::WLTH_NOT_SET),
|
||||
compaction_job_stats_(compaction_job_stats),
|
||||
job_stats_(compaction_job_stats),
|
||||
job_id_(job_id),
|
||||
dbname_(dbname),
|
||||
db_id_(db_id),
|
||||
@@ -191,7 +191,7 @@ CompactionJob::CompactionJob(
|
||||
extra_num_subcompaction_threads_reserved_(0),
|
||||
bg_compaction_scheduled_(bg_compaction_scheduled),
|
||||
bg_bottom_compaction_scheduled_(bg_bottom_compaction_scheduled) {
|
||||
assert(compaction_job_stats_ != nullptr);
|
||||
assert(job_stats_ != nullptr);
|
||||
assert(log_buffer_ != nullptr);
|
||||
|
||||
const auto* cfd = compact_->compaction->column_family_data();
|
||||
@@ -240,9 +240,8 @@ void CompactionJob::ReportStartedCompaction(Compaction* compaction) {
|
||||
// to ensure GetThreadList() can always show them all together.
|
||||
ThreadStatusUtil::SetThreadOperation(ThreadStatus::OP_COMPACTION);
|
||||
|
||||
compaction_job_stats_->is_manual_compaction =
|
||||
compaction->is_manual_compaction();
|
||||
compaction_job_stats_->is_full_compaction = compaction->is_full_compaction();
|
||||
job_stats_->is_manual_compaction = compaction->is_manual_compaction();
|
||||
job_stats_->is_full_compaction = compaction->is_full_compaction();
|
||||
}
|
||||
|
||||
void CompactionJob::Prepare(
|
||||
@@ -260,7 +259,8 @@ void CompactionJob::Prepare(
|
||||
assert(storage_info);
|
||||
assert(storage_info->NumLevelFiles(compact_->compaction->level()) > 0);
|
||||
|
||||
write_hint_ = storage_info->CalculateSSTWriteHint(c->output_level());
|
||||
write_hint_ = storage_info->CalculateSSTWriteHint(
|
||||
c->output_level(), db_options_.calculate_sst_write_lifetime_hint_set);
|
||||
bottommost_level_ = c->bottommost_level();
|
||||
|
||||
if (!known_single_subcompact.has_value() && c->ShouldFormSubcompactions()) {
|
||||
@@ -301,8 +301,7 @@ void CompactionJob::Prepare(
|
||||
SequenceNumber preserve_time_min_seqno = kMaxSequenceNumber;
|
||||
SequenceNumber preclude_last_level_min_seqno = kMaxSequenceNumber;
|
||||
uint64_t preserve_time_duration =
|
||||
std::max(c->mutable_cf_options().preserve_internal_time_seconds,
|
||||
c->mutable_cf_options().preclude_last_level_data_seconds);
|
||||
MinAndMaxPreserveSeconds(c->mutable_cf_options()).max_preserve_seconds;
|
||||
|
||||
if (preserve_time_duration > 0) {
|
||||
const ReadOptions read_options(Env::IOActivity::kCompaction);
|
||||
@@ -379,8 +378,8 @@ void CompactionJob::Prepare(
|
||||
}
|
||||
// Now combine what we would like to preclude from last level with what we
|
||||
// can safely support without dangerously moving data back up the LSM tree,
|
||||
// to get the final seqno threshold for penultimate vs. last. In particular,
|
||||
// when the reserved output key range for the penultimate level does not
|
||||
// to get the final seqno threshold for proximal vs. last. In particular,
|
||||
// when the reserved output key range for the proximal level does not
|
||||
// include the entire last level input key range, we need to keep entries
|
||||
// already in the last level there. (Even allowing within-range entries to
|
||||
// move back up could cause problems with range tombstones. Perhaps it
|
||||
@@ -389,8 +388,8 @@ void CompactionJob::Prepare(
|
||||
// tracking and complexity to CompactionIterator that is probably not
|
||||
// worthwhile overall. Correctness is also more clear when splitting by
|
||||
// seqno threshold.)
|
||||
penultimate_after_seqno_ = std::max(preclude_last_level_min_seqno,
|
||||
c->GetKeepInLastLevelThroughSeqno());
|
||||
proximal_after_seqno_ = std::max(preclude_last_level_min_seqno,
|
||||
c->GetKeepInLastLevelThroughSeqno());
|
||||
|
||||
options_file_number_ = versions_->options_file_number();
|
||||
}
|
||||
@@ -696,17 +695,17 @@ Status CompactionJob::Run() {
|
||||
thread.join();
|
||||
}
|
||||
|
||||
compaction_stats_.SetMicros(db_options_.clock->NowMicros() - start_micros);
|
||||
internal_stats_.SetMicros(db_options_.clock->NowMicros() - start_micros);
|
||||
|
||||
for (auto& state : compact_->sub_compact_states) {
|
||||
compaction_stats_.AddCpuMicros(state.compaction_job_stats.cpu_micros);
|
||||
internal_stats_.AddCpuMicros(state.compaction_job_stats.cpu_micros);
|
||||
state.RemoveLastEmptyOutput();
|
||||
}
|
||||
|
||||
RecordTimeToHistogram(stats_, COMPACTION_TIME,
|
||||
compaction_stats_.stats.micros);
|
||||
internal_stats_.output_level_stats.micros);
|
||||
RecordTimeToHistogram(stats_, COMPACTION_CPU_TIME,
|
||||
compaction_stats_.stats.cpu_micros);
|
||||
internal_stats_.output_level_stats.cpu_micros);
|
||||
|
||||
TEST_SYNC_POINT("CompactionJob::Run:BeforeVerify");
|
||||
|
||||
@@ -841,7 +840,6 @@ Status CompactionJob::Run() {
|
||||
TEST_SYNC_POINT("CompactionJob::ReleaseSubcompactionResources:0");
|
||||
TEST_SYNC_POINT("CompactionJob::ReleaseSubcompactionResources:1");
|
||||
|
||||
TablePropertiesCollection tp;
|
||||
for (const auto& state : compact_->sub_compact_states) {
|
||||
for (const auto& output : state.GetOutputs()) {
|
||||
auto fn =
|
||||
@@ -856,46 +854,60 @@ Status CompactionJob::Run() {
|
||||
// compaction_service is set. We now know whether each sub_compaction was
|
||||
// done remotely or not. Reset is_remote_compaction back to false and allow
|
||||
// AggregateCompactionStats() to set the right value.
|
||||
compaction_job_stats_->is_remote_compaction = false;
|
||||
job_stats_->is_remote_compaction = false;
|
||||
|
||||
// Finish up all bookkeeping to unify the subcompaction results.
|
||||
compact_->AggregateCompactionStats(compaction_stats_, *compaction_job_stats_);
|
||||
compact_->AggregateCompactionStats(internal_stats_, *job_stats_);
|
||||
|
||||
uint64_t num_input_range_del = 0;
|
||||
bool ok = UpdateCompactionStats(&num_input_range_del);
|
||||
// (Sub)compactions returned ok, do sanity check on the number of input keys.
|
||||
if (status.ok() && ok && compaction_job_stats_->has_num_input_records) {
|
||||
size_t ts_sz = compact_->compaction->column_family_data()
|
||||
->user_comparator()
|
||||
->timestamp_size();
|
||||
// When trim_ts_ is non-empty, CompactionIterator takes
|
||||
// HistoryTrimmingIterator as input iterator and sees a trimmed view of
|
||||
// input keys. So the number of keys it processed is not suitable for
|
||||
// verification here.
|
||||
// TODO: support verification when trim_ts_ is non-empty.
|
||||
if (!(ts_sz > 0 && !trim_ts_.empty())) {
|
||||
assert(compaction_stats_.stats.num_input_records > 0);
|
||||
// TODO: verify the number of range deletion entries.
|
||||
uint64_t expected =
|
||||
compaction_stats_.stats.num_input_records - num_input_range_del;
|
||||
uint64_t actual = compaction_job_stats_->num_input_records;
|
||||
if (expected != actual) {
|
||||
char scratch[2345];
|
||||
compact_->compaction->Summary(scratch, sizeof(scratch));
|
||||
std::string msg =
|
||||
"Compaction number of input keys does not match "
|
||||
"number of keys processed. Expected " +
|
||||
std::to_string(expected) + " but processed " +
|
||||
std::to_string(actual) + ". Compaction summary: " + scratch;
|
||||
bool ok = BuildStatsFromInputTableProperties(&num_input_range_del);
|
||||
// (Sub)compactions returned ok, do sanity check on the number of input
|
||||
// keys.
|
||||
if (status.ok() && ok) {
|
||||
if (job_stats_->has_num_input_records) {
|
||||
status = VerifyInputRecordCount(num_input_range_del);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log, "[%s] [JOB %d] Compaction with status: %s",
|
||||
compact_->compaction->column_family_data()->GetName().c_str(),
|
||||
job_context_->job_id, msg.c_str());
|
||||
if (db_options_.compaction_verify_record_count) {
|
||||
status = Status::Corruption(msg);
|
||||
}
|
||||
job_context_->job_id, status.ToString().c_str());
|
||||
}
|
||||
}
|
||||
UpdateCompactionJobInputStats(internal_stats_, num_input_range_del);
|
||||
}
|
||||
UpdateCompactionJobOutputStats(internal_stats_);
|
||||
|
||||
// Verify number of output records
|
||||
if (status.ok() && db_options_.compaction_verify_record_count) {
|
||||
uint64_t total_output_num = 0;
|
||||
for (const auto& state : compact_->sub_compact_states) {
|
||||
for (const auto& output : state.GetOutputs()) {
|
||||
total_output_num += output.table_properties->num_entries -
|
||||
output.table_properties->num_range_deletions;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t expected = internal_stats_.output_level_stats.num_output_records;
|
||||
if (internal_stats_.has_proximal_level_output) {
|
||||
expected += internal_stats_.proximal_level_stats.num_output_records;
|
||||
}
|
||||
if (expected != total_output_num) {
|
||||
char scratch[2345];
|
||||
compact_->compaction->Summary(scratch, sizeof(scratch));
|
||||
std::string msg =
|
||||
"Number of keys in compaction output SST files does not match "
|
||||
"number of keys added. Expected " +
|
||||
std::to_string(expected) + " but there are " +
|
||||
std::to_string(total_output_num) +
|
||||
" in output SST files. Compaction summary: " + scratch;
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log, "[%s] [JOB %d] Compaction with status: %s",
|
||||
compact_->compaction->column_family_data()->GetName().c_str(),
|
||||
job_context_->job_id, msg.c_str());
|
||||
status = Status::Corruption(msg);
|
||||
}
|
||||
}
|
||||
|
||||
RecordCompactionIOStats();
|
||||
LogFlush(db_options_.info_log);
|
||||
TEST_SYNC_POINT("CompactionJob::Run():End");
|
||||
@@ -917,7 +929,7 @@ Status CompactionJob::Install(bool* compaction_released) {
|
||||
|
||||
int output_level = compact_->compaction->output_level();
|
||||
cfd->internal_stats()->AddCompactionStats(output_level, thread_pri_,
|
||||
compaction_stats_);
|
||||
internal_stats_);
|
||||
|
||||
if (status.ok()) {
|
||||
status = InstallCompactionResults(compaction_released);
|
||||
@@ -928,7 +940,7 @@ Status CompactionJob::Install(bool* compaction_released) {
|
||||
|
||||
VersionStorageInfo::LevelSummaryStorage tmp;
|
||||
auto vstorage = cfd->current()->storage_info();
|
||||
const auto& stats = compaction_stats_.stats;
|
||||
const auto& stats = internal_stats_.output_level_stats;
|
||||
|
||||
double read_write_amp = 0.0;
|
||||
double write_amp = 0.0;
|
||||
@@ -994,19 +1006,20 @@ Status CompactionJob::Install(bool* compaction_released) {
|
||||
blob_files.back()->GetBlobFileNumber());
|
||||
}
|
||||
|
||||
if (compaction_stats_.has_penultimate_level_output) {
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer_,
|
||||
"[%s] has Penultimate Level output: %" PRIu64
|
||||
", level %d, number of files: %" PRIu64 ", number of records: %" PRIu64,
|
||||
column_family_name.c_str(),
|
||||
compaction_stats_.penultimate_level_stats.bytes_written,
|
||||
compact_->compaction->GetPenultimateLevel(),
|
||||
compaction_stats_.penultimate_level_stats.num_output_files,
|
||||
compaction_stats_.penultimate_level_stats.num_output_records);
|
||||
if (internal_stats_.has_proximal_level_output) {
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] has Proximal Level output: %" PRIu64
|
||||
", level %d, number of files: %" PRIu64
|
||||
", number of records: %" PRIu64,
|
||||
column_family_name.c_str(),
|
||||
internal_stats_.proximal_level_stats.bytes_written,
|
||||
compact_->compaction->GetProximalLevel(),
|
||||
internal_stats_.proximal_level_stats.num_output_files,
|
||||
internal_stats_.proximal_level_stats.num_output_records);
|
||||
}
|
||||
|
||||
UpdateCompactionJobStats(stats);
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"CompactionJob::Install:AfterUpdateCompactionJobStats", job_stats_);
|
||||
|
||||
auto stream = event_logger_->LogToBuffer(log_buffer_, 8192);
|
||||
stream << "job" << job_id_ << "event" << "compaction_finished"
|
||||
@@ -1028,17 +1041,16 @@ Status CompactionJob::Install(bool* compaction_released) {
|
||||
<< CompressionTypeToString(compact_->compaction->output_compression());
|
||||
|
||||
stream << "num_single_delete_mismatches"
|
||||
<< compaction_job_stats_->num_single_del_mismatch;
|
||||
<< job_stats_->num_single_del_mismatch;
|
||||
stream << "num_single_delete_fallthrough"
|
||||
<< compaction_job_stats_->num_single_del_fallthru;
|
||||
<< job_stats_->num_single_del_fallthru;
|
||||
|
||||
if (measure_io_stats_) {
|
||||
stream << "file_write_nanos" << compaction_job_stats_->file_write_nanos;
|
||||
stream << "file_range_sync_nanos"
|
||||
<< compaction_job_stats_->file_range_sync_nanos;
|
||||
stream << "file_fsync_nanos" << compaction_job_stats_->file_fsync_nanos;
|
||||
stream << "file_write_nanos" << job_stats_->file_write_nanos;
|
||||
stream << "file_range_sync_nanos" << job_stats_->file_range_sync_nanos;
|
||||
stream << "file_fsync_nanos" << job_stats_->file_fsync_nanos;
|
||||
stream << "file_prepare_write_nanos"
|
||||
<< compaction_job_stats_->file_prepare_write_nanos;
|
||||
<< job_stats_->file_prepare_write_nanos;
|
||||
}
|
||||
|
||||
stream << "lsm_state";
|
||||
@@ -1056,16 +1068,16 @@ Status CompactionJob::Install(bool* compaction_released) {
|
||||
stream << "blob_file_tail" << blob_files.back()->GetBlobFileNumber();
|
||||
}
|
||||
|
||||
if (compaction_stats_.has_penultimate_level_output) {
|
||||
if (internal_stats_.has_proximal_level_output) {
|
||||
InternalStats::CompactionStats& pl_stats =
|
||||
compaction_stats_.penultimate_level_stats;
|
||||
stream << "penultimate_level_num_output_files" << pl_stats.num_output_files;
|
||||
stream << "penultimate_level_bytes_written" << pl_stats.bytes_written;
|
||||
stream << "penultimate_level_num_output_records"
|
||||
internal_stats_.proximal_level_stats;
|
||||
stream << "proximal_level_num_output_files" << pl_stats.num_output_files;
|
||||
stream << "proximal_level_bytes_written" << pl_stats.bytes_written;
|
||||
stream << "proximal_level_num_output_records"
|
||||
<< pl_stats.num_output_records;
|
||||
stream << "penultimate_level_num_output_files_blob"
|
||||
stream << "proximal_level_num_output_files_blob"
|
||||
<< pl_stats.num_output_files_blob;
|
||||
stream << "penultimate_level_bytes_written_blob"
|
||||
stream << "proximal_level_bytes_written_blob"
|
||||
<< pl_stats.bytes_written_blob;
|
||||
}
|
||||
|
||||
@@ -1313,7 +1325,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
|
||||
std::vector<std::string> blob_file_paths;
|
||||
|
||||
// TODO: BlobDB to support output_to_penultimate_level compaction, which needs
|
||||
// TODO: BlobDB to support output_to_proximal_level compaction, which needs
|
||||
// 2 builders, so may need to move to `CompactionOutputs`
|
||||
std::unique_ptr<BlobFileBuilder> blob_file_builder(
|
||||
(mutable_cf_options.enable_blob_files &&
|
||||
@@ -1398,30 +1410,30 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
}
|
||||
|
||||
const auto& ikey = c_iter->ikey();
|
||||
bool use_penultimate_output = ikey.sequence > penultimate_after_seqno_;
|
||||
bool use_proximal_output = ikey.sequence > proximal_after_seqno_;
|
||||
#ifndef NDEBUG
|
||||
if (sub_compact->compaction->SupportsPerKeyPlacement()) {
|
||||
// Could be overridden by unittest
|
||||
PerKeyPlacementContext context(sub_compact->compaction->output_level(),
|
||||
ikey.user_key, c_iter->value(),
|
||||
ikey.sequence, use_penultimate_output);
|
||||
ikey.sequence, use_proximal_output);
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionIterator::PrepareOutput.context",
|
||||
&context);
|
||||
if (use_penultimate_output) {
|
||||
// Verify that entries sent to the penultimate level are within the
|
||||
if (use_proximal_output) {
|
||||
// Verify that entries sent to the proximal level are within the
|
||||
// allowed range (because the input key range of the last level could
|
||||
// be larger than the allowed output key range of the penultimate
|
||||
// be larger than the allowed output key range of the proximal
|
||||
// level). This check uses user keys (ignores sequence numbers) because
|
||||
// compaction boundaries are a "clean cut" between user keys (see
|
||||
// CompactionPicker::ExpandInputsToCleanCut()), which is especially
|
||||
// important when preferred sequence numbers has been swapped in for
|
||||
// kTypeValuePreferredSeqno / TimedPut.
|
||||
sub_compact->compaction->TEST_AssertWithinPenultimateLevelOutputRange(
|
||||
sub_compact->compaction->TEST_AssertWithinProximalLevelOutputRange(
|
||||
c_iter->user_key());
|
||||
}
|
||||
} else {
|
||||
assert(penultimate_after_seqno_ == kMaxSequenceNumber);
|
||||
assert(!use_penultimate_output);
|
||||
assert(proximal_after_seqno_ == kMaxSequenceNumber);
|
||||
assert(!use_proximal_output);
|
||||
}
|
||||
#endif // NDEBUG
|
||||
|
||||
@@ -1430,7 +1442,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
// and `close_file_func`.
|
||||
// TODO: it would be better to have the compaction file open/close moved
|
||||
// into `CompactionOutputs` which has the output file information.
|
||||
status = sub_compact->AddToOutput(*c_iter, use_penultimate_output,
|
||||
status = sub_compact->AddToOutput(*c_iter, use_proximal_output,
|
||||
open_file_func, close_file_func);
|
||||
if (!status.ok()) {
|
||||
break;
|
||||
@@ -1642,15 +1654,15 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
std::pair<SequenceNumber, SequenceNumber> keep_seqno_range{
|
||||
0, kMaxSequenceNumber};
|
||||
if (sub_compact->compaction->SupportsPerKeyPlacement()) {
|
||||
if (outputs.IsPenultimateLevel()) {
|
||||
keep_seqno_range.first = penultimate_after_seqno_;
|
||||
if (outputs.IsProximalLevel()) {
|
||||
keep_seqno_range.first = proximal_after_seqno_;
|
||||
} else {
|
||||
keep_seqno_range.second = penultimate_after_seqno_;
|
||||
keep_seqno_range.second = proximal_after_seqno_;
|
||||
}
|
||||
}
|
||||
CompactionIterationStats range_del_out_stats;
|
||||
// NOTE1: Use `bottommost_level_ = true` for both bottommost and
|
||||
// output_to_penultimate_level compaction here, as it's only used to decide
|
||||
// output_to_proximal_level compaction here, as it's only used to decide
|
||||
// if range dels could be dropped. (Logically, we are taking a single sorted
|
||||
// run returned from CompactionIterator and physically splitting it between
|
||||
// two output levels.)
|
||||
@@ -1813,22 +1825,22 @@ Status CompactionJob::InstallCompactionResults(bool* compaction_released) {
|
||||
|
||||
{
|
||||
Compaction::InputLevelSummaryBuffer inputs_summary;
|
||||
if (compaction_stats_.has_penultimate_level_output) {
|
||||
if (internal_stats_.has_proximal_level_output) {
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer_,
|
||||
"[%s] [JOB %d] Compacted %s => output_to_penultimate_level: %" PRIu64
|
||||
"[%s] [JOB %d] Compacted %s => output_to_proximal_level: %" PRIu64
|
||||
" bytes + last: %" PRIu64 " bytes. Total: %" PRIu64 " bytes",
|
||||
compaction->column_family_data()->GetName().c_str(), job_id_,
|
||||
compaction->InputLevelSummary(&inputs_summary),
|
||||
compaction_stats_.penultimate_level_stats.bytes_written,
|
||||
compaction_stats_.stats.bytes_written,
|
||||
compaction_stats_.TotalBytesWritten());
|
||||
internal_stats_.proximal_level_stats.bytes_written,
|
||||
internal_stats_.output_level_stats.bytes_written,
|
||||
internal_stats_.TotalBytesWritten());
|
||||
} else {
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] [JOB %d] Compacted %s => %" PRIu64 " bytes",
|
||||
compaction->column_family_data()->GetName().c_str(),
|
||||
job_id_, compaction->InputLevelSummary(&inputs_summary),
|
||||
compaction_stats_.TotalBytesWritten());
|
||||
internal_stats_.TotalBytesWritten());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1947,11 +1959,11 @@ Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact,
|
||||
// Here last_level_temperature supersedes default_write_temperature, when
|
||||
// enabled and applicable
|
||||
if (last_level_temp != Temperature::kUnknown &&
|
||||
sub_compact->compaction->is_last_level() &&
|
||||
!outputs.IsPenultimateLevel()) {
|
||||
sub_compact->compaction->is_last_level() && !outputs.IsProximalLevel()) {
|
||||
temperature = last_level_temp;
|
||||
}
|
||||
fo_copy.temperature = temperature;
|
||||
fo_copy.write_hint = write_hint_;
|
||||
|
||||
Status s;
|
||||
IOStatus io_s = NewWritableFile(fs_.get(), fname, &writable_file, fo_copy);
|
||||
@@ -2037,7 +2049,9 @@ Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact,
|
||||
}
|
||||
|
||||
writable_file->SetIOPriority(GetRateLimiterPriority());
|
||||
writable_file->SetWriteLifeTimeHint(write_hint_);
|
||||
// Subsequent attempts to override the hint via SetWriteLifeTimeHint
|
||||
// with the very same value will be ignored by the fs.
|
||||
writable_file->SetWriteLifeTimeHint(fo_copy.write_hint);
|
||||
FileTypeSet tmp_set = db_options_.checksum_handoff_file_types;
|
||||
writable_file->SetPreallocationBlockSize(static_cast<size_t>(
|
||||
sub_compact->compaction->OutputFilePreallocationSize()));
|
||||
@@ -2062,7 +2076,7 @@ Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact,
|
||||
bottommost_level_, TableFileCreationReason::kCompaction,
|
||||
0 /* oldest_key_time */, current_time, db_id_, db_session_id_,
|
||||
sub_compact->compaction->max_output_file_size(), file_number,
|
||||
penultimate_after_seqno_ /*last_level_inclusive_max_seqno_threshold*/);
|
||||
proximal_after_seqno_ /*last_level_inclusive_max_seqno_threshold*/);
|
||||
|
||||
outputs.NewBuilder(tboptions);
|
||||
|
||||
@@ -2086,12 +2100,13 @@ void CopyPrefix(const Slice& src, size_t prefix_length, std::string* dst) {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool CompactionJob::UpdateCompactionStats(uint64_t* num_input_range_del) {
|
||||
bool CompactionJob::BuildStatsFromInputTableProperties(
|
||||
uint64_t* num_input_range_del) {
|
||||
assert(compact_);
|
||||
|
||||
Compaction* compaction = compact_->compaction;
|
||||
compaction_stats_.stats.num_input_files_in_non_output_levels = 0;
|
||||
compaction_stats_.stats.num_input_files_in_output_level = 0;
|
||||
internal_stats_.output_level_stats.num_input_files_in_non_output_levels = 0;
|
||||
internal_stats_.output_level_stats.num_input_files_in_output_level = 0;
|
||||
|
||||
bool has_error = false;
|
||||
const ReadOptions read_options(Env::IOActivity::kCompaction);
|
||||
@@ -2103,13 +2118,14 @@ bool CompactionJob::UpdateCompactionStats(uint64_t* num_input_range_del) {
|
||||
size_t num_input_files = flevel->num_files;
|
||||
uint64_t* bytes_read;
|
||||
if (compaction->level(input_level) != compaction->output_level()) {
|
||||
compaction_stats_.stats.num_input_files_in_non_output_levels +=
|
||||
internal_stats_.output_level_stats.num_input_files_in_non_output_levels +=
|
||||
static_cast<int>(num_input_files);
|
||||
bytes_read = &compaction_stats_.stats.bytes_read_non_output_levels;
|
||||
bytes_read =
|
||||
&internal_stats_.output_level_stats.bytes_read_non_output_levels;
|
||||
} else {
|
||||
compaction_stats_.stats.num_input_files_in_output_level +=
|
||||
internal_stats_.output_level_stats.num_input_files_in_output_level +=
|
||||
static_cast<int>(num_input_files);
|
||||
bytes_read = &compaction_stats_.stats.bytes_read_output_level;
|
||||
bytes_read = &internal_stats_.output_level_stats.bytes_read_output_level;
|
||||
}
|
||||
for (size_t i = 0; i < num_input_files; ++i) {
|
||||
const FileMetaData* file_meta = flevel->files[i].file_metadata;
|
||||
@@ -2129,7 +2145,8 @@ bool CompactionJob::UpdateCompactionStats(uint64_t* num_input_range_del) {
|
||||
has_error = true;
|
||||
}
|
||||
}
|
||||
compaction_stats_.stats.num_input_records += file_input_entries;
|
||||
internal_stats_.output_level_stats.num_input_records +=
|
||||
file_input_entries;
|
||||
if (num_input_range_del) {
|
||||
*num_input_range_del += file_num_range_del;
|
||||
}
|
||||
@@ -2140,62 +2157,122 @@ bool CompactionJob::UpdateCompactionStats(uint64_t* num_input_range_del) {
|
||||
size_t num_filtered_input_files = filtered_flevel.size();
|
||||
uint64_t* bytes_skipped;
|
||||
if (compaction->level(input_level) != compaction->output_level()) {
|
||||
compaction_stats_.stats.num_filtered_input_files_in_non_output_levels +=
|
||||
internal_stats_.output_level_stats
|
||||
.num_filtered_input_files_in_non_output_levels +=
|
||||
static_cast<int>(num_filtered_input_files);
|
||||
bytes_skipped = &compaction_stats_.stats.bytes_skipped_non_output_levels;
|
||||
bytes_skipped =
|
||||
&internal_stats_.output_level_stats.bytes_skipped_non_output_levels;
|
||||
} else {
|
||||
compaction_stats_.stats.num_filtered_input_files_in_output_level +=
|
||||
internal_stats_.output_level_stats
|
||||
.num_filtered_input_files_in_output_level +=
|
||||
static_cast<int>(num_filtered_input_files);
|
||||
bytes_skipped = &compaction_stats_.stats.bytes_skipped_output_level;
|
||||
bytes_skipped =
|
||||
&internal_stats_.output_level_stats.bytes_skipped_output_level;
|
||||
}
|
||||
for (const FileMetaData* filtered_file_meta : filtered_flevel) {
|
||||
*bytes_skipped += filtered_file_meta->fd.GetFileSize();
|
||||
}
|
||||
}
|
||||
|
||||
assert(compaction_job_stats_);
|
||||
compaction_stats_.stats.bytes_read_blob =
|
||||
compaction_job_stats_->total_blob_bytes_read;
|
||||
|
||||
compaction_stats_.stats.num_dropped_records =
|
||||
compaction_stats_.DroppedRecords();
|
||||
// TODO - find a better place to set these two
|
||||
assert(job_stats_);
|
||||
internal_stats_.output_level_stats.bytes_read_blob =
|
||||
job_stats_->total_blob_bytes_read;
|
||||
internal_stats_.output_level_stats.num_dropped_records =
|
||||
internal_stats_.DroppedRecords();
|
||||
return !has_error;
|
||||
}
|
||||
|
||||
void CompactionJob::UpdateCompactionJobStats(
|
||||
const InternalStats::CompactionStats& stats) const {
|
||||
compaction_job_stats_->elapsed_micros = stats.micros;
|
||||
|
||||
void CompactionJob::UpdateCompactionJobInputStats(
|
||||
const InternalStats::CompactionStatsFull& internal_stats,
|
||||
uint64_t num_input_range_del) const {
|
||||
assert(job_stats_);
|
||||
// input information
|
||||
compaction_job_stats_->total_input_bytes =
|
||||
stats.bytes_read_non_output_levels + stats.bytes_read_output_level;
|
||||
compaction_job_stats_->num_input_records = stats.num_input_records;
|
||||
compaction_job_stats_->num_input_files =
|
||||
stats.num_input_files_in_non_output_levels +
|
||||
stats.num_input_files_in_output_level;
|
||||
compaction_job_stats_->num_input_files_at_output_level =
|
||||
stats.num_input_files_in_output_level;
|
||||
compaction_job_stats_->num_filtered_input_files =
|
||||
stats.num_filtered_input_files_in_non_output_levels +
|
||||
stats.num_filtered_input_files_in_output_level;
|
||||
compaction_job_stats_->num_filtered_input_files_at_output_level =
|
||||
stats.num_filtered_input_files_in_output_level;
|
||||
compaction_job_stats_->total_skipped_input_bytes =
|
||||
stats.bytes_skipped_non_output_levels + stats.bytes_skipped_output_level;
|
||||
job_stats_->total_input_bytes =
|
||||
internal_stats.output_level_stats.bytes_read_non_output_levels +
|
||||
internal_stats.output_level_stats.bytes_read_output_level;
|
||||
job_stats_->num_input_records =
|
||||
internal_stats.output_level_stats.num_input_records - num_input_range_del;
|
||||
job_stats_->num_input_files =
|
||||
internal_stats.output_level_stats.num_input_files_in_non_output_levels +
|
||||
internal_stats.output_level_stats.num_input_files_in_output_level;
|
||||
job_stats_->num_input_files_at_output_level =
|
||||
internal_stats.output_level_stats.num_input_files_in_output_level;
|
||||
job_stats_->num_filtered_input_files =
|
||||
internal_stats.output_level_stats
|
||||
.num_filtered_input_files_in_non_output_levels +
|
||||
internal_stats.output_level_stats
|
||||
.num_filtered_input_files_in_output_level;
|
||||
job_stats_->num_filtered_input_files_at_output_level =
|
||||
internal_stats.output_level_stats
|
||||
.num_filtered_input_files_in_output_level;
|
||||
job_stats_->total_skipped_input_bytes =
|
||||
internal_stats.output_level_stats.bytes_skipped_non_output_levels +
|
||||
internal_stats.output_level_stats.bytes_skipped_output_level;
|
||||
|
||||
if (internal_stats.has_proximal_level_output) {
|
||||
job_stats_->total_input_bytes +=
|
||||
internal_stats.proximal_level_stats.bytes_read_non_output_levels +
|
||||
internal_stats.proximal_level_stats.bytes_read_output_level;
|
||||
job_stats_->num_input_records +=
|
||||
internal_stats.proximal_level_stats.num_input_records;
|
||||
job_stats_->num_input_files +=
|
||||
internal_stats.proximal_level_stats
|
||||
.num_input_files_in_non_output_levels +
|
||||
internal_stats.proximal_level_stats.num_input_files_in_output_level;
|
||||
job_stats_->num_input_files_at_output_level +=
|
||||
internal_stats.proximal_level_stats.num_input_files_in_output_level;
|
||||
job_stats_->num_filtered_input_files +=
|
||||
internal_stats.proximal_level_stats
|
||||
.num_filtered_input_files_in_non_output_levels +
|
||||
internal_stats.proximal_level_stats
|
||||
.num_filtered_input_files_in_output_level;
|
||||
job_stats_->num_filtered_input_files_at_output_level +=
|
||||
internal_stats.proximal_level_stats
|
||||
.num_filtered_input_files_in_output_level;
|
||||
job_stats_->total_skipped_input_bytes +=
|
||||
internal_stats.proximal_level_stats.bytes_skipped_non_output_levels +
|
||||
internal_stats.proximal_level_stats.bytes_skipped_output_level;
|
||||
}
|
||||
}
|
||||
|
||||
void CompactionJob::UpdateCompactionJobOutputStats(
|
||||
const InternalStats::CompactionStatsFull& internal_stats) const {
|
||||
assert(job_stats_);
|
||||
job_stats_->elapsed_micros = internal_stats.output_level_stats.micros;
|
||||
job_stats_->cpu_micros = internal_stats.output_level_stats.cpu_micros;
|
||||
|
||||
// output information
|
||||
compaction_job_stats_->total_output_bytes = stats.bytes_written;
|
||||
compaction_job_stats_->total_output_bytes_blob = stats.bytes_written_blob;
|
||||
compaction_job_stats_->num_output_records = stats.num_output_records;
|
||||
compaction_job_stats_->num_output_files = stats.num_output_files;
|
||||
compaction_job_stats_->num_output_files_blob = stats.num_output_files_blob;
|
||||
job_stats_->total_output_bytes =
|
||||
internal_stats.output_level_stats.bytes_written;
|
||||
job_stats_->total_output_bytes_blob =
|
||||
internal_stats.output_level_stats.bytes_written_blob;
|
||||
job_stats_->num_output_records =
|
||||
internal_stats.output_level_stats.num_output_records;
|
||||
job_stats_->num_output_files =
|
||||
internal_stats.output_level_stats.num_output_files;
|
||||
job_stats_->num_output_files_blob =
|
||||
internal_stats.output_level_stats.num_output_files_blob;
|
||||
|
||||
if (stats.num_output_files > 0) {
|
||||
if (internal_stats.has_proximal_level_output) {
|
||||
job_stats_->total_output_bytes +=
|
||||
internal_stats.proximal_level_stats.bytes_written;
|
||||
job_stats_->total_output_bytes_blob +=
|
||||
internal_stats.proximal_level_stats.bytes_written_blob;
|
||||
job_stats_->num_output_records +=
|
||||
internal_stats.proximal_level_stats.num_output_records;
|
||||
job_stats_->num_output_files +=
|
||||
internal_stats.proximal_level_stats.num_output_files;
|
||||
job_stats_->num_output_files_blob +=
|
||||
internal_stats.proximal_level_stats.num_output_files_blob;
|
||||
}
|
||||
|
||||
if (job_stats_->num_output_files > 0) {
|
||||
CopyPrefix(compact_->SmallestUserKey(),
|
||||
CompactionJobStats::kMaxPrefixLength,
|
||||
&compaction_job_stats_->smallest_output_key_prefix);
|
||||
&job_stats_->smallest_output_key_prefix);
|
||||
CopyPrefix(compact_->LargestUserKey(), CompactionJobStats::kMaxPrefixLength,
|
||||
&compaction_job_stats_->largest_output_key_prefix);
|
||||
&job_stats_->largest_output_key_prefix);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2233,19 +2310,19 @@ void CompactionJob::LogCompaction() {
|
||||
? int64_t{-1} // Use -1 for "none"
|
||||
: static_cast<int64_t>(existing_snapshots_[0]));
|
||||
if (compaction->SupportsPerKeyPlacement()) {
|
||||
stream << "prenultimate_after_seqno" << penultimate_after_seqno_;
|
||||
stream << "proximal_after_seqno" << proximal_after_seqno_;
|
||||
stream << "preserve_seqno_after" << preserve_seqno_after_;
|
||||
stream << "penultimate_output_level" << compaction->GetPenultimateLevel();
|
||||
stream << "penultimate_output_range"
|
||||
<< GetCompactionPenultimateOutputRangeTypeString(
|
||||
compaction->GetPenultimateOutputRangeType());
|
||||
stream << "proximal_output_level" << compaction->GetProximalLevel();
|
||||
stream << "proximal_output_range"
|
||||
<< GetCompactionProximalOutputRangeTypeString(
|
||||
compaction->GetProximalOutputRangeType());
|
||||
|
||||
if (compaction->GetPenultimateOutputRangeType() ==
|
||||
Compaction::PenultimateOutputRangeType::kDisabled) {
|
||||
if (compaction->GetProximalOutputRangeType() ==
|
||||
Compaction::ProximalOutputRangeType::kDisabled) {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log,
|
||||
"[%s] [JOB %d] Penultimate level output is disabled, likely "
|
||||
"because of the range conflict in the penultimate level",
|
||||
"[%s] [JOB %d] Proximal level output is disabled, likely "
|
||||
"because of the range conflict in the proximal level",
|
||||
cfd->GetName().c_str(), job_id_);
|
||||
}
|
||||
}
|
||||
@@ -2270,4 +2347,36 @@ Env::IOPriority CompactionJob::GetRateLimiterPriority() {
|
||||
return Env::IO_LOW;
|
||||
}
|
||||
|
||||
Status CompactionJob::VerifyInputRecordCount(
|
||||
uint64_t num_input_range_del) const {
|
||||
size_t ts_sz = compact_->compaction->column_family_data()
|
||||
->user_comparator()
|
||||
->timestamp_size();
|
||||
// When trim_ts_ is non-empty, CompactionIterator takes
|
||||
// HistoryTrimmingIterator as input iterator and sees a trimmed view of
|
||||
// input keys. So the number of keys it processed is not suitable for
|
||||
// verification here.
|
||||
// TODO: support verification when trim_ts_ is non-empty.
|
||||
if (!(ts_sz > 0 && !trim_ts_.empty())) {
|
||||
assert(internal_stats_.output_level_stats.num_input_records > 0);
|
||||
// TODO: verify the number of range deletion entries.
|
||||
uint64_t expected = internal_stats_.output_level_stats.num_input_records -
|
||||
num_input_range_del;
|
||||
uint64_t actual = job_stats_->num_input_records;
|
||||
if (expected != actual) {
|
||||
char scratch[2345];
|
||||
compact_->compaction->Summary(scratch, sizeof(scratch));
|
||||
std::string msg =
|
||||
"Compaction number of input keys does not match "
|
||||
"number of keys processed. Expected " +
|
||||
std::to_string(expected) + " but processed " +
|
||||
std::to_string(actual) + ". Compaction summary: " + scratch;
|
||||
if (db_options_.compaction_verify_record_count) {
|
||||
return Status::Corruption(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -67,7 +67,7 @@ class SubcompactionState;
|
||||
// if needed.
|
||||
//
|
||||
// CompactionJob has 2 main stats:
|
||||
// 1. CompactionJobStats compaction_job_stats_
|
||||
// 1. CompactionJobStats job_stats_
|
||||
// CompactionJobStats is a public data structure which is part of Compaction
|
||||
// event listener that rocksdb share the job stats with the user.
|
||||
// Internally it's an aggregation of all the compaction_job_stats from each
|
||||
@@ -81,7 +81,7 @@ class SubcompactionState;
|
||||
// +------------------------+ |
|
||||
// | CompactionJob | | +------------------------+
|
||||
// | | | | SubcompactionState |
|
||||
// | compaction_job_stats +-----+ | |
|
||||
// | job_stats +-----+ | |
|
||||
// | | +--------->| compaction_job_stats |
|
||||
// | | | | |
|
||||
// +------------------------+ | +------------------------+
|
||||
@@ -98,16 +98,13 @@ class SubcompactionState;
|
||||
// +--------->+ |
|
||||
// +------------------------+
|
||||
//
|
||||
// 2. CompactionStatsFull compaction_stats_
|
||||
// 2. CompactionStatsFull internal_stats_
|
||||
// `CompactionStatsFull` is an internal stats about the compaction, which
|
||||
// is eventually sent to `ColumnFamilyData::internal_stats_` and used for
|
||||
// logging and public metrics.
|
||||
// Internally, it's an aggregation of stats_ from each `SubcompactionState`.
|
||||
// It has 2 parts, normal stats about the main compaction information and
|
||||
// the penultimate level output stats.
|
||||
// `SubcompactionState` maintains the CompactionOutputs for normal output and
|
||||
// the penultimate level output if exists, the per_level stats is
|
||||
// stored with the outputs.
|
||||
// It has 2 parts, ordinary output level stats and the proximal level output
|
||||
// stats.
|
||||
// +---------------------------+
|
||||
// | SubcompactionState |
|
||||
// | |
|
||||
@@ -119,15 +116,15 @@ class SubcompactionState;
|
||||
// | | |
|
||||
// | | +----------------------+ |
|
||||
// +--------------------------------+ | | | CompactionOutputs | |
|
||||
// | CompactionJob | | | | (penultimate_level) | |
|
||||
// | CompactionJob | | | | (proximal_level) | |
|
||||
// | | +--------->| stats_ | |
|
||||
// | compaction_stats_ | | | | +----------------------+ |
|
||||
// | internal_stats_ | | | | +----------------------+ |
|
||||
// | +-------------------------+ | | | | |
|
||||
// | |stats (normal) |------|----+ +---------------------------+
|
||||
// | |output_level_stats |------|----+ +---------------------------+
|
||||
// | +-------------------------+ | | |
|
||||
// | | | |
|
||||
// | +-------------------------+ | | | +---------------------------+
|
||||
// | |penultimate_level_stats +------+ | | SubcompactionState |
|
||||
// | |proximal_level_stats |------+ | | SubcompactionState |
|
||||
// | +-------------------------+ | | | | |
|
||||
// | | | | | +----------------------+ |
|
||||
// | | | | | | CompactionOutputs | |
|
||||
@@ -137,7 +134,7 @@ class SubcompactionState;
|
||||
// | | |
|
||||
// | | +----------------------+ |
|
||||
// | | | CompactionOutputs | |
|
||||
// | | | (penultimate_level) | |
|
||||
// | | | (proximal_level) | |
|
||||
// +--------->| stats_ | |
|
||||
// | +----------------------+ |
|
||||
// | |
|
||||
@@ -199,23 +196,9 @@ class CompactionJob {
|
||||
IOStatus io_status() const { return io_status_; }
|
||||
|
||||
protected:
|
||||
// Update the following stats in compaction_stats_.stats
|
||||
// - num_input_files_in_non_output_levels
|
||||
// - num_input_files_in_output_level
|
||||
// - bytes_read_non_output_levels
|
||||
// - bytes_read_output_level
|
||||
// - num_input_records
|
||||
// - bytes_read_blob
|
||||
// - num_dropped_records
|
||||
//
|
||||
// @param num_input_range_del if non-null, will be set to the number of range
|
||||
// deletion entries in this compaction input.
|
||||
//
|
||||
// Returns true iff compaction_stats_.stats.num_input_records and
|
||||
// num_input_range_del are calculated successfully.
|
||||
bool UpdateCompactionStats(uint64_t* num_input_range_del = nullptr);
|
||||
virtual void UpdateCompactionJobStats(
|
||||
const InternalStats::CompactionStats& stats) const;
|
||||
void UpdateCompactionJobOutputStats(
|
||||
const InternalStats::CompactionStatsFull& internal_stats) const;
|
||||
|
||||
void LogCompaction();
|
||||
virtual void RecordCompactionIOStats();
|
||||
void CleanupCompaction();
|
||||
@@ -224,7 +207,7 @@ class CompactionJob {
|
||||
void ProcessKeyValueCompaction(SubcompactionState* sub_compact);
|
||||
|
||||
CompactionState* compact_;
|
||||
InternalStats::CompactionStatsFull compaction_stats_;
|
||||
InternalStats::CompactionStatsFull internal_stats_;
|
||||
const ImmutableDBOptions& db_options_;
|
||||
const MutableDBOptions mutable_db_options_copy_;
|
||||
LogBuffer* log_buffer_;
|
||||
@@ -237,11 +220,37 @@ class CompactionJob {
|
||||
|
||||
IOStatus io_status_;
|
||||
|
||||
CompactionJobStats* compaction_job_stats_;
|
||||
CompactionJobStats* job_stats_;
|
||||
|
||||
private:
|
||||
friend class CompactionJobTestBase;
|
||||
|
||||
// Collect the following stats from Input Table Properties
|
||||
// - num_input_files_in_non_output_levels
|
||||
// - num_input_files_in_output_level
|
||||
// - bytes_read_non_output_levels
|
||||
// - bytes_read_output_level
|
||||
// - num_input_records
|
||||
// - bytes_read_blob
|
||||
// - num_dropped_records
|
||||
// and set them in internal_stats_.output_level_stats
|
||||
//
|
||||
// @param num_input_range_del if non-null, will be set to the number of range
|
||||
// deletion entries in this compaction input.
|
||||
//
|
||||
// Returns true iff internal_stats_.output_level_stats.num_input_records and
|
||||
// num_input_range_del are calculated successfully.
|
||||
//
|
||||
// This should be called only once for compactions (not per subcompaction)
|
||||
bool BuildStatsFromInputTableProperties(
|
||||
uint64_t* num_input_range_del = nullptr);
|
||||
|
||||
void UpdateCompactionJobInputStats(
|
||||
const InternalStats::CompactionStatsFull& internal_stats,
|
||||
uint64_t num_input_range_del) const;
|
||||
|
||||
Status VerifyInputRecordCount(uint64_t num_input_range_del) const;
|
||||
|
||||
// Generates a histogram representing potential divisions of key ranges from
|
||||
// the input. It adds the starting and/or ending keys of certain input files
|
||||
// to the working set and then finds the approximate size of data in between
|
||||
@@ -363,8 +372,8 @@ class CompactionJob {
|
||||
|
||||
// Minimal sequence number to preclude the data from the last level. If the
|
||||
// key has bigger (newer) sequence number than this, it will be precluded from
|
||||
// the last level (output to penultimate level).
|
||||
SequenceNumber penultimate_after_seqno_ = kMaxSequenceNumber;
|
||||
// the last level (output to proximal level).
|
||||
SequenceNumber proximal_after_seqno_ = kMaxSequenceNumber;
|
||||
|
||||
// Options File Number used for Remote Compaction
|
||||
// Setting this requires DBMutex.
|
||||
@@ -431,6 +440,8 @@ struct CompactionServiceOutputFile {
|
||||
bool marked_for_compaction;
|
||||
UniqueId64x2 unique_id{};
|
||||
TableProperties table_properties;
|
||||
bool is_proximal_level_output;
|
||||
Temperature file_temperature;
|
||||
|
||||
CompactionServiceOutputFile() = default;
|
||||
CompactionServiceOutputFile(
|
||||
@@ -440,7 +451,8 @@ struct CompactionServiceOutputFile {
|
||||
uint64_t _epoch_number, const std::string& _file_checksum,
|
||||
const std::string& _file_checksum_func_name, uint64_t _paranoid_hash,
|
||||
bool _marked_for_compaction, UniqueId64x2 _unique_id,
|
||||
const TableProperties& _table_properties)
|
||||
const TableProperties& _table_properties, bool _is_proximal_level_output,
|
||||
Temperature _file_temperature)
|
||||
: file_name(name),
|
||||
smallest_seqno(smallest),
|
||||
largest_seqno(largest),
|
||||
@@ -454,7 +466,9 @@ struct CompactionServiceOutputFile {
|
||||
paranoid_hash(_paranoid_hash),
|
||||
marked_for_compaction(_marked_for_compaction),
|
||||
unique_id(std::move(_unique_id)),
|
||||
table_properties(_table_properties) {}
|
||||
table_properties(_table_properties),
|
||||
is_proximal_level_output(_is_proximal_level_output),
|
||||
file_temperature(_file_temperature) {}
|
||||
};
|
||||
|
||||
// CompactionServiceResult contains the compaction result from a different db
|
||||
@@ -470,8 +484,21 @@ struct CompactionServiceResult {
|
||||
|
||||
uint64_t bytes_read = 0;
|
||||
uint64_t bytes_written = 0;
|
||||
|
||||
// Job-level Compaction Stats.
|
||||
//
|
||||
// NOTE: Job level stats cannot be rebuilt from scratch by simply aggregating
|
||||
// per-level stats due to some fields populated directly during compaction
|
||||
// (e.g. RecordDroppedKeys()). This is why we need both job-level stats and
|
||||
// per-level in the serialized result. If rebuilding job-level stats from
|
||||
// per-level stats become possible in the future, consider deprecating this
|
||||
// field.
|
||||
CompactionJobStats stats;
|
||||
|
||||
// Per-level Compaction Stats for both output_level_stats and
|
||||
// proximal_level_stats
|
||||
InternalStats::CompactionStatsFull internal_stats;
|
||||
|
||||
// serialization interface to read and write the object
|
||||
static Status Read(const std::string& data_str, CompactionServiceResult* obj);
|
||||
Status Write(std::string* output);
|
||||
@@ -517,9 +544,6 @@ class CompactionServiceCompactionJob : private CompactionJob {
|
||||
protected:
|
||||
void RecordCompactionIOStats() override;
|
||||
|
||||
void UpdateCompactionJobStats(
|
||||
const InternalStats::CompactionStats& stats) const override;
|
||||
|
||||
private:
|
||||
// Get table file name in output_path
|
||||
std::string GetTableFileName(uint64_t file_number) override;
|
||||
|
||||
@@ -232,6 +232,11 @@ class CompactionJobTestBase : public testing::Test {
|
||||
// set default for the tests
|
||||
mutable_cf_options_.target_file_size_base = 1024 * 1024;
|
||||
mutable_cf_options_.max_compaction_bytes = 10 * 1024 * 1024;
|
||||
|
||||
// Turn off compaction_verify_record_count MockTables
|
||||
if (table_type == TableTypeForTest::kMockTable) {
|
||||
db_options_.compaction_verify_record_count = false;
|
||||
}
|
||||
}
|
||||
|
||||
void SetUp() override {
|
||||
@@ -1474,7 +1479,7 @@ TEST_F(CompactionJobTest, OldestBlobFileNumber) {
|
||||
/* expected_oldest_blob_file_numbers */ {19});
|
||||
}
|
||||
|
||||
TEST_F(CompactionJobTest, VerifyPenultimateLevelOutput) {
|
||||
TEST_F(CompactionJobTest, VerifyProximalLevelOutput) {
|
||||
cf_options_.last_level_temperature = Temperature::kCold;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"Compaction::SupportsPerKeyPlacement:Enabled", [&](void* arg) {
|
||||
@@ -1487,8 +1492,7 @@ TEST_F(CompactionJobTest, VerifyPenultimateLevelOutput) {
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionIterator::PrepareOutput.context", [&](void* arg) {
|
||||
auto context = static_cast<PerKeyPlacementContext*>(arg);
|
||||
context->output_to_penultimate_level =
|
||||
context->seq_num > latest_cold_seq;
|
||||
context->output_to_proximal_level = context->seq_num > latest_cold_seq;
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
@@ -1534,11 +1538,11 @@ TEST_F(CompactionJobTest, VerifyPenultimateLevelOutput) {
|
||||
/*verify_func=*/[&](Compaction& comp) {
|
||||
for (char c = 'a'; c <= 'z'; c++) {
|
||||
if (c == 'a') {
|
||||
comp.TEST_AssertWithinPenultimateLevelOutputRange(
|
||||
comp.TEST_AssertWithinProximalLevelOutputRange(
|
||||
"a", true /*expect_failure*/);
|
||||
} else {
|
||||
std::string c_str{c};
|
||||
comp.TEST_AssertWithinPenultimateLevelOutputRange(c_str);
|
||||
comp.TEST_AssertWithinProximalLevelOutputRange(c_str);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1682,7 +1686,8 @@ TEST_F(CompactionJobTest, ResultSerialization) {
|
||||
file_checksum /* file_checksum */,
|
||||
file_checksum_func_name /* file_checksum_func_name */,
|
||||
rnd64.Uniform(UINT64_MAX) /* paranoid_hash */,
|
||||
rnd.OneIn(2) /* marked_for_compaction */, id /* unique_id */, tp);
|
||||
rnd.OneIn(2) /* marked_for_compaction */, id /* unique_id */, tp,
|
||||
false /* is_proximal_level_output */, Temperature::kHot);
|
||||
}
|
||||
result.output_level = rnd.Uniform(10);
|
||||
result.output_path = rnd.RandomString(rnd.Uniform(kStrMaxLen));
|
||||
@@ -1736,6 +1741,8 @@ TEST_F(CompactionJobTest, ResultSerialization) {
|
||||
ASSERT_EQ(deserialized_tmp.output_files[0].file_checksum, file_checksum);
|
||||
ASSERT_EQ(deserialized_tmp.output_files[0].file_checksum_func_name,
|
||||
file_checksum_func_name);
|
||||
ASSERT_EQ(deserialized_tmp.output_files[0].file_temperature,
|
||||
Temperature::kHot);
|
||||
}
|
||||
|
||||
// Test unknown field
|
||||
|
||||
@@ -54,7 +54,7 @@ Status CompactionOutputs::Finish(
|
||||
}
|
||||
current_output().finished = true;
|
||||
stats_.bytes_written += current_bytes;
|
||||
stats_.num_output_files = outputs_.size();
|
||||
stats_.num_output_files = static_cast<int>(outputs_.size());
|
||||
|
||||
return s;
|
||||
}
|
||||
@@ -792,8 +792,8 @@ void CompactionOutputs::FillFilesToCutForTtl() {
|
||||
}
|
||||
|
||||
CompactionOutputs::CompactionOutputs(const Compaction* compaction,
|
||||
const bool is_penultimate_level)
|
||||
: compaction_(compaction), is_penultimate_level_(is_penultimate_level) {
|
||||
const bool is_proximal_level)
|
||||
: compaction_(compaction), is_proximal_level_(is_proximal_level) {
|
||||
partitioner_ = compaction->output_level() == 0
|
||||
? nullptr
|
||||
: compaction->CreateSstPartitioner();
|
||||
|
||||
@@ -30,29 +30,32 @@ class CompactionOutputs {
|
||||
// compaction output file
|
||||
struct Output {
|
||||
Output(FileMetaData&& _meta, const InternalKeyComparator& _icmp,
|
||||
bool _enable_hash, bool _finished, uint64_t precalculated_hash)
|
||||
bool _enable_hash, bool _finished, uint64_t precalculated_hash,
|
||||
bool _is_proximal_level)
|
||||
: meta(std::move(_meta)),
|
||||
validator(_icmp, _enable_hash, precalculated_hash),
|
||||
finished(_finished) {}
|
||||
finished(_finished),
|
||||
is_proximal_level(_is_proximal_level) {}
|
||||
FileMetaData meta;
|
||||
OutputValidator validator;
|
||||
bool finished;
|
||||
bool is_proximal_level;
|
||||
std::shared_ptr<const TableProperties> table_properties;
|
||||
};
|
||||
|
||||
CompactionOutputs() = delete;
|
||||
|
||||
explicit CompactionOutputs(const Compaction* compaction,
|
||||
const bool is_penultimate_level);
|
||||
const bool is_proximal_level);
|
||||
|
||||
bool IsPenultimateLevel() const { return is_penultimate_level_; }
|
||||
bool IsProximalLevel() const { return is_proximal_level_; }
|
||||
|
||||
// Add generated output to the list
|
||||
void AddOutput(FileMetaData&& meta, const InternalKeyComparator& icmp,
|
||||
bool enable_hash, bool finished = false,
|
||||
uint64_t precalculated_hash = 0) {
|
||||
outputs_.emplace_back(std::move(meta), icmp, enable_hash, finished,
|
||||
precalculated_hash);
|
||||
precalculated_hash, is_proximal_level_);
|
||||
}
|
||||
|
||||
// Set new table builder for the current output
|
||||
@@ -63,34 +66,29 @@ class CompactionOutputs {
|
||||
file_writer_.reset(writer);
|
||||
}
|
||||
|
||||
// TODO: Remove it when remote compaction support tiered compaction
|
||||
void AddBytesWritten(uint64_t bytes) { stats_.bytes_written += bytes; }
|
||||
void SetNumOutputRecords(uint64_t num) { stats_.num_output_records = num; }
|
||||
void SetNumOutputFiles(uint64_t num) { stats_.num_output_files = num; }
|
||||
|
||||
// TODO: Move the BlobDB builder into CompactionOutputs
|
||||
const std::vector<BlobFileAddition>& GetBlobFileAdditions() const {
|
||||
if (is_penultimate_level_) {
|
||||
if (is_proximal_level_) {
|
||||
assert(blob_file_additions_.empty());
|
||||
}
|
||||
return blob_file_additions_;
|
||||
}
|
||||
|
||||
std::vector<BlobFileAddition>* GetBlobFileAdditionsPtr() {
|
||||
assert(!is_penultimate_level_);
|
||||
assert(!is_proximal_level_);
|
||||
return &blob_file_additions_;
|
||||
}
|
||||
|
||||
bool HasBlobFileAdditions() const { return !blob_file_additions_.empty(); }
|
||||
|
||||
BlobGarbageMeter* CreateBlobGarbageMeter() {
|
||||
assert(!is_penultimate_level_);
|
||||
assert(!is_proximal_level_);
|
||||
blob_garbage_meter_ = std::make_unique<BlobGarbageMeter>();
|
||||
return blob_garbage_meter_.get();
|
||||
}
|
||||
|
||||
BlobGarbageMeter* GetBlobGarbageMeter() const {
|
||||
if (is_penultimate_level_) {
|
||||
if (is_proximal_level_) {
|
||||
// blobdb doesn't support per_key_placement yet
|
||||
assert(blob_garbage_meter_ == nullptr);
|
||||
return nullptr;
|
||||
@@ -99,8 +97,9 @@ class CompactionOutputs {
|
||||
}
|
||||
|
||||
void UpdateBlobStats() {
|
||||
assert(!is_penultimate_level_);
|
||||
stats_.num_output_files_blob = blob_file_additions_.size();
|
||||
assert(!is_proximal_level_);
|
||||
stats_.num_output_files_blob =
|
||||
static_cast<int>(blob_file_additions_.size());
|
||||
for (const auto& blob : blob_file_additions_) {
|
||||
stats_.bytes_written_blob += blob.GetTotalBlobBytes();
|
||||
}
|
||||
@@ -304,12 +303,12 @@ class CompactionOutputs {
|
||||
std::vector<BlobFileAddition> blob_file_additions_;
|
||||
std::unique_ptr<BlobGarbageMeter> blob_garbage_meter_;
|
||||
|
||||
// Basic compaction output stats for this level's outputs
|
||||
InternalStats::CompactionOutputsStats stats_;
|
||||
// Per level's output stat
|
||||
InternalStats::CompactionStats stats_;
|
||||
|
||||
// indicate if this CompactionOutputs obj for penultimate_level, should always
|
||||
// indicate if this CompactionOutputs obj for proximal_level, should always
|
||||
// be false if per_key_placement feature is not enabled.
|
||||
const bool is_penultimate_level_;
|
||||
const bool is_proximal_level_;
|
||||
|
||||
// partitioner information
|
||||
std::string last_key_for_partitioner_;
|
||||
@@ -363,7 +362,7 @@ class CompactionOutputs {
|
||||
std::vector<size_t> level_ptrs_;
|
||||
};
|
||||
|
||||
// helper struct to concatenate the last level and penultimate level outputs
|
||||
// helper struct to concatenate the last level and proximal level outputs
|
||||
// which could be replaced by std::ranges::join_view() in c++20
|
||||
struct OutputIterator {
|
||||
public:
|
||||
|
||||
@@ -272,8 +272,8 @@ bool CompactionPicker::RangeOverlapWithCompaction(
|
||||
return true;
|
||||
}
|
||||
if (c->SupportsPerKeyPlacement()) {
|
||||
if (c->OverlapPenultimateLevelOutputRange(smallest_user_key,
|
||||
largest_user_key)) {
|
||||
if (c->OverlapProximalLevelOutputRange(smallest_user_key,
|
||||
largest_user_key)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -284,7 +284,7 @@ bool CompactionPicker::RangeOverlapWithCompaction(
|
||||
|
||||
bool CompactionPicker::FilesRangeOverlapWithCompaction(
|
||||
const std::vector<CompactionInputFiles>& inputs, int level,
|
||||
int penultimate_level) const {
|
||||
int proximal_level) const {
|
||||
bool is_empty = true;
|
||||
for (auto& in : inputs) {
|
||||
if (!in.empty()) {
|
||||
@@ -301,18 +301,18 @@ bool CompactionPicker::FilesRangeOverlapWithCompaction(
|
||||
// files cannot be overlapped in the order of L0 files.
|
||||
InternalKey smallest, largest;
|
||||
GetRange(inputs, &smallest, &largest, Compaction::kInvalidLevel);
|
||||
if (penultimate_level != Compaction::kInvalidLevel) {
|
||||
if (proximal_level != Compaction::kInvalidLevel) {
|
||||
if (ioptions_.compaction_style == kCompactionStyleUniversal) {
|
||||
if (RangeOverlapWithCompaction(smallest.user_key(), largest.user_key(),
|
||||
penultimate_level)) {
|
||||
proximal_level)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
InternalKey penultimate_smallest, penultimate_largest;
|
||||
GetRange(inputs, &penultimate_smallest, &penultimate_largest, level);
|
||||
if (RangeOverlapWithCompaction(penultimate_smallest.user_key(),
|
||||
penultimate_largest.user_key(),
|
||||
penultimate_level)) {
|
||||
InternalKey proximal_smallest, proximal_largest;
|
||||
GetRange(inputs, &proximal_smallest, &proximal_largest, level);
|
||||
if (RangeOverlapWithCompaction(proximal_smallest.user_key(),
|
||||
proximal_largest.user_key(),
|
||||
proximal_level)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -353,7 +353,7 @@ Compaction* CompactionPicker::CompactFiles(
|
||||
}
|
||||
assert(output_level == 0 || !FilesRangeOverlapWithCompaction(
|
||||
input_files, output_level,
|
||||
Compaction::EvaluatePenultimateLevel(
|
||||
Compaction::EvaluateProximalLevel(
|
||||
vstorage, mutable_cf_options, ioptions_,
|
||||
start_level, output_level)));
|
||||
#endif /* !NDEBUG */
|
||||
@@ -659,9 +659,9 @@ Compaction* CompactionPicker::CompactRange(
|
||||
// overlaping outputs in the same level.
|
||||
if (FilesRangeOverlapWithCompaction(
|
||||
inputs, output_level,
|
||||
Compaction::EvaluatePenultimateLevel(vstorage, mutable_cf_options,
|
||||
ioptions_, start_level,
|
||||
output_level))) {
|
||||
Compaction::EvaluateProximalLevel(vstorage, mutable_cf_options,
|
||||
ioptions_, start_level,
|
||||
output_level))) {
|
||||
// This compaction output could potentially conflict with the output
|
||||
// of a currently running compaction, we cannot run it.
|
||||
*manual_conflict = true;
|
||||
@@ -848,9 +848,9 @@ Compaction* CompactionPicker::CompactRange(
|
||||
// overlaping outputs in the same level.
|
||||
if (FilesRangeOverlapWithCompaction(
|
||||
compaction_inputs, output_level,
|
||||
Compaction::EvaluatePenultimateLevel(vstorage, mutable_cf_options,
|
||||
ioptions_, input_level,
|
||||
output_level))) {
|
||||
Compaction::EvaluateProximalLevel(vstorage, mutable_cf_options,
|
||||
ioptions_, input_level,
|
||||
output_level))) {
|
||||
// This compaction output could potentially conflict with the output
|
||||
// of a currently running compaction, we cannot run it.
|
||||
*manual_conflict = true;
|
||||
@@ -1137,7 +1137,7 @@ Status CompactionPicker::SanitizeAndConvertCompactionInputFiles(
|
||||
if (output_level != 0 &&
|
||||
FilesRangeOverlapWithCompaction(
|
||||
*converted_input_files, output_level,
|
||||
Compaction::EvaluatePenultimateLevel(
|
||||
Compaction::EvaluateProximalLevel(
|
||||
version->storage_info(), version->GetMutableCFOptions(),
|
||||
ioptions_, (*converted_input_files)[0].level, output_level))) {
|
||||
return Status::Aborted(
|
||||
@@ -1154,7 +1154,7 @@ void CompactionPicker::RegisterCompaction(Compaction* c) {
|
||||
assert(ioptions_.compaction_style != kCompactionStyleLevel ||
|
||||
c->output_level() == 0 ||
|
||||
!FilesRangeOverlapWithCompaction(*c->inputs(), c->output_level(),
|
||||
c->GetPenultimateLevel()));
|
||||
c->GetProximalLevel()));
|
||||
// CompactionReason::kExternalSstIngestion's start level is just a placeholder
|
||||
// number without actual meaning as file ingestion technically does not have
|
||||
// an input level like other compactions
|
||||
|
||||
@@ -138,6 +138,12 @@ class CompactionPicker {
|
||||
return !level0_compactions_in_progress_.empty();
|
||||
}
|
||||
|
||||
// Is any compaction in progress
|
||||
bool IsCompactionInProgress() const {
|
||||
return !(level0_compactions_in_progress_.empty() &&
|
||||
compactions_in_progress_.empty());
|
||||
}
|
||||
|
||||
// Return true if the passed key range overlap with a compaction output
|
||||
// that is currently running.
|
||||
bool RangeOverlapWithCompaction(const Slice& smallest_user_key,
|
||||
@@ -190,7 +196,7 @@ class CompactionPicker {
|
||||
// key range of a currently running compaction.
|
||||
bool FilesRangeOverlapWithCompaction(
|
||||
const std::vector<CompactionInputFiles>& inputs, int level,
|
||||
int penultimate_level) const;
|
||||
int proximal_level) const;
|
||||
|
||||
bool SetupOtherInputs(const std::string& cf_name,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
|
||||
@@ -414,9 +414,9 @@ void LevelCompactionBuilder::SetupOtherFilesWithRoundRobinExpansion() {
|
||||
&tmp_start_level_inputs) ||
|
||||
compaction_picker_->FilesRangeOverlapWithCompaction(
|
||||
{tmp_start_level_inputs}, output_level_,
|
||||
Compaction::EvaluatePenultimateLevel(vstorage_, mutable_cf_options_,
|
||||
ioptions_, start_level_,
|
||||
output_level_))) {
|
||||
Compaction::EvaluateProximalLevel(vstorage_, mutable_cf_options_,
|
||||
ioptions_, start_level_,
|
||||
output_level_))) {
|
||||
// Constraint 1a
|
||||
tmp_start_level_inputs.clear();
|
||||
return;
|
||||
@@ -490,9 +490,9 @@ bool LevelCompactionBuilder::SetupOtherInputsIfNeeded() {
|
||||
// We need to disallow this from happening.
|
||||
if (compaction_picker_->FilesRangeOverlapWithCompaction(
|
||||
compaction_inputs_, output_level_,
|
||||
Compaction::EvaluatePenultimateLevel(vstorage_, mutable_cf_options_,
|
||||
ioptions_, start_level_,
|
||||
output_level_))) {
|
||||
Compaction::EvaluateProximalLevel(vstorage_, mutable_cf_options_,
|
||||
ioptions_, start_level_,
|
||||
output_level_))) {
|
||||
// This compaction output could potentially conflict with the output
|
||||
// of a currently running compaction, we cannot run it.
|
||||
return false;
|
||||
@@ -846,9 +846,9 @@ bool LevelCompactionBuilder::PickFileToCompact() {
|
||||
&start_level_inputs_) ||
|
||||
compaction_picker_->FilesRangeOverlapWithCompaction(
|
||||
{start_level_inputs_}, output_level_,
|
||||
Compaction::EvaluatePenultimateLevel(vstorage_, mutable_cf_options_,
|
||||
ioptions_, start_level_,
|
||||
output_level_))) {
|
||||
Compaction::EvaluateProximalLevel(vstorage_, mutable_cf_options_,
|
||||
ioptions_, start_level_,
|
||||
output_level_))) {
|
||||
// A locked (pending compaction) input-level file was pulled in due to
|
||||
// user-key overlap.
|
||||
start_level_inputs_.clear();
|
||||
|
||||
@@ -3677,7 +3677,7 @@ TEST_F(CompactionPickerTest, UniversalSizeRatioTierCompactionLastLevel) {
|
||||
const uint64_t kFileSize = 100000;
|
||||
const int kNumLevels = 7;
|
||||
const int kLastLevel = kNumLevels - 1;
|
||||
const int kPenultimateLevel = kLastLevel - 1;
|
||||
const int kProximalLevel = kLastLevel - 1;
|
||||
|
||||
ioptions_.compaction_style = kCompactionStyleUniversal;
|
||||
mutable_cf_options_.preclude_last_level_data_seconds = 1000;
|
||||
@@ -3702,14 +3702,14 @@ TEST_F(CompactionPickerTest, UniversalSizeRatioTierCompactionLastLevel) {
|
||||
// Here to make sure it's size ratio compaction instead of size amp
|
||||
ASSERT_EQ(compaction->compaction_reason(),
|
||||
CompactionReason::kUniversalSizeRatio);
|
||||
ASSERT_EQ(compaction->output_level(), kPenultimateLevel - 1);
|
||||
ASSERT_EQ(compaction->output_level(), kProximalLevel - 1);
|
||||
ASSERT_EQ(compaction->input_levels(0)->num_files, 2);
|
||||
ASSERT_EQ(compaction->input_levels(5)->num_files, 0);
|
||||
ASSERT_EQ(compaction->input_levels(6)->num_files, 0);
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, UniversalSizeAmpTierCompactionNotSuport) {
|
||||
// Tiered compaction only support level_num > 2 (otherwise the penultimate
|
||||
// Tiered compaction only support level_num > 2 (otherwise the proximal
|
||||
// level is going to be level 0, which may make thing more complicated), so
|
||||
// when there's only 2 level, still treating level 1 as the last level for
|
||||
// size amp compaction
|
||||
@@ -3753,7 +3753,7 @@ TEST_F(CompactionPickerTest, UniversalSizeAmpTierCompactionLastLevel) {
|
||||
const uint64_t kFileSize = 100000;
|
||||
const int kNumLevels = 7;
|
||||
const int kLastLevel = kNumLevels - 1;
|
||||
const int kPenultimateLevel = kLastLevel - 1;
|
||||
const int kProximalLevel = kLastLevel - 1;
|
||||
|
||||
ioptions_.compaction_style = kCompactionStyleUniversal;
|
||||
mutable_cf_options_.preclude_last_level_data_seconds = 1000;
|
||||
@@ -3775,10 +3775,10 @@ TEST_F(CompactionPickerTest, UniversalSizeAmpTierCompactionLastLevel) {
|
||||
vstorage_.get(), &log_buffer_));
|
||||
|
||||
// It's a Size Amp compaction, but doesn't include the last level file and
|
||||
// output to the penultimate level.
|
||||
// output to the proximal level.
|
||||
ASSERT_EQ(compaction->compaction_reason(),
|
||||
CompactionReason::kUniversalSizeAmplification);
|
||||
ASSERT_EQ(compaction->output_level(), kPenultimateLevel);
|
||||
ASSERT_EQ(compaction->output_level(), kProximalLevel);
|
||||
ASSERT_EQ(compaction->input_levels(0)->num_files, 2);
|
||||
ASSERT_EQ(compaction->input_levels(5)->num_files, 1);
|
||||
ASSERT_EQ(compaction->input_levels(6)->num_files, 0);
|
||||
@@ -3940,7 +3940,7 @@ TEST_P(PerKeyPlacementCompactionPickerTest, OverlapWithNormalCompaction) {
|
||||
ASSERT_EQ(enable_per_key_placement_,
|
||||
level_compaction_picker.FilesRangeOverlapWithCompaction(
|
||||
input_files, 6,
|
||||
Compaction::EvaluatePenultimateLevel(
|
||||
Compaction::EvaluateProximalLevel(
|
||||
vstorage_.get(), mutable_cf_options_, ioptions_, 0, 6)));
|
||||
}
|
||||
|
||||
@@ -4028,7 +4028,7 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
|
||||
ASSERT_EQ(enable_per_key_placement_,
|
||||
universal_compaction_picker.FilesRangeOverlapWithCompaction(
|
||||
input_files, 6,
|
||||
Compaction::EvaluatePenultimateLevel(
|
||||
Compaction::EvaluateProximalLevel(
|
||||
vstorage_.get(), mutable_cf_options_, ioptions_, 0, 6)));
|
||||
}
|
||||
|
||||
@@ -4076,9 +4076,9 @@ TEST_P(PerKeyPlacementCompactionPickerTest, NormalCompactionOverlapUniversal) {
|
||||
input_files, 5, Compaction::kInvalidLevel));
|
||||
}
|
||||
|
||||
TEST_P(PerKeyPlacementCompactionPickerTest, PenultimateOverlapUniversal) {
|
||||
TEST_P(PerKeyPlacementCompactionPickerTest, ProximalOverlapUniversal) {
|
||||
// This test is make sure the Tiered compaction would lock whole range of
|
||||
// both output level and penultimate level
|
||||
// both output level and proximal level
|
||||
if (enable_per_key_placement_) {
|
||||
mutable_cf_options_.preclude_last_level_data_seconds = 10000;
|
||||
}
|
||||
@@ -4098,7 +4098,7 @@ TEST_P(PerKeyPlacementCompactionPickerTest, PenultimateOverlapUniversal) {
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
// the existing compaction is the 1st L4 file + L6 file
|
||||
// then compaction of the 2nd L4 file to L5 (penultimate level) is overlapped
|
||||
// then compaction of the 2nd L4 file to L5 (proximal level) is overlapped
|
||||
// when the tiered compaction feature is on.
|
||||
CompactionOptions comp_options;
|
||||
std::unordered_set<uint64_t> input_set;
|
||||
@@ -4187,9 +4187,9 @@ TEST_P(PerKeyPlacementCompactionPickerTest, LastLevelOnlyOverlapUniversal) {
|
||||
}
|
||||
|
||||
TEST_P(PerKeyPlacementCompactionPickerTest,
|
||||
LastLevelOnlyFailPenultimateUniversal) {
|
||||
LastLevelOnlyFailProximalUniversal) {
|
||||
// This is to test last_level only compaction still unable to do the
|
||||
// penultimate level compaction if there's already a file in the penultimate
|
||||
// proximal level compaction if there's already a file in the proximal
|
||||
// level.
|
||||
// This should rarely happen in universal compaction, as the non-empty L5
|
||||
// should be included in the compaction.
|
||||
@@ -4222,9 +4222,9 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
|
||||
mutable_db_options_, 0));
|
||||
|
||||
ASSERT_TRUE(comp1);
|
||||
ASSERT_EQ(comp1->GetPenultimateLevel(), Compaction::kInvalidLevel);
|
||||
ASSERT_EQ(comp1->GetProximalLevel(), Compaction::kInvalidLevel);
|
||||
|
||||
// As comp1 cannot be output to the penultimate level, compacting file 40 to
|
||||
// As comp1 cannot be output to the proximal level, compacting file 40 to
|
||||
// L5 is always safe.
|
||||
input_set.clear();
|
||||
input_files.clear();
|
||||
@@ -4239,14 +4239,14 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
|
||||
comp_options, input_files, 5, vstorage_.get(), mutable_cf_options_,
|
||||
mutable_db_options_, 0));
|
||||
ASSERT_TRUE(comp2);
|
||||
ASSERT_EQ(Compaction::kInvalidLevel, comp2->GetPenultimateLevel());
|
||||
ASSERT_EQ(Compaction::kInvalidLevel, comp2->GetProximalLevel());
|
||||
}
|
||||
|
||||
TEST_P(PerKeyPlacementCompactionPickerTest,
|
||||
LastLevelOnlyConflictWithOngoingUniversal) {
|
||||
// This is to test last_level only compaction still unable to do the
|
||||
// penultimate level compaction if there's already an ongoing compaction to
|
||||
// the penultimate level
|
||||
// proximal level compaction if there's already an ongoing compaction to
|
||||
// the proximal level
|
||||
if (enable_per_key_placement_) {
|
||||
mutable_cf_options_.preclude_last_level_data_seconds = 10000;
|
||||
}
|
||||
@@ -4265,7 +4265,7 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
|
||||
Add(6, 60U, "101", "351", 60000000U);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
// create an ongoing compaction to L5 (penultimate level)
|
||||
// create an ongoing compaction to L5 (proximal level)
|
||||
CompactionOptions comp_options;
|
||||
std::unordered_set<uint64_t> input_set;
|
||||
input_set.insert(40);
|
||||
@@ -4278,7 +4278,7 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
|
||||
mutable_db_options_, 0));
|
||||
|
||||
ASSERT_TRUE(comp1);
|
||||
ASSERT_EQ(comp1->GetPenultimateLevel(), Compaction::kInvalidLevel);
|
||||
ASSERT_EQ(comp1->GetProximalLevel(), Compaction::kInvalidLevel);
|
||||
|
||||
input_set.clear();
|
||||
input_files.clear();
|
||||
@@ -4289,7 +4289,7 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
|
||||
ASSERT_EQ(enable_per_key_placement_,
|
||||
universal_compaction_picker.FilesRangeOverlapWithCompaction(
|
||||
input_files, 6,
|
||||
Compaction::EvaluatePenultimateLevel(
|
||||
Compaction::EvaluateProximalLevel(
|
||||
vstorage_.get(), mutable_cf_options_, ioptions_, 6, 6)));
|
||||
|
||||
if (!enable_per_key_placement_) {
|
||||
@@ -4297,7 +4297,7 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
|
||||
comp_options, input_files, 6, vstorage_.get(), mutable_cf_options_,
|
||||
mutable_db_options_, 0));
|
||||
ASSERT_TRUE(comp2);
|
||||
ASSERT_EQ(Compaction::kInvalidLevel, comp2->GetPenultimateLevel());
|
||||
ASSERT_EQ(Compaction::kInvalidLevel, comp2->GetProximalLevel());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4306,7 +4306,7 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
|
||||
// This is similar to `LastLevelOnlyConflictWithOngoingUniversal`, the only
|
||||
// change is the ongoing compaction to L5 has no overlap with the last level
|
||||
// compaction, so it's safe to move data from the last level to the
|
||||
// penultimate level.
|
||||
// proximal level.
|
||||
if (enable_per_key_placement_) {
|
||||
mutable_cf_options_.preclude_last_level_data_seconds = 10000;
|
||||
}
|
||||
@@ -4325,7 +4325,7 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
|
||||
Add(6, 60U, "101", "351", 60000000U);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
// create an ongoing compaction to L5 (penultimate level)
|
||||
// create an ongoing compaction to L5 (proximal level)
|
||||
CompactionOptions comp_options;
|
||||
std::unordered_set<uint64_t> input_set;
|
||||
input_set.insert(42);
|
||||
@@ -4338,7 +4338,7 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
|
||||
mutable_db_options_, 0));
|
||||
|
||||
ASSERT_TRUE(comp1);
|
||||
ASSERT_EQ(comp1->GetPenultimateLevel(), Compaction::kInvalidLevel);
|
||||
ASSERT_EQ(comp1->GetProximalLevel(), Compaction::kInvalidLevel);
|
||||
|
||||
input_set.clear();
|
||||
input_files.clear();
|
||||
@@ -4349,8 +4349,8 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
|
||||
// always safe to move data up
|
||||
ASSERT_FALSE(universal_compaction_picker.FilesRangeOverlapWithCompaction(
|
||||
input_files, 6,
|
||||
Compaction::EvaluatePenultimateLevel(vstorage_.get(), mutable_cf_options_,
|
||||
ioptions_, 6, 6)));
|
||||
Compaction::EvaluateProximalLevel(vstorage_.get(), mutable_cf_options_,
|
||||
ioptions_, 6, 6)));
|
||||
|
||||
// 2 compactions can be run in parallel
|
||||
std::unique_ptr<Compaction> comp2(universal_compaction_picker.CompactFiles(
|
||||
@@ -4358,9 +4358,9 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
|
||||
mutable_db_options_, 0));
|
||||
ASSERT_TRUE(comp2);
|
||||
if (enable_per_key_placement_) {
|
||||
ASSERT_NE(Compaction::kInvalidLevel, comp2->GetPenultimateLevel());
|
||||
ASSERT_NE(Compaction::kInvalidLevel, comp2->GetProximalLevel());
|
||||
} else {
|
||||
ASSERT_EQ(Compaction::kInvalidLevel, comp2->GetPenultimateLevel());
|
||||
ASSERT_EQ(Compaction::kInvalidLevel, comp2->GetProximalLevel());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -288,7 +288,9 @@ class UniversalCompactionBuilder {
|
||||
// and the index of the file in that level
|
||||
|
||||
struct InputFileInfo {
|
||||
InputFileInfo() : f(nullptr), level(0), index(0) {}
|
||||
InputFileInfo() : InputFileInfo(nullptr, 0, 0) {}
|
||||
InputFileInfo(FileMetaData* file_meta, size_t l, size_t i)
|
||||
: f(file_meta), level(l), index(i) {}
|
||||
|
||||
FileMetaData* f;
|
||||
size_t level;
|
||||
@@ -321,22 +323,14 @@ SmallestKeyHeap create_level_heap(Compaction* c, const Comparator* ucmp) {
|
||||
SmallestKeyHeap smallest_key_priority_q =
|
||||
SmallestKeyHeap(SmallestKeyHeapComparator(ucmp));
|
||||
|
||||
InputFileInfo input_file;
|
||||
|
||||
for (size_t l = 0; l < c->num_input_levels(); l++) {
|
||||
if (c->num_input_files(l) != 0) {
|
||||
if (l == 0 && c->start_level() == 0) {
|
||||
for (size_t i = 0; i < c->num_input_files(0); i++) {
|
||||
input_file.f = c->input(0, i);
|
||||
input_file.level = 0;
|
||||
input_file.index = i;
|
||||
smallest_key_priority_q.push(std::move(input_file));
|
||||
smallest_key_priority_q.emplace(c->input(0, i), 0, i);
|
||||
}
|
||||
} else {
|
||||
input_file.f = c->input(l, 0);
|
||||
input_file.level = l;
|
||||
input_file.index = 0;
|
||||
smallest_key_priority_q.push(std::move(input_file));
|
||||
smallest_key_priority_q.emplace(c->input(l, 0), l, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -374,7 +368,7 @@ bool UniversalCompactionBuilder::IsInputFilesNonOverlapping(Compaction* c) {
|
||||
auto comparator = icmp_->user_comparator();
|
||||
int first_iter = 1;
|
||||
|
||||
InputFileInfo prev, curr, next;
|
||||
InputFileInfo prev, curr;
|
||||
|
||||
SmallestKeyHeap smallest_key_priority_q =
|
||||
create_level_heap(c, icmp_->user_comparator());
|
||||
@@ -397,17 +391,10 @@ bool UniversalCompactionBuilder::IsInputFilesNonOverlapping(Compaction* c) {
|
||||
prev = curr;
|
||||
}
|
||||
|
||||
next.f = nullptr;
|
||||
|
||||
if (c->level(curr.level) != 0 &&
|
||||
curr.index < c->num_input_files(curr.level) - 1) {
|
||||
next.f = c->input(curr.level, curr.index + 1);
|
||||
next.level = curr.level;
|
||||
next.index = curr.index + 1;
|
||||
}
|
||||
|
||||
if (next.f) {
|
||||
smallest_key_priority_q.push(std::move(next));
|
||||
smallest_key_priority_q.emplace(c->input(curr.level, curr.index + 1),
|
||||
curr.level, curr.index + 1);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -996,7 +983,7 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
|
||||
|
||||
if (output_level != 0 && picker_->FilesRangeOverlapWithCompaction(
|
||||
inputs, output_level,
|
||||
Compaction::EvaluatePenultimateLevel(
|
||||
Compaction::EvaluateProximalLevel(
|
||||
vstorage_, mutable_cf_options_, ioptions_,
|
||||
start_level, output_level))) {
|
||||
return nullptr;
|
||||
@@ -1345,7 +1332,7 @@ Compaction* UniversalCompactionBuilder::PickIncrementalForReduceSizeAmp(
|
||||
// intra L0 compactions outputs could have overlap
|
||||
if (output_level != 0 && picker_->FilesRangeOverlapWithCompaction(
|
||||
inputs, output_level,
|
||||
Compaction::EvaluatePenultimateLevel(
|
||||
Compaction::EvaluateProximalLevel(
|
||||
vstorage_, mutable_cf_options_, ioptions_,
|
||||
start_level, output_level))) {
|
||||
return nullptr;
|
||||
@@ -1486,9 +1473,9 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
|
||||
}
|
||||
if (picker_->FilesRangeOverlapWithCompaction(
|
||||
inputs, output_level,
|
||||
Compaction::EvaluatePenultimateLevel(
|
||||
vstorage_, mutable_cf_options_, ioptions_, start_level,
|
||||
output_level))) {
|
||||
Compaction::EvaluateProximalLevel(vstorage_, mutable_cf_options_,
|
||||
ioptions_, start_level,
|
||||
output_level))) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -1590,7 +1577,7 @@ Compaction* UniversalCompactionBuilder::PickCompactionWithSortedRunRange(
|
||||
// intra L0 compactions outputs could have overlap
|
||||
if (output_level != 0 && picker_->FilesRangeOverlapWithCompaction(
|
||||
inputs, output_level,
|
||||
Compaction::EvaluatePenultimateLevel(
|
||||
Compaction::EvaluateProximalLevel(
|
||||
vstorage_, mutable_cf_options_, ioptions_,
|
||||
start_level, output_level))) {
|
||||
return nullptr;
|
||||
|
||||
@@ -110,6 +110,9 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
break;
|
||||
}
|
||||
|
||||
std::string debug_str_before_wait =
|
||||
compaction->input_version()->DebugString();
|
||||
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
"[%s] [JOB %d] Waiting for remote compaction...",
|
||||
compaction->column_family_data()->GetName().c_str(), job_id_);
|
||||
@@ -118,6 +121,16 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
db_options_.compaction_service->Wait(response.scheduled_job_id,
|
||||
&compaction_result_binary);
|
||||
|
||||
if (compaction_status != CompactionServiceJobStatus::kSuccess) {
|
||||
ROCKS_LOG_ERROR(db_options_.info_log,
|
||||
"[%s] [JOB %d] Wait() status is not kSuccess. "
|
||||
"\nDebugString Before Wait():\n%s"
|
||||
"\nDebugString After Wait():\n%s",
|
||||
compaction->column_family_data()->GetName().c_str(),
|
||||
job_id_, debug_str_before_wait.c_str(),
|
||||
compaction->input_version()->DebugString().c_str());
|
||||
}
|
||||
|
||||
if (compaction_status == CompactionServiceJobStatus::kUseLocal) {
|
||||
ROCKS_LOG_INFO(
|
||||
db_options_.info_log,
|
||||
@@ -226,19 +239,34 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
meta.file_checksum_func_name = file.file_checksum_func_name;
|
||||
meta.marked_for_compaction = file.marked_for_compaction;
|
||||
meta.unique_id = file.unique_id;
|
||||
meta.temperature = file.file_temperature;
|
||||
|
||||
auto cfd = compaction->column_family_data();
|
||||
sub_compact->Current().AddOutput(std::move(meta),
|
||||
cfd->internal_comparator(), false, true,
|
||||
file.paranoid_hash);
|
||||
sub_compact->Current().UpdateTableProperties(file.table_properties);
|
||||
CompactionOutputs* compaction_outputs =
|
||||
sub_compact->Outputs(file.is_proximal_level_output);
|
||||
assert(compaction_outputs);
|
||||
compaction_outputs->AddOutput(std::move(meta), cfd->internal_comparator(),
|
||||
false, true, file.paranoid_hash);
|
||||
compaction_outputs->UpdateTableProperties(file.table_properties);
|
||||
}
|
||||
|
||||
// Set per-level stats
|
||||
auto compaction_output_stats =
|
||||
sub_compact->OutputStats(false /* is_proximal_level */);
|
||||
assert(compaction_output_stats);
|
||||
compaction_output_stats->Add(
|
||||
compaction_result.internal_stats.output_level_stats);
|
||||
if (compaction->SupportsPerKeyPlacement()) {
|
||||
compaction_output_stats =
|
||||
sub_compact->OutputStats(true /* is_proximal_level */);
|
||||
assert(compaction_output_stats);
|
||||
compaction_output_stats->Add(
|
||||
compaction_result.internal_stats.proximal_level_stats);
|
||||
}
|
||||
|
||||
// Set job stats
|
||||
sub_compact->compaction_job_stats = compaction_result.stats;
|
||||
sub_compact->Current().SetNumOutputRecords(
|
||||
compaction_result.stats.num_output_records);
|
||||
sub_compact->Current().SetNumOutputFiles(
|
||||
compaction_result.stats.num_output_files);
|
||||
sub_compact->Current().AddBytesWritten(compaction_result.bytes_written);
|
||||
|
||||
RecordTick(stats_, REMOTE_COMPACT_READ_BYTES, compaction_result.bytes_read);
|
||||
RecordTick(stats_, REMOTE_COMPACT_WRITE_BYTES,
|
||||
compaction_result.bytes_written);
|
||||
@@ -258,18 +286,6 @@ void CompactionServiceCompactionJob::RecordCompactionIOStats() {
|
||||
CompactionJob::RecordCompactionIOStats();
|
||||
}
|
||||
|
||||
void CompactionServiceCompactionJob::UpdateCompactionJobStats(
|
||||
const InternalStats::CompactionStats& stats) const {
|
||||
compaction_job_stats_->elapsed_micros = stats.micros;
|
||||
|
||||
// output information only in remote compaction
|
||||
compaction_job_stats_->total_output_bytes = stats.bytes_written;
|
||||
compaction_job_stats_->total_output_bytes_blob = stats.bytes_written_blob;
|
||||
compaction_job_stats_->num_output_records = stats.num_output_records;
|
||||
compaction_job_stats_->num_output_files = stats.num_output_files;
|
||||
compaction_job_stats_->num_output_files_blob = stats.num_output_files_blob;
|
||||
}
|
||||
|
||||
CompactionServiceCompactionJob::CompactionServiceCompactionJob(
|
||||
int job_id, Compaction* compaction, const ImmutableDBOptions& db_options,
|
||||
const MutableDBOptions& mutable_db_options, const FileOptions& file_options,
|
||||
@@ -331,15 +347,14 @@ Status CompactionServiceCompactionJob::Run() {
|
||||
|
||||
ProcessKeyValueCompaction(sub_compact);
|
||||
|
||||
compaction_stats_.stats.micros =
|
||||
db_options_.clock->NowMicros() - start_micros;
|
||||
compaction_stats_.stats.cpu_micros =
|
||||
sub_compact->compaction_job_stats.cpu_micros;
|
||||
uint64_t elapsed_micros = db_options_.clock->NowMicros() - start_micros;
|
||||
internal_stats_.SetMicros(elapsed_micros);
|
||||
internal_stats_.AddCpuMicros(elapsed_micros);
|
||||
|
||||
RecordTimeToHistogram(stats_, COMPACTION_TIME,
|
||||
compaction_stats_.stats.micros);
|
||||
internal_stats_.output_level_stats.micros);
|
||||
RecordTimeToHistogram(stats_, COMPACTION_CPU_TIME,
|
||||
compaction_stats_.stats.cpu_micros);
|
||||
internal_stats_.output_level_stats.cpu_micros);
|
||||
|
||||
Status status = sub_compact->status;
|
||||
IOStatus io_s = sub_compact->io_status;
|
||||
@@ -369,25 +384,31 @@ Status CompactionServiceCompactionJob::Run() {
|
||||
|
||||
// Build Compaction Job Stats
|
||||
|
||||
// 1. Aggregate CompactionOutputStats into Internal Compaction Stats
|
||||
// (compaction_stats_) and aggregate Compaction Job Stats
|
||||
// (compaction_job_stats_) from the sub compactions
|
||||
compact_->AggregateCompactionStats(compaction_stats_, *compaction_job_stats_);
|
||||
// 1. Aggregate internal stats and job stats for all subcompactions
|
||||
// internal stats: sub_compact.proximal_level_outputs_.stats and
|
||||
// sub_compact.compaction_outputs_.stats into
|
||||
// internal_stats_.output_level_stats and
|
||||
// internal_stats_.proximal_level_stats
|
||||
// job-level stats: sub_compact.compaction_job_stats into compact.job_stats_
|
||||
//
|
||||
// For remote compaction, there's only one subcompaction.
|
||||
compact_->AggregateCompactionStats(internal_stats_, *job_stats_);
|
||||
|
||||
// 2. Update the Output information in the Compaction Job Stats with
|
||||
// aggregated Internal Compaction Stats.
|
||||
UpdateCompactionJobStats(compaction_stats_.stats);
|
||||
|
||||
// 3. Set fields that are not propagated as part of aggregations above
|
||||
// 2. Update job-level output stats with the aggregated internal_stats_
|
||||
// Please note that input stats will be updated by primary host when all
|
||||
// subcompactions are finished
|
||||
UpdateCompactionJobOutputStats(internal_stats_);
|
||||
// and set fields that are not propagated as part of the update
|
||||
compaction_result_->stats.is_manual_compaction = c->is_manual_compaction();
|
||||
compaction_result_->stats.is_full_compaction = c->is_full_compaction();
|
||||
compaction_result_->stats.is_remote_compaction = true;
|
||||
|
||||
// 4. Update IO Stats that are not part of the aggregations above (bytes_read,
|
||||
// bytes_written)
|
||||
// 3. Update IO Stats that are not part of the the update above
|
||||
// (bytes_read, bytes_written)
|
||||
RecordCompactionIOStats();
|
||||
|
||||
// Build Output
|
||||
compaction_result_->internal_stats = internal_stats_;
|
||||
compaction_result_->output_level = compact_->compaction->output_level();
|
||||
compaction_result_->output_path = output_path_;
|
||||
if (status.ok()) {
|
||||
@@ -400,7 +421,8 @@ Status CompactionServiceCompactionJob::Run() {
|
||||
meta.file_creation_time, meta.epoch_number, meta.file_checksum,
|
||||
meta.file_checksum_func_name, output_file.validator.GetHash(),
|
||||
meta.marked_for_compaction, meta.unique_id,
|
||||
*output_file.table_properties);
|
||||
*output_file.table_properties, output_file.is_proximal_level,
|
||||
meta.temperature);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -572,7 +594,16 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
const auto this_one = static_cast<const TableProperties*>(addr1);
|
||||
const auto that_one = static_cast<const TableProperties*>(addr2);
|
||||
return this_one->AreEqual(opts, that_one, mismatch);
|
||||
}}}};
|
||||
}}},
|
||||
{"is_proximal_level_output",
|
||||
{offsetof(struct CompactionServiceOutputFile,
|
||||
is_proximal_level_output),
|
||||
OptionType::kBoolean, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"file_temperature",
|
||||
{offsetof(struct CompactionServiceOutputFile, file_temperature),
|
||||
OptionType::kTemperature, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}}};
|
||||
|
||||
static std::unordered_map<std::string, OptionTypeInfo>
|
||||
compaction_job_stats_type_info = {
|
||||
@@ -697,6 +728,125 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
OptionTypeFlags::kNone}},
|
||||
};
|
||||
|
||||
static std::unordered_map<std::string, OptionTypeInfo>
|
||||
compaction_stats_type_info = {
|
||||
{"micros",
|
||||
{offsetof(struct InternalStats::CompactionStats, micros),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"cpu_micros",
|
||||
{offsetof(struct InternalStats::CompactionStats, cpu_micros),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"bytes_read_non_output_levels",
|
||||
{offsetof(struct InternalStats::CompactionStats,
|
||||
bytes_read_non_output_levels),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"bytes_read_output_level",
|
||||
{offsetof(struct InternalStats::CompactionStats,
|
||||
bytes_read_output_level),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"bytes_skipped_non_output_levels",
|
||||
{offsetof(struct InternalStats::CompactionStats,
|
||||
bytes_skipped_non_output_levels),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"bytes_skipped_output_level",
|
||||
{offsetof(struct InternalStats::CompactionStats,
|
||||
bytes_skipped_output_level),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"bytes_read_blob",
|
||||
{offsetof(struct InternalStats::CompactionStats, bytes_read_blob),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"bytes_written",
|
||||
{offsetof(struct InternalStats::CompactionStats, bytes_written),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"bytes_written_blob",
|
||||
{offsetof(struct InternalStats::CompactionStats, bytes_written_blob),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"bytes_moved",
|
||||
{offsetof(struct InternalStats::CompactionStats, bytes_moved),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"num_input_files_in_non_output_levels",
|
||||
{offsetof(struct InternalStats::CompactionStats,
|
||||
num_input_files_in_non_output_levels),
|
||||
OptionType::kInt, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"num_input_files_in_output_level",
|
||||
{offsetof(struct InternalStats::CompactionStats,
|
||||
num_input_files_in_output_level),
|
||||
OptionType::kInt, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"num_filtered_input_files_in_non_output_levels",
|
||||
{offsetof(struct InternalStats::CompactionStats,
|
||||
num_filtered_input_files_in_non_output_levels),
|
||||
OptionType::kInt, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"num_filtered_input_files_in_output_level",
|
||||
{offsetof(struct InternalStats::CompactionStats,
|
||||
num_filtered_input_files_in_output_level),
|
||||
OptionType::kInt, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"num_output_files",
|
||||
{offsetof(struct InternalStats::CompactionStats, num_output_files),
|
||||
OptionType::kInt, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"num_output_files_blob",
|
||||
{offsetof(struct InternalStats::CompactionStats,
|
||||
num_output_files_blob),
|
||||
OptionType::kInt, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"num_input_records",
|
||||
{offsetof(struct InternalStats::CompactionStats, num_input_records),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"num_dropped_records",
|
||||
{offsetof(struct InternalStats::CompactionStats, num_dropped_records),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"num_output_records",
|
||||
{offsetof(struct InternalStats::CompactionStats, num_output_records),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"count",
|
||||
{offsetof(struct InternalStats::CompactionStats, count),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"counts", OptionTypeInfo::Array<
|
||||
int, static_cast<int>(CompactionReason::kNumOfReasons)>(
|
||||
offsetof(struct InternalStats::CompactionStats, counts),
|
||||
OptionVerificationType::kNormal, OptionTypeFlags::kNone,
|
||||
{0, OptionType::kInt})},
|
||||
};
|
||||
|
||||
static std::unordered_map<std::string, OptionTypeInfo>
|
||||
compaction_internal_stats_type_info = {
|
||||
{"output_level_stats",
|
||||
OptionTypeInfo::Struct(
|
||||
"output_level_stats", &compaction_stats_type_info,
|
||||
offsetof(struct InternalStats::CompactionStatsFull,
|
||||
output_level_stats),
|
||||
OptionVerificationType::kNormal, OptionTypeFlags::kNone)},
|
||||
{"has_proximal_level_output",
|
||||
{offsetof(struct InternalStats::CompactionStatsFull,
|
||||
has_proximal_level_output),
|
||||
OptionType::kBoolean, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"proximal_level_stats",
|
||||
OptionTypeInfo::Struct(
|
||||
"proximal_level_stats", &compaction_stats_type_info,
|
||||
offsetof(struct InternalStats::CompactionStatsFull,
|
||||
proximal_level_stats),
|
||||
OptionVerificationType::kNormal, OptionTypeFlags::kNone)},
|
||||
};
|
||||
|
||||
namespace {
|
||||
// this is a helper struct to serialize and deserialize class Status, because
|
||||
// Status's members are not public.
|
||||
@@ -803,6 +953,11 @@ static std::unordered_map<std::string, OptionTypeInfo> cs_result_type_info = {
|
||||
"stats", &compaction_job_stats_type_info,
|
||||
offsetof(struct CompactionServiceResult, stats),
|
||||
OptionVerificationType::kNormal, OptionTypeFlags::kNone)},
|
||||
{"internal_stats",
|
||||
OptionTypeInfo::Struct(
|
||||
"internal_stats", &compaction_internal_stats_type_info,
|
||||
offsetof(struct CompactionServiceResult, internal_stats),
|
||||
OptionVerificationType::kNormal, OptionTypeFlags::kNone)},
|
||||
};
|
||||
|
||||
Status CompactionServiceInput::Read(const std::string& data_str,
|
||||
|
||||
@@ -357,11 +357,12 @@ TEST_F(CompactionServiceTest, BasicCompactions) {
|
||||
} else {
|
||||
ASSERT_OK(result.status);
|
||||
}
|
||||
ASSERT_GE(result.stats.elapsed_micros, 1);
|
||||
ASSERT_GE(result.stats.cpu_micros, 1);
|
||||
ASSERT_GE(result.internal_stats.output_level_stats.micros, 1);
|
||||
ASSERT_GE(result.internal_stats.output_level_stats.cpu_micros, 1);
|
||||
|
||||
ASSERT_EQ(20, result.stats.num_output_records);
|
||||
ASSERT_EQ(result.output_files.size(), result.stats.num_output_files);
|
||||
ASSERT_EQ(20, result.internal_stats.output_level_stats.num_output_records);
|
||||
ASSERT_EQ(result.output_files.size(),
|
||||
result.internal_stats.output_level_stats.num_output_files);
|
||||
|
||||
uint64_t total_size = 0;
|
||||
for (auto output_file : result.output_files) {
|
||||
@@ -372,7 +373,7 @@ TEST_F(CompactionServiceTest, BasicCompactions) {
|
||||
ASSERT_GT(file_size, 0);
|
||||
total_size += file_size;
|
||||
}
|
||||
ASSERT_EQ(total_size, result.stats.total_output_bytes);
|
||||
ASSERT_EQ(total_size, result.internal_stats.TotalBytesWritten());
|
||||
|
||||
ASSERT_TRUE(result.stats.is_remote_compaction);
|
||||
ASSERT_TRUE(result.stats.is_manual_compaction);
|
||||
@@ -716,6 +717,46 @@ TEST_F(CompactionServiceTest, VerifyStatsLocalFallback) {
|
||||
VerifyTestData();
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, VerifyInputRecordCount) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
|
||||
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();
|
||||
|
||||
// Only iterator through 10 keys and force compaction to finish.
|
||||
int num_iter = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionJob::ProcessKeyValueCompaction()::stop", [&](void* stop_ptr) {
|
||||
num_iter++;
|
||||
if (num_iter == 10) {
|
||||
*(bool*)stop_ptr = true;
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// CompactRange() should fail
|
||||
Status s = db_->CompactRange(CompactRangeOptions(), &start, &end);
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
const char* expected_message =
|
||||
"Compaction number of input keys does not match number of keys "
|
||||
"processed.";
|
||||
ASSERT_TRUE(std::strstr(s.getState(), expected_message));
|
||||
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, CorruptedOutput) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
@@ -1188,34 +1229,38 @@ TEST_F(CompactionServiceTest, PrecludeLastLevel) {
|
||||
|
||||
for (int i = 0; i < kNumTrigger; i++) {
|
||||
for (int j = 0; j < kNumKeys; j++) {
|
||||
// FIXME: need to assign outputs to levels to allow overlapping ranges:
|
||||
// ASSERT_OK(Put(Key(j * kNumTrigger + i), "v" + std::to_string(i)));
|
||||
// instead of this (too easy):
|
||||
ASSERT_OK(Put(Key(i * kNumKeys + j), "v" + std::to_string(i)));
|
||||
ASSERT_OK(Put(Key(j * kNumTrigger + i), "v" + std::to_string(i)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
// Data split between penultimate (kUnknown) and last (kCold) levels
|
||||
// FIXME: need to assign outputs to levels to get this:
|
||||
// ASSERT_EQ("0,0,0,0,0,1,1", FilesPerLevel());
|
||||
// ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
|
||||
// ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
|
||||
// instead of this (WRONG but currently expected):
|
||||
ASSERT_EQ("0,0,0,0,0,0,2", FilesPerLevel());
|
||||
// Check manifest temperatures
|
||||
// Data split between proximal (kUnknown) and last (kCold) levels
|
||||
ASSERT_EQ("0,0,0,0,0,1,1", FilesPerLevel());
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
|
||||
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
|
||||
|
||||
// TODO: Check FileSystem temperatures with FileTemperatureTestFS
|
||||
|
||||
for (int i = 0; i < kNumTrigger; i++) {
|
||||
for (int j = 0; j < kNumKeys; j++) {
|
||||
// FIXME
|
||||
// ASSERT_EQ(Get(Key(j * kNumTrigger + i)), "v" + std::to_string(i));
|
||||
ASSERT_EQ(Get(Key(i * kNumKeys + j)), "v" + std::to_string(i));
|
||||
ASSERT_EQ(Get(Key(j * kNumTrigger + i)), "v" + std::to_string(i));
|
||||
}
|
||||
}
|
||||
|
||||
// Verify Output Stats
|
||||
auto my_cs = GetCompactionService();
|
||||
CompactionServiceResult result;
|
||||
my_cs->GetResult(&result);
|
||||
ASSERT_OK(result.status);
|
||||
ASSERT_GT(result.internal_stats.output_level_stats.cpu_micros, 0);
|
||||
ASSERT_GT(result.internal_stats.output_level_stats.micros, 0);
|
||||
ASSERT_EQ(result.internal_stats.output_level_stats.num_output_records +
|
||||
result.internal_stats.proximal_level_stats.num_output_records,
|
||||
kNumTrigger * kNumKeys);
|
||||
ASSERT_EQ(result.internal_stats.output_level_stats.num_output_files +
|
||||
result.internal_stats.proximal_level_stats.num_output_files,
|
||||
2);
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, ConcurrentCompaction) {
|
||||
|
||||
@@ -36,11 +36,11 @@ Slice CompactionState::LargestUserKey() {
|
||||
}
|
||||
|
||||
void CompactionState::AggregateCompactionStats(
|
||||
InternalStats::CompactionStatsFull& compaction_stats,
|
||||
CompactionJobStats& compaction_job_stats) {
|
||||
InternalStats::CompactionStatsFull& internal_stats,
|
||||
CompactionJobStats& job_stats) {
|
||||
for (const auto& sc : sub_compact_states) {
|
||||
sc.AggregateCompactionOutputStats(compaction_stats);
|
||||
compaction_job_stats.Add(sc.compaction_job_stats);
|
||||
sc.AggregateCompactionOutputStats(internal_stats);
|
||||
job_stats.Add(sc.compaction_job_stats);
|
||||
}
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -29,8 +29,8 @@ class CompactionState {
|
||||
Status status;
|
||||
|
||||
void AggregateCompactionStats(
|
||||
InternalStats::CompactionStatsFull& compaction_stats,
|
||||
CompactionJobStats& compaction_job_stats);
|
||||
InternalStats::CompactionStatsFull& internal_stats,
|
||||
CompactionJobStats& job_stats);
|
||||
|
||||
explicit CompactionState(Compaction* c) : compaction(c) {}
|
||||
|
||||
|
||||
@@ -14,33 +14,32 @@
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
void SubcompactionState::AggregateCompactionOutputStats(
|
||||
InternalStats::CompactionStatsFull& compaction_stats) const {
|
||||
InternalStats::CompactionStatsFull& internal_stats) const {
|
||||
// Outputs should be closed. By extension, any files created just for
|
||||
// range deletes have already been written also.
|
||||
assert(compaction_outputs_.HasBuilder() == false);
|
||||
assert(penultimate_level_outputs_.HasBuilder() == false);
|
||||
assert(proximal_level_outputs_.HasBuilder() == false);
|
||||
|
||||
// FIXME: These stats currently include abandonned output files
|
||||
// assert(compaction_outputs_.stats_.num_output_files ==
|
||||
// compaction_outputs_.outputs_.size());
|
||||
// assert(penultimate_level_outputs_.stats_.num_output_files ==
|
||||
// penultimate_level_outputs_.outputs_.size());
|
||||
// assert(proximal_level_outputs_.stats_.num_output_files ==
|
||||
// proximal_level_outputs_.outputs_.size());
|
||||
|
||||
compaction_stats.stats.Add(compaction_outputs_.stats_);
|
||||
if (penultimate_level_outputs_.HasOutput()) {
|
||||
compaction_stats.has_penultimate_level_output = true;
|
||||
compaction_stats.penultimate_level_stats.Add(
|
||||
penultimate_level_outputs_.stats_);
|
||||
internal_stats.output_level_stats.Add(compaction_outputs_.stats_);
|
||||
if (proximal_level_outputs_.HasOutput()) {
|
||||
internal_stats.has_proximal_level_output = true;
|
||||
internal_stats.proximal_level_stats.Add(proximal_level_outputs_.stats_);
|
||||
}
|
||||
}
|
||||
|
||||
OutputIterator SubcompactionState::GetOutputs() const {
|
||||
return OutputIterator(penultimate_level_outputs_.outputs_,
|
||||
return OutputIterator(proximal_level_outputs_.outputs_,
|
||||
compaction_outputs_.outputs_);
|
||||
}
|
||||
|
||||
void SubcompactionState::Cleanup(Cache* cache) {
|
||||
penultimate_level_outputs_.Cleanup();
|
||||
proximal_level_outputs_.Cleanup();
|
||||
compaction_outputs_.Cleanup();
|
||||
|
||||
if (!status.ok()) {
|
||||
@@ -63,9 +62,9 @@ void SubcompactionState::Cleanup(Cache* cache) {
|
||||
}
|
||||
|
||||
Slice SubcompactionState::SmallestUserKey() const {
|
||||
if (penultimate_level_outputs_.HasOutput()) {
|
||||
if (proximal_level_outputs_.HasOutput()) {
|
||||
Slice a = compaction_outputs_.SmallestUserKey();
|
||||
Slice b = penultimate_level_outputs_.SmallestUserKey();
|
||||
Slice b = proximal_level_outputs_.SmallestUserKey();
|
||||
if (a.empty()) {
|
||||
return b;
|
||||
}
|
||||
@@ -85,9 +84,9 @@ Slice SubcompactionState::SmallestUserKey() const {
|
||||
}
|
||||
|
||||
Slice SubcompactionState::LargestUserKey() const {
|
||||
if (penultimate_level_outputs_.HasOutput()) {
|
||||
if (proximal_level_outputs_.HasOutput()) {
|
||||
Slice a = compaction_outputs_.LargestUserKey();
|
||||
Slice b = penultimate_level_outputs_.LargestUserKey();
|
||||
Slice b = proximal_level_outputs_.LargestUserKey();
|
||||
if (a.empty()) {
|
||||
return b;
|
||||
}
|
||||
@@ -107,12 +106,12 @@ Slice SubcompactionState::LargestUserKey() const {
|
||||
}
|
||||
|
||||
Status SubcompactionState::AddToOutput(
|
||||
const CompactionIterator& iter, bool use_penultimate_output,
|
||||
const CompactionIterator& iter, bool use_proximal_output,
|
||||
const CompactionFileOpenFunc& open_file_func,
|
||||
const CompactionFileCloseFunc& close_file_func) {
|
||||
// update target output
|
||||
current_outputs_ = use_penultimate_output ? &penultimate_level_outputs_
|
||||
: &compaction_outputs_;
|
||||
current_outputs_ =
|
||||
use_proximal_output ? &proximal_level_outputs_ : &compaction_outputs_;
|
||||
return current_outputs_->AddToOutput(iter, open_file_func, close_file_func);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,13 +26,13 @@ namespace ROCKSDB_NAMESPACE {
|
||||
// Maintains state and outputs for each sub-compaction
|
||||
// It contains 2 `CompactionOutputs`:
|
||||
// 1. one for the normal output files
|
||||
// 2. another for the penultimate level outputs
|
||||
// 2. another for the proximal level outputs
|
||||
// a `current` pointer maintains the current output group, when calling
|
||||
// `AddToOutput()`, it checks the output of the current compaction_iterator key
|
||||
// and point `current` to the target output group. By default, it just points to
|
||||
// normal compaction_outputs, if the compaction_iterator key should be placed on
|
||||
// the penultimate level, `current` is changed to point to
|
||||
// `penultimate_level_outputs`.
|
||||
// the proximal level, `current` is changed to point to
|
||||
// `proximal_level_outputs`.
|
||||
// The later operations uses `Current()` to get the target group.
|
||||
//
|
||||
// +----------+ +-----------------------------+ +---------+
|
||||
@@ -43,7 +43,7 @@ namespace ROCKSDB_NAMESPACE {
|
||||
// | | ... |
|
||||
// |
|
||||
// | +-----------------------------+ +---------+
|
||||
// +-------------> | penultimate_level_outputs |----->| output |
|
||||
// +-------------> | proximal_level_outputs |----->| output |
|
||||
// +-----------------------------+ +---------+
|
||||
// | ... |
|
||||
|
||||
@@ -78,7 +78,7 @@ class SubcompactionState {
|
||||
Slice LargestUserKey() const;
|
||||
|
||||
// Get all outputs from the subcompaction. For per_key_placement compaction,
|
||||
// it returns both the last level outputs and penultimate level outputs.
|
||||
// it returns both the last level outputs and proximal level outputs.
|
||||
OutputIterator GetOutputs() const;
|
||||
|
||||
// Assign range dels aggregator. The various tombstones will potentially
|
||||
@@ -92,7 +92,7 @@ class SubcompactionState {
|
||||
|
||||
void RemoveLastEmptyOutput() {
|
||||
compaction_outputs_.RemoveLastEmptyOutput();
|
||||
penultimate_level_outputs_.RemoveLastEmptyOutput();
|
||||
proximal_level_outputs_.RemoveLastEmptyOutput();
|
||||
}
|
||||
|
||||
void BuildSubcompactionJobInfo(
|
||||
@@ -119,14 +119,14 @@ class SubcompactionState {
|
||||
start(_start),
|
||||
end(_end),
|
||||
sub_job_id(_sub_job_id),
|
||||
compaction_outputs_(c, /*is_penultimate_level=*/false),
|
||||
penultimate_level_outputs_(c, /*is_penultimate_level=*/true) {
|
||||
compaction_outputs_(c, /*is_proximal_level=*/false),
|
||||
proximal_level_outputs_(c, /*is_proximal_level=*/true) {
|
||||
assert(compaction != nullptr);
|
||||
// Set output split key (used for RoundRobin feature) only for normal
|
||||
// compaction_outputs, output to penultimate_level feature doesn't support
|
||||
// compaction_outputs, output to proximal_level feature doesn't support
|
||||
// RoundRobin feature (and may never going to be supported, because for
|
||||
// RoundRobin, the data time is mostly naturally sorted, no need to have
|
||||
// per-key placement with output_to_penultimate_level).
|
||||
// per-key placement with output_to_proximal_level).
|
||||
compaction_outputs_.SetOutputSlitKey(start, end);
|
||||
}
|
||||
|
||||
@@ -141,18 +141,17 @@ class SubcompactionState {
|
||||
compaction_job_stats(std::move(state.compaction_job_stats)),
|
||||
sub_job_id(state.sub_job_id),
|
||||
compaction_outputs_(std::move(state.compaction_outputs_)),
|
||||
penultimate_level_outputs_(std::move(state.penultimate_level_outputs_)),
|
||||
proximal_level_outputs_(std::move(state.proximal_level_outputs_)),
|
||||
range_del_agg_(std::move(state.range_del_agg_)) {
|
||||
current_outputs_ =
|
||||
state.current_outputs_ == &state.penultimate_level_outputs_
|
||||
? &penultimate_level_outputs_
|
||||
: &compaction_outputs_;
|
||||
current_outputs_ = state.current_outputs_ == &state.proximal_level_outputs_
|
||||
? &proximal_level_outputs_
|
||||
: &compaction_outputs_;
|
||||
}
|
||||
|
||||
// Add all the new files from this compaction to version_edit
|
||||
void AddOutputsEdit(VersionEdit* out_edit) const {
|
||||
for (const auto& file : penultimate_level_outputs_.outputs_) {
|
||||
out_edit->AddFile(compaction->GetPenultimateLevel(), file.meta);
|
||||
for (const auto& file : proximal_level_outputs_.outputs_) {
|
||||
out_edit->AddFile(compaction->GetProximalLevel(), file.meta);
|
||||
}
|
||||
for (const auto& file : compaction_outputs_.outputs_) {
|
||||
out_edit->AddFile(compaction->output_level(), file.meta);
|
||||
@@ -162,13 +161,32 @@ class SubcompactionState {
|
||||
void Cleanup(Cache* cache);
|
||||
|
||||
void AggregateCompactionOutputStats(
|
||||
InternalStats::CompactionStatsFull& compaction_stats) const;
|
||||
InternalStats::CompactionStatsFull& internal_stats) const;
|
||||
|
||||
CompactionOutputs& Current() const {
|
||||
assert(current_outputs_);
|
||||
return *current_outputs_;
|
||||
}
|
||||
|
||||
CompactionOutputs* Outputs(bool is_proximal_level) {
|
||||
assert(compaction);
|
||||
if (is_proximal_level) {
|
||||
assert(compaction->SupportsPerKeyPlacement());
|
||||
return &proximal_level_outputs_;
|
||||
}
|
||||
return &compaction_outputs_;
|
||||
}
|
||||
|
||||
// Per-level stats for the output
|
||||
InternalStats::CompactionStats* OutputStats(bool is_proximal_level) {
|
||||
assert(compaction);
|
||||
if (is_proximal_level) {
|
||||
assert(compaction->SupportsPerKeyPlacement());
|
||||
return &proximal_level_outputs_.stats_;
|
||||
}
|
||||
return &compaction_outputs_.stats_;
|
||||
}
|
||||
|
||||
CompactionRangeDelAggregator* RangeDelAgg() const {
|
||||
return range_del_agg_.get();
|
||||
}
|
||||
@@ -179,12 +197,11 @@ class SubcompactionState {
|
||||
}
|
||||
|
||||
// Add compaction_iterator key/value to the `Current` output group.
|
||||
Status AddToOutput(const CompactionIterator& iter,
|
||||
bool use_penultimate_output,
|
||||
Status AddToOutput(const CompactionIterator& iter, bool use_proximal_output,
|
||||
const CompactionFileOpenFunc& open_file_func,
|
||||
const CompactionFileCloseFunc& close_file_func);
|
||||
|
||||
// Close all compaction output files, both output_to_penultimate_level outputs
|
||||
// Close all compaction output files, both output_to_proximal_level outputs
|
||||
// and normal outputs.
|
||||
Status CloseCompactionFiles(const Status& curr_status,
|
||||
const CompactionFileOpenFunc& open_file_func,
|
||||
@@ -195,11 +212,11 @@ class SubcompactionState {
|
||||
// CloseOutput() may open new compaction output files.
|
||||
Status s = curr_status;
|
||||
if (per_key) {
|
||||
s = penultimate_level_outputs_.CloseOutput(
|
||||
s, range_del_agg_.get(), open_file_func, close_file_func);
|
||||
s = proximal_level_outputs_.CloseOutput(s, range_del_agg_.get(),
|
||||
open_file_func, close_file_func);
|
||||
} else {
|
||||
assert(penultimate_level_outputs_.HasBuilder() == false);
|
||||
assert(penultimate_level_outputs_.HasOutput() == false);
|
||||
assert(proximal_level_outputs_.HasBuilder() == false);
|
||||
assert(proximal_level_outputs_.HasOutput() == false);
|
||||
}
|
||||
s = compaction_outputs_.CloseOutput(s, range_del_agg_.get(), open_file_func,
|
||||
close_file_func);
|
||||
@@ -209,7 +226,7 @@ class SubcompactionState {
|
||||
private:
|
||||
// State kept for output being generated
|
||||
CompactionOutputs compaction_outputs_;
|
||||
CompactionOutputs penultimate_level_outputs_;
|
||||
CompactionOutputs proximal_level_outputs_;
|
||||
CompactionOutputs* current_outputs_ = &compaction_outputs_;
|
||||
std::unique_ptr<CompactionRangeDelAggregator> range_del_agg_;
|
||||
};
|
||||
|
||||
@@ -33,42 +33,13 @@ ConfigOptions GetStrictConfigOptions() {
|
||||
class TieredCompactionTest : public DBTestBase {
|
||||
public:
|
||||
TieredCompactionTest()
|
||||
: DBTestBase("tiered_compaction_test", /*env_do_fsync=*/true),
|
||||
kBasicCompStats(CompactionReason::kUniversalSizeAmplification, 1),
|
||||
kBasicPerKeyPlacementCompStats(
|
||||
CompactionReason::kUniversalSizeAmplification, 1),
|
||||
kBasicFlushStats(CompactionReason::kFlush, 1) {
|
||||
kBasicCompStats.micros = kHasValue;
|
||||
kBasicCompStats.cpu_micros = kHasValue;
|
||||
kBasicCompStats.bytes_read_non_output_levels = kHasValue;
|
||||
kBasicCompStats.num_input_files_in_non_output_levels = kHasValue;
|
||||
kBasicCompStats.num_input_records = kHasValue;
|
||||
kBasicCompStats.num_dropped_records = kHasValue;
|
||||
|
||||
kBasicPerLevelStats.num_output_records = kHasValue;
|
||||
kBasicPerLevelStats.bytes_written = kHasValue;
|
||||
kBasicPerLevelStats.num_output_files = kHasValue;
|
||||
|
||||
kBasicPerKeyPlacementCompStats.micros = kHasValue;
|
||||
kBasicPerKeyPlacementCompStats.cpu_micros = kHasValue;
|
||||
kBasicPerKeyPlacementCompStats.Add(kBasicPerLevelStats);
|
||||
|
||||
kBasicFlushStats.micros = kHasValue;
|
||||
kBasicFlushStats.cpu_micros = kHasValue;
|
||||
kBasicFlushStats.bytes_written = kHasValue;
|
||||
kBasicFlushStats.num_output_files = kHasValue;
|
||||
}
|
||||
: DBTestBase("tiered_compaction_test", /*env_do_fsync=*/true) {}
|
||||
|
||||
protected:
|
||||
static constexpr uint8_t kHasValue = 1;
|
||||
|
||||
InternalStats::CompactionStats kBasicCompStats;
|
||||
InternalStats::CompactionStats kBasicPerKeyPlacementCompStats;
|
||||
InternalStats::CompactionOutputsStats kBasicPerLevelStats;
|
||||
InternalStats::CompactionStats kBasicFlushStats;
|
||||
|
||||
std::atomic_bool enable_per_key_placement = true;
|
||||
|
||||
CompactionJobStats job_stats;
|
||||
|
||||
void SetUp() override {
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"Compaction::SupportsPerKeyPlacement:Enabled", [&](void* arg) {
|
||||
@@ -108,21 +79,36 @@ class TieredCompactionTest : public DBTestBase {
|
||||
|
||||
// Verify the compaction stats, the stats are roughly compared
|
||||
void VerifyCompactionStats(
|
||||
const std::vector<InternalStats::CompactionStats>& expect_stats,
|
||||
const InternalStats::CompactionStats& expect_pl_stats) {
|
||||
const std::vector<InternalStats::CompactionStats>& expected_stats,
|
||||
const InternalStats::CompactionStats& expected_pl_stats,
|
||||
size_t output_level, uint64_t num_input_range_del = 0) {
|
||||
const std::vector<InternalStats::CompactionStats>& stats =
|
||||
GetCompactionStats();
|
||||
const size_t kLevels = expect_stats.size();
|
||||
const size_t kLevels = expected_stats.size();
|
||||
ASSERT_EQ(kLevels, stats.size());
|
||||
ASSERT_TRUE(output_level < kLevels);
|
||||
|
||||
for (auto it = stats.begin(), expect = expect_stats.begin();
|
||||
it != stats.end(); it++, expect++) {
|
||||
VerifyCompactionStats(*it, *expect);
|
||||
for (size_t level = 0; level < kLevels; level++) {
|
||||
VerifyCompactionStats(stats[level], expected_stats[level]);
|
||||
}
|
||||
|
||||
const InternalStats::CompactionStats& pl_stats =
|
||||
GetPerKeyPlacementCompactionStats();
|
||||
VerifyCompactionStats(pl_stats, expect_pl_stats);
|
||||
VerifyCompactionStats(pl_stats, expected_pl_stats);
|
||||
|
||||
const auto& output_level_stats = stats[output_level];
|
||||
CompactionJobStats expected_job_stats;
|
||||
expected_job_stats.cpu_micros = output_level_stats.cpu_micros;
|
||||
expected_job_stats.num_input_files =
|
||||
output_level_stats.num_input_files_in_output_level +
|
||||
output_level_stats.num_input_files_in_non_output_levels;
|
||||
expected_job_stats.num_input_records =
|
||||
output_level_stats.num_input_records - num_input_range_del;
|
||||
expected_job_stats.num_output_files =
|
||||
output_level_stats.num_output_files + pl_stats.num_output_files;
|
||||
expected_job_stats.num_output_records =
|
||||
output_level_stats.num_output_records + pl_stats.num_output_records;
|
||||
VerifyCompactionJobStats(job_stats, expected_job_stats);
|
||||
}
|
||||
|
||||
void ResetAllStats(std::vector<InternalStats::CompactionStats>& stats,
|
||||
@@ -139,42 +125,52 @@ class TieredCompactionTest : public DBTestBase {
|
||||
}
|
||||
|
||||
private:
|
||||
void CompareStats(uint64_t val, uint64_t expect) {
|
||||
if (expect > 0) {
|
||||
ASSERT_TRUE(val > 0);
|
||||
} else {
|
||||
ASSERT_EQ(val, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void VerifyCompactionStats(
|
||||
const InternalStats::CompactionStats& stats,
|
||||
const InternalStats::CompactionStats& expect_stats) {
|
||||
CompareStats(stats.micros, expect_stats.micros);
|
||||
CompareStats(stats.cpu_micros, expect_stats.cpu_micros);
|
||||
CompareStats(stats.bytes_read_non_output_levels,
|
||||
expect_stats.bytes_read_non_output_levels);
|
||||
CompareStats(stats.bytes_read_output_level,
|
||||
expect_stats.bytes_read_output_level);
|
||||
CompareStats(stats.bytes_read_blob, expect_stats.bytes_read_blob);
|
||||
CompareStats(stats.bytes_written, expect_stats.bytes_written);
|
||||
CompareStats(stats.bytes_moved, expect_stats.bytes_moved);
|
||||
CompareStats(stats.num_input_files_in_non_output_levels,
|
||||
expect_stats.num_input_files_in_non_output_levels);
|
||||
CompareStats(stats.num_input_files_in_output_level,
|
||||
expect_stats.num_input_files_in_output_level);
|
||||
CompareStats(stats.num_output_files, expect_stats.num_output_files);
|
||||
CompareStats(stats.num_output_files_blob,
|
||||
expect_stats.num_output_files_blob);
|
||||
CompareStats(stats.num_input_records, expect_stats.num_input_records);
|
||||
CompareStats(stats.num_dropped_records, expect_stats.num_dropped_records);
|
||||
CompareStats(stats.num_output_records, expect_stats.num_output_records);
|
||||
ASSERT_EQ(stats.micros > 0, expect_stats.micros > 0);
|
||||
ASSERT_EQ(stats.cpu_micros > 0, expect_stats.cpu_micros > 0);
|
||||
|
||||
// Hard to get consistent byte sizes of SST files.
|
||||
// Use ASSERT_NEAR for comparison
|
||||
ASSERT_NEAR(stats.bytes_read_non_output_levels * 1.0f,
|
||||
expect_stats.bytes_read_non_output_levels * 1.0f,
|
||||
stats.bytes_read_non_output_levels * 0.5f);
|
||||
ASSERT_NEAR(stats.bytes_read_output_level * 1.0f,
|
||||
expect_stats.bytes_read_output_level * 1.0f,
|
||||
stats.bytes_read_output_level * 0.5f);
|
||||
ASSERT_NEAR(stats.bytes_read_blob * 1.0f,
|
||||
expect_stats.bytes_read_blob * 1.0f,
|
||||
stats.bytes_read_blob * 0.5f);
|
||||
ASSERT_NEAR(stats.bytes_written * 1.0f, expect_stats.bytes_written * 1.0f,
|
||||
stats.bytes_written * 0.5f);
|
||||
|
||||
ASSERT_EQ(stats.bytes_moved, expect_stats.bytes_moved);
|
||||
ASSERT_EQ(stats.num_input_files_in_non_output_levels,
|
||||
expect_stats.num_input_files_in_non_output_levels);
|
||||
ASSERT_EQ(stats.num_input_files_in_output_level,
|
||||
expect_stats.num_input_files_in_output_level);
|
||||
ASSERT_EQ(stats.num_output_files, expect_stats.num_output_files);
|
||||
ASSERT_EQ(stats.num_output_files_blob, expect_stats.num_output_files_blob);
|
||||
ASSERT_EQ(stats.num_input_records, expect_stats.num_input_records);
|
||||
ASSERT_EQ(stats.num_dropped_records, expect_stats.num_dropped_records);
|
||||
ASSERT_EQ(stats.num_output_records, expect_stats.num_output_records);
|
||||
|
||||
ASSERT_EQ(stats.count, expect_stats.count);
|
||||
for (int i = 0; i < static_cast<int>(CompactionReason::kNumOfReasons);
|
||||
i++) {
|
||||
ASSERT_EQ(stats.counts[i], expect_stats.counts[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void VerifyCompactionJobStats(const CompactionJobStats& stats,
|
||||
const CompactionJobStats& expected_stats) {
|
||||
ASSERT_EQ(stats.cpu_micros, expected_stats.cpu_micros);
|
||||
ASSERT_EQ(stats.num_input_files, expected_stats.num_input_files);
|
||||
ASSERT_EQ(stats.num_input_records, expected_stats.num_input_records);
|
||||
ASSERT_EQ(job_stats.num_output_files, expected_stats.num_output_files);
|
||||
ASSERT_EQ(job_stats.num_output_records, expected_stats.num_output_records);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(TieredCompactionTest, SequenceBasedTieredStorageUniversal) {
|
||||
@@ -199,19 +195,37 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageUniversal) {
|
||||
[&](void* arg) {
|
||||
*static_cast<SequenceNumber*>(arg) = latest_cold_seq.load();
|
||||
});
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionJob::Install:AfterUpdateCompactionJobStats", [&](void* arg) {
|
||||
job_stats.Reset();
|
||||
job_stats.Add(*(static_cast<CompactionJobStats*>(arg)));
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::vector<InternalStats::CompactionStats> expect_stats(kNumLevels);
|
||||
InternalStats::CompactionStats& last_stats = expect_stats[kLastLevel];
|
||||
InternalStats::CompactionStats expect_pl_stats;
|
||||
|
||||
// Put keys in the following way to create overlaps
|
||||
// First file from 0 ~ 99
|
||||
// Second file from 10 ~ 109
|
||||
// ...
|
||||
size_t bytes_per_file = 1952;
|
||||
uint64_t total_input_key_count = kNumTrigger * kNumKeys;
|
||||
uint64_t total_output_key_count = 130; // 0 ~ 129
|
||||
|
||||
for (int i = 0; i < kNumTrigger; i++) {
|
||||
for (int j = 0; j < kNumKeys; j++) {
|
||||
ASSERT_OK(Put(Key(i * 10 + j), "value" + std::to_string(i)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
seq_history.emplace_back(dbfull()->GetLatestSequenceNumber());
|
||||
expect_stats[0].Add(kBasicFlushStats);
|
||||
InternalStats::CompactionStats flush_stats(CompactionReason::kFlush, 1);
|
||||
flush_stats.cpu_micros = 1;
|
||||
flush_stats.micros = 1;
|
||||
flush_stats.bytes_written = bytes_per_file;
|
||||
flush_stats.num_output_files = 1;
|
||||
expect_stats[0].Add(flush_stats);
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
@@ -221,32 +235,97 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageUniversal) {
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
|
||||
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
|
||||
|
||||
// basic compaction stats are still counted to the last level
|
||||
expect_stats[kLastLevel].Add(kBasicCompStats);
|
||||
expect_pl_stats.Add(kBasicPerKeyPlacementCompStats);
|
||||
uint64_t bytes_written_penultimate_level =
|
||||
GetPerKeyPlacementCompactionStats().bytes_written;
|
||||
|
||||
VerifyCompactionStats(expect_stats, expect_pl_stats);
|
||||
// TODO - Use designated initializer when c++20 support is required
|
||||
{
|
||||
InternalStats::CompactionStats last_level_compaction_stats(
|
||||
CompactionReason::kUniversalSizeAmplification, 1);
|
||||
last_level_compaction_stats.cpu_micros = 1;
|
||||
last_level_compaction_stats.micros = 1;
|
||||
last_level_compaction_stats.bytes_written = 0;
|
||||
last_level_compaction_stats.bytes_read_non_output_levels =
|
||||
bytes_per_file * kNumTrigger;
|
||||
last_level_compaction_stats.num_input_files_in_non_output_levels =
|
||||
kNumTrigger;
|
||||
last_level_compaction_stats.num_input_records = total_input_key_count;
|
||||
last_level_compaction_stats.num_dropped_records =
|
||||
total_input_key_count - total_output_key_count;
|
||||
last_level_compaction_stats.num_output_records = 0;
|
||||
last_level_compaction_stats.num_output_files = 0;
|
||||
expect_stats[kLastLevel].Add(last_level_compaction_stats);
|
||||
}
|
||||
{
|
||||
InternalStats::CompactionStats penultimate_level_compaction_stats(
|
||||
CompactionReason::kUniversalSizeAmplification, 1);
|
||||
penultimate_level_compaction_stats.cpu_micros = 1;
|
||||
penultimate_level_compaction_stats.micros = 1;
|
||||
penultimate_level_compaction_stats.bytes_written =
|
||||
bytes_written_penultimate_level;
|
||||
penultimate_level_compaction_stats.num_output_files = 1;
|
||||
penultimate_level_compaction_stats.num_output_records =
|
||||
total_output_key_count;
|
||||
expect_pl_stats.Add(penultimate_level_compaction_stats);
|
||||
}
|
||||
VerifyCompactionStats(expect_stats, expect_pl_stats, kLastLevel);
|
||||
|
||||
ResetAllStats(expect_stats, expect_pl_stats);
|
||||
|
||||
// move forward the cold_seq to split the file into 2 levels, so should have
|
||||
// both the last level stats and the output_to_penultimate_level stats
|
||||
// both the last level stats and the penultimate level stats
|
||||
latest_cold_seq = seq_history[0];
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ("0,0,0,0,0,1,1", FilesPerLevel());
|
||||
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
|
||||
|
||||
last_stats.Add(kBasicCompStats);
|
||||
last_stats.ResetCompactionReason(CompactionReason::kManualCompaction);
|
||||
last_stats.Add(kBasicPerLevelStats);
|
||||
last_stats.num_dropped_records = 0;
|
||||
expect_pl_stats.Add(kBasicPerKeyPlacementCompStats);
|
||||
expect_pl_stats.ResetCompactionReason(CompactionReason::kManualCompaction);
|
||||
VerifyCompactionStats(expect_stats, expect_pl_stats);
|
||||
// Now update the input count to be the total count from the previous
|
||||
total_input_key_count = total_output_key_count;
|
||||
uint64_t moved_to_last_level_key_count = 10;
|
||||
|
||||
// delete all cold data, so all data will be on penultimate level
|
||||
// bytes read in non output = bytes written in penultimate level from previous
|
||||
uint64_t bytes_read_in_non_output_level = bytes_written_penultimate_level;
|
||||
uint64_t bytes_written_output_level =
|
||||
GetCompactionStats()[kLastLevel].bytes_written;
|
||||
|
||||
// Now get the new bytes written in penultimate level
|
||||
bytes_written_penultimate_level =
|
||||
GetPerKeyPlacementCompactionStats().bytes_written;
|
||||
{
|
||||
InternalStats::CompactionStats last_level_compaction_stats(
|
||||
CompactionReason::kManualCompaction, 1);
|
||||
last_level_compaction_stats.cpu_micros = 1;
|
||||
last_level_compaction_stats.micros = 1;
|
||||
last_level_compaction_stats.bytes_written = bytes_written_output_level;
|
||||
last_level_compaction_stats.bytes_read_non_output_levels =
|
||||
bytes_read_in_non_output_level;
|
||||
last_level_compaction_stats.num_input_files_in_non_output_levels = 1;
|
||||
last_level_compaction_stats.num_input_records = total_input_key_count;
|
||||
last_level_compaction_stats.num_dropped_records =
|
||||
total_input_key_count - total_output_key_count;
|
||||
last_level_compaction_stats.num_output_records =
|
||||
moved_to_last_level_key_count;
|
||||
last_level_compaction_stats.num_output_files = 1;
|
||||
expect_stats[kLastLevel].Add(last_level_compaction_stats);
|
||||
}
|
||||
{
|
||||
InternalStats::CompactionStats penultimate_level_compaction_stats(
|
||||
CompactionReason::kManualCompaction, 1);
|
||||
penultimate_level_compaction_stats.cpu_micros = 1;
|
||||
penultimate_level_compaction_stats.micros = 1;
|
||||
penultimate_level_compaction_stats.bytes_written =
|
||||
bytes_written_penultimate_level;
|
||||
penultimate_level_compaction_stats.num_output_files = 1;
|
||||
penultimate_level_compaction_stats.num_output_records =
|
||||
total_output_key_count - moved_to_last_level_key_count;
|
||||
expect_pl_stats.Add(penultimate_level_compaction_stats);
|
||||
}
|
||||
VerifyCompactionStats(expect_stats, expect_pl_stats, kLastLevel);
|
||||
|
||||
// delete all cold data, so all data will be on proximal level
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ASSERT_OK(Delete(Key(i)));
|
||||
}
|
||||
@@ -255,17 +334,54 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageUniversal) {
|
||||
ResetAllStats(expect_stats, expect_pl_stats);
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ("0,0,0,0,0,1", FilesPerLevel());
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
|
||||
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
|
||||
|
||||
last_stats.Add(kBasicCompStats);
|
||||
last_stats.ResetCompactionReason(CompactionReason::kManualCompaction);
|
||||
last_stats.bytes_read_output_level = kHasValue;
|
||||
last_stats.num_input_files_in_output_level = kHasValue;
|
||||
expect_pl_stats.Add(kBasicPerKeyPlacementCompStats);
|
||||
expect_pl_stats.ResetCompactionReason(CompactionReason::kManualCompaction);
|
||||
VerifyCompactionStats(expect_stats, expect_pl_stats);
|
||||
// 10 tombstones added
|
||||
total_input_key_count = total_input_key_count + 10;
|
||||
total_output_key_count = total_output_key_count - 10;
|
||||
|
||||
auto last_level_stats = GetCompactionStats()[kLastLevel];
|
||||
bytes_written_penultimate_level =
|
||||
GetPerKeyPlacementCompactionStats().bytes_written;
|
||||
|
||||
ASSERT_LT(bytes_written_penultimate_level,
|
||||
last_level_stats.bytes_read_non_output_levels +
|
||||
last_level_stats.bytes_read_output_level);
|
||||
{
|
||||
InternalStats::CompactionStats last_level_compaction_stats(
|
||||
CompactionReason::kManualCompaction, 1);
|
||||
last_level_compaction_stats.cpu_micros = 1;
|
||||
last_level_compaction_stats.micros = 1;
|
||||
last_level_compaction_stats.bytes_written = 0;
|
||||
last_level_compaction_stats.bytes_read_non_output_levels =
|
||||
last_level_stats.bytes_read_non_output_levels;
|
||||
last_level_compaction_stats.bytes_read_output_level =
|
||||
last_level_stats.bytes_read_output_level;
|
||||
last_level_compaction_stats.num_input_files_in_non_output_levels = 2;
|
||||
last_level_compaction_stats.num_input_files_in_output_level = 1;
|
||||
last_level_compaction_stats.num_input_records = total_input_key_count;
|
||||
last_level_compaction_stats.num_dropped_records =
|
||||
total_input_key_count - total_output_key_count;
|
||||
last_level_compaction_stats.num_output_records = 0;
|
||||
last_level_compaction_stats.num_output_files = 0;
|
||||
expect_stats[kLastLevel].Add(last_level_compaction_stats);
|
||||
}
|
||||
{
|
||||
InternalStats::CompactionStats penultimate_level_compaction_stats(
|
||||
CompactionReason::kManualCompaction, 1);
|
||||
penultimate_level_compaction_stats.cpu_micros = 1;
|
||||
penultimate_level_compaction_stats.micros = 1;
|
||||
penultimate_level_compaction_stats.bytes_written =
|
||||
bytes_written_penultimate_level;
|
||||
penultimate_level_compaction_stats.num_output_files = 1;
|
||||
penultimate_level_compaction_stats.num_output_records =
|
||||
total_output_key_count;
|
||||
expect_pl_stats.Add(penultimate_level_compaction_stats);
|
||||
}
|
||||
VerifyCompactionStats(expect_stats, expect_pl_stats, kLastLevel);
|
||||
|
||||
// move forward the cold_seq again with range delete, take a snapshot to keep
|
||||
// the range dels in both cold and hot SSTs
|
||||
@@ -275,6 +391,7 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageUniversal) {
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), start, end));
|
||||
ASSERT_OK(Flush());
|
||||
uint64_t num_input_range_del = 1;
|
||||
|
||||
ResetAllStats(expect_stats, expect_pl_stats);
|
||||
|
||||
@@ -283,12 +400,49 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageUniversal) {
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
|
||||
|
||||
last_stats.Add(kBasicCompStats);
|
||||
last_stats.Add(kBasicPerLevelStats);
|
||||
last_stats.ResetCompactionReason(CompactionReason::kManualCompaction);
|
||||
expect_pl_stats.Add(kBasicPerKeyPlacementCompStats);
|
||||
expect_pl_stats.ResetCompactionReason(CompactionReason::kManualCompaction);
|
||||
VerifyCompactionStats(expect_stats, expect_pl_stats);
|
||||
// Previous output + one delete range
|
||||
total_input_key_count = total_output_key_count + num_input_range_del;
|
||||
moved_to_last_level_key_count = 20;
|
||||
|
||||
last_level_stats = GetCompactionStats()[kLastLevel];
|
||||
bytes_written_penultimate_level =
|
||||
GetPerKeyPlacementCompactionStats().bytes_written;
|
||||
// Expected to write more in last level
|
||||
ASSERT_GT(bytes_written_penultimate_level, last_level_stats.bytes_written);
|
||||
{
|
||||
InternalStats::CompactionStats last_level_compaction_stats(
|
||||
CompactionReason::kManualCompaction, 1);
|
||||
last_level_compaction_stats.cpu_micros = 1;
|
||||
last_level_compaction_stats.micros = 1;
|
||||
last_level_compaction_stats.bytes_written = last_level_stats.bytes_written;
|
||||
last_level_compaction_stats.bytes_read_non_output_levels =
|
||||
last_level_stats.bytes_read_non_output_levels;
|
||||
last_level_compaction_stats.bytes_read_output_level = 0;
|
||||
last_level_compaction_stats.num_input_files_in_non_output_levels = 2;
|
||||
last_level_compaction_stats.num_input_files_in_output_level = 0;
|
||||
last_level_compaction_stats.num_input_records = total_input_key_count;
|
||||
last_level_compaction_stats.num_dropped_records =
|
||||
num_input_range_del; // delete range tombstone
|
||||
last_level_compaction_stats.num_output_records =
|
||||
moved_to_last_level_key_count;
|
||||
last_level_compaction_stats.num_output_files = 1;
|
||||
expect_stats[kLastLevel].Add(last_level_compaction_stats);
|
||||
}
|
||||
{
|
||||
InternalStats::CompactionStats penultimate_level_compaction_stats(
|
||||
CompactionReason::kManualCompaction, 1);
|
||||
penultimate_level_compaction_stats.cpu_micros = 1;
|
||||
penultimate_level_compaction_stats.micros = 1;
|
||||
penultimate_level_compaction_stats.bytes_written =
|
||||
bytes_written_penultimate_level;
|
||||
penultimate_level_compaction_stats.num_output_files = 1;
|
||||
penultimate_level_compaction_stats.num_output_records =
|
||||
total_input_key_count - moved_to_last_level_key_count -
|
||||
num_input_range_del;
|
||||
expect_pl_stats.Add(penultimate_level_compaction_stats);
|
||||
}
|
||||
VerifyCompactionStats(expect_stats, expect_pl_stats, kLastLevel,
|
||||
num_input_range_del);
|
||||
|
||||
// verify data
|
||||
std::string value;
|
||||
@@ -341,11 +495,11 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageUniversal) {
|
||||
// This test was essentially for a hacked-up version on future functionality.
|
||||
// It can be resurrected if/when a form of range-based tiering is properly
|
||||
// implemented.
|
||||
// TODO - Add stats verification when adding this test back
|
||||
TEST_F(TieredCompactionTest, DISABLED_RangeBasedTieredStorageUniversal) {
|
||||
const int kNumTrigger = 4;
|
||||
const int kNumLevels = 7;
|
||||
const int kNumKeys = 100;
|
||||
const int kLastLevel = kNumLevels - 1;
|
||||
|
||||
auto options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
@@ -364,14 +518,13 @@ TEST_F(TieredCompactionTest, DISABLED_RangeBasedTieredStorageUniversal) {
|
||||
"CompactionIterator::PrepareOutput.context", [&](void* arg) {
|
||||
auto context = static_cast<PerKeyPlacementContext*>(arg);
|
||||
MutexLock l(&mutex);
|
||||
context->output_to_penultimate_level =
|
||||
context->output_to_proximal_level =
|
||||
cmp->Compare(context->key, hot_start) >= 0 &&
|
||||
cmp->Compare(context->key, hot_end) < 0;
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::vector<InternalStats::CompactionStats> expect_stats(kNumLevels);
|
||||
InternalStats::CompactionStats& last_stats = expect_stats[kLastLevel];
|
||||
InternalStats::CompactionStats expect_pl_stats;
|
||||
|
||||
for (int i = 0; i < kNumTrigger; i++) {
|
||||
@@ -379,21 +532,15 @@ TEST_F(TieredCompactionTest, DISABLED_RangeBasedTieredStorageUniversal) {
|
||||
ASSERT_OK(Put(Key(j), "value" + std::to_string(j)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
expect_stats[0].Add(kBasicFlushStats);
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ("0,0,0,0,0,1,1", FilesPerLevel());
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
|
||||
|
||||
last_stats.Add(kBasicCompStats);
|
||||
last_stats.Add(kBasicPerLevelStats);
|
||||
expect_pl_stats.Add(kBasicPerKeyPlacementCompStats);
|
||||
VerifyCompactionStats(expect_stats, expect_pl_stats);
|
||||
|
||||
ResetAllStats(expect_stats, expect_pl_stats);
|
||||
|
||||
// change to all cold, no output_to_penultimate_level output
|
||||
// change to all cold, no output_to_proximal_level output
|
||||
{
|
||||
MutexLock l(&mutex);
|
||||
hot_start = Key(100);
|
||||
@@ -404,14 +551,6 @@ TEST_F(TieredCompactionTest, DISABLED_RangeBasedTieredStorageUniversal) {
|
||||
ASSERT_EQ(GetSstSizeHelper(Temperature::kUnknown), 0);
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
|
||||
|
||||
last_stats.Add(kBasicCompStats);
|
||||
last_stats.ResetCompactionReason(CompactionReason::kManualCompaction);
|
||||
last_stats.Add(kBasicPerLevelStats);
|
||||
last_stats.num_dropped_records = 0;
|
||||
last_stats.bytes_read_output_level = kHasValue;
|
||||
last_stats.num_input_files_in_output_level = kHasValue;
|
||||
VerifyCompactionStats(expect_stats, expect_pl_stats);
|
||||
|
||||
// change to all hot, universal compaction support moving data to up level if
|
||||
// it's within compaction level range.
|
||||
{
|
||||
@@ -421,7 +560,7 @@ TEST_F(TieredCompactionTest, DISABLED_RangeBasedTieredStorageUniversal) {
|
||||
}
|
||||
|
||||
// No data is moved from cold tier to hot tier because no input files from L5
|
||||
// or higher, it's not safe to move data to output_to_penultimate_level level.
|
||||
// or higher, it's not safe to move data to output_to_proximal_level level.
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_EQ("0,0,0,0,0,1", FilesPerLevel());
|
||||
|
||||
@@ -567,7 +706,7 @@ TEST_F(TieredCompactionTest, LevelColdRangeDelete) {
|
||||
|
||||
// 20->30 will be marked as cold data, but it cannot be placed to cold tier
|
||||
// (bottommost) otherwise, it will be "deleted" by the range del in
|
||||
// output_to_penultimate_level level verify that these data will be able to
|
||||
// output_to_proximal_level level verify that these data will be able to
|
||||
// queried
|
||||
for (int i = 20; i < 30; i++) {
|
||||
ASSERT_OK(Put(Key(i), "value" + std::to_string(i)));
|
||||
@@ -677,17 +816,17 @@ TEST_F(TieredCompactionTest, LevelOutofBoundaryRangeDelete) {
|
||||
std::vector<std::vector<FileMetaData>> level_to_files;
|
||||
dbfull()->TEST_GetFilesMetaData(dbfull()->DefaultColumnFamily(),
|
||||
&level_to_files);
|
||||
// range tombstone is in the penultimate level
|
||||
const int penultimate_level = kNumLevels - 2;
|
||||
ASSERT_EQ(level_to_files[penultimate_level].size(), 1);
|
||||
ASSERT_EQ(level_to_files[penultimate_level][0].num_entries, 1);
|
||||
ASSERT_EQ(level_to_files[penultimate_level][0].num_deletions, 1);
|
||||
ASSERT_EQ(level_to_files[penultimate_level][0].temperature,
|
||||
// range tombstone is in the proximal level
|
||||
const int proximal_level = kNumLevels - 2;
|
||||
ASSERT_EQ(level_to_files[proximal_level].size(), 1);
|
||||
ASSERT_EQ(level_to_files[proximal_level][0].num_entries, 1);
|
||||
ASSERT_EQ(level_to_files[proximal_level][0].num_deletions, 1);
|
||||
ASSERT_EQ(level_to_files[proximal_level][0].temperature,
|
||||
Temperature::kUnknown);
|
||||
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
|
||||
ASSERT_EQ("0,1,10",
|
||||
FilesPerLevel()); // one file is at the penultimate level which
|
||||
FilesPerLevel()); // one file is at the proximal level which
|
||||
// only contains a range delete
|
||||
|
||||
// Add 2 hot keys, each is a new SST, they will be placed in the same level as
|
||||
@@ -701,7 +840,7 @@ TEST_F(TieredCompactionTest, LevelOutofBoundaryRangeDelete) {
|
||||
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
ASSERT_EQ("0,2,10",
|
||||
FilesPerLevel()); // one file is at the penultimate level
|
||||
FilesPerLevel()); // one file is at the proximal level
|
||||
// which only contains a range delete
|
||||
std::vector<LiveFileMetaData> live_file_meta;
|
||||
db_->GetLiveFilesMetaData(&live_file_meta);
|
||||
@@ -711,7 +850,7 @@ TEST_F(TieredCompactionTest, LevelOutofBoundaryRangeDelete) {
|
||||
if (meta.num_deletions > 0) {
|
||||
// found SST with del, which has 2 entries, one for data one for range del
|
||||
ASSERT_EQ(meta.level,
|
||||
kNumLevels - 2); // output to penultimate level
|
||||
kNumLevels - 2); // output to proximal level
|
||||
ASSERT_EQ(meta.num_entries, 2);
|
||||
ASSERT_EQ(meta.num_deletions, 1);
|
||||
found_sst_with_del = true;
|
||||
@@ -722,7 +861,7 @@ TEST_F(TieredCompactionTest, LevelOutofBoundaryRangeDelete) {
|
||||
|
||||
// release the first snapshot and compact, which should compact the range del
|
||||
// but new inserted key `0` and `6` are still hot data which will be placed on
|
||||
// the penultimate level
|
||||
// the proximal level
|
||||
db_->ReleaseSnapshot(snap);
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
ASSERT_EQ("0,2,7", FilesPerLevel());
|
||||
@@ -738,7 +877,7 @@ TEST_F(TieredCompactionTest, LevelOutofBoundaryRangeDelete) {
|
||||
ASSERT_FALSE(found_sst_with_del);
|
||||
|
||||
// Now make all data cold, key 0 will be moved to the last level, but key 6 is
|
||||
// still in snap2, so it will be kept at the penultimate level
|
||||
// still in snap2, so it will be kept at the proximal level
|
||||
latest_cold_seq = dbfull()->GetLatestSequenceNumber();
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
ASSERT_EQ("0,1,8", FilesPerLevel());
|
||||
@@ -783,7 +922,7 @@ TEST_F(TieredCompactionTest, UniversalRangeDelete) {
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// compact to the penultimate level with 10 files
|
||||
// compact to the proximal level with 10 files
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
@@ -810,7 +949,7 @@ TEST_F(TieredCompactionTest, UniversalRangeDelete) {
|
||||
|
||||
ASSERT_EQ("0,0,0,0,0,0,8", FilesPerLevel());
|
||||
|
||||
// range del with snapshot should be preserved in the penultimate level
|
||||
// range del with snapshot should be preserved in the proximal level
|
||||
auto snap = db_->GetSnapshot();
|
||||
|
||||
start = Key(6);
|
||||
@@ -841,7 +980,7 @@ TEST_F(TieredCompactionTest, UniversalRangeDelete) {
|
||||
if (meta.num_deletions > 0) {
|
||||
// found SST with del, which has 2 entries, one for data one for range del
|
||||
ASSERT_EQ(meta.level,
|
||||
kNumLevels - 2); // output_to_penultimate_level level
|
||||
kNumLevels - 2); // output_to_proximal_level level
|
||||
ASSERT_EQ(meta.num_entries, 2);
|
||||
ASSERT_EQ(meta.num_deletions, 1);
|
||||
found_sst_with_del = true;
|
||||
@@ -890,6 +1029,8 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageLevel) {
|
||||
const int kNumKeys = 100;
|
||||
const int kLastLevel = kNumLevels - 1;
|
||||
|
||||
int output_level = 0;
|
||||
|
||||
auto options = CurrentOptions();
|
||||
SetColdTemperature(options);
|
||||
options.level0_file_num_compaction_trigger = kNumTrigger;
|
||||
@@ -906,18 +1047,40 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageLevel) {
|
||||
[&](void* arg) {
|
||||
*static_cast<SequenceNumber*>(arg) = latest_cold_seq.load();
|
||||
});
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionJob::Install:AfterUpdateCompactionJobStats", [&](void* arg) {
|
||||
job_stats.Reset();
|
||||
job_stats.Add(*(static_cast<CompactionJobStats*>(arg)));
|
||||
});
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionJob::ProcessKeyValueCompaction()::Processing", [&](void* arg) {
|
||||
auto compaction = static_cast<Compaction*>(arg);
|
||||
output_level = compaction->output_level();
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::vector<InternalStats::CompactionStats> expect_stats(kNumLevels);
|
||||
InternalStats::CompactionStats& last_stats = expect_stats[kLastLevel];
|
||||
InternalStats::CompactionStats expect_pl_stats;
|
||||
|
||||
// Put keys in the following way to create overlaps
|
||||
// First file from 0 ~ 99
|
||||
// Second file from 10 ~ 109
|
||||
// ...
|
||||
size_t bytes_per_file = 1952;
|
||||
uint64_t total_input_key_count = kNumTrigger * kNumKeys;
|
||||
uint64_t total_output_key_count = 130; // 0 ~ 129
|
||||
|
||||
for (int i = 0; i < kNumTrigger; i++) {
|
||||
for (int j = 0; j < kNumKeys; j++) {
|
||||
ASSERT_OK(Put(Key(i * 10 + j), "value" + std::to_string(i)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
expect_stats[0].Add(kBasicFlushStats);
|
||||
InternalStats::CompactionStats flush_stats(CompactionReason::kFlush, 1);
|
||||
flush_stats.cpu_micros = 1;
|
||||
flush_stats.micros = 1;
|
||||
flush_stats.bytes_written = bytes_per_file;
|
||||
flush_stats.num_output_files = 1;
|
||||
expect_stats[0].Add(flush_stats);
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
@@ -926,10 +1089,30 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageLevel) {
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
|
||||
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
|
||||
|
||||
expect_stats[1].Add(kBasicCompStats);
|
||||
expect_stats[1].Add(kBasicPerLevelStats);
|
||||
expect_stats[1].ResetCompactionReason(CompactionReason::kLevelL0FilesNum);
|
||||
VerifyCompactionStats(expect_stats, expect_pl_stats);
|
||||
uint64_t bytes_written_output_level =
|
||||
GetCompactionStats()[output_level].bytes_written;
|
||||
ASSERT_GT(bytes_written_output_level, 0);
|
||||
|
||||
{
|
||||
InternalStats::CompactionStats output_level_compaction_stats(
|
||||
CompactionReason::kLevelL0FilesNum, 1);
|
||||
output_level_compaction_stats.cpu_micros = 1;
|
||||
output_level_compaction_stats.micros = 1;
|
||||
output_level_compaction_stats.bytes_written = bytes_written_output_level;
|
||||
output_level_compaction_stats.bytes_read_non_output_levels =
|
||||
bytes_per_file * kNumTrigger;
|
||||
output_level_compaction_stats.bytes_read_output_level = 0;
|
||||
output_level_compaction_stats.num_input_files_in_non_output_levels =
|
||||
kNumTrigger;
|
||||
output_level_compaction_stats.num_input_files_in_output_level = 0;
|
||||
output_level_compaction_stats.num_input_records = total_input_key_count;
|
||||
output_level_compaction_stats.num_dropped_records =
|
||||
total_input_key_count - total_output_key_count;
|
||||
output_level_compaction_stats.num_output_records = total_output_key_count;
|
||||
output_level_compaction_stats.num_output_files = 1;
|
||||
expect_stats[output_level].Add(output_level_compaction_stats);
|
||||
}
|
||||
VerifyCompactionStats(expect_stats, expect_pl_stats, output_level);
|
||||
|
||||
// move all data to the last level
|
||||
MoveFilesToLevel(kLastLevel);
|
||||
@@ -944,15 +1127,26 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageLevel) {
|
||||
ASSERT_EQ(GetSstSizeHelper(Temperature::kUnknown), 0);
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
|
||||
|
||||
last_stats.Add(kBasicCompStats);
|
||||
last_stats.Add(kBasicPerLevelStats);
|
||||
last_stats.num_dropped_records = 0;
|
||||
last_stats.bytes_read_non_output_levels = 0;
|
||||
last_stats.num_input_files_in_non_output_levels = 0;
|
||||
last_stats.bytes_read_output_level = kHasValue;
|
||||
last_stats.num_input_files_in_output_level = kHasValue;
|
||||
last_stats.ResetCompactionReason(CompactionReason::kManualCompaction);
|
||||
VerifyCompactionStats(expect_stats, expect_pl_stats);
|
||||
total_input_key_count = total_output_key_count;
|
||||
{
|
||||
InternalStats::CompactionStats output_level_compaction_stats(
|
||||
CompactionReason::kManualCompaction, 1);
|
||||
output_level_compaction_stats.cpu_micros = 1;
|
||||
output_level_compaction_stats.micros = 1;
|
||||
output_level_compaction_stats.bytes_written = bytes_written_output_level;
|
||||
output_level_compaction_stats.bytes_read_non_output_levels = 0;
|
||||
output_level_compaction_stats.bytes_read_output_level =
|
||||
bytes_written_output_level;
|
||||
output_level_compaction_stats.num_input_files_in_non_output_levels = 0;
|
||||
output_level_compaction_stats.num_input_files_in_output_level = 1;
|
||||
output_level_compaction_stats.num_input_records = total_input_key_count;
|
||||
output_level_compaction_stats.num_dropped_records =
|
||||
total_input_key_count - total_output_key_count;
|
||||
output_level_compaction_stats.num_output_records = total_output_key_count;
|
||||
output_level_compaction_stats.num_output_files = 1;
|
||||
expect_stats[output_level].Add(output_level_compaction_stats);
|
||||
}
|
||||
VerifyCompactionStats(expect_stats, expect_pl_stats, output_level);
|
||||
|
||||
// Add new data, which is all hot and overriding all existing data
|
||||
latest_cold_seq = dbfull()->GetLatestSequenceNumber();
|
||||
@@ -976,17 +1170,47 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageLevel) {
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
|
||||
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
|
||||
|
||||
uint64_t bytes_written_in_proximal_level =
|
||||
GetPerKeyPlacementCompactionStats().bytes_written;
|
||||
for (int level = 2; level < kNumLevels - 1; level++) {
|
||||
expect_stats[level].bytes_moved = kHasValue;
|
||||
expect_stats[level].bytes_moved = bytes_written_in_proximal_level;
|
||||
}
|
||||
|
||||
last_stats.Add(kBasicCompStats);
|
||||
last_stats.bytes_read_output_level = kHasValue;
|
||||
last_stats.num_input_files_in_output_level = kHasValue;
|
||||
last_stats.ResetCompactionReason(CompactionReason::kManualCompaction);
|
||||
expect_pl_stats.Add(kBasicPerKeyPlacementCompStats);
|
||||
expect_pl_stats.ResetCompactionReason(CompactionReason::kManualCompaction);
|
||||
VerifyCompactionStats(expect_stats, expect_pl_stats);
|
||||
// Another set of 130 keys + from the previous
|
||||
total_input_key_count = total_output_key_count + 130;
|
||||
// Merged into 130
|
||||
total_output_key_count = 130;
|
||||
|
||||
{
|
||||
InternalStats::CompactionStats output_level_compaction_stats(
|
||||
CompactionReason::kManualCompaction, 1);
|
||||
output_level_compaction_stats.cpu_micros = 1;
|
||||
output_level_compaction_stats.micros = 1;
|
||||
output_level_compaction_stats.bytes_written = 0;
|
||||
output_level_compaction_stats.bytes_read_non_output_levels =
|
||||
bytes_written_in_proximal_level;
|
||||
output_level_compaction_stats.bytes_read_output_level =
|
||||
bytes_written_output_level;
|
||||
output_level_compaction_stats.num_input_files_in_non_output_levels = 1;
|
||||
output_level_compaction_stats.num_input_files_in_output_level = 1;
|
||||
output_level_compaction_stats.num_input_records = total_input_key_count;
|
||||
output_level_compaction_stats.num_dropped_records =
|
||||
total_input_key_count - total_output_key_count;
|
||||
output_level_compaction_stats.num_output_records = 0;
|
||||
output_level_compaction_stats.num_output_files = 0;
|
||||
expect_stats[output_level].Add(output_level_compaction_stats);
|
||||
}
|
||||
{
|
||||
InternalStats::CompactionStats proximal_level_compaction_stats(
|
||||
CompactionReason::kManualCompaction, 1);
|
||||
expect_pl_stats.cpu_micros = 1;
|
||||
expect_pl_stats.micros = 1;
|
||||
expect_pl_stats.bytes_written = bytes_written_in_proximal_level;
|
||||
expect_pl_stats.num_output_files = 1;
|
||||
expect_pl_stats.num_output_records = total_output_key_count;
|
||||
expect_pl_stats.Add(proximal_level_compaction_stats);
|
||||
}
|
||||
VerifyCompactionStats(expect_stats, expect_pl_stats, output_level);
|
||||
|
||||
// move forward the cold_seq, try to split the data into cold and hot, but in
|
||||
// this case it's unsafe to split the data
|
||||
@@ -1138,7 +1362,7 @@ TEST_F(TieredCompactionTest, DISABLED_RangeBasedTieredStorageLevel) {
|
||||
"CompactionIterator::PrepareOutput.context", [&](void* arg) {
|
||||
auto context = static_cast<PerKeyPlacementContext*>(arg);
|
||||
MutexLock l(&mutex);
|
||||
context->output_to_penultimate_level =
|
||||
context->output_to_proximal_level =
|
||||
cmp->Compare(context->key, hot_start) >= 0 &&
|
||||
cmp->Compare(context->key, hot_end) < 0;
|
||||
});
|
||||
@@ -1221,10 +1445,10 @@ TEST_F(TieredCompactionTest, DISABLED_RangeBasedTieredStorageLevel) {
|
||||
options.statistics->getTickerCount(COMPACTION_RANGE_DEL_DROP_OBSOLETE),
|
||||
1);
|
||||
|
||||
// Tests that we only compact keys up to penultimate level
|
||||
// that are within penultimate level input's internal key range.
|
||||
// UPDATE: this functionality has changed. With penultimate-enabled
|
||||
// compaction, the expanded potential output range in the penultimate
|
||||
// Tests that we only compact keys up to proximal level
|
||||
// that are within proximal level input's internal key range.
|
||||
// UPDATE: this functionality has changed. With proximal-enabled
|
||||
// compaction, the expanded potential output range in the proximal
|
||||
// level is reserved so should be safe to use.
|
||||
{
|
||||
MutexLock l(&mutex);
|
||||
@@ -1376,7 +1600,7 @@ TEST_P(PrecludeLastLevelTest, MigrationFromPreserveTimeManualCompaction) {
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
|
||||
// all data is moved up to the penultimate level
|
||||
// all data is moved up to the proximal level
|
||||
ASSERT_EQ("0,0,0,0,0,1", FilesPerLevel());
|
||||
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
|
||||
@@ -1448,7 +1672,7 @@ TEST_P(PrecludeLastLevelTest, MigrationFromPreserveTimeAutoCompaction) {
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
}
|
||||
|
||||
// all data is moved up to the penultimate level
|
||||
// all data is moved up to the proximal level
|
||||
ASSERT_EQ("0,0,0,0,0,1", FilesPerLevel());
|
||||
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
|
||||
@@ -1489,8 +1713,7 @@ TEST_P(PrecludeLastLevelTest, MigrationFromPreserveTimePartial) {
|
||||
ASSERT_EQ("0,0,0,0,0,0,1", FilesPerLevel());
|
||||
|
||||
std::vector<KeyVersion> key_versions;
|
||||
ASSERT_OK(GetAllKeyVersions(db_, Slice(), Slice(),
|
||||
std::numeric_limits<size_t>::max(),
|
||||
ASSERT_OK(GetAllKeyVersions(db_, {}, {}, std::numeric_limits<size_t>::max(),
|
||||
&key_versions));
|
||||
|
||||
// make sure there're more than 300 keys and first 100 keys are having seqno
|
||||
@@ -1579,9 +1802,9 @@ TEST_P(PrecludeLastLevelTest, SmallPrecludeTime) {
|
||||
}
|
||||
|
||||
TEST_P(PrecludeLastLevelTest, CheckInternalKeyRange) {
|
||||
// When compacting keys from the last level to penultimate level,
|
||||
// output to penultimate level should be within internal key range
|
||||
// of input files from penultimate level.
|
||||
// When compacting keys from the last level to proximal level,
|
||||
// output to proximal level should be within internal key range
|
||||
// of input files from proximal level.
|
||||
// Set up:
|
||||
// L5:
|
||||
// File 1: DeleteRange[1, 3)@4, File 2: [3@5, 100@6]
|
||||
@@ -1719,8 +1942,8 @@ TEST_P(PrecludeWithCompactStyleTest, RangeTombstoneSnapshotMigrateFromLast) {
|
||||
|
||||
ApplyConfigChange(&options, {{"preclude_last_level_data_seconds", "10000"}});
|
||||
|
||||
// To exercise the WithinPenultimateLevelOutputRange feature, we want files
|
||||
// around the middle file to be compacted on the penultimate level
|
||||
// To exercise the WithinProximalLevelOutputRange feature, we want files
|
||||
// around the middle file to be compacted on the proximal level
|
||||
ASSERT_OK(Put(Key(0), "val0"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put(Key(3), "val3"));
|
||||
@@ -1777,9 +2000,9 @@ TEST_P(PrecludeWithCompactStyleTest, RangeTombstoneSnapshotMigrateFromLast) {
|
||||
EXPECT_EQ("0,0,0,0,0,3,1", FilesPerLevel());
|
||||
VerifyLogicalState(__LINE__);
|
||||
|
||||
// Compact everything, but some data still goes to both penultimate and last
|
||||
// Compact everything, but some data still goes to both proximal and last
|
||||
// levels. A full-range compaction should be safe to "migrate" data from the
|
||||
// last level to penultimate (because of preclude setting change).
|
||||
// last level to proximal (because of preclude setting change).
|
||||
ASSERT_OK(CompactRange({}, {}, {}));
|
||||
EXPECT_EQ("0,0,0,0,0,1,1", FilesPerLevel());
|
||||
VerifyLogicalState(__LINE__);
|
||||
@@ -1898,7 +2121,7 @@ TEST_P(TimedPutPrecludeLastLevelTest, InterleavedTimedPutAndPut) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_P(TimedPutPrecludeLastLevelTest, PreserveTimedPutOnPenultimateLevel) {
|
||||
TEST_P(TimedPutPrecludeLastLevelTest, PreserveTimedPutOnProximalLevel) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.disable_auto_compactions = true;
|
||||
@@ -1924,14 +2147,14 @@ TEST_P(TimedPutPrecludeLastLevelTest, PreserveTimedPutOnPenultimateLevel) {
|
||||
ASSERT_OK(TimedPut(0, Key(2), "v2", kMockStartTime - 1 * 24 * 60 * 60, wo));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Should still be in penultimate level.
|
||||
// Should still be in proximal level.
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_EQ("0,0,0,0,0,1", FilesPerLevel());
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kHot), 0);
|
||||
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
|
||||
|
||||
// Wait one more day and release snapshot. Data's preferred seqno should be
|
||||
// swapped in, but data should still stay in penultimate level. SST file's
|
||||
// swapped in, but data should still stay in proximal level. SST file's
|
||||
// seqno to time mapping should continue to cover preferred seqno after
|
||||
// compaction.
|
||||
db_->ReleaseSnapshot(snap1);
|
||||
@@ -2079,8 +2302,7 @@ TEST_P(PrecludeLastLevelTest, LastLevelOnlyCompactionPartial) {
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
|
||||
|
||||
std::vector<KeyVersion> key_versions;
|
||||
ASSERT_OK(GetAllKeyVersions(db_, Slice(), Slice(),
|
||||
std::numeric_limits<size_t>::max(),
|
||||
ASSERT_OK(GetAllKeyVersions(db_, {}, {}, std::numeric_limits<size_t>::max(),
|
||||
&key_versions));
|
||||
|
||||
// make sure there're more than 300 keys and first 100 keys are having seqno
|
||||
@@ -2253,13 +2475,13 @@ TEST_P(PrecludeLastLevelOptionalTest, LastLevelOnlyCompactionNoPreclude) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_P(PrecludeLastLevelOptionalTest, PeriodicCompactionToPenultimateLevel) {
|
||||
TEST_P(PrecludeLastLevelOptionalTest, PeriodicCompactionToProximalLevel) {
|
||||
// Test the last level only periodic compaction should also be blocked by an
|
||||
// ongoing compaction in penultimate level if tiered compaction is enabled
|
||||
// ongoing compaction in proximal level if tiered compaction is enabled
|
||||
// otherwise, the periodic compaction should just run for the last level.
|
||||
const int kNumTrigger = 4;
|
||||
const int kNumLevels = 7;
|
||||
const int kPenultimateLevel = kNumLevels - 2;
|
||||
const int kProximalLevel = kNumLevels - 2;
|
||||
const int kKeyPerSec = 1;
|
||||
const int kNumKeys = 100;
|
||||
|
||||
@@ -2301,13 +2523,13 @@ TEST_P(PrecludeLastLevelOptionalTest, PeriodicCompactionToPenultimateLevel) {
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionJob::ProcessKeyValueCompaction()::Processing", [&](void* arg) {
|
||||
auto compaction = static_cast<Compaction*>(arg);
|
||||
if (compaction->output_level() == kPenultimateLevel) {
|
||||
if (compaction->output_level() == kProximalLevel) {
|
||||
is_size_ratio_compaction_running = true;
|
||||
TEST_SYNC_POINT(
|
||||
"PrecludeLastLevelTest::PeriodicCompactionToPenultimateLevel:"
|
||||
"PrecludeLastLevelTest::PeriodicCompactionToProximalLevel:"
|
||||
"SizeRatioCompaction1");
|
||||
TEST_SYNC_POINT(
|
||||
"PrecludeLastLevelTest::PeriodicCompactionToPenultimateLevel:"
|
||||
"PrecludeLastLevelTest::PeriodicCompactionToProximalLevel:"
|
||||
"SizeRatioCompaction2");
|
||||
is_size_ratio_compaction_running = false;
|
||||
}
|
||||
@@ -2329,17 +2551,17 @@ TEST_P(PrecludeLastLevelOptionalTest, PeriodicCompactionToPenultimateLevel) {
|
||||
verified_last_level_compaction = true;
|
||||
}
|
||||
TEST_SYNC_POINT(
|
||||
"PrecludeLastLevelTest::PeriodicCompactionToPenultimateLevel:"
|
||||
"PrecludeLastLevelTest::PeriodicCompactionToProximalLevel:"
|
||||
"AutoCompactionPicked");
|
||||
});
|
||||
|
||||
SyncPoint::GetInstance()->LoadDependency({
|
||||
{"PrecludeLastLevelTest::PeriodicCompactionToPenultimateLevel:"
|
||||
{"PrecludeLastLevelTest::PeriodicCompactionToProximalLevel:"
|
||||
"SizeRatioCompaction1",
|
||||
"PrecludeLastLevelTest::PeriodicCompactionToPenultimateLevel:DoneWrite"},
|
||||
{"PrecludeLastLevelTest::PeriodicCompactionToPenultimateLevel:"
|
||||
"PrecludeLastLevelTest::PeriodicCompactionToProximalLevel:DoneWrite"},
|
||||
{"PrecludeLastLevelTest::PeriodicCompactionToProximalLevel:"
|
||||
"AutoCompactionPicked",
|
||||
"PrecludeLastLevelTest::PeriodicCompactionToPenultimateLevel:"
|
||||
"PrecludeLastLevelTest::PeriodicCompactionToProximalLevel:"
|
||||
"SizeRatioCompaction2"},
|
||||
});
|
||||
|
||||
@@ -2356,11 +2578,11 @@ TEST_P(PrecludeLastLevelOptionalTest, PeriodicCompactionToPenultimateLevel) {
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT(
|
||||
"PrecludeLastLevelTest::PeriodicCompactionToPenultimateLevel:DoneWrite");
|
||||
"PrecludeLastLevelTest::PeriodicCompactionToProximalLevel:DoneWrite");
|
||||
|
||||
// wait for periodic compaction time and flush to trigger the periodic
|
||||
// compaction, which should be blocked by ongoing compaction in the
|
||||
// penultimate level
|
||||
// proximal level
|
||||
mock_clock_->MockSleepForSeconds(10000);
|
||||
for (int i = 0; i < 3 * kNumKeys; i++) {
|
||||
ASSERT_OK(Put(Key(i), rnd.RandomString(10)));
|
||||
@@ -2423,7 +2645,7 @@ class ThreeRangesPartitionerFactory : public SstPartitionerFactory {
|
||||
}
|
||||
};
|
||||
|
||||
TEST_P(PrecludeLastLevelTest, PartialPenultimateLevelCompaction) {
|
||||
TEST_P(PrecludeLastLevelTest, PartialProximalLevelCompaction) {
|
||||
const int kNumTrigger = 4;
|
||||
const int kNumLevels = 7;
|
||||
const int kKeyPerSec = 10;
|
||||
@@ -2593,8 +2815,8 @@ TEST_P(PrecludeLastLevelTest, RangeDelsCauseFileEndpointsToOverlap) {
|
||||
"UniversalCompactionBuilder::PickCompaction:Return", [&](void* arg) {
|
||||
auto compaction = static_cast<Compaction*>(arg);
|
||||
if (compaction->SupportsPerKeyPlacement()) {
|
||||
ASSERT_EQ(compaction->GetPenultimateOutputRangeType(),
|
||||
Compaction::PenultimateOutputRangeType::kNonLastRange);
|
||||
ASSERT_EQ(compaction->GetProximalOutputRangeType(),
|
||||
Compaction::ProximalOutputRangeType::kNonLastRange);
|
||||
per_key_comp_num++;
|
||||
}
|
||||
});
|
||||
@@ -2650,7 +2872,7 @@ TEST_P(PrecludeLastLevelTest, RangeDelsCauseFileEndpointsToOverlap) {
|
||||
ASSERT_EQ(3, per_key_comp_num);
|
||||
verify_db();
|
||||
|
||||
// Finish off the penultimate level.
|
||||
// Finish off the proximal level.
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
ASSERT_EQ("0,0,0,0,0,0,3", FilesPerLevel());
|
||||
verify_db();
|
||||
|
||||
@@ -26,6 +26,17 @@ Status DeleteFilesInRange(DB* db, ColumnFamilyHandle* column_family,
|
||||
|
||||
Status DeleteFilesInRanges(DB* db, ColumnFamilyHandle* column_family,
|
||||
const RangePtr* ranges, size_t n, bool include_end) {
|
||||
std::vector<RangeOpt> range_opts(n);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
range_opts[i] = {OptSlice::CopyFromPtr(ranges[i].start),
|
||||
OptSlice::CopyFromPtr(ranges[i].limit)};
|
||||
}
|
||||
return DeleteFilesInRanges(db, column_family, range_opts.data(), n,
|
||||
include_end);
|
||||
}
|
||||
|
||||
Status DeleteFilesInRanges(DB* db, ColumnFamilyHandle* column_family,
|
||||
const RangeOpt* ranges, size_t n, bool include_end) {
|
||||
return (static_cast_with_check<DBImpl>(db->GetRootDB()))
|
||||
->DeleteFilesInRanges(column_family, ranges, n, include_end);
|
||||
}
|
||||
|
||||
@@ -851,6 +851,9 @@ TEST_F(CorruptionTest, ParanoidFileChecksOnCompact) {
|
||||
options.env = env_.get();
|
||||
options.paranoid_file_checks = true;
|
||||
options.create_if_missing = true;
|
||||
// Skip verifying record count against TableProperties for
|
||||
// MockTables
|
||||
options.compaction_verify_record_count = false;
|
||||
Status s;
|
||||
for (const auto& mode : corruption_modes) {
|
||||
delete db_;
|
||||
@@ -863,7 +866,6 @@ TEST_F(CorruptionTest, ParanoidFileChecksOnCompact) {
|
||||
ASSERT_OK(DB::Open(options, dbname_, &db_));
|
||||
assert(db_ != nullptr); // suppress false clang-analyze report
|
||||
Build(100, 2);
|
||||
// ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
|
||||
ASSERT_OK(dbi->TEST_FlushMemTable());
|
||||
mock->SetCorruptionMode(mode);
|
||||
|
||||
+98
-14
@@ -161,6 +161,7 @@ TEST_F(DBBasicTest, UniqueSession) {
|
||||
|
||||
ASSERT_EQ(sid2, sid3);
|
||||
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"goku"}, options);
|
||||
ASSERT_OK(db_->GetDbSessionId(sid1));
|
||||
ASSERT_OK(Put("bar", "e1"));
|
||||
@@ -179,6 +180,7 @@ TEST_F(DBBasicTest, UniqueSession) {
|
||||
TEST_F(DBBasicTest, ReadOnlyDB) {
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
ASSERT_OK(Put("bar", "v2"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("foo", "v3"));
|
||||
Close();
|
||||
|
||||
@@ -208,10 +210,11 @@ TEST_F(DBBasicTest, ReadOnlyDB) {
|
||||
|
||||
auto options = CurrentOptions();
|
||||
assert(options.env == env_);
|
||||
ASSERT_OK(ReadOnlyReopen(options));
|
||||
ASSERT_OK(EnforcedReadOnlyReopen(options));
|
||||
ASSERT_EQ("v3", Get("foo"));
|
||||
ASSERT_EQ("v2", Get("bar"));
|
||||
verify_all_iters();
|
||||
ASSERT_EQ(Flush().code(), Status::Code::kNotSupported);
|
||||
Close();
|
||||
|
||||
// Reopen and flush memtable.
|
||||
@@ -219,26 +222,38 @@ TEST_F(DBBasicTest, ReadOnlyDB) {
|
||||
ASSERT_OK(Flush());
|
||||
Close();
|
||||
// Now check keys in read only mode.
|
||||
ASSERT_OK(ReadOnlyReopen(options));
|
||||
ASSERT_OK(EnforcedReadOnlyReopen(options));
|
||||
ASSERT_EQ("v3", Get("foo"));
|
||||
ASSERT_EQ("v2", Get("bar"));
|
||||
verify_all_iters();
|
||||
ASSERT_TRUE(db_->SyncWAL().IsNotSupported());
|
||||
ASSERT_EQ(db_->SyncWAL().code(), Status::Code::kNotSupported);
|
||||
|
||||
// More ops that should fail
|
||||
std::vector<ColumnFamilyHandle*> cfhs{{}};
|
||||
ASSERT_EQ(db_->CreateColumnFamily(options, "blah", &cfhs[0]).code(),
|
||||
Status::Code::kNotSupported);
|
||||
|
||||
ASSERT_EQ(db_->CreateColumnFamilies(options, {"blah"}, &cfhs).code(),
|
||||
Status::Code::kNotSupported);
|
||||
|
||||
std::vector<ColumnFamilyDescriptor> cfds;
|
||||
cfds.push_back({"blah", options});
|
||||
ASSERT_EQ(db_->CreateColumnFamilies(cfds, &cfhs).code(),
|
||||
Status::Code::kNotSupported);
|
||||
}
|
||||
|
||||
// TODO akanksha: Update the test to check that combination
|
||||
// does not actually write to FS (use open read-only with
|
||||
// CompositeEnvWrapper+ReadOnlyFileSystem).
|
||||
TEST_F(DBBasicTest, DISABLED_ReadOnlyDBWithWriteDBIdToManifestSet) {
|
||||
TEST_F(DBBasicTest, ReadOnlyDBWithWriteDBIdToManifestSet) {
|
||||
auto options = CurrentOptions();
|
||||
options.write_dbid_to_manifest = false;
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
ASSERT_OK(Put("bar", "v2"));
|
||||
ASSERT_OK(Put("foo", "v3"));
|
||||
Close();
|
||||
|
||||
auto options = CurrentOptions();
|
||||
options.write_dbid_to_manifest = true;
|
||||
assert(options.env == env_);
|
||||
ASSERT_OK(ReadOnlyReopen(options));
|
||||
ASSERT_OK(EnforcedReadOnlyReopen(options));
|
||||
std::string db_id1;
|
||||
ASSERT_OK(db_->GetDbIdentity(db_id1));
|
||||
ASSERT_EQ("v3", Get("foo"));
|
||||
@@ -258,7 +273,7 @@ TEST_F(DBBasicTest, DISABLED_ReadOnlyDBWithWriteDBIdToManifestSet) {
|
||||
ASSERT_OK(Flush());
|
||||
Close();
|
||||
// Now check keys in read only mode.
|
||||
ASSERT_OK(ReadOnlyReopen(options));
|
||||
ASSERT_OK(EnforcedReadOnlyReopen(options));
|
||||
ASSERT_EQ("v3", Get("foo"));
|
||||
ASSERT_EQ("v2", Get("bar"));
|
||||
ASSERT_TRUE(db_->SyncWAL().IsNotSupported());
|
||||
@@ -3273,8 +3288,7 @@ TEST_F(DBBasicTest, GetAllKeyVersions) {
|
||||
ASSERT_OK(Delete(std::to_string(i)));
|
||||
}
|
||||
std::vector<KeyVersion> key_versions;
|
||||
ASSERT_OK(GetAllKeyVersions(db_, Slice(), Slice(),
|
||||
std::numeric_limits<size_t>::max(),
|
||||
ASSERT_OK(GetAllKeyVersions(db_, {}, {}, std::numeric_limits<size_t>::max(),
|
||||
&key_versions));
|
||||
ASSERT_EQ(kNumInserts + kNumDeletes + kNumUpdates, key_versions.size());
|
||||
for (size_t i = 0; i < kNumInserts + kNumDeletes + kNumUpdates; i++) {
|
||||
@@ -3284,7 +3298,7 @@ TEST_F(DBBasicTest, GetAllKeyVersions) {
|
||||
ASSERT_EQ(key_versions[i].GetTypeName(), "TypeValue");
|
||||
}
|
||||
}
|
||||
ASSERT_OK(GetAllKeyVersions(db_, handles_[0], Slice(), Slice(),
|
||||
ASSERT_OK(GetAllKeyVersions(db_, handles_[0], {}, {},
|
||||
std::numeric_limits<size_t>::max(),
|
||||
&key_versions));
|
||||
ASSERT_EQ(kNumInserts + kNumDeletes + kNumUpdates, key_versions.size());
|
||||
@@ -3299,10 +3313,17 @@ TEST_F(DBBasicTest, GetAllKeyVersions) {
|
||||
for (size_t i = 0; i + 1 != kNumDeletes; ++i) {
|
||||
ASSERT_OK(Delete(1, std::to_string(i)));
|
||||
}
|
||||
ASSERT_OK(GetAllKeyVersions(db_, handles_[1], Slice(), Slice(),
|
||||
ASSERT_OK(GetAllKeyVersions(db_, handles_[1], {}, {},
|
||||
std::numeric_limits<size_t>::max(),
|
||||
&key_versions));
|
||||
ASSERT_EQ(kNumInserts + kNumDeletes + kNumUpdates - 3, key_versions.size());
|
||||
|
||||
// Change from historical behavior: empty key is now interpreted literally as
|
||||
// a legal key (rather than as a "not present" key)
|
||||
ASSERT_OK(GetAllKeyVersions(db_, handles_[1], Slice(), Slice(),
|
||||
std::numeric_limits<size_t>::max(),
|
||||
&key_versions));
|
||||
ASSERT_EQ(key_versions.size(), 0);
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, ValueTypeString) {
|
||||
@@ -3354,6 +3375,69 @@ TEST_F(DBBasicTest, MultiGetIOBufferOverrun) {
|
||||
keys.data(), values.data(), statuses.data(), true);
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, MultiGetWithSnapshotsAndPersistedTier) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.atomic_flush = true;
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"cf1", "cf2"}, options);
|
||||
|
||||
// Insert initial data
|
||||
ASSERT_OK(Put(0, "key1", "value1_cf0"));
|
||||
ASSERT_OK(Put(1, "key1", "value1_cf1"));
|
||||
ASSERT_OK(Put(2, "key1", "value1_cf2"));
|
||||
ASSERT_OK(Flush({0, 1, 2}));
|
||||
for (auto cf : {0, 1, 2}) {
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0, cf));
|
||||
}
|
||||
|
||||
ASSERT_OK(Put(0, "key1", "value2_cf0"));
|
||||
ASSERT_OK(Put(1, "key1", "value2_cf1"));
|
||||
ASSERT_OK(Put(2, "key1", "value2_cf2"));
|
||||
|
||||
// Prepare for concurrent atomic flush
|
||||
std::atomic<bool> flush_done(false);
|
||||
std::thread flush_thread([&]() {
|
||||
ASSERT_OK(Flush({0, 1, 2}));
|
||||
flush_done.store(true);
|
||||
});
|
||||
|
||||
// Perform MultiGet with snapshot and read_tier = kPersistentTier
|
||||
ReadOptions ro;
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
ro.snapshot = snapshot;
|
||||
ro.read_tier = kPersistedTier;
|
||||
|
||||
std::string k = "key1";
|
||||
std::vector<Slice> keys(3, Slice(k));
|
||||
std::vector<Status> statuses(keys.size());
|
||||
std::vector<ColumnFamilyHandle*> cfs(keys.size());
|
||||
std::vector<Slice> new_keys(keys.size());
|
||||
std::vector<PinnableSlice> pin_values(keys.size());
|
||||
for (size_t i = 0; i < keys.size(); ++i) {
|
||||
cfs[i] = handles_[i];
|
||||
}
|
||||
db_->MultiGet(ro, cfs.size(), cfs.data(), keys.data(), pin_values.data(),
|
||||
statuses.data());
|
||||
for (const auto& s : statuses) {
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
if (pin_values[0] == "value1_cf0") {
|
||||
// Check if the first value matches expected value
|
||||
ASSERT_EQ(pin_values[1], "value1_cf1");
|
||||
ASSERT_EQ(pin_values[2], "value1_cf2");
|
||||
} else {
|
||||
// If first value doesn't match, check if we got the updated values
|
||||
ASSERT_EQ(pin_values[0], "value2_cf0");
|
||||
ASSERT_EQ(pin_values[1], "value2_cf1");
|
||||
ASSERT_EQ(pin_values[2], "value2_cf2");
|
||||
}
|
||||
|
||||
flush_thread.join();
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, IncrementalRecoveryNoCorrupt) {
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
+164
-9
@@ -127,6 +127,19 @@ class DBCompactionTestWithParam
|
||||
exclusive_manual_compaction_ = std::get<1>(GetParam());
|
||||
}
|
||||
|
||||
class TrivialMoveEventListener : public EventListener {
|
||||
public:
|
||||
explicit TrivialMoveEventListener(size_t expected_trivially_moved_files)
|
||||
: expected_trivially_moved_files_(expected_trivially_moved_files) {}
|
||||
void OnCompactionBegin(DB* /*db*/, const CompactionJobInfo& ci) override {
|
||||
ASSERT_EQ(ci.stats.num_input_files_trivially_moved,
|
||||
expected_trivially_moved_files_);
|
||||
}
|
||||
|
||||
private:
|
||||
size_t expected_trivially_moved_files_ = 0;
|
||||
};
|
||||
|
||||
// Required if inheriting from testing::WithParamInterface<>
|
||||
static void SetUpTestCase() {}
|
||||
static void TearDownTestCase() {}
|
||||
@@ -1301,6 +1314,9 @@ TEST_P(DBCompactionTestWithParam, TrivialMoveOneFile) {
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 100000000;
|
||||
TrivialMoveEventListener* trivial_move_listener =
|
||||
new TrivialMoveEventListener(1 /*expected_trivially_moved_files*/);
|
||||
options.listeners.emplace_back(trivial_move_listener);
|
||||
options.max_subcompactions = max_subcompactions_;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
@@ -1361,6 +1377,10 @@ TEST_P(DBCompactionTestWithParam, TrivialMoveNonOverlappingFiles) {
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
// 8 is number of `ranges` that each is a non overlapping file.
|
||||
TrivialMoveEventListener* trivial_move_listener =
|
||||
new TrivialMoveEventListener(8 /*expected_trivially_moved_files*/);
|
||||
options.listeners.emplace_back(trivial_move_listener);
|
||||
options.write_buffer_size = 10 * 1024 * 1024;
|
||||
options.max_subcompactions = max_subcompactions_;
|
||||
|
||||
@@ -1408,6 +1428,11 @@ TEST_P(DBCompactionTestWithParam, TrivialMoveNonOverlappingFiles) {
|
||||
trivial_move = 0;
|
||||
non_trivial_move = 0;
|
||||
values.clear();
|
||||
options.listeners.clear();
|
||||
// Same ranges of files, but now overlapping, trivial move not applicable.
|
||||
TrivialMoveEventListener* trivial_move_listener2 =
|
||||
new TrivialMoveEventListener(0 /*expected_trivially_moved_files*/);
|
||||
options.listeners.emplace_back(trivial_move_listener2);
|
||||
DestroyAndReopen(options);
|
||||
// Same ranges as above but overlapping
|
||||
ranges = {
|
||||
@@ -1455,6 +1480,11 @@ TEST_P(DBCompactionTestWithParam, TrivialMoveTargetLevel) {
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
// Two non overlapping files in L0 trivialy moved:
|
||||
// file 1 [0 => 300], file 2 [600 => 700]
|
||||
TrivialMoveEventListener* trivial_move_listener1 =
|
||||
new TrivialMoveEventListener(2 /*expected_trivially_moved_files*/);
|
||||
options.listeners.emplace_back(trivial_move_listener1);
|
||||
options.write_buffer_size = 10 * 1024 * 1024;
|
||||
options.num_levels = 7;
|
||||
options.max_subcompactions = max_subcompactions_;
|
||||
@@ -2087,13 +2117,10 @@ TEST_P(DBDeleteFileRangeTest, DeleteFilesInRanges) {
|
||||
auto begin_str1 = Key(0), end_str1 = Key(100);
|
||||
auto begin_str2 = Key(100), end_str2 = Key(200);
|
||||
auto begin_str3 = Key(200), end_str3 = Key(299);
|
||||
Slice begin1(begin_str1), end1(end_str1);
|
||||
Slice begin2(begin_str2), end2(end_str2);
|
||||
Slice begin3(begin_str3), end3(end_str3);
|
||||
std::vector<RangePtr> ranges;
|
||||
ranges.emplace_back(&begin1, &end1);
|
||||
ranges.emplace_back(&begin2, &end2);
|
||||
ranges.emplace_back(&begin3, &end3);
|
||||
std::vector<RangeOpt> ranges;
|
||||
ranges.emplace_back(begin_str1, end_str1);
|
||||
ranges.emplace_back(begin_str2, end_str2);
|
||||
ranges.emplace_back(begin_str3, end_str3);
|
||||
ASSERT_OK(DeleteFilesInRanges(db_, db_->DefaultColumnFamily(),
|
||||
ranges.data(), ranges.size()));
|
||||
ASSERT_EQ("0,3,7", FilesPerLevel(0));
|
||||
@@ -2141,7 +2168,7 @@ TEST_P(DBDeleteFileRangeTest, DeleteFilesInRanges) {
|
||||
|
||||
// Delete all files.
|
||||
{
|
||||
RangePtr range;
|
||||
RangeOpt range;
|
||||
ASSERT_OK(DeleteFilesInRanges(db_, db_->DefaultColumnFamily(), &range, 1));
|
||||
ASSERT_EQ("", FilesPerLevel(0));
|
||||
|
||||
@@ -5912,6 +5939,9 @@ TEST_F(DBCompactionTest, SubcompactionEvent) {
|
||||
ASSERT_EQ(running_compactions_.find(ci.job_id),
|
||||
running_compactions_.end());
|
||||
running_compactions_.emplace(ci.job_id, std::unordered_set<int>());
|
||||
if (expected_num_l0_files_pre_compaction_ != -1) {
|
||||
ASSERT_EQ(expected_num_l0_files_pre_compaction_, ci.num_l0_files);
|
||||
}
|
||||
}
|
||||
|
||||
void OnCompactionCompleted(DB* /*db*/,
|
||||
@@ -5921,6 +5951,9 @@ TEST_F(DBCompactionTest, SubcompactionEvent) {
|
||||
ASSERT_NE(it, running_compactions_.end());
|
||||
ASSERT_EQ(it->second.size(), 0);
|
||||
running_compactions_.erase(it);
|
||||
if (expected_num_l0_files_post_compaction_ != -1) {
|
||||
ASSERT_EQ(expected_num_l0_files_post_compaction_, ci.num_l0_files);
|
||||
}
|
||||
}
|
||||
|
||||
void OnSubcompactionBegin(const SubcompactionJobInfo& si) override {
|
||||
@@ -5950,10 +5983,25 @@ TEST_F(DBCompactionTest, SubcompactionEvent) {
|
||||
return total_subcompaction_cnt_;
|
||||
}
|
||||
|
||||
void SetExpectedNumL0FilesPreCompaction(int num) {
|
||||
expected_num_l0_files_pre_compaction_ = num;
|
||||
}
|
||||
|
||||
void SetExpectedNumL0FilesPostCompaction(int num) {
|
||||
expected_num_l0_files_post_compaction_ = num;
|
||||
}
|
||||
|
||||
void ResetExpectedNumL0Files() {
|
||||
SetExpectedNumL0FilesPreCompaction(-1);
|
||||
SetExpectedNumL0FilesPostCompaction(-1);
|
||||
}
|
||||
|
||||
private:
|
||||
InstrumentedMutex mutex_;
|
||||
std::unordered_map<int, std::unordered_set<int>> running_compactions_;
|
||||
size_t total_subcompaction_cnt_ = 0;
|
||||
int expected_num_l0_files_pre_compaction_ = -1;
|
||||
int expected_num_l0_files_post_compaction_ = -1;
|
||||
};
|
||||
|
||||
Options options = CurrentOptions();
|
||||
@@ -5973,6 +6021,7 @@ TEST_F(DBCompactionTest, SubcompactionEvent) {
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
MoveFilesToLevel(2);
|
||||
ASSERT_EQ(FilesPerLevel(), "0,0,4");
|
||||
|
||||
// generate 2 files @ L1 which overlaps with L2 files
|
||||
for (int i = 0; i < 2; i++) {
|
||||
@@ -5982,11 +6031,18 @@ TEST_F(DBCompactionTest, SubcompactionEvent) {
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
listener->SetExpectedNumL0FilesPreCompaction(2 /* num */);
|
||||
listener->SetExpectedNumL0FilesPostCompaction(0 /* num */);
|
||||
|
||||
MoveFilesToLevel(1);
|
||||
ASSERT_EQ(FilesPerLevel(), "0,2,4");
|
||||
|
||||
listener->ResetExpectedNumL0Files();
|
||||
|
||||
CompactRangeOptions comp_opts;
|
||||
comp_opts.max_subcompactions = 4;
|
||||
|
||||
listener->SetExpectedNumL0FilesPreCompaction(0 /* num */);
|
||||
Status s = dbfull()->CompactRange(comp_opts, nullptr, nullptr);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
@@ -5994,6 +6050,8 @@ TEST_F(DBCompactionTest, SubcompactionEvent) {
|
||||
ASSERT_EQ(listener->GetRunningCompactionCount(), 0);
|
||||
// and sub compaction is triggered
|
||||
ASSERT_GT(listener->GetTotalSubcompactionCount(), 0);
|
||||
|
||||
listener->ResetExpectedNumL0Files();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, CompactFilesOutputRangeConflict) {
|
||||
@@ -10472,7 +10530,7 @@ TEST_F(DBCompactionTest, NumberOfSubcompactions) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, VerifyRecordCount) {
|
||||
TEST_F(DBCompactionTest, VerifyInputRecordCount) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleLevel;
|
||||
options.level0_file_num_compaction_trigger = 3;
|
||||
@@ -10510,6 +10568,103 @@ TEST_F(DBCompactionTest, VerifyRecordCount) {
|
||||
ASSERT_TRUE(std::strstr(s.getState(), expect));
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, VerifyOutputRecordCountBlockBasedTable) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleLevel;
|
||||
options.level0_file_num_compaction_trigger = 3;
|
||||
options.compaction_verify_record_count = true;
|
||||
DestroyAndReopen(options);
|
||||
Random rnd(301);
|
||||
|
||||
// Create 2 overlapping L0 files
|
||||
for (int i = 1; i < 20; i += 2) {
|
||||
ASSERT_OK(Put(Key(i), rnd.RandomString(100)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(db_->DeleteRange(WriteOptions(), Key(10), Key(15)));
|
||||
|
||||
for (int i = 0; i < 20; i += 2) {
|
||||
ASSERT_OK(Put(Key(i), rnd.RandomString(100)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Skip adding every 7th key in the output table
|
||||
int num_iter = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlockBasedTableBuilder::Add::skip", [&](void* skip) {
|
||||
num_iter++;
|
||||
if (num_iter % 7 == 0) {
|
||||
*(bool*)skip = true;
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Status s = db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
const char* expect =
|
||||
"Number of keys in compaction output SST files does not match number of "
|
||||
"keys added.";
|
||||
ASSERT_TRUE(std::strstr(s.getState(), expect));
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, VerifyOutputRecordCountPlainTable) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleLevel;
|
||||
options.level0_file_num_compaction_trigger = 3;
|
||||
options.compaction_verify_record_count = true;
|
||||
|
||||
PlainTableOptions plain_table_options;
|
||||
plain_table_options.user_key_len = 0;
|
||||
plain_table_options.bloom_bits_per_key = 2;
|
||||
plain_table_options.hash_table_ratio = 0.8;
|
||||
plain_table_options.index_sparseness = 3;
|
||||
plain_table_options.huge_page_tlb_size = 0;
|
||||
plain_table_options.encoding_type = kPrefix;
|
||||
plain_table_options.full_scan_mode = false;
|
||||
plain_table_options.store_index_in_file = false;
|
||||
|
||||
options.table_factory.reset(NewPlainTableFactory(plain_table_options));
|
||||
options.memtable_factory.reset(NewHashLinkListRepFactory(4, 0, 3, true));
|
||||
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(8));
|
||||
options.allow_mmap_reads = false;
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
options.unordered_write = false;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
Random rnd(301);
|
||||
|
||||
// Create 2 overlapping L0 files
|
||||
for (int i = 1; i < 20; i += 2) {
|
||||
ASSERT_OK(Put(Key(i), rnd.RandomString(100)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
for (int i = 0; i < 20; i += 2) {
|
||||
ASSERT_OK(Put(Key(i), rnd.RandomString(100)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Skip adding every 7th key in the output table
|
||||
int num_iter = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"PlainTableBuilder::Add::skip", [&](void* skip) {
|
||||
num_iter++;
|
||||
if (num_iter % 7 == 0) {
|
||||
*(bool*)skip = true;
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Status s = db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
const char* expect =
|
||||
"Number of keys in compaction output SST files does not match number of "
|
||||
"keys added.";
|
||||
ASSERT_TRUE(std::strstr(s.getState(), expect));
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, ErrorWhenReadFileHead) {
|
||||
// This is to test a bug that is fixed in
|
||||
// https://github.com/facebook/rocksdb/pull/11782.
|
||||
|
||||
+264
-217
@@ -80,6 +80,7 @@
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/compaction_filter.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/merge_operator.h"
|
||||
@@ -284,9 +285,8 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
periodic_task_functions_.emplace(PeriodicTaskType::kFlushInfoLog,
|
||||
[this]() { this->FlushInfoLog(); });
|
||||
periodic_task_functions_.emplace(
|
||||
PeriodicTaskType::kRecordSeqnoTime, [this]() {
|
||||
this->RecordSeqnoToTimeMapping(/*populate_historical_seconds=*/0);
|
||||
});
|
||||
PeriodicTaskType::kRecordSeqnoTime,
|
||||
[this]() { this->RecordSeqnoToTimeMapping(); });
|
||||
|
||||
versions_.reset(new VersionSet(
|
||||
dbname_, &immutable_db_options_, file_options_, table_cache_.get(),
|
||||
@@ -843,57 +843,40 @@ Status DBImpl::StartPeriodicTaskScheduler() {
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::RegisterRecordSeqnoTimeWorker(const ReadOptions& read_options,
|
||||
const WriteOptions& write_options,
|
||||
bool is_new_db) {
|
||||
Status DBImpl::RegisterRecordSeqnoTimeWorker() {
|
||||
options_mutex_.AssertHeld();
|
||||
|
||||
uint64_t min_preserve_seconds = std::numeric_limits<uint64_t>::max();
|
||||
uint64_t max_preserve_seconds = std::numeric_limits<uint64_t>::min();
|
||||
std::vector<SuperVersionContext> sv_contexts;
|
||||
// We assume InstallSuperVersionForConfigChange has already ensured suitable
|
||||
// mappings are present for each relevant CF. We just need to be sure the DB's
|
||||
// seqno_to_time_mapping_ and worker scheduler are appropriate for the
|
||||
// combination of CF settings.
|
||||
|
||||
MinAndMaxPreserveSeconds preserve_info;
|
||||
uint64_t seqno_time_cadence;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
auto& mopts = cfd->GetLatestMutableCFOptions();
|
||||
// preserve time is the max of 2 options.
|
||||
uint64_t preserve_seconds =
|
||||
std::max(mopts.preserve_internal_time_seconds,
|
||||
mopts.preclude_last_level_data_seconds);
|
||||
if (!cfd->IsDropped() && preserve_seconds > 0) {
|
||||
min_preserve_seconds = std::min(preserve_seconds, min_preserve_seconds);
|
||||
max_preserve_seconds = std::max(preserve_seconds, max_preserve_seconds);
|
||||
if (!cfd->IsDropped()) {
|
||||
preserve_info.Combine(mopts);
|
||||
}
|
||||
}
|
||||
size_t old_mapping_size = seqno_to_time_mapping_.Size();
|
||||
if (min_preserve_seconds == std::numeric_limits<uint64_t>::max()) {
|
||||
// Don't track
|
||||
seqno_time_cadence = preserve_info.GetRecodingCadence();
|
||||
if (seqno_time_cadence == 0) {
|
||||
// To return as much as possible to the feature being disabled,
|
||||
// clear the existing mapping
|
||||
seqno_to_time_mapping_.SetCapacity(0);
|
||||
seqno_to_time_mapping_.SetMaxTimeSpan(UINT64_MAX);
|
||||
assert(seqno_to_time_mapping_.Empty());
|
||||
} else {
|
||||
uint64_t cap = std::min(kMaxSeqnoToTimeEntries,
|
||||
max_preserve_seconds * kMaxSeqnoTimePairsPerCF /
|
||||
min_preserve_seconds);
|
||||
preserve_info.max_preserve_seconds *
|
||||
kMaxSeqnoTimePairsPerCF /
|
||||
preserve_info.min_preserve_seconds);
|
||||
seqno_to_time_mapping_.SetCapacity(cap);
|
||||
seqno_to_time_mapping_.SetMaxTimeSpan(max_preserve_seconds);
|
||||
seqno_to_time_mapping_.SetMaxTimeSpan(preserve_info.max_preserve_seconds);
|
||||
}
|
||||
if (old_mapping_size != seqno_to_time_mapping_.Size()) {
|
||||
InstallSeqnoToTimeMappingInSV(&sv_contexts);
|
||||
}
|
||||
}
|
||||
|
||||
// clean up outside db mutex
|
||||
for (SuperVersionContext& sv_context : sv_contexts) {
|
||||
sv_context.Clean();
|
||||
}
|
||||
sv_contexts.clear();
|
||||
|
||||
uint64_t seqno_time_cadence = 0;
|
||||
if (min_preserve_seconds != std::numeric_limits<uint64_t>::max()) {
|
||||
// round up to 1 when the time_duration is smaller than
|
||||
// kMaxSeqnoTimePairsPerCF
|
||||
seqno_time_cadence = (min_preserve_seconds + kMaxSeqnoTimePairsPerCF - 1) /
|
||||
kMaxSeqnoTimePairsPerCF;
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
@@ -903,64 +886,6 @@ Status DBImpl::RegisterRecordSeqnoTimeWorker(const ReadOptions& read_options,
|
||||
if (seqno_time_cadence == 0) {
|
||||
s = periodic_task_scheduler_.Unregister(PeriodicTaskType::kRecordSeqnoTime);
|
||||
} else {
|
||||
// Before registering the periodic task, we need to be sure to fulfill two
|
||||
// promises:
|
||||
// 1) Any DB created with preserve/preclude options set from the beginning
|
||||
// will get pre-allocated seqnos with pre-populated time mappings back to
|
||||
// the times we are interested in. (This will enable future import of data
|
||||
// while preserving rough write time. We can only do this reliably from
|
||||
// DB::Open, as otherwise there could be a race between CreateColumnFamily
|
||||
// and the first Write to the DB, and seqno-to-time mappings need to be
|
||||
// monotonic.
|
||||
// 2) In any DB, any data written after setting preserve/preclude options
|
||||
// must have a reasonable time estimate (so that we can accurately place
|
||||
// the data), which means at least one entry in seqno_to_time_mapping_.
|
||||
//
|
||||
// FIXME: We don't currently guarantee that if the first column family with
|
||||
// that setting is added or configured after initial DB::Open but before
|
||||
// the first user Write. Fixing this causes complications with the crash
|
||||
// test because if DB starts without preserve/preclude option, does some
|
||||
// user writes but all those writes are lost in crash, then re-opens with
|
||||
// preserve/preclude option, it sees seqno==1 which looks like one of the
|
||||
// user writes was recovered, when actually it was not.
|
||||
bool last_seqno_zero = GetLatestSequenceNumber() == 0;
|
||||
assert(!is_new_db || last_seqno_zero);
|
||||
if (is_new_db && last_seqno_zero) {
|
||||
// Pre-allocate seqnos and pre-populate historical mapping
|
||||
// We can simply modify these, before writes are allowed
|
||||
constexpr uint64_t kMax = kMaxSeqnoTimePairsPerSST;
|
||||
versions_->SetLastAllocatedSequence(kMax);
|
||||
versions_->SetLastPublishedSequence(kMax);
|
||||
versions_->SetLastSequence(kMax);
|
||||
|
||||
// And record in manifest, to avoid going backwards in seqno on re-open
|
||||
// (potentially with different options). Concurrency is simple because we
|
||||
// are in DB::Open
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
VersionEdit edit;
|
||||
edit.SetLastSequence(kMax);
|
||||
s = versions_->LogAndApplyToDefaultColumnFamily(
|
||||
read_options, write_options, &edit, &mutex_,
|
||||
directories_.GetDbDir());
|
||||
if (!s.ok() && versions_->io_status().IsIOError()) {
|
||||
error_handler_.SetBGError(versions_->io_status(),
|
||||
BackgroundErrorReason::kManifestWrite);
|
||||
}
|
||||
}
|
||||
|
||||
// Pre-populate mappings for reserved sequence numbers.
|
||||
RecordSeqnoToTimeMapping(max_preserve_seconds);
|
||||
} else {
|
||||
if (!last_seqno_zero) {
|
||||
// Ensure at least one mapping (or log a warning), and
|
||||
// an updated entry whenever relevant SetOptions is called
|
||||
RecordSeqnoToTimeMapping(/*populate_historical_seconds=*/0);
|
||||
} else {
|
||||
// FIXME (see limitation described above)
|
||||
}
|
||||
}
|
||||
|
||||
s = periodic_task_scheduler_.Register(
|
||||
PeriodicTaskType::kRecordSeqnoTime,
|
||||
periodic_task_functions_.at(PeriodicTaskType::kRecordSeqnoTime),
|
||||
@@ -1309,8 +1234,9 @@ Status DBImpl::SetOptions(
|
||||
//
|
||||
// (b) Append a new Version without manifest write nor DB mutex release
|
||||
//
|
||||
// Thus aren't releasing the DB mutex again until the end of this block,
|
||||
// after installing the new SuperVersion.
|
||||
// Thus aren't releasing the DB mutex from LogAndApply calling pre_cb,
|
||||
// through installing the new Version until the end of this block, after
|
||||
// installing the new SuperVersion.
|
||||
auto pre_cb = [&]() -> Status {
|
||||
Status cb_s = cfd->SetOptions(db_options, options_map);
|
||||
if (cb_s.ok()) {
|
||||
@@ -1335,7 +1261,7 @@ Status DBImpl::SetOptions(
|
||||
// Trigger possible flush/compactions. This has to be before we persist
|
||||
// options to file, otherwise there will be a deadlock with writer
|
||||
// thread.
|
||||
InstallSuperVersionAndScheduleWork(cfd, &sv_context);
|
||||
InstallSuperVersionForConfigChange(cfd, &sv_context);
|
||||
persist_options_status =
|
||||
WriteOptionsFile(write_options, true /*db_mutex_already_held*/);
|
||||
bg_cv_.SignalAll();
|
||||
@@ -1353,8 +1279,7 @@ Status DBImpl::SetOptions(
|
||||
|
||||
if (s.ok() && (options_map.count("preserve_internal_time_seconds") > 0 ||
|
||||
options_map.count("preclude_last_level_data_seconds") > 0)) {
|
||||
s = RegisterRecordSeqnoTimeWorker(read_options, write_options,
|
||||
false /* is_new_db*/);
|
||||
s = RegisterRecordSeqnoTimeWorker();
|
||||
}
|
||||
|
||||
ROCKS_LOG_INFO(
|
||||
@@ -2704,7 +2629,7 @@ Status DBImpl::MultiCFSnapshot(const ReadOptions& read_options,
|
||||
}
|
||||
};
|
||||
|
||||
bool last_try = false;
|
||||
bool acquire_mutex = false;
|
||||
if (cf_list->size() == 1) {
|
||||
// Fast path for a single column family. We can simply get the thread local
|
||||
// super version
|
||||
@@ -2753,29 +2678,32 @@ Status DBImpl::MultiCFSnapshot(const ReadOptions& read_options,
|
||||
// sure.
|
||||
constexpr int num_retries = 3;
|
||||
for (int i = 0; i < num_retries; ++i) {
|
||||
last_try = (i == num_retries - 1);
|
||||
// When reading from kPersistedTier, we want a consistent view into CFs.
|
||||
// So we take mutex to prevent any SV change in any CF.
|
||||
acquire_mutex = ((i == num_retries - 1) && !read_options.snapshot) ||
|
||||
read_options.read_tier == kPersistedTier;
|
||||
bool retry = false;
|
||||
|
||||
if (i > 0) {
|
||||
sv_cleanup_func();
|
||||
}
|
||||
if (read_options.snapshot == nullptr) {
|
||||
if (last_try) {
|
||||
TEST_SYNC_POINT("DBImpl::MultiCFSnapshot::LastTry");
|
||||
// We're close to max number of retries. For the last retry,
|
||||
// acquire the lock so we're sure to succeed
|
||||
mutex_.Lock();
|
||||
}
|
||||
*snapshot = GetLastPublishedSequence();
|
||||
} else {
|
||||
*snapshot =
|
||||
static_cast_with_check<const SnapshotImpl>(read_options.snapshot)
|
||||
->number_;
|
||||
}
|
||||
if (acquire_mutex) {
|
||||
TEST_SYNC_POINT("DBImpl::MultiCFSnapshot::LastTry");
|
||||
// We're close to max number of retries. For the last retry,
|
||||
// acquire the lock so we're sure to succeed
|
||||
mutex_.Lock();
|
||||
}
|
||||
for (auto cf_iter = cf_list->begin(); cf_iter != cf_list->end();
|
||||
++cf_iter) {
|
||||
auto node = iter_deref_func(cf_iter);
|
||||
if (!last_try) {
|
||||
if (!acquire_mutex) {
|
||||
if (extra_sv_ref) {
|
||||
node->super_version = node->cfd->GetReferencedSuperVersion(this);
|
||||
} else {
|
||||
@@ -2799,7 +2727,7 @@ Status DBImpl::MultiCFSnapshot(const ReadOptions& read_options,
|
||||
}
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::MultiCFSnapshot::BeforeCheckingSnapshot");
|
||||
if (read_options.snapshot != nullptr || last_try) {
|
||||
if (read_options.snapshot != nullptr || acquire_mutex) {
|
||||
// If user passed a snapshot, then we don't care if a memtable is
|
||||
// sealed or compaction happens because the snapshot would ensure
|
||||
// that older key versions are kept around. If this is the last
|
||||
@@ -2810,7 +2738,7 @@ Status DBImpl::MultiCFSnapshot(const ReadOptions& read_options,
|
||||
// memtables, which will include immutable memtables as well, but that
|
||||
// might be tricky to maintain in case we decide, in future, to do
|
||||
// memtable compaction.
|
||||
if (!last_try) {
|
||||
if (!acquire_mutex) {
|
||||
SequenceNumber seq =
|
||||
node->super_version->mem->GetEarliestSequenceNumber();
|
||||
if (seq > *snapshot) {
|
||||
@@ -2820,19 +2748,20 @@ Status DBImpl::MultiCFSnapshot(const ReadOptions& read_options,
|
||||
}
|
||||
}
|
||||
if (!retry) {
|
||||
if (last_try) {
|
||||
if (acquire_mutex) {
|
||||
mutex_.Unlock();
|
||||
TEST_SYNC_POINT("DBImpl::MultiCFSnapshot::AfterLastTryRefSV");
|
||||
}
|
||||
break;
|
||||
}
|
||||
assert(!acquire_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT("DBImpl::MultiCFSnapshot:AfterGetSeqNum1");
|
||||
TEST_SYNC_POINT("DBImpl::MultiCFSnapshot:AfterGetSeqNum2");
|
||||
PERF_TIMER_STOP(get_snapshot_time);
|
||||
*sv_from_thread_local = !last_try;
|
||||
*sv_from_thread_local = !acquire_mutex;
|
||||
if (!s.ok()) {
|
||||
sv_cleanup_func();
|
||||
}
|
||||
@@ -3538,7 +3467,7 @@ void DBImpl::MultiGetEntityWithCallback(
|
||||
}
|
||||
|
||||
Status DBImpl::WrapUpCreateColumnFamilies(
|
||||
const ReadOptions& read_options, const WriteOptions& write_options,
|
||||
const WriteOptions& write_options,
|
||||
const std::vector<const ColumnFamilyOptions*>& cf_options) {
|
||||
options_mutex_.AssertHeld();
|
||||
|
||||
@@ -3555,8 +3484,7 @@ Status DBImpl::WrapUpCreateColumnFamilies(
|
||||
// Attempt both follow-up actions even if one fails
|
||||
Status s = WriteOptionsFile(write_options, false /*db_mutex_already_held*/);
|
||||
if (register_worker) {
|
||||
s.UpdateIfOk(RegisterRecordSeqnoTimeWorker(read_options, write_options,
|
||||
/* is_new_db */ false));
|
||||
s.UpdateIfOk(RegisterRecordSeqnoTimeWorker());
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -3571,8 +3499,7 @@ Status DBImpl::CreateColumnFamily(const ReadOptions& read_options,
|
||||
Status s = CreateColumnFamilyImpl(read_options, write_options, cf_options,
|
||||
column_family, handle);
|
||||
if (s.ok()) {
|
||||
s.UpdateIfOk(
|
||||
WrapUpCreateColumnFamilies(read_options, write_options, {&cf_options}));
|
||||
s.UpdateIfOk(WrapUpCreateColumnFamilies(write_options, {&cf_options}));
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -3599,8 +3526,7 @@ Status DBImpl::CreateColumnFamilies(
|
||||
success_once = true;
|
||||
}
|
||||
if (success_once) {
|
||||
s.UpdateIfOk(
|
||||
WrapUpCreateColumnFamilies(read_options, write_options, {&cf_options}));
|
||||
s.UpdateIfOk(WrapUpCreateColumnFamilies(write_options, {&cf_options}));
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -3630,8 +3556,7 @@ Status DBImpl::CreateColumnFamilies(
|
||||
cf_opts.push_back(&column_families[i].options);
|
||||
}
|
||||
if (success_once) {
|
||||
s.UpdateIfOk(
|
||||
WrapUpCreateColumnFamilies(read_options, write_options, cf_opts));
|
||||
s.UpdateIfOk(WrapUpCreateColumnFamilies(write_options, cf_opts));
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -3700,7 +3625,7 @@ Status DBImpl::CreateColumnFamilyImpl(const ReadOptions& read_options,
|
||||
auto* cfd =
|
||||
versions_->GetColumnFamilySet()->GetColumnFamily(column_family_name);
|
||||
assert(cfd != nullptr);
|
||||
InstallSuperVersionAndScheduleWork(cfd, &sv_context);
|
||||
InstallSuperVersionForConfigChange(cfd, &sv_context);
|
||||
|
||||
if (!cfd->mem()->IsSnapshotSupported()) {
|
||||
is_snapshot_supported_ = false;
|
||||
@@ -3784,7 +3709,7 @@ Status DBImpl::DropColumnFamilyImpl(ColumnFamilyHandle* column_family) {
|
||||
Status s;
|
||||
// Save re-aquiring lock for RegisterRecordSeqnoTimeWorker when not
|
||||
// applicable
|
||||
bool used_preserve_preclude = false;
|
||||
MinAndMaxPreserveSeconds preserve_info;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
if (cfd->IsDropped()) {
|
||||
@@ -3802,8 +3727,7 @@ Status DBImpl::DropColumnFamilyImpl(ColumnFamilyHandle* column_family) {
|
||||
auto& moptions = cfd->GetLatestMutableCFOptions();
|
||||
max_total_in_memory_state_ -=
|
||||
moptions.write_buffer_size * moptions.max_write_buffer_number;
|
||||
used_preserve_preclude = moptions.preserve_internal_time_seconds > 0 ||
|
||||
moptions.preclude_last_level_data_seconds > 0;
|
||||
preserve_info.Combine(moptions);
|
||||
}
|
||||
|
||||
if (!cf_support_snapshot) {
|
||||
@@ -3821,9 +3745,8 @@ Status DBImpl::DropColumnFamilyImpl(ColumnFamilyHandle* column_family) {
|
||||
bg_cv_.SignalAll();
|
||||
}
|
||||
|
||||
if (used_preserve_preclude) {
|
||||
s = RegisterRecordSeqnoTimeWorker(read_options, write_options,
|
||||
/* is_new_db */ false);
|
||||
if (preserve_info.IsEnabled()) {
|
||||
s = RegisterRecordSeqnoTimeWorker();
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
@@ -4485,7 +4408,7 @@ Status DBImpl::GetPropertiesOfTablesInRange(ColumnFamilyHandle* column_family,
|
||||
// Add timestamp if needed
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
auto [start, limit] = MaybeAddTimestampsToRange(
|
||||
&range[i].start, &range[i].limit, ts_sz, &keys.emplace_back(),
|
||||
range[i].start, range[i].limit, ts_sz, &keys.emplace_back(),
|
||||
&keys.emplace_back(), /*exclusive_end=*/false);
|
||||
assert(start.has_value());
|
||||
assert(limit.has_value());
|
||||
@@ -4502,6 +4425,29 @@ Status DBImpl::GetPropertiesOfTablesInRange(ColumnFamilyHandle* column_family,
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::GetPropertiesOfTablesByLevel(
|
||||
ColumnFamilyHandle* column_family,
|
||||
std::vector<std::unique_ptr<TablePropertiesCollection>>* props_by_level) {
|
||||
auto cfh = static_cast_with_check<ColumnFamilyHandleImpl>(column_family);
|
||||
auto cfd = cfh->cfd();
|
||||
|
||||
// Increment the ref count
|
||||
mutex_.Lock();
|
||||
auto version = cfd->current();
|
||||
version->Ref();
|
||||
mutex_.Unlock();
|
||||
|
||||
const ReadOptions read_options;
|
||||
auto s = version->GetPropertiesOfTablesByLevel(read_options, props_by_level);
|
||||
|
||||
// Decrement the ref count
|
||||
mutex_.Lock();
|
||||
version->Unref();
|
||||
mutex_.Unlock();
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
const std::string& DBImpl::GetName() const { return dbname_; }
|
||||
|
||||
Env* DBImpl::GetEnv() const { return env_; }
|
||||
@@ -4802,7 +4748,7 @@ void DBImpl::GetApproximateMemTableStats(ColumnFamilyHandle* column_family,
|
||||
// Add timestamp if needed
|
||||
std::string start_with_ts, limit_with_ts;
|
||||
auto [start, limit] = MaybeAddTimestampsToRange(
|
||||
&range.start, &range.limit, ts_sz, &start_with_ts, &limit_with_ts);
|
||||
range.start, range.limit, ts_sz, &start_with_ts, &limit_with_ts);
|
||||
assert(start.has_value());
|
||||
assert(limit.has_value());
|
||||
// Convert user_key into a corresponding internal key.
|
||||
@@ -4840,9 +4786,8 @@ Status DBImpl::GetApproximateSizes(const SizeApproximationOptions& options,
|
||||
for (int i = 0; i < n; i++) {
|
||||
// Add timestamp if needed
|
||||
std::string start_with_ts, limit_with_ts;
|
||||
auto [start, limit] =
|
||||
MaybeAddTimestampsToRange(&range[i].start, &range[i].limit, ts_sz,
|
||||
&start_with_ts, &limit_with_ts);
|
||||
auto [start, limit] = MaybeAddTimestampsToRange(
|
||||
range[i].start, range[i].limit, ts_sz, &start_with_ts, &limit_with_ts);
|
||||
assert(start.has_value());
|
||||
assert(limit.has_value());
|
||||
// Convert user_key into a corresponding internal key.
|
||||
@@ -4918,7 +4863,7 @@ Status DBImpl::GetUpdatesSince(
|
||||
}
|
||||
|
||||
Status DBImpl::DeleteFilesInRanges(ColumnFamilyHandle* column_family,
|
||||
const RangePtr* ranges, size_t n,
|
||||
const RangeOpt* ranges, size_t n,
|
||||
bool include_end) {
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
const ReadOptions read_options;
|
||||
@@ -4930,7 +4875,7 @@ Status DBImpl::DeleteFilesInRanges(ColumnFamilyHandle* column_family,
|
||||
const Comparator* ucmp = cfd->user_comparator();
|
||||
assert(ucmp);
|
||||
const size_t ts_sz = ucmp->timestamp_size();
|
||||
autovector<UserKeyRangePtr> ukey_ranges;
|
||||
autovector<UserKeyRangeOpt> ukey_ranges;
|
||||
std::vector<std::string> keys;
|
||||
std::vector<Slice> key_slices;
|
||||
ukey_ranges.reserve(n);
|
||||
@@ -4940,8 +4885,8 @@ Status DBImpl::DeleteFilesInRanges(ColumnFamilyHandle* column_family,
|
||||
auto [start, limit] = MaybeAddTimestampsToRange(
|
||||
ranges[i].start, ranges[i].limit, ts_sz, &keys.emplace_back(),
|
||||
&keys.emplace_back(), !include_end);
|
||||
assert((ranges[i].start != nullptr) == start.has_value());
|
||||
assert((ranges[i].limit != nullptr) == limit.has_value());
|
||||
assert(ranges[i].start.has_value() == start.has_value());
|
||||
assert(ranges[i].limit.has_value() == limit.has_value());
|
||||
ukey_ranges.emplace_back(start, limit);
|
||||
}
|
||||
|
||||
@@ -5877,6 +5822,27 @@ Status DBImpl::IngestExternalFiles(
|
||||
"timestamps enabled doesn't support ingest behind.");
|
||||
}
|
||||
}
|
||||
if (arg.atomic_replace_range.has_value()) {
|
||||
if (ingest_opts.ingest_behind) {
|
||||
return Status::InvalidArgument(
|
||||
"Can't combine atomic_replace_range with ingest_behind.");
|
||||
}
|
||||
if (ingest_opts.snapshot_consistency) {
|
||||
// TODO: support generating and ingesting a big tombstone file, which
|
||||
// might depend on non-nullptr start and limit
|
||||
return Status::NotSupported(
|
||||
"atomic_replace_range not yet supported with "
|
||||
"snapshot_consistency.");
|
||||
} else {
|
||||
if (arg.atomic_replace_range->start.has_value() ^
|
||||
arg.atomic_replace_range->limit.has_value()) {
|
||||
return Status::NotSupported(
|
||||
"Only one of atomic_replace_range.{start,limit}.has_value() is "
|
||||
"not supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ingest_opts.allow_db_generated_files) {
|
||||
if (ingest_opts.write_global_seqno) {
|
||||
return Status::NotSupported(
|
||||
@@ -5925,8 +5891,8 @@ Status DBImpl::IngestExternalFiles(
|
||||
this);
|
||||
Status es = ingestion_jobs[i].Prepare(
|
||||
args[i].external_files, args[i].files_checksums,
|
||||
args[i].files_checksum_func_names, args[i].file_temperature,
|
||||
start_file_number, super_version);
|
||||
args[i].files_checksum_func_names, args[i].atomic_replace_range,
|
||||
args[i].file_temperature, start_file_number, super_version);
|
||||
// capture first error only
|
||||
if (!es.ok() && status.ok()) {
|
||||
status = es;
|
||||
@@ -5941,8 +5907,8 @@ Status DBImpl::IngestExternalFiles(
|
||||
this);
|
||||
Status es = ingestion_jobs[0].Prepare(
|
||||
args[0].external_files, args[0].files_checksums,
|
||||
args[0].files_checksum_func_names, args[0].file_temperature,
|
||||
next_file_number, super_version);
|
||||
args[0].files_checksum_func_names, args[0].atomic_replace_range,
|
||||
args[0].file_temperature, next_file_number, super_version);
|
||||
if (!es.ok()) {
|
||||
status = es;
|
||||
}
|
||||
@@ -6235,7 +6201,7 @@ Status DBImpl::CreateColumnFamilyWithImport(
|
||||
versions_->LogAndApply(cfd, read_options, write_options, &dummy_edit,
|
||||
&mutex_, directories_.GetDbDir());
|
||||
if (status.ok()) {
|
||||
InstallSuperVersionAndScheduleWork(cfd, &dummy_sv_ctx);
|
||||
InstallSuperVersionForConfigChange(cfd, &dummy_sv_ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6272,7 +6238,7 @@ Status DBImpl::CreateColumnFamilyWithImport(
|
||||
import_job.edit(), &mutex_,
|
||||
directories_.GetDbDir());
|
||||
if (status.ok()) {
|
||||
InstallSuperVersionAndScheduleWork(cfd, &sv_context);
|
||||
InstallSuperVersionForConfigChange(cfd, &sv_context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6333,9 +6299,9 @@ Status DBImpl::ClipColumnFamily(ColumnFamilyHandle* column_family,
|
||||
|
||||
if (status.ok()) {
|
||||
// DeleteFilesInRanges non-overlap files except L0
|
||||
std::vector<RangePtr> ranges;
|
||||
ranges.emplace_back(nullptr, &begin_key);
|
||||
ranges.emplace_back(&end_key, nullptr);
|
||||
std::vector<RangeOpt> ranges;
|
||||
ranges.emplace_back(OptSlice{}, begin_key);
|
||||
ranges.emplace_back(end_key, OptSlice{});
|
||||
status = DeleteFilesInRanges(column_family, ranges.data(), ranges.size());
|
||||
}
|
||||
|
||||
@@ -6732,7 +6698,7 @@ Status DBImpl::GetCreationTimeOfOldestFile(uint64_t* creation_time) {
|
||||
}
|
||||
}
|
||||
|
||||
void DBImpl::RecordSeqnoToTimeMapping(uint64_t populate_historical_seconds) {
|
||||
std::pair<SequenceNumber, uint64_t> DBImpl::GetSeqnoToTimeSample() const {
|
||||
// TECHNICALITY: Sample last sequence number *before* time, as prescribed
|
||||
// for SeqnoToTimeMapping. We don't know how long it has been since the last
|
||||
// sequence number was written, so we at least have a one-sided bound by
|
||||
@@ -6741,63 +6707,162 @@ void DBImpl::RecordSeqnoToTimeMapping(uint64_t populate_historical_seconds) {
|
||||
// while holding the DB mutex. (This is really to make testing happy because
|
||||
// it's fine to throw out extra close-but-not-quite-consistent mappings in
|
||||
// production.)
|
||||
std::vector<SuperVersionContext> sv_contexts;
|
||||
bool success = true;
|
||||
SequenceNumber seqno;
|
||||
uint64_t unix_time;
|
||||
mutex_.AssertHeld();
|
||||
SequenceNumber seqno = GetLatestSequenceNumber();
|
||||
// HACK/TODO: seqno might be zero but we can't record a mapping for that.
|
||||
// Start with 1, which should be close enough.
|
||||
seqno = std::max(seqno, SequenceNumber{1});
|
||||
int64_t unix_time_signed = 0;
|
||||
immutable_db_options_.clock->GetCurrentTime(&unix_time_signed)
|
||||
.PermitUncheckedError(); // Ignore error
|
||||
return {seqno, static_cast<uint64_t>(unix_time_signed)};
|
||||
}
|
||||
|
||||
void DBImpl::EnsureSeqnoToTimeMapping(
|
||||
const MinAndMaxPreserveSeconds& preserve_info) {
|
||||
mutex_.AssertHeld();
|
||||
assert(preserve_info.IsEnabled());
|
||||
|
||||
// Atomically with CF creation or mutable option change (see
|
||||
// InstallSuperVersionForConfigChange()), we need to be sure any data written
|
||||
// after setting preserve/preclude options must have a reasonable time
|
||||
// estimate (so that we can accurately place the data), which means at least
|
||||
// one entry in seqno_to_time_mapping_. It's not critical that `preserve_info`
|
||||
// take into account all CFs, as that's mostly relevant to how we add
|
||||
// recurring entries and purge old ones.
|
||||
|
||||
auto [seqno, unix_time_now] = GetSeqnoToTimeSample();
|
||||
// Ensure at least one sample that is sufficiently recent
|
||||
uint64_t unix_time_last_sample = 0;
|
||||
if (seqno_to_time_mapping_.Empty()) {
|
||||
// The exact best settings will be found and applied in
|
||||
// RegisterRecordSeqnoTimeWorker()
|
||||
seqno_to_time_mapping_.SetCapacity(kMaxSeqnoToTimeEntries);
|
||||
} else {
|
||||
unix_time_last_sample =
|
||||
seqno_to_time_mapping_.GetProximalTimeBeforeSeqno(kMaxSequenceNumber);
|
||||
}
|
||||
uint64_t cadence = preserve_info.GetRecodingCadence();
|
||||
// Extend cadence so as to avoid stepping on toes of recorder job, which
|
||||
// could lag a bit.
|
||||
cadence += 3 + cadence / 100;
|
||||
if (unix_time_now >= cadence &&
|
||||
unix_time_last_sample <= unix_time_now - cadence) {
|
||||
assert(seqno > 0); // See GetSeqnoToTimeSample()
|
||||
// Always successful assuming seqno never go backwards
|
||||
seqno_to_time_mapping_.Append(seqno, unix_time_now);
|
||||
}
|
||||
}
|
||||
|
||||
void DBImpl::PrepopulateSeqnoToTimeMapping(
|
||||
const MinAndMaxPreserveSeconds& preserve_info) {
|
||||
// Only for opening a new DB, with preserve/preclude options set
|
||||
if (!preserve_info.IsEnabled()) {
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
if (GetLatestSequenceNumber() != 0) {
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Here we fulfill the following promise:
|
||||
//
|
||||
// Any DB/CF created with preserve/preclude options set from the beginning
|
||||
// will get pre-allocated seqnos with pre-populated time mappings back to
|
||||
// the times we are interested in. (This will enable future import of data
|
||||
// while preserving rough write time. We can only do this reliably from
|
||||
// DB::Open, as otherwise there could be a race between CreateColumnFamily
|
||||
// and the first Write to the DB, and seqno-to-time mappings need to be
|
||||
// monotonic.
|
||||
//
|
||||
// FIXME: We don't currently guarantee that if the first column family with
|
||||
// that setting is added or configured after initial DB::Open but before
|
||||
// the first user Write. Fixing this causes complications with the crash
|
||||
// test because if DB starts without preserve/preclude option, does some
|
||||
// user writes but all those writes are lost in crash, then re-opens with
|
||||
// preserve/preclude option, it sees seqno==1 which looks like one of the
|
||||
// user writes was recovered, when actually it was not.
|
||||
|
||||
// Pre-allocate seqnos and pre-populate historical mapping
|
||||
// We can simply modify these, before writes are allowed
|
||||
constexpr uint64_t kMax = kMaxSeqnoTimePairsPerSST;
|
||||
versions_->SetLastAllocatedSequence(kMax);
|
||||
versions_->SetLastPublishedSequence(kMax);
|
||||
versions_->SetLastSequence(kMax);
|
||||
|
||||
// And record in manifest, to avoid going backwards in seqno on re-open
|
||||
// (potentially with different options). Concurrency is simple because we
|
||||
// are in DB::Open
|
||||
const WriteOptions write_options(Env::IOActivity::kDBOpen);
|
||||
const ReadOptions read_options(Env::IOActivity::kDBOpen);
|
||||
VersionEdit edit;
|
||||
edit.SetLastSequence(kMax);
|
||||
Status s = versions_->LogAndApplyToDefaultColumnFamily(
|
||||
read_options, write_options, &edit, &mutex_, directories_.GetDbDir());
|
||||
if (!s.ok() && versions_->io_status().IsIOError()) {
|
||||
error_handler_.SetBGError(versions_->io_status(),
|
||||
BackgroundErrorReason::kManifestWrite);
|
||||
}
|
||||
|
||||
auto [seqno, unix_time_now] = GetSeqnoToTimeSample();
|
||||
uint64_t populate_historical_seconds = preserve_info.max_preserve_seconds;
|
||||
if (seqno > 1 && unix_time_now > populate_historical_seconds) {
|
||||
// seqno=0 is reserved
|
||||
SequenceNumber from_seqno = 1;
|
||||
seqno_to_time_mapping_.PrePopulate(
|
||||
from_seqno, seqno, unix_time_now - populate_historical_seconds,
|
||||
unix_time_now);
|
||||
} else {
|
||||
// One of these will fail
|
||||
assert(seqno > 1);
|
||||
assert(unix_time_now > populate_historical_seconds);
|
||||
}
|
||||
}
|
||||
|
||||
void DBImpl::InstallSuperVersionForConfigChange(
|
||||
ColumnFamilyData* cfd, SuperVersionContext* sv_context) {
|
||||
MinAndMaxPreserveSeconds preserve_info{cfd->GetLatestCFOptions()};
|
||||
std::shared_ptr<SeqnoToTimeMapping> new_seqno_to_time_mapping;
|
||||
if (preserve_info.IsEnabled()) {
|
||||
// TODO: detect & optimize if mapping hasn't changed from previous
|
||||
// SuperVersion
|
||||
EnsureSeqnoToTimeMapping(preserve_info);
|
||||
new_seqno_to_time_mapping = std::make_shared<SeqnoToTimeMapping>();
|
||||
new_seqno_to_time_mapping->CopyFrom(seqno_to_time_mapping_);
|
||||
}
|
||||
InstallSuperVersionAndScheduleWork(cfd, sv_context,
|
||||
std::move(new_seqno_to_time_mapping));
|
||||
}
|
||||
|
||||
void DBImpl::RecordSeqnoToTimeMapping() {
|
||||
SuperVersionContext sv_context;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
// Record next sample
|
||||
seqno_to_time_mapping_.Append(GetSeqnoToTimeSample());
|
||||
// Create an immutable snapshot for sharing across CFs
|
||||
std::shared_ptr<SeqnoToTimeMapping> new_seqno_to_time_mapping =
|
||||
std::make_shared<SeqnoToTimeMapping>();
|
||||
new_seqno_to_time_mapping->CopyFrom(seqno_to_time_mapping_);
|
||||
|
||||
seqno = GetLatestSequenceNumber();
|
||||
int64_t unix_time_signed = 0;
|
||||
immutable_db_options_.clock->GetCurrentTime(&unix_time_signed)
|
||||
.PermitUncheckedError(); // Ignore error
|
||||
unix_time = static_cast<uint64_t>(unix_time_signed);
|
||||
|
||||
if (populate_historical_seconds > 0) {
|
||||
if (seqno > 1 && unix_time > populate_historical_seconds) {
|
||||
// seqno=0 is reserved
|
||||
SequenceNumber from_seqno = 1;
|
||||
success = seqno_to_time_mapping_.PrePopulate(
|
||||
from_seqno, seqno, unix_time - populate_historical_seconds,
|
||||
unix_time);
|
||||
InstallSeqnoToTimeMappingInSV(&sv_contexts);
|
||||
} else {
|
||||
// One of these will fail
|
||||
assert(seqno > 1);
|
||||
assert(unix_time > populate_historical_seconds);
|
||||
success = false;
|
||||
// Update in SV of all applicable CFs
|
||||
for (ColumnFamilyData* cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (cfd->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
MinAndMaxPreserveSeconds preserve_info{cfd->GetLatestCFOptions()};
|
||||
if (preserve_info.IsEnabled()) {
|
||||
sv_context.NewSuperVersion();
|
||||
cfd->InstallSuperVersion(&sv_context, &mutex_,
|
||||
new_seqno_to_time_mapping);
|
||||
}
|
||||
} else {
|
||||
// FIXME: assert(seqno > 0);
|
||||
// Always successful assuming seqno never go backwards
|
||||
seqno_to_time_mapping_.Append(seqno, unix_time);
|
||||
InstallSeqnoToTimeMappingInSV(&sv_contexts);
|
||||
}
|
||||
bg_cv_.SignalAll();
|
||||
}
|
||||
|
||||
// clean up & report outside db mutex
|
||||
for (SuperVersionContext& sv_context : sv_contexts) {
|
||||
sv_context.Clean();
|
||||
}
|
||||
|
||||
if (populate_historical_seconds > 0) {
|
||||
if (success) {
|
||||
ROCKS_LOG_INFO(
|
||||
immutable_db_options_.info_log,
|
||||
"Pre-populated sequence number to time entries: [1,%" PRIu64
|
||||
"] -> [%" PRIu64 ",%" PRIu64 "]",
|
||||
seqno, unix_time - populate_historical_seconds, unix_time);
|
||||
} else {
|
||||
ROCKS_LOG_WARN(
|
||||
immutable_db_options_.info_log,
|
||||
"Failed to pre-populate sequence number to time entries: [1,%" PRIu64
|
||||
"] -> [%" PRIu64 ",%" PRIu64 "]",
|
||||
seqno, unix_time - populate_historical_seconds, unix_time);
|
||||
}
|
||||
} else {
|
||||
assert(success);
|
||||
}
|
||||
sv_context.Clean();
|
||||
}
|
||||
|
||||
void DBImpl::TrackOrUntrackFiles(
|
||||
@@ -6856,22 +6921,4 @@ void DBImpl::TrackOrUntrackFiles(
|
||||
}
|
||||
}
|
||||
|
||||
void DBImpl::InstallSeqnoToTimeMappingInSV(
|
||||
std::vector<SuperVersionContext>* sv_contexts) {
|
||||
mutex_.AssertHeld();
|
||||
std::shared_ptr<SeqnoToTimeMapping> new_seqno_to_time_mapping =
|
||||
std::make_shared<SeqnoToTimeMapping>();
|
||||
new_seqno_to_time_mapping->CopyFrom(seqno_to_time_mapping_);
|
||||
for (ColumnFamilyData* cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (cfd->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
sv_contexts->emplace_back(/*create_superversion=*/true);
|
||||
sv_contexts->back().new_seqno_to_time_mapping = new_seqno_to_time_mapping;
|
||||
cfd->InstallSuperVersion(&sv_contexts->back(),
|
||||
cfd->GetLatestMutableCFOptions());
|
||||
}
|
||||
bg_cv_.SignalAll();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+36
-28
@@ -542,7 +542,7 @@ class DBImpl : public DB {
|
||||
const TransactionLogIterator::ReadOptions& read_options =
|
||||
TransactionLogIterator::ReadOptions()) override;
|
||||
Status DeleteFilesInRanges(ColumnFamilyHandle* column_family,
|
||||
const RangePtr* ranges, size_t n,
|
||||
const RangeOpt* ranges, size_t n,
|
||||
bool include_end = true);
|
||||
|
||||
void GetLiveFilesMetaData(std::vector<LiveFileMetaData>* metadata) override;
|
||||
@@ -651,6 +651,11 @@ class DBImpl : public DB {
|
||||
ColumnFamilyHandle* column_family, const Range* range, std::size_t n,
|
||||
TablePropertiesCollection* props) override;
|
||||
|
||||
Status GetPropertiesOfTablesByLevel(
|
||||
ColumnFamilyHandle* column_family,
|
||||
std::vector<std::unique_ptr<TablePropertiesCollection>>* props_by_level)
|
||||
override;
|
||||
|
||||
// ---- End of implementations of the DB interface ----
|
||||
SystemClock* GetSystemClock() const;
|
||||
|
||||
@@ -1267,27 +1272,18 @@ class DBImpl : public DB {
|
||||
// flush LOG out of application buffer
|
||||
void FlushInfoLog();
|
||||
|
||||
// record current sequence number to time mapping. If
|
||||
// populate_historical_seconds > 0 then pre-populate all the
|
||||
// sequence numbers from [1, last] to map to [now minus
|
||||
// populate_historical_seconds, now].
|
||||
void RecordSeqnoToTimeMapping(uint64_t populate_historical_seconds);
|
||||
// For the background timer job
|
||||
void RecordSeqnoToTimeMapping();
|
||||
|
||||
// Everytime DB's seqno to time mapping changed (which already hold the db
|
||||
// mutex), we install a new SuperVersion in each column family with a shared
|
||||
// copy of the new mapping while holding the db mutex.
|
||||
// This is done for all column families even though the column family does not
|
||||
// explicitly enabled the
|
||||
// `preclude_last_level_data_seconds` or `preserve_internal_time_seconds`
|
||||
// features.
|
||||
// This mapping supports iterators to fulfill the
|
||||
// "rocksdb.iterator.write-time" iterator property for entries in memtables.
|
||||
//
|
||||
// Since this new SuperVersion doesn't involve an LSM tree shape change, we
|
||||
// don't schedule work after installing this SuperVersion. It returns the used
|
||||
// `SuperVersionContext` for clean up after release mutex.
|
||||
void InstallSeqnoToTimeMappingInSV(
|
||||
std::vector<SuperVersionContext>* sv_contexts);
|
||||
// REQUIRES: DB mutex held
|
||||
std::pair<SequenceNumber, uint64_t> GetSeqnoToTimeSample() const;
|
||||
|
||||
// REQUIRES: DB mutex held or during open
|
||||
void EnsureSeqnoToTimeMapping(const MinAndMaxPreserveSeconds& preserve_secs);
|
||||
|
||||
// Only called during open
|
||||
void PrepopulateSeqnoToTimeMapping(
|
||||
const MinAndMaxPreserveSeconds& preserve_secs);
|
||||
|
||||
// Interface to block and signal the DB in case of stalling writes by
|
||||
// WriteBufferManager. Each DBImpl object contains ptr to WBMStallInterface.
|
||||
@@ -1979,7 +1975,7 @@ class DBImpl : public DB {
|
||||
|
||||
// Follow-up work to user creating a column family or (families)
|
||||
Status WrapUpCreateColumnFamilies(
|
||||
const ReadOptions& read_options, const WriteOptions& write_options,
|
||||
const WriteOptions& write_options,
|
||||
const std::vector<const ColumnFamilyOptions*>& cf_options);
|
||||
|
||||
Status DropColumnFamilyImpl(ColumnFamilyHandle* column_family);
|
||||
@@ -2138,6 +2134,11 @@ class DBImpl : public DB {
|
||||
bool flushed, std::unordered_map<int, VersionEdit>* version_edits,
|
||||
RecoveryContext* recovery_ctx);
|
||||
|
||||
// Check that DB sequence number is not set back during recovery between
|
||||
// replaying of WAL files and between replaying of WriteBatches.
|
||||
Status CheckSeqnoNotSetBackDuringRecovery(SequenceNumber prev_next_seqno,
|
||||
SequenceNumber current_next_seqno);
|
||||
|
||||
void FinishLogFilesRecovery(int job_id, const Status& status);
|
||||
// The following two methods are used to flush a memtable to
|
||||
// storage. The first one is used at database RecoveryTime (when the
|
||||
@@ -2450,9 +2451,7 @@ class DBImpl : public DB {
|
||||
// Cancel scheduled periodic tasks
|
||||
Status CancelPeriodicTaskScheduler();
|
||||
|
||||
Status RegisterRecordSeqnoTimeWorker(const ReadOptions& read_options,
|
||||
const WriteOptions& write_options,
|
||||
bool is_new_db);
|
||||
Status RegisterRecordSeqnoTimeWorker();
|
||||
|
||||
void PrintStatistics();
|
||||
|
||||
@@ -2518,12 +2517,21 @@ class DBImpl : public DB {
|
||||
|
||||
// Background threads call this function, which is just a wrapper around
|
||||
// the InstallSuperVersion() function. Background threads carry
|
||||
// sv_context which can have new_superversion already
|
||||
// allocated.
|
||||
// sv_context to allow allocation of SuperVersion object outside of holding
|
||||
// the DB mutex.
|
||||
// All ColumnFamily state changes go through this function. Here we analyze
|
||||
// the new state and we schedule background work if we detect that the new
|
||||
// state needs flush or compaction.
|
||||
void InstallSuperVersionAndScheduleWork(ColumnFamilyData* cfd,
|
||||
// See also InstallSuperVersionForConfigChange().
|
||||
void InstallSuperVersionAndScheduleWork(
|
||||
ColumnFamilyData* cfd, SuperVersionContext* sv_context,
|
||||
std::optional<std::shared_ptr<SeqnoToTimeMapping>>
|
||||
new_seqno_to_time_mapping = {});
|
||||
|
||||
// A variant of InstallSuperVersionAndScheduleWork() that must be used for
|
||||
// new CFs or for changes to mutable_cf_options. This is so that it can
|
||||
// update seqno_to_time_mapping cached for the new SuperVersion as relevant.
|
||||
void InstallSuperVersionForConfigChange(ColumnFamilyData* cfd,
|
||||
SuperVersionContext* sv_context);
|
||||
|
||||
bool GetIntPropertyInternal(ColumnFamilyData* cfd,
|
||||
|
||||
@@ -981,7 +981,8 @@ Status DBImpl::CompactRange(const CompactRangeOptions& options,
|
||||
|
||||
std::string begin_str, end_str;
|
||||
auto [begin, end] =
|
||||
MaybeAddTimestampsToRange(begin_without_ts, end_without_ts, ts_sz,
|
||||
MaybeAddTimestampsToRange(OptSlice::CopyFromPtr(begin_without_ts),
|
||||
OptSlice::CopyFromPtr(end_without_ts), ts_sz,
|
||||
&begin_str, &end_str, false /*exclusive_end*/);
|
||||
|
||||
return CompactRangeInternal(
|
||||
@@ -1695,11 +1696,13 @@ void DBImpl::NotifyOnCompactionBegin(ColumnFamilyData* cfd, Compaction* c,
|
||||
}
|
||||
|
||||
c->SetNotifyOnCompactionCompleted();
|
||||
int num_l0_files = c->input_version()->storage_info()->NumLevelFiles(0);
|
||||
// release lock while notifying events
|
||||
mutex_.Unlock();
|
||||
TEST_SYNC_POINT("DBImpl::NotifyOnCompactionBegin::UnlockMutex");
|
||||
{
|
||||
CompactionJobInfo info{};
|
||||
info.num_l0_files = num_l0_files;
|
||||
BuildCompactionJobInfo(cfd, c, st, job_stats, job_id, &info);
|
||||
for (const auto& listener : immutable_db_options_.listeners) {
|
||||
listener->OnCompactionBegin(this, info);
|
||||
@@ -1724,11 +1727,13 @@ void DBImpl::NotifyOnCompactionCompleted(
|
||||
return;
|
||||
}
|
||||
|
||||
int num_l0_files = cfd->current()->storage_info()->NumLevelFiles(0);
|
||||
// release lock while notifying events
|
||||
mutex_.Unlock();
|
||||
TEST_SYNC_POINT("DBImpl::NotifyOnCompactionCompleted::UnlockMutex");
|
||||
{
|
||||
CompactionJobInfo info{};
|
||||
info.num_l0_files = num_l0_files;
|
||||
BuildCompactionJobInfo(cfd, c, st, compaction_job_stats, job_id, &info);
|
||||
for (const auto& listener : immutable_db_options_.listeners) {
|
||||
listener->OnCompactionCompleted(this, info);
|
||||
@@ -3798,6 +3803,8 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
compaction_job_stats.num_input_files = c->num_input_files(0);
|
||||
// Trivial moves do not get compacted remotely
|
||||
compaction_job_stats.is_remote_compaction = false;
|
||||
compaction_job_stats.num_input_files_trivially_moved =
|
||||
compaction_job_stats.num_input_files;
|
||||
|
||||
NotifyOnCompactionBegin(c->column_family_data(), c.get(), status,
|
||||
compaction_job_stats, job_context->job_id);
|
||||
@@ -4273,9 +4280,10 @@ void DBImpl::BuildCompactionJobInfo(
|
||||
// for superversion_to_free
|
||||
|
||||
void DBImpl::InstallSuperVersionAndScheduleWork(
|
||||
ColumnFamilyData* cfd, SuperVersionContext* sv_context) {
|
||||
ColumnFamilyData* cfd, SuperVersionContext* sv_context,
|
||||
std::optional<std::shared_ptr<SeqnoToTimeMapping>>
|
||||
new_seqno_to_time_mapping) {
|
||||
mutex_.AssertHeld();
|
||||
const auto& mutable_cf_options = cfd->GetLatestMutableCFOptions();
|
||||
|
||||
// Update max_total_in_memory_state_
|
||||
size_t old_memtable_size = 0;
|
||||
@@ -4289,7 +4297,8 @@ void DBImpl::InstallSuperVersionAndScheduleWork(
|
||||
if (UNLIKELY(sv_context->new_superversion == nullptr)) {
|
||||
sv_context->NewSuperVersion();
|
||||
}
|
||||
cfd->InstallSuperVersion(sv_context, mutable_cf_options);
|
||||
cfd->InstallSuperVersion(sv_context, &mutex_,
|
||||
std::move(new_seqno_to_time_mapping));
|
||||
|
||||
// There may be a small data race here. The snapshot tricking bottommost
|
||||
// compaction may already be released here. But assuming there will always be
|
||||
@@ -4316,9 +4325,10 @@ void DBImpl::InstallSuperVersionAndScheduleWork(
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
|
||||
// Update max_total_in_memory_state_
|
||||
max_total_in_memory_state_ = max_total_in_memory_state_ - old_memtable_size +
|
||||
mutable_cf_options.write_buffer_size *
|
||||
mutable_cf_options.max_write_buffer_number;
|
||||
max_total_in_memory_state_ =
|
||||
max_total_in_memory_state_ - old_memtable_size +
|
||||
cfd->GetLatestMutableCFOptions().write_buffer_size *
|
||||
cfd->GetLatestMutableCFOptions().max_write_buffer_number;
|
||||
}
|
||||
|
||||
// ShouldPurge is called by FindObsoleteFiles when doing a full scan,
|
||||
|
||||
@@ -379,10 +379,13 @@ void DBImpl::TEST_VerifyNoObsoleteFilesCached(
|
||||
uint64_t file_number;
|
||||
GetUnaligned(reinterpret_cast<const uint64_t*>(key.data()), &file_number);
|
||||
// Assert file is in live/quarantined set
|
||||
if (live_and_quar_files.find(file_number) == live_and_quar_files.end()) {
|
||||
bool cached_file_is_live_or_quar =
|
||||
live_and_quar_files.find(file_number) != live_and_quar_files.end();
|
||||
if (!cached_file_is_live_or_quar) {
|
||||
// Fail with useful info
|
||||
std::cerr << "File " << file_number << " is not live nor quarantined"
|
||||
<< std::endl;
|
||||
assert(false);
|
||||
assert(cached_file_is_live_or_quar);
|
||||
}
|
||||
};
|
||||
table_cache_->ApplyToAllEntries(fn, {});
|
||||
|
||||
@@ -368,6 +368,7 @@ void DBImpl::DeleteObsoleteFileImpl(int job_id, const std::string& fname,
|
||||
FileType type, uint64_t number) {
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::DeleteObsoleteFileImpl::BeforeDeletion",
|
||||
const_cast<std::string*>(&fname));
|
||||
IGNORE_STATUS_IF_ERROR(Status::IOError());
|
||||
|
||||
Status file_deletion_status;
|
||||
if (type == kTableFile || type == kBlobFile || type == kWalFile) {
|
||||
@@ -423,6 +424,8 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
// FindObsoleteFiles() should've populated this so nonzero
|
||||
assert(state.manifest_file_number != 0);
|
||||
|
||||
IGNORE_STATUS_IF_ERROR(Status::IOError());
|
||||
|
||||
// Now, convert lists to unordered sets, WITHOUT mutex held; set is slow.
|
||||
std::unordered_set<uint64_t> sst_live_set(state.sst_live.begin(),
|
||||
state.sst_live.end());
|
||||
|
||||
+61
-15
@@ -35,8 +35,8 @@ Options SanitizeOptions(const std::string& dbname, const Options& src,
|
||||
auto db_options =
|
||||
SanitizeOptions(dbname, DBOptions(src), read_only, logger_creation_s);
|
||||
ImmutableDBOptions immutable_db_options(db_options);
|
||||
auto cf_options =
|
||||
SanitizeOptions(immutable_db_options, ColumnFamilyOptions(src));
|
||||
auto cf_options = SanitizeCfOptions(immutable_db_options, read_only,
|
||||
ColumnFamilyOptions(src));
|
||||
return Options(db_options, cf_options);
|
||||
}
|
||||
|
||||
@@ -1203,6 +1203,13 @@ Status DBImpl::ProcessLogFiles(
|
||||
PredecessorWALInfo predecessor_wal_info;
|
||||
|
||||
for (auto wal_number : wal_numbers) {
|
||||
// Detecting early break on the next iteration after `wal_number` has been
|
||||
// advanced since this `wal_number` doesn't affect follow-up handling after
|
||||
// breaking out of the for loop.
|
||||
if (!status.ok()) {
|
||||
break;
|
||||
}
|
||||
SequenceNumber prev_next_sequence = *next_sequence;
|
||||
if (status.ok()) {
|
||||
status = ProcessLogFile(
|
||||
wal_number, min_wal_number, is_retry, read_only, job_id,
|
||||
@@ -1210,6 +1217,10 @@ Status DBImpl::ProcessLogFiles(
|
||||
&stop_replay_by_wal_filter, &corrupted_wal_number,
|
||||
corrupted_wal_found, version_edits, &flushed, predecessor_wal_info);
|
||||
}
|
||||
if (status.ok()) {
|
||||
status = CheckSeqnoNotSetBackDuringRecovery(prev_next_sequence,
|
||||
*next_sequence);
|
||||
}
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
@@ -1317,6 +1328,7 @@ Status DBImpl::ProcessLogFile(
|
||||
}
|
||||
|
||||
// FIXME(hx235): consolidate `process_status` and `status`
|
||||
SequenceNumber prev_next_sequence = *next_sequence;
|
||||
Status process_status = ProcessLogRecord(
|
||||
record, reader, running_ts_sz, wal_number, fname, read_only, job_id,
|
||||
logFileDropped, &reporter, &record_checksum, &last_seqno_observed,
|
||||
@@ -1325,6 +1337,12 @@ Status DBImpl::ProcessLogFile(
|
||||
|
||||
if (!process_status.ok()) {
|
||||
return process_status;
|
||||
} else if (Status seqno_check_status = CheckSeqnoNotSetBackDuringRecovery(
|
||||
prev_next_sequence, *next_sequence);
|
||||
!seqno_check_status.ok()) {
|
||||
// Sequence number being set back indicates a serious software bug, the DB
|
||||
// should not be opened in this case.
|
||||
return seqno_check_status;
|
||||
} else if (*stop_replay_for_corruption) {
|
||||
break;
|
||||
}
|
||||
@@ -1863,6 +1881,20 @@ Status DBImpl::MaybeFlushFinalMemtableOrRestoreActiveLogFiles(
|
||||
return status;
|
||||
}
|
||||
|
||||
Status DBImpl::CheckSeqnoNotSetBackDuringRecovery(
|
||||
SequenceNumber prev_next_seqno, SequenceNumber current_next_seqno) {
|
||||
if (prev_next_seqno == kMaxSequenceNumber ||
|
||||
prev_next_seqno <= current_next_seqno) {
|
||||
return Status::OK();
|
||||
}
|
||||
std::string msg =
|
||||
"Sequence number is being set backwards during recovery, this is likely "
|
||||
"a software bug or a data corruption. Prev next seqno: " +
|
||||
std::to_string(prev_next_seqno) +
|
||||
" , current next seqno: " + std::to_string(current_next_seqno);
|
||||
return Status::Corruption(msg);
|
||||
}
|
||||
|
||||
void DBImpl::FinishLogFilesRecovery(int job_id, const Status& status) {
|
||||
event_logger_.Log() << "job" << job_id << "event"
|
||||
<< (status.ok() ? "recovery_finished" : "recovery_failed")
|
||||
@@ -1995,8 +2027,9 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
meta.oldest_ancester_time = current_time;
|
||||
meta.epoch_number = cfd->NewEpochNumber();
|
||||
{
|
||||
auto write_hint =
|
||||
cfd->current()->storage_info()->CalculateSSTWriteHint(/*level=*/0);
|
||||
auto write_hint = cfd->current()->storage_info()->CalculateSSTWriteHint(
|
||||
/*level=*/0,
|
||||
immutable_db_options_.calculate_sst_write_lifetime_hint_set);
|
||||
mutex_.Unlock();
|
||||
|
||||
SequenceNumber earliest_write_conflict_snapshot;
|
||||
@@ -2264,6 +2297,7 @@ IOStatus DBImpl::CreateWAL(const WriteOptions& write_options,
|
||||
BuildDBOptions(immutable_db_options_, mutable_db_options_);
|
||||
FileOptions opt_file_options =
|
||||
fs_->OptimizeForLogWrite(file_options_, db_options);
|
||||
opt_file_options.write_hint = CalculateWALWriteHint();
|
||||
// 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;
|
||||
@@ -2285,7 +2319,9 @@ IOStatus DBImpl::CreateWAL(const WriteOptions& write_options,
|
||||
}
|
||||
|
||||
if (io_s.ok()) {
|
||||
lfile->SetWriteLifeTimeHint(CalculateWALWriteHint());
|
||||
// Subsequent attempts to override the hint via SetWriteLifeTimeHint
|
||||
// with the very same value will be ignored by the fs.
|
||||
lfile->SetWriteLifeTimeHint(opt_file_options.write_hint);
|
||||
lfile->SetPreallocationBlockSize(preallocate_block_size);
|
||||
|
||||
const auto& listeners = immutable_db_options_.listeners;
|
||||
@@ -2340,9 +2376,11 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
handles->clear();
|
||||
|
||||
size_t max_write_buffer_size = 0;
|
||||
MinAndMaxPreserveSeconds preserve_info;
|
||||
for (const auto& cf : column_families) {
|
||||
max_write_buffer_size =
|
||||
std::max(max_write_buffer_size, cf.options.write_buffer_size);
|
||||
preserve_info.Combine(cf.options);
|
||||
}
|
||||
|
||||
auto impl = std::make_unique<DBImpl>(db_options, dbname, seq_per_batch,
|
||||
@@ -2475,6 +2513,12 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
s = impl->InitPersistStatsColumnFamily();
|
||||
}
|
||||
|
||||
// After reaching the post-recovery seqno but before creating SuperVersions
|
||||
// ensure seqno to time mapping is pre-populated as needed.
|
||||
if (s.ok() && recovery_ctx.is_new_db_ && preserve_info.IsEnabled()) {
|
||||
impl->PrepopulateSeqnoToTimeMapping(preserve_info);
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
// set column family handles
|
||||
for (const auto& cf : column_families) {
|
||||
@@ -2484,6 +2528,9 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
handles->push_back(
|
||||
new ColumnFamilyHandleImpl(cfd, impl.get(), &impl->mutex_));
|
||||
impl->NewThreadStatusCfInfo(cfd);
|
||||
SuperVersionContext sv_context(/* create_superversion */ true);
|
||||
impl->InstallSuperVersionForConfigChange(cfd, &sv_context);
|
||||
sv_context.Clean();
|
||||
} else {
|
||||
if (db_options.create_missing_column_families) {
|
||||
// missing column family, create it
|
||||
@@ -2491,6 +2538,7 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
impl->mutex_.Unlock();
|
||||
// NOTE: the work normally done in WrapUpCreateColumnFamilies will
|
||||
// be done separately below.
|
||||
// This includes InstallSuperVersionForConfigChange.
|
||||
s = impl->CreateColumnFamilyImpl(read_options, write_options,
|
||||
cf.options, cf.name, &handle);
|
||||
impl->mutex_.Lock();
|
||||
@@ -2507,15 +2555,14 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
SuperVersionContext sv_context(/* create_superversion */ true);
|
||||
for (auto cfd : *impl->versions_->GetColumnFamilySet()) {
|
||||
impl->InstallSuperVersionAndScheduleWork(cfd, &sv_context);
|
||||
}
|
||||
sv_context.Clean();
|
||||
}
|
||||
|
||||
if (s.ok() && impl->immutable_db_options_.persist_stats_to_disk) {
|
||||
// Install SuperVersion for hidden column family
|
||||
assert(impl->persist_stats_cf_handle_);
|
||||
assert(impl->persist_stats_cf_handle_->cfd());
|
||||
SuperVersionContext sv_context(/* create_superversion */ true);
|
||||
impl->InstallSuperVersionForConfigChange(
|
||||
impl->persist_stats_cf_handle_->cfd(), &sv_context);
|
||||
sv_context.Clean();
|
||||
// try to read format version
|
||||
s = impl->PersistentStatsProcessFormatVersion();
|
||||
}
|
||||
@@ -2624,8 +2671,7 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
s = impl->StartPeriodicTaskScheduler();
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = impl->RegisterRecordSeqnoTimeWorker(read_options, write_options,
|
||||
recovery_ctx.is_new_db_);
|
||||
s = impl->RegisterRecordSeqnoTimeWorker();
|
||||
}
|
||||
impl->options_mutex_.Unlock();
|
||||
if (s.ok()) {
|
||||
|
||||
@@ -155,6 +155,29 @@ class DBImplReadOnly : public DBImpl {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
using DB::CreateColumnFamily;
|
||||
using DBImpl::CreateColumnFamily;
|
||||
Status CreateColumnFamily(const ColumnFamilyOptions& /*cf_options*/,
|
||||
const std::string& /*column_family*/,
|
||||
ColumnFamilyHandle** /*handle*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
using DB::CreateColumnFamilies;
|
||||
using DBImpl::CreateColumnFamilies;
|
||||
Status CreateColumnFamilies(
|
||||
const ColumnFamilyOptions& /*cf_options*/,
|
||||
const std::vector<std::string>& /*column_family_names*/,
|
||||
std::vector<ColumnFamilyHandle*>* /*handles*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
Status CreateColumnFamilies(
|
||||
const std::vector<ColumnFamilyDescriptor>& /*column_families*/,
|
||||
std::vector<ColumnFamilyHandle*>* /*handles*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
// FIXME: some missing overrides for more "write" functions
|
||||
|
||||
protected:
|
||||
|
||||
@@ -971,7 +971,7 @@ TEST_F(DBMergeOperatorTest, MaxSuccessiveMergesBaseValues) {
|
||||
|
||||
// No base value
|
||||
{
|
||||
constexpr char key[] = "key1";
|
||||
const std::string key = "key1";
|
||||
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), db_->DefaultColumnFamily(), key, foo));
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), db_->DefaultColumnFamily(), key, bar));
|
||||
@@ -994,7 +994,7 @@ TEST_F(DBMergeOperatorTest, MaxSuccessiveMergesBaseValues) {
|
||||
|
||||
// Plain base value
|
||||
{
|
||||
constexpr char key[] = "key2";
|
||||
const std::string key = "key2";
|
||||
|
||||
ASSERT_OK(db_->Put(WriteOptions(), db_->DefaultColumnFamily(), key, foo));
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), db_->DefaultColumnFamily(), key, bar));
|
||||
@@ -1019,7 +1019,7 @@ TEST_F(DBMergeOperatorTest, MaxSuccessiveMergesBaseValues) {
|
||||
|
||||
// Wide-column base value
|
||||
{
|
||||
constexpr char key[] = "key3";
|
||||
const std::string key = "key3";
|
||||
const WideColumns columns{{kDefaultWideColumnName, foo}, {bar, baz}};
|
||||
|
||||
ASSERT_OK(db_->PutEntity(WriteOptions(), db_->DefaultColumnFamily(), key,
|
||||
|
||||
@@ -70,7 +70,8 @@ class DBOptionsTest : public DBTestBase {
|
||||
options.env = env_;
|
||||
ImmutableDBOptions db_options(options);
|
||||
test::RandomInitCFOptions(&options, options, rnd);
|
||||
auto sanitized_options = SanitizeOptions(db_options, options);
|
||||
auto sanitized_options =
|
||||
SanitizeCfOptions(db_options, /*read_only*/ false, options);
|
||||
auto opt_map = GetMutableCFOptionsMap(sanitized_options);
|
||||
delete options.compaction_filter;
|
||||
return opt_map;
|
||||
|
||||
@@ -160,6 +160,7 @@ TEST_F(DBSecondaryTest, NonExistingDb) {
|
||||
TEST_F(DBSecondaryTest, ReopenAsSecondary) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.preserve_internal_time_seconds = 300;
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("foo", "foo_value"));
|
||||
ASSERT_OK(Put("bar", "bar_value"));
|
||||
|
||||
@@ -229,6 +229,56 @@ TEST_F(DBTablePropertiesTest, CreateOnDeletionCollectorFactory) {
|
||||
ASSERT_EQ(0.5, del_factory->GetDeletionRatio());
|
||||
}
|
||||
|
||||
TEST_F(DBTablePropertiesTest, GetPropertiesOfTablesByLevelTest) {
|
||||
Random rnd(202);
|
||||
Options options;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.create_if_missing = true;
|
||||
options.write_buffer_size = 4096;
|
||||
options.max_write_buffer_number = 2;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.level0_slowdown_writes_trigger = 2;
|
||||
options.level0_stop_writes_trigger = 2;
|
||||
options.target_file_size_base = 2048;
|
||||
options.max_bytes_for_level_base = 40960;
|
||||
options.max_bytes_for_level_multiplier = 4;
|
||||
options.hard_pending_compaction_bytes_limit = 16 * 1024;
|
||||
options.num_levels = 8;
|
||||
options.env = env_;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// build a decent LSM
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
EXPECT_OK(Put(test::RandomKey(&rnd, 5), rnd.RandomString(102)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
if (NumTableFilesAtLevel(0) == 0) {
|
||||
EXPECT_OK(Put(test::RandomKey(&rnd, 5), rnd.RandomString(102)));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
ASSERT_OK(db_->PauseBackgroundWork());
|
||||
|
||||
// Ensure that we have at least L0, L1 and L2
|
||||
ASSERT_GT(NumTableFilesAtLevel(0), 0);
|
||||
ASSERT_GT(NumTableFilesAtLevel(1), 0);
|
||||
ASSERT_GT(NumTableFilesAtLevel(2), 0);
|
||||
ColumnFamilyMetaData cf_meta;
|
||||
db_->GetColumnFamilyMetaData(&cf_meta);
|
||||
std::vector<std::unique_ptr<TablePropertiesCollection>> levels_props;
|
||||
ASSERT_OK(db_->GetPropertiesOfTablesByLevel(db_->DefaultColumnFamily(),
|
||||
&levels_props));
|
||||
for (int i = 0; i < 8; i++) {
|
||||
const std::unique_ptr<TablePropertiesCollection>& level_props =
|
||||
levels_props[i];
|
||||
ASSERT_EQ(level_props->size(), cf_meta.levels[i].files.size());
|
||||
}
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
// Test params:
|
||||
// 1) whether to enable user-defined timestamps
|
||||
class DBTablePropertiesInRangeTest : public DBTestBase,
|
||||
@@ -292,7 +342,7 @@ class DBTablePropertiesInRangeTest : public DBTestBase,
|
||||
keys.reserve(range_size * 2);
|
||||
for (auto& r : ranges) {
|
||||
auto [start, limit] = MaybeAddTimestampsToRange(
|
||||
&r.start, &r.limit, ts_sz, &keys.emplace_back(), &keys.emplace_back(),
|
||||
r.start, r.limit, ts_sz, &keys.emplace_back(), &keys.emplace_back(),
|
||||
/*exclusive_end=*/false);
|
||||
EXPECT_TRUE(start.has_value());
|
||||
EXPECT_TRUE(limit.has_value());
|
||||
|
||||
+32
-2
@@ -59,11 +59,13 @@
|
||||
#include "rocksdb/utilities/checkpoint.h"
|
||||
#include "rocksdb/utilities/optimistic_transaction_db.h"
|
||||
#include "rocksdb/utilities/write_batch_with_index.h"
|
||||
#include "table/block_based/block_based_table_factory.h"
|
||||
#include "table/mock_table.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/compression.h"
|
||||
#include "util/defer.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/random.h"
|
||||
#include "util/rate_limiter_impl.h"
|
||||
@@ -3180,6 +3182,15 @@ class ModelDB : public DB {
|
||||
return Status();
|
||||
}
|
||||
|
||||
using DB::GetPropertiesOfTablesByLevel;
|
||||
Status GetPropertiesOfTablesByLevel(
|
||||
ColumnFamilyHandle* /* column_family */,
|
||||
std::vector<
|
||||
std::unique_ptr<TablePropertiesCollection>>* /* props_by_level */)
|
||||
override {
|
||||
return Status();
|
||||
}
|
||||
|
||||
using DB::KeyMayExist;
|
||||
bool KeyMayExist(const ReadOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/, const Slice& /*key*/,
|
||||
@@ -5400,8 +5411,7 @@ TEST_F(DBTest, DynamicLevelCompressionPerLevel) {
|
||||
|
||||
for (const auto& file : cf_meta.levels[4].files) {
|
||||
listener->SetExpectedFileName(dbname_ + file.name);
|
||||
Slice start(file.smallestkey), limit(file.largestkey);
|
||||
const RangePtr ranges(&start, &limit);
|
||||
const RangeOpt ranges(file.smallestkey, file.largestkey);
|
||||
// Given verification from above, we're guaranteed that by deleting all the
|
||||
// files in [<smallestkey>, <largestkey>] range, we're effectively deleting
|
||||
// that very single file and nothing more.
|
||||
@@ -5453,6 +5463,7 @@ TEST_F(DBTest, DynamicLevelCompressionPerLevel2) {
|
||||
options.max_bytes_for_level_multiplier = 8;
|
||||
options.max_background_compactions = 1;
|
||||
options.num_levels = 5;
|
||||
options.compaction_verify_record_count = false;
|
||||
std::shared_ptr<mock::MockTableFactory> mtf(new mock::MockTableFactory);
|
||||
options.table_factory = mtf;
|
||||
|
||||
@@ -6084,6 +6095,11 @@ TEST_F(DBTest, L0L1L2AndUpHitCounter) {
|
||||
}
|
||||
|
||||
TEST_F(DBTest, EncodeDecompressedBlockSizeTest) {
|
||||
bool& allow_unsupported_fv =
|
||||
BlockBasedTableFactory::AllowUnsupportedFormatVersion();
|
||||
SaveAndRestore guard(&allow_unsupported_fv);
|
||||
ASSERT_FALSE(allow_unsupported_fv);
|
||||
|
||||
// iter 0 -- zlib
|
||||
// iter 1 -- bzip2
|
||||
// iter 2 -- lz4
|
||||
@@ -6106,7 +6122,16 @@ TEST_F(DBTest, EncodeDecompressedBlockSizeTest) {
|
||||
table_options.format_version = first_table_version;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(10));
|
||||
Options options = CurrentOptions();
|
||||
|
||||
// Hack to generate old files (checked in factory construction)
|
||||
allow_unsupported_fv = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
ASSERT_EQ(options.table_factory->GetOptions<BlockBasedTableOptions>()
|
||||
->format_version,
|
||||
first_table_version);
|
||||
// Able to read old files without the hack
|
||||
allow_unsupported_fv = false;
|
||||
|
||||
options.create_if_missing = true;
|
||||
options.compression = comp;
|
||||
DestroyAndReopen(options);
|
||||
@@ -6118,9 +6143,14 @@ TEST_F(DBTest, EncodeDecompressedBlockSizeTest) {
|
||||
// compressible string
|
||||
ASSERT_OK(Put(Key(i), rnd.RandomString(128) + std::string(128, 'a')));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
table_options.format_version = first_table_version == 1 ? 2 : 1;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
// format_version (for writing) is sanitized to minimum supported
|
||||
ASSERT_EQ(options.table_factory->GetOptions<BlockBasedTableOptions>()
|
||||
->format_version,
|
||||
BlockBasedTableFactory::kMinSupportedFormatVersion);
|
||||
Reopen(options);
|
||||
for (int i = 0; i < kNumKeysWritten; ++i) {
|
||||
auto r = Get(Key(i));
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include "cache/cache_reservation_manager.h"
|
||||
#include "db/forward_iterator.h"
|
||||
#include "env/fs_readonly.h"
|
||||
#include "env/mock_env.h"
|
||||
#include "port/lang.h"
|
||||
#include "rocksdb/cache.h"
|
||||
@@ -716,6 +717,17 @@ Status DBTestBase::ReadOnlyReopen(const Options& options) {
|
||||
return DB::OpenForReadOnly(options, dbname_, &db_);
|
||||
}
|
||||
|
||||
Status DBTestBase::EnforcedReadOnlyReopen(const Options& options) {
|
||||
Close();
|
||||
Options options_copy = options;
|
||||
MaybeInstallTimeElapseOnlySleep(options_copy);
|
||||
auto fs_read_only =
|
||||
std::make_shared<ReadOnlyFileSystem>(env_->GetFileSystem());
|
||||
env_read_only_ = std::make_shared<CompositeEnvWrapper>(env_, fs_read_only);
|
||||
options_copy.env = env_read_only_.get();
|
||||
return DB::OpenForReadOnly(options_copy, dbname_, &db_);
|
||||
}
|
||||
|
||||
Status DBTestBase::TryReopen(const Options& options) {
|
||||
Close();
|
||||
last_options_.table_factory.reset();
|
||||
|
||||
@@ -1062,6 +1062,7 @@ class DBTestBase : public testing::Test {
|
||||
MockEnv* mem_env_;
|
||||
Env* encrypted_env_;
|
||||
SpecialEnv* env_;
|
||||
std::shared_ptr<Env> env_read_only_;
|
||||
std::shared_ptr<Env> env_guard_;
|
||||
DB* db_;
|
||||
std::vector<ColumnFamilyHandle*> handles_;
|
||||
@@ -1178,6 +1179,9 @@ class DBTestBase : public testing::Test {
|
||||
|
||||
Status ReadOnlyReopen(const Options& options);
|
||||
|
||||
// With a filesystem wrapper that fails on attempted write
|
||||
Status EnforcedReadOnlyReopen(const Options& options);
|
||||
|
||||
Status TryReopen(const Options& options);
|
||||
|
||||
bool IsDirectIOSupported();
|
||||
|
||||
+8
-6
@@ -83,6 +83,8 @@ extern const ValueType kValueTypeForSeekForPrev;
|
||||
|
||||
// A range of user keys used internally by RocksDB. Also see `Range` used by
|
||||
// public APIs.
|
||||
// TODO: merge with Range in pubic API, but this is generally inclusive limit
|
||||
// and it is maybe exclusive limit
|
||||
struct UserKeyRange {
|
||||
// In case of user_defined timestamp, if enabled, `start` and `limit` should
|
||||
// include user_defined timestamps.
|
||||
@@ -93,18 +95,17 @@ struct UserKeyRange {
|
||||
UserKeyRange(const Slice& s, const Slice& l) : start(s), limit(l) {}
|
||||
};
|
||||
|
||||
// A range of user keys used internally by RocksDB. Also see `RangePtr` used by
|
||||
// A range of user keys used internally by RocksDB. Also see `RangeOpt` used by
|
||||
// public APIs.
|
||||
struct UserKeyRangePtr {
|
||||
struct UserKeyRangeOpt {
|
||||
// In case of user_defined timestamp, if enabled, `start` and `limit` should
|
||||
// point to key with timestamp part.
|
||||
// An optional range start, if missing, indicating a start before all keys.
|
||||
std::optional<Slice> start;
|
||||
OptSlice start;
|
||||
// An optional range end, if missing, indicating an end after all keys.
|
||||
std::optional<Slice> limit;
|
||||
OptSlice limit;
|
||||
|
||||
UserKeyRangePtr(const std::optional<Slice>& s, const std::optional<Slice>& l)
|
||||
: start(s), limit(l) {}
|
||||
UserKeyRangeOpt(const OptSlice& s, const OptSlice& l) : start(s), limit(l) {}
|
||||
};
|
||||
|
||||
// Checks whether a type is an inline value type
|
||||
@@ -469,6 +470,7 @@ class InternalKey {
|
||||
|
||||
Slice user_key() const { return ExtractUserKey(rep_); }
|
||||
size_t size() const { return rep_.size(); }
|
||||
bool unset() const { return rep_.empty(); }
|
||||
|
||||
void Set(const Slice& _user_key, SequenceNumber s, ValueType t) {
|
||||
SetFrom(ParsedInternalKey(_user_key, s, t));
|
||||
|
||||
+2
-1
@@ -57,7 +57,8 @@ Status GetFileChecksumsFromCurrentManifest(FileSystem* fs,
|
||||
}
|
||||
assert(checksum_list);
|
||||
|
||||
const ReadOptions read_options(Env::IOActivity::kReadManifest);
|
||||
const ReadOptions read_options(
|
||||
Env::IOActivity::kGetFileChecksumsFromCurrentManifest);
|
||||
checksum_list->reset();
|
||||
|
||||
std::unique_ptr<SequentialFileReader> file_reader;
|
||||
|
||||
@@ -1954,21 +1954,44 @@ TEST_F(ExternalSSTFileBasicTest, OverlappingFiles) {
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
std::string file3 = sst_files_dir_ + "file3.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file3));
|
||||
ASSERT_OK(sst_file_writer.Put("j", "j1"));
|
||||
ASSERT_OK(sst_file_writer.Put("k", "k1"));
|
||||
ASSERT_OK(sst_file_writer.Put("m", "m1"));
|
||||
ExternalSstFileInfo file3_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file3_info));
|
||||
files.push_back(std::move(file3));
|
||||
}
|
||||
|
||||
// This could be ingested to the same level as file3 and file4, but the
|
||||
// greedy/simple overlap check relegates it to a later level
|
||||
{
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
std::string file4 = sst_files_dir_ + "file4.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file4));
|
||||
ASSERT_OK(sst_file_writer.Put("j", "j1"));
|
||||
ExternalSstFileInfo file4_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file4_info));
|
||||
files.push_back(std::move(file4));
|
||||
}
|
||||
|
||||
{
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
std::string file5 = sst_files_dir_ + "file5.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file5));
|
||||
ASSERT_OK(sst_file_writer.Put("i", "i3"));
|
||||
ExternalSstFileInfo file5_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file5_info));
|
||||
files.push_back(std::move(file5));
|
||||
}
|
||||
|
||||
IngestExternalFileOptions ifo;
|
||||
ifo.allow_global_seqno = false;
|
||||
ASSERT_NOK(db_->IngestExternalFile(files, ifo));
|
||||
ifo.allow_global_seqno = true;
|
||||
ASSERT_OK(db_->IngestExternalFile(files, ifo));
|
||||
ASSERT_EQ(Get("a"), "a1");
|
||||
ASSERT_EQ(Get("i"), "i2");
|
||||
ASSERT_EQ(Get("i"), "i3");
|
||||
ASSERT_EQ(Get("j"), "j1");
|
||||
ASSERT_EQ(Get("k"), "k1");
|
||||
ASSERT_EQ(Get("m"), "m1");
|
||||
|
||||
int total_keys = 0;
|
||||
@@ -1979,10 +2002,11 @@ TEST_F(ExternalSSTFileBasicTest, OverlappingFiles) {
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
delete iter;
|
||||
ASSERT_EQ(total_keys, 4);
|
||||
ASSERT_EQ(total_keys, 5);
|
||||
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(6));
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(5));
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(4));
|
||||
}
|
||||
|
||||
class CompactionJobStatsCheckerForFilteredFiles : public EventListener {
|
||||
@@ -2686,10 +2710,11 @@ TEST_F(ExternalSSTFileBasicTest, FailIfNotBottommostLevelAndDisallowMemtable) {
|
||||
|
||||
// Ingest with snapshot consistency
|
||||
std::string file_path = sst_files_dir_ + std::to_string(1);
|
||||
std::string file_path2 = sst_files_dir_ + std::to_string(2);
|
||||
SstFileWriter sfw(EnvOptions(), options);
|
||||
|
||||
ASSERT_OK(sfw.Open(file_path));
|
||||
ASSERT_OK(sfw.Put("b", "dontcare"));
|
||||
ASSERT_OK(sfw.Put("b", "0"));
|
||||
ASSERT_OK(sfw.Finish());
|
||||
|
||||
{
|
||||
@@ -2700,6 +2725,7 @@ TEST_F(ExternalSSTFileBasicTest, FailIfNotBottommostLevelAndDisallowMemtable) {
|
||||
ifo.snapshot_consistency = true;
|
||||
ASSERT_OK(db_->IngestExternalFile(handles_[0], {file_path}, ifo));
|
||||
}
|
||||
ASSERT_EQ(Get(0, "b"), "0");
|
||||
|
||||
// Test level compaction
|
||||
options.compaction_style = CompactionStyle::kCompactionStyleLevel;
|
||||
@@ -2721,8 +2747,6 @@ TEST_F(ExternalSSTFileBasicTest, FailIfNotBottommostLevelAndDisallowMemtable) {
|
||||
EXPECT_EQ(Put(1, "a", "1").code(), Status::Code::kInvalidArgument);
|
||||
|
||||
// Use ingestion to get to the same state as above
|
||||
std::string file_path2 = sst_files_dir_ + std::to_string(2);
|
||||
|
||||
ASSERT_OK(sfw.Open(file_path2));
|
||||
ASSERT_OK(sfw.Put("a", "1"));
|
||||
ASSERT_OK(sfw.Put("c", "3"));
|
||||
@@ -2735,16 +2759,216 @@ TEST_F(ExternalSSTFileBasicTest, FailIfNotBottommostLevelAndDisallowMemtable) {
|
||||
ASSERT_OK(sfw.Finish());
|
||||
ASSERT_OK(db_->IngestExternalFile(handles_[1], {file_path2}, {}));
|
||||
}
|
||||
ASSERT_EQ(Get(1, "a"), "1");
|
||||
ASSERT_EQ(Get(1, "b"), "2");
|
||||
ASSERT_EQ(Get(1, "c"), "3");
|
||||
ASSERT_EQ(Get(1, "d"), "4");
|
||||
|
||||
{
|
||||
// Test fail_if_not_bottommost_level, which fails if there's any overlap
|
||||
// anywhere, even with snapshot_consistency=false
|
||||
IngestExternalFileOptions ifo;
|
||||
ASSERT_FALSE(ifo.fail_if_not_bottommost_level);
|
||||
ifo.fail_if_not_bottommost_level = true;
|
||||
ifo.snapshot_consistency = false;
|
||||
// Fails with overlap on earlier level
|
||||
Status s = db_->IngestExternalFile(handles_[1], {file_path}, ifo);
|
||||
ASSERT_EQ(s.code(), Status::Code::kTryAgain);
|
||||
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
ASSERT_OK(db_->CompactRange(cro, handles_[1], nullptr, nullptr));
|
||||
|
||||
// Fails with overlap on last level
|
||||
s = db_->IngestExternalFile(handles_[1], {file_path}, ifo);
|
||||
ASSERT_EQ(s.code(), Status::Code::kTryAgain);
|
||||
|
||||
// No change to data
|
||||
ASSERT_EQ(Get(1, "a"), "1");
|
||||
ASSERT_EQ(Get(1, "b"), "2");
|
||||
ASSERT_EQ(Get(1, "c"), "3");
|
||||
ASSERT_EQ(Get(1, "d"), "4");
|
||||
}
|
||||
|
||||
if (!disallow_memtable) {
|
||||
// Test allow_blocking_flush=false (fail because of memtable overlap)
|
||||
IngestExternalFileOptions ifo;
|
||||
ifo.fail_if_not_bottommost_level = true;
|
||||
const Status s = db_->IngestExternalFile(handles_[1], {file_path}, ifo);
|
||||
ASSERT_TRUE(s.IsTryAgain());
|
||||
ASSERT_TRUE(ifo.allow_blocking_flush);
|
||||
ifo.allow_blocking_flush = false;
|
||||
ASSERT_OK(Put(1, "b", "42"));
|
||||
Status s = db_->IngestExternalFile(handles_[1], {file_path}, ifo);
|
||||
ASSERT_EQ(s.code(), Status::Code::kInvalidArgument);
|
||||
|
||||
ASSERT_EQ(Get(1, "a"), "1");
|
||||
ASSERT_EQ(Get(1, "b"), "42");
|
||||
ASSERT_EQ(Get(1, "c"), "3");
|
||||
ASSERT_EQ(Get(1, "d"), "4");
|
||||
|
||||
// Revert state
|
||||
ASSERT_OK(Put(1, "b", "2"));
|
||||
ASSERT_OK(Flush(1));
|
||||
}
|
||||
|
||||
{
|
||||
// Test atomic_replace_range
|
||||
IngestExternalFileArg arg;
|
||||
arg.column_family = handles_[1];
|
||||
arg.external_files = {file_path};
|
||||
arg.atomic_replace_range = {{"a", "zzz"}};
|
||||
|
||||
// start with some failure cases
|
||||
// TODO: support snapshot consistency with tombstone file
|
||||
ASSERT_TRUE(arg.options.snapshot_consistency);
|
||||
Status s = db_->IngestExternalFiles({arg});
|
||||
ASSERT_EQ(s.code(), Status::Code::kNotSupported);
|
||||
|
||||
ASSERT_EQ(Get(1, "a"), "1");
|
||||
ASSERT_EQ(Get(1, "b"), "2");
|
||||
ASSERT_EQ(Get(1, "c"), "3");
|
||||
ASSERT_EQ(Get(1, "d"), "4");
|
||||
|
||||
arg.options.snapshot_consistency = false;
|
||||
// Can usually be used with atomic_replace_range and
|
||||
// snapshot_consistency=false, except it requires no input overlap
|
||||
arg.options.fail_if_not_bottommost_level = true;
|
||||
|
||||
// one-sided ranges not yet supported
|
||||
arg.atomic_replace_range = {{{}, "zzz"}};
|
||||
s = db_->IngestExternalFiles({arg});
|
||||
ASSERT_EQ(s.code(), Status::Code::kNotSupported);
|
||||
|
||||
arg.atomic_replace_range = {{"a", {}}};
|
||||
s = db_->IngestExternalFiles({arg});
|
||||
ASSERT_EQ(s.code(), Status::Code::kNotSupported);
|
||||
|
||||
// rejected because doesn't cover ingested file
|
||||
arg.atomic_replace_range = {{"x", "z"}};
|
||||
s = db_->IngestExternalFiles({arg});
|
||||
ASSERT_EQ(s.code(), Status::Code::kInvalidArgument);
|
||||
|
||||
// rejected because of partial file overlap
|
||||
arg.atomic_replace_range = {{"a", "c"}};
|
||||
s = db_->IngestExternalFiles({arg});
|
||||
ASSERT_EQ(s.code(), Status::Code::kInvalidArgument);
|
||||
|
||||
if (!disallow_memtable) {
|
||||
// memtable overlap with replace range
|
||||
ASSERT_OK(Put(1, "e", "5"));
|
||||
arg.options.allow_blocking_flush = false;
|
||||
|
||||
// rejected because of memtable overlap
|
||||
arg.atomic_replace_range = {{"a", "z"}};
|
||||
s = db_->IngestExternalFiles({arg});
|
||||
ASSERT_EQ(s.code(), Status::Code::kInvalidArgument);
|
||||
|
||||
// rejected because of memtable overlap
|
||||
arg.atomic_replace_range = {{nullptr, nullptr}};
|
||||
s = db_->IngestExternalFiles({arg});
|
||||
ASSERT_EQ(s.code(), Status::Code::kInvalidArgument);
|
||||
|
||||
// FIXME: upper bound should be exclusive (DeleteRange semantics).
|
||||
// currently rejected because of documented bug
|
||||
arg.atomic_replace_range = {{"a", "e"}};
|
||||
s = db_->IngestExternalFiles({arg});
|
||||
ASSERT_EQ(s.code(), Status::Code::kInvalidArgument);
|
||||
|
||||
// work-around ensuring no memtable overlap
|
||||
arg.atomic_replace_range = {{"a", "d2"}};
|
||||
ASSERT_OK(db_->IngestExternalFiles({arg}));
|
||||
|
||||
ASSERT_EQ(Get(1, "e"), "5");
|
||||
} else {
|
||||
// rejected because of partial file overlap
|
||||
arg.atomic_replace_range = {{"b", "z"}};
|
||||
s = db_->IngestExternalFiles({arg});
|
||||
ASSERT_EQ(s.code(), Status::Code::kInvalidArgument);
|
||||
|
||||
// no memtable complications
|
||||
arg.atomic_replace_range = {{"a", "z"}};
|
||||
ASSERT_OK(db_->IngestExternalFiles({arg}));
|
||||
|
||||
ASSERT_EQ(Get(1, "e"), "NOT_FOUND");
|
||||
}
|
||||
ASSERT_EQ(Get(1, "a"), "NOT_FOUND");
|
||||
ASSERT_EQ(Get(1, "b"), "0");
|
||||
ASSERT_EQ(Get(1, "c"), "NOT_FOUND");
|
||||
ASSERT_EQ(Get(1, "d"), "NOT_FOUND");
|
||||
|
||||
// The single ingested file replaced everything (except perhaps memtable)
|
||||
std::vector<LiveFileMetaData> live_files;
|
||||
db_->GetLiveFilesMetaData(&live_files);
|
||||
// One file in each CF
|
||||
ASSERT_EQ(live_files.size(), 2);
|
||||
|
||||
ASSERT_OK(sfw.Open(file_path));
|
||||
ASSERT_OK(sfw.Put("f", "6"));
|
||||
ASSERT_OK(sfw.Finish());
|
||||
|
||||
// Another file
|
||||
ASSERT_OK(sfw.Open(file_path2));
|
||||
ASSERT_OK(sfw.Put("f", "7"));
|
||||
ASSERT_OK(sfw.Put("g", "8"));
|
||||
ASSERT_OK(sfw.Finish());
|
||||
|
||||
if (!disallow_memtable) {
|
||||
// rejected because of memtable overlap with range
|
||||
arg.atomic_replace_range = {{"e", "z"}};
|
||||
s = db_->IngestExternalFiles({arg});
|
||||
ASSERT_EQ(s.code(), Status::Code::kInvalidArgument);
|
||||
|
||||
// allow blocking flush of "e" (which is then replaced), and the file
|
||||
// with just "b" is not replaced
|
||||
arg.options.allow_blocking_flush = true;
|
||||
ASSERT_OK(db_->IngestExternalFiles({arg}));
|
||||
|
||||
ASSERT_EQ(Get(1, "b"), "0");
|
||||
ASSERT_EQ(Get(1, "e"), "NOT_FOUND");
|
||||
ASSERT_EQ(Get(1, "f"), "6");
|
||||
ASSERT_EQ(Get(1, "g"), "NOT_FOUND");
|
||||
|
||||
// memtable overlap with replace range
|
||||
ASSERT_OK(Put(1, "e", "5"));
|
||||
arg.options.allow_blocking_flush = false;
|
||||
arg.external_files = {file_path2};
|
||||
|
||||
// rejected because of memtable overlap
|
||||
arg.atomic_replace_range = {{nullptr, nullptr}};
|
||||
s = db_->IngestExternalFiles({arg});
|
||||
ASSERT_EQ(s.code(), Status::Code::kInvalidArgument);
|
||||
|
||||
// Replace everything, including with memtable flush
|
||||
arg.options.allow_blocking_flush = true;
|
||||
ASSERT_OK(db_->IngestExternalFiles({arg}));
|
||||
|
||||
ASSERT_EQ(Get(1, "b"), "NOT_FOUND");
|
||||
ASSERT_EQ(Get(1, "e"), "NOT_FOUND");
|
||||
ASSERT_EQ(Get(1, "f"), "7");
|
||||
ASSERT_EQ(Get(1, "g"), "8");
|
||||
} else {
|
||||
arg.external_files = {file_path2, file_path};
|
||||
|
||||
// rejected because of overlap in files to ingest with fail_if_ = true
|
||||
arg.atomic_replace_range = {{"e", "z"}};
|
||||
s = db_->IngestExternalFiles({arg});
|
||||
ASSERT_EQ(s.code(), Status::Code::kTryAgain);
|
||||
|
||||
arg.options.fail_if_not_bottommost_level = false;
|
||||
|
||||
// rejected because range doesn't cover ingested files
|
||||
// FIXME: upper bound should be exclusive "g" instead
|
||||
arg.atomic_replace_range = {{"e", "f2"}};
|
||||
s = db_->IngestExternalFiles({arg});
|
||||
ASSERT_EQ(s.code(), Status::Code::kInvalidArgument);
|
||||
|
||||
// Loaded into different levels, and the file with just "b" is not
|
||||
// replaced
|
||||
arg.atomic_replace_range = {{"e", "z"}};
|
||||
ASSERT_OK(db_->IngestExternalFiles({arg}));
|
||||
|
||||
ASSERT_EQ(Get(1, "b"), "0");
|
||||
ASSERT_EQ(Get(1, "f"), "6"); // earlier file listed later to ingest
|
||||
ASSERT_EQ(Get(1, "g"), "8");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
const std::vector<std::string>& external_files_paths,
|
||||
const std::vector<std::string>& files_checksums,
|
||||
const std::vector<std::string>& files_checksum_func_names,
|
||||
const std::optional<RangeOpt>& atomic_replace_range,
|
||||
const Temperature& file_temperature, uint64_t next_file_number,
|
||||
SuperVersion* sv) {
|
||||
Status status;
|
||||
@@ -80,15 +81,47 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
std::sort(sorted_files.begin(), sorted_files.end(), file_range_checker_);
|
||||
|
||||
for (size_t i = 0; i + 1 < num_files; i++) {
|
||||
if (file_range_checker_.OverlapsWithPrev(sorted_files[i],
|
||||
sorted_files[i + 1],
|
||||
/* ranges_sorted= */ true)) {
|
||||
if (file_range_checker_.Overlaps(*sorted_files[i], *sorted_files[i + 1],
|
||||
/* known_sorted= */ true)) {
|
||||
files_overlap_ = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (atomic_replace_range.has_value()) {
|
||||
atomic_replace_range_.emplace();
|
||||
|
||||
if (atomic_replace_range->start && atomic_replace_range->limit) {
|
||||
// User keys to internal keys (with timestamps)
|
||||
const size_t ts_sz = ucmp_->timestamp_size();
|
||||
std::string start_with_ts, limit_with_ts;
|
||||
auto [start, limit] = MaybeAddTimestampsToRange(
|
||||
atomic_replace_range->start, atomic_replace_range->limit, ts_sz,
|
||||
&start_with_ts, &limit_with_ts);
|
||||
assert(start.has_value());
|
||||
assert(limit.has_value());
|
||||
atomic_replace_range_->smallest_internal_key.Set(
|
||||
*start, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
atomic_replace_range_->largest_internal_key.Set(
|
||||
*limit, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
// Check files to ingest against replace range
|
||||
for (size_t i = 0; i < num_files; i++) {
|
||||
if (!file_range_checker_.Contains(*atomic_replace_range_,
|
||||
files_to_ingest_[i])) {
|
||||
return Status::InvalidArgument(
|
||||
"Atomic replace range does not contain all files");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Currently if either bound is not present, both must be
|
||||
assert(atomic_replace_range->start.has_value() == false);
|
||||
assert(atomic_replace_range->limit.has_value() == false);
|
||||
assert(atomic_replace_range_->smallest_internal_key.unset());
|
||||
assert(atomic_replace_range_->largest_internal_key.unset());
|
||||
}
|
||||
}
|
||||
|
||||
if (ingestion_options_.ingest_behind && files_overlap_) {
|
||||
return Status::NotSupported(
|
||||
"Files with overlapping ranges cannot be ingested with ingestion "
|
||||
@@ -359,9 +392,9 @@ void ExternalSstFileIngestionJob::DivideInputFilesIntoBatches() {
|
||||
|
||||
file_batches_to_ingest_.emplace_back(/* _track_batch_range= */ true);
|
||||
for (auto& file : files_to_ingest_) {
|
||||
if (file_range_checker_.OverlapsWithPrev(&file_batches_to_ingest_.back(),
|
||||
&file,
|
||||
/* ranges_sorted= */ false)) {
|
||||
if (!file_batches_to_ingest_.back().unset() &&
|
||||
file_range_checker_.Overlaps(file_batches_to_ingest_.back(), file,
|
||||
/* known_sorted= */ false)) {
|
||||
file_batches_to_ingest_.emplace_back(/* _track_batch_range= */ true);
|
||||
}
|
||||
file_batches_to_ingest_.back().AddFile(&file, file_range_checker_);
|
||||
@@ -370,14 +403,32 @@ void ExternalSstFileIngestionJob::DivideInputFilesIntoBatches() {
|
||||
|
||||
Status ExternalSstFileIngestionJob::NeedsFlush(bool* flush_needed,
|
||||
SuperVersion* super_version) {
|
||||
size_t n = files_to_ingest_.size();
|
||||
autovector<UserKeyRange> ranges;
|
||||
ranges.reserve(n);
|
||||
for (const IngestedFileInfo& file_to_ingest : files_to_ingest_) {
|
||||
ranges.emplace_back(file_to_ingest.start_ukey, file_to_ingest.limit_ukey);
|
||||
Status status;
|
||||
if (atomic_replace_range_.has_value() && atomic_replace_range_->unset()) {
|
||||
// For replacing whole CF, we can simply check whether memtable is empty
|
||||
*flush_needed = !super_version->mem->IsEmpty();
|
||||
} else {
|
||||
autovector<UserKeyRange> ranges;
|
||||
if (atomic_replace_range_.has_value()) {
|
||||
assert(!atomic_replace_range_->smallest_internal_key.unset());
|
||||
assert(!atomic_replace_range_->largest_internal_key.unset());
|
||||
// NOTE: we already checked in Prepare() that the atomic_replace_range
|
||||
// covers all the files_to_ingest
|
||||
// FIXME: need to make upper bound key exclusive (not easy here because
|
||||
// the existing internal APIs deal in inclusive upper bound user keys)
|
||||
ranges.emplace_back(
|
||||
atomic_replace_range_->smallest_internal_key.user_key(),
|
||||
atomic_replace_range_->largest_internal_key.user_key());
|
||||
} else {
|
||||
ranges.reserve(files_to_ingest_.size());
|
||||
for (const IngestedFileInfo& file_to_ingest : files_to_ingest_) {
|
||||
ranges.emplace_back(file_to_ingest.start_ukey,
|
||||
file_to_ingest.limit_ukey);
|
||||
}
|
||||
}
|
||||
status = cfd_->RangesOverlapWithMemtables(
|
||||
ranges, super_version, db_options_.allow_data_in_errors, flush_needed);
|
||||
}
|
||||
Status status = cfd_->RangesOverlapWithMemtables(
|
||||
ranges, super_version, db_options_.allow_data_in_errors, flush_needed);
|
||||
if (status.ok() && *flush_needed) {
|
||||
if (!ingestion_options_.allow_blocking_flush) {
|
||||
status = Status::InvalidArgument("External file requires flush");
|
||||
@@ -430,8 +481,49 @@ Status ExternalSstFileIngestionJob::Run() {
|
||||
// the only active writer, and hence they are equal
|
||||
SequenceNumber last_seqno = versions_->LastSequence();
|
||||
edit_.SetColumnFamily(cfd_->GetID());
|
||||
// The levels that the files will be ingested into
|
||||
|
||||
if (atomic_replace_range_.has_value()) {
|
||||
auto* vstorage = super_version->current->storage_info();
|
||||
if (atomic_replace_range_->unset()) {
|
||||
if (cfd_->compaction_picker()->IsCompactionInProgress()) {
|
||||
return Status::InvalidArgument(
|
||||
"Atomic replace range (full) overlaps with pending compaction");
|
||||
}
|
||||
for (int lvl = 0; lvl < cfd_->NumberLevels(); lvl++) {
|
||||
for (auto file : vstorage->LevelFiles(lvl)) {
|
||||
// Set up to delete file to be replaced
|
||||
edit_.DeleteFile(lvl, file->fd.GetNumber());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assert(!atomic_replace_range_->smallest_internal_key.unset());
|
||||
assert(!atomic_replace_range_->largest_internal_key.unset());
|
||||
for (int lvl = 0; lvl < cfd_->NumberLevels(); lvl++) {
|
||||
if (cfd_->RangeOverlapWithCompaction(
|
||||
atomic_replace_range_->smallest_internal_key.user_key(),
|
||||
atomic_replace_range_->largest_internal_key.user_key(), lvl)) {
|
||||
return Status::InvalidArgument(
|
||||
"Atomic replace range overlaps with pending compaction");
|
||||
}
|
||||
for (auto file : vstorage->LevelFiles(lvl)) {
|
||||
if (file_range_checker_.Overlaps(*atomic_replace_range_,
|
||||
file->smallest, file->largest)) {
|
||||
if (file_range_checker_.Contains(*atomic_replace_range_,
|
||||
file->smallest, file->largest)) {
|
||||
// Set up to delete file to be replaced
|
||||
edit_.DeleteFile(lvl, file->fd.GetNumber());
|
||||
} else {
|
||||
// TODO: generate and ingest a tombstone file also
|
||||
return Status::InvalidArgument(
|
||||
"Atomic replace range partially overlaps with existing file");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find levels to ingest into
|
||||
std::optional<int> prev_batch_uppermost_level;
|
||||
for (auto& batch : file_batches_to_ingest_) {
|
||||
int batch_uppermost_level = 0;
|
||||
@@ -1104,6 +1196,10 @@ Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile(
|
||||
if (lvl > 0 && lvl < vstorage->base_level()) {
|
||||
continue;
|
||||
}
|
||||
if (atomic_replace_range_.has_value()) {
|
||||
target_level = lvl;
|
||||
continue;
|
||||
}
|
||||
if (cfd_->RangeOverlapWithCompaction(file_to_ingest->start_ukey,
|
||||
file_to_ingest->limit_ukey, lvl)) {
|
||||
// We must use L0 or any level higher than `lvl` to be able to overwrite
|
||||
@@ -1172,6 +1268,8 @@ Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile(
|
||||
|
||||
Status ExternalSstFileIngestionJob::CheckLevelForIngestedBehindFile(
|
||||
IngestedFileInfo* file_to_ingest) {
|
||||
assert(!atomic_replace_range_.has_value());
|
||||
|
||||
auto* vstorage = cfd_->current()->storage_info();
|
||||
// First, check if new files fit in the last level
|
||||
int last_lvl = cfd_->NumberLevels() - 1;
|
||||
|
||||
@@ -27,50 +27,77 @@ class SystemClock;
|
||||
|
||||
struct KeyRangeInfo {
|
||||
// Smallest internal key in an external file or for a batch of external files.
|
||||
// unset() could be either invalid or "before all keys"
|
||||
InternalKey smallest_internal_key;
|
||||
// Largest internal key in an external file or for a batch of external files.
|
||||
// unset() could be either invalid or "after all keys"
|
||||
InternalKey largest_internal_key;
|
||||
|
||||
bool empty() const {
|
||||
return smallest_internal_key.size() == 0 &&
|
||||
largest_internal_key.size() == 0;
|
||||
bool unset() const {
|
||||
// Legal internal keys are at least 8 bytes.
|
||||
return smallest_internal_key.unset() || largest_internal_key.unset();
|
||||
}
|
||||
};
|
||||
|
||||
// Helper class to apply SST file key range checks to the external files.
|
||||
// XXX: using sstableKeyCompare with user comparator on internal keys is
|
||||
// very broken
|
||||
class ExternalFileRangeChecker {
|
||||
public:
|
||||
explicit ExternalFileRangeChecker(const Comparator* ucmp) : ucmp_(ucmp) {}
|
||||
|
||||
// Operator used for sorting ranges.
|
||||
bool operator()(const KeyRangeInfo* prev_range,
|
||||
const KeyRangeInfo* range) const {
|
||||
assert(prev_range);
|
||||
assert(range);
|
||||
return sstableKeyCompare(ucmp_, prev_range->smallest_internal_key,
|
||||
range->smallest_internal_key) < 0;
|
||||
bool operator()(const KeyRangeInfo* range1,
|
||||
const KeyRangeInfo* range2) const {
|
||||
assert(range1);
|
||||
assert(range2);
|
||||
assert(!range1->unset());
|
||||
assert(!range2->unset());
|
||||
return sstableKeyCompare(ucmp_, range1->smallest_internal_key,
|
||||
range2->smallest_internal_key) < 0;
|
||||
}
|
||||
|
||||
// Check whether `range` overlaps with `prev_range`. `ranges_sorted` can be
|
||||
// set to true when the inputs are already sorted based on the sorting logic
|
||||
// provided by this checker's operator(), which can help simplify the check.
|
||||
bool OverlapsWithPrev(const KeyRangeInfo* prev_range,
|
||||
const KeyRangeInfo* range,
|
||||
bool ranges_sorted = false) const {
|
||||
assert(prev_range);
|
||||
assert(range);
|
||||
if (prev_range->empty() || range->empty()) {
|
||||
bool Overlaps(const KeyRangeInfo& range1, const KeyRangeInfo& range2,
|
||||
bool known_sorted = false) const {
|
||||
return Overlaps(range1, range2.smallest_internal_key,
|
||||
range2.largest_internal_key, known_sorted);
|
||||
}
|
||||
bool Overlaps(const KeyRangeInfo& range1, const InternalKey& range2_smallest,
|
||||
const InternalKey& range2_largest,
|
||||
bool known_sorted = false) const {
|
||||
bool any_unset =
|
||||
range1.unset() || range2_smallest.unset() || range2_largest.unset();
|
||||
if (any_unset) {
|
||||
assert(!any_unset);
|
||||
return false;
|
||||
}
|
||||
if (ranges_sorted) {
|
||||
return sstableKeyCompare(ucmp_, prev_range->largest_internal_key,
|
||||
range->smallest_internal_key) >= 0;
|
||||
if (known_sorted) {
|
||||
return sstableKeyCompare(ucmp_, range1.largest_internal_key,
|
||||
range2_smallest) >= 0;
|
||||
}
|
||||
|
||||
return sstableKeyCompare(ucmp_, prev_range->largest_internal_key,
|
||||
range->smallest_internal_key) >= 0 &&
|
||||
sstableKeyCompare(ucmp_, prev_range->smallest_internal_key,
|
||||
range->largest_internal_key) <= 0;
|
||||
return sstableKeyCompare(ucmp_, range1.largest_internal_key,
|
||||
range2_smallest) >= 0 &&
|
||||
sstableKeyCompare(ucmp_, range1.smallest_internal_key,
|
||||
range2_largest) <= 0;
|
||||
}
|
||||
|
||||
bool Contains(const KeyRangeInfo& range1, const KeyRangeInfo& range2) {
|
||||
return Contains(range1, range2.smallest_internal_key,
|
||||
range2.largest_internal_key);
|
||||
}
|
||||
bool Contains(const KeyRangeInfo& range1, const InternalKey& range2_smallest,
|
||||
const InternalKey& range2_largest) {
|
||||
bool any_unset =
|
||||
range1.unset() || range2_smallest.unset() || range2_largest.unset();
|
||||
if (any_unset) {
|
||||
assert(!any_unset);
|
||||
return false;
|
||||
}
|
||||
return sstableKeyCompare(ucmp_, range1.smallest_internal_key,
|
||||
range2_smallest) <= 0 &&
|
||||
sstableKeyCompare(ucmp_, range1.largest_internal_key,
|
||||
range2_largest) >= 0;
|
||||
}
|
||||
|
||||
void MaybeUpdateRange(const InternalKey& start_key,
|
||||
@@ -218,6 +245,7 @@ class ExternalSstFileIngestionJob {
|
||||
Status Prepare(const std::vector<std::string>& external_files_paths,
|
||||
const std::vector<std::string>& files_checksums,
|
||||
const std::vector<std::string>& files_checksum_func_names,
|
||||
const std::optional<RangeOpt>& atomic_replace_range,
|
||||
const Temperature& file_temperature, uint64_t next_file_number,
|
||||
SuperVersion* sv);
|
||||
|
||||
@@ -362,6 +390,7 @@ class ExternalSstFileIngestionJob {
|
||||
autovector<IngestedFileInfo> files_to_ingest_;
|
||||
std::vector<FileBatchInfo> file_batches_to_ingest_;
|
||||
const IngestExternalFileOptions& ingestion_options_;
|
||||
std::optional<KeyRangeInfo> atomic_replace_range_;
|
||||
Directories* directories_;
|
||||
EventLogger* event_logger_;
|
||||
VersionEdit edit_;
|
||||
|
||||
@@ -3915,9 +3915,7 @@ TEST_P(IngestDBGeneratedFileTest, FailureCase) {
|
||||
s = db_->IngestExternalFile(to_ingest_files, ingest_opts);
|
||||
ASSERT_TRUE(s.ToString().find(err) != std::string::npos);
|
||||
ASSERT_NOK(s);
|
||||
if (options.compaction_style != kCompactionStyleUniversal) {
|
||||
// FIXME: after fixing ingestion with universal compaction, currently
|
||||
// will always ingest into L0.
|
||||
if (options.num_levels > 1) {
|
||||
ingest_opts.fail_if_not_bottommost_level = true;
|
||||
s = db_->IngestExternalFile(to_ingest_files, ingest_opts);
|
||||
ASSERT_NOK(s);
|
||||
|
||||
+6
-6
@@ -870,7 +870,8 @@ Status FlushJob::WriteLevel0Table() {
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
|
||||
{
|
||||
auto write_hint = base_->storage_info()->CalculateSSTWriteHint(/*level=*/0);
|
||||
auto write_hint = base_->storage_info()->CalculateSSTWriteHint(
|
||||
/*level=*/0, db_options_.calculate_sst_write_lifetime_hint_set);
|
||||
Env::IOPriority io_priority = GetRateLimiterPriority();
|
||||
db_mutex_->Unlock();
|
||||
if (log_buffer_) {
|
||||
@@ -1193,13 +1194,12 @@ void FlushJob::GetEffectiveCutoffUDTForPickedMemTables() {
|
||||
}
|
||||
|
||||
void FlushJob::GetPrecludeLastLevelMinSeqno() {
|
||||
if (mutable_cf_options_.preclude_last_level_data_seconds == 0 ||
|
||||
// FIXME: create FlushJob and build SuperVersions such that
|
||||
// preclude_last_level_data_seconds > 0 implies
|
||||
// seqno_to_time_mapping_ != nullptr
|
||||
seqno_to_time_mapping_ == nullptr) {
|
||||
if (mutable_cf_options_.preclude_last_level_data_seconds == 0) {
|
||||
return;
|
||||
}
|
||||
// SuperVersion should guarantee this
|
||||
assert(seqno_to_time_mapping_);
|
||||
assert(!seqno_to_time_mapping_->Empty());
|
||||
int64_t current_time = 0;
|
||||
Status s = db_options_.clock->GetCurrentTime(¤t_time);
|
||||
if (!s.ok()) {
|
||||
|
||||
+1
-1
@@ -234,7 +234,7 @@ class FlushJob {
|
||||
|
||||
// The current minimum seqno that compaction jobs will preclude the data from
|
||||
// the last level. Data with seqnos larger than this or larger than
|
||||
// `earliest_snapshot_` will be output to the penultimate level had it gone
|
||||
// `earliest_snapshot_` will be output to the proximal level had it gone
|
||||
// through a compaction to the last level.
|
||||
SequenceNumber preclude_last_level_min_seqno_ = kMaxSequenceNumber;
|
||||
};
|
||||
|
||||
+26
-51
@@ -153,23 +153,6 @@ class InternalStats {
|
||||
|
||||
InternalStats(int num_levels, SystemClock* clock, ColumnFamilyData* cfd);
|
||||
|
||||
// Per level compaction stats
|
||||
struct CompactionOutputsStats {
|
||||
uint64_t num_output_records = 0;
|
||||
uint64_t bytes_written = 0;
|
||||
uint64_t bytes_written_blob = 0;
|
||||
uint64_t num_output_files = 0;
|
||||
uint64_t num_output_files_blob = 0;
|
||||
|
||||
void Add(const CompactionOutputsStats& stats) {
|
||||
this->num_output_records += stats.num_output_records;
|
||||
this->bytes_written += stats.bytes_written;
|
||||
this->bytes_written_blob += stats.bytes_written_blob;
|
||||
this->num_output_files += stats.num_output_files;
|
||||
this->num_output_files_blob += stats.num_output_files_blob;
|
||||
}
|
||||
};
|
||||
|
||||
// Per level compaction stats. comp_stats_[level] stores the stats for
|
||||
// compactions that produced data for the specified "level".
|
||||
struct CompactionStats {
|
||||
@@ -420,15 +403,6 @@ class InternalStats {
|
||||
}
|
||||
}
|
||||
|
||||
void Add(const CompactionOutputsStats& stats) {
|
||||
this->num_output_files += static_cast<int>(stats.num_output_files);
|
||||
this->num_output_records += stats.num_output_records;
|
||||
this->bytes_written += stats.bytes_written;
|
||||
this->bytes_written_blob += stats.bytes_written_blob;
|
||||
this->num_output_files_blob +=
|
||||
static_cast<int>(stats.num_output_files_blob);
|
||||
}
|
||||
|
||||
void Subtract(const CompactionStats& c) {
|
||||
this->micros -= c.micros;
|
||||
this->cpu_micros -= c.cpu_micros;
|
||||
@@ -473,49 +447,51 @@ class InternalStats {
|
||||
}
|
||||
};
|
||||
|
||||
// Compaction stats, for per_key_placement compaction, it includes 2 levels
|
||||
// stats: the last level and the penultimate level.
|
||||
// Compaction internal stats, for per_key_placement compaction, it includes 2
|
||||
// output level stats: the last level and the proximal level.
|
||||
struct CompactionStatsFull {
|
||||
// the stats for the target primary output level
|
||||
CompactionStats stats;
|
||||
CompactionStats output_level_stats;
|
||||
|
||||
// stats for penultimate level output if exist
|
||||
bool has_penultimate_level_output = false;
|
||||
CompactionStats penultimate_level_stats;
|
||||
// stats for proximal level output if exist
|
||||
bool has_proximal_level_output = false;
|
||||
CompactionStats proximal_level_stats;
|
||||
|
||||
explicit CompactionStatsFull() : stats(), penultimate_level_stats() {}
|
||||
explicit CompactionStatsFull()
|
||||
: output_level_stats(), proximal_level_stats() {}
|
||||
|
||||
explicit CompactionStatsFull(CompactionReason reason, int c)
|
||||
: stats(reason, c), penultimate_level_stats(reason, c) {}
|
||||
: output_level_stats(reason, c), proximal_level_stats(reason, c) {}
|
||||
|
||||
uint64_t TotalBytesWritten() const {
|
||||
uint64_t bytes_written = stats.bytes_written + stats.bytes_written_blob;
|
||||
if (has_penultimate_level_output) {
|
||||
bytes_written += penultimate_level_stats.bytes_written +
|
||||
penultimate_level_stats.bytes_written_blob;
|
||||
uint64_t bytes_written = output_level_stats.bytes_written +
|
||||
output_level_stats.bytes_written_blob;
|
||||
if (has_proximal_level_output) {
|
||||
bytes_written += proximal_level_stats.bytes_written +
|
||||
proximal_level_stats.bytes_written_blob;
|
||||
}
|
||||
return bytes_written;
|
||||
}
|
||||
|
||||
uint64_t DroppedRecords() {
|
||||
uint64_t output_records = stats.num_output_records;
|
||||
if (has_penultimate_level_output) {
|
||||
output_records += penultimate_level_stats.num_output_records;
|
||||
uint64_t output_records = output_level_stats.num_output_records;
|
||||
if (has_proximal_level_output) {
|
||||
output_records += proximal_level_stats.num_output_records;
|
||||
}
|
||||
if (stats.num_input_records > output_records) {
|
||||
return stats.num_input_records - output_records;
|
||||
if (output_level_stats.num_input_records > output_records) {
|
||||
return output_level_stats.num_input_records - output_records;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SetMicros(uint64_t val) {
|
||||
stats.micros = val;
|
||||
penultimate_level_stats.micros = val;
|
||||
output_level_stats.micros = val;
|
||||
proximal_level_stats.micros = val;
|
||||
}
|
||||
|
||||
void AddCpuMicros(uint64_t val) {
|
||||
stats.cpu_micros += val;
|
||||
penultimate_level_stats.cpu_micros += val;
|
||||
output_level_stats.cpu_micros += val;
|
||||
proximal_level_stats.cpu_micros += val;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -587,10 +563,9 @@ class InternalStats {
|
||||
|
||||
void AddCompactionStats(int level, Env::Priority thread_pri,
|
||||
const CompactionStatsFull& comp_stats_full) {
|
||||
AddCompactionStats(level, thread_pri, comp_stats_full.stats);
|
||||
if (comp_stats_full.has_penultimate_level_output) {
|
||||
per_key_placement_comp_stats_.Add(
|
||||
comp_stats_full.penultimate_level_stats);
|
||||
AddCompactionStats(level, thread_pri, comp_stats_full.output_level_stats);
|
||||
if (comp_stats_full.has_proximal_level_output) {
|
||||
per_key_placement_comp_stats_.Add(comp_stats_full.proximal_level_stats);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-6
@@ -22,6 +22,9 @@ namespace ROCKSDB_NAMESPACE {
|
||||
class MemTable;
|
||||
struct SuperVersion;
|
||||
|
||||
// The purpose of this struct is to simplify pushing work such as
|
||||
// allocation/construction, de-allocation/destruction, and notifications to
|
||||
// outside of holding the DB mutex.
|
||||
struct SuperVersionContext {
|
||||
struct WriteStallNotification {
|
||||
WriteStallInfo write_stall_info;
|
||||
@@ -35,12 +38,6 @@ struct SuperVersionContext {
|
||||
std::unique_ptr<SuperVersion>
|
||||
new_superversion; // if nullptr no new superversion
|
||||
|
||||
// If not nullptr, a new seqno to time mapping is available to be installed.
|
||||
// Otherwise, make a shared copy of the one in the existing SuperVersion and
|
||||
// carry it over to the new SuperVersion. This is moved to the SuperVersion
|
||||
// during installation.
|
||||
std::shared_ptr<const SeqnoToTimeMapping> new_seqno_to_time_mapping{nullptr};
|
||||
|
||||
explicit SuperVersionContext(bool create_superversion = false)
|
||||
: new_superversion(create_superversion ? new SuperVersion() : nullptr) {}
|
||||
|
||||
|
||||
@@ -1338,6 +1338,50 @@ TEST_P(PlainTableDBTest, AdaptiveTable) {
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(PlainTableDBTest, PlainTableDBTest, ::testing::Bool());
|
||||
|
||||
TEST_P(PlainTableDBTest, DeleteRangeNotSupported) {
|
||||
// XXX: After attempting DeleteRange with PlainTable, Writes will permanently
|
||||
// fail. Even if re-opening the DB, if WAL is used, the WAL is not recoverable
|
||||
// (without manual intervention). Furthermore, a partial write batch can
|
||||
// be exposed to readers, breaking WriteBatch atomicity.
|
||||
for (bool use_write_batch : {/*false, */ true}) {
|
||||
DestroyAndReopen();
|
||||
|
||||
ASSERT_OK(Put("a0001111", "1"));
|
||||
ASSERT_OK(Put("b0001111", "2"));
|
||||
ASSERT_OK(Put("c0001111", "3"));
|
||||
if (use_write_batch) {
|
||||
WriteBatch wb;
|
||||
ASSERT_OK(wb.Put("d0001111", "4"));
|
||||
ASSERT_OK(wb.DeleteRange("a", "b"));
|
||||
ASSERT_OK(wb.Put("e0001111", "5"));
|
||||
ASSERT_EQ(dbfull()->Write({}, &wb).code(), Status::Code::kNotSupported);
|
||||
} else {
|
||||
ASSERT_EQ(dbfull()->DeleteRange({}, "az", "bz").code(),
|
||||
Status::Code::kNotSupported);
|
||||
}
|
||||
ASSERT_EQ(Get("a0001111"), "1");
|
||||
ASSERT_EQ(Get("b0001111"), "2");
|
||||
ASSERT_EQ(Get("c0001111"), "3");
|
||||
if (use_write_batch) {
|
||||
// XXX: broken WriteBatch atomicity
|
||||
ASSERT_EQ(Get("d0001111"), "4");
|
||||
} else {
|
||||
ASSERT_EQ(Get("d0001111"), "NOT_FOUND");
|
||||
}
|
||||
ASSERT_EQ(Get("e0001111"), "NOT_FOUND");
|
||||
|
||||
ASSERT_EQ(Put("e0001111", "5").code(), Status::Code::kNotSupported);
|
||||
ASSERT_EQ(Get("e0001111"), "NOT_FOUND");
|
||||
|
||||
// Even trying to flush
|
||||
ASSERT_EQ(dbfull()->TEST_FlushMemTable().code(),
|
||||
Status::Code::kNotSupported);
|
||||
|
||||
// XXX: WAL is not recoverable
|
||||
ASSERT_EQ(TryReopen().code(), Status::Code::kNotSupported);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+9
-6
@@ -100,13 +100,15 @@ class Repairer {
|
||||
db_options_(SanitizeOptions(dbname_, db_options)),
|
||||
immutable_db_options_(ImmutableDBOptions(db_options_)),
|
||||
icmp_(default_cf_opts.comparator),
|
||||
default_cf_opts_(
|
||||
SanitizeOptions(immutable_db_options_, default_cf_opts)),
|
||||
default_cf_opts_(SanitizeCfOptions(immutable_db_options_,
|
||||
/*read_only*/ false,
|
||||
default_cf_opts)),
|
||||
default_iopts_(
|
||||
ImmutableOptions(immutable_db_options_, default_cf_opts_)),
|
||||
default_mopts_(MutableCFOptions(default_cf_opts_)),
|
||||
unknown_cf_opts_(
|
||||
SanitizeOptions(immutable_db_options_, unknown_cf_opts)),
|
||||
unknown_cf_opts_(SanitizeCfOptions(immutable_db_options_,
|
||||
/*read_only*/ false,
|
||||
unknown_cf_opts)),
|
||||
create_unknown_cfs_(create_unknown_cfs),
|
||||
raw_table_cache_(
|
||||
// TableCache can be small since we expect each table to be opened
|
||||
@@ -456,8 +458,9 @@ class Repairer {
|
||||
meta.file_creation_time = current_time;
|
||||
SnapshotChecker* snapshot_checker = DisableGCSnapshotChecker::Instance();
|
||||
|
||||
auto write_hint =
|
||||
cfd->current()->storage_info()->CalculateSSTWriteHint(/*level=*/0);
|
||||
auto write_hint = cfd->current()->storage_info()->CalculateSSTWriteHint(
|
||||
/*level=*/0, db_options_.calculate_sst_write_lifetime_hint_set);
|
||||
|
||||
std::vector<std::unique_ptr<FragmentedRangeTombstoneIterator>>
|
||||
range_del_iters;
|
||||
auto range_del_iter = mem->NewRangeTombstoneIterator(
|
||||
|
||||
@@ -96,7 +96,7 @@ TEST_F(SeqnoTimeTest, TemperatureBasicUniversal) {
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
// All data is hot, only output to penultimate level
|
||||
// All data is hot, only output to proximal level
|
||||
ASSERT_EQ("0,0,0,0,0,1", FilesPerLevel());
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
|
||||
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
|
||||
@@ -185,7 +185,7 @@ TEST_F(SeqnoTimeTest, TemperatureBasicLevel) {
|
||||
options.num_levels = kNumLevels;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
// TODO(zjay): for level compaction, auto-compaction may stuck in deadloop, if
|
||||
// the penultimate level score > 1, but the hot is not cold enough to compact
|
||||
// the proximal level score > 1, but the hot is not cold enough to compact
|
||||
// to last level, which will keep triggering compaction.
|
||||
options.disable_auto_compactions = true;
|
||||
DestroyAndReopen(options);
|
||||
@@ -205,7 +205,7 @@ TEST_F(SeqnoTimeTest, TemperatureBasicLevel) {
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
|
||||
// All data is hot, only output to penultimate level
|
||||
// All data is hot, only output to proximal level
|
||||
ASSERT_EQ("0,0,0,0,0,1", FilesPerLevel());
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
|
||||
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
|
||||
@@ -753,7 +753,7 @@ TEST_P(SeqnoTimeTablePropTest, SeqnoToTimeMappingUniversal) {
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
// make sure the data is all compacted to penultimate level if the feature is
|
||||
// make sure the data is all compacted to proximal level if the feature is
|
||||
// on, otherwise, compacted to the last level.
|
||||
if (options.preclude_last_level_data_seconds > 0) {
|
||||
ASSERT_GT(NumTableFilesAtLevel(5), 0);
|
||||
@@ -792,8 +792,7 @@ TEST_P(SeqnoTimeTablePropTest, SeqnoToTimeMappingUniversal) {
|
||||
}
|
||||
ASSERT_GT(num_seqno_zeroing, 0);
|
||||
std::vector<KeyVersion> key_versions;
|
||||
ASSERT_OK(GetAllKeyVersions(db_, Slice(), Slice(),
|
||||
std::numeric_limits<size_t>::max(),
|
||||
ASSERT_OK(GetAllKeyVersions(db_, {}, {}, std::numeric_limits<size_t>::max(),
|
||||
&key_versions));
|
||||
// make sure there're more than 300 keys and first 100 keys are having seqno
|
||||
// zeroed out, the last 100 key seqno not zeroed out
|
||||
@@ -919,10 +918,11 @@ TEST_P(SeqnoTimeTablePropTest, PrePopulateInDB) {
|
||||
ASSERT_EQ(db_->GetLatestSequenceNumber(), 0);
|
||||
|
||||
// And even if we re-open read-write, we do not get pre-population,
|
||||
// because that's only for new DBs.
|
||||
// because that's only for new DBs. We just get a single bootstrap
|
||||
// entry as a lower bound on write times of future writes.
|
||||
Reopen(track_options);
|
||||
sttm = dbfull()->TEST_GetSeqnoToTimeMapping();
|
||||
ASSERT_EQ(sttm.Size(), 0);
|
||||
ASSERT_EQ(sttm.Size(), 1);
|
||||
ASSERT_EQ(db_->GetLatestSequenceNumber(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,7 +490,7 @@ bool SeqnoToTimeMapping::Append(SequenceNumber seqno, uint64_t time) {
|
||||
return added;
|
||||
}
|
||||
|
||||
bool SeqnoToTimeMapping::PrePopulate(SequenceNumber from_seqno,
|
||||
void SeqnoToTimeMapping::PrePopulate(SequenceNumber from_seqno,
|
||||
SequenceNumber to_seqno,
|
||||
uint64_t from_time, uint64_t to_time) {
|
||||
assert(Empty());
|
||||
@@ -505,8 +505,6 @@ bool SeqnoToTimeMapping::PrePopulate(SequenceNumber from_seqno,
|
||||
(to_seqno - from_seqno);
|
||||
pairs_.emplace_back(i, t);
|
||||
}
|
||||
|
||||
return /*success*/ true;
|
||||
}
|
||||
|
||||
std::string SeqnoToTimeMapping::ToHumanString() const {
|
||||
|
||||
@@ -138,7 +138,7 @@ class SeqnoToTimeMapping {
|
||||
// Adds a series of mappings interpolating from from_seqno->from_time to
|
||||
// to_seqno->to_time. This can only be called on an empty object and both
|
||||
// seqno range and time range are inclusive.
|
||||
bool PrePopulate(SequenceNumber from_seqno, SequenceNumber to_seqno,
|
||||
void PrePopulate(SequenceNumber from_seqno, SequenceNumber to_seqno,
|
||||
uint64_t from_time, uint64_t to_time);
|
||||
|
||||
// Append a new entry to the list. The `seqno` should be >= all previous
|
||||
@@ -148,6 +148,10 @@ class SeqnoToTimeMapping {
|
||||
// rather than creating a new entry.
|
||||
bool Append(SequenceNumber seqno, uint64_t time);
|
||||
|
||||
bool Append(std::pair<SequenceNumber, uint64_t> seqno_time_pair) {
|
||||
return Append(seqno_time_pair.first, seqno_time_pair.second);
|
||||
}
|
||||
|
||||
// Clear all entries and (re-)enter enforced mode if not already in that
|
||||
// state. Enforced limits are unchanged.
|
||||
void Clear() {
|
||||
@@ -274,6 +278,48 @@ class SeqnoToTimeMapping {
|
||||
pair_const_iterator FindGreaterEqSeqno(SequenceNumber seqno) const;
|
||||
};
|
||||
|
||||
// A struct to help combining settings across column families
|
||||
struct MinAndMaxPreserveSeconds {
|
||||
uint64_t min_preserve_seconds = std::numeric_limits<uint64_t>::max();
|
||||
uint64_t max_preserve_seconds = std::numeric_limits<uint64_t>::min();
|
||||
|
||||
MinAndMaxPreserveSeconds() = default;
|
||||
|
||||
template <class CFOpts>
|
||||
explicit MinAndMaxPreserveSeconds(const CFOpts& opts) {
|
||||
Combine(opts);
|
||||
}
|
||||
|
||||
bool IsEnabled() const {
|
||||
return min_preserve_seconds != std::numeric_limits<uint64_t>::max();
|
||||
}
|
||||
|
||||
// Incorporate another CF's settings into the result. If preserve/preclude are
|
||||
// disabled for this CF, they are excluded from the result.
|
||||
template <class CFOpts>
|
||||
void Combine(const CFOpts& opts) {
|
||||
uint64_t preserve_seconds = std::max(opts.preserve_internal_time_seconds,
|
||||
opts.preclude_last_level_data_seconds);
|
||||
if (preserve_seconds > 0) {
|
||||
min_preserve_seconds = std::min(preserve_seconds, min_preserve_seconds);
|
||||
max_preserve_seconds = std::max(preserve_seconds, max_preserve_seconds);
|
||||
}
|
||||
}
|
||||
|
||||
// Choose how many seconds between mapping samples
|
||||
uint64_t GetRecodingCadence() const {
|
||||
if (IsEnabled()) {
|
||||
// round up to 1 when the time_duration is smaller than
|
||||
// kMaxSeqnoTimePairsPerCF
|
||||
return (min_preserve_seconds + kMaxSeqnoTimePairsPerCF - 1) /
|
||||
kMaxSeqnoTimePairsPerCF;
|
||||
} else {
|
||||
// disabled
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// === Utility methods used for TimedPut === //
|
||||
|
||||
// Pack a value Slice and a unix write time into buffer `buf` and return a Slice
|
||||
|
||||
@@ -205,6 +205,7 @@ Status TableCache::FindTable(
|
||||
RecordTick(ioptions_.stats, NO_FILE_ERRORS);
|
||||
// We do not cache error results so that if the error is transient,
|
||||
// or somebody repairs the file, we recover automatically.
|
||||
IGNORE_STATUS_IF_ERROR(s);
|
||||
} else {
|
||||
s = cache_.Insert(key, table_reader.get(), 1, handle);
|
||||
if (s.ok()) {
|
||||
|
||||
@@ -408,7 +408,7 @@ void VersionEditHandler::CheckIterationResult(const log::Reader& reader,
|
||||
if (cfd->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
if (read_only_) {
|
||||
if (version_set_->unchanging()) {
|
||||
cfd->table_cache()->SetTablesAreImmortal();
|
||||
}
|
||||
*s = LoadTables(cfd, /*prefetch_index_and_filter_in_cache=*/false,
|
||||
@@ -471,8 +471,8 @@ void VersionEditHandler::CheckIterationResult(const log::Reader& reader,
|
||||
ColumnFamilyData* VersionEditHandler::CreateCfAndInit(
|
||||
const ColumnFamilyOptions& cf_options, const VersionEdit& edit) {
|
||||
uint32_t cf_id = edit.GetColumnFamily();
|
||||
ColumnFamilyData* cfd =
|
||||
version_set_->CreateColumnFamily(cf_options, read_options_, &edit);
|
||||
ColumnFamilyData* cfd = version_set_->CreateColumnFamily(
|
||||
cf_options, read_options_, &edit, read_only_);
|
||||
assert(cfd != nullptr);
|
||||
cfd->set_initialized();
|
||||
assert(builders_.find(cf_id) == builders_.end());
|
||||
|
||||
@@ -198,7 +198,9 @@ class VersionEditHandler : public VersionEditHandlerBase {
|
||||
bool prefetch_index_and_filter_in_cache,
|
||||
bool is_initial_load);
|
||||
|
||||
virtual bool MustOpenAllColumnFamilies() const { return !read_only_; }
|
||||
virtual bool MustOpenAllColumnFamilies() const {
|
||||
return !version_set_->unchanging();
|
||||
}
|
||||
|
||||
const bool read_only_;
|
||||
std::vector<ColumnFamilyDescriptor> column_families_;
|
||||
@@ -334,10 +336,10 @@ class ManifestTailer : public VersionEditHandlerPointInTime {
|
||||
const ReadOptions& read_options,
|
||||
EpochNumberRequirement epoch_number_requirement =
|
||||
EpochNumberRequirement::kMustPresent)
|
||||
: VersionEditHandlerPointInTime(/*read_only=*/false, column_families,
|
||||
version_set, io_tracer, read_options,
|
||||
/*allow_incomplete_valid_version=*/false,
|
||||
epoch_number_requirement),
|
||||
: VersionEditHandlerPointInTime(
|
||||
/*read_only=*/true, column_families, version_set, io_tracer,
|
||||
read_options,
|
||||
/*allow_incomplete_valid_version=*/false, epoch_number_requirement),
|
||||
mode_(Mode::kRecovery) {}
|
||||
|
||||
Status VerifyFile(ColumnFamilyData* cfd, const std::string& fpath, int level,
|
||||
|
||||
+60
-25
@@ -1627,8 +1627,8 @@ Status Version::GetTableProperties(const ReadOptions& read_options,
|
||||
return s;
|
||||
}
|
||||
|
||||
Status Version::GetPropertiesOfAllTables(const ReadOptions& read_options,
|
||||
TablePropertiesCollection* props) {
|
||||
Status Version::GetPropertiesOfAllTables(
|
||||
const ReadOptions& read_options, TablePropertiesCollection* props) const {
|
||||
Status s;
|
||||
for (int level = 0; level < storage_info_.num_levels_; level++) {
|
||||
s = GetPropertiesOfAllTables(read_options, props, level);
|
||||
@@ -1699,7 +1699,7 @@ Status Version::TablesRangeTombstoneSummary(int max_entries_to_print,
|
||||
|
||||
Status Version::GetPropertiesOfAllTables(const ReadOptions& read_options,
|
||||
TablePropertiesCollection* props,
|
||||
int level) {
|
||||
int level) const {
|
||||
for (const auto& file_meta : storage_info_.files_[level]) {
|
||||
auto fname =
|
||||
TableFileName(cfd_->ioptions().cf_paths, file_meta->fd.GetNumber(),
|
||||
@@ -1753,6 +1753,24 @@ Status Version::GetPropertiesOfTablesInRange(
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status Version::GetPropertiesOfTablesByLevel(
|
||||
const ReadOptions& read_options,
|
||||
std::vector<std::unique_ptr<TablePropertiesCollection>>* props_by_level)
|
||||
const {
|
||||
Status s;
|
||||
|
||||
props_by_level->reserve(storage_info_.num_levels_);
|
||||
for (int level = 0; level < storage_info_.num_levels_; level++) {
|
||||
props_by_level->push_back(std::make_unique<TablePropertiesCollection>());
|
||||
s = GetPropertiesOfAllTables(read_options, props_by_level->back().get(),
|
||||
level);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status Version::GetAggregatedTableProperties(
|
||||
const ReadOptions& read_options, std::shared_ptr<const TableProperties>* tp,
|
||||
int level) {
|
||||
@@ -4761,7 +4779,7 @@ void VersionStorageInfo::CalculateBaseBytes(const ImmutableOptions& ioptions,
|
||||
cur_level_size <= base_bytes_min &&
|
||||
(options.preclude_last_level_data_seconds == 0 ||
|
||||
i < num_levels_ - 2)) {
|
||||
// When per_key_placement is enabled, the penultimate level is
|
||||
// When per_key_placement is enabled, the proximal level is
|
||||
// necessary.
|
||||
lowest_unnecessary_level_ = i;
|
||||
}
|
||||
@@ -4903,24 +4921,38 @@ bool VersionStorageInfo::RangeMightExistAfterSortedRun(
|
||||
}
|
||||
|
||||
Env::WriteLifeTimeHint VersionStorageInfo::CalculateSSTWriteHint(
|
||||
int level) const {
|
||||
if (compaction_style_ != kCompactionStyleLevel) {
|
||||
int level, CompactionStyleSet compaction_style_set) const {
|
||||
if (!compaction_style_set.Contains(compaction_style_)) {
|
||||
return Env::WLTH_NOT_SET;
|
||||
}
|
||||
if (level == 0) {
|
||||
return Env::WLTH_MEDIUM;
|
||||
}
|
||||
|
||||
// L1: medium, L2: long, ...
|
||||
if (level - base_level_ >= 2) {
|
||||
return Env::WLTH_EXTREME;
|
||||
} else if (level < base_level_) {
|
||||
// There is no restriction which prevents level passed in to be smaller
|
||||
// than base_level.
|
||||
return Env::WLTH_MEDIUM;
|
||||
switch (compaction_style_) {
|
||||
case kCompactionStyleLevel:
|
||||
if (level == 0) {
|
||||
return Env::WLTH_MEDIUM;
|
||||
}
|
||||
|
||||
// L1: medium, L2: long, ...
|
||||
if (level - base_level_ >= 2) {
|
||||
return Env::WLTH_EXTREME;
|
||||
} else if (level < base_level_) {
|
||||
// There is no restriction which prevents level passed in to be smaller
|
||||
// than base_level.
|
||||
return Env::WLTH_MEDIUM;
|
||||
}
|
||||
return static_cast<Env::WriteLifeTimeHint>(
|
||||
level - base_level_ + static_cast<int>(Env::WLTH_MEDIUM));
|
||||
case kCompactionStyleUniversal:
|
||||
if (level == 0) {
|
||||
return Env::WLTH_SHORT;
|
||||
}
|
||||
if (level == 1) {
|
||||
return Env::WLTH_MEDIUM;
|
||||
}
|
||||
return Env::WLTH_LONG;
|
||||
default:
|
||||
return Env::WLTH_NOT_SET;
|
||||
}
|
||||
return static_cast<Env::WriteLifeTimeHint>(
|
||||
level - base_level_ + static_cast<int>(Env::WLTH_MEDIUM));
|
||||
}
|
||||
|
||||
void Version::AddLiveFiles(std::vector<uint64_t>* live_table_files,
|
||||
@@ -5114,7 +5146,7 @@ VersionSet::VersionSet(
|
||||
BlockCacheTracer* const block_cache_tracer,
|
||||
const std::shared_ptr<IOTracer>& io_tracer, const std::string& db_id,
|
||||
const std::string& db_session_id, const std::string& daily_offpeak_time_utc,
|
||||
ErrorHandler* const error_handler, const bool read_only)
|
||||
ErrorHandler* error_handler, bool unchanging)
|
||||
: column_family_set_(new ColumnFamilySet(
|
||||
dbname, _db_options, storage_options, table_cache,
|
||||
write_buffer_manager, write_controller, block_cache_tracer, io_tracer,
|
||||
@@ -5143,12 +5175,12 @@ VersionSet::VersionSet(
|
||||
db_session_id_(db_session_id),
|
||||
offpeak_time_option_(OffpeakTimeOption(daily_offpeak_time_utc)),
|
||||
error_handler_(error_handler),
|
||||
read_only_(read_only),
|
||||
unchanging_(unchanging),
|
||||
closed_(false) {}
|
||||
|
||||
Status VersionSet::Close(FSDirectory* db_dir, InstrumentedMutex* mu) {
|
||||
Status s;
|
||||
if (closed_ || read_only_ || !manifest_file_number_ || !descriptor_log_) {
|
||||
if (closed_ || unchanging_ || !manifest_file_number_ || !descriptor_log_) {
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -5741,7 +5773,8 @@ Status VersionSet::ProcessManifestWrites(
|
||||
assert(new_cf_options != nullptr);
|
||||
assert(max_last_sequence == descriptor_last_sequence_);
|
||||
CreateColumnFamily(*new_cf_options, read_options,
|
||||
first_writer.edit_list.front());
|
||||
first_writer.edit_list.front(),
|
||||
/*read_only*/ false);
|
||||
} else if (first_writer.edit_list.front()->IsColumnFamilyDrop()) {
|
||||
assert(batch_edits.size() == 1);
|
||||
assert(max_last_sequence == descriptor_last_sequence_);
|
||||
@@ -7294,8 +7327,10 @@ uint64_t VersionSet::GetObsoleteSstFilesSize() const {
|
||||
|
||||
ColumnFamilyData* VersionSet::CreateColumnFamily(
|
||||
const ColumnFamilyOptions& cf_options, const ReadOptions& read_options,
|
||||
const VersionEdit* edit) {
|
||||
const VersionEdit* edit, bool read_only) {
|
||||
assert(edit->IsColumnFamilyAdd());
|
||||
// Unchanging LSM tree implies no writes to the CF
|
||||
assert(!unchanging_ || read_only);
|
||||
|
||||
MutableCFOptions dummy_cf_options;
|
||||
Version* dummy_versions =
|
||||
@@ -7305,7 +7340,7 @@ ColumnFamilyData* VersionSet::CreateColumnFamily(
|
||||
dummy_versions->Ref();
|
||||
auto new_cfd = column_family_set_->CreateColumnFamily(
|
||||
edit->GetColumnFamilyName(), edit->GetColumnFamily(), dummy_versions,
|
||||
cf_options);
|
||||
cf_options, read_only);
|
||||
|
||||
Version* v = new Version(new_cfd, this, file_options_,
|
||||
new_cfd->GetLatestMutableCFOptions(), io_tracer_,
|
||||
@@ -7429,7 +7464,7 @@ ReactiveVersionSet::ReactiveVersionSet(
|
||||
write_buffer_manager, write_controller,
|
||||
/*block_cache_tracer=*/nullptr, io_tracer, /*db_id*/ "",
|
||||
/*db_session_id*/ "", /*daily_offpeak_time_utc*/ "",
|
||||
/*error_handler=*/nullptr, /*read_only=*/true) {}
|
||||
/*error_handler=*/nullptr, /*unchanging=*/false) {}
|
||||
|
||||
ReactiveVersionSet::~ReactiveVersionSet() = default;
|
||||
|
||||
|
||||
+24
-9
@@ -630,7 +630,8 @@ class VersionStorageInfo {
|
||||
const Slice& largest_user_key,
|
||||
int last_level, int last_l0_idx);
|
||||
|
||||
Env::WriteLifeTimeHint CalculateSSTWriteHint(int level) const;
|
||||
Env::WriteLifeTimeHint CalculateSSTWriteHint(
|
||||
int level, CompactionStyleSet compaction_style_set) const;
|
||||
|
||||
const Comparator* user_comparator() const { return user_comparator_; }
|
||||
|
||||
@@ -993,17 +994,21 @@ class Version {
|
||||
const FileMetaData* file_meta,
|
||||
const std::string* fname = nullptr) const;
|
||||
|
||||
// REQUIRES: lock is held
|
||||
// On success, *props will be populated with all SSTables' table properties.
|
||||
// The keys of `props` are the sst file name, the values of `props` are the
|
||||
// tables' properties, represented as std::shared_ptr.
|
||||
Status GetPropertiesOfAllTables(const ReadOptions& read_options,
|
||||
TablePropertiesCollection* props);
|
||||
TablePropertiesCollection* props) const;
|
||||
Status GetPropertiesOfAllTables(const ReadOptions& read_options,
|
||||
TablePropertiesCollection* props, int level);
|
||||
TablePropertiesCollection* props,
|
||||
int level) const;
|
||||
Status GetPropertiesOfTablesInRange(const ReadOptions& read_options,
|
||||
const autovector<UserKeyRange>& ranges,
|
||||
TablePropertiesCollection* props) const;
|
||||
Status GetPropertiesOfTablesByLevel(
|
||||
const ReadOptions& read_options,
|
||||
std::vector<std::unique_ptr<TablePropertiesCollection>>* props_by_level)
|
||||
const;
|
||||
|
||||
// Print summary of range delete tombstones in SST files into out_str,
|
||||
// with maximum max_entries_to_print entries printed out.
|
||||
@@ -1174,6 +1179,9 @@ class AtomicGroupReadBuffer {
|
||||
// VersionSet is the collection of versions of all the column families of the
|
||||
// database. Each database owns one VersionSet. A VersionSet has access to all
|
||||
// column families via ColumnFamilySet, i.e. set of the column families.
|
||||
// `unchanging` means the LSM tree structure of the column families will not
|
||||
// change during the lifetime of this VersionSet (true for read-only instance,
|
||||
// but false for secondary instance or writable DB).
|
||||
class VersionSet {
|
||||
public:
|
||||
VersionSet(const std::string& dbname, const ImmutableDBOptions* db_options,
|
||||
@@ -1184,7 +1192,7 @@ class VersionSet {
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::string& db_id, const std::string& db_session_id,
|
||||
const std::string& daily_offpeak_time_utc,
|
||||
ErrorHandler* const error_handler, const bool read_only);
|
||||
ErrorHandler* error_handler, bool unchanging);
|
||||
// No copying allowed
|
||||
VersionSet(const VersionSet&) = delete;
|
||||
void operator=(const VersionSet&) = delete;
|
||||
@@ -1263,8 +1271,11 @@ class VersionSet {
|
||||
void WakeUpWaitingManifestWriters();
|
||||
|
||||
// Recover the last saved descriptor (MANIFEST) from persistent storage.
|
||||
// If read_only == true, Recover() will not complain if some column families
|
||||
// are not opened
|
||||
// Unlike `unchanging` on the VersionSet, `read_only` here and in other
|
||||
// functions below refers to the CF receiving no writes or modifications
|
||||
// through this VersionSet, but could through external manifest updates
|
||||
// etc. Thus, `read_only=true` for secondary instances as well as read-only
|
||||
// instances.
|
||||
Status Recover(const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
bool read_only = false, std::string* db_id = nullptr,
|
||||
bool no_error_if_files_missing = false, bool is_retry = false,
|
||||
@@ -1342,6 +1353,8 @@ class VersionSet {
|
||||
return min_log_number_to_keep_.load();
|
||||
}
|
||||
|
||||
bool unchanging() const { return unchanging_; }
|
||||
|
||||
// Allocate and return a new file number
|
||||
uint64_t NewFileNumber() { return next_file_number_.fetch_add(1); }
|
||||
|
||||
@@ -1573,6 +1586,8 @@ class VersionSet {
|
||||
AppendVersion(cfd, version);
|
||||
}
|
||||
|
||||
bool& TEST_unchanging() { return const_cast<bool&>(unchanging_); }
|
||||
|
||||
protected:
|
||||
struct ManifestWriter;
|
||||
|
||||
@@ -1625,7 +1640,7 @@ class VersionSet {
|
||||
|
||||
ColumnFamilyData* CreateColumnFamily(const ColumnFamilyOptions& cf_options,
|
||||
const ReadOptions& read_options,
|
||||
const VersionEdit* edit);
|
||||
const VersionEdit* edit, bool read_only);
|
||||
|
||||
Status VerifyFileMetadata(const ReadOptions& read_options,
|
||||
ColumnFamilyData* cfd, const std::string& fpath,
|
||||
@@ -1722,7 +1737,7 @@ class VersionSet {
|
||||
VersionEdit* edit, SequenceNumber* max_last_sequence,
|
||||
InstrumentedMutex* mu);
|
||||
|
||||
const bool read_only_;
|
||||
const bool unchanging_;
|
||||
bool closed_;
|
||||
};
|
||||
|
||||
|
||||
+11
-8
@@ -26,6 +26,7 @@
|
||||
#include "test_util/mock_time_env.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/defer.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -1905,7 +1906,7 @@ TEST_F(VersionSetTest, WalAddition) {
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*read_only=*/false));
|
||||
/*error_handler=*/nullptr, /*unchanging=*/false));
|
||||
ASSERT_OK(new_versions->Recover(column_families_, /*read_only=*/false));
|
||||
const auto& wals = new_versions->GetWalSet().GetWals();
|
||||
ASSERT_EQ(wals.size(), 1);
|
||||
@@ -1973,7 +1974,7 @@ TEST_F(VersionSetTest, WalCloseWithoutSync) {
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*read_only=*/false));
|
||||
/*error_handler=*/nullptr, /*unchanging=*/false));
|
||||
ASSERT_OK(new_versions->Recover(column_families_, false));
|
||||
const auto& wals = new_versions->GetWalSet().GetWals();
|
||||
ASSERT_EQ(wals.size(), 2);
|
||||
@@ -2027,7 +2028,7 @@ TEST_F(VersionSetTest, WalDeletion) {
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*read_only=*/false));
|
||||
/*error_handler=*/nullptr, /*unchanging=*/false));
|
||||
ASSERT_OK(new_versions->Recover(column_families_, false));
|
||||
const auto& wals = new_versions->GetWalSet().GetWals();
|
||||
ASSERT_EQ(wals.size(), 1);
|
||||
@@ -2066,7 +2067,7 @@ TEST_F(VersionSetTest, WalDeletion) {
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*read_only=*/false));
|
||||
/*error_handler=*/nullptr, /*unchanging=*/false));
|
||||
ASSERT_OK(new_versions->Recover(column_families_, false));
|
||||
const auto& wals = new_versions->GetWalSet().GetWals();
|
||||
ASSERT_EQ(wals.size(), 1);
|
||||
@@ -2187,7 +2188,7 @@ TEST_F(VersionSetTest, DeleteWalsBeforeNonExistingWalNumber) {
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*read_only=*/false));
|
||||
/*error_handler=*/nullptr, /*unchanging=*/false));
|
||||
ASSERT_OK(new_versions->Recover(column_families_, false));
|
||||
const auto& wals = new_versions->GetWalSet().GetWals();
|
||||
ASSERT_EQ(wals.size(), 1);
|
||||
@@ -2224,7 +2225,7 @@ TEST_F(VersionSetTest, DeleteAllWals) {
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*read_only=*/false));
|
||||
/*error_handler=*/nullptr, /*unchanging=*/false));
|
||||
ASSERT_OK(new_versions->Recover(column_families_, false));
|
||||
const auto& wals = new_versions->GetWalSet().GetWals();
|
||||
ASSERT_EQ(wals.size(), 0);
|
||||
@@ -2267,7 +2268,7 @@ TEST_F(VersionSetTest, AtomicGroupWithWalEdits) {
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*read_only=*/false));
|
||||
/*error_handler=*/nullptr, /*unchanging=*/false));
|
||||
std::string db_id;
|
||||
ASSERT_OK(
|
||||
new_versions->Recover(column_families_, /*read_only=*/false, &db_id));
|
||||
@@ -2447,7 +2448,7 @@ class VersionSetWithTimestampTest : public VersionSetTest {
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*read_only=*/false));
|
||||
/*error_handler=*/nullptr, /*unchanging=*/false));
|
||||
ASSERT_OK(vset->Recover(column_families_, /*read_only=*/false,
|
||||
/*db_id=*/nullptr));
|
||||
for (auto* cfd : *(vset->GetColumnFamilySet())) {
|
||||
@@ -3749,6 +3750,8 @@ TEST_P(VersionSetTestEmptyDb, OpenCompleteManifest) {
|
||||
}
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
SaveAndRestore<bool> override_unchanging(&versions_->TEST_unchanging(),
|
||||
read_only);
|
||||
s = versions_->TryRecoverFromOneManifest(manifest_path, column_families,
|
||||
read_only, &db_id,
|
||||
&has_missing_table_file);
|
||||
|
||||
@@ -283,6 +283,7 @@ void WalManager::ArchiveWALFile(const std::string& fname, uint64_t number) {
|
||||
// The sync point below is used in (DBTest,TransactionLogIteratorRace)
|
||||
TEST_SYNC_POINT("WalManager::PurgeObsoleteFiles:1");
|
||||
Status s = env_->RenameFile(fname, archived_log_name);
|
||||
IGNORE_STATUS_IF_ERROR(s);
|
||||
// The sync point below is used in (DBTest,TransactionLogIteratorRace)
|
||||
TEST_SYNC_POINT("WalManager::PurgeObsoleteFiles:2");
|
||||
// The sync point below is used in
|
||||
|
||||
@@ -137,7 +137,7 @@ class SharedState {
|
||||
for (int i = 0; i < FLAGS_column_families; ++i) {
|
||||
key_locks_[i].reset(new port::Mutex[num_locks]);
|
||||
}
|
||||
if (FLAGS_read_fault_one_in) {
|
||||
if (FLAGS_read_fault_one_in || FLAGS_metadata_read_fault_one_in) {
|
||||
#ifdef NDEBUG
|
||||
// Unsupported in release mode because it relies on
|
||||
// `IGNORE_STATUS_IF_ERROR` to distinguish faults not expected to lead to
|
||||
|
||||
@@ -1600,12 +1600,6 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
Slice ub_slice;
|
||||
ReadOptions ro_copy = read_opts;
|
||||
|
||||
// There is a narrow window in iterator auto refresh run where injected read
|
||||
// errors are simply untraceable, ex. failure to delete file as a part of
|
||||
// superversion cleanup callback invoked by the DBIter destructor.
|
||||
bool ignore_injected_read_error_in_iter =
|
||||
ro_copy.auto_refresh_iterator_with_snapshot;
|
||||
|
||||
// Randomly test with `iterate_upper_bound` and `prefix_same_as_start`
|
||||
//
|
||||
// Get the next prefix first and then see if we want to set it to be the
|
||||
@@ -1698,8 +1692,7 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
FaultInjectionIOType::kRead),
|
||||
fault_fs_guard->GetAndResetInjectedThreadLocalErrorCount(
|
||||
FaultInjectionIOType::kMetadataRead));
|
||||
if (!ignore_injected_read_error_in_iter &&
|
||||
!SharedState::ignore_read_error && injected_error_count > 0 &&
|
||||
if (!SharedState::ignore_read_error && injected_error_count > 0 &&
|
||||
s.ok()) {
|
||||
// Grab mutex so multiple thread don't try to print the
|
||||
// stack trace at the same time
|
||||
|
||||
Vendored
+31
@@ -732,6 +732,37 @@ std::string Env::PriorityToString(Env::Priority priority) {
|
||||
return "Invalid";
|
||||
}
|
||||
|
||||
std::string Env::IOActivityToString(IOActivity activity) {
|
||||
switch (activity) {
|
||||
case Env::IOActivity::kFlush:
|
||||
return "Flush";
|
||||
case Env::IOActivity::kCompaction:
|
||||
return "Compaction";
|
||||
case Env::IOActivity::kDBOpen:
|
||||
return "DBOpen";
|
||||
case Env::IOActivity::kGet:
|
||||
return "Get";
|
||||
case Env::IOActivity::kMultiGet:
|
||||
return "MultiGet";
|
||||
case Env::IOActivity::kDBIterator:
|
||||
return "DBIterator";
|
||||
case Env::IOActivity::kVerifyDBChecksum:
|
||||
return "VerifyDBChecksum";
|
||||
case Env::IOActivity::kVerifyFileChecksums:
|
||||
return "VerifyFileChecksums";
|
||||
case Env::IOActivity::kGetEntity:
|
||||
return "GetEntity";
|
||||
case Env::IOActivity::kMultiGetEntity:
|
||||
return "MultiGetEntity";
|
||||
case Env::IOActivity::kGetFileChecksumsFromCurrentManifest:
|
||||
return "GetFileChecksumsFromCurrentManifest";
|
||||
case Env::IOActivity::kUnknown:
|
||||
return "Unknown";
|
||||
};
|
||||
assert(false);
|
||||
return "Invalid";
|
||||
}
|
||||
|
||||
uint64_t Env::GetThreadID() const {
|
||||
std::hash<std::thread::id> hasher;
|
||||
return hasher(std::this_thread::get_id());
|
||||
|
||||
@@ -130,6 +130,7 @@ Status DeleteScheduler::AddFileToDeletionQueue(const std::string& file_path,
|
||||
s.ToString().c_str());
|
||||
|
||||
if (!s.ok()) {
|
||||
IGNORE_STATUS_IF_ERROR(s);
|
||||
ROCKS_LOG_ERROR(info_log_, "Failed to mark %s as trash -- %s",
|
||||
file_path.c_str(), s.ToString().c_str());
|
||||
s = fs_->DeleteFile(file_path, IOOptions(), nullptr);
|
||||
@@ -151,6 +152,7 @@ Status DeleteScheduler::AddFileToDeletionQueue(const std::string& file_path,
|
||||
if (io_s.ok()) {
|
||||
total_trash_size_.fetch_add(trash_file_size);
|
||||
}
|
||||
IGNORE_STATUS_IF_ERROR(s);
|
||||
}
|
||||
//**TODO: What should we do if we failed to
|
||||
// get the file size?
|
||||
|
||||
@@ -300,7 +300,7 @@ struct CompressedSecondaryCacheOptions : LRUCacheOptions {
|
||||
|
||||
// compress_format_version can have two values:
|
||||
// compress_format_version == 1 -- decompressed size is not included in the
|
||||
// block header.
|
||||
// block header. DEPRECATED
|
||||
// compress_format_version == 2 -- decompressed size is included in the block
|
||||
// header in varint32 format.
|
||||
uint32_t compress_format_version = 2;
|
||||
|
||||
@@ -33,6 +33,8 @@ struct CompactionJobStats {
|
||||
uint64_t num_blobs_read = 0;
|
||||
// the number of compaction input files (table files)
|
||||
size_t num_input_files = 0;
|
||||
// The number of input files that get trivially moved.
|
||||
size_t num_input_files_trivially_moved = 0;
|
||||
// the number of compaction input files at the output level (table files)
|
||||
size_t num_input_files_at_output_level = 0;
|
||||
// the number of compaction input files that are filtered out by compaction
|
||||
@@ -118,6 +120,6 @@ struct CompactionJobStats {
|
||||
// number of single-deletes which meet something other than a put
|
||||
uint64_t num_single_del_mismatch = 0;
|
||||
|
||||
// TODO: Add output_to_penultimate_level output information
|
||||
// TODO: Add output_to_proximal_level output information
|
||||
};
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -410,7 +410,7 @@ Status GetStringFromColumnFamilyOptions(std::string* opts_str,
|
||||
Status GetStringFromCompressionType(std::string* compression_str,
|
||||
CompressionType compression_type);
|
||||
|
||||
std::vector<CompressionType> GetSupportedCompressions();
|
||||
const std::vector<CompressionType>& GetSupportedCompressions();
|
||||
|
||||
Status GetBlockBasedTableOptionsFromString(
|
||||
const ConfigOptions& config_options,
|
||||
@@ -450,6 +450,22 @@ Status DeleteFilesInRange(DB* db, ColumnFamilyHandle* column_family,
|
||||
// Delete files in multiple ranges at once
|
||||
// Delete files in a lot of ranges one at a time can be slow, use this API for
|
||||
// better performance in that case.
|
||||
Status DeleteFilesInRanges(DB* db, ColumnFamilyHandle* column_family,
|
||||
const RangeOpt* ranges, size_t n,
|
||||
bool include_end = true);
|
||||
|
||||
// DEPRECATED
|
||||
struct RangePtr {
|
||||
// In case of user_defined timestamp, if enabled, `start` and `limit` should
|
||||
// point to key without timestamp part.
|
||||
const Slice* start;
|
||||
const Slice* limit;
|
||||
|
||||
RangePtr() : start(nullptr), limit(nullptr) {}
|
||||
RangePtr(const Slice* s, const Slice* l) : start(s), limit(l) {}
|
||||
};
|
||||
|
||||
// DEPRECATED
|
||||
Status DeleteFilesInRanges(DB* db, ColumnFamilyHandle* column_family,
|
||||
const RangePtr* ranges, size_t n,
|
||||
bool include_end = true);
|
||||
|
||||
+5
-45
@@ -96,43 +96,6 @@ class ColumnFamilyHandle {
|
||||
static const int kMajorVersion = __ROCKSDB_MAJOR__;
|
||||
static const int kMinorVersion = __ROCKSDB_MINOR__;
|
||||
|
||||
// A range of keys
|
||||
struct Range {
|
||||
// In case of user_defined timestamp, if enabled, `start` and `limit` should
|
||||
// point to key without timestamp part.
|
||||
Slice start;
|
||||
Slice limit;
|
||||
|
||||
Range() {}
|
||||
Range(const Slice& s, const Slice& l) : start(s), limit(l) {}
|
||||
};
|
||||
|
||||
struct RangePtr {
|
||||
// In case of user_defined timestamp, if enabled, `start` and `limit` should
|
||||
// point to key without timestamp part.
|
||||
const Slice* start;
|
||||
const Slice* limit;
|
||||
|
||||
RangePtr() : start(nullptr), limit(nullptr) {}
|
||||
RangePtr(const Slice* s, const Slice* l) : start(s), limit(l) {}
|
||||
};
|
||||
|
||||
// It is valid that files_checksums and files_checksum_func_names are both
|
||||
// empty (no checksum information is provided for ingestion). Otherwise,
|
||||
// their sizes should be the same as external_files. The file order should
|
||||
// be the same in three vectors and guaranteed by the caller.
|
||||
// Note that, we assume the temperatures of this batch of files to be
|
||||
// ingested are the same.
|
||||
struct IngestExternalFileArg {
|
||||
ColumnFamilyHandle* column_family = nullptr;
|
||||
std::vector<std::string> external_files;
|
||||
IngestExternalFileOptions options;
|
||||
std::vector<std::string> files_checksums;
|
||||
std::vector<std::string> files_checksum_func_names;
|
||||
// A hint as to the temperature for *reading* the files to be ingested.
|
||||
Temperature file_temperature = Temperature::kUnknown;
|
||||
};
|
||||
|
||||
struct GetMergeOperandsOptions {
|
||||
using ContinueCallback = std::function<bool(Slice)>;
|
||||
|
||||
@@ -2081,14 +2044,11 @@ class DB {
|
||||
ColumnFamilyHandle* column_family, const Range* range, std::size_t n,
|
||||
TablePropertiesCollection* props) = 0;
|
||||
|
||||
// Get the table properties of files per level.
|
||||
virtual Status GetPropertiesOfTablesForLevels(
|
||||
ColumnFamilyHandle* /* column_family */,
|
||||
std::vector<
|
||||
std::unique_ptr<TablePropertiesCollection>>* /* levels_props */) {
|
||||
return Status::NotSupported(
|
||||
"GetPropertiesOfTablesForLevels() is not implemented.");
|
||||
}
|
||||
// Get the table properties of files by level.
|
||||
virtual Status GetPropertiesOfTablesByLevel(
|
||||
ColumnFamilyHandle* column_family,
|
||||
std::vector<std::unique_ptr<TablePropertiesCollection>>*
|
||||
props_by_level) = 0;
|
||||
|
||||
virtual Status SuggestCompactRange(ColumnFamilyHandle* /*column_family*/,
|
||||
const Slice* /*begin*/,
|
||||
|
||||
@@ -455,10 +455,12 @@ class Env : public Customizable {
|
||||
kVerifyFileChecksums = 7,
|
||||
kGetEntity = 8,
|
||||
kMultiGetEntity = 9,
|
||||
kReadManifest = 10,
|
||||
kGetFileChecksumsFromCurrentManifest = 10,
|
||||
kUnknown, // Keep last for easy array of non-unknowns
|
||||
};
|
||||
|
||||
static std::string IOActivityToString(IOActivity activity);
|
||||
|
||||
// Arrange to run "(*function)(arg)" once in a background thread, in
|
||||
// the thread pool specified by pri. By default, jobs go to the 'LOW'
|
||||
// priority thread pool.
|
||||
|
||||
@@ -192,6 +192,12 @@ struct FileOptions : EnvOptions {
|
||||
// handoff during file writes.
|
||||
ChecksumType handoff_checksum_type;
|
||||
|
||||
// Expose write lifetime hint on the FileOptions level to provide more
|
||||
// flexibility in setting the hint in downstream, custom implementations
|
||||
// that might be able to process the hint only at the time of the actual
|
||||
// FSWritableFile object creation.
|
||||
Env::WriteLifeTimeHint write_hint = Env::WLTH_NOT_SET;
|
||||
|
||||
FileOptions() : EnvOptions(), handoff_checksum_type(ChecksumType::kCRC32c) {}
|
||||
|
||||
FileOptions(const DBOptions& opts)
|
||||
@@ -206,7 +212,8 @@ struct FileOptions : EnvOptions {
|
||||
: EnvOptions(opts),
|
||||
io_options(opts.io_options),
|
||||
temperature(opts.temperature),
|
||||
handoff_checksum_type(opts.handoff_checksum_type) {}
|
||||
handoff_checksum_type(opts.handoff_checksum_type),
|
||||
write_hint(opts.write_hint) {}
|
||||
|
||||
FileOptions& operator=(const FileOptions&) = default;
|
||||
};
|
||||
|
||||
@@ -439,6 +439,9 @@ struct CompactionJobInfo {
|
||||
// the job id, which is unique in the same thread.
|
||||
int job_id;
|
||||
|
||||
// the number of L0 files in the CF right before and after the compaction
|
||||
int num_l0_files;
|
||||
|
||||
// the smallest input level of the compaction.
|
||||
int base_input_level;
|
||||
// the output level of the compaction.
|
||||
|
||||
@@ -62,6 +62,8 @@ struct Options;
|
||||
struct DbPath;
|
||||
|
||||
using FileTypeSet = SmallEnumSet<FileType, FileType::kBlobFile>;
|
||||
using CompactionStyleSet =
|
||||
SmallEnumSet<CompactionStyle, CompactionStyle::kCompactionStyleNone>;
|
||||
|
||||
struct ColumnFamilyOptions : public AdvancedColumnFamilyOptions {
|
||||
// The function recovers options to a previous version. Only 4.6 or later
|
||||
@@ -1620,6 +1622,24 @@ struct DBOptions {
|
||||
// `kUnknown`, this overrides any temperature set by OptimizeForLogWrite
|
||||
// functions.
|
||||
Temperature wal_write_temperature = Temperature::kUnknown;
|
||||
|
||||
// Enum set indicative of which compaction styles SST write lifetime hint
|
||||
// calculation is allowed on. Today, RocksDB provides native support for
|
||||
// kCompactionStyleLevel and kCompactionStyleUniversal (experimental version).
|
||||
// Other compaction styles, even when enabled in the set, won't have any
|
||||
// effect in the default PosixWritableFile file implementation. There are
|
||||
// numerous benefits coming from employing the hints including reduction in
|
||||
// write amplification caused by OS file movement during garbage collection,
|
||||
// and reduction in wear-leveling (SSDs). However, as currently implemented,
|
||||
// SST write lifetime hints are calculated in a static way and solely based on
|
||||
// the level, which might not be suitable for non-uniform workloads with
|
||||
// dynamic / high-variance lifespan of data within the same level. In those
|
||||
// cases (or when the performance is not satisfactory), it's recommended to
|
||||
// disable the hints by assigning the setting to the empty set (= {});
|
||||
//
|
||||
// Default: Enabled in kCompactionStyleLevel mode.
|
||||
CompactionStyleSet calculate_sst_write_lifetime_hint_set = {
|
||||
CompactionStyle::kCompactionStyleLevel};
|
||||
// End EXPERIMENTAL
|
||||
};
|
||||
|
||||
@@ -2225,6 +2245,29 @@ struct CompactRangeOptions {
|
||||
double blob_garbage_collection_age_cutoff = -1;
|
||||
};
|
||||
|
||||
// A range of keys. In case of user_defined timestamp, if enabled, `start` and
|
||||
// `limit` should point to key without timestamp part.
|
||||
struct Range {
|
||||
Slice start;
|
||||
Slice limit;
|
||||
|
||||
Range() {}
|
||||
Range(const Slice& s, const Slice& l) : start(s), limit(l) {}
|
||||
};
|
||||
|
||||
// A key range with optional endpoints. In case of user_defined timestamp, if
|
||||
// enabled, `start` and `limit` should point to key without timestamp part.
|
||||
struct RangeOpt {
|
||||
// When start.has_value() == false, refers to starting before every key
|
||||
OptSlice start;
|
||||
// When limit.has_value() == false, refers to ending after every key
|
||||
OptSlice limit;
|
||||
|
||||
RangeOpt() {}
|
||||
RangeOpt(const OptSlice& s, const OptSlice& l) : start(s), limit(l) {}
|
||||
// RangeOpt(const Slice& s, const Slice& l) : start(s), limit(l) {}
|
||||
};
|
||||
|
||||
// IngestExternalFileOptions is used by IngestExternalFile()
|
||||
struct IngestExternalFileOptions {
|
||||
// Can be set to true to move the files instead of copying them.
|
||||
@@ -2247,8 +2290,11 @@ struct IngestExternalFileOptions {
|
||||
// during file ingestion in the DB (the conditions under which a global_seqno
|
||||
// must be assigned to the ingested file).
|
||||
bool allow_global_seqno = true;
|
||||
// If set to false and the file key range overlaps with the memtable key range
|
||||
// (memtable flush required), IngestExternalFile will fail.
|
||||
// Normally (true), IngestExternalFile() will trigger and block for flushing
|
||||
// memtable(s) if there is overlap between ingested files and memtable(s). If
|
||||
// allow_blocking_flush is set to false, IngestExternalFile() will fail if the
|
||||
// file key range overlaps with the memtable key range (memtable flush
|
||||
// required).
|
||||
bool allow_blocking_flush = true;
|
||||
// Set to true if you would like duplicate keys in the file being ingested
|
||||
// to be skipped rather than overwriting existing data under that key.
|
||||
@@ -2331,6 +2377,44 @@ struct IngestExternalFileOptions {
|
||||
bool fill_cache = true;
|
||||
};
|
||||
|
||||
// It is valid that files_checksums and files_checksum_func_names are both
|
||||
// empty (no checksum information is provided for ingestion). Otherwise,
|
||||
// their sizes should be the same as external_files. The file order should
|
||||
// be the same in three vectors and guaranteed by the caller.
|
||||
// Note that, we assume the temperatures of this batch of files to be
|
||||
// ingested are the same.
|
||||
struct IngestExternalFileArg {
|
||||
ColumnFamilyHandle* column_family = nullptr;
|
||||
std::vector<std::string> external_files;
|
||||
IngestExternalFileOptions options;
|
||||
std::vector<std::string> files_checksums;
|
||||
std::vector<std::string> files_checksum_func_names;
|
||||
// A hint as to the temperature for *reading* the files to be ingested.
|
||||
Temperature file_temperature = Temperature::kUnknown;
|
||||
// EXPERIMENTAL: When specified, existing keys in the given range will be
|
||||
// cleared atomically as part of the ingestion, where the ingested files are
|
||||
// logically applied on top of the cleared key range.
|
||||
// * If both `start` and `limit` are nullptr, the entire column family is
|
||||
// cleared; however, setting just one bound to nullptr is not yet supported.
|
||||
// * When a range is specified, all the external files in this batch must
|
||||
// be contained in that key range.
|
||||
// * Checks for memtable overlap and possible blocking flush will apply
|
||||
// to this range (not just the file ranges).
|
||||
// * Not compatible with ingest_behind=true.
|
||||
// * When options.snapshot_consistency = false, the range is cleared
|
||||
// similarly to DeleteFilesInRange, but fails if any files overlap the range
|
||||
// only partially.
|
||||
// * It is recommended to use fail_if_not_bottommost_level=true to ensure
|
||||
// data in the key range is ingested to a single compacted level (the
|
||||
// last level). (fail_if_not_bottommost_level=false allows overlap between
|
||||
// the ingested files.)
|
||||
// * options.snapshot_consistency = true is not yet supported.
|
||||
// BUG: the upper bound of the range may be interpreted as inclusive or
|
||||
// exclusive, so it is best not to depend on one or the other until it is
|
||||
// sorted out.
|
||||
std::optional<RangeOpt> atomic_replace_range;
|
||||
};
|
||||
|
||||
enum TraceFilterType : uint64_t {
|
||||
// Trace all the operations
|
||||
kTraceFilterNone = 0x0,
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
@@ -129,6 +130,46 @@ class Slice {
|
||||
// Intentionally copyable
|
||||
};
|
||||
|
||||
// A likely more efficient alternative to std::optional<Slice>. For example,
|
||||
// an empty key might be distinct from "not specified" (and Slice* as an
|
||||
// optional is more troublesome to deal with).
|
||||
class OptSlice {
|
||||
public:
|
||||
OptSlice() : slice_(nullptr, SIZE_MAX) {}
|
||||
/*implicit*/ OptSlice(const Slice& s) : slice_(s) {}
|
||||
/*implicit*/ OptSlice(const std::string& s) : slice_(s) {}
|
||||
/*implicit*/ OptSlice(const std::string_view& sv) : slice_(sv) {}
|
||||
/*implicit*/ OptSlice(const char* c_str) : slice_(c_str) {}
|
||||
// For easier migrating from APIs uing Slice* as an optional type.
|
||||
// CAUTION: OptSlice{nullptr} is "no value" while Slice{nullptr} is "empty"
|
||||
/*implicit*/ OptSlice(std::nullptr_t) : OptSlice() {}
|
||||
|
||||
bool has_value() const noexcept { return slice_.size() != SIZE_MAX; }
|
||||
explicit operator bool() const noexcept { return has_value(); }
|
||||
|
||||
const Slice& value() const noexcept {
|
||||
assert(has_value());
|
||||
return slice_;
|
||||
}
|
||||
const Slice& operator*() const noexcept { return value(); }
|
||||
const Slice* operator->() const noexcept { return &value(); }
|
||||
|
||||
const Slice* AsPtr() const noexcept {
|
||||
return has_value() ? &slice_ : nullptr;
|
||||
}
|
||||
// Populate from an optional pointer. This is a very explicit conversion
|
||||
// to minimize risk of bugs as in
|
||||
// Slice start, limit;
|
||||
// RangeOpt rng = {&start, &limit};
|
||||
// start = ...; // BUG: would not affect rng
|
||||
static OptSlice CopyFromPtr(const Slice* ptr) {
|
||||
return ptr ? OptSlice{*ptr} : OptSlice{};
|
||||
}
|
||||
|
||||
protected:
|
||||
Slice slice_;
|
||||
};
|
||||
|
||||
/**
|
||||
* A Slice that can be pinned with some cleanup tasks, which will be run upon
|
||||
* ::Reset() or object destruction, whichever is invoked first. This can be used
|
||||
|
||||
+16
-8
@@ -126,7 +126,15 @@ struct CacheUsageOptions {
|
||||
};
|
||||
|
||||
// Configures how SST files using the block-based table format (standard)
|
||||
// are written and read.
|
||||
// are written and read. With few exceptions, each option only affects either
|
||||
// (a) how new SST files are written, or (b) how SST files are read. If an
|
||||
// option seems to affect how the SST file is constructed, e.g. format_version,
|
||||
// that option *ONLY* has an effect at construction time. Contrast this with
|
||||
// options like the various `cache` and `pin` options, that only affect
|
||||
// in-memory and IO behavior at read time. In general, any version of RocksDB
|
||||
// able to read the full key-value and indexing data in the SST file will read
|
||||
// it as written regardless of current options for writing new files. See
|
||||
// filter_policy regarding filters.
|
||||
//
|
||||
// Except as specifically noted, all options here are "mutable" using
|
||||
// SetOptions(), with the caveat that only new table builders and new table
|
||||
@@ -480,6 +488,10 @@ struct BlockBasedTableOptions {
|
||||
// If non-nullptr, use the specified filter policy to reduce disk reads.
|
||||
// Many applications will benefit from passing the result of
|
||||
// NewBloomFilterPolicy() here.
|
||||
//
|
||||
// Because filters only impact performance and are not data-critical, an
|
||||
// SST file can be opened and used without filters if (a) the filter
|
||||
// policy name or schema is unrecognized, or (b) filter_policy is nullptr.
|
||||
std::shared_ptr<const FilterPolicy> filter_policy = nullptr;
|
||||
|
||||
// If true, place whole keys in the filter (not just prefixes).
|
||||
@@ -524,13 +536,9 @@ struct BlockBasedTableOptions {
|
||||
// Default: 0 (disabled)
|
||||
uint32_t read_amp_bytes_per_bit = 0;
|
||||
|
||||
// We currently have these versions:
|
||||
// 0 -- This version can be read by really old RocksDB's. Doesn't support
|
||||
// changing checksum type (default is CRC32).
|
||||
// 1 -- Can be read by RocksDB's versions since 3.0. Supports non-default
|
||||
// checksum, like xxHash. It is written by RocksDB when
|
||||
// BlockBasedTableOptions::checksum is something other than kCRC32c. (version
|
||||
// 0 is silently upconverted)
|
||||
// We currently have these format versions:
|
||||
// 0 - 1 -- Unsupported for writing new files and quietly sanitized to 2.
|
||||
// Read support is deprecated and could be removed in the future.
|
||||
// 2 -- Can be read by RocksDB's versions since 3.10. Changes the way we
|
||||
// encode compressed blocks with LZ4, BZip2 and Zlib compression. If you
|
||||
// don't plan to run RocksDB before version 3.10, you should probably use
|
||||
|
||||
@@ -64,7 +64,7 @@ struct ThreadStatus {
|
||||
OP_VERIFY_FILE_CHECKSUMS,
|
||||
OP_GETENTITY,
|
||||
OP_MULTIGETENTITY,
|
||||
OP_READ_MANIFEST,
|
||||
OP_GET_FILE_CHECKSUMS_FROM_CURRENT_MANIFEST,
|
||||
NUM_OP_TYPES
|
||||
};
|
||||
|
||||
|
||||
@@ -33,12 +33,12 @@ struct KeyVersion {
|
||||
// copied to memory, if the range covers too many keys, the memory usage
|
||||
// may be huge. `max_num_ikeys` can be used to cap the memory usage.
|
||||
// The result is inserted into the provided vector, `key_versions`.
|
||||
Status GetAllKeyVersions(DB* db, Slice begin_key, Slice end_key,
|
||||
Status GetAllKeyVersions(DB* db, OptSlice begin_key, OptSlice end_key,
|
||||
size_t max_num_ikeys,
|
||||
std::vector<KeyVersion>* key_versions);
|
||||
|
||||
Status GetAllKeyVersions(DB* db, ColumnFamilyHandle* cfh, Slice begin_key,
|
||||
Slice end_key, size_t max_num_ikeys,
|
||||
Status GetAllKeyVersions(DB* db, ColumnFamilyHandle* cfh, OptSlice begin_key,
|
||||
OptSlice end_key, size_t max_num_ikeys,
|
||||
std::vector<KeyVersion>* key_versions);
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -554,6 +554,14 @@ class StackableDB : public DB {
|
||||
return db_->GetPropertiesOfTablesInRange(column_family, range, n, props);
|
||||
}
|
||||
|
||||
using DB::GetPropertiesOfTablesByLevel;
|
||||
Status GetPropertiesOfTablesByLevel(
|
||||
ColumnFamilyHandle* column_family,
|
||||
std::vector<std::unique_ptr<TablePropertiesCollection>>* props_by_level)
|
||||
override {
|
||||
return db_->GetPropertiesOfTablesByLevel(column_family, props_by_level);
|
||||
}
|
||||
|
||||
Status GetUpdatesSince(
|
||||
SequenceNumber seq_number, std::unique_ptr<TransactionLogIterator>* iter,
|
||||
const TransactionLogIterator::ReadOptions& read_options) override {
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
// NOTE: in 'main' development branch, this should be the *next*
|
||||
// minor or major version number planned for release.
|
||||
#define ROCKSDB_MAJOR 10
|
||||
#define ROCKSDB_MINOR 0
|
||||
#define ROCKSDB_PATCH 1
|
||||
#define ROCKSDB_MINOR 2
|
||||
#define ROCKSDB_PATCH 0
|
||||
|
||||
// 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
|
||||
|
||||
@@ -3637,7 +3637,7 @@ void Java_org_rocksdb_RocksDB_destroyDB(JNIEnv* env, jclass, jstring jdb_path,
|
||||
}
|
||||
|
||||
bool get_slice_helper(JNIEnv* env, jobjectArray ranges, jsize index,
|
||||
std::unique_ptr<ROCKSDB_NAMESPACE::Slice>& slice,
|
||||
ROCKSDB_NAMESPACE::OptSlice& opt_slice,
|
||||
std::vector<std::unique_ptr<jbyte[]>>& ranges_to_free) {
|
||||
jobject jArray = env->GetObjectArrayElement(ranges, index);
|
||||
if (env->ExceptionCheck()) {
|
||||
@@ -3659,8 +3659,8 @@ bool get_slice_helper(JNIEnv* env, jobjectArray ranges, jsize index,
|
||||
return false;
|
||||
}
|
||||
env->DeleteLocalRef(jArray);
|
||||
slice.reset(new ROCKSDB_NAMESPACE::Slice(
|
||||
reinterpret_cast<char*>(ranges_to_free.back().get()), len_ba));
|
||||
opt_slice = ROCKSDB_NAMESPACE::Slice(
|
||||
reinterpret_cast<char*>(ranges_to_free.back().get()), len_ba);
|
||||
return true;
|
||||
}
|
||||
/*
|
||||
@@ -3675,24 +3675,24 @@ void Java_org_rocksdb_RocksDB_deleteFilesInRanges(JNIEnv* env, jclass /*jdb*/,
|
||||
jboolean include_end) {
|
||||
jsize length = env->GetArrayLength(ranges);
|
||||
|
||||
std::vector<ROCKSDB_NAMESPACE::RangePtr> rangesVector;
|
||||
std::vector<std::unique_ptr<ROCKSDB_NAMESPACE::Slice>> slices;
|
||||
std::vector<ROCKSDB_NAMESPACE::RangeOpt> rangesVector;
|
||||
std::vector<ROCKSDB_NAMESPACE::OptSlice> slices;
|
||||
std::vector<std::unique_ptr<jbyte[]>> ranges_to_free;
|
||||
for (jsize i = 0; (i + 1) < length; i += 2) {
|
||||
slices.push_back(std::unique_ptr<ROCKSDB_NAMESPACE::Slice>());
|
||||
slices.emplace_back();
|
||||
if (!get_slice_helper(env, ranges, i, slices.back(), ranges_to_free)) {
|
||||
// exception thrown
|
||||
return;
|
||||
}
|
||||
|
||||
slices.push_back(std::unique_ptr<ROCKSDB_NAMESPACE::Slice>());
|
||||
slices.emplace_back();
|
||||
if (!get_slice_helper(env, ranges, i + 1, slices.back(), ranges_to_free)) {
|
||||
// exception thrown
|
||||
return;
|
||||
}
|
||||
|
||||
rangesVector.push_back(ROCKSDB_NAMESPACE::RangePtr(
|
||||
slices[slices.size() - 2].get(), slices[slices.size() - 1].get()));
|
||||
rangesVector.push_back(ROCKSDB_NAMESPACE::RangeOpt(
|
||||
slices[slices.size() - 2], slices[slices.size() - 1]));
|
||||
}
|
||||
|
||||
auto* db = reinterpret_cast<ROCKSDB_NAMESPACE::DB*>(jdb_handle);
|
||||
|
||||
+2
-2
@@ -134,8 +134,8 @@ class SkipList {
|
||||
// i up to max_height_ is the predecessor of prev_[0] and prev_height_
|
||||
// is the height of prev_[0]. prev_[0] can only be equal to head before
|
||||
// insertion, in which case max_height_ and prev_height_ are 1.
|
||||
Node** prev_;
|
||||
int32_t prev_height_;
|
||||
Node** prev_;
|
||||
|
||||
inline int GetMaxHeight() const {
|
||||
return max_height_.load(std::memory_order_relaxed);
|
||||
@@ -438,7 +438,7 @@ SkipList<Key, Comparator>::SkipList(const Comparator cmp, Allocator* allocator,
|
||||
kScaledInverseBranching_((Random::kMaxNext + 1) / kBranching_),
|
||||
compare_(cmp),
|
||||
allocator_(allocator),
|
||||
head_(NewNode(0 /* any key will do */, max_height)),
|
||||
head_(NewNode({} /* any key will do */, max_height)),
|
||||
max_height_(1),
|
||||
prev_height_(1) {
|
||||
assert(max_height > 0 && kMaxHeight_ == static_cast<uint32_t>(max_height));
|
||||
|
||||
@@ -61,6 +61,7 @@ bool WBWIMemTable::Get(const LookupKey& key, std::string* value,
|
||||
assert(!wbwi_->GetWriteBatch()->HasDeleteRange());
|
||||
assert(merge_context);
|
||||
|
||||
*out_seq = kMaxSequenceNumber;
|
||||
[[maybe_unused]] SequenceNumber read_seq =
|
||||
GetInternalKeySeqno(key.internal_key());
|
||||
// This is memtable is a single write batch, no snapshot can be taken within
|
||||
|
||||
@@ -50,8 +50,9 @@ Env::IOActivity ThreadStatusUtil::TEST_GetExpectedIOActivity(
|
||||
return Env::IOActivity::kGetEntity;
|
||||
case ThreadStatus::OperationType::OP_MULTIGETENTITY:
|
||||
return Env::IOActivity::kMultiGetEntity;
|
||||
case ThreadStatus::OperationType::OP_READ_MANIFEST:
|
||||
return Env::IOActivity::kReadManifest;
|
||||
case ThreadStatus::OperationType::
|
||||
OP_GET_FILE_CHECKSUMS_FROM_CURRENT_MANIFEST:
|
||||
return Env::IOActivity::kGetFileChecksumsFromCurrentManifest;
|
||||
default:
|
||||
return Env::IOActivity::kUnknown;
|
||||
}
|
||||
|
||||
@@ -141,6 +141,7 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
std::shared_ptr<Statistics> statistics;
|
||||
std::vector<DbPath> db_paths;
|
||||
FileTypeSet checksum_handoff_file_types;
|
||||
CompactionStyleSet calculate_sst_write_lifetime_hint_set;
|
||||
*/
|
||||
{"advise_random_on_open",
|
||||
{offsetof(struct ImmutableDBOptions, advise_random_on_open),
|
||||
@@ -801,7 +802,9 @@ ImmutableDBOptions::ImmutableDBOptions(const DBOptions& options)
|
||||
follower_catchup_retry_count(options.follower_catchup_retry_count),
|
||||
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) {
|
||||
wal_write_temperature(options.wal_write_temperature),
|
||||
calculate_sst_write_lifetime_hint_set(
|
||||
options.calculate_sst_write_lifetime_hint_set) {
|
||||
fs = env->GetFileSystem();
|
||||
clock = env->GetSystemClock().get();
|
||||
logger = info_log.get();
|
||||
|
||||
@@ -107,6 +107,7 @@ struct ImmutableDBOptions {
|
||||
uint64_t follower_catchup_retry_wait_ms;
|
||||
Temperature metadata_write_temperature;
|
||||
Temperature wal_write_temperature;
|
||||
CompactionStyleSet calculate_sst_write_lifetime_hint_set;
|
||||
|
||||
// Beginning convenience/helper objects that are not part of the base
|
||||
// DBOptions
|
||||
|
||||
+37
-26
@@ -199,6 +199,8 @@ void BuildDBOptions(const ImmutableDBOptions& immutable_db_options,
|
||||
immutable_db_options.metadata_write_temperature;
|
||||
options.wal_write_temperature = immutable_db_options.wal_write_temperature;
|
||||
options.compaction_service = immutable_db_options.compaction_service;
|
||||
options.calculate_sst_write_lifetime_hint_set =
|
||||
immutable_db_options.calculate_sst_write_lifetime_hint_set;
|
||||
}
|
||||
|
||||
ColumnFamilyOptions BuildColumnFamilyOptions(
|
||||
@@ -385,38 +387,47 @@ std::unordered_map<std::string, CompressionType>
|
||||
{"kZSTD", kZSTD},
|
||||
{"kDisableCompressionOption", kDisableCompressionOption}};
|
||||
|
||||
std::vector<CompressionType> GetSupportedCompressions() {
|
||||
// std::set internally to deduplicate potential name aliases
|
||||
std::set<CompressionType> supported_compressions;
|
||||
for (const auto& comp_to_name : OptionsHelper::compression_type_string_map) {
|
||||
CompressionType t = comp_to_name.second;
|
||||
if (t != kDisableCompressionOption && CompressionTypeSupported(t)) {
|
||||
supported_compressions.insert(t);
|
||||
const std::vector<CompressionType>& GetSupportedCompressions() {
|
||||
static std::vector<CompressionType> supported_compressions = []() {
|
||||
// std::set internally to deduplicate potential name aliases
|
||||
std::set<CompressionType> comp_set;
|
||||
for (const auto& comp_to_name :
|
||||
OptionsHelper::compression_type_string_map) {
|
||||
CompressionType t = comp_to_name.second;
|
||||
if (t != kDisableCompressionOption && CompressionTypeSupported(t)) {
|
||||
comp_set.insert(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::vector<CompressionType>(supported_compressions.begin(),
|
||||
supported_compressions.end());
|
||||
return std::vector<CompressionType>(comp_set.begin(), comp_set.end());
|
||||
}();
|
||||
return supported_compressions;
|
||||
}
|
||||
|
||||
std::vector<CompressionType> GetSupportedDictCompressions() {
|
||||
std::set<CompressionType> dict_compression_types;
|
||||
for (const auto& comp_to_name : OptionsHelper::compression_type_string_map) {
|
||||
CompressionType t = comp_to_name.second;
|
||||
if (t != kDisableCompressionOption && DictCompressionTypeSupported(t)) {
|
||||
dict_compression_types.insert(t);
|
||||
const std::vector<CompressionType>& GetSupportedDictCompressions() {
|
||||
static std::vector<CompressionType> supported_dict_compressions = []() {
|
||||
std::set<CompressionType> comp_set;
|
||||
for (const auto& comp_to_name :
|
||||
OptionsHelper::compression_type_string_map) {
|
||||
CompressionType t = comp_to_name.second;
|
||||
if (t != kDisableCompressionOption && DictCompressionTypeSupported(t)) {
|
||||
comp_set.insert(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::vector<CompressionType>(dict_compression_types.begin(),
|
||||
dict_compression_types.end());
|
||||
return std::vector<CompressionType>(comp_set.begin(), comp_set.end());
|
||||
}();
|
||||
return supported_dict_compressions;
|
||||
}
|
||||
|
||||
std::vector<ChecksumType> GetSupportedChecksums() {
|
||||
std::set<ChecksumType> checksum_types;
|
||||
for (const auto& e : OptionsHelper::checksum_type_string_map) {
|
||||
checksum_types.insert(e.second);
|
||||
}
|
||||
return std::vector<ChecksumType>(checksum_types.begin(),
|
||||
checksum_types.end());
|
||||
const std::vector<ChecksumType>& GetSupportedChecksums() {
|
||||
static std::vector<ChecksumType> supported_checksums = []() {
|
||||
std::set<ChecksumType> checksum_types;
|
||||
for (const auto& e : OptionsHelper::checksum_type_string_map) {
|
||||
checksum_types.insert(e.second);
|
||||
}
|
||||
return std::vector<ChecksumType>(checksum_types.begin(),
|
||||
checksum_types.end());
|
||||
}();
|
||||
return supported_checksums;
|
||||
}
|
||||
|
||||
static bool ParseOptionHelper(void* opt_address, const OptionType& opt_type,
|
||||
|
||||
@@ -25,11 +25,11 @@ struct MutableDBOptions;
|
||||
struct MutableCFOptions;
|
||||
struct Options;
|
||||
|
||||
std::vector<CompressionType> GetSupportedCompressions();
|
||||
const std::vector<CompressionType>& GetSupportedCompressions();
|
||||
|
||||
std::vector<CompressionType> GetSupportedDictCompressions();
|
||||
const std::vector<CompressionType>& GetSupportedDictCompressions();
|
||||
|
||||
std::vector<ChecksumType> GetSupportedChecksums();
|
||||
const std::vector<ChecksumType>& GetSupportedChecksums();
|
||||
|
||||
inline bool IsSupportedChecksumType(ChecksumType type) {
|
||||
// Avoid annoying compiler warning-as-error (-Werror=type-limits)
|
||||
|
||||
@@ -342,6 +342,8 @@ TEST_F(OptionsSettableTest, DBOptionsAllFieldsSettable) {
|
||||
{offsetof(struct DBOptions, compaction_service),
|
||||
sizeof(std::shared_ptr<CompactionService>)},
|
||||
{offsetof(struct DBOptions, daily_offpeak_time_utc), sizeof(std::string)},
|
||||
{offsetof(struct DBOptions, calculate_sst_write_lifetime_hint_set),
|
||||
sizeof(CompactionStyleSet)},
|
||||
};
|
||||
|
||||
char* options_ptr = new char[sizeof(DBOptions)];
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "index_builder.h"
|
||||
#include "logging/logging.h"
|
||||
#include "memory/memory_allocator_impl.h"
|
||||
#include "options/options_helper.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/env.h"
|
||||
@@ -979,16 +980,6 @@ BlockBasedTableBuilder::BlockBasedTableBuilder(
|
||||
const BlockBasedTableOptions& table_options, const TableBuilderOptions& tbo,
|
||||
WritableFileWriter* file) {
|
||||
BlockBasedTableOptions sanitized_table_options(table_options);
|
||||
if (sanitized_table_options.format_version == 0 &&
|
||||
sanitized_table_options.checksum != kCRC32c) {
|
||||
ROCKS_LOG_WARN(
|
||||
tbo.ioptions.logger,
|
||||
"Silently converting format_version to 1 because checksum is "
|
||||
"non-default");
|
||||
// silently convert format_version to 1 to keep consistent with current
|
||||
// behavior
|
||||
sanitized_table_options.format_version = 1;
|
||||
}
|
||||
auto ucmp = tbo.internal_comparator.user_comparator();
|
||||
assert(ucmp);
|
||||
(void)ucmp; // avoids unused variable error.
|
||||
@@ -1027,6 +1018,11 @@ void BlockBasedTableBuilder::Add(const Slice& ikey, const Slice& value) {
|
||||
if (r->props.num_entries > r->props.num_range_deletions) {
|
||||
assert(r->internal_comparator.Compare(ikey, Slice(r->last_ikey)) > 0);
|
||||
}
|
||||
bool skip = false;
|
||||
TEST_SYNC_POINT_CALLBACK("BlockBasedTableBuilder::Add::skip", (void*)&skip);
|
||||
if (skip) {
|
||||
return;
|
||||
}
|
||||
#endif // !NDEBUG
|
||||
|
||||
auto should_flush = r->flush_block_policy->Update(ikey, value);
|
||||
@@ -1246,6 +1242,20 @@ void BlockBasedTableBuilder::CompressAndVerifyBlock(
|
||||
r->ioptions.clock,
|
||||
ShouldReportDetailedTime(r->ioptions.env, r->ioptions.stats));
|
||||
|
||||
*type = r->compression_type;
|
||||
#ifndef NDEBUG
|
||||
if (r->compression_type != kNoCompression &&
|
||||
g_hack_mixed_compression_in_block_based_table.LoadRelaxed() > 0U) {
|
||||
// If zstd is in the mix, the compression_name table property needs to be
|
||||
// set to it, for proper handling of context and dictionaries.
|
||||
assert(!ZSTD_Supported() || r->compression_type == kZSTD);
|
||||
const auto& compressions = GetSupportedCompressions();
|
||||
auto counter =
|
||||
g_hack_mixed_compression_in_block_based_table.FetchAddRelaxed(1);
|
||||
*type = compressions[counter % compressions.size()];
|
||||
}
|
||||
#endif // !NDEBUG
|
||||
|
||||
if (is_data_block) {
|
||||
r->compressible_input_data_bytes.fetch_add(uncompressed_block_data.size(),
|
||||
std::memory_order_relaxed);
|
||||
@@ -1258,7 +1268,7 @@ void BlockBasedTableBuilder::CompressAndVerifyBlock(
|
||||
}
|
||||
assert(compression_dict != nullptr);
|
||||
CompressionInfo compression_info(r->compression_opts, compression_ctx,
|
||||
*compression_dict, r->compression_type,
|
||||
*compression_dict, *type,
|
||||
r->sample_for_compression);
|
||||
|
||||
std::string sampled_output_fast;
|
||||
@@ -2185,4 +2195,8 @@ const std::string BlockBasedTable::kObsoleteFilterBlockPrefix = "filter.";
|
||||
const std::string BlockBasedTable::kFullFilterBlockPrefix = "fullfilter.";
|
||||
const std::string BlockBasedTable::kPartitionedFilterBlockPrefix =
|
||||
"partitionedfilter.";
|
||||
|
||||
#ifndef NDEBUG
|
||||
RelaxedAtomic<uint64_t> g_hack_mixed_compression_in_block_based_table{0};
|
||||
#endif // !NDEBUG
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "rocksdb/table.h"
|
||||
#include "table/meta_blocks.h"
|
||||
#include "table/table_builder.h"
|
||||
#include "util/atomic.h"
|
||||
#include "util/compression.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -205,4 +206,10 @@ Slice CompressBlock(const Slice& uncompressed_data, const CompressionInfo& info,
|
||||
std::string* sampled_output_fast,
|
||||
std::string* sampled_output_slow);
|
||||
|
||||
#ifndef NDEBUG
|
||||
// 0 == disable the hack
|
||||
// > 0 => counter for rotating through compression types
|
||||
extern RelaxedAtomic<uint64_t> g_hack_mixed_compression_in_block_based_table;
|
||||
#endif
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -467,6 +467,20 @@ void BlockBasedTableFactory::InitializeOptions() {
|
||||
options_overrides_iter->second.charged = options.charged;
|
||||
}
|
||||
}
|
||||
|
||||
if (table_options_.format_version < kMinSupportedFormatVersion) {
|
||||
if (AllowUnsupportedFormatVersion()) {
|
||||
// Allow old format version for testing.
|
||||
// And relevant old sanitization.
|
||||
if (table_options_.format_version == 0 &&
|
||||
table_options_.checksum != kCRC32c) {
|
||||
// silently convert format_version to 1 to support non-CRC32c checksum
|
||||
table_options_.format_version = 1;
|
||||
}
|
||||
} else {
|
||||
table_options_.format_version = kMinSupportedFormatVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Status BlockBasedTableFactory::PrepareOptions(const ConfigOptions& opts) {
|
||||
@@ -910,6 +924,11 @@ Status BlockBasedTableFactory::ParseOption(const ConfigOptions& config_options,
|
||||
return status;
|
||||
}
|
||||
|
||||
bool& BlockBasedTableFactory::AllowUnsupportedFormatVersion() {
|
||||
static bool allow = false;
|
||||
return allow;
|
||||
}
|
||||
|
||||
Status GetBlockBasedTableOptionsFromString(
|
||||
const ConfigOptions& config_options,
|
||||
const BlockBasedTableOptions& table_options, const std::string& opts_str,
|
||||
|
||||
@@ -87,6 +87,12 @@ class BlockBasedTableFactory : public TableFactory {
|
||||
return &shared_state_->tail_prefetch_stats;
|
||||
}
|
||||
|
||||
static constexpr int kMinSupportedFormatVersion = 2;
|
||||
|
||||
// Set to true to allow unit testing of writing unsupported block-based table
|
||||
// format versions (to test read side)
|
||||
static bool& AllowUnsupportedFormatVersion();
|
||||
|
||||
protected:
|
||||
const void* GetOptionsPtr(const std::string& name) const override;
|
||||
Status ParseOption(const ConfigOptions& config_options,
|
||||
|
||||
@@ -320,6 +320,7 @@ void BlockFetcher::ReadBlock(bool retry) {
|
||||
}
|
||||
|
||||
PERF_COUNTER_ADD(block_read_byte, block_size_with_trailer_);
|
||||
IGNORE_STATUS_IF_ERROR(io_status_);
|
||||
if (io_status_.ok()) {
|
||||
if (use_fs_scratch_ && !read_req.status.ok()) {
|
||||
io_status_ = read_req.status;
|
||||
|
||||
@@ -151,6 +151,14 @@ void PlainTableBuilder::Add(const Slice& key, const Slice& value) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool skip = false;
|
||||
TEST_SYNC_POINT_CALLBACK("PlainTableBuilder::Add::skip", (void*)&skip);
|
||||
if (skip) {
|
||||
return;
|
||||
}
|
||||
#endif // !NDEBUG
|
||||
|
||||
// Store key hash
|
||||
if (store_index_in_file_) {
|
||||
if (moptions_.prefix_extractor == nullptr) {
|
||||
|
||||
@@ -474,12 +474,11 @@ Status SstFileDumper::ReadSequential(bool print_kv, uint64_t read_num_limit,
|
||||
const Comparator* ucmp = internal_comparator_.user_comparator();
|
||||
size_t ts_sz = ucmp->timestamp_size();
|
||||
|
||||
Slice from_slice = from_key;
|
||||
Slice to_slice = to_key;
|
||||
OptSlice from_opt = has_from ? from_key : OptSlice{};
|
||||
OptSlice to_opt = has_to ? to_key : OptSlice{};
|
||||
std::string from_key_buf, to_key_buf;
|
||||
auto [from, to] = MaybeAddTimestampsToRange(
|
||||
has_from ? &from_slice : nullptr, has_to ? &to_slice : nullptr, ts_sz,
|
||||
&from_key_buf, &to_key_buf);
|
||||
auto [from, to] = MaybeAddTimestampsToRange(from_opt, to_opt, ts_sz,
|
||||
&from_key_buf, &to_key_buf);
|
||||
uint64_t i = 0;
|
||||
if (from.has_value()) {
|
||||
InternalKey ikey;
|
||||
|
||||
Loaded 100 of 113 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user