mirror of
https://github.com/facebook/rocksdb.git
synced 2026-07-07 14:47:40 +08:00
Compare commits
104 Commits
9.11.fb
...
rocksdb_fork
| 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 | |||
| 4c975a7c22 | |||
| 5139ff5c29 | |||
| d7aea6955c | |||
| 3c2c2689b9 | |||
| 1e1c199316 | |||
| 836e88ab7a | |||
| 129b7791f9 | |||
| 36838bbf51 | |||
| 920d25e34e | |||
| a0edca32cf | |||
| 9be5e15a26 | |||
| 4b5f0a4fcc | |||
| 7069691f7e | |||
| 6aacec07dc | |||
| affcad0cc9 | |||
| f6b2cdd350 | |||
| 8234d67e5a | |||
| a30c0204cc | |||
| e6972196bc | |||
| 3f460ad0d2 | |||
| c9ce4a3d6b | |||
| 1f36399a77 | |||
| d48af21386 | |||
| dd01f73e26 | |||
| a377bded9f | |||
| c1fb33e1d0 | |||
| 302254d928 | |||
| 62531da510 | |||
| 354025fc86 | |||
| 4ace09f6eb | |||
| 833a2266a3 | |||
| ed2a87db07 | |||
| 242e69f067 | |||
| 61ee80faa0 | |||
| f5f4f83fcc | |||
| 22270dea6f | |||
| 864964b008 | |||
| 7774a4de17 | |||
| 7f271a3fa8 | |||
| a10b4aa9a3 | |||
| 1341c0c670 | |||
| ce6065ef70 | |||
| d000134cb5 | |||
| 78210c82ad | |||
| 1f0426c44b | |||
| 57c177be55 | |||
| d4676bfadd | |||
| c3b71a6706 | |||
| d79a5e5854 | |||
| a6322b9ec6 | |||
| 2e0dc21a9f | |||
| f37ce33fcc | |||
| b8d915c7fa | |||
| 880f85a162 | |||
| 591f5b1266 | |||
| ac6c671308 | |||
| a8bd6a3ffe | |||
| ac2ad2160d | |||
| 4ac85f0a79 | |||
| 35a27d859b | |||
| 0e469c7f99 | |||
| f6c1489c7a | |||
| 5405835505 | |||
| 2257f4fae5 |
@@ -45,7 +45,7 @@ jobs:
|
||||
- name: Install argparse
|
||||
run: pip install argparse
|
||||
- name: Download clang-format-diff.py
|
||||
run: wget https://raw.githubusercontent.com/llvm/llvm-project/release/12.x/clang/tools/clang-format/clang-format-diff.py
|
||||
run: wget https://rocksdb-deps.s3.us-west-2.amazonaws.com/llvm/llvm-project/release/12.x/clang/tools/clang-format/clang-format-diff.py
|
||||
- name: Check format
|
||||
run: VERBOSE_CHECK=1 make check-format
|
||||
- name: Compare buckify output
|
||||
|
||||
@@ -214,6 +214,7 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"table/cuckoo/cuckoo_table_builder.cc",
|
||||
"table/cuckoo/cuckoo_table_factory.cc",
|
||||
"table/cuckoo/cuckoo_table_reader.cc",
|
||||
"table/external_table.cc",
|
||||
"table/format.cc",
|
||||
"table/get_context.cc",
|
||||
"table/iterator.cc",
|
||||
@@ -317,6 +318,8 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"utilities/persistent_cache/block_cache_tier_metadata.cc",
|
||||
"utilities/persistent_cache/persistent_cache_tier.cc",
|
||||
"utilities/persistent_cache/volatile_tier_impl.cc",
|
||||
"utilities/secondary_index/secondary_index_iterator.cc",
|
||||
"utilities/secondary_index/simple_secondary_index.cc",
|
||||
"utilities/simulator_cache/cache_simulator.cc",
|
||||
"utilities/simulator_cache/sim_cache.cc",
|
||||
"utilities/table_properties_collectors/compact_for_tiering_collector.cc",
|
||||
@@ -406,6 +409,7 @@ cpp_library_wrapper(name="rocksdb_tools_lib", srcs=[
|
||||
"tools/block_cache_analyzer/block_cache_trace_analyzer.cc",
|
||||
"tools/db_bench_tool.cc",
|
||||
"tools/simulated_hybrid_file_system.cc",
|
||||
"tools/tool_hooks.cc",
|
||||
"tools/trace_analyzer_tool.cc",
|
||||
], deps=[":rocksdb_lib"], headers=[], link_whole=False, extra_test_libs=False)
|
||||
|
||||
|
||||
@@ -835,6 +835,7 @@ set(SOURCES
|
||||
table/cuckoo/cuckoo_table_builder.cc
|
||||
table/cuckoo/cuckoo_table_factory.cc
|
||||
table/cuckoo/cuckoo_table_reader.cc
|
||||
table/external_table.cc
|
||||
table/format.cc
|
||||
table/get_context.cc
|
||||
table/iterator.cc
|
||||
@@ -939,6 +940,8 @@ set(SOURCES
|
||||
utilities/persistent_cache/block_cache_tier_metadata.cc
|
||||
utilities/persistent_cache/persistent_cache_tier.cc
|
||||
utilities/persistent_cache/volatile_tier_impl.cc
|
||||
utilities/secondary_index/secondary_index_iterator.cc
|
||||
utilities/secondary_index/simple_secondary_index.cc
|
||||
utilities/simulator_cache/cache_simulator.cc
|
||||
utilities/simulator_cache/sim_cache.cc
|
||||
utilities/table_properties_collectors/compact_for_tiering_collector.cc
|
||||
@@ -1571,6 +1574,7 @@ if(WITH_BENCHMARK_TOOLS)
|
||||
add_executable(db_bench${ARTIFACT_SUFFIX}
|
||||
tools/simulated_hybrid_file_system.cc
|
||||
tools/db_bench.cc
|
||||
tools/tool_hooks.cc
|
||||
tools/db_bench_tool.cc)
|
||||
target_link_libraries(db_bench${ARTIFACT_SUFFIX}
|
||||
${ROCKSDB_LIB} ${THIRDPARTY_LIBS})
|
||||
|
||||
+65
@@ -1,6 +1,71 @@
|
||||
# Rocksdb Change Log
|
||||
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
|
||||
|
||||
## 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.
|
||||
* 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
|
||||
* Introduced new `auto_refresh_iterator_with_snapshot` opt-in knob that (when enabled) will periodically release obsolete memory and storage resources for as long as the iterator is making progress and its supplied `read_options.snapshot` was initialized with non-nullptr value.
|
||||
* Added the ability to plug-in a custom table reader implementation. See include/rocksdb/external_table_reader.h for more details.
|
||||
* Experimental feature: RocksDB now supports FAISS inverted file based indices via the secondary indexing framework. Applications can use FAISS secondary indices to automatically quantize embeddings and perform K-nearest-neighbors similarity searches. See `FaissIVFIndex` and `SecondaryIndex` for more details. Note: the FAISS integration currently requires using the BUCK build.
|
||||
* Add new DB property `num_running_compaction_sorted_runs` that tracks the number of sorted runs being processed by currently running compactions
|
||||
* Experimental feature: added support for simple secondary indices that index the specified column as-is. See `SimpleSecondaryIndex` and `SecondaryIndex` for more details.
|
||||
* Added new `TransactionDBOptions::txn_commit_bypass_memtable_threshold`, which enables optimized transaction commit (see `TransactionOptions::commit_bypass_memtable`) when the transaction size exceeds a configured threshold.
|
||||
|
||||
### Public API Changes
|
||||
* Updated the query API of the experimental secondary indexing feature by removing the earlier `SecondaryIndex::NewIterator` virtual and adding a `SecondaryIndexIterator` class that can be utilized by applications to find the primary keys for a given search target.
|
||||
* Added back the ability to leverage the primary key when building secondary index entries. This involved changes to the signatures of `SecondaryIndex::GetSecondary{KeyPrefix,Value}` as well as the addition of a new method `SecondaryIndex::FinalizeSecondaryKeyPrefix`. See the API comments for more details.
|
||||
* Minimum supported version of ZSTD is now 1.4.0, for code simplification. Obsolete `CompressionType` `kZSTDNotFinalCompression` is also removed.
|
||||
|
||||
### Behavior Changes
|
||||
* `VerifyBackup` in `verify_with_checksum`=`true` mode will now evaluate checksums in parallel. As a result, unlike in case of original implementation, the API won't bail out on a very first corruption / mismatch and instead will iterate over all the backup files logging success / _degree_of_failure_ for each.
|
||||
* Reversed the order of updates to the same key in WriteBatchWithIndex. This means if there are multiple updates to the same key, the most recent update is ordered first. This affects the output of WBWIIterator. When WriteBatchWithIndex is created with `overwrite_key=true`, this affects the output only if Merge is used (#13387).
|
||||
* Added support for Merge operations in transactions using option `TransactionOptions::commit_bypass_memtable`.
|
||||
|
||||
### Bug Fixes
|
||||
* Fixed GetMergeOperands() API in ReadOnlyDB and SecondaryDB
|
||||
* Fix a bug in `GetMergeOperands()` that can return incorrect status (MergeInProgress) and incorrect number of merge operands. This can happen when `GetMergeOperandsOptions::continue_cb` is set, both active and immutable memtables have merge operands and the callback stops the look up at the immutable memtable.
|
||||
|
||||
## 9.11.0 (01/17/2025)
|
||||
### New Features
|
||||
* Introduce CancelAwaitingJobs() in CompactionService interface which will allow users to implement cancellation of running remote compactions from the primary instance
|
||||
* Experimental feature: RocksDB now supports defining secondary indices, which are automatically maintained by the storage engine. Secondary indices provide a new customization point: applications can provide their own by implementing the new `SecondaryIndex` interface. See the `SecondaryIndex` API comments for more details. Note: this feature is currently only available in conjunction with write-committed pessimistic transactions, and `Merge` is not yet supported.
|
||||
* Provide a new option `track_and_verify_wals` to track and verify various information about WAL during WAL recovery. This is intended to be a better replacement to `track_and_verify_wals_in_manifest`.
|
||||
|
||||
### Public API Changes
|
||||
* Add `io_buffer_size` to BackupEngineOptions to enable optimal configuration of IO size
|
||||
* Clean up all the references to `random_access_max_buffer_size`, related rules and all the clients wrappers. This option has been officially deprecated in 5.4.0.
|
||||
* Add `file_ingestion_nanos` and `file_ingestion_blocking_live_writes_nanos` in PerfContext to observe file ingestions
|
||||
* Offer new DB::Open and variants that use `std::unique_ptr<DB>*` output parameters and deprecate the old versions that use `DB**` output parameters.
|
||||
* The DB::DeleteFile API is officially deprecated.
|
||||
|
||||
### Behavior Changes
|
||||
* For leveled compaction, manual compaction (CompactRange()) will be more strict about keeping compaction size under `max_compaction_bytes`. This prevents overly large compactions in some cases (#13306).
|
||||
* Experimental tiering options `preclude_last_level_data_seconds` and `preserve_internal_time_seconds` are now mutable with `SetOptions()`. Some changes to handling of these features along with long-lived snapshots and range deletes made this possible.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a longstanding major bug with SetOptions() in which setting changes can be quietly reverted.
|
||||
|
||||
## 9.10.0 (12/12/2024)
|
||||
### New Features
|
||||
* Introduce `TransactionOptions::commit_bypass_memtable` to enable transaction commit to bypass memtable insertions. This can be beneficial for transactions with many operations, as it reduces commit time that is mostly spent on memtable insertion.
|
||||
|
||||
@@ -2,7 +2,8 @@ This is the list of all known third-party language bindings for RocksDB. If some
|
||||
|
||||
* Java - https://github.com/facebook/rocksdb/tree/main/java
|
||||
* Python
|
||||
* http://python-rocksdb.readthedocs.io/en/latest/
|
||||
* https://github.com/rocksdict/RocksDict
|
||||
* http://python-rocksdb.readthedocs.io/en/latest/ (unmaintained)
|
||||
* http://pyrocksdb.readthedocs.org/en/latest/ (unmaintained)
|
||||
* Perl - https://metacpan.org/pod/RocksDB
|
||||
* Node.js - https://npmjs.org/package/rocksdb
|
||||
|
||||
@@ -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 62baa6ba07ff0a23ee4f2ea2f5207e4c88464deb
|
||||
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
|
||||
|
||||
@@ -360,9 +360,13 @@ EOF
|
||||
fi
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_ZSTD; then
|
||||
# Test whether zstd library is installed
|
||||
# Test whether zstd library is installed with minimum version
|
||||
# (Keep in sync with compression.h)
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <zstd.h>
|
||||
#if ZSTD_VERSION_NUMBER < 10400
|
||||
#error "ZSTD support requires version >= 1.4.0 (libzstd-devel)"
|
||||
#endif // ZSTD_VERSION_NUMBER
|
||||
int main() {}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
|
||||
@@ -59,7 +59,7 @@ fi
|
||||
if ! test $ROCKSDB_DISABLE_ZSTD; then
|
||||
ZSTD_INCLUDE=" -I $ZSTD_BASE/include/"
|
||||
ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd${MAYBE_PIC}.a"
|
||||
CFLAGS+=" -DZSTD"
|
||||
CFLAGS+=" -DZSTD -DZSTD_STATIC_LINKING_ONLY"
|
||||
fi
|
||||
|
||||
# location of gflags headers and libraries
|
||||
|
||||
+112
-25
@@ -64,8 +64,118 @@ void ArenaWrappedDBIter::Init(
|
||||
memtable_range_tombstone_iter_ = nullptr;
|
||||
}
|
||||
|
||||
void ArenaWrappedDBIter::MaybeAutoRefresh(bool is_seek,
|
||||
DBIter::Direction direction) {
|
||||
if (cfh_ != nullptr && read_options_.snapshot != nullptr && allow_refresh_ &&
|
||||
read_options_.auto_refresh_iterator_with_snapshot) {
|
||||
// The intent here is to capture the superversion number change
|
||||
// reasonably soon from the time it actually happened. As such,
|
||||
// we're fine with weaker synchronization / ordering guarantees
|
||||
// provided by relaxed atomic (in favor of less CPU / mem overhead).
|
||||
uint64_t cur_sv_number = cfh_->cfd()->GetSuperVersionNumberRelaxed();
|
||||
if ((sv_number_ != cur_sv_number) && status().ok()) {
|
||||
// Changing iterators' direction is pretty heavy-weight operation and
|
||||
// could have unintended consequences when it comes to prefix seek.
|
||||
// Therefore, we need an efficient implementation that does not duplicate
|
||||
// the effort by doing things like double seek(forprev).
|
||||
//
|
||||
// Auto refresh can be triggered on the following groups of operations:
|
||||
//
|
||||
// 1. [Seek]: Seek(), SeekForPrev()
|
||||
// 2. [Non-Seek]: Next(), Prev()
|
||||
//
|
||||
// In case of 'Seek' group, procedure is fairly straightforward as we'll
|
||||
// simply call refresh and then invoke the operation on intended target.
|
||||
//
|
||||
// In case of 'Non-Seek' group, we'll first advance the cursor by invoking
|
||||
// intended user operation (Next() or Prev()), capture the target key T,
|
||||
// refresh the iterator and then reconcile the refreshed iterator by
|
||||
// explicitly calling [Seek(T) or SeekForPrev(T)]. Below is an example
|
||||
// flow for Next(), but same principle applies to Prev():
|
||||
//
|
||||
//
|
||||
// T0: Before the operation T1: Execute Next()
|
||||
// | |
|
||||
// | -------------
|
||||
// | | * capture the key (T)
|
||||
// DBIter(SV#A) | |
|
||||
// --------------\ /------\ /---------
|
||||
// SV #A | ... -> [ X ] -> [ T ] -> ... |
|
||||
// -----------------------------------
|
||||
// / |
|
||||
// / |
|
||||
// / T2: Refresh iterator
|
||||
// /
|
||||
// DBIter(SV#A') /
|
||||
// ----------------------------------
|
||||
// SV #A' | ... -> [ T ] -> ... |
|
||||
// ----------------/ \---------------
|
||||
// |
|
||||
// ---- T3: Seek(T)
|
||||
//
|
||||
bool valid = false;
|
||||
std::string key;
|
||||
if (!is_seek && db_iter_->Valid()) {
|
||||
// The key() Slice is valid until the iterator state changes.
|
||||
// Given that refresh is heavy-weight operation it itself,
|
||||
// we should copy the target key upfront to avoid reading bad value.
|
||||
valid = true;
|
||||
key = db_iter_->key().ToString();
|
||||
}
|
||||
|
||||
// It's perfectly fine to unref the corresponding superversion
|
||||
// as we rely on pinning behavior of snapshot for consistency.
|
||||
DoRefresh(read_options_.snapshot, cur_sv_number);
|
||||
|
||||
if (!is_seek && valid) { // Reconcile new iterator after Next() / Prev()
|
||||
if (direction == DBIter::kForward) {
|
||||
db_iter_->Seek(key);
|
||||
} else {
|
||||
assert(direction == DBIter::kReverse);
|
||||
db_iter_->SeekForPrev(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Status ArenaWrappedDBIter::Refresh() { return Refresh(nullptr); }
|
||||
|
||||
void ArenaWrappedDBIter::DoRefresh(const Snapshot* snapshot,
|
||||
[[maybe_unused]] uint64_t sv_number) {
|
||||
Env* env = db_iter_->env();
|
||||
|
||||
// NOTE:
|
||||
//
|
||||
// Errors like file deletion (as a part of SV cleanup in ~DBIter) will be
|
||||
// present in the error log, but won't be reflected in the iterator status.
|
||||
// This is by design as we expect compaction to clean up those obsolete files
|
||||
// eventually.
|
||||
db_iter_->~DBIter();
|
||||
|
||||
arena_.~Arena();
|
||||
new (&arena_) Arena();
|
||||
|
||||
auto cfd = cfh_->cfd();
|
||||
auto db_impl = cfh_->db();
|
||||
|
||||
SuperVersion* sv = cfd->GetReferencedSuperVersion(db_impl);
|
||||
assert(sv->version_number >= sv_number);
|
||||
SequenceNumber read_seq = GetSeqNum(db_impl, snapshot);
|
||||
if (read_callback_) {
|
||||
read_callback_->Refresh(read_seq);
|
||||
}
|
||||
Init(env, read_options_, cfd->ioptions(), sv->mutable_cf_options, sv->current,
|
||||
read_seq, sv->mutable_cf_options.max_sequential_skip_in_iterations,
|
||||
sv->version_number, read_callback_, cfh_, expose_blob_index_,
|
||||
allow_refresh_);
|
||||
|
||||
InternalIterator* internal_iter = db_impl->NewInternalIterator(
|
||||
read_options_, cfd, sv, &arena_, read_seq,
|
||||
/* allow_unprepared_value */ true, /* db_iter */ this);
|
||||
SetIterUnderDBIter(internal_iter);
|
||||
}
|
||||
|
||||
Status ArenaWrappedDBIter::Refresh(const Snapshot* snapshot) {
|
||||
if (cfh_ == nullptr || !allow_refresh_) {
|
||||
return Status::NotSupported("Creating renew iterator is not allowed.");
|
||||
@@ -85,32 +195,9 @@ Status ArenaWrappedDBIter::Refresh(const Snapshot* snapshot) {
|
||||
TEST_SYNC_POINT("ArenaWrappedDBIter::Refresh:1");
|
||||
TEST_SYNC_POINT("ArenaWrappedDBIter::Refresh:2");
|
||||
|
||||
auto reinit_internal_iter = [&]() {
|
||||
Env* env = db_iter_->env();
|
||||
db_iter_->~DBIter();
|
||||
arena_.~Arena();
|
||||
new (&arena_) Arena();
|
||||
|
||||
SuperVersion* sv = cfd->GetReferencedSuperVersion(db_impl);
|
||||
assert(sv->version_number >= cur_sv_number);
|
||||
SequenceNumber read_seq = GetSeqNum(db_impl, snapshot);
|
||||
if (read_callback_) {
|
||||
read_callback_->Refresh(read_seq);
|
||||
}
|
||||
Init(env, read_options_, cfd->ioptions(), sv->mutable_cf_options,
|
||||
sv->current, read_seq,
|
||||
sv->mutable_cf_options.max_sequential_skip_in_iterations,
|
||||
sv->version_number, read_callback_, cfh_, expose_blob_index_,
|
||||
allow_refresh_);
|
||||
|
||||
InternalIterator* internal_iter = db_impl->NewInternalIterator(
|
||||
read_options_, cfd, sv, &arena_, read_seq,
|
||||
/* allow_unprepared_value */ true, /* db_iter */ this);
|
||||
SetIterUnderDBIter(internal_iter);
|
||||
};
|
||||
while (true) {
|
||||
if (sv_number_ != cur_sv_number) {
|
||||
reinit_internal_iter();
|
||||
DoRefresh(snapshot, cur_sv_number);
|
||||
break;
|
||||
} else {
|
||||
SequenceNumber read_seq = GetSeqNum(db_impl, snapshot);
|
||||
@@ -138,7 +225,7 @@ Status ArenaWrappedDBIter::Refresh(const Snapshot* snapshot) {
|
||||
db_impl->ReturnAndCleanupSuperVersion(cfd, sv);
|
||||
// The memtable under DBIter did not have range tombstone before
|
||||
// refresh.
|
||||
reinit_internal_iter();
|
||||
DoRefresh(snapshot, cur_sv_number);
|
||||
break;
|
||||
} else {
|
||||
*memtable_range_tombstone_iter_ =
|
||||
|
||||
@@ -65,12 +65,26 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
void SeekToLast() override { db_iter_->SeekToLast(); }
|
||||
// 'target' does not contain timestamp, even if user timestamp feature is
|
||||
// enabled.
|
||||
void Seek(const Slice& target) override { db_iter_->Seek(target); }
|
||||
void Seek(const Slice& target) override {
|
||||
MaybeAutoRefresh(true /* is_seek */, DBIter::kForward);
|
||||
db_iter_->Seek(target);
|
||||
}
|
||||
|
||||
void SeekForPrev(const Slice& target) override {
|
||||
MaybeAutoRefresh(true /* is_seek */, DBIter::kReverse);
|
||||
db_iter_->SeekForPrev(target);
|
||||
}
|
||||
void Next() override { db_iter_->Next(); }
|
||||
void Prev() override { db_iter_->Prev(); }
|
||||
|
||||
void Next() override {
|
||||
db_iter_->Next();
|
||||
MaybeAutoRefresh(false /* is_seek */, DBIter::kForward);
|
||||
}
|
||||
|
||||
void Prev() override {
|
||||
db_iter_->Prev();
|
||||
MaybeAutoRefresh(false /* is_seek */, DBIter::kReverse);
|
||||
}
|
||||
|
||||
Slice key() const override { return db_iter_->key(); }
|
||||
Slice value() const override { return db_iter_->value(); }
|
||||
const WideColumns& columns() const override { return db_iter_->columns(); }
|
||||
@@ -103,6 +117,9 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
}
|
||||
|
||||
private:
|
||||
void DoRefresh(const Snapshot* snapshot, uint64_t sv_number);
|
||||
void MaybeAutoRefresh(bool is_seek, DBIter::Direction direction);
|
||||
|
||||
DBIter* db_iter_ = nullptr;
|
||||
Arena arena_;
|
||||
uint64_t sv_number_;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1858,10 +1858,6 @@ extern ROCKSDB_LIBRARY_API void rocksdb_approximate_sizes_cf_with_flags(
|
||||
delete[] ranges;
|
||||
}
|
||||
|
||||
void DEPRECATED_rocksdb_delete_file(rocksdb_t* db, const char* name) {
|
||||
db->rep->DEPRECATED_DeleteFile(name);
|
||||
}
|
||||
|
||||
const rocksdb_livefiles_t* rocksdb_livefiles(rocksdb_t* db) {
|
||||
rocksdb_livefiles_t* result = new rocksdb_livefiles_t;
|
||||
db->rep->GetLiveFilesMetaData(&result->rep);
|
||||
|
||||
+50
-22
@@ -168,7 +168,8 @@ Status CheckConcurrentWritesSupported(const ColumnFamilyOptions& cf_options) {
|
||||
}
|
||||
if (!cf_options.memtable_factory->IsInsertConcurrentlySupported()) {
|
||||
return Status::InvalidArgument(
|
||||
"Memtable doesn't concurrent writes (allow_concurrent_memtable_write)");
|
||||
"Memtable doesn't allow concurrent writes "
|
||||
"(allow_concurrent_memtable_write)");
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
@@ -199,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>,
|
||||
@@ -239,6 +241,10 @@ ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
|
||||
|
||||
result.min_write_buffer_number_to_merge = 1;
|
||||
}
|
||||
if (result.disallow_memtable_writes) {
|
||||
// A simple memtable that enforces MarkReadOnly (unlike skip list)
|
||||
result.memtable_factory = std::make_shared<VectorRepFactory>();
|
||||
}
|
||||
|
||||
if (result.num_levels < 1) {
|
||||
result.num_levels = 1;
|
||||
@@ -435,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;
|
||||
}
|
||||
|
||||
@@ -492,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 {
|
||||
@@ -530,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),
|
||||
@@ -540,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_(
|
||||
@@ -1339,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);
|
||||
@@ -1365,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;
|
||||
@@ -1378,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) {
|
||||
@@ -1680,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),
|
||||
@@ -1752,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();
|
||||
|
||||
+30
-27
@@ -210,6 +210,7 @@ struct SuperVersion {
|
||||
ReadOnlyMemTable* mem;
|
||||
MemTableListVersion* imm;
|
||||
Version* current;
|
||||
// TODO: do we really need this in addition to what's in current Version?
|
||||
MutableCFOptions mutable_cf_options;
|
||||
// Version number of the current SuperVersion
|
||||
uint64_t version_number;
|
||||
@@ -280,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.
|
||||
@@ -380,17 +382,20 @@ class ColumnFamilyData {
|
||||
return mem()->GetFirstSequenceNumber() == 0 && imm()->NumNotFlushed() == 0;
|
||||
}
|
||||
|
||||
Version* current() { return current_; }
|
||||
Version* dummy_versions() { return dummy_versions_; }
|
||||
void SetCurrent(Version* _current);
|
||||
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
|
||||
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
|
||||
// REQUIRE: DB mutex held
|
||||
void SetMemtable(MemTable* new_mem) {
|
||||
AssignMemtableID(new_mem);
|
||||
mem_ = new_mem;
|
||||
if (ioptions_.disallow_memtable_writes) {
|
||||
mem_->MarkImmutable();
|
||||
}
|
||||
}
|
||||
|
||||
void AssignMemtableID(ReadOnlyMemTable* new_imm) {
|
||||
@@ -483,15 +488,14 @@ class ColumnFamilyData {
|
||||
uint64_t GetSuperVersionNumber() const {
|
||||
return super_version_number_.load();
|
||||
}
|
||||
// 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()
|
||||
uint64_t GetSuperVersionNumberRelaxed() const {
|
||||
return super_version_number_.load(std::memory_order_relaxed);
|
||||
}
|
||||
// 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();
|
||||
|
||||
@@ -586,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;
|
||||
|
||||
@@ -757,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 */
|
||||
|
||||
|
||||
+272
-162
@@ -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,10 @@ 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();
|
||||
}
|
||||
|
||||
uint64_t CompactionJob::GetSubcompactionsLimit() {
|
||||
@@ -694,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");
|
||||
|
||||
@@ -839,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 =
|
||||
@@ -854,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");
|
||||
@@ -915,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);
|
||||
@@ -926,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;
|
||||
@@ -992,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"
|
||||
@@ -1026,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";
|
||||
@@ -1054,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;
|
||||
}
|
||||
|
||||
@@ -1128,8 +1142,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
if (db_options_.compaction_service) {
|
||||
CompactionServiceJobStatus comp_status =
|
||||
ProcessKeyValueCompactionWithCompactionService(sub_compact);
|
||||
if (comp_status == CompactionServiceJobStatus::kSuccess ||
|
||||
comp_status == CompactionServiceJobStatus::kFailure) {
|
||||
if (comp_status != CompactionServiceJobStatus::kUseLocal) {
|
||||
return;
|
||||
}
|
||||
// fallback to local compaction
|
||||
@@ -1312,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 &&
|
||||
@@ -1397,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
|
||||
|
||||
@@ -1429,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;
|
||||
@@ -1641,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.)
|
||||
@@ -1812,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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1946,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);
|
||||
@@ -2036,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()));
|
||||
@@ -2061,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);
|
||||
|
||||
@@ -2085,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);
|
||||
@@ -2102,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;
|
||||
@@ -2128,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;
|
||||
}
|
||||
@@ -2139,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2232,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_);
|
||||
}
|
||||
}
|
||||
@@ -2269,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,12 @@ 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.
|
||||
uint64_t options_file_number_ = 0;
|
||||
|
||||
// Get table file name in where it's outputting to, which should also be in
|
||||
// `output_directory_`.
|
||||
@@ -427,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(
|
||||
@@ -436,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),
|
||||
@@ -450,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
|
||||
@@ -466,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);
|
||||
@@ -513,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;
|
||||
|
||||
@@ -48,10 +48,7 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
compaction_input.has_end = sub_compact->end.has_value();
|
||||
compaction_input.end =
|
||||
compaction_input.has_end ? sub_compact->end->ToString() : "";
|
||||
compaction_input.options_file_number =
|
||||
sub_compact->compaction->input_version()
|
||||
->version_set()
|
||||
->options_file_number();
|
||||
compaction_input.options_file_number = options_file_number_;
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"CompactionServiceJob::ProcessKeyValueCompactionWithCompactionService",
|
||||
@@ -86,6 +83,14 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
switch (response.status) {
|
||||
case CompactionServiceJobStatus::kSuccess:
|
||||
break;
|
||||
case CompactionServiceJobStatus::kAborted:
|
||||
sub_compact->status =
|
||||
Status::Aborted("Scheduling a remote compaction job was aborted");
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log,
|
||||
"[%s] [JOB %d] Remote compaction was aborted at Schedule()",
|
||||
compaction->column_family_data()->GetName().c_str(), job_id_);
|
||||
return response.status;
|
||||
case CompactionServiceJobStatus::kFailure:
|
||||
sub_compact->status = Status::Incomplete(
|
||||
"CompactionService failed to schedule a remote compaction job.");
|
||||
@@ -105,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_);
|
||||
@@ -113,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,
|
||||
@@ -121,6 +139,16 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
return compaction_status;
|
||||
}
|
||||
|
||||
if (compaction_status == CompactionServiceJobStatus::kAborted) {
|
||||
sub_compact->status =
|
||||
Status::Aborted("Waiting a remote compaction job was aborted");
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
"[%s] [JOB %d] Remote compaction was aborted during Wait()",
|
||||
compaction->column_family_data()->GetName().c_str(),
|
||||
job_id_);
|
||||
return compaction_status;
|
||||
}
|
||||
|
||||
CompactionServiceResult compaction_result;
|
||||
s = CompactionServiceResult::Read(compaction_result_binary,
|
||||
&compaction_result);
|
||||
@@ -211,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);
|
||||
@@ -243,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,
|
||||
@@ -316,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;
|
||||
@@ -354,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()) {
|
||||
@@ -385,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -557,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 = {
|
||||
@@ -682,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.
|
||||
@@ -788,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) {
|
||||
@@ -1471,6 +1516,40 @@ TEST_F(CompactionServiceTest, FallbackLocalManual) {
|
||||
VerifyTestData();
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, AbortedWhileWait) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
ReopenWithCompactionService(&options);
|
||||
|
||||
GenerateTestData();
|
||||
VerifyTestData();
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
Statistics* compactor_statistics = GetCompactorStatistics();
|
||||
Statistics* primary_statistics = GetPrimaryStatistics();
|
||||
|
||||
my_cs->ResetOverride();
|
||||
std::string start_str = Key(15);
|
||||
std::string end_str = Key(45);
|
||||
Slice start(start_str);
|
||||
Slice end(end_str);
|
||||
|
||||
// Override Wait() result with kAborted
|
||||
my_cs->OverrideWaitStatus(CompactionServiceJobStatus::kAborted);
|
||||
start_str = Key(120);
|
||||
start = start_str;
|
||||
|
||||
Status s = db_->CompactRange(CompactRangeOptions(), &start, nullptr);
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_TRUE(s.IsAborted());
|
||||
// no remote compaction is run
|
||||
ASSERT_EQ(my_cs->GetCompactionNum(), 0);
|
||||
// make sure the compaction statistics is not recorded any side
|
||||
ASSERT_EQ(primary_statistics->getTickerCount(COMPACT_WRITE_BYTES), 0);
|
||||
ASSERT_EQ(primary_statistics->getTickerCount(REMOTE_COMPACT_WRITE_BYTES), 0);
|
||||
ASSERT_EQ(compactor_statistics->getTickerCount(COMPACT_WRITE_BYTES), 0);
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, RemoteEventListener) {
|
||||
class RemoteEventListenerTest : public EventListener {
|
||||
public:
|
||||
|
||||
@@ -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);
|
||||
|
||||
+189
-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);
|
||||
@@ -4994,6 +5078,97 @@ TEST_F(DBBasicTest, VerifyFileChecksumsReadahead) {
|
||||
(sst_size + alignment - 1) / (alignment));
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, DisallowMemtableWrite) {
|
||||
// This test is mostly about what you can't do with memtable writes
|
||||
// disallowed. For what you can do, see
|
||||
// ExternalSSTFileBasicTest.FailIfNotBottommostLevelAndDisallowMemtable
|
||||
Options options_allow = GetDefaultOptions();
|
||||
options_allow.create_if_missing = true;
|
||||
Options options_disallow = options_allow;
|
||||
options_disallow.disallow_memtable_writes = true;
|
||||
|
||||
DestroyAndReopen(options_allow);
|
||||
// CFs allowing and disallowing memtable write
|
||||
CreateColumnFamilies({"cf1", "cf2"}, options_allow);
|
||||
CreateColumnFamilies({"cf3"}, options_disallow);
|
||||
// XXX: needed to get consistent handles_ mappings
|
||||
ReopenWithColumnFamilies(
|
||||
{"default", "cf1", "cf2", "cf3"},
|
||||
{options_allow, options_allow, options_allow, options_disallow});
|
||||
|
||||
EXPECT_EQ(Put(0, "a0", "1").code(), Status::Code::kOk);
|
||||
EXPECT_EQ(Put(1, "a1", "1").code(), Status::Code::kOk);
|
||||
EXPECT_EQ(Put(2, "a2", "1").code(), Status::Code::kOk);
|
||||
EXPECT_EQ(Put(3, "a3", "1").code(), Status::Code::kInvalidArgument);
|
||||
|
||||
EXPECT_EQ(Get(0, "a0"), "1");
|
||||
EXPECT_EQ(Get(1, "a1"), "1");
|
||||
EXPECT_EQ(Get(2, "a2"), "1");
|
||||
EXPECT_EQ(Get(3, "a3"), "NOT_FOUND");
|
||||
|
||||
EXPECT_EQ(Delete(0, "z0").code(), Status::Code::kOk);
|
||||
EXPECT_EQ(Delete(1, "z1").code(), Status::Code::kOk);
|
||||
EXPECT_EQ(Delete(2, "z2").code(), Status::Code::kOk);
|
||||
EXPECT_EQ(Delete(3, "z3").code(), Status::Code::kInvalidArgument);
|
||||
|
||||
WriteBatch wb;
|
||||
EXPECT_EQ(wb.Put(handles_[0], "b0", "2").code(), Status::Code::kOk);
|
||||
EXPECT_EQ(wb.Put(handles_[1], "b1", "2").code(), Status::Code::kOk);
|
||||
EXPECT_EQ(wb.Put(handles_[2], "b2", "2").code(), Status::Code::kOk);
|
||||
EXPECT_EQ(wb.Put(handles_[3], "b3", "2").code(),
|
||||
Status::Code::kInvalidArgument);
|
||||
ASSERT_OK(db_->Write({}, &wb));
|
||||
wb.Clear();
|
||||
|
||||
EXPECT_EQ(Get(0, "b0"), "2");
|
||||
EXPECT_EQ(Get(1, "b1"), "2");
|
||||
EXPECT_EQ(Get(2, "b2"), "2");
|
||||
EXPECT_EQ(Get(3, "b3"), "NOT_FOUND");
|
||||
|
||||
// When the DB is re-opened with WAL entries for a CF that is newly setting
|
||||
// disallow_memtable_writes, we detect that and fail the open gracefully.
|
||||
ASSERT_EQ(TryReopenWithColumnFamilies(
|
||||
{"default", "cf1", "cf2", "cf3"},
|
||||
{options_allow, options_allow, options_disallow, options_allow})
|
||||
.code(),
|
||||
Status::Code::kInvalidArgument);
|
||||
|
||||
// Successfully opening with allow creates L0 files from the WAL
|
||||
ReopenWithColumnFamilies({"default", "cf1", "cf2", "cf3"}, options_allow);
|
||||
|
||||
EXPECT_EQ(Get(0, "a0"), "1");
|
||||
EXPECT_EQ(Get(1, "a1"), "1");
|
||||
EXPECT_EQ(Get(2, "a2"), "1");
|
||||
EXPECT_EQ(Get(3, "a3"), "NOT_FOUND");
|
||||
|
||||
// Now able to disallow on CF2 because no relevant WAL entries
|
||||
ReopenWithColumnFamilies(
|
||||
{"default", "cf1", "cf2", "cf3"},
|
||||
{options_allow, options_allow, options_disallow, options_allow});
|
||||
|
||||
EXPECT_EQ(Get(0, "a0"), "1");
|
||||
EXPECT_EQ(Get(1, "a1"), "1");
|
||||
EXPECT_EQ(Get(2, "a2"), "1");
|
||||
EXPECT_EQ(Get(3, "a3"), "NOT_FOUND");
|
||||
|
||||
// Now able to write to CF 3 but not CF 2
|
||||
EXPECT_EQ(Put(0, "c0", "3").code(), Status::Code::kOk);
|
||||
EXPECT_EQ(Put(1, "c1", "3").code(), Status::Code::kOk);
|
||||
EXPECT_EQ(Put(2, "c2", "3").code(), Status::Code::kInvalidArgument);
|
||||
EXPECT_EQ(Put(3, "c3", "3").code(), Status::Code::kOk);
|
||||
|
||||
EXPECT_EQ(Get(0, "c0"), "3");
|
||||
EXPECT_EQ(Get(1, "c1"), "3");
|
||||
EXPECT_EQ(Get(2, "c2"), "NOT_FOUND");
|
||||
EXPECT_EQ(Get(3, "c3"), "3");
|
||||
|
||||
// disallow_memtable_writes not supported on default column family.
|
||||
// (Would be complicated to make a WriteBatch aware of the setting in order
|
||||
// to reject the write before entering the write path.)
|
||||
Destroy(options_allow);
|
||||
EXPECT_EQ(TryReopen(options_disallow).code(), Status::Code::kInvalidArgument);
|
||||
}
|
||||
|
||||
// TODO: re-enable after we provide finer-grained control for WAL tracking to
|
||||
// meet the needs of different use cases, durability levels and recovery modes.
|
||||
TEST_F(DBBasicTest, DISABLED_ManualWalSync) {
|
||||
|
||||
@@ -835,8 +835,6 @@ TEST_F(DBBlockCacheTest, CacheCompressionDict) {
|
||||
}
|
||||
if (ZSTD_Supported()) {
|
||||
compression_types.push_back(kZSTD);
|
||||
} else if (ZSTDNotFinal_Supported()) {
|
||||
compression_types.push_back(kZSTDNotFinalCompression);
|
||||
}
|
||||
Random rnd(301);
|
||||
for (auto compression_type : compression_types) {
|
||||
|
||||
@@ -2244,6 +2244,54 @@ TEST_F(DBBloomFilterTest, MemtableWholeKeyBloomFilterMultiGet) {
|
||||
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
|
||||
TEST_F(DBBloomFilterTest, TestMemtableBloomAndWBM) {
|
||||
Options options = CurrentOptions();
|
||||
options.arena_block_size = 4096;
|
||||
options.write_buffer_size = 4000000;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(LRUCacheOptions(
|
||||
options.write_buffer_size * 3 /* capacity */, 1 /* num_shard_bits */,
|
||||
false /* strict_capacity_limit */, 0.0 /* high_pri_pool_ratio */,
|
||||
nullptr /* memory_allocator */, kDefaultToAdaptiveMutex,
|
||||
kDontChargeCacheMetadata));
|
||||
|
||||
options.write_buffer_manager.reset(
|
||||
new WriteBufferManager(options.write_buffer_size, cache));
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
|
||||
const auto kDummyEntrySize =
|
||||
CacheReservationManagerImpl<CacheEntryRole::kMisc>::GetDummyEntrySize();
|
||||
|
||||
// Just the start of a memtable, no Bloom
|
||||
ASSERT_GE(cache->GetUsage(), options.arena_block_size);
|
||||
ASSERT_LE(cache->GetUsage(), kDummyEntrySize);
|
||||
|
||||
// Now enable memtable bloom filter
|
||||
const double kRatio = 0.25;
|
||||
options.memtable_prefix_bloom_size_ratio = kRatio;
|
||||
options.memtable_whole_key_filtering = true;
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("foo2", "bar2"));
|
||||
|
||||
// Expecting a memtable Bloom of ratio times write_buffer_size, memory tracked
|
||||
auto bloom_size = static_cast<size_t>(kRatio * options.write_buffer_size);
|
||||
ASSERT_GE(cache->GetUsage(), bloom_size);
|
||||
ASSERT_LE(cache->GetUsage(), bloom_size + 2U * kDummyEntrySize);
|
||||
|
||||
// Now get another memtable and test
|
||||
// Pin this memtable with an iterator
|
||||
std::unique_ptr<Iterator> iter{db_->NewIterator({})};
|
||||
iter->Seek("foo2");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("foo2", "bar2"));
|
||||
|
||||
// Expecting twice as much
|
||||
ASSERT_GE(cache->GetUsage(), 2U * bloom_size);
|
||||
ASSERT_LE(cache->GetUsage(), 2U * bloom_size + 2U * kDummyEntrySize);
|
||||
}
|
||||
|
||||
namespace {
|
||||
std::pair<uint64_t, uint64_t> GetBloomStat(const Options& options, bool sst) {
|
||||
if (sst) {
|
||||
|
||||
+215
-11
@@ -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));
|
||||
|
||||
@@ -5856,6 +5883,26 @@ TEST_F(DBCompactionTest, CompactionStatsTest) {
|
||||
options.listeners.emplace_back(collector);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Verify that the internal statistics for num_running_compactions and
|
||||
// num_running_compaction_sorted_runs start and end at valid states
|
||||
uint64_t num_running_compactions = 0;
|
||||
ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kNumRunningCompactions,
|
||||
&num_running_compactions));
|
||||
ASSERT_EQ(num_running_compactions, 0);
|
||||
uint64_t num_running_compaction_sorted_runs = 0;
|
||||
ASSERT_TRUE(
|
||||
db_->GetIntProperty(DB::Properties::kNumRunningCompactionSortedRuns,
|
||||
&num_running_compaction_sorted_runs));
|
||||
ASSERT_EQ(num_running_compaction_sorted_runs, 0);
|
||||
// Check that the stat actually gets changed some time between the start and
|
||||
// end of compaction
|
||||
std::atomic<bool> sorted_runs_count_incremented = false;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionMergingIterator::UpdateInternalStats",
|
||||
[&](void*) { sorted_runs_count_incremented = true; });
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
for (int i = 0; i < 32; i++) {
|
||||
for (int j = 0; j < 5000; j++) {
|
||||
ASSERT_OK(Put(std::to_string(j), std::string(1, 'A')));
|
||||
@@ -5869,6 +5916,19 @@ TEST_F(DBCompactionTest, CompactionStatsTest) {
|
||||
ColumnFamilyData* cfd = cfh->cfd();
|
||||
|
||||
VerifyCompactionStats(*cfd, *collector);
|
||||
// There should be no more running compactions, and thus no more sorted runs
|
||||
// to process
|
||||
ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kNumRunningCompactions,
|
||||
&num_running_compactions));
|
||||
ASSERT_EQ(num_running_compactions, 0);
|
||||
ASSERT_TRUE(
|
||||
db_->GetIntProperty(DB::Properties::kNumRunningCompactionSortedRuns,
|
||||
&num_running_compaction_sorted_runs));
|
||||
ASSERT_EQ(num_running_compaction_sorted_runs, 0);
|
||||
ASSERT_TRUE(sorted_runs_count_incremented);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, SubcompactionEvent) {
|
||||
@@ -5879,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*/,
|
||||
@@ -5888,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 {
|
||||
@@ -5917,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();
|
||||
@@ -5940,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++) {
|
||||
@@ -5949,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());
|
||||
@@ -5961,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) {
|
||||
@@ -6407,10 +6498,27 @@ TEST_F(DBCompactionTest, PersistRoundRobinCompactCursor) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(RoundRobinSubcompactionsAgainstPressureToken, PressureTokenTest) {
|
||||
// FIXME: the test is flaky and failing the assertion
|
||||
// ASSERT_EQ(num_planned_subcompactions, kNumSubcompactions);
|
||||
// It's likely a test set up issue, fix if we are to use RoubdRobin compaction.
|
||||
TEST_P(RoundRobinSubcompactionsAgainstPressureToken,
|
||||
DISABLED_PressureTokenTest) {
|
||||
const int kKeysPerBuffer = 100;
|
||||
const int kNumSubcompactions = 2;
|
||||
const int kFilesPerLevel = 50;
|
||||
SyncPoint::GetInstance()->LoadDependency({
|
||||
// SetBackgroundThreads() only starts the bg threads. Background
|
||||
// threads may not be immediately available after SetBackgroundThreads
|
||||
// returns. There are some initialization before they start waiting for
|
||||
// new jobs, see ThreadPoolImpl::Impl::BGThread(). Here is a hacky way
|
||||
// to wait until there are at least two background threads (thread id
|
||||
// starts from 0) start waiting to accept jobs.
|
||||
{"ThreadPoolImpl::BGThread::Start:th1", "WaitForThreadAvailable"},
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
env_->SetBackgroundThreads(kNumSubcompactions, Env::LOW);
|
||||
TEST_SYNC_POINT("WaitForThreadAvailable");
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
Options options = CurrentOptions();
|
||||
options.num_levels = 3;
|
||||
options.max_bytes_for_level_multiplier = 2;
|
||||
@@ -6431,7 +6539,6 @@ TEST_P(RoundRobinSubcompactionsAgainstPressureToken, PressureTokenTest) {
|
||||
options.max_background_compactions = kNumSubcompactions;
|
||||
options.max_compaction_bytes = 100000000;
|
||||
DestroyAndReopen(options);
|
||||
env_->SetBackgroundThreads(kNumSubcompactions, Env::LOW);
|
||||
|
||||
Random rnd(301);
|
||||
for (int lvl = 2; lvl > 0; lvl--) {
|
||||
@@ -10423,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;
|
||||
@@ -10461,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.
|
||||
|
||||
+308
-328
@@ -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),
|
||||
@@ -1296,33 +1221,65 @@ Status DBImpl::SetOptions(
|
||||
{
|
||||
auto db_options = GetDBOptions();
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
s = cfd->SetOptions(db_options, options_map);
|
||||
if (s.ok()) {
|
||||
new_options_copy = cfd->GetLatestMutableCFOptions();
|
||||
// Append new version to recompute compaction score.
|
||||
VersionEdit dummy_edit;
|
||||
s = versions_->LogAndApply(cfd, read_options, write_options, &dummy_edit,
|
||||
&mutex_, directories_.GetDbDir());
|
||||
if (!versions_->io_status().ok()) {
|
||||
assert(!s.ok());
|
||||
error_handler_.SetBGError(versions_->io_status(),
|
||||
BackgroundErrorReason::kManifestWrite);
|
||||
// Manifest writers + Version appenders like flush and compaction use
|
||||
// LogAndApply, which releases DB mutex to wait for other manifest writers
|
||||
// and for the manifest write. We need to append a Version for the options
|
||||
// to take full effect (e.g. compaction scores), but we don't want to
|
||||
// interleave with other callers of LogAndApply, which could at least
|
||||
// temporarily roll back option changes. Thus, we use a special call to
|
||||
// LogAndApply that allows us to
|
||||
//
|
||||
// (a) Apply the options update when we know we are the exclusive version
|
||||
// appender + (fake) manifest writer, and
|
||||
//
|
||||
// (b) Append a new Version without manifest write nor DB mutex release
|
||||
//
|
||||
// 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()) {
|
||||
new_options_copy = cfd->GetLatestMutableCFOptions();
|
||||
}
|
||||
return cb_s;
|
||||
};
|
||||
VersionEdit dummy_edit;
|
||||
dummy_edit.MarkNoManifestWriteDummy();
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::SetOptions:dummy_edit", &dummy_edit);
|
||||
s = versions_->LogAndApply(
|
||||
cfd, read_options, write_options, &dummy_edit, &mutex_,
|
||||
directories_.GetDbDir(), false /*new_descriptor_log=*/,
|
||||
nullptr /*new_opts*/, {} /*manifest_wcb*/, pre_cb);
|
||||
if (!versions_->io_status().ok()) {
|
||||
assert(!s.ok());
|
||||
error_handler_.SetBGError(versions_->io_status(),
|
||||
BackgroundErrorReason::kManifestWrite);
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
// 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();
|
||||
|
||||
#if __cplusplus >= 202002L
|
||||
assert(new_options_copy == cfd->GetLatestMutableCFOptions());
|
||||
assert(cfd->GetLatestMutableCFOptions() ==
|
||||
cfd->GetCurrentMutableCFOptions());
|
||||
assert(cfd->GetCurrentMutableCFOptions() ==
|
||||
cfd->current()->GetMutableCFOptions());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
sv_context.Clean();
|
||||
|
||||
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(
|
||||
@@ -2566,6 +2523,8 @@ Status DBImpl::GetImpl(const ReadOptions& read_options, const Slice& key,
|
||||
// Return all merge operands for get_impl_options.key
|
||||
*get_impl_options.number_of_operands =
|
||||
static_cast<int>(merge_context.GetNumOperands());
|
||||
// OK status is returned, some merge operand is found.
|
||||
assert(*get_impl_options.number_of_operands > 0);
|
||||
if (*get_impl_options.number_of_operands >
|
||||
get_impl_options.get_merge_operands_options
|
||||
->expected_max_number_of_operands) {
|
||||
@@ -2670,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
|
||||
@@ -2719,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 {
|
||||
@@ -2765,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
|
||||
@@ -2776,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) {
|
||||
@@ -2786,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();
|
||||
}
|
||||
@@ -3504,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();
|
||||
|
||||
@@ -3521,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;
|
||||
}
|
||||
@@ -3537,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;
|
||||
}
|
||||
@@ -3565,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;
|
||||
}
|
||||
@@ -3596,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;
|
||||
}
|
||||
@@ -3666,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;
|
||||
@@ -3750,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()) {
|
||||
@@ -3768,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) {
|
||||
@@ -3787,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()) {
|
||||
@@ -4451,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());
|
||||
@@ -4468,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_; }
|
||||
@@ -4768,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.
|
||||
@@ -4806,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.
|
||||
@@ -4883,110 +4862,8 @@ Status DBImpl::GetUpdatesSince(
|
||||
return wal_manager_.GetUpdatesSince(seq, iter, read_options, versions_.get());
|
||||
}
|
||||
|
||||
Status DBImpl::DEPRECATED_DeleteFile(std::string name) {
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
const ReadOptions read_options;
|
||||
const WriteOptions write_options;
|
||||
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
WalFileType log_type;
|
||||
if (!ParseFileName(name, &number, &type, &log_type) ||
|
||||
(type != kTableFile && type != kWalFile)) {
|
||||
ROCKS_LOG_ERROR(immutable_db_options_.info_log, "DeleteFile %s failed.\n",
|
||||
name.c_str());
|
||||
return Status::InvalidArgument("Invalid file name");
|
||||
}
|
||||
|
||||
if (type == kWalFile) {
|
||||
// Only allow deleting archived log files
|
||||
if (log_type != kArchivedLogFile) {
|
||||
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
|
||||
"DeleteFile %s failed - not archived log.\n",
|
||||
name.c_str());
|
||||
return Status::NotSupported("Delete only supported for archived logs");
|
||||
}
|
||||
Status status = wal_manager_.DeleteFile(name, number);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
|
||||
"DeleteFile %s failed -- %s.\n", name.c_str(),
|
||||
status.ToString().c_str());
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
Status status;
|
||||
int level;
|
||||
FileMetaData* metadata;
|
||||
ColumnFamilyData* cfd;
|
||||
VersionEdit edit;
|
||||
JobContext job_context(next_job_id_.fetch_add(1), true);
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
status = versions_->GetMetadataForFile(number, &level, &metadata, &cfd);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"DeleteFile %s failed. File not found\n", name.c_str());
|
||||
job_context.Clean();
|
||||
return Status::InvalidArgument("File not found");
|
||||
}
|
||||
assert(level < cfd->NumberLevels());
|
||||
|
||||
// If the file is being compacted no need to delete.
|
||||
if (metadata->being_compacted) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"DeleteFile %s Skipped. File about to be compacted\n",
|
||||
name.c_str());
|
||||
job_context.Clean();
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Only the files in the last level can be deleted externally.
|
||||
// This is to make sure that any deletion tombstones are not
|
||||
// lost. Check that the level passed is the last level.
|
||||
auto* vstoreage = cfd->current()->storage_info();
|
||||
for (int i = level + 1; i < cfd->NumberLevels(); i++) {
|
||||
if (vstoreage->NumLevelFiles(i) != 0) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"DeleteFile %s FAILED. File not in last level\n",
|
||||
name.c_str());
|
||||
job_context.Clean();
|
||||
return Status::InvalidArgument("File not in last level");
|
||||
}
|
||||
}
|
||||
// if level == 0, it has to be the oldest file
|
||||
if (level == 0 &&
|
||||
vstoreage->LevelFiles(0).back()->fd.GetNumber() != number) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"DeleteFile %s failed ---"
|
||||
" target file in level 0 must be the oldest.",
|
||||
name.c_str());
|
||||
job_context.Clean();
|
||||
return Status::InvalidArgument("File in level 0, but not oldest");
|
||||
}
|
||||
edit.SetColumnFamily(cfd->GetID());
|
||||
edit.DeleteFile(level, number);
|
||||
status = versions_->LogAndApply(cfd, read_options, write_options, &edit,
|
||||
&mutex_, directories_.GetDbDir());
|
||||
if (status.ok()) {
|
||||
InstallSuperVersionAndScheduleWork(
|
||||
cfd, job_context.superversion_contexts.data());
|
||||
}
|
||||
FindObsoleteFiles(&job_context, false);
|
||||
} // lock released here
|
||||
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
// remove files outside the db-lock
|
||||
if (job_context.HaveSomethingToDelete()) {
|
||||
// Call PurgeObsoleteFiles() without holding mutex.
|
||||
PurgeObsoleteFiles(job_context);
|
||||
}
|
||||
job_context.Clean();
|
||||
return status;
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -4998,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);
|
||||
@@ -5008,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);
|
||||
}
|
||||
|
||||
@@ -5945,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(
|
||||
@@ -5993,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;
|
||||
@@ -6009,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;
|
||||
}
|
||||
@@ -6054,6 +5952,7 @@ Status DBImpl::IngestExternalFiles(
|
||||
|
||||
num_running_ingest_file_ += static_cast<int>(num_cfs);
|
||||
TEST_SYNC_POINT("DBImpl::IngestExternalFile:AfterIncIngestFileCounter");
|
||||
TEST_SYNC_POINT("DBImpl::IngestExternalFile:AfterIncIngestFileCounter:2");
|
||||
|
||||
bool at_least_one_cf_need_flush = false;
|
||||
std::vector<bool> need_flush(num_cfs, false);
|
||||
@@ -6302,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6339,7 +6238,7 @@ Status DBImpl::CreateColumnFamilyWithImport(
|
||||
import_job.edit(), &mutex_,
|
||||
directories_.GetDbDir());
|
||||
if (status.ok()) {
|
||||
InstallSuperVersionAndScheduleWork(cfd, &sv_context);
|
||||
InstallSuperVersionForConfigChange(cfd, &sv_context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6400,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());
|
||||
}
|
||||
|
||||
@@ -6799,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
|
||||
@@ -6808,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(
|
||||
@@ -6923,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
|
||||
|
||||
+61
-51
@@ -162,15 +162,21 @@ class Directories {
|
||||
std::unique_ptr<FSDirectory> wal_dir_;
|
||||
};
|
||||
|
||||
struct DBOpenLogReporter : public log::Reader::Reporter {
|
||||
struct DBOpenLogRecordReadReporter : public log::Reader::Reporter {
|
||||
Env* env;
|
||||
Logger* info_log;
|
||||
const char* fname;
|
||||
Status* status; // nullptr if immutable_db_options_.paranoid_checks==false
|
||||
bool* old_log_record;
|
||||
void Corruption(size_t bytes, const Status& s) override;
|
||||
void Corruption(size_t bytes, const Status& s,
|
||||
uint64_t log_number = kMaxSequenceNumber) override;
|
||||
|
||||
void OldLogRecord(size_t bytes) override;
|
||||
|
||||
uint64_t GetCorruptedLogNumber() const { return corrupted_log_number_; }
|
||||
|
||||
private:
|
||||
uint64_t corrupted_log_number_ = kMaxSequenceNumber;
|
||||
};
|
||||
|
||||
// While DB is the public interface of RocksDB, and DBImpl is the actual
|
||||
@@ -535,9 +541,8 @@ class DBImpl : public DB {
|
||||
SequenceNumber seq_number, std::unique_ptr<TransactionLogIterator>* iter,
|
||||
const TransactionLogIterator::ReadOptions& read_options =
|
||||
TransactionLogIterator::ReadOptions()) override;
|
||||
Status DEPRECATED_DeleteFile(std::string name) 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;
|
||||
@@ -646,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;
|
||||
|
||||
@@ -950,13 +960,6 @@ class DBImpl : public DB {
|
||||
return num_running_flushes_;
|
||||
}
|
||||
|
||||
// Returns the number of currently running compactions.
|
||||
// REQUIREMENT: mutex_ must be held when calling this function.
|
||||
int num_running_compactions() {
|
||||
mutex_.AssertHeld();
|
||||
return num_running_compactions_;
|
||||
}
|
||||
|
||||
const WriteController& write_controller() { return write_controller_; }
|
||||
|
||||
// hollow transactions shell used for recovery.
|
||||
@@ -1269,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.
|
||||
@@ -1981,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);
|
||||
@@ -2075,21 +2069,25 @@ class DBImpl : public DB {
|
||||
|
||||
void SetupLogFileProcessing(uint64_t wal_number);
|
||||
|
||||
Status InitializeLogReader(
|
||||
uint64_t wal_number, bool is_retry, std::string& fname,
|
||||
Status InitializeLogReader(uint64_t wal_number, bool is_retry,
|
||||
std::string& fname,
|
||||
|
||||
bool stop_replay_for_corruption, uint64_t min_wal_number,
|
||||
const PredecessorWALInfo& predecessor_wal_info,
|
||||
bool* const old_log_record, Status* const reporter_status,
|
||||
DBOpenLogReporter* reporter, std::unique_ptr<log::Reader>& reader);
|
||||
bool stop_replay_for_corruption,
|
||||
uint64_t min_wal_number,
|
||||
const PredecessorWALInfo& predecessor_wal_info,
|
||||
bool* const old_log_record,
|
||||
Status* const reporter_status,
|
||||
DBOpenLogRecordReadReporter* reporter,
|
||||
std::unique_ptr<log::Reader>& reader);
|
||||
Status ProcessLogRecord(
|
||||
Slice record, const std::unique_ptr<log::Reader>& reader,
|
||||
const UnorderedMap<uint32_t, size_t>& running_ts_sz, uint64_t wal_number,
|
||||
const std::string& fname, bool read_only, int job_id,
|
||||
std::function<void()> logFileDropped, DBOpenLogReporter* reporter,
|
||||
uint64_t* record_checksum, SequenceNumber* last_seqno_observed,
|
||||
SequenceNumber* next_sequence, bool* stop_replay_for_corruption,
|
||||
Status* status, bool* stop_replay_by_wal_filter,
|
||||
const std::function<void()>& logFileDropped,
|
||||
DBOpenLogRecordReadReporter* reporter, uint64_t* record_checksum,
|
||||
SequenceNumber* last_seqno_observed, SequenceNumber* next_sequence,
|
||||
bool* stop_replay_for_corruption, Status* status,
|
||||
bool* stop_replay_by_wal_filter,
|
||||
std::unordered_map<int, VersionEdit>* version_edits, bool* flushed);
|
||||
|
||||
Status InitializeWriteBatchForLogRecord(
|
||||
@@ -2114,9 +2112,9 @@ class DBImpl : public DB {
|
||||
|
||||
Status HandleNonOkStatusOrOldLogRecord(
|
||||
uint64_t wal_number, SequenceNumber const* const next_sequence,
|
||||
Status log_read_status, bool* old_log_record,
|
||||
bool* stop_replay_for_corruption, uint64_t* corrupted_wal_number,
|
||||
bool* corrupted_wal_found);
|
||||
Status status, const DBOpenLogRecordReadReporter& reporter,
|
||||
bool* old_log_record, bool* stop_replay_for_corruption,
|
||||
uint64_t* corrupted_wal_number, bool* corrupted_wal_found);
|
||||
|
||||
Status UpdatePredecessorWALInfo(uint64_t wal_number,
|
||||
const SequenceNumber last_seqno_observed,
|
||||
@@ -2136,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
|
||||
@@ -2448,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();
|
||||
|
||||
@@ -2516,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);
|
||||
@@ -3522,6 +3527,7 @@ void DBImpl::BackgroundCallCompaction(PrepickedCompaction* prepicked_compaction,
|
||||
}
|
||||
}
|
||||
|
||||
// Precondition: mutex_ must be held when calling this function.
|
||||
Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
JobContext* job_context,
|
||||
LogBuffer* log_buffer,
|
||||
@@ -3797,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);
|
||||
@@ -4272,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;
|
||||
@@ -4288,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
|
||||
@@ -4315,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());
|
||||
|
||||
+88
-27
@@ -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);
|
||||
}
|
||||
|
||||
@@ -224,6 +224,12 @@ Status DBImpl::ValidateOptions(
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
if (cfd.name == kDefaultColumnFamilyName) {
|
||||
if (cfd.options.disallow_memtable_writes) {
|
||||
return Status::InvalidArgument(
|
||||
"Default column family cannot use disallow_memtable_writes=true");
|
||||
}
|
||||
}
|
||||
}
|
||||
s = ValidateOptions(db_options);
|
||||
return s;
|
||||
@@ -1106,16 +1112,18 @@ bool DBImpl::InvokeWalFilterIfNeededOnWalRecord(uint64_t wal_number,
|
||||
return true;
|
||||
}
|
||||
|
||||
void DBOpenLogReporter::Corruption(size_t bytes, const Status& s) {
|
||||
void DBOpenLogRecordReadReporter::Corruption(size_t bytes, const Status& s,
|
||||
uint64_t log_number) {
|
||||
ROCKS_LOG_WARN(info_log, "%s%s: dropping %d bytes; %s",
|
||||
(status == nullptr ? "(ignoring error) " : ""), fname,
|
||||
static_cast<int>(bytes), s.ToString().c_str());
|
||||
if (status != nullptr && status->ok()) {
|
||||
*status = s;
|
||||
corrupted_log_number_ = log_number;
|
||||
}
|
||||
}
|
||||
|
||||
void DBOpenLogReporter::OldLogRecord(size_t bytes) {
|
||||
void DBOpenLogRecordReadReporter::OldLogRecord(size_t bytes) {
|
||||
if (old_log_record != nullptr) {
|
||||
*old_log_record = true;
|
||||
}
|
||||
@@ -1195,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,
|
||||
@@ -1202,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()) {
|
||||
@@ -1229,7 +1248,7 @@ Status DBImpl::ProcessLogFile(
|
||||
Status status;
|
||||
bool old_log_record = false;
|
||||
|
||||
DBOpenLogReporter reporter;
|
||||
DBOpenLogRecordReadReporter reporter;
|
||||
std::unique_ptr<log::Reader> reader;
|
||||
|
||||
std::string fname =
|
||||
@@ -1309,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,
|
||||
@@ -1317,13 +1337,19 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Recovered to log #%" PRIu64 " seq #%" PRIu64, wal_number,
|
||||
"Recovered to log #%" PRIu64 " next seq #%" PRIu64, wal_number,
|
||||
*next_sequence);
|
||||
|
||||
if (status.ok()) {
|
||||
@@ -1333,7 +1359,7 @@ Status DBImpl::ProcessLogFile(
|
||||
|
||||
if (!status.ok() || old_log_record) {
|
||||
status = HandleNonOkStatusOrOldLogRecord(
|
||||
wal_number, next_sequence, status, &old_log_record,
|
||||
wal_number, next_sequence, status, reporter, &old_log_record,
|
||||
stop_replay_for_corruption, corrupted_wal_number, corrupted_wal_found);
|
||||
}
|
||||
|
||||
@@ -1357,7 +1383,7 @@ Status DBImpl::InitializeLogReader(
|
||||
uint64_t wal_number, bool is_retry, std::string& fname,
|
||||
bool stop_replay_for_corruption, uint64_t min_wal_number,
|
||||
const PredecessorWALInfo& predecessor_wal_info, bool* const old_log_record,
|
||||
Status* const reporter_status, DBOpenLogReporter* reporter,
|
||||
Status* const reporter_status, DBOpenLogRecordReadReporter* reporter,
|
||||
std::unique_ptr<log::Reader>& reader) {
|
||||
assert(old_log_record);
|
||||
assert(reporter_status);
|
||||
@@ -1408,10 +1434,11 @@ Status DBImpl::ProcessLogRecord(
|
||||
Slice record, const std::unique_ptr<log::Reader>& reader,
|
||||
const UnorderedMap<uint32_t, size_t>& running_ts_sz, uint64_t wal_number,
|
||||
const std::string& fname, bool read_only, int job_id,
|
||||
std::function<void()> logFileDropped, DBOpenLogReporter* reporter,
|
||||
uint64_t* record_checksum, SequenceNumber* last_seqno_observed,
|
||||
SequenceNumber* next_sequence, bool* stop_replay_for_corruption,
|
||||
Status* status, bool* stop_replay_by_wal_filter,
|
||||
const std::function<void()>& logFileDropped,
|
||||
DBOpenLogRecordReadReporter* reporter, uint64_t* record_checksum,
|
||||
SequenceNumber* last_seqno_observed, SequenceNumber* next_sequence,
|
||||
bool* stop_replay_for_corruption, Status* status,
|
||||
bool* stop_replay_by_wal_filter,
|
||||
std::unordered_map<int, VersionEdit>* version_edits, bool* flushed) {
|
||||
assert(reporter);
|
||||
assert(last_seqno_observed);
|
||||
@@ -1607,7 +1634,8 @@ Status DBImpl::MaybeWriteLevel0TableForRecovery(
|
||||
|
||||
Status DBImpl::HandleNonOkStatusOrOldLogRecord(
|
||||
uint64_t wal_number, SequenceNumber const* const next_sequence,
|
||||
Status status, bool* old_log_record, bool* stop_replay_for_corruption,
|
||||
Status status, const DBOpenLogRecordReadReporter& reporter,
|
||||
bool* old_log_record, bool* stop_replay_for_corruption,
|
||||
uint64_t* corrupted_wal_number, bool* corrupted_wal_found) {
|
||||
assert(!status.ok() || *old_log_record);
|
||||
|
||||
@@ -1641,7 +1669,12 @@ Status DBImpl::HandleNonOkStatusOrOldLogRecord(
|
||||
// We should ignore the error but not continue replaying
|
||||
*old_log_record = false;
|
||||
*stop_replay_for_corruption = true;
|
||||
*corrupted_wal_number = wal_number;
|
||||
// TODO(hx235): have a single source of corrupted WAL number once we
|
||||
// consolidate the statuses
|
||||
uint64_t reporter_corrupted_wal_number = reporter.GetCorruptedLogNumber();
|
||||
*corrupted_wal_number = reporter_corrupted_wal_number != kMaxSequenceNumber
|
||||
? reporter_corrupted_wal_number
|
||||
: wal_number;
|
||||
if (corrupted_wal_found != nullptr) {
|
||||
*corrupted_wal_found = true;
|
||||
}
|
||||
@@ -1848,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")
|
||||
@@ -1980,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;
|
||||
@@ -2249,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;
|
||||
@@ -2270,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;
|
||||
@@ -2325,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,
|
||||
@@ -2460,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) {
|
||||
@@ -2469,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
|
||||
@@ -2476,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();
|
||||
@@ -2492,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();
|
||||
}
|
||||
@@ -2609,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()) {
|
||||
|
||||
@@ -32,7 +32,8 @@ Status DBImplReadOnly::GetImpl(const ReadOptions& read_options,
|
||||
const Slice& key,
|
||||
GetImplOptions& get_impl_options) {
|
||||
assert(get_impl_options.value != nullptr ||
|
||||
get_impl_options.columns != nullptr);
|
||||
get_impl_options.columns != nullptr ||
|
||||
get_impl_options.merge_operands != nullptr);
|
||||
assert(get_impl_options.column_family);
|
||||
|
||||
Status s;
|
||||
@@ -86,7 +87,11 @@ Status DBImplReadOnly::GetImpl(const ReadOptions& read_options,
|
||||
return s;
|
||||
}
|
||||
}
|
||||
// Prepare to store a list of merge operations if merge occurs.
|
||||
MergeContext merge_context;
|
||||
// TODO - Large Result Optimization for Read Only DB
|
||||
// (https://github.com/facebook/rocksdb/pull/10458)
|
||||
|
||||
SequenceNumber max_covering_tombstone_seq = 0;
|
||||
LookupKey lkey(key, snapshot, read_options.timestamp);
|
||||
PERF_TIMER_STOP(get_snapshot_time);
|
||||
@@ -97,7 +102,8 @@ Status DBImplReadOnly::GetImpl(const ReadOptions& read_options,
|
||||
get_impl_options.value ? get_impl_options.value->GetSelf() : nullptr,
|
||||
get_impl_options.columns, ts, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, read_options,
|
||||
false /* immutable_memtable */, &read_cb)) {
|
||||
false /* immutable_memtable */, &read_cb,
|
||||
/*is_blob_index=*/nullptr, /*do_merge=*/get_impl_options.get_value)) {
|
||||
if (get_impl_options.value) {
|
||||
get_impl_options.value->PinSelf();
|
||||
}
|
||||
@@ -111,7 +117,7 @@ Status DBImplReadOnly::GetImpl(const ReadOptions& read_options,
|
||||
/*value_found*/ nullptr,
|
||||
/*key_exists*/ nullptr, /*seq*/ nullptr, &read_cb,
|
||||
/*is_blob*/ nullptr,
|
||||
/*do_merge*/ true);
|
||||
/*do_merge=*/get_impl_options.get_value);
|
||||
RecordTick(stats_, MEMTABLE_MISS);
|
||||
}
|
||||
{
|
||||
@@ -121,6 +127,14 @@ Status DBImplReadOnly::GetImpl(const ReadOptions& read_options,
|
||||
size = get_impl_options.value->size();
|
||||
} else if (get_impl_options.columns) {
|
||||
size = get_impl_options.columns->serialized_size();
|
||||
} else if (get_impl_options.merge_operands) {
|
||||
*get_impl_options.number_of_operands =
|
||||
static_cast<int>(merge_context.GetNumOperands());
|
||||
for (const Slice& sl : merge_context.GetOperands()) {
|
||||
size += sl.size();
|
||||
get_impl_options.merge_operands->PinSelf(sl);
|
||||
get_impl_options.merge_operands++;
|
||||
}
|
||||
}
|
||||
RecordTick(stats_, BYTES_READ, size);
|
||||
RecordInHistogram(stats_, BYTES_PER_READ, size);
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -342,7 +342,8 @@ Status DBImplSecondary::GetImpl(const ReadOptions& read_options,
|
||||
const Slice& key,
|
||||
GetImplOptions& get_impl_options) {
|
||||
assert(get_impl_options.value != nullptr ||
|
||||
get_impl_options.columns != nullptr);
|
||||
get_impl_options.columns != nullptr ||
|
||||
get_impl_options.merge_operands != nullptr);
|
||||
assert(get_impl_options.column_family);
|
||||
|
||||
Status s;
|
||||
@@ -397,37 +398,64 @@ Status DBImplSecondary::GetImpl(const ReadOptions& read_options,
|
||||
}
|
||||
}
|
||||
MergeContext merge_context;
|
||||
// TODO - Large Result Optimization for Secondary DB
|
||||
// (https://github.com/facebook/rocksdb/pull/10458)
|
||||
|
||||
SequenceNumber max_covering_tombstone_seq = 0;
|
||||
LookupKey lkey(key, snapshot, read_options.timestamp);
|
||||
PERF_TIMER_STOP(get_snapshot_time);
|
||||
bool done = false;
|
||||
|
||||
// Look up starts here
|
||||
if (super_version->mem->Get(
|
||||
lkey,
|
||||
get_impl_options.value ? get_impl_options.value->GetSelf() : nullptr,
|
||||
get_impl_options.columns, ts, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, read_options,
|
||||
false /* immutable_memtable */, &read_cb)) {
|
||||
done = true;
|
||||
if (get_impl_options.value) {
|
||||
get_impl_options.value->PinSelf();
|
||||
if (get_impl_options.get_value) {
|
||||
if (super_version->mem->Get(
|
||||
lkey,
|
||||
get_impl_options.value ? get_impl_options.value->GetSelf()
|
||||
: nullptr,
|
||||
get_impl_options.columns, ts, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, read_options,
|
||||
false /* immutable_memtable */, &read_cb,
|
||||
/*is_blob_index=*/nullptr, /*do_merge=*/true)) {
|
||||
done = true;
|
||||
if (get_impl_options.value) {
|
||||
get_impl_options.value->PinSelf();
|
||||
}
|
||||
RecordTick(stats_, MEMTABLE_HIT);
|
||||
} else if ((s.ok() || s.IsMergeInProgress()) &&
|
||||
super_version->imm->Get(
|
||||
lkey,
|
||||
get_impl_options.value ? get_impl_options.value->GetSelf()
|
||||
: nullptr,
|
||||
get_impl_options.columns, ts, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, read_options, &read_cb)) {
|
||||
done = true;
|
||||
if (get_impl_options.value) {
|
||||
get_impl_options.value->PinSelf();
|
||||
}
|
||||
RecordTick(stats_, MEMTABLE_HIT);
|
||||
}
|
||||
RecordTick(stats_, MEMTABLE_HIT);
|
||||
} else if ((s.ok() || s.IsMergeInProgress()) &&
|
||||
super_version->imm->Get(
|
||||
lkey,
|
||||
get_impl_options.value ? get_impl_options.value->GetSelf()
|
||||
: nullptr,
|
||||
get_impl_options.columns, ts, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, read_options, &read_cb)) {
|
||||
done = true;
|
||||
if (get_impl_options.value) {
|
||||
get_impl_options.value->PinSelf();
|
||||
} else {
|
||||
// GetMergeOperands
|
||||
if (super_version->mem->Get(
|
||||
lkey,
|
||||
get_impl_options.value ? get_impl_options.value->GetSelf()
|
||||
: nullptr,
|
||||
get_impl_options.columns, ts, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, read_options,
|
||||
false /* immutable_memtable */, &read_cb,
|
||||
/*is_blob_index=*/nullptr, /*do_merge=*/false)) {
|
||||
done = true;
|
||||
RecordTick(stats_, MEMTABLE_HIT);
|
||||
} else if ((s.ok() || s.IsMergeInProgress()) &&
|
||||
super_version->imm->GetMergeOperands(lkey, &s, &merge_context,
|
||||
&max_covering_tombstone_seq,
|
||||
read_options)) {
|
||||
done = true;
|
||||
RecordTick(stats_, MEMTABLE_HIT);
|
||||
}
|
||||
RecordTick(stats_, MEMTABLE_HIT);
|
||||
}
|
||||
if (!done && !s.ok() && !s.IsMergeInProgress()) {
|
||||
if (!s.ok() && !s.IsMergeInProgress() && !s.IsNotFound()) {
|
||||
assert(done);
|
||||
ReturnAndCleanupSuperVersion(cfd, super_version);
|
||||
return s;
|
||||
}
|
||||
@@ -451,6 +479,14 @@ Status DBImplSecondary::GetImpl(const ReadOptions& read_options,
|
||||
size = get_impl_options.value->size();
|
||||
} else if (get_impl_options.columns) {
|
||||
size = get_impl_options.columns->serialized_size();
|
||||
} else if (get_impl_options.merge_operands) {
|
||||
*get_impl_options.number_of_operands =
|
||||
static_cast<int>(merge_context.GetNumOperands());
|
||||
for (const Slice& sl : merge_context.GetOperands()) {
|
||||
size += sl.size();
|
||||
get_impl_options.merge_operands->PinSelf(sl);
|
||||
get_impl_options.merge_operands++;
|
||||
}
|
||||
}
|
||||
RecordTick(stats_, BYTES_READ, size);
|
||||
RecordTimeToHistogram(stats_, BYTES_PER_READ, size);
|
||||
@@ -877,6 +913,10 @@ Status DBImplSecondary::CompactWithoutInstallation(
|
||||
Status s = cfd->compaction_picker()->GetCompactionInputsFromFileNumbers(
|
||||
&input_files, &input_set, vstorage, comp_options);
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_ERROR(
|
||||
immutable_db_options_.info_log,
|
||||
"GetCompactionInputsFromFileNumbers() failed - %s.\n DebugString: %s",
|
||||
s.ToString().c_str(), version->DebugString().c_str());
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -954,6 +994,7 @@ Status DB::OpenAndCompact(
|
||||
DBOptions db_options;
|
||||
ConfigOptions config_options;
|
||||
config_options.env = override_options.env;
|
||||
config_options.ignore_unknown_options = true;
|
||||
std::vector<ColumnFamilyDescriptor> all_column_families;
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
|
||||
@@ -52,7 +52,8 @@ class LogReaderContainer {
|
||||
Logger* info_log;
|
||||
std::string fname;
|
||||
Status* status; // nullptr if immutable_db_options_.paranoid_checks==false
|
||||
void Corruption(size_t bytes, const Status& s) override {
|
||||
void Corruption(size_t bytes, const Status& s,
|
||||
uint64_t /*log_number*/ = kMaxSequenceNumber) override {
|
||||
ROCKS_LOG_WARN(info_log, "%s%s: dropping %d bytes; %s",
|
||||
(this->status == nullptr ? "(ignoring error) " : ""),
|
||||
fname.c_str(), static_cast<int>(bytes),
|
||||
|
||||
@@ -205,7 +205,7 @@ Status DBImpl::IngestWBWI(std::shared_ptr<WriteBatchWithIndex> wbwi,
|
||||
ColumnFamilySet* cf_set = versions_->GetColumnFamilySet();
|
||||
|
||||
// Create WBWIMemTables
|
||||
for (const auto [cf_id, stat] : wbwi->GetCFStats()) {
|
||||
for (const auto& [cf_id, stat] : wbwi->GetCFStats()) {
|
||||
ColumnFamilyData* cfd = cf_set->GetColumnFamily(cf_id);
|
||||
if (!cfd) {
|
||||
if (ignore_missing_cf) {
|
||||
@@ -587,6 +587,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
|
||||
IOStatus io_s;
|
||||
Status pre_release_cb_status;
|
||||
size_t seq_inc = 0;
|
||||
if (status.ok()) {
|
||||
// Rules for when we can update the memtable concurrently
|
||||
// 1. supported by memtable
|
||||
@@ -640,7 +641,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
// disable_memtable in between; although we do not write this batch to
|
||||
// memtable it still consumes a seq. Otherwise, if !seq_per_batch_, we inc
|
||||
// the seq per valid written key to mem.
|
||||
size_t seq_inc = seq_per_batch_ ? valid_batches : total_count;
|
||||
seq_inc = seq_per_batch_ ? valid_batches : total_count;
|
||||
if (wbwi) {
|
||||
// Reserve sequence numbers for the ingested memtable. We need to reserve
|
||||
// at lease this amount for recovery. During recovery,
|
||||
@@ -713,6 +714,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
assert(last_sequence != kMaxSequenceNumber);
|
||||
const SequenceNumber current_sequence = last_sequence + 1;
|
||||
last_sequence += seq_inc;
|
||||
// Seqno assigned to this write are [current_sequence, last_sequence]
|
||||
|
||||
if (log_context.need_log_sync) {
|
||||
VersionEdit synced_wals;
|
||||
@@ -836,13 +838,17 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
if (status.ok() && w.status.ok()) {
|
||||
// w.batch contains (potentially empty) commit time batch updates,
|
||||
// only ingest wbwi if w.batch is applied to memtable successfully
|
||||
assert(wbwi->GetWriteBatch()->Count() > 0);
|
||||
|
||||
uint32_t memtable_update_count = w.batch->Count();
|
||||
SequenceNumber lb = versions_->LastSequence() + memtable_update_count + 1;
|
||||
SequenceNumber ub = versions_->LastSequence() + memtable_update_count +
|
||||
wbwi->GetWriteBatch()->Count();
|
||||
assert(ub == last_sequence);
|
||||
uint32_t wbwi_count = wbwi->GetWriteBatch()->Count();
|
||||
// Seqno assigned to this write are [last_seq + 1 - seq_inc, last_seq].
|
||||
// seq_inc includes w.batch (memtable updates) and wbwi
|
||||
// w.batch gets first `memtable_update_count` sequence numbers.
|
||||
// wbwi gets the rest `wbwi_count` sequence numbers.
|
||||
assert(seq_inc == memtable_update_count + wbwi_count);
|
||||
assert(wbwi_count > 0);
|
||||
assert(last_sequence != kMaxSequenceNumber);
|
||||
SequenceNumber lb = last_sequence + 1 - wbwi_count;
|
||||
SequenceNumber ub = last_sequence;
|
||||
if (two_write_queues_) {
|
||||
assert(ub <= versions_->LastAllocatedSequence());
|
||||
}
|
||||
|
||||
@@ -2544,6 +2544,145 @@ TEST_P(DBIteratorTest, RefreshWithSnapshot) {
|
||||
ASSERT_OK(db_->Close());
|
||||
}
|
||||
|
||||
TEST_P(DBIteratorTest, AutoRefreshIterator) {
|
||||
constexpr int kNumKeys = 1000;
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
for (const DBIter::Direction direction :
|
||||
{DBIter::kForward, DBIter::kReverse}) {
|
||||
for (const bool auto_refresh_enabled : {false, true}) {
|
||||
for (const bool explicit_snapshot : {false, true}) {
|
||||
DestroyAndReopen(options);
|
||||
// Multi dimensional iterator:
|
||||
//
|
||||
// L0 (level iterator): [key000000]
|
||||
// L1 (table iterator): [key000001]
|
||||
// Memtable : [key000000, key000999]
|
||||
for (int i = 0; i < kNumKeys + 2; i++) {
|
||||
ASSERT_OK(Put(Key(i % kNumKeys), "val" + std::to_string(i)));
|
||||
if (i <= 1) {
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
if (i == 0) {
|
||||
MoveFilesToLevel(1);
|
||||
}
|
||||
}
|
||||
|
||||
ReadOptions read_options;
|
||||
std::unique_ptr<ManagedSnapshot> snapshot = nullptr;
|
||||
if (explicit_snapshot) {
|
||||
snapshot = std::make_unique<ManagedSnapshot>(db_);
|
||||
}
|
||||
read_options.snapshot =
|
||||
explicit_snapshot ? snapshot->snapshot() : nullptr;
|
||||
read_options.auto_refresh_iterator_with_snapshot = auto_refresh_enabled;
|
||||
std::unique_ptr<Iterator> iter(NewIterator(read_options));
|
||||
|
||||
int trigger_compact_on_it = kNumKeys / 2;
|
||||
|
||||
// This update should NOT be visible from the iterator.
|
||||
ASSERT_OK(Put(Key(trigger_compact_on_it + 1), "new val"));
|
||||
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(1));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
|
||||
uint64_t all_memtables_size_before_refresh;
|
||||
uint64_t all_memtables_size_after_refresh;
|
||||
|
||||
std::string prop_value;
|
||||
ASSERT_OK(iter->GetProperty("rocksdb.iterator.super-version-number",
|
||||
&prop_value));
|
||||
int superversion_number = std::stoi(prop_value);
|
||||
|
||||
std::vector<LiveFileMetaData> old_files;
|
||||
db_->GetLiveFilesMetaData(&old_files);
|
||||
|
||||
int expected_next_key_int;
|
||||
if (direction == DBIter::kForward) {
|
||||
expected_next_key_int = 0;
|
||||
iter->SeekToFirst();
|
||||
} else { // DBIter::kReverse
|
||||
expected_next_key_int = kNumKeys - 1;
|
||||
iter->SeekToLast();
|
||||
}
|
||||
|
||||
int it_num = 0;
|
||||
std::unordered_map<std::string, std::string> kvs;
|
||||
while (iter->Valid()) {
|
||||
ASSERT_OK(iter->status());
|
||||
it_num++;
|
||||
if (it_num == trigger_compact_on_it) {
|
||||
// Bump the superversion by manually scheduling flush + compaction.
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr,
|
||||
nullptr));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForBackgroundWork());
|
||||
|
||||
// For accuracy, capture the memtables size right before consecutive
|
||||
// iterator call to Next() will update its' stale superversion ref.
|
||||
dbfull()->GetIntProperty("rocksdb.size-all-mem-tables",
|
||||
&all_memtables_size_before_refresh);
|
||||
}
|
||||
|
||||
if (it_num == trigger_compact_on_it + 1) {
|
||||
dbfull()->GetIntProperty("rocksdb.size-all-mem-tables",
|
||||
&all_memtables_size_after_refresh);
|
||||
ASSERT_OK(iter->GetProperty("rocksdb.iterator.super-version-number",
|
||||
&prop_value));
|
||||
uint64_t new_superversion_number = std::stoi(prop_value);
|
||||
Status expected_status_for_preexisting_files;
|
||||
if (auto_refresh_enabled && explicit_snapshot) {
|
||||
// Iterator is expected to detect its' superversion staleness.
|
||||
ASSERT_LT(superversion_number, new_superversion_number);
|
||||
// ... and since our iterator was the only reference to that very
|
||||
// superversion, we expect most of the active memory to be
|
||||
// returned upon automatical iterator refresh.
|
||||
ASSERT_GT(all_memtables_size_before_refresh,
|
||||
all_memtables_size_after_refresh);
|
||||
expected_status_for_preexisting_files = Status::NotFound();
|
||||
} else {
|
||||
ASSERT_EQ(superversion_number, new_superversion_number);
|
||||
ASSERT_EQ(all_memtables_size_after_refresh,
|
||||
all_memtables_size_before_refresh);
|
||||
expected_status_for_preexisting_files = Status::OK();
|
||||
}
|
||||
|
||||
for (const auto& file : old_files) {
|
||||
ASSERT_EQ(env_->FileExists(file.db_path + "/" + file.name),
|
||||
expected_status_for_preexisting_files);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure we're visiting the keys in desired order and at most once!
|
||||
ASSERT_EQ(IdFromKey(iter->key().ToString()), expected_next_key_int);
|
||||
kvs[iter->key().ToString()] = iter->value().ToString();
|
||||
|
||||
if (direction == DBIter::kForward) {
|
||||
iter->Next();
|
||||
expected_next_key_int++;
|
||||
} else {
|
||||
iter->Prev();
|
||||
expected_next_key_int--;
|
||||
}
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
// Data validation.
|
||||
ASSERT_EQ(kvs.size(), kNumKeys);
|
||||
for (int i = 0; i < kNumKeys; i++) {
|
||||
auto kv = kvs.find(Key(i));
|
||||
ASSERT_TRUE(kv != kvs.end());
|
||||
int val = i;
|
||||
if (i <= 1) {
|
||||
val += kNumKeys;
|
||||
}
|
||||
ASSERT_EQ(kv->second, "val" + std::to_string(val));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(DBIteratorTest, CreationFailure) {
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::NewInternalIterator:StatusCallback", [](void* arg) {
|
||||
|
||||
@@ -120,8 +120,11 @@ TEST_F(DBMergeOperandTest, FlushedMergeOperandReadAfterFreeBug) {
|
||||
|
||||
TEST_F(DBMergeOperandTest, GetMergeOperandsBasic) {
|
||||
Options options = CurrentOptions();
|
||||
|
||||
int limit = 2;
|
||||
// Use only the latest two merge operands.
|
||||
options.merge_operator = std::make_shared<LimitedStringAppendMergeOp>(2, ',');
|
||||
options.merge_operator =
|
||||
std::make_shared<LimitedStringAppendMergeOp>(limit, ',');
|
||||
Reopen(options);
|
||||
int num_records = 4;
|
||||
int number_of_operands = 0;
|
||||
@@ -263,6 +266,7 @@ TEST_F(DBMergeOperandTest, GetMergeOperandsBasic) {
|
||||
ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(),
|
||||
"k3.2", values.data(), &merge_operands_info,
|
||||
&number_of_operands));
|
||||
ASSERT_EQ(number_of_operands, 2);
|
||||
ASSERT_EQ(values[0], "cd");
|
||||
ASSERT_EQ(values[1], "de");
|
||||
|
||||
@@ -280,6 +284,7 @@ TEST_F(DBMergeOperandTest, GetMergeOperandsBasic) {
|
||||
ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(),
|
||||
"k4", values.data(), &merge_operands_info,
|
||||
&number_of_operands));
|
||||
ASSERT_EQ(number_of_operands, 4);
|
||||
ASSERT_EQ(values[0], "ba");
|
||||
ASSERT_EQ(values[1], "cb");
|
||||
ASSERT_EQ(values[2], "dc");
|
||||
@@ -299,9 +304,26 @@ TEST_F(DBMergeOperandTest, GetMergeOperandsBasic) {
|
||||
ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(),
|
||||
"k5", values.data(), &merge_operands_info,
|
||||
&number_of_operands));
|
||||
ASSERT_EQ(number_of_operands, 4);
|
||||
ASSERT_EQ(values[0], "remember");
|
||||
ASSERT_EQ(values[1], "i");
|
||||
ASSERT_EQ(values[2], "am");
|
||||
ASSERT_EQ(values[3], "rocks");
|
||||
|
||||
// GetMergeOperands() in ReadOnly DB
|
||||
ASSERT_OK(Merge("k6", "better"));
|
||||
ASSERT_OK(Merge("k6", "call"));
|
||||
ASSERT_OK(Merge("k6", "saul"));
|
||||
|
||||
ASSERT_OK(ReadOnlyReopen(options));
|
||||
std::vector<PinnableSlice> readonly_values(num_records);
|
||||
ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(),
|
||||
"k6", readonly_values.data(),
|
||||
&merge_operands_info, &number_of_operands));
|
||||
ASSERT_EQ(number_of_operands, 3);
|
||||
ASSERT_EQ(readonly_values[0], "better");
|
||||
ASSERT_EQ(readonly_values[1], "call");
|
||||
ASSERT_EQ(readonly_values[2], "saul");
|
||||
}
|
||||
|
||||
TEST_F(DBMergeOperandTest, BlobDBGetMergeOperandsBasic) {
|
||||
@@ -577,6 +599,42 @@ TEST_F(DBMergeOperandTest, GetMergeOperandsBaseDeletionInImmMem) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBMergeOperandTest, GetMergeOperandCallbackStopAtImm) {
|
||||
Options options = CurrentOptions();
|
||||
options.max_write_buffer_number = 10;
|
||||
options.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
ASSERT_OK(db_->PauseBackgroundWork());
|
||||
ASSERT_OK(Merge("key", "v1"));
|
||||
ASSERT_OK(dbfull()->TEST_SwitchMemtable());
|
||||
// Keep this merge in an immutable memtable
|
||||
uint64_t num_imm = 0;
|
||||
ASSERT_TRUE(
|
||||
db_->GetIntProperty(DB::Properties::kNumImmutableMemTable, &num_imm));
|
||||
ASSERT_EQ(num_imm, 1);
|
||||
ASSERT_OK(Merge("key", "v2"));
|
||||
|
||||
std::vector<PinnableSlice> merge_operands(2);
|
||||
GetMergeOperandsOptions merge_operands_info;
|
||||
merge_operands_info.expected_max_number_of_operands = 2;
|
||||
int num_fetched = 0;
|
||||
merge_operands_info.continue_cb = [&num_fetched](Slice /* value */) {
|
||||
num_fetched++;
|
||||
// Stop in the first immutable memtable.
|
||||
return num_fetched < 2;
|
||||
};
|
||||
int num_merge_operands = 0;
|
||||
ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(),
|
||||
"key", merge_operands.data(),
|
||||
&merge_operands_info, &num_merge_operands));
|
||||
ASSERT_EQ(2, num_merge_operands);
|
||||
ASSERT_EQ(2, num_fetched);
|
||||
|
||||
ASSERT_EQ("v1", merge_operands[0]);
|
||||
ASSERT_EQ("v2", merge_operands[1]);
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -120,6 +120,10 @@ TEST_F(DBMergeOperatorTest, LimitMergeOperands) {
|
||||
ASSERT_OK(Merge("k3", "de"));
|
||||
ASSERT_OK(db_->Get(ReadOptions(), "k3", &value));
|
||||
ASSERT_EQ(value, "cd,de");
|
||||
// Tests that merge operands reach exact limit at memtable.
|
||||
ASSERT_OK(Merge("k3", "fg"));
|
||||
ASSERT_OK(db_->Get(ReadOptions(), "k3", &value));
|
||||
ASSERT_EQ(value, "de,fg");
|
||||
|
||||
// All K4 values are in different levels
|
||||
ASSERT_OK(Merge("k4", "ab"));
|
||||
@@ -967,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));
|
||||
@@ -990,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));
|
||||
@@ -1015,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,
|
||||
|
||||
+65
-1
@@ -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;
|
||||
@@ -1595,6 +1596,69 @@ TEST_F(DBOptionsTest, TempOptionsFailTest) {
|
||||
ASSERT_FALSE(found_temp_file);
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, SetOptionsNoManifestWrite) {
|
||||
ASSERT_OK(Put("x", "x"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// In addition to checking manifest file, we want to ensure that SetOptions
|
||||
// is essentially atomic, without releasing the DB mutex between applying
|
||||
// the options to the cfd and installing new Version and SuperVersion. We
|
||||
// probabilistically verify that by attempting to catch an inconsistency.
|
||||
auto* const cfd =
|
||||
static_cast<ColumnFamilyHandleImpl*>(db_->DefaultColumnFamily())->cfd();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
std::optional<std::thread> t;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::LogAndApply:WakeUpAndNotDone", [&](void* arg) {
|
||||
auto* mu = static_cast<InstrumentedMutex*>(arg);
|
||||
// Option not yet modified
|
||||
ASSERT_FALSE(cfd->GetLatestMutableCFOptions().disable_auto_compactions);
|
||||
ASSERT_FALSE(
|
||||
cfd->current()->GetMutableCFOptions().disable_auto_compactions);
|
||||
ASSERT_FALSE(
|
||||
cfd->GetCurrentMutableCFOptions().disable_auto_compactions);
|
||||
t = std::thread([mu, cfd]() {
|
||||
InstrumentedMutexLock l(mu);
|
||||
// Assuming above correctness, we can only acquire the mutex after
|
||||
// options fully installed.
|
||||
ASSERT_TRUE(
|
||||
cfd->GetLatestMutableCFOptions().disable_auto_compactions);
|
||||
ASSERT_TRUE(
|
||||
cfd->current()->GetMutableCFOptions().disable_auto_compactions);
|
||||
ASSERT_TRUE(
|
||||
cfd->GetCurrentMutableCFOptions().disable_auto_compactions);
|
||||
});
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Baseline manifest file info
|
||||
std::vector<std::string> live_files;
|
||||
uint64_t orig_manifest_file_size;
|
||||
ASSERT_OK(dbfull()->GetLiveFiles(live_files, &orig_manifest_file_size));
|
||||
uint64_t orig_manifest_file_num = dbfull()->TEST_Current_Manifest_FileNo();
|
||||
|
||||
// Although this test mostly concerns SetOptions, we also include SetDBOptions
|
||||
// just for the added scope
|
||||
ASSERT_OK(db_->SetDBOptions({{"max_open_files", "100"}}));
|
||||
ASSERT_OK(db_->SetOptions({{"disable_auto_compactions", "true"}}));
|
||||
|
||||
// Verify that our above check was activated and completed
|
||||
ASSERT_TRUE(t.has_value());
|
||||
t->join();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
// Verify manifest was not written to
|
||||
uint64_t new_manifest_file_size;
|
||||
ASSERT_OK(dbfull()->GetLiveFiles(live_files, &new_manifest_file_size));
|
||||
uint64_t new_manifest_file_num = dbfull()->TEST_Current_Manifest_FileNo();
|
||||
ASSERT_EQ(orig_manifest_file_num, new_manifest_file_num);
|
||||
ASSERT_EQ(orig_manifest_file_size, new_manifest_file_size);
|
||||
|
||||
ASSERT_EQ(Get("x"), "x");
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+39
-2
@@ -14,10 +14,9 @@
|
||||
#include "rocksdb/utilities/transaction_db.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "utilities/fault_injection_env.h"
|
||||
#include "utilities/merge_operators/string_append/stringappend2.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class DBSecondaryTestBase : public DBBasicTestWithTimestampBase {
|
||||
public:
|
||||
explicit DBSecondaryTestBase(const std::string& dbname)
|
||||
@@ -161,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"));
|
||||
@@ -331,6 +331,43 @@ TEST_F(DBSecondaryTest, InternalCompactionMultiLevels) {
|
||||
// cfh, input1, &result));
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, GetMergeOperands) {
|
||||
Options options;
|
||||
options.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||
options.env = env_;
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(Merge("k1", "v1"));
|
||||
ASSERT_OK(Merge("k1", "v2"));
|
||||
ASSERT_OK(Merge("k1", "v3"));
|
||||
ASSERT_OK(Merge("k1", "v4"));
|
||||
|
||||
options.max_open_files = -1;
|
||||
OpenSecondary(options);
|
||||
|
||||
ASSERT_OK(db_secondary_->TryCatchUpWithPrimary());
|
||||
|
||||
int num_records = 4;
|
||||
int number_of_operands = 0;
|
||||
std::vector<PinnableSlice> values(num_records);
|
||||
GetMergeOperandsOptions merge_operands_info;
|
||||
merge_operands_info.expected_max_number_of_operands = num_records;
|
||||
|
||||
auto cfh = db_secondary_->DefaultColumnFamily();
|
||||
|
||||
const Status s = db_secondary_->GetMergeOperands(
|
||||
ReadOptions(), cfh, "k1", values.data(), &merge_operands_info,
|
||||
&number_of_operands);
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_TRUE(s.IsMergeInProgress());
|
||||
|
||||
ASSERT_EQ(number_of_operands, 4);
|
||||
ASSERT_EQ(values[0].ToString(), "v1");
|
||||
ASSERT_EQ(values[1].ToString(), "v2");
|
||||
ASSERT_EQ(values[2].ToString(), "v3");
|
||||
ASSERT_EQ(values[3].ToString(), "v4");
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, InternalCompactionCompactedFiles) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
|
||||
@@ -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());
|
||||
|
||||
+71
-8
@@ -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*/,
|
||||
@@ -3409,10 +3420,6 @@ class ModelDB : public DB {
|
||||
return Status::NotSupported();
|
||||
}
|
||||
|
||||
Status DEPRECATED_DeleteFile(std::string /*name*/) override {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status GetUpdatesSince(
|
||||
ROCKSDB_NAMESPACE::SequenceNumber,
|
||||
std::unique_ptr<ROCKSDB_NAMESPACE::TransactionLogIterator>*,
|
||||
@@ -5294,7 +5301,6 @@ TEST_F(DBTest, DynamicLevelCompressionPerLevel) {
|
||||
for (int i = 0; i < kNKeys; i++) {
|
||||
keys[i] = i;
|
||||
}
|
||||
RandomShuffle(std::begin(keys), std::end(keys));
|
||||
|
||||
Random rnd(301);
|
||||
Options options;
|
||||
@@ -5319,7 +5325,7 @@ TEST_F(DBTest, DynamicLevelCompressionPerLevel) {
|
||||
options.compression_per_level[0] = kNoCompression;
|
||||
// No compression for the Ln whre L0 is compacted to
|
||||
options.compression_per_level[1] = kNoCompression;
|
||||
// Snpapy compression for Ln+1
|
||||
// Snappy compression for Ln+1
|
||||
options.compression_per_level[2] = kSnappyCompression;
|
||||
|
||||
OnFileDeletionListener* listener = new OnFileDeletionListener();
|
||||
@@ -5331,6 +5337,7 @@ TEST_F(DBTest, DynamicLevelCompressionPerLevel) {
|
||||
// be compressed, so there shouldn't be any compression.
|
||||
for (int i = 0; i < 20; i++) {
|
||||
ASSERT_OK(Put(Key(keys[i]), CompressibleString(&rnd, 4000)));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForBackgroundWork());
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
@@ -5349,8 +5356,9 @@ TEST_F(DBTest, DynamicLevelCompressionPerLevel) {
|
||||
// above compression settings for each level, there will be some compression.
|
||||
ASSERT_OK(options.statistics->Reset());
|
||||
ASSERT_EQ(num_block_compressed, 0);
|
||||
for (int i = 21; i < 120; i++) {
|
||||
for (int i = 20; i < 120; i++) {
|
||||
ASSERT_OK(Put(Key(keys[i]), CompressibleString(&rnd, 4000)));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForBackgroundWork());
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
@@ -5371,9 +5379,44 @@ TEST_F(DBTest, DynamicLevelCompressionPerLevel) {
|
||||
}));
|
||||
ColumnFamilyMetaData cf_meta;
|
||||
db_->GetColumnFamilyMetaData(&cf_meta);
|
||||
|
||||
// Ensure that L1+ files are non-overlapping and together with L0 encompass
|
||||
// full key range between smallestkey and largestkey from CF file metadata.
|
||||
int largestkey_in_prev_level = -1;
|
||||
int keys_found = 0;
|
||||
for (int level = (int)cf_meta.levels.size() - 1; level >= 0; level--) {
|
||||
int files_in_level = (int)cf_meta.levels[level].files.size();
|
||||
int largestkey_in_prev_file = -1;
|
||||
for (int j = 0; j < files_in_level; j++) {
|
||||
int smallestkey = IdFromKey(cf_meta.levels[level].files[j].smallestkey);
|
||||
int largestkey = IdFromKey(cf_meta.levels[level].files[j].largestkey);
|
||||
int num_entries = (int)cf_meta.levels[level].files[j].num_entries;
|
||||
ASSERT_EQ(num_entries, largestkey - smallestkey + 1);
|
||||
keys_found += num_entries;
|
||||
if (level > 0) {
|
||||
if (j == 0) {
|
||||
ASSERT_GT(smallestkey, largestkey_in_prev_level);
|
||||
}
|
||||
if (j > 0) {
|
||||
ASSERT_GT(smallestkey, largestkey_in_prev_file);
|
||||
}
|
||||
if (j == files_in_level - 1) {
|
||||
largestkey_in_prev_level = largestkey;
|
||||
}
|
||||
}
|
||||
largestkey_in_prev_file = largestkey;
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(keys_found, kNKeys);
|
||||
|
||||
for (const auto& file : cf_meta.levels[4].files) {
|
||||
listener->SetExpectedFileName(dbname_ + file.name);
|
||||
ASSERT_OK(dbfull()->DEPRECATED_DeleteFile(file.name));
|
||||
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.
|
||||
EXPECT_OK(dbfull()->DeleteFilesInRanges(dbfull()->DefaultColumnFamily(),
|
||||
&ranges, true /* include_end */));
|
||||
}
|
||||
listener->VerifyMatchedCount(cf_meta.levels[4].files.size());
|
||||
|
||||
@@ -5420,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;
|
||||
|
||||
@@ -6051,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
|
||||
@@ -6073,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);
|
||||
@@ -6085,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));
|
||||
|
||||
+4
-7
@@ -1475,8 +1475,7 @@ TEST_P(PresetCompressionDictTest, Flush) {
|
||||
// TODO(ajkr): fix the below assertion to work with ZSTD. The expectation on
|
||||
// number of bytes needs to be adjusted in case the cached block is in
|
||||
// ZSTD's digested dictionary format.
|
||||
if (compression_type_ != kZSTD &&
|
||||
compression_type_ != kZSTDNotFinalCompression) {
|
||||
if (compression_type_ != kZSTD) {
|
||||
// Although we limited buffering to `kBlockLen`, there may be up to two
|
||||
// blocks of data included in the dictionary since we only check limit
|
||||
// after each block is built.
|
||||
@@ -1553,8 +1552,7 @@ TEST_P(PresetCompressionDictTest, CompactNonBottommost) {
|
||||
// TODO(ajkr): fix the below assertion to work with ZSTD. The expectation on
|
||||
// number of bytes needs to be adjusted in case the cached block is in
|
||||
// ZSTD's digested dictionary format.
|
||||
if (compression_type_ != kZSTD &&
|
||||
compression_type_ != kZSTDNotFinalCompression) {
|
||||
if (compression_type_ != kZSTD) {
|
||||
// Although we limited buffering to `kBlockLen`, there may be up to two
|
||||
// blocks of data included in the dictionary since we only check limit
|
||||
// after each block is built.
|
||||
@@ -1615,8 +1613,7 @@ TEST_P(PresetCompressionDictTest, CompactBottommost) {
|
||||
// TODO(ajkr): fix the below assertion to work with ZSTD. The expectation on
|
||||
// number of bytes needs to be adjusted in case the cached block is in ZSTD's
|
||||
// digested dictionary format.
|
||||
if (compression_type_ != kZSTD &&
|
||||
compression_type_ != kZSTDNotFinalCompression) {
|
||||
if (compression_type_ != kZSTD) {
|
||||
// Although we limited buffering to `kBlockLen`, there may be up to two
|
||||
// blocks of data included in the dictionary since we only check limit after
|
||||
// each block is built.
|
||||
@@ -8000,7 +7997,7 @@ TEST_F(DBTest2, GetLatestSeqAndTsForKey) {
|
||||
ASSERT_EQ(0, options.statistics->getTickerCount(GET_HIT_L0));
|
||||
}
|
||||
|
||||
#if defined(ZSTD_ADVANCED)
|
||||
#if defined(ZSTD)
|
||||
TEST_F(DBTest2, ZSTDChecksum) {
|
||||
// Verify that corruption during decompression is caught.
|
||||
Options options = CurrentOptions();
|
||||
|
||||
@@ -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_;
|
||||
@@ -1106,6 +1107,11 @@ class DBTestBase : public testing::Test {
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
// Expects valid key created by Key().
|
||||
static int IdFromKey(const std::string& key) {
|
||||
return std::stoi(key.substr(3));
|
||||
}
|
||||
|
||||
static bool ShouldSkipOptions(int option_config, int skip_mask = kNoSkip);
|
||||
|
||||
// Switch to a fresh database with the next option configuration to
|
||||
@@ -1173,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();
|
||||
|
||||
@@ -1153,6 +1153,7 @@ TEST_F(DBWALTest, FullPurgePreservesRecycledLog) {
|
||||
dbfull()->FindObsoleteFiles(&job_context, true /* force */);
|
||||
dbfull()->TEST_UnlockMutex();
|
||||
dbfull()->PurgeObsoleteFiles(job_context);
|
||||
job_context.Clean();
|
||||
|
||||
if (i == 0) {
|
||||
ASSERT_OK(
|
||||
|
||||
@@ -1480,6 +1480,55 @@ TEST_F(DBBasicTestWithTimestamp, ReseekToUserKeyBeforeSavedKey) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp,
|
||||
FIXME_ReverseIterationWithBlobAndUnpreparedValue) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.env = env_;
|
||||
options.enable_blob_files = true;
|
||||
options.max_sequential_skip_in_iterations = 0;
|
||||
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
constexpr uint64_t kMaxKey = 1024;
|
||||
|
||||
const std::vector<std::string> write_timestamps = {Timestamp(1, 0),
|
||||
Timestamp(3, 0)};
|
||||
for (uint64_t key = 0; key <= kMaxKey; ++key) {
|
||||
for (size_t i = 0; i < write_timestamps.size(); ++i) {
|
||||
ASSERT_OK(db_->Put(WriteOptions(), Key1(key), write_timestamps[i],
|
||||
"value" + std::to_string(i)));
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
{
|
||||
const std::string read_timestamp_str = Timestamp(4, 0);
|
||||
const Slice read_timestamp(read_timestamp_str);
|
||||
|
||||
ReadOptions read_opts;
|
||||
read_opts.timestamp = &read_timestamp;
|
||||
read_opts.allow_unprepared_value = true;
|
||||
|
||||
std::unique_ptr<Iterator> it(db_->NewIterator(read_opts));
|
||||
|
||||
it->SeekForPrev(Key1(kMaxKey));
|
||||
ASSERT_TRUE(it->Valid());
|
||||
ASSERT_OK(it->status());
|
||||
|
||||
// FIXME: PrepareValue() should succeed and status() should remain OK
|
||||
ASSERT_FALSE(it->PrepareValue());
|
||||
ASSERT_TRUE(it->status().IsCorruption());
|
||||
}
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, MultiGetWithFastLocalBloom) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
|
||||
+10
-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));
|
||||
@@ -498,6 +500,8 @@ class InternalKey {
|
||||
// Intended only to be used together with ConvertFromUserKey().
|
||||
std::string* rep() { return &rep_; }
|
||||
|
||||
const std::string* const_rep() const { return &rep_; }
|
||||
|
||||
// Assuming that *rep() contains a user key, this method makes internal key
|
||||
// out of it in-place. This saves a memcpy compared to Set()/SetFrom().
|
||||
void ConvertFromUserKey(SequenceNumber s, ValueType t) {
|
||||
|
||||
@@ -135,57 +135,6 @@ class DeleteFileTest : public DBTestBase {
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(DeleteFileTest, AddKeysAndQueryLevels) {
|
||||
Options options = CurrentOptions();
|
||||
SetOptions(&options);
|
||||
Destroy(options);
|
||||
options.create_if_missing = true;
|
||||
Reopen(options);
|
||||
|
||||
CreateTwoLevels();
|
||||
std::vector<LiveFileMetaData> metadata;
|
||||
db_->GetLiveFilesMetaData(&metadata);
|
||||
|
||||
std::string level1file;
|
||||
int level1keycount = 0;
|
||||
std::string level2file;
|
||||
int level2keycount = 0;
|
||||
int level1index = 0;
|
||||
int level2index = 1;
|
||||
|
||||
ASSERT_EQ((int)metadata.size(), 2);
|
||||
if (metadata[0].level == 2) {
|
||||
level1index = 1;
|
||||
level2index = 0;
|
||||
}
|
||||
|
||||
level1file = metadata[level1index].name;
|
||||
int startkey = atoi(metadata[level1index].smallestkey.c_str());
|
||||
int endkey = atoi(metadata[level1index].largestkey.c_str());
|
||||
level1keycount = (endkey - startkey + 1);
|
||||
level2file = metadata[level2index].name;
|
||||
startkey = atoi(metadata[level2index].smallestkey.c_str());
|
||||
endkey = atoi(metadata[level2index].largestkey.c_str());
|
||||
level2keycount = (endkey - startkey + 1);
|
||||
|
||||
// COntrolled setup. Levels 1 and 2 should both have 50K files.
|
||||
// This is a little fragile as it depends on the current
|
||||
// compaction heuristics.
|
||||
ASSERT_EQ(level1keycount, 50000);
|
||||
ASSERT_EQ(level2keycount, 50000);
|
||||
|
||||
Status status = db_->DEPRECATED_DeleteFile("0.sst");
|
||||
ASSERT_TRUE(status.IsInvalidArgument());
|
||||
|
||||
// intermediate level files cannot be deleted.
|
||||
status = db_->DEPRECATED_DeleteFile(level1file);
|
||||
ASSERT_TRUE(status.IsInvalidArgument());
|
||||
|
||||
// Lowest level file deletion should succeed.
|
||||
status = db_->DEPRECATED_DeleteFile(level2file);
|
||||
ASSERT_OK(status);
|
||||
}
|
||||
|
||||
TEST_F(DeleteFileTest, PurgeObsoleteFilesTest) {
|
||||
Options options = CurrentOptions();
|
||||
SetOptions(&options);
|
||||
@@ -496,145 +445,6 @@ TEST_F(DeleteFileTest, BackgroundPurgeTestMultipleJobs) {
|
||||
CheckFileTypeCounts(dbname_, 0, 1, 1);
|
||||
}
|
||||
|
||||
TEST_F(DeleteFileTest, DeleteFileWithIterator) {
|
||||
Options options = CurrentOptions();
|
||||
SetOptions(&options);
|
||||
Destroy(options);
|
||||
options.create_if_missing = true;
|
||||
Reopen(options);
|
||||
|
||||
CreateTwoLevels();
|
||||
ReadOptions read_options;
|
||||
Iterator* it = db_->NewIterator(read_options);
|
||||
ASSERT_OK(it->status());
|
||||
std::vector<LiveFileMetaData> metadata;
|
||||
db_->GetLiveFilesMetaData(&metadata);
|
||||
|
||||
std::string level2file;
|
||||
|
||||
ASSERT_EQ(metadata.size(), static_cast<size_t>(2));
|
||||
if (metadata[0].level == 1) {
|
||||
level2file = metadata[1].name;
|
||||
} else {
|
||||
level2file = metadata[0].name;
|
||||
}
|
||||
|
||||
Status status = db_->DEPRECATED_DeleteFile(level2file);
|
||||
fprintf(stdout, "Deletion status %s: %s\n", level2file.c_str(),
|
||||
status.ToString().c_str());
|
||||
ASSERT_OK(status);
|
||||
it->SeekToFirst();
|
||||
int numKeysIterated = 0;
|
||||
while (it->Valid()) {
|
||||
numKeysIterated++;
|
||||
it->Next();
|
||||
}
|
||||
ASSERT_EQ(numKeysIterated, 50000);
|
||||
delete it;
|
||||
}
|
||||
|
||||
TEST_F(DeleteFileTest, DeleteLogFiles) {
|
||||
Options options = CurrentOptions();
|
||||
SetOptions(&options);
|
||||
Destroy(options);
|
||||
options.create_if_missing = true;
|
||||
Reopen(options);
|
||||
|
||||
AddKeys(10, 0);
|
||||
VectorLogPtr logfiles;
|
||||
ASSERT_OK(db_->GetSortedWalFiles(logfiles));
|
||||
ASSERT_GT(logfiles.size(), 0UL);
|
||||
// Take the last log file which is expected to be alive and try to delete it
|
||||
// Should not succeed because live logs are not allowed to be deleted
|
||||
std::unique_ptr<LogFile> alive_log = std::move(logfiles.back());
|
||||
ASSERT_EQ(alive_log->Type(), kAliveLogFile);
|
||||
ASSERT_OK(env_->FileExists(wal_dir_ + "/" + alive_log->PathName()));
|
||||
fprintf(stdout, "Deleting alive log file %s\n",
|
||||
alive_log->PathName().c_str());
|
||||
ASSERT_NOK(db_->DEPRECATED_DeleteFile(alive_log->PathName()));
|
||||
ASSERT_OK(env_->FileExists(wal_dir_ + "/" + alive_log->PathName()));
|
||||
logfiles.clear();
|
||||
|
||||
// Call Flush to bring about a new working log file and add more keys
|
||||
// Call Flush again to flush out memtable and move alive log to archived log
|
||||
// and try to delete the archived log file
|
||||
FlushOptions fopts;
|
||||
ASSERT_OK(db_->Flush(fopts));
|
||||
AddKeys(10, 0);
|
||||
ASSERT_OK(db_->Flush(fopts));
|
||||
ASSERT_OK(db_->GetSortedWalFiles(logfiles));
|
||||
ASSERT_GT(logfiles.size(), 0UL);
|
||||
std::unique_ptr<LogFile> archived_log = std::move(logfiles.front());
|
||||
ASSERT_EQ(archived_log->Type(), kArchivedLogFile);
|
||||
ASSERT_OK(env_->FileExists(wal_dir_ + "/" + archived_log->PathName()));
|
||||
fprintf(stdout, "Deleting archived log file %s\n",
|
||||
archived_log->PathName().c_str());
|
||||
ASSERT_OK(db_->DEPRECATED_DeleteFile(archived_log->PathName()));
|
||||
ASSERT_TRUE(
|
||||
env_->FileExists(wal_dir_ + "/" + archived_log->PathName()).IsNotFound());
|
||||
}
|
||||
|
||||
TEST_F(DeleteFileTest, DeleteNonDefaultColumnFamily) {
|
||||
Options options = CurrentOptions();
|
||||
SetOptions(&options);
|
||||
Destroy(options);
|
||||
options.create_if_missing = true;
|
||||
Reopen(options);
|
||||
CreateAndReopenWithCF({"new_cf"}, options);
|
||||
|
||||
Random rnd(5);
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
ASSERT_OK(db_->Put(WriteOptions(), handles_[1], test::RandomKey(&rnd, 10),
|
||||
test::RandomKey(&rnd, 10)));
|
||||
}
|
||||
ASSERT_OK(db_->Flush(FlushOptions(), handles_[1]));
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
ASSERT_OK(db_->Put(WriteOptions(), handles_[1], test::RandomKey(&rnd, 10),
|
||||
test::RandomKey(&rnd, 10)));
|
||||
}
|
||||
ASSERT_OK(db_->Flush(FlushOptions(), handles_[1]));
|
||||
|
||||
std::vector<LiveFileMetaData> metadata;
|
||||
db_->GetLiveFilesMetaData(&metadata);
|
||||
ASSERT_EQ(2U, metadata.size());
|
||||
ASSERT_EQ("new_cf", metadata[0].column_family_name);
|
||||
ASSERT_EQ("new_cf", metadata[1].column_family_name);
|
||||
auto old_file = metadata[0].smallest_seqno < metadata[1].smallest_seqno
|
||||
? metadata[0].name
|
||||
: metadata[1].name;
|
||||
auto new_file = metadata[0].smallest_seqno > metadata[1].smallest_seqno
|
||||
? metadata[0].name
|
||||
: metadata[1].name;
|
||||
ASSERT_TRUE(db_->DEPRECATED_DeleteFile(new_file).IsInvalidArgument());
|
||||
ASSERT_OK(db_->DEPRECATED_DeleteFile(old_file));
|
||||
|
||||
{
|
||||
std::unique_ptr<Iterator> itr(db_->NewIterator(ReadOptions(), handles_[1]));
|
||||
ASSERT_OK(itr->status());
|
||||
int count = 0;
|
||||
for (itr->SeekToFirst(); itr->Valid(); itr->Next()) {
|
||||
ASSERT_OK(itr->status());
|
||||
++count;
|
||||
}
|
||||
ASSERT_OK(itr->status());
|
||||
ASSERT_EQ(count, 1000);
|
||||
}
|
||||
|
||||
Close();
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "new_cf"}, options);
|
||||
|
||||
{
|
||||
std::unique_ptr<Iterator> itr(db_->NewIterator(ReadOptions(), handles_[1]));
|
||||
int count = 0;
|
||||
for (itr->SeekToFirst(); itr->Valid(); itr->Next()) {
|
||||
ASSERT_OK(itr->status());
|
||||
++count;
|
||||
}
|
||||
ASSERT_OK(itr->status());
|
||||
ASSERT_EQ(count, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+4
-2
@@ -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;
|
||||
@@ -74,7 +75,8 @@ Status GetFileChecksumsFromCurrentManifest(FileSystem* fs,
|
||||
|
||||
struct LogReporter : public log::Reader::Reporter {
|
||||
Status* status_ptr;
|
||||
void Corruption(size_t /*bytes*/, const Status& st) override {
|
||||
void Corruption(size_t /*bytes*/, const Status& st,
|
||||
uint64_t /*log_number*/ = kMaxSequenceNumber) override {
|
||||
if (status_ptr->ok()) {
|
||||
*status_ptr = st;
|
||||
}
|
||||
|
||||
@@ -265,8 +265,8 @@ TEST_F(ExternalSSTFileBasicTest, Basic) {
|
||||
SyncPoint::GetInstance()->LoadDependency({
|
||||
{"DBImpl::IngestExternalFile:AfterIncIngestFileCounter",
|
||||
"ExternalSSTFileBasicTest.LiveWriteStart"},
|
||||
{"ExternalSSTFileBasicTest.LiveWriteStart",
|
||||
"DBImpl::IngestExternalFiles:InstallSVForFirstCF:0"},
|
||||
{"WriteThread::JoinBatchGroup:Wait",
|
||||
"DBImpl::IngestExternalFile:AfterIncIngestFileCounter:2"},
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
PerfContext* write_thread_perf_context;
|
||||
@@ -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 {
|
||||
@@ -2669,51 +2693,283 @@ TEST_F(ExternalSSTFileBasicTest, IngestWithTemperature) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileBasicTest, FailIfNotBottommostLevel) {
|
||||
Options options = GetDefaultOptions();
|
||||
TEST_F(ExternalSSTFileBasicTest, FailIfNotBottommostLevelAndDisallowMemtable) {
|
||||
for (bool disallow_memtable : {false, true}) {
|
||||
Options options = GetDefaultOptions();
|
||||
|
||||
std::string file_path = sst_files_dir_ + std::to_string(1);
|
||||
SstFileWriter sfw(EnvOptions(), options);
|
||||
// First test with universal compaction
|
||||
options.create_if_missing = true;
|
||||
options.compaction_style = CompactionStyle::kCompactionStyleUniversal;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(sfw.Open(file_path));
|
||||
ASSERT_OK(sfw.Put("b", "dontcare"));
|
||||
ASSERT_OK(sfw.Finish());
|
||||
// And a CF potentially disallowing memtable write
|
||||
options.disallow_memtable_writes = disallow_memtable;
|
||||
CreateColumnFamilies({"cf0"}, options);
|
||||
ASSERT_EQ(db_->GetOptions(handles_[0]).disallow_memtable_writes,
|
||||
disallow_memtable);
|
||||
|
||||
// Test universal compaction + ingest with snapshot consistency
|
||||
options.create_if_missing = true;
|
||||
options.compaction_style = CompactionStyle::kCompactionStyleUniversal;
|
||||
DestroyAndReopen(options);
|
||||
{
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
ManagedSnapshot snapshot_guard(db_, snapshot);
|
||||
IngestExternalFileOptions ifo;
|
||||
ifo.fail_if_not_bottommost_level = true;
|
||||
ifo.snapshot_consistency = true;
|
||||
const Status s = db_->IngestExternalFile({file_path}, ifo);
|
||||
ASSERT_TRUE(s.ok());
|
||||
}
|
||||
// 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);
|
||||
|
||||
// Test level compaction
|
||||
options.compaction_style = CompactionStyle::kCompactionStyleLevel;
|
||||
options.num_levels = 2;
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "a", "dontcare"));
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "c", "dontcare"));
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
ASSERT_OK(sfw.Open(file_path));
|
||||
ASSERT_OK(sfw.Put("b", "0"));
|
||||
ASSERT_OK(sfw.Finish());
|
||||
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "b", "dontcare"));
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "d", "dontcare"));
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
{
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
ManagedSnapshot snapshot_guard(db_, snapshot);
|
||||
IngestExternalFileOptions ifo;
|
||||
ifo.fail_if_not_bottommost_level = true;
|
||||
ifo.snapshot_consistency = true;
|
||||
ASSERT_OK(db_->IngestExternalFile(handles_[0], {file_path}, ifo));
|
||||
}
|
||||
ASSERT_EQ(Get(0, "b"), "0");
|
||||
|
||||
{
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
// Test level compaction
|
||||
options.compaction_style = CompactionStyle::kCompactionStyleLevel;
|
||||
options.num_levels = 2;
|
||||
CreateColumnFamilies({"cf1"}, options);
|
||||
ASSERT_EQ(db_->GetOptions(handles_[1]).disallow_memtable_writes,
|
||||
disallow_memtable);
|
||||
|
||||
IngestExternalFileOptions ifo;
|
||||
ifo.fail_if_not_bottommost_level = true;
|
||||
const Status s = db_->IngestExternalFile({file_path}, ifo);
|
||||
ASSERT_TRUE(s.IsTryAgain());
|
||||
if (!disallow_memtable) {
|
||||
ASSERT_OK(Put(1, "a", "1"));
|
||||
ASSERT_OK(Put(1, "c", "3"));
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
ASSERT_OK(Put(1, "b", "2"));
|
||||
ASSERT_OK(Put(1, "d", "4"));
|
||||
ASSERT_OK(Flush(1));
|
||||
} else {
|
||||
// Memtable write disallowed
|
||||
EXPECT_EQ(Put(1, "a", "1").code(), Status::Code::kInvalidArgument);
|
||||
|
||||
// Use ingestion to get to the same state as above
|
||||
ASSERT_OK(sfw.Open(file_path2));
|
||||
ASSERT_OK(sfw.Put("a", "1"));
|
||||
ASSERT_OK(sfw.Put("c", "3"));
|
||||
ASSERT_OK(sfw.Finish());
|
||||
ASSERT_OK(db_->IngestExternalFile(handles_[1], {file_path2}, {}));
|
||||
|
||||
ASSERT_OK(sfw.Open(file_path2));
|
||||
ASSERT_OK(sfw.Put("b", "2"));
|
||||
ASSERT_OK(sfw.Put("d", "4"));
|
||||
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;
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -301,6 +301,8 @@ static const std::string aggregated_table_properties =
|
||||
static const std::string aggregated_table_properties_at_level =
|
||||
aggregated_table_properties + "-at-level";
|
||||
static const std::string num_running_compactions = "num-running-compactions";
|
||||
static const std::string num_running_compaction_sorted_runs =
|
||||
"num-running-compaction-sorted-runs";
|
||||
static const std::string num_running_flushes = "num-running-flushes";
|
||||
static const std::string actual_delayed_write_rate =
|
||||
"actual-delayed-write-rate";
|
||||
@@ -351,6 +353,8 @@ const std::string DB::Properties::kCompactionPending =
|
||||
rocksdb_prefix + compaction_pending;
|
||||
const std::string DB::Properties::kNumRunningCompactions =
|
||||
rocksdb_prefix + num_running_compactions;
|
||||
const std::string DB::Properties::kNumRunningCompactionSortedRuns =
|
||||
rocksdb_prefix + num_running_compaction_sorted_runs;
|
||||
const std::string DB::Properties::kNumRunningFlushes =
|
||||
rocksdb_prefix + num_running_flushes;
|
||||
const std::string DB::Properties::kBackgroundErrors =
|
||||
@@ -580,6 +584,9 @@ const UnorderedMap<std::string, DBPropertyInfo>
|
||||
{DB::Properties::kNumRunningCompactions,
|
||||
{false, nullptr, &InternalStats::HandleNumRunningCompactions, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kNumRunningCompactionSortedRuns,
|
||||
{false, nullptr, &InternalStats::HandleNumRunningCompactionSortedRuns,
|
||||
nullptr, nullptr}},
|
||||
{DB::Properties::kActualDelayedWriteRate,
|
||||
{false, nullptr, &InternalStats::HandleActualDelayedWriteRate, nullptr,
|
||||
nullptr}},
|
||||
@@ -636,6 +643,7 @@ InternalStats::InternalStats(int num_levels, SystemClock* clock,
|
||||
file_read_latency_(num_levels),
|
||||
has_cf_change_since_dump_(true),
|
||||
bg_error_count_(0),
|
||||
num_running_compaction_sorted_runs_(0),
|
||||
number_levels_(num_levels),
|
||||
clock_(clock),
|
||||
cfd_(cfd),
|
||||
@@ -1265,6 +1273,18 @@ bool InternalStats::HandleNumRunningCompactions(uint64_t* value, DBImpl* db,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InternalStats::HandleNumRunningCompactionSortedRuns(uint64_t* value,
|
||||
DBImpl* db,
|
||||
Version* /*version*/) {
|
||||
db->mutex()->AssertHeld();
|
||||
uint64_t sorted_runs = 0;
|
||||
for (auto* cfd : *db->versions_->GetColumnFamilySet()) {
|
||||
sorted_runs += cfd->internal_stats()->NumRunningCompactionSortedRuns();
|
||||
}
|
||||
*value = sorted_runs;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InternalStats::HandleBackgroundErrors(uint64_t* value, DBImpl* /*db*/,
|
||||
Version* /*version*/) {
|
||||
// Accumulated number of errors in background flushes or compactions.
|
||||
|
||||
+48
-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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -604,6 +579,20 @@ class InternalStats {
|
||||
++cf_stats_count_[type];
|
||||
}
|
||||
|
||||
void IncrNumRunningCompactionSortedRuns(uint64_t value) {
|
||||
num_running_compaction_sorted_runs_.fetch_add(value,
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void DecrNumRunningCompactionSortedRuns(uint64_t value) {
|
||||
num_running_compaction_sorted_runs_.fetch_sub(value,
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
uint64_t NumRunningCompactionSortedRuns() {
|
||||
return num_running_compaction_sorted_runs_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void AddDBStats(InternalDBStatsType type, uint64_t value,
|
||||
bool concurrent = false) {
|
||||
auto& v = db_stats_[type];
|
||||
@@ -847,6 +836,8 @@ class InternalStats {
|
||||
bool HandleCompactionPending(uint64_t* value, DBImpl* db, Version* version);
|
||||
bool HandleNumRunningCompactions(uint64_t* value, DBImpl* db,
|
||||
Version* version);
|
||||
bool HandleNumRunningCompactionSortedRuns(uint64_t* value, DBImpl* db,
|
||||
Version* version);
|
||||
bool HandleBackgroundErrors(uint64_t* value, DBImpl* db, Version* version);
|
||||
bool HandleCurSizeActiveMemTable(uint64_t* value, DBImpl* db,
|
||||
Version* version);
|
||||
@@ -921,6 +912,12 @@ class InternalStats {
|
||||
// or compaction will cause the counter to increase too.
|
||||
uint64_t bg_error_count_;
|
||||
|
||||
// This is a rolling count of the number of sorted runs being processed by
|
||||
// currently running compactions. Other metrics are only incremented, but this
|
||||
// metric is also decremented. Additionally, we also do not want to reset this
|
||||
// count to zero at a periodic interval.
|
||||
std::atomic<uint64_t> num_running_compaction_sorted_runs_;
|
||||
|
||||
const int number_levels_;
|
||||
SystemClock* clock_;
|
||||
ColumnFamilyData* cfd_;
|
||||
|
||||
+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) {}
|
||||
|
||||
|
||||
+28
-8
@@ -1285,7 +1285,9 @@ class BlobDBJobLevelEventListenerTest : public EventListener {
|
||||
ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault();
|
||||
EXPECT_NE(cfd, nullptr);
|
||||
|
||||
test_->dbfull()->TEST_LockMutex();
|
||||
Version* const current = cfd->current();
|
||||
test_->dbfull()->TEST_UnlockMutex();
|
||||
EXPECT_NE(current, nullptr);
|
||||
|
||||
const VersionStorageInfo* const storage_info = current->storage_info();
|
||||
@@ -1325,10 +1327,9 @@ class BlobDBJobLevelEventListenerTest : public EventListener {
|
||||
}
|
||||
|
||||
void OnFlushCompleted(DB* /*db*/, const FlushJobInfo& info) override {
|
||||
call_count_++;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
IncreaseCallCount(/*mutex_locked*/ true);
|
||||
flushed_files_.push_back(info.file_path);
|
||||
}
|
||||
|
||||
@@ -1339,7 +1340,7 @@ class BlobDBJobLevelEventListenerTest : public EventListener {
|
||||
|
||||
void OnCompactionCompleted(DB* /*db*/,
|
||||
const CompactionJobInfo& info) override {
|
||||
call_count_++;
|
||||
IncreaseCallCount(/*mutex_locked*/ false);
|
||||
|
||||
EXPECT_EQ(info.blob_compression_type, kNoCompression);
|
||||
|
||||
@@ -1355,12 +1356,31 @@ class BlobDBJobLevelEventListenerTest : public EventListener {
|
||||
}
|
||||
}
|
||||
|
||||
void IncreaseCallCount(bool mutex_locked) {
|
||||
if (!mutex_locked) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
call_count_++;
|
||||
} else {
|
||||
call_count_++;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t GetCallCount() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return call_count_;
|
||||
}
|
||||
|
||||
void ResetCallCount() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
call_count_ = 0;
|
||||
}
|
||||
|
||||
EventListenerTest* test_;
|
||||
uint32_t call_count_;
|
||||
|
||||
private:
|
||||
std::vector<std::string> flushed_files_;
|
||||
std::mutex mutex_;
|
||||
std::vector<std::string> flushed_files_;
|
||||
uint32_t call_count_;
|
||||
};
|
||||
|
||||
// Test OnFlushCompleted EventListener called for blob files
|
||||
@@ -1389,7 +1409,7 @@ TEST_F(EventListenerTest, BlobDBOnFlushCompleted) {
|
||||
ASSERT_EQ(Get("Key2"), "blob_value2");
|
||||
ASSERT_EQ(Get("Key3"), "blob_value3");
|
||||
|
||||
ASSERT_GT(blob_event_listener->call_count_, 0U);
|
||||
ASSERT_GT(blob_event_listener->GetCallCount(), 0U);
|
||||
}
|
||||
|
||||
// Test OnCompactionCompleted EventListener called for blob files
|
||||
@@ -1423,7 +1443,7 @@ TEST_F(EventListenerTest, BlobDBOnCompactionCompleted) {
|
||||
ASSERT_OK(Put("Key6", "blob_value6"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
blob_event_listener->call_count_ = 0;
|
||||
blob_event_listener->ResetCallCount();
|
||||
constexpr Slice* begin = nullptr;
|
||||
constexpr Slice* end = nullptr;
|
||||
|
||||
@@ -1432,7 +1452,7 @@ TEST_F(EventListenerTest, BlobDBOnCompactionCompleted) {
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), begin, end));
|
||||
|
||||
// Make sure, OnCompactionCompleted is called.
|
||||
ASSERT_GT(blob_event_listener->call_count_, 0U);
|
||||
ASSERT_GT(blob_event_listener->GetCallCount(), 0U);
|
||||
}
|
||||
|
||||
// Test CompactFiles calls OnCompactionCompleted EventListener for blob files
|
||||
|
||||
+14
-8
@@ -374,14 +374,16 @@ void Reader::MaybeVerifyPredecessorWALInfo(
|
||||
if (recorded_predecessor_log_number >= min_wal_number_to_keep_) {
|
||||
std::string reason = "Missing WAL of log number " +
|
||||
std::to_string(recorded_predecessor_log_number);
|
||||
ReportCorruption(fragment.size(), reason.c_str());
|
||||
ReportCorruption(fragment.size(), reason.c_str(),
|
||||
recorded_predecessor_log_number);
|
||||
}
|
||||
} else {
|
||||
if (observed_predecessor_wal_info_.GetLogNumber() !=
|
||||
recorded_predecessor_log_number) {
|
||||
std::string reason = "Missing WAL of log number " +
|
||||
std::to_string(recorded_predecessor_log_number);
|
||||
ReportCorruption(fragment.size(), reason.c_str());
|
||||
ReportCorruption(fragment.size(), reason.c_str(),
|
||||
recorded_predecessor_log_number);
|
||||
} else if (observed_predecessor_wal_info_.GetLastSeqnoRecorded() !=
|
||||
recorded_predecessor_wal_info.GetLastSeqnoRecorded()) {
|
||||
std::string reason =
|
||||
@@ -392,7 +394,8 @@ void Reader::MaybeVerifyPredecessorWALInfo(
|
||||
std::to_string(
|
||||
observed_predecessor_wal_info_.GetLastSeqnoRecorded()) +
|
||||
". (Last sequence number equal to 0 indicates no WAL records)";
|
||||
ReportCorruption(fragment.size(), reason.c_str());
|
||||
ReportCorruption(fragment.size(), reason.c_str(),
|
||||
recorded_predecessor_log_number);
|
||||
} else if (observed_predecessor_wal_info_.GetSizeBytes() !=
|
||||
recorded_predecessor_wal_info.GetSizeBytes()) {
|
||||
std::string reason =
|
||||
@@ -402,7 +405,8 @@ void Reader::MaybeVerifyPredecessorWALInfo(
|
||||
" bytes. Observed " +
|
||||
std::to_string(observed_predecessor_wal_info_.GetSizeBytes()) +
|
||||
" bytes.";
|
||||
ReportCorruption(fragment.size(), reason.c_str());
|
||||
ReportCorruption(fragment.size(), reason.c_str(),
|
||||
recorded_predecessor_log_number);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -483,13 +487,15 @@ void Reader::UnmarkEOFInternal() {
|
||||
}
|
||||
}
|
||||
|
||||
void Reader::ReportCorruption(size_t bytes, const char* reason) {
|
||||
ReportDrop(bytes, Status::Corruption(reason));
|
||||
void Reader::ReportCorruption(size_t bytes, const char* reason,
|
||||
uint64_t log_number) {
|
||||
ReportDrop(bytes, Status::Corruption(reason), log_number);
|
||||
}
|
||||
|
||||
void Reader::ReportDrop(size_t bytes, const Status& reason) {
|
||||
void Reader::ReportDrop(size_t bytes, const Status& reason,
|
||||
uint64_t log_number) {
|
||||
if (reporter_ != nullptr) {
|
||||
reporter_->Corruption(bytes, reason);
|
||||
reporter_->Corruption(bytes, reason, log_number);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+13
-13
@@ -45,7 +45,8 @@ class Reader {
|
||||
|
||||
// Some corruption was detected. "size" is the approximate number
|
||||
// of bytes dropped due to the corruption.
|
||||
virtual void Corruption(size_t bytes, const Status& status) = 0;
|
||||
virtual void Corruption(size_t bytes, const Status& status,
|
||||
uint64_t log_number = kMaxSequenceNumber) = 0;
|
||||
|
||||
virtual void OldLogRecord(size_t /*bytes*/) {}
|
||||
};
|
||||
@@ -220,8 +221,10 @@ class Reader {
|
||||
|
||||
// Reports dropped bytes to the reporter.
|
||||
// buffer_ must be updated to remove the dropped bytes prior to invocation.
|
||||
void ReportCorruption(size_t bytes, const char* reason);
|
||||
void ReportDrop(size_t bytes, const Status& reason);
|
||||
void ReportCorruption(size_t bytes, const char* reason,
|
||||
uint64_t log_number = kMaxSequenceNumber);
|
||||
void ReportDrop(size_t bytes, const Status& reason,
|
||||
uint64_t log_number = kMaxSequenceNumber);
|
||||
void ReportOldLogRecord(size_t bytes);
|
||||
|
||||
void InitCompression(const CompressionTypeRecord& compression_record);
|
||||
@@ -236,17 +239,14 @@ class Reader {
|
||||
|
||||
class FragmentBufferedReader : public Reader {
|
||||
public:
|
||||
FragmentBufferedReader(
|
||||
std::shared_ptr<Logger> info_log,
|
||||
std::unique_ptr<SequentialFileReader>&& _file, Reporter* reporter,
|
||||
bool checksum, uint64_t log_num, bool verify_and_track_wals = false,
|
||||
bool stop_replay_for_corruption = false,
|
||||
uint64_t min_wal_number_to_keep = std::numeric_limits<uint64_t>::max(),
|
||||
const PredecessorWALInfo& observed_predecessor_wal_info =
|
||||
PredecessorWALInfo())
|
||||
FragmentBufferedReader(std::shared_ptr<Logger> info_log,
|
||||
std::unique_ptr<SequentialFileReader>&& _file,
|
||||
Reporter* reporter, bool checksum, uint64_t log_num)
|
||||
: Reader(info_log, std::move(_file), reporter, checksum, log_num,
|
||||
verify_and_track_wals, stop_replay_for_corruption,
|
||||
min_wal_number_to_keep, observed_predecessor_wal_info),
|
||||
false /*verify_and_track_wals*/,
|
||||
false /*stop_replay_for_corruption*/,
|
||||
std::numeric_limits<uint64_t>::max() /*min_wal_number_to_keep*/,
|
||||
PredecessorWALInfo() /*observed_predecessor_wal_info*/),
|
||||
fragments_(),
|
||||
in_fragmented_record_(false) {}
|
||||
~FragmentBufferedReader() override {}
|
||||
|
||||
+4
-2
@@ -129,7 +129,8 @@ class LogTest
|
||||
std::string message_;
|
||||
|
||||
ReportCollector() : dropped_bytes_(0) {}
|
||||
void Corruption(size_t bytes, const Status& status) override {
|
||||
void Corruption(size_t bytes, const Status& status,
|
||||
uint64_t /*log_number*/ = kMaxSequenceNumber) override {
|
||||
dropped_bytes_ += bytes;
|
||||
message_.append(status.ToString());
|
||||
}
|
||||
@@ -825,7 +826,8 @@ class RetriableLogTest : public ::testing::TestWithParam<int> {
|
||||
std::string message_;
|
||||
|
||||
ReportCollector() : dropped_bytes_(0) {}
|
||||
void Corruption(size_t bytes, const Status& status) override {
|
||||
void Corruption(size_t bytes, const Status& status,
|
||||
uint64_t /*log_number*/ = kMaxSequenceNumber) override {
|
||||
dropped_bytes_ += bytes;
|
||||
message_.append(status.ToString());
|
||||
}
|
||||
|
||||
+5
-39
@@ -1325,47 +1325,13 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
return false;
|
||||
}
|
||||
case kTypeMerge: {
|
||||
if (!merge_operator) {
|
||||
*(s->status) = Status::InvalidArgument(
|
||||
"merge_operator is not properly initialized.");
|
||||
// Normally we continue the loop (return true) when we see a merge
|
||||
// operand. But in case of an error, we should stop the loop
|
||||
// immediately and pretend we have found the value to stop further
|
||||
// seek. Otherwise, the later call will override this error status.
|
||||
*(s->found_final_value) = true;
|
||||
return false;
|
||||
}
|
||||
Slice v = GetLengthPrefixedSlice(key_ptr + key_length);
|
||||
*(s->merge_in_progress) = true;
|
||||
merge_context->PushOperand(
|
||||
v, s->inplace_update_support == false /* operand_pinned */);
|
||||
PERF_COUNTER_ADD(internal_merge_point_lookup_count, 1);
|
||||
|
||||
if (s->do_merge && merge_operator->ShouldMerge(
|
||||
merge_context->GetOperandsDirectionBackward())) {
|
||||
if (s->value || s->columns) {
|
||||
// `op_failure_scope` (an output parameter) is not provided (set to
|
||||
// nullptr) since a failure must be propagated regardless of its
|
||||
// value.
|
||||
*(s->status) = MergeHelper::TimedFullMerge(
|
||||
merge_operator, s->key->user_key(), MergeHelper::kNoBaseValue,
|
||||
merge_context->GetOperands(), s->logger, s->statistics,
|
||||
s->clock, /* update_num_ops_stats */ true,
|
||||
/* op_failure_scope */ nullptr, s->value, s->columns);
|
||||
}
|
||||
|
||||
*(s->found_final_value) = true;
|
||||
return false;
|
||||
}
|
||||
if (merge_context->get_merge_operands_options != nullptr &&
|
||||
merge_context->get_merge_operands_options->continue_cb != nullptr &&
|
||||
!merge_context->get_merge_operands_options->continue_cb(v)) {
|
||||
// We were told not to continue.
|
||||
*(s->found_final_value) = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
*(s->found_final_value) = ReadOnlyMemTable::HandleTypeMerge(
|
||||
s->key->user_key(), v, s->inplace_update_support == false,
|
||||
s->do_merge, merge_context, s->merge_operator, s->clock,
|
||||
s->statistics, s->logger, s->status, s->value, s->columns);
|
||||
return !*(s->found_final_value);
|
||||
}
|
||||
default: {
|
||||
std::string msg("Corrupted value not expected.");
|
||||
|
||||
+58
-4
@@ -186,10 +186,13 @@ class ReadOnlyMemTable {
|
||||
SequenceNumber read_seq,
|
||||
size_t ts_sz) = 0;
|
||||
|
||||
// Used to Get value associated with key or Get Merge Operands associated
|
||||
// with key.
|
||||
// Keys are considered if they are no larger than the parameter `key` in
|
||||
// Used to get value associated with `key`, or Merge operands associated
|
||||
// with key, or get the latest sequence number of `key` (e.g. transaction
|
||||
// conflict checking).
|
||||
//
|
||||
// Keys are considered if they are no smaller than the parameter `key` in
|
||||
// the order defined by comparator and share the save user key with `key`.
|
||||
//
|
||||
// If do_merge = true the default behavior which is Get value for key is
|
||||
// executed. Expected behavior is described right below.
|
||||
// If memtable contains a value for key, store it in *value and return true.
|
||||
@@ -207,6 +210,7 @@ class ReadOnlyMemTable {
|
||||
// returned). Otherwise, *seq will be set to kMaxSequenceNumber.
|
||||
// On success, *s may be set to OK, NotFound, or MergeInProgress. Any other
|
||||
// status returned indicates a corruption or other unexpected error.
|
||||
//
|
||||
// If do_merge = false then any Merge Operands encountered for key are simply
|
||||
// stored in merge_context.operands_list and never actually merged to get a
|
||||
// final value. The raw Merge Operands are eventually returned to the user.
|
||||
@@ -215,6 +219,9 @@ class ReadOnlyMemTable {
|
||||
// @param column If not null and memtable contains a value/WideColumn for key,
|
||||
// `column` will be set to the result value/WideColumn.
|
||||
// Note: only one of `value` and `column` can be non-nullptr.
|
||||
// To only query for key existence or the latest sequence number of a key,
|
||||
// `value` and `column` can be both nullptr. In this case, returned status can
|
||||
// be OK, NotFound or MergeInProgress if a key is found.
|
||||
// @param immutable_memtable Whether this memtable is immutable. Used
|
||||
// internally by NewRangeTombstoneIterator(). See comment above
|
||||
// NewRangeTombstoneIterator() for more detail.
|
||||
@@ -394,7 +401,6 @@ class ReadOnlyMemTable {
|
||||
// can also be retained.
|
||||
merge_context->PushOperand(value, value_pinned);
|
||||
} else if (merge_in_progress) {
|
||||
assert(do_merge);
|
||||
// `op_failure_scope` (an output parameter) is not provided (set to
|
||||
// nullptr) since a failure must be propagated regardless of its
|
||||
// value.
|
||||
@@ -442,6 +448,54 @@ class ReadOnlyMemTable {
|
||||
}
|
||||
}
|
||||
|
||||
// Returns if a final value is found.
|
||||
static bool HandleTypeMerge(const Slice& lookup_user_key, const Slice& value,
|
||||
bool value_pinned, bool do_merge,
|
||||
MergeContext* merge_context,
|
||||
const MergeOperator* merge_operator,
|
||||
SystemClock* clock, Statistics* statistics,
|
||||
Logger* logger, Status* s, std::string* out_value,
|
||||
PinnableWideColumns* out_columns) {
|
||||
if (!merge_operator) {
|
||||
*s = Status::InvalidArgument(
|
||||
"merge_operator is not properly initialized.");
|
||||
// Normally we continue the loop (return true) when we see a merge
|
||||
// operand. But in case of an error, we should stop the loop
|
||||
// immediately and pretend we have found the value to stop further
|
||||
// seek. Otherwise, the later call will override this error status.
|
||||
return true;
|
||||
}
|
||||
merge_context->PushOperand(value, value_pinned /* operand_pinned */);
|
||||
PERF_COUNTER_ADD(internal_merge_point_lookup_count, 1);
|
||||
|
||||
if (do_merge && merge_operator->ShouldMerge(
|
||||
merge_context->GetOperandsDirectionBackward())) {
|
||||
if (out_value || out_columns) {
|
||||
// `op_failure_scope` (an output parameter) is not provided (set to
|
||||
// nullptr) since a failure must be propagated regardless of its
|
||||
// value.
|
||||
*s = MergeHelper::TimedFullMerge(
|
||||
merge_operator, lookup_user_key, MergeHelper::kNoBaseValue,
|
||||
merge_context->GetOperands(), logger, statistics, clock,
|
||||
/* update_num_ops_stats */ true,
|
||||
/* op_failure_scope */ nullptr, out_value, out_columns);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (merge_context->get_merge_operands_options != nullptr &&
|
||||
merge_context->get_merge_operands_options->continue_cb != nullptr &&
|
||||
!merge_context->get_merge_operands_options->continue_cb(value)) {
|
||||
// We were told not to continue. `status` may be MergeInProress(),
|
||||
// overwrite to signal the end of successful get. This status
|
||||
// will be checked at the end of GetImpl().
|
||||
*s = Status::OK();
|
||||
return true;
|
||||
}
|
||||
|
||||
// no final value found yet
|
||||
return false;
|
||||
}
|
||||
|
||||
protected:
|
||||
friend class MemTableList;
|
||||
|
||||
|
||||
+2
-1
@@ -140,10 +140,11 @@ class MergeContext {
|
||||
}
|
||||
}
|
||||
|
||||
// List of operands
|
||||
// List of operands, the order of operands depends on operands_reversed_.
|
||||
mutable std::unique_ptr<std::vector<Slice>> operand_list_;
|
||||
// Copy of operands that are not pinned.
|
||||
std::unique_ptr<std::vector<std::unique_ptr<std::string>>> copied_operands_;
|
||||
// Reversed means the newest update is ordered first.
|
||||
mutable bool operands_reversed_ = true;
|
||||
};
|
||||
|
||||
|
||||
@@ -377,6 +377,14 @@ void testCountersWithFlushAndCompaction(Counters& counters, DB* db) {
|
||||
{"testCountersWithFlushAndCompaction:AfterGet",
|
||||
"testCountersWithFlushAndCompaction::bg_flush_thread:2"},
|
||||
});
|
||||
// This test relies on old behavior of SetOptions writing to the
|
||||
// manifest. Here we restore that old behavior for reproducer purposes.
|
||||
// (Brief attempts to use an alternative to SetOptions failed.)
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::SetOptions:dummy_edit", [&](void* arg) {
|
||||
auto* dummy_edit = static_cast<VersionEdit*>(arg);
|
||||
dummy_edit->Clear();
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
port::Thread set_options_thread([&]() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+13
-8
@@ -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
|
||||
@@ -356,10 +358,12 @@ class Repairer {
|
||||
Env* env;
|
||||
std::shared_ptr<Logger> info_log;
|
||||
uint64_t lognum;
|
||||
void Corruption(size_t bytes, const Status& s) override {
|
||||
void Corruption(size_t bytes, const Status& s,
|
||||
uint64_t log_number = kMaxSequenceNumber) override {
|
||||
// We print error messages for corruption, but continue repairing.
|
||||
ROCKS_LOG_ERROR(info_log, "Log #%" PRIu64 ": dropping %d bytes; %s",
|
||||
lognum, static_cast<int>(bytes), s.ToString().c_str());
|
||||
log_number == kMaxSequenceNumber ? lognum : log_number,
|
||||
static_cast<int>(bytes), s.ToString().c_str());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -454,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()) {
|
||||
|
||||
@@ -98,7 +98,8 @@ class TransactionLogIteratorImpl : public TransactionLogIterator {
|
||||
struct LogReporter : public log::Reader::Reporter {
|
||||
Env* env;
|
||||
Logger* info_log;
|
||||
void Corruption(size_t bytes, const Status& s) override {
|
||||
void Corruption(size_t bytes, const Status& s,
|
||||
uint64_t /*log_number*/ = kMaxSequenceNumber) override {
|
||||
ROCKS_LOG_ERROR(info_log, "dropping %" ROCKSDB_PRIszt " bytes; %s", bytes,
|
||||
s.ToString().c_str());
|
||||
}
|
||||
|
||||
+2
-33
@@ -59,42 +59,11 @@ Status FileMetaData::UpdateBoundaries(const Slice& key, const Slice& value,
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void VersionEdit::Clear() {
|
||||
max_level_ = 0;
|
||||
db_id_.clear();
|
||||
comparator_.clear();
|
||||
log_number_ = 0;
|
||||
prev_log_number_ = 0;
|
||||
next_file_number_ = 0;
|
||||
max_column_family_ = 0;
|
||||
min_log_number_to_keep_ = 0;
|
||||
last_sequence_ = 0;
|
||||
has_db_id_ = false;
|
||||
has_comparator_ = false;
|
||||
has_log_number_ = false;
|
||||
has_prev_log_number_ = false;
|
||||
has_next_file_number_ = false;
|
||||
has_max_column_family_ = false;
|
||||
has_min_log_number_to_keep_ = false;
|
||||
has_last_sequence_ = false;
|
||||
compact_cursors_.clear();
|
||||
deleted_files_.clear();
|
||||
new_files_.clear();
|
||||
blob_file_additions_.clear();
|
||||
blob_file_garbages_.clear();
|
||||
wal_additions_.clear();
|
||||
wal_deletion_.Reset();
|
||||
column_family_ = 0;
|
||||
is_column_family_add_ = false;
|
||||
is_column_family_drop_ = false;
|
||||
column_family_name_.clear();
|
||||
is_in_atomic_group_ = false;
|
||||
remaining_entries_ = 0;
|
||||
full_history_ts_low_.clear();
|
||||
}
|
||||
void VersionEdit::Clear() { *this = VersionEdit(); }
|
||||
|
||||
bool VersionEdit::EncodeTo(std::string* dst,
|
||||
std::optional<size_t> ts_sz) const {
|
||||
assert(!IsNoManifestWriteDummy());
|
||||
if (has_db_id_) {
|
||||
PutVarint32(dst, kDbId);
|
||||
PutLengthPrefixedSlice(dst, db_id_);
|
||||
|
||||
+5
-1
@@ -685,6 +685,9 @@ class VersionEdit {
|
||||
|
||||
bool IsColumnFamilyDrop() const { return is_column_family_drop_; }
|
||||
|
||||
void MarkNoManifestWriteDummy() { is_no_manifest_write_dummy_ = true; }
|
||||
bool IsNoManifestWriteDummy() const { return is_no_manifest_write_dummy_; }
|
||||
|
||||
void MarkAtomicGroup(uint32_t remaining_entries) {
|
||||
is_in_atomic_group_ = true;
|
||||
remaining_entries_ = remaining_entries;
|
||||
@@ -779,8 +782,9 @@ class VersionEdit {
|
||||
bool is_column_family_add_ = false;
|
||||
std::string column_family_name_;
|
||||
|
||||
bool is_in_atomic_group_ = false;
|
||||
uint32_t remaining_entries_ = 0;
|
||||
bool is_in_atomic_group_ = false;
|
||||
bool is_no_manifest_write_dummy_ = false;
|
||||
|
||||
std::string full_history_ts_low_;
|
||||
bool persist_user_defined_timestamps_ = true;
|
||||
|
||||
@@ -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,
|
||||
|
||||
+186
-101
@@ -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;
|
||||
}
|
||||
|
||||
@@ -5305,6 +5337,9 @@ Status VersionSet::ProcessManifestWrites(
|
||||
// succeeds.
|
||||
SequenceNumber max_last_sequence = descriptor_last_sequence_;
|
||||
|
||||
bool skip_manifest_write =
|
||||
first_writer.edit_list.front()->IsNoManifestWriteDummy();
|
||||
|
||||
if (first_writer.edit_list.front()->IsColumnFamilyManipulation()) {
|
||||
// No group commits for column family add or drop
|
||||
LogAndApplyCFHelper(first_writer.edit_list.front(), &max_last_sequence);
|
||||
@@ -5313,12 +5348,9 @@ Status VersionSet::ProcessManifestWrites(
|
||||
} else {
|
||||
auto it = manifest_writers_.cbegin();
|
||||
size_t group_start = std::numeric_limits<size_t>::max();
|
||||
while (it != manifest_writers_.cend()) {
|
||||
if ((*it)->edit_list.front()->IsColumnFamilyManipulation()) {
|
||||
// no group commits for column family add or drop
|
||||
break;
|
||||
}
|
||||
last_writer = *(it++);
|
||||
for (;;) {
|
||||
assert(!(*it)->edit_list.front()->IsColumnFamilyManipulation());
|
||||
last_writer = *it;
|
||||
assert(last_writer != nullptr);
|
||||
assert(last_writer->cfd != nullptr);
|
||||
if (last_writer->cfd->IsDropped()) {
|
||||
@@ -5353,66 +5385,83 @@ Status VersionSet::ProcessManifestWrites(
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// We do a linear search on versions because versions is small.
|
||||
// TODO(yanqin) maybe consider unordered_map
|
||||
Version* version = nullptr;
|
||||
VersionBuilder* builder = nullptr;
|
||||
for (int i = 0; i != static_cast<int>(versions.size()); ++i) {
|
||||
uint32_t cf_id = last_writer->cfd->GetID();
|
||||
if (versions[i]->cfd()->GetID() == cf_id) {
|
||||
version = versions[i];
|
||||
assert(!builder_guards.empty() &&
|
||||
builder_guards.size() == versions.size());
|
||||
builder = builder_guards[i]->version_builder();
|
||||
} else {
|
||||
// We do a linear search on versions because versions is small.
|
||||
// TODO(yanqin) maybe consider unordered_map
|
||||
Version* version = nullptr;
|
||||
VersionBuilder* builder = nullptr;
|
||||
for (int i = 0; i != static_cast<int>(versions.size()); ++i) {
|
||||
uint32_t cf_id = last_writer->cfd->GetID();
|
||||
if (versions[i]->cfd()->GetID() == cf_id) {
|
||||
version = versions[i];
|
||||
assert(!builder_guards.empty() &&
|
||||
builder_guards.size() == versions.size());
|
||||
builder = builder_guards[i]->version_builder();
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"VersionSet::ProcessManifestWrites:SameColumnFamily", &cf_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (version == nullptr) {
|
||||
// WAL manipulations do not need to be applied to versions.
|
||||
if (!last_writer->IsAllWalEdits()) {
|
||||
version = new Version(
|
||||
last_writer->cfd, this, file_options_,
|
||||
last_writer->cfd ? last_writer->cfd->GetLatestMutableCFOptions()
|
||||
: MutableCFOptions(*new_cf_options),
|
||||
io_tracer_, current_version_number_++);
|
||||
versions.push_back(version);
|
||||
builder_guards.emplace_back(
|
||||
new BaseReferencedVersionBuilder(last_writer->cfd));
|
||||
builder = builder_guards.back()->version_builder();
|
||||
}
|
||||
assert(last_writer->IsAllWalEdits() || builder);
|
||||
assert(last_writer->IsAllWalEdits() || version);
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"VersionSet::ProcessManifestWrites:SameColumnFamily", &cf_id);
|
||||
break;
|
||||
"VersionSet::ProcessManifestWrites:NewVersion", version);
|
||||
}
|
||||
const Comparator* ucmp = last_writer->cfd->user_comparator();
|
||||
assert(ucmp);
|
||||
std::optional<size_t> edit_ts_sz = ucmp->timestamp_size();
|
||||
for (const auto& e : last_writer->edit_list) {
|
||||
if (e->IsInAtomicGroup()) {
|
||||
if (batch_edits.empty() || !batch_edits.back()->IsInAtomicGroup() ||
|
||||
(batch_edits.back()->IsInAtomicGroup() &&
|
||||
batch_edits.back()->GetRemainingEntries() == 0)) {
|
||||
group_start = batch_edits.size();
|
||||
}
|
||||
} else if (group_start != std::numeric_limits<size_t>::max()) {
|
||||
group_start = std::numeric_limits<size_t>::max();
|
||||
}
|
||||
Status s = LogAndApplyHelper(last_writer->cfd, builder, e,
|
||||
&max_last_sequence, mu);
|
||||
if (!s.ok()) {
|
||||
// free up the allocated memory
|
||||
for (auto v : versions) {
|
||||
delete v;
|
||||
}
|
||||
// FIXME? manifest_writers_ still has requested updates
|
||||
return s;
|
||||
}
|
||||
batch_edits.push_back(e);
|
||||
batch_edits_ts_sz.push_back(edit_ts_sz);
|
||||
}
|
||||
}
|
||||
if (version == nullptr) {
|
||||
// WAL manipulations do not need to be applied to versions.
|
||||
if (!last_writer->IsAllWalEdits()) {
|
||||
version = new Version(
|
||||
last_writer->cfd, this, file_options_,
|
||||
last_writer->cfd ? last_writer->cfd->GetLatestMutableCFOptions()
|
||||
: MutableCFOptions(*new_cf_options),
|
||||
io_tracer_, current_version_number_++);
|
||||
versions.push_back(version);
|
||||
builder_guards.emplace_back(
|
||||
new BaseReferencedVersionBuilder(last_writer->cfd));
|
||||
builder = builder_guards.back()->version_builder();
|
||||
}
|
||||
assert(last_writer->IsAllWalEdits() || builder);
|
||||
assert(last_writer->IsAllWalEdits() || version);
|
||||
TEST_SYNC_POINT_CALLBACK("VersionSet::ProcessManifestWrites:NewVersion",
|
||||
version);
|
||||
// Loop increment/conditions
|
||||
++it;
|
||||
if (it == manifest_writers_.cend()) {
|
||||
break;
|
||||
}
|
||||
const Comparator* ucmp = last_writer->cfd->user_comparator();
|
||||
assert(ucmp);
|
||||
std::optional<size_t> edit_ts_sz = ucmp->timestamp_size();
|
||||
for (const auto& e : last_writer->edit_list) {
|
||||
if (e->IsInAtomicGroup()) {
|
||||
if (batch_edits.empty() || !batch_edits.back()->IsInAtomicGroup() ||
|
||||
(batch_edits.back()->IsInAtomicGroup() &&
|
||||
batch_edits.back()->GetRemainingEntries() == 0)) {
|
||||
group_start = batch_edits.size();
|
||||
}
|
||||
} else if (group_start != std::numeric_limits<size_t>::max()) {
|
||||
group_start = std::numeric_limits<size_t>::max();
|
||||
}
|
||||
Status s = LogAndApplyHelper(last_writer->cfd, builder, e,
|
||||
&max_last_sequence, mu);
|
||||
if (!s.ok()) {
|
||||
// free up the allocated memory
|
||||
for (auto v : versions) {
|
||||
delete v;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
batch_edits.push_back(e);
|
||||
batch_edits_ts_sz.push_back(edit_ts_sz);
|
||||
if (skip_manifest_write) {
|
||||
// no grouping when skipping manifest write
|
||||
break;
|
||||
}
|
||||
const auto* next = (*it)->edit_list.front();
|
||||
if (next->IsColumnFamilyManipulation() ||
|
||||
next->IsNoManifestWriteDummy()) {
|
||||
// no group commits for column family add or drop
|
||||
// nor for dummy skipping manifest write
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < static_cast<int>(versions.size()); ++i) {
|
||||
@@ -5425,6 +5474,7 @@ Status VersionSet::ProcessManifestWrites(
|
||||
for (auto v : versions) {
|
||||
delete v;
|
||||
}
|
||||
// FIXME? manifest_writers_ still has requested updates
|
||||
return s;
|
||||
}
|
||||
}
|
||||
@@ -5464,11 +5514,16 @@ Status VersionSet::ProcessManifestWrites(
|
||||
"VersionSet::ProcessManifestWrites:CheckOneAtomicGroup", &tmp);
|
||||
k = i;
|
||||
}
|
||||
if (skip_manifest_write) {
|
||||
// no grouping when skipping manifest write
|
||||
assert(last_writer == &first_writer);
|
||||
}
|
||||
#endif // NDEBUG
|
||||
|
||||
assert(pending_manifest_file_number_ == 0);
|
||||
if (!descriptor_log_ ||
|
||||
manifest_file_size_ > db_options_->max_manifest_file_size) {
|
||||
if (!skip_manifest_write &&
|
||||
(!descriptor_log_ ||
|
||||
manifest_file_size_ > db_options_->max_manifest_file_size)) {
|
||||
TEST_SYNC_POINT("VersionSet::ProcessManifestWrites:BeforeNewManifest");
|
||||
new_descriptor_log = true;
|
||||
} else {
|
||||
@@ -5506,8 +5561,18 @@ Status VersionSet::ProcessManifestWrites(
|
||||
Status s;
|
||||
IOStatus io_s;
|
||||
IOStatus manifest_io_status;
|
||||
manifest_io_status.PermitUncheckedError();
|
||||
std::unique_ptr<log::Writer> new_desc_log_ptr;
|
||||
{
|
||||
if (skip_manifest_write) {
|
||||
if (s.ok()) {
|
||||
constexpr bool update_stats = true;
|
||||
for (int i = 0; i < static_cast<int>(versions.size()); ++i) {
|
||||
// NOTE: normally called with DB mutex released, but we don't
|
||||
// want to release the DB mutex in this mode of LogAndApply
|
||||
versions[i]->PrepareAppend(read_options, update_stats);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
FileOptions opt_file_opts = fs_->OptimizeForManifestWrite(file_options_);
|
||||
// DB option (in file_options_) takes precedence when not kUnknown
|
||||
if (file_options_.temperature != Temperature::kUnknown) {
|
||||
@@ -5708,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_);
|
||||
@@ -5750,11 +5816,13 @@ Status VersionSet::ProcessManifestWrites(
|
||||
AppendVersion(cfd, versions[i]);
|
||||
}
|
||||
}
|
||||
assert(max_last_sequence >= descriptor_last_sequence_);
|
||||
descriptor_last_sequence_ = max_last_sequence;
|
||||
manifest_file_number_ = pending_manifest_file_number_;
|
||||
manifest_file_size_ = new_manifest_file_size;
|
||||
prev_log_number_ = first_writer.edit_list.front()->GetPrevLogNumber();
|
||||
if (!skip_manifest_write) {
|
||||
assert(max_last_sequence >= descriptor_last_sequence_);
|
||||
descriptor_last_sequence_ = max_last_sequence;
|
||||
manifest_file_number_ = pending_manifest_file_number_;
|
||||
manifest_file_size_ = new_manifest_file_size;
|
||||
prev_log_number_ = first_writer.edit_list.front()->GetPrevLogNumber();
|
||||
}
|
||||
} else {
|
||||
std::string version_edits;
|
||||
for (auto& e : batch_edits) {
|
||||
@@ -5877,7 +5945,8 @@ Status VersionSet::LogAndApply(
|
||||
const autovector<autovector<VersionEdit*>>& edit_lists,
|
||||
InstrumentedMutex* mu, FSDirectory* dir_contains_current_file,
|
||||
bool new_descriptor_log, const ColumnFamilyOptions* new_cf_options,
|
||||
const std::vector<std::function<void(const Status&)>>& manifest_wcbs) {
|
||||
const std::vector<std::function<void(const Status&)>>& manifest_wcbs,
|
||||
const std::function<Status()>& pre_cb) {
|
||||
mu->AssertHeld();
|
||||
int num_edits = 0;
|
||||
for (const auto& elist : edit_lists) {
|
||||
@@ -5890,6 +5959,7 @@ Status VersionSet::LogAndApply(
|
||||
for (const auto& edit_list : edit_lists) {
|
||||
for (const auto& edit : edit_list) {
|
||||
assert(!edit->IsColumnFamilyManipulation());
|
||||
assert(!edit->IsNoManifestWriteDummy());
|
||||
}
|
||||
}
|
||||
#endif /* ! NDEBUG */
|
||||
@@ -5932,6 +6002,7 @@ Status VersionSet::LogAndApply(
|
||||
// should only SetBGError once.
|
||||
return first_writer.status;
|
||||
}
|
||||
TEST_SYNC_POINT_CALLBACK("VersionSet::LogAndApply:WakeUpAndNotDone", mu);
|
||||
|
||||
int num_undropped_cfds = 0;
|
||||
for (auto cfd : column_family_datas) {
|
||||
@@ -5940,7 +6011,17 @@ Status VersionSet::LogAndApply(
|
||||
++num_undropped_cfds;
|
||||
}
|
||||
}
|
||||
Status s;
|
||||
if (0 == num_undropped_cfds) {
|
||||
s = Status::ColumnFamilyDropped();
|
||||
}
|
||||
// Call pre_cb once we know we have work to do and are scheduled as the
|
||||
// exclusive manifest writer (and new Version appender)
|
||||
if (s.ok() && pre_cb) {
|
||||
s = pre_cb();
|
||||
}
|
||||
if (!s.ok()) {
|
||||
// Revert manifest_writers_
|
||||
for (int i = 0; i != num_cfds; ++i) {
|
||||
manifest_writers_.pop_front();
|
||||
}
|
||||
@@ -5948,11 +6029,12 @@ Status VersionSet::LogAndApply(
|
||||
if (!manifest_writers_.empty()) {
|
||||
manifest_writers_.front()->cv.Signal();
|
||||
}
|
||||
return Status::ColumnFamilyDropped();
|
||||
return s;
|
||||
} else {
|
||||
return ProcessManifestWrites(writers, mu, dir_contains_current_file,
|
||||
new_descriptor_log, new_cf_options,
|
||||
read_options, write_options);
|
||||
}
|
||||
return ProcessManifestWrites(writers, mu, dir_contains_current_file,
|
||||
new_descriptor_log, new_cf_options, read_options,
|
||||
write_options);
|
||||
}
|
||||
|
||||
void VersionSet::LogAndApplyCFHelper(VersionEdit* edit,
|
||||
@@ -7110,7 +7192,8 @@ InternalIterator* VersionSet::MakeInputIterator(
|
||||
assert(num <= space);
|
||||
InternalIterator* result = NewCompactionMergingIterator(
|
||||
&c->column_family_data()->internal_comparator(), list,
|
||||
static_cast<int>(num), range_tombstones);
|
||||
static_cast<int>(num), range_tombstones, /*arena=*/nullptr,
|
||||
c->column_family_data()->internal_stats());
|
||||
delete[] list;
|
||||
return result;
|
||||
}
|
||||
@@ -7244,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 =
|
||||
@@ -7255,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_,
|
||||
@@ -7379,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;
|
||||
|
||||
|
||||
+36
-18
@@ -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;
|
||||
@@ -1216,7 +1224,8 @@ class VersionSet {
|
||||
InstrumentedMutex* mu, FSDirectory* dir_contains_current_file,
|
||||
bool new_descriptor_log = false,
|
||||
const ColumnFamilyOptions* column_family_options = nullptr,
|
||||
const std::function<void(const Status&)>& manifest_wcb = {}) {
|
||||
const std::function<void(const Status&)>& manifest_wcb = {},
|
||||
const std::function<Status()>& pre_cb = {}) {
|
||||
autovector<ColumnFamilyData*> cfds;
|
||||
cfds.emplace_back(column_family_data);
|
||||
autovector<autovector<VersionEdit*>> edit_lists;
|
||||
@@ -1225,7 +1234,7 @@ class VersionSet {
|
||||
edit_lists.emplace_back(edit_list);
|
||||
return LogAndApply(cfds, read_options, write_options, edit_lists, mu,
|
||||
dir_contains_current_file, new_descriptor_log,
|
||||
column_family_options, {manifest_wcb});
|
||||
column_family_options, {manifest_wcb}, pre_cb);
|
||||
}
|
||||
// The batch version. If edit_list.size() > 1, caller must ensure that
|
||||
// no edit in the list column family add or drop
|
||||
@@ -1235,14 +1244,15 @@ class VersionSet {
|
||||
const autovector<VersionEdit*>& edit_list, InstrumentedMutex* mu,
|
||||
FSDirectory* dir_contains_current_file, bool new_descriptor_log = false,
|
||||
const ColumnFamilyOptions* column_family_options = nullptr,
|
||||
const std::function<void(const Status&)>& manifest_wcb = {}) {
|
||||
const std::function<void(const Status&)>& manifest_wcb = {},
|
||||
const std::function<Status()>& pre_cb = {}) {
|
||||
autovector<ColumnFamilyData*> cfds;
|
||||
cfds.emplace_back(column_family_data);
|
||||
autovector<autovector<VersionEdit*>> edit_lists;
|
||||
edit_lists.emplace_back(edit_list);
|
||||
return LogAndApply(cfds, read_options, write_options, edit_lists, mu,
|
||||
dir_contains_current_file, new_descriptor_log,
|
||||
column_family_options, {manifest_wcb});
|
||||
column_family_options, {manifest_wcb}, pre_cb);
|
||||
}
|
||||
|
||||
// The across-multi-cf batch version. If edit_lists contain more than
|
||||
@@ -1255,14 +1265,17 @@ class VersionSet {
|
||||
InstrumentedMutex* mu, FSDirectory* dir_contains_current_file,
|
||||
bool new_descriptor_log = false,
|
||||
const ColumnFamilyOptions* new_cf_options = nullptr,
|
||||
const std::vector<std::function<void(const Status&)>>& manifest_wcbs =
|
||||
{});
|
||||
const std::vector<std::function<void(const Status&)>>& manifest_wcbs = {},
|
||||
const std::function<Status()>& pre_cb = {});
|
||||
|
||||
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,
|
||||
@@ -1340,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); }
|
||||
|
||||
@@ -1571,6 +1586,8 @@ class VersionSet {
|
||||
AppendVersion(cfd, version);
|
||||
}
|
||||
|
||||
bool& TEST_unchanging() { return const_cast<bool&>(unchanging_); }
|
||||
|
||||
protected:
|
||||
struct ManifestWriter;
|
||||
|
||||
@@ -1583,7 +1600,8 @@ class VersionSet {
|
||||
|
||||
struct LogReporter : public log::Reader::Reporter {
|
||||
Status* status;
|
||||
void Corruption(size_t /*bytes*/, const Status& s) override {
|
||||
void Corruption(size_t /*bytes*/, const Status& s,
|
||||
uint64_t /*log_number*/ = kMaxSequenceNumber) override {
|
||||
if (status->ok()) {
|
||||
*status = s;
|
||||
}
|
||||
@@ -1622,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,
|
||||
@@ -1719,7 +1737,7 @@ class VersionSet {
|
||||
VersionEdit* edit, SequenceNumber* max_last_sequence,
|
||||
InstrumentedMutex* mu);
|
||||
|
||||
const bool read_only_;
|
||||
const bool unchanging_;
|
||||
bool closed_;
|
||||
};
|
||||
|
||||
@@ -1783,8 +1801,8 @@ class ReactiveVersionSet : public VersionSet {
|
||||
const autovector<autovector<VersionEdit*>>& /*edit_lists*/,
|
||||
InstrumentedMutex* /*mu*/, FSDirectory* /*dir_contains_current_file*/,
|
||||
bool /*new_descriptor_log*/, const ColumnFamilyOptions* /*new_cf_option*/,
|
||||
const std::vector<std::function<void(const Status&)>>& /*manifest_wcbs*/)
|
||||
override {
|
||||
const std::vector<std::function<void(const Status&)>>& /*manifest_wcbs*/,
|
||||
const std::function<Status()>& /*pre_cb*/) override {
|
||||
return Status::NotSupported("not supported in reactive mode");
|
||||
}
|
||||
|
||||
|
||||
+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);
|
||||
|
||||
+3
-1
@@ -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
|
||||
@@ -468,7 +469,8 @@ Status WalManager::ReadFirstLine(const std::string& fname,
|
||||
|
||||
Status* status;
|
||||
bool ignore_error; // true if db_options_.paranoid_checks==false
|
||||
void Corruption(size_t bytes, const Status& s) override {
|
||||
void Corruption(size_t bytes, const Status& s,
|
||||
uint64_t /*log_number*/ = kMaxSequenceNumber) override {
|
||||
ROCKS_LOG_WARN(info_log, "[WalManager] %s%s: dropping %d bytes; %s",
|
||||
(this->ignore_error ? "(ignoring error) " : ""), fname,
|
||||
static_cast<int>(bytes), s.ToString().c_str());
|
||||
|
||||
@@ -815,6 +815,12 @@ WriteBatchInternal::GetColumnFamilyIdAndTimestampSize(
|
||||
s = Status::InvalidArgument("Default cf timestamp size mismatch");
|
||||
}
|
||||
}
|
||||
auto* cfd =
|
||||
static_cast_with_check<ColumnFamilyHandleImpl>(column_family)->cfd();
|
||||
if (cfd && cfd->ioptions().disallow_memtable_writes) {
|
||||
s = Status::InvalidArgument(
|
||||
"This column family has disallow_memtable_writes=true");
|
||||
}
|
||||
} else if (b->default_cf_ts_sz_ > 0) {
|
||||
ts_sz = b->default_cf_ts_sz_;
|
||||
}
|
||||
@@ -836,6 +842,12 @@ Status CheckColumnFamilyTimestampSize(ColumnFamilyHandle* column_family,
|
||||
if (cf_ts_sz != ts.size()) {
|
||||
return Status::InvalidArgument("timestamp size mismatch");
|
||||
}
|
||||
auto* cfd =
|
||||
static_cast_with_check<ColumnFamilyHandleImpl>(column_family)->cfd();
|
||||
if (cfd && cfd->ioptions().disallow_memtable_writes) {
|
||||
return Status::InvalidArgument(
|
||||
"This column family has disallow_memtable_writes=true");
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
} // anonymous namespace
|
||||
@@ -2185,6 +2197,13 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
auto* current = cf_mems_->current();
|
||||
if (current && current->ioptions().disallow_memtable_writes) {
|
||||
*s = Status::InvalidArgument(
|
||||
"This column family has disallow_memtable_writes=true");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (recovering_log_number_ != 0 &&
|
||||
recovering_log_number_ < cf_mems_->GetLogNumber()) {
|
||||
// This is true only in recovery environment (recovering_log_number_ is
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user