mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 11:02:36 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ffcfbaa584 | ||
|
|
f2cbed0cc4 | ||
|
|
e283b751a6 | ||
|
|
c15ee5a758 | ||
|
|
5f981e1145 | ||
|
|
732ab9a131 | ||
|
|
4bef82ad85 | ||
|
|
935ddf6469 | ||
|
|
2aed669e36 | ||
|
|
30a98ee159 | ||
|
|
4db32f3df9 | ||
|
|
a5f08293d8 |
No files matched your search
@@ -1592,6 +1592,3 @@ option(WITH_BENCHMARK "build benchmark tests" OFF)
|
||||
if(WITH_BENCHMARK)
|
||||
add_subdirectory(${PROJECT_SOURCE_DIR}/microbench/)
|
||||
endif()
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
|
||||
+4
-71
@@ -1,82 +1,15 @@
|
||||
# Rocksdb Change Log
|
||||
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
|
||||
|
||||
## 8.6.7 (09/26/2023)
|
||||
### Bug Fixes
|
||||
* Fixed a bug where compaction read under non direct IO still falls back to RocksDB internal prefetching after file system's prefetching returns non-OK status other than `Status::NotSupported()`
|
||||
|
||||
### Behavior Changes
|
||||
* For non direct IO, eliminate the file system prefetching attempt for compaction read when `Options::compaction_readahead_size` is 0
|
||||
|
||||
## 8.6.6 (09/25/2023)
|
||||
### Bug Fixes
|
||||
* Fix a bug with atomic_flush=true that can cause DB to stuck after a flush fails (#11872).
|
||||
* Fix a bug where RocksDB (with atomic_flush=false) can delete output SST files of pending flushes when a previous concurrent flush fails (#11865). This can result in DB entering read-only state with error message like `IO error: No such file or directory: While open a file for random read: /tmp/rocksdbtest-501/db_flush_test_87732_4230653031040984171/000013.sst`.
|
||||
* When the compressed secondary cache capacity is reduced to 0, it should be completely disabled. Before this fix, inserts and lookups would still go to the backing `LRUCache` before returning, thus incurring locking overhead. With this fix, inserts and lookups are no-ops and do not add any overhead.
|
||||
|
||||
## 8.6.5 (09/15/2023)
|
||||
### Bug Fixes
|
||||
* Fixed a bug where `rocksdb.file.read.verify.file.checksums.micros` is not populated.
|
||||
|
||||
## 8.6.4 (09/13/2023)
|
||||
### Public API changes
|
||||
* Add a column family option `default_temperature` that is used for file reading accounting purpose, such as io statistics, for files that don't have an explicitly set temperature.
|
||||
|
||||
## 8.6.3 (09/12/2023)
|
||||
## 8.4.4 (09/01/2023)
|
||||
### Bug Fixes
|
||||
* Fix a bug where if there is an error reading from offset 0 of a file from L1+ and that the file is not the first file in the sorted run, data can be lost in compaction and read/scan can return incorrect results.
|
||||
* Fix a bug where iterator may return incorrect result for DeleteRange() users if there was an error reading from a file.
|
||||
* Fixed a race condition in `GenericRateLimiter` that could cause it to stop granting requests
|
||||
|
||||
## 8.6.2 (09/11/2023)
|
||||
## 8.4.3 (07/27/2023)
|
||||
### Bug Fixes
|
||||
* Add a fix for async_io where during seek, when reading a block for seeking a target key in a file without any readahead, the iterator aligned the read on a page boundary and reading more than necessary. This increased the storage read bandwidth usage.
|
||||
|
||||
## 8.6.1 (08/30/2023)
|
||||
### Public API Changes
|
||||
* `Options::compaction_readahead_size` 's default value is changed from 0 to 2MB.
|
||||
|
||||
### Behavior Changes
|
||||
* Compaction read performance will regress when `Options::compaction_readahead_size` is explicitly set to 0
|
||||
|
||||
## 8.6.0 (08/18/2023)
|
||||
### New Features
|
||||
* Added enhanced data integrity checking on SST files with new format_version=6. Performance impact is very small or negligible. Previously if SST data was misplaced or re-arranged by the storage layer, it could pass block checksum with higher than 1 in 4 billion probability. With format_version=6, block checksums depend on what file they are in and location within the file. This way, misplaced SST data is no more likely to pass checksum verification than randomly corrupted data. Also in format_version=6, SST footers are checksum-protected.
|
||||
* Add a new feature to trim readahead_size during scans upto upper_bound when iterate_upper_bound is specified. It's enabled through ReadOptions.auto_readahead_size. Users must also specify ReadOptions.iterate_upper_bound.
|
||||
* RocksDB will compare the number of input keys to the number of keys processed after each compaction. Compaction will fail and report Corruption status if the verification fails. Option `compaction_verify_record_count` is introduced for this purpose and is enabled by default.
|
||||
* Add a CF option `bottommost_file_compaction_delay` to allow specifying the delay of bottommost level single-file compactions.
|
||||
* Add support to allow enabling / disabling user-defined timestamps feature for an existing column family in combination with the in-Memtable only feature.
|
||||
* Implement a new admission policy for the compressed secondary cache that admits blocks evicted from the primary cache with the hit bit set. This policy can be specified in TieredVolatileCacheOptions by setting the newly added adm_policy option.
|
||||
* Add a column family option `memtable_max_range_deletions` that limits the number of range deletions in a memtable. RocksDB will try to do an automatic flush after the limit is reached. (#11358)
|
||||
* Add PutEntity API in sst_file_writer
|
||||
* Add `timeout` in microsecond option to `WaitForCompactOptions` to allow timely termination of prolonged waiting in scenarios like recurring recoverable errors, such as out-of-space situations and continuous write streams that sustain ongoing flush and compactions
|
||||
* New statistics `rocksdb.file.read.{get|multiget|db.iterator|verify.checksum|verify.file.checksums}.micros` measure read time of block-based SST tables or blob files during db open, `Get()`, `MultiGet()`, using db iterator, `VerifyFileChecksums()` and `VerifyChecksum()`. They require stats level greater than `StatsLevel::kExceptDetailedTimers`.
|
||||
* Add close_db option to `WaitForCompactOptions` to call Close() after waiting is done.
|
||||
* Add a new compression option `CompressionOptions::checksum` for enabling ZSTD's checksum feature to detect corruption during decompression.
|
||||
|
||||
### Public API Changes
|
||||
* Mark `Options::access_hint_on_compaction_start` related APIs as deprecated. See #11631 for alternative behavior.
|
||||
|
||||
### Behavior Changes
|
||||
* Statistics `rocksdb.sst.read.micros` now includes time spent on multi read and async read into the file
|
||||
* For Universal Compaction users, periodic compaction (option `periodic_compaction_seconds`) will be set to 30 days by default if block based table is used.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a bug in FileTTLBooster that can cause users with a large number of levels (more than 65) to see errors like "runtime error: shift exponent .. is too large.." (#11673).
|
||||
|
||||
## 8.5.0 (07/21/2023)
|
||||
### Public API Changes
|
||||
* Removed recently added APIs `GeneralCache` and `MakeSharedGeneralCache()` as our plan changed to stop exposing a general-purpose cache interface. The old forms of these APIs, `Cache` and `NewLRUCache()`, are still available, although general-purpose caching support will be dropped eventually.
|
||||
|
||||
### Behavior Changes
|
||||
* Option `periodic_compaction_seconds` no longer supports FIFO compaction: setting it has no effect on FIFO compactions. FIFO compaction users should only set option `ttl` instead.
|
||||
* Move prefetching responsibility to page cache for compaction read for non directIO use case
|
||||
|
||||
### Performance Improvements
|
||||
* In case of direct_io, if buffer passed by callee is already aligned, RandomAccessFileRead::Read will avoid realloacting a new buffer, reducing memcpy and use already passed aligned buffer.
|
||||
* Small efficiency improvement to HyperClockCache by reducing chance of compiler-generated heap allocations
|
||||
|
||||
### Bug Fixes
|
||||
* Fix use_after_free bug in async_io MultiReads when underlying FS enabled kFSBuffer. kFSBuffer is when underlying FS pass their own buffer instead of using RocksDB scratch in FSReadRequest. Right now it's an experimental feature.
|
||||
* Fix use_after_free bug in async_io MultiReads when underlying FS enabled kFSBuffer. kFSBuffer is when underlying FS pass their own buffer instead of using RocksDB scratch in FSReadRequest.
|
||||
|
||||
## 8.4.0 (06/26/2023)
|
||||
### New Features
|
||||
|
||||
@@ -88,25 +88,6 @@ endif
|
||||
|
||||
$(info $$DEBUG_LEVEL is $(DEBUG_LEVEL), $$LIB_MODE is $(LIB_MODE))
|
||||
|
||||
# Detect what platform we're building on.
|
||||
# Export some common variables that might have been passed as Make variables
|
||||
# instead of environment variables.
|
||||
dummy := $(shell (export ROCKSDB_ROOT="$(CURDIR)"; \
|
||||
export CXXFLAGS="$(EXTRA_CXXFLAGS)"; \
|
||||
export LDFLAGS="$(EXTRA_LDFLAGS)"; \
|
||||
export COMPILE_WITH_ASAN="$(COMPILE_WITH_ASAN)"; \
|
||||
export COMPILE_WITH_TSAN="$(COMPILE_WITH_TSAN)"; \
|
||||
export COMPILE_WITH_UBSAN="$(COMPILE_WITH_UBSAN)"; \
|
||||
export PORTABLE="$(PORTABLE)"; \
|
||||
export ROCKSDB_NO_FBCODE="$(ROCKSDB_NO_FBCODE)"; \
|
||||
export USE_CLANG="$(USE_CLANG)"; \
|
||||
export LIB_MODE="$(LIB_MODE)"; \
|
||||
export ROCKSDB_CXX_STANDARD="$(ROCKSDB_CXX_STANDARD)"; \
|
||||
export USE_FOLLY="$(USE_FOLLY)"; \
|
||||
"$(CURDIR)/build_tools/build_detect_platform" "$(CURDIR)/make_config.mk"))
|
||||
# this file is generated by the previous line to set build flags and sources
|
||||
include make_config.mk
|
||||
|
||||
# Figure out optimize level.
|
||||
ifneq ($(DEBUG_LEVEL), 2)
|
||||
OPTIMIZE_LEVEL ?= -O2
|
||||
@@ -242,6 +223,25 @@ am__v_AR_1 =
|
||||
AM_LINK = $(AM_V_CCLD)$(CXX) -L. $(patsubst lib%.a, -l%, $(patsubst lib%.$(PLATFORM_SHARED_EXT), -l%, $^)) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
|
||||
AM_SHARE = $(AM_V_CCLD) $(CXX) $(PLATFORM_SHARED_LDFLAGS)$@ -L. $(patsubst lib%.$(PLATFORM_SHARED_EXT), -l%, $^) $(EXEC_LDFLAGS) $(LDFLAGS) -o $@
|
||||
|
||||
# Detect what platform we're building on.
|
||||
# Export some common variables that might have been passed as Make variables
|
||||
# instead of environment variables.
|
||||
dummy := $(shell (export ROCKSDB_ROOT="$(CURDIR)"; \
|
||||
export CXXFLAGS="$(EXTRA_CXXFLAGS)"; \
|
||||
export LDFLAGS="$(EXTRA_LDFLAGS)"; \
|
||||
export COMPILE_WITH_ASAN="$(COMPILE_WITH_ASAN)"; \
|
||||
export COMPILE_WITH_TSAN="$(COMPILE_WITH_TSAN)"; \
|
||||
export COMPILE_WITH_UBSAN="$(COMPILE_WITH_UBSAN)"; \
|
||||
export PORTABLE="$(PORTABLE)"; \
|
||||
export ROCKSDB_NO_FBCODE="$(ROCKSDB_NO_FBCODE)"; \
|
||||
export USE_CLANG="$(USE_CLANG)"; \
|
||||
export LIB_MODE="$(LIB_MODE)"; \
|
||||
export ROCKSDB_CXX_STANDARD="$(ROCKSDB_CXX_STANDARD)"; \
|
||||
export USE_FOLLY="$(USE_FOLLY)"; \
|
||||
"$(CURDIR)/build_tools/build_detect_platform" "$(CURDIR)/make_config.mk"))
|
||||
# this file is generated by the previous line to set build flags and sources
|
||||
include make_config.mk
|
||||
|
||||
ROCKSDB_PLUGIN_MKS = $(foreach plugin, $(ROCKSDB_PLUGINS), plugin/$(plugin)/*.mk)
|
||||
include $(ROCKSDB_PLUGIN_MKS)
|
||||
ROCKSDB_PLUGIN_PROTO =ROCKSDB_NAMESPACE::ObjectLibrary\&, const std::string\&
|
||||
|
||||
@@ -674,13 +674,13 @@ else
|
||||
fi
|
||||
|
||||
if [[ "${PLATFORM}" == "OS_MACOSX" ]]; then
|
||||
# For portability compile for macOS 10.14 or newer
|
||||
COMMON_FLAGS="$COMMON_FLAGS -mmacosx-version-min=10.14"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -mmacosx-version-min=10.14"
|
||||
# For portability compile for macOS 10.13 (2017) or newer
|
||||
COMMON_FLAGS="$COMMON_FLAGS -mmacosx-version-min=10.13"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -mmacosx-version-min=10.13"
|
||||
# -mmacosx-version-min must come first here.
|
||||
PLATFORM_SHARED_LDFLAGS="-mmacosx-version-min=10.14 $PLATFORM_SHARED_LDFLAGS"
|
||||
PLATFORM_CMAKE_FLAGS="-DCMAKE_OSX_DEPLOYMENT_TARGET=10.14"
|
||||
JAVA_STATIC_DEPS_COMMON_FLAGS="-mmacosx-version-min=10.14"
|
||||
PLATFORM_SHARED_LDFLAGS="-mmacosx-version-min=10.13 $PLATFORM_SHARED_LDFLAGS"
|
||||
PLATFORM_CMAKE_FLAGS="-DCMAKE_OSX_DEPLOYMENT_TARGET=10.13"
|
||||
JAVA_STATIC_DEPS_COMMON_FLAGS="-mmacosx-version-min=10.13"
|
||||
JAVA_STATIC_DEPS_LDFLAGS="$JAVA_STATIC_DEPS_COMMON_FLAGS"
|
||||
JAVA_STATIC_DEPS_CCFLAGS="$JAVA_STATIC_DEPS_COMMON_FLAGS"
|
||||
JAVA_STATIC_DEPS_CXXFLAGS="$JAVA_STATIC_DEPS_COMMON_FLAGS"
|
||||
|
||||
Vendored
+61
-157
@@ -31,7 +31,6 @@
|
||||
#include "util/hash.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/random.h"
|
||||
#include "util/stderr_logger.h"
|
||||
#include "util/stop_watch.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
@@ -50,11 +49,8 @@ DEFINE_double(resident_ratio, 0.25,
|
||||
"Ratio of keys fitting in cache to keyspace.");
|
||||
DEFINE_uint64(ops_per_thread, 2000000U, "Number of operations per thread.");
|
||||
DEFINE_uint32(value_bytes, 8 * KiB, "Size of each value added.");
|
||||
DEFINE_uint32(value_bytes_estimate, 0,
|
||||
"If > 0, overrides estimated_entry_charge or "
|
||||
"min_avg_entry_charge depending on cache_type.");
|
||||
|
||||
DEFINE_uint32(skew, 5, "Degree of skew in key selection. 0 = no skew");
|
||||
DEFINE_uint32(skew, 5, "Degree of skew in key selection");
|
||||
DEFINE_bool(populate_cache, true, "Populate cache before operations");
|
||||
|
||||
DEFINE_uint32(lookup_insert_percent, 87,
|
||||
@@ -75,22 +71,12 @@ DEFINE_uint32(
|
||||
|
||||
DEFINE_uint32(gather_stats_entries_per_lock, 256,
|
||||
"For Cache::ApplyToAllEntries");
|
||||
DEFINE_bool(skewed, false, "If true, skew the key access distribution");
|
||||
|
||||
DEFINE_bool(lean, false,
|
||||
"If true, no additional computation is performed besides cache "
|
||||
"operations.");
|
||||
|
||||
DEFINE_bool(early_exit, false,
|
||||
"Exit before deallocating most memory. Good for malloc stats, e.g."
|
||||
"MALLOC_CONF=\"stats_print:true\"");
|
||||
|
||||
DEFINE_bool(histograms, true,
|
||||
"Whether to track and print histogram statistics.");
|
||||
|
||||
DEFINE_bool(report_problems, true, "Whether to ReportProblems() at the end.");
|
||||
|
||||
DEFINE_uint32(seed, 0, "Hashing/random seed to use. 0 = choose at random");
|
||||
|
||||
DEFINE_string(secondary_cache_uri, "",
|
||||
"Full URI for creating a custom secondary cache object");
|
||||
static class std::shared_ptr<ROCKSDB_NAMESPACE::SecondaryCache> secondary_cache;
|
||||
@@ -159,6 +145,9 @@ class SharedState {
|
||||
public:
|
||||
explicit SharedState(CacheBench* cache_bench)
|
||||
: cv_(&mu_),
|
||||
num_initialized_(0),
|
||||
start_(false),
|
||||
num_done_(0),
|
||||
cache_bench_(cache_bench) {}
|
||||
|
||||
~SharedState() {}
|
||||
@@ -181,27 +170,15 @@ class SharedState {
|
||||
|
||||
bool Started() const { return start_; }
|
||||
|
||||
void AddLookupStats(uint64_t hits, uint64_t misses) {
|
||||
MutexLock l(&mu_);
|
||||
lookup_count_ += hits + misses;
|
||||
lookup_hits_ += hits;
|
||||
}
|
||||
|
||||
double GetLookupHitRatio() const {
|
||||
return 1.0 * lookup_hits_ / lookup_count_;
|
||||
}
|
||||
|
||||
private:
|
||||
port::Mutex mu_;
|
||||
port::CondVar cv_;
|
||||
|
||||
CacheBench* cache_bench_;
|
||||
uint64_t num_initialized_;
|
||||
bool start_;
|
||||
uint64_t num_done_;
|
||||
|
||||
uint64_t num_initialized_ = 0;
|
||||
bool start_ = false;
|
||||
uint64_t num_done_ = 0;
|
||||
uint64_t lookup_count_ = 0;
|
||||
uint64_t lookup_hits_ = 0;
|
||||
CacheBench* cache_bench_;
|
||||
};
|
||||
|
||||
// Per-thread state for concurrent executions of the same benchmark.
|
||||
@@ -213,19 +190,27 @@ struct ThreadState {
|
||||
uint64_t duration_us = 0;
|
||||
|
||||
ThreadState(uint32_t index, SharedState* _shared)
|
||||
: tid(index), rnd(FLAGS_seed + 1 + index), shared(_shared) {}
|
||||
: tid(index), rnd(1000 + index), shared(_shared) {}
|
||||
};
|
||||
|
||||
struct KeyGen {
|
||||
char key_data[27];
|
||||
|
||||
Slice GetRand(Random64& rnd, uint64_t max_key, uint32_t skew) {
|
||||
uint64_t raw = rnd.Next();
|
||||
// Skew according to setting
|
||||
for (uint32_t i = 0; i < skew; ++i) {
|
||||
raw = std::min(raw, rnd.Next());
|
||||
Slice GetRand(Random64& rnd, uint64_t max_key, int max_log) {
|
||||
uint64_t key = 0;
|
||||
if (!FLAGS_skewed) {
|
||||
uint64_t raw = rnd.Next();
|
||||
// Skew according to setting
|
||||
for (uint32_t i = 0; i < FLAGS_skew; ++i) {
|
||||
raw = std::min(raw, rnd.Next());
|
||||
}
|
||||
key = FastRange64(raw, max_key);
|
||||
} else {
|
||||
key = rnd.Skewed(max_log);
|
||||
if (key > max_key) {
|
||||
key -= max_key;
|
||||
}
|
||||
}
|
||||
uint64_t key = FastRange64(raw, max_key);
|
||||
// Variable size and alignment
|
||||
size_t off = key % 8;
|
||||
key_data[0] = char{42};
|
||||
@@ -296,38 +281,31 @@ class CacheBench {
|
||||
lookup_threshold_(insert_threshold_ +
|
||||
kHundredthUint64 * FLAGS_lookup_percent),
|
||||
erase_threshold_(lookup_threshold_ +
|
||||
kHundredthUint64 * FLAGS_erase_percent) {
|
||||
kHundredthUint64 * FLAGS_erase_percent),
|
||||
skewed_(FLAGS_skewed) {
|
||||
if (erase_threshold_ != 100U * kHundredthUint64) {
|
||||
fprintf(stderr, "Percentages must add to 100.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
max_log_ = 0;
|
||||
if (skewed_) {
|
||||
uint64_t max_key = max_key_;
|
||||
while (max_key >>= 1) max_log_++;
|
||||
if (max_key > (static_cast<uint64_t>(1) << max_log_)) max_log_++;
|
||||
}
|
||||
|
||||
if (FLAGS_cache_type == "clock_cache") {
|
||||
fprintf(stderr, "Old clock cache implementation has been removed.\n");
|
||||
exit(1);
|
||||
} else if (EndsWith(FLAGS_cache_type, "hyper_clock_cache")) {
|
||||
HyperClockCacheOptions opts(
|
||||
FLAGS_cache_size, /*estimated_entry_charge=*/0, FLAGS_num_shard_bits);
|
||||
opts.hash_seed = BitwiseAnd(FLAGS_seed, INT32_MAX);
|
||||
if (FLAGS_cache_type == "fixed_hyper_clock_cache" ||
|
||||
FLAGS_cache_type == "hyper_clock_cache") {
|
||||
opts.estimated_entry_charge = FLAGS_value_bytes_estimate > 0
|
||||
? FLAGS_value_bytes_estimate
|
||||
: FLAGS_value_bytes;
|
||||
} else if (FLAGS_cache_type == "auto_hyper_clock_cache") {
|
||||
if (FLAGS_value_bytes_estimate > 0) {
|
||||
opts.min_avg_entry_charge = FLAGS_value_bytes_estimate;
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "Cache type not supported.");
|
||||
exit(1);
|
||||
}
|
||||
cache_ = opts.MakeSharedCache();
|
||||
} else if (FLAGS_cache_type == "hyper_clock_cache") {
|
||||
cache_ = HyperClockCacheOptions(FLAGS_cache_size, FLAGS_value_bytes,
|
||||
FLAGS_num_shard_bits)
|
||||
.MakeSharedCache();
|
||||
} else if (FLAGS_cache_type == "lru_cache") {
|
||||
LRUCacheOptions opts(FLAGS_cache_size, FLAGS_num_shard_bits,
|
||||
false /* strict_capacity_limit */,
|
||||
0.5 /* high_pri_pool_ratio */);
|
||||
opts.hash_seed = BitwiseAnd(FLAGS_seed, INT32_MAX);
|
||||
if (!FLAGS_secondary_cache_uri.empty()) {
|
||||
Status s = SecondaryCache::CreateFromString(
|
||||
ConfigOptions(), FLAGS_secondary_cache_uri, &secondary_cache);
|
||||
@@ -351,50 +329,13 @@ class CacheBench {
|
||||
~CacheBench() {}
|
||||
|
||||
void PopulateCache() {
|
||||
Random64 rnd(FLAGS_seed);
|
||||
Random64 rnd(1);
|
||||
KeyGen keygen;
|
||||
size_t max_occ = 0;
|
||||
size_t inserts_since_max_occ_increase = 0;
|
||||
size_t keys_since_last_not_found = 0;
|
||||
|
||||
// Avoid redundant insertions by checking Lookup before Insert.
|
||||
// Loop until insertions consistently fail to increase max occupancy or
|
||||
// it becomes difficult to find keys not already inserted.
|
||||
while (inserts_since_max_occ_increase < 100 &&
|
||||
keys_since_last_not_found < 100) {
|
||||
Slice key = keygen.GetRand(rnd, max_key_, FLAGS_skew);
|
||||
|
||||
Cache::Handle* handle = cache_->Lookup(key);
|
||||
if (handle != nullptr) {
|
||||
cache_->Release(handle);
|
||||
++keys_since_last_not_found;
|
||||
continue;
|
||||
}
|
||||
keys_since_last_not_found = 0;
|
||||
|
||||
Status s =
|
||||
cache_->Insert(key, createValue(rnd), &helper1, FLAGS_value_bytes);
|
||||
for (uint64_t i = 0; i < 2 * FLAGS_cache_size; i += FLAGS_value_bytes) {
|
||||
Status s = cache_->Insert(keygen.GetRand(rnd, max_key_, max_log_),
|
||||
createValue(rnd), &helper1, FLAGS_value_bytes);
|
||||
assert(s.ok());
|
||||
|
||||
handle = cache_->Lookup(key);
|
||||
if (!handle) {
|
||||
fprintf(stderr, "Failed to lookup key just inserted.\n");
|
||||
assert(false);
|
||||
exit(42);
|
||||
} else {
|
||||
cache_->Release(handle);
|
||||
}
|
||||
|
||||
size_t occ = cache_->GetOccupancyCount();
|
||||
if (occ > max_occ) {
|
||||
max_occ = occ;
|
||||
inserts_since_max_occ_increase = 0;
|
||||
} else {
|
||||
++inserts_since_max_occ_increase;
|
||||
}
|
||||
}
|
||||
printf("Population complete (%zu entries, %g average charge)\n", max_occ,
|
||||
1.0 * FLAGS_cache_size / max_occ);
|
||||
}
|
||||
|
||||
bool Run() {
|
||||
@@ -453,33 +394,19 @@ class CacheBench {
|
||||
FLAGS_ops_per_thread / elapsed_secs);
|
||||
printf("Thread ops/sec = %u\n", ops_per_sec);
|
||||
|
||||
printf("Lookup hit ratio: %g\n", shared.GetLookupHitRatio());
|
||||
printf("\nOperation latency (ns):\n");
|
||||
HistogramImpl combined;
|
||||
for (uint32_t i = 0; i < FLAGS_threads; i++) {
|
||||
combined.Merge(threads[i]->latency_ns_hist);
|
||||
}
|
||||
printf("%s", combined.ToString().c_str());
|
||||
|
||||
size_t occ = cache_->GetOccupancyCount();
|
||||
size_t slot = cache_->GetTableAddressCount();
|
||||
printf("Final load factor: %g (%zu / %zu)\n", 1.0 * occ / slot, occ, slot);
|
||||
|
||||
if (FLAGS_histograms) {
|
||||
printf("\nOperation latency (ns):\n");
|
||||
HistogramImpl combined;
|
||||
for (uint32_t i = 0; i < FLAGS_threads; i++) {
|
||||
combined.Merge(threads[i]->latency_ns_hist);
|
||||
}
|
||||
printf("%s", combined.ToString().c_str());
|
||||
|
||||
if (FLAGS_gather_stats) {
|
||||
printf("\nGather stats latency (us):\n");
|
||||
printf("%s", stats_hist.ToString().c_str());
|
||||
}
|
||||
if (FLAGS_gather_stats) {
|
||||
printf("\nGather stats latency (us):\n");
|
||||
printf("%s", stats_hist.ToString().c_str());
|
||||
}
|
||||
|
||||
if (FLAGS_report_problems) {
|
||||
printf("\n");
|
||||
std::shared_ptr<Logger> logger =
|
||||
std::make_shared<StderrLogger>(InfoLogLevel::DEBUG_LEVEL);
|
||||
cache_->ReportProblems(logger);
|
||||
}
|
||||
printf("%s", stats_report.c_str());
|
||||
printf("\n%s", stats_report.c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -492,6 +419,8 @@ class CacheBench {
|
||||
const uint64_t insert_threshold_;
|
||||
const uint64_t lookup_threshold_;
|
||||
const uint64_t erase_threshold_;
|
||||
const bool skewed_;
|
||||
int max_log_;
|
||||
|
||||
// A benchmark version of gathering stats on an active block cache by
|
||||
// iterating over it. The primary purpose is to measure the impact of
|
||||
@@ -524,7 +453,7 @@ class CacheBench {
|
||||
for (;;) {
|
||||
if (shared->AllDone()) {
|
||||
std::ostringstream ostr;
|
||||
ostr << "\nMost recent cache entry stats:\n"
|
||||
ostr << "Most recent cache entry stats:\n"
|
||||
<< "Number of entries: " << total_entry_count << "\n"
|
||||
<< "Table occupancy: " << table_occupancy << " / "
|
||||
<< table_size << " = "
|
||||
@@ -561,17 +490,13 @@ class CacheBench {
|
||||
// Something slightly more expensive as in stats by category
|
||||
helpers.insert(helper);
|
||||
};
|
||||
if (FLAGS_histograms) {
|
||||
timer.Start();
|
||||
}
|
||||
timer.Start();
|
||||
Cache::ApplyToAllEntriesOptions opts;
|
||||
opts.average_entries_per_lock = FLAGS_gather_stats_entries_per_lock;
|
||||
shared->GetCacheBench()->cache_->ApplyToAllEntries(fn, opts);
|
||||
table_occupancy = shared->GetCacheBench()->cache_->GetOccupancyCount();
|
||||
table_size = shared->GetCacheBench()->cache_->GetTableAddressCount();
|
||||
if (FLAGS_histograms) {
|
||||
stats_hist->Add(timer.ElapsedNanos() / 1000);
|
||||
}
|
||||
stats_hist->Add(timer.ElapsedNanos() / 1000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -602,8 +527,6 @@ class CacheBench {
|
||||
void OperateCache(ThreadState* thread) {
|
||||
// To use looked-up values
|
||||
uint64_t result = 0;
|
||||
uint64_t lookup_misses = 0;
|
||||
uint64_t lookup_hits = 0;
|
||||
// To hold handles for a non-trivial amount of time
|
||||
Cache::Handle* handle = nullptr;
|
||||
KeyGen gen;
|
||||
@@ -612,12 +535,10 @@ class CacheBench {
|
||||
StopWatchNano timer(clock);
|
||||
|
||||
for (uint64_t i = 0; i < FLAGS_ops_per_thread; i++) {
|
||||
Slice key = gen.GetRand(thread->rnd, max_key_, FLAGS_skew);
|
||||
Slice key = gen.GetRand(thread->rnd, max_key_, max_log_);
|
||||
uint64_t random_op = thread->rnd.Next();
|
||||
|
||||
if (FLAGS_histograms) {
|
||||
timer.Start();
|
||||
}
|
||||
timer.Start();
|
||||
|
||||
if (random_op < lookup_insert_threshold_) {
|
||||
if (handle) {
|
||||
@@ -628,14 +549,12 @@ class CacheBench {
|
||||
handle = cache_->Lookup(key, &helper2, /*context*/ nullptr,
|
||||
Cache::Priority::LOW);
|
||||
if (handle) {
|
||||
++lookup_hits;
|
||||
if (!FLAGS_lean) {
|
||||
// do something with the data
|
||||
result += NPHash64(static_cast<char*>(cache_->Value(handle)),
|
||||
FLAGS_value_bytes);
|
||||
}
|
||||
} else {
|
||||
++lookup_misses;
|
||||
// do insert
|
||||
Status s = cache_->Insert(key, createValue(thread->rnd), &helper2,
|
||||
FLAGS_value_bytes, &handle);
|
||||
@@ -659,14 +578,11 @@ class CacheBench {
|
||||
handle = cache_->Lookup(key, &helper2, /*context*/ nullptr,
|
||||
Cache::Priority::LOW);
|
||||
if (handle) {
|
||||
++lookup_hits;
|
||||
if (!FLAGS_lean) {
|
||||
// do something with the data
|
||||
result += NPHash64(static_cast<char*>(cache_->Value(handle)),
|
||||
FLAGS_value_bytes);
|
||||
}
|
||||
} else {
|
||||
++lookup_misses;
|
||||
}
|
||||
} else if (random_op < erase_threshold_) {
|
||||
// do erase
|
||||
@@ -675,14 +591,7 @@ class CacheBench {
|
||||
// Should be extremely unlikely (noop)
|
||||
assert(random_op >= kHundredthUint64 * 100U);
|
||||
}
|
||||
if (FLAGS_histograms) {
|
||||
thread->latency_ns_hist.Add(timer.ElapsedNanos());
|
||||
}
|
||||
thread->shared->AddLookupStats(lookup_hits, lookup_misses);
|
||||
}
|
||||
if (FLAGS_early_exit) {
|
||||
MutexLock l(thread->shared->GetMutex());
|
||||
exit(0);
|
||||
thread->latency_ns_hist.Add(timer.ElapsedNanos());
|
||||
}
|
||||
if (handle) {
|
||||
cache_->Release(handle);
|
||||
@@ -704,9 +613,7 @@ class CacheBench {
|
||||
#ifndef NDEBUG
|
||||
printf("WARNING: Assertions are enabled; benchmarks unnecessarily slow\n");
|
||||
#endif
|
||||
printf("----------------------------\n");
|
||||
printf("RocksDB version : %d.%d\n", kMajorVersion, kMinorVersion);
|
||||
printf("Cache impl name : %s\n", cache_->Name());
|
||||
printf("DMutex impl name : %s\n", DMutex::kName());
|
||||
printf("Number of threads : %u\n", FLAGS_threads);
|
||||
printf("Ops per thread : %" PRIu64 "\n", FLAGS_ops_per_thread);
|
||||
@@ -1045,14 +952,11 @@ int cache_bench_tool(int argc, char** argv) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (FLAGS_seed == 0) {
|
||||
FLAGS_seed = static_cast<uint32_t>(port::GetProcessID());
|
||||
printf("Using seed = %" PRIu32 "\n", FLAGS_seed);
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::CacheBench bench;
|
||||
if (FLAGS_populate_cache) {
|
||||
bench.PopulateCache();
|
||||
printf("Population complete\n");
|
||||
printf("----------------------------\n");
|
||||
}
|
||||
if (bench.Run()) {
|
||||
return 0;
|
||||
|
||||
Vendored
+92
-43
@@ -70,11 +70,18 @@ const Cache::CacheItemHelper kDumbHelper{
|
||||
CacheEntryRole::kMisc,
|
||||
[](Cache::ObjectPtr /*value*/, MemoryAllocator* /*alloc*/) {}};
|
||||
|
||||
const Cache::CacheItemHelper kInvokeOnDeleteHelper{
|
||||
const Cache::CacheItemHelper kEraseOnDeleteHelper1{
|
||||
CacheEntryRole::kMisc,
|
||||
[](Cache::ObjectPtr value, MemoryAllocator* /*alloc*/) {
|
||||
auto& fn = *static_cast<std::function<void()>*>(value);
|
||||
fn();
|
||||
Cache* cache = static_cast<Cache*>(value);
|
||||
cache->Erase("foo");
|
||||
}};
|
||||
|
||||
const Cache::CacheItemHelper kEraseOnDeleteHelper2{
|
||||
CacheEntryRole::kMisc,
|
||||
[](Cache::ObjectPtr value, MemoryAllocator* /*alloc*/) {
|
||||
Cache* cache = static_cast<Cache*>(value);
|
||||
cache->Erase(EncodeKey16Bytes(1234));
|
||||
}};
|
||||
} // anonymous namespace
|
||||
|
||||
@@ -113,7 +120,8 @@ class CacheTest : public testing::Test,
|
||||
// Currently, HyperClockCache requires keys to be 16B long, whereas
|
||||
// LRUCache doesn't, so the encoding depends on the cache type.
|
||||
std::string EncodeKey(int k) {
|
||||
if (IsHyperClock()) {
|
||||
auto type = GetParam();
|
||||
if (type == kHyperClock) {
|
||||
return EncodeKey16Bytes(k);
|
||||
} else {
|
||||
return EncodeKey32Bits(k);
|
||||
@@ -121,7 +129,8 @@ class CacheTest : public testing::Test,
|
||||
}
|
||||
|
||||
int DecodeKey(const Slice& k) {
|
||||
if (IsHyperClock()) {
|
||||
auto type = GetParam();
|
||||
if (type == kHyperClock) {
|
||||
return DecodeKey16Bytes(k);
|
||||
} else {
|
||||
return DecodeKey32Bits(k);
|
||||
@@ -173,13 +182,15 @@ std::string CacheTest::type_;
|
||||
class LRUCacheTest : public CacheTest {};
|
||||
|
||||
TEST_P(CacheTest, UsageTest) {
|
||||
auto type = GetParam();
|
||||
|
||||
// cache is std::shared_ptr and will be automatically cleaned up.
|
||||
const size_t kCapacity = 100000;
|
||||
auto cache = NewCache(kCapacity, 8, false, kDontChargeCacheMetadata);
|
||||
auto precise_cache = NewCache(kCapacity, 0, false, kFullChargeCacheMetadata);
|
||||
ASSERT_EQ(0, cache->GetUsage());
|
||||
size_t baseline_meta_usage = precise_cache->GetUsage();
|
||||
if (!IsHyperClock()) {
|
||||
if (type != kHyperClock) {
|
||||
ASSERT_EQ(0, baseline_meta_usage);
|
||||
}
|
||||
|
||||
@@ -187,13 +198,18 @@ TEST_P(CacheTest, UsageTest) {
|
||||
char value[10] = "abcdef";
|
||||
// make sure everything will be cached
|
||||
for (int i = 1; i < 100; ++i) {
|
||||
std::string key = EncodeKey(i);
|
||||
std::string key;
|
||||
if (type == kLRU) {
|
||||
key = std::string(i, 'a');
|
||||
} else {
|
||||
key = EncodeKey(i);
|
||||
}
|
||||
auto kv_size = key.size() + 5;
|
||||
ASSERT_OK(cache->Insert(key, value, &kDumbHelper, kv_size));
|
||||
ASSERT_OK(precise_cache->Insert(key, value, &kDumbHelper, kv_size));
|
||||
usage += kv_size;
|
||||
ASSERT_EQ(usage, cache->GetUsage());
|
||||
if (IsHyperClock()) {
|
||||
if (type == kHyperClock) {
|
||||
ASSERT_EQ(baseline_meta_usage + usage, precise_cache->GetUsage());
|
||||
} else {
|
||||
ASSERT_LT(usage, precise_cache->GetUsage());
|
||||
@@ -207,7 +223,12 @@ TEST_P(CacheTest, UsageTest) {
|
||||
|
||||
// make sure the cache will be overloaded
|
||||
for (size_t i = 1; i < kCapacity; ++i) {
|
||||
std::string key = EncodeKey(static_cast<int>(1000 + i));
|
||||
std::string key;
|
||||
if (type == kLRU) {
|
||||
key = std::to_string(i);
|
||||
} else {
|
||||
key = EncodeKey(static_cast<int>(1000 + i));
|
||||
}
|
||||
ASSERT_OK(cache->Insert(key, value, &kDumbHelper, key.size() + 5));
|
||||
ASSERT_OK(precise_cache->Insert(key, value, &kDumbHelper, key.size() + 5));
|
||||
}
|
||||
@@ -216,7 +237,7 @@ TEST_P(CacheTest, UsageTest) {
|
||||
ASSERT_GT(kCapacity, cache->GetUsage());
|
||||
ASSERT_GT(kCapacity, precise_cache->GetUsage());
|
||||
ASSERT_LT(kCapacity * 0.95, cache->GetUsage());
|
||||
if (!IsHyperClock()) {
|
||||
if (type != kHyperClock) {
|
||||
ASSERT_LT(kCapacity * 0.95, precise_cache->GetUsage());
|
||||
} else {
|
||||
// estimated value size of 1 is weird for clock cache, because
|
||||
@@ -227,20 +248,22 @@ TEST_P(CacheTest, UsageTest) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This test takes longer than expected on FixedHyperClockCache.
|
||||
// This is because the values size estimate at construction is too sloppy.
|
||||
// TODO: This test takes longer than expected on ClockCache. This is
|
||||
// because the values size estimate at construction is too sloppy.
|
||||
// Fix this.
|
||||
// Why is it so slow? The cache is constructed with an estimate of 1, but
|
||||
// then the charge is claimed to be 21. This will cause the hash table
|
||||
// to be extremely sparse, which in turn means clock needs to scan too
|
||||
// many slots to find victims.
|
||||
TEST_P(CacheTest, PinnedUsageTest) {
|
||||
auto type = GetParam();
|
||||
|
||||
// cache is std::shared_ptr and will be automatically cleaned up.
|
||||
const size_t kCapacity = 200000;
|
||||
auto cache = NewCache(kCapacity, 8, false, kDontChargeCacheMetadata);
|
||||
auto precise_cache = NewCache(kCapacity, 8, false, kFullChargeCacheMetadata);
|
||||
size_t baseline_meta_usage = precise_cache->GetUsage();
|
||||
if (!IsHyperClock()) {
|
||||
if (type != kHyperClock) {
|
||||
ASSERT_EQ(0, baseline_meta_usage);
|
||||
}
|
||||
|
||||
@@ -253,7 +276,12 @@ TEST_P(CacheTest, PinnedUsageTest) {
|
||||
// Add entries. Unpin some of them after insertion. Then, pin some of them
|
||||
// again. Check GetPinnedUsage().
|
||||
for (int i = 1; i < 100; ++i) {
|
||||
std::string key = EncodeKey(i);
|
||||
std::string key;
|
||||
if (type == kLRU) {
|
||||
key = std::string(i, 'a');
|
||||
} else {
|
||||
key = EncodeKey(i);
|
||||
}
|
||||
auto kv_size = key.size() + 5;
|
||||
Cache::Handle* handle;
|
||||
Cache::Handle* handle_in_precise_cache;
|
||||
@@ -294,7 +322,12 @@ TEST_P(CacheTest, PinnedUsageTest) {
|
||||
|
||||
// check that overloading the cache does not change the pinned usage
|
||||
for (size_t i = 1; i < 2 * kCapacity; ++i) {
|
||||
std::string key = EncodeKey(static_cast<int>(1000 + i));
|
||||
std::string key;
|
||||
if (type == kLRU) {
|
||||
key = std::to_string(i);
|
||||
} else {
|
||||
key = EncodeKey(static_cast<int>(1000 + i));
|
||||
}
|
||||
ASSERT_OK(cache->Insert(key, value, &kDumbHelper, key.size() + 5));
|
||||
ASSERT_OK(precise_cache->Insert(key, value, &kDumbHelper, key.size() + 5));
|
||||
}
|
||||
@@ -335,7 +368,7 @@ TEST_P(CacheTest, HitAndMiss) {
|
||||
ASSERT_EQ(-1, Lookup(300));
|
||||
|
||||
Insert(100, 102);
|
||||
if (IsHyperClock()) {
|
||||
if (GetParam() == kHyperClock) {
|
||||
// ClockCache usually doesn't overwrite on Insert
|
||||
ASSERT_EQ(101, Lookup(100));
|
||||
} else {
|
||||
@@ -345,7 +378,7 @@ TEST_P(CacheTest, HitAndMiss) {
|
||||
ASSERT_EQ(-1, Lookup(300));
|
||||
|
||||
ASSERT_EQ(1U, deleted_values_.size());
|
||||
if (IsHyperClock()) {
|
||||
if (GetParam() == kHyperClock) {
|
||||
ASSERT_EQ(102, deleted_values_[0]);
|
||||
} else {
|
||||
ASSERT_EQ(101, deleted_values_[0]);
|
||||
@@ -353,7 +386,7 @@ TEST_P(CacheTest, HitAndMiss) {
|
||||
}
|
||||
|
||||
TEST_P(CacheTest, InsertSameKey) {
|
||||
if (IsHyperClock()) {
|
||||
if (GetParam() == kHyperClock) {
|
||||
ROCKSDB_GTEST_BYPASS(
|
||||
"ClockCache doesn't guarantee Insert overwrite same key.");
|
||||
return;
|
||||
@@ -382,7 +415,7 @@ TEST_P(CacheTest, Erase) {
|
||||
}
|
||||
|
||||
TEST_P(CacheTest, EntriesArePinned) {
|
||||
if (IsHyperClock()) {
|
||||
if (GetParam() == kHyperClock) {
|
||||
ROCKSDB_GTEST_BYPASS(
|
||||
"ClockCache doesn't guarantee Insert overwrite same key.");
|
||||
return;
|
||||
@@ -446,7 +479,7 @@ TEST_P(CacheTest, ExternalRefPinsEntries) {
|
||||
Insert(1000 + j, 2000 + j);
|
||||
}
|
||||
// Clock cache is even more stateful and needs more churn to evict
|
||||
if (IsHyperClock()) {
|
||||
if (GetParam() == kHyperClock) {
|
||||
for (int j = 0; j < kCacheSize; j++) {
|
||||
Insert(11000 + j, 11000 + j);
|
||||
}
|
||||
@@ -484,20 +517,20 @@ TEST_P(CacheTest, EvictionPolicyRef) {
|
||||
// Check whether the entries inserted in the beginning
|
||||
// are evicted. Ones without extra ref are evicted and
|
||||
// those with are not.
|
||||
EXPECT_EQ(-1, Lookup(100));
|
||||
EXPECT_EQ(-1, Lookup(101));
|
||||
EXPECT_EQ(-1, Lookup(102));
|
||||
EXPECT_EQ(-1, Lookup(103));
|
||||
ASSERT_EQ(-1, Lookup(100));
|
||||
ASSERT_EQ(-1, Lookup(101));
|
||||
ASSERT_EQ(-1, Lookup(102));
|
||||
ASSERT_EQ(-1, Lookup(103));
|
||||
|
||||
EXPECT_EQ(-1, Lookup(300));
|
||||
EXPECT_EQ(-1, Lookup(301));
|
||||
EXPECT_EQ(-1, Lookup(302));
|
||||
EXPECT_EQ(-1, Lookup(303));
|
||||
ASSERT_EQ(-1, Lookup(300));
|
||||
ASSERT_EQ(-1, Lookup(301));
|
||||
ASSERT_EQ(-1, Lookup(302));
|
||||
ASSERT_EQ(-1, Lookup(303));
|
||||
|
||||
EXPECT_EQ(101, Lookup(200));
|
||||
EXPECT_EQ(102, Lookup(201));
|
||||
EXPECT_EQ(103, Lookup(202));
|
||||
EXPECT_EQ(104, Lookup(203));
|
||||
ASSERT_EQ(101, Lookup(200));
|
||||
ASSERT_EQ(102, Lookup(201));
|
||||
ASSERT_EQ(103, Lookup(202));
|
||||
ASSERT_EQ(104, Lookup(203));
|
||||
|
||||
// Cleaning up all the handles
|
||||
cache_->Release(h201);
|
||||
@@ -507,22 +540,37 @@ TEST_P(CacheTest, EvictionPolicyRef) {
|
||||
}
|
||||
|
||||
TEST_P(CacheTest, EvictEmptyCache) {
|
||||
auto type = GetParam();
|
||||
|
||||
// Insert item large than capacity to trigger eviction on empty cache.
|
||||
auto cache = NewCache(1, 0, false);
|
||||
ASSERT_OK(cache->Insert(EncodeKey(1000), nullptr, &kDumbHelper, 10));
|
||||
if (type == kLRU) {
|
||||
ASSERT_OK(cache->Insert("foo", nullptr, &kDumbHelper, 10));
|
||||
} else {
|
||||
ASSERT_OK(cache->Insert(EncodeKey(1000), nullptr, &kDumbHelper, 10));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(CacheTest, EraseFromDeleter) {
|
||||
auto type = GetParam();
|
||||
|
||||
// Have deleter which will erase item from cache, which will re-enter
|
||||
// the cache at that point.
|
||||
std::shared_ptr<Cache> cache = NewCache(10, 0, false);
|
||||
std::string foo = EncodeKey(1234);
|
||||
std::string bar = EncodeKey(5678);
|
||||
|
||||
std::function<void()> erase_fn = [&]() { cache->Erase(foo); };
|
||||
std::string foo, bar;
|
||||
const Cache::CacheItemHelper* erase_helper;
|
||||
if (type == kLRU) {
|
||||
foo = "foo";
|
||||
bar = "bar";
|
||||
erase_helper = &kEraseOnDeleteHelper1;
|
||||
} else {
|
||||
foo = EncodeKey(1234);
|
||||
bar = EncodeKey(5678);
|
||||
erase_helper = &kEraseOnDeleteHelper2;
|
||||
}
|
||||
|
||||
ASSERT_OK(cache->Insert(foo, nullptr, &kDumbHelper, 1));
|
||||
ASSERT_OK(cache->Insert(bar, &erase_fn, &kInvokeOnDeleteHelper, 1));
|
||||
ASSERT_OK(cache->Insert(bar, cache.get(), erase_helper, 1));
|
||||
|
||||
cache->Erase(bar);
|
||||
ASSERT_EQ(nullptr, cache->Lookup(foo));
|
||||
@@ -630,10 +678,10 @@ using TypedHandle = SharedCache::TypedHandle;
|
||||
} // namespace
|
||||
|
||||
TEST_P(CacheTest, SetCapacity) {
|
||||
if (IsHyperClock()) {
|
||||
// TODO: update test & code for limited supoort
|
||||
auto type = GetParam();
|
||||
if (type == kHyperClock) {
|
||||
ROCKSDB_GTEST_BYPASS(
|
||||
"HyperClockCache doesn't support arbitrary capacity "
|
||||
"FastLRUCache and HyperClockCache don't support arbitrary capacity "
|
||||
"adjustments.");
|
||||
return;
|
||||
}
|
||||
@@ -763,7 +811,7 @@ TEST_P(CacheTest, OverCapacity) {
|
||||
cache.Release(handles[i]);
|
||||
}
|
||||
|
||||
if (IsHyperClock()) {
|
||||
if (GetParam() == kHyperClock) {
|
||||
// Make sure eviction is triggered.
|
||||
ASSERT_OK(cache.Insert(EncodeKey(-1), nullptr, 1, &handles[0]));
|
||||
|
||||
@@ -875,7 +923,8 @@ TEST_P(CacheTest, DefaultShardBits) {
|
||||
// Prevent excessive allocation (to save time & space)
|
||||
estimated_value_size_ = 100000;
|
||||
// Implementations use different minimum shard sizes
|
||||
size_t min_shard_size = (IsHyperClock() ? 32U * 1024U : 512U) * 1024U;
|
||||
size_t min_shard_size =
|
||||
(GetParam() == kHyperClock ? 32U * 1024U : 512U) * 1024U;
|
||||
|
||||
std::shared_ptr<Cache> cache = NewCache(32U * min_shard_size);
|
||||
ShardedCacheBase* sc = dynamic_cast<ShardedCacheBase*>(cache.get());
|
||||
|
||||
Vendored
+456
-633
File diff suppressed because it is too large.
Load diff
Vendored
+151
-246
@@ -24,14 +24,12 @@
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/math.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
namespace clock_cache {
|
||||
|
||||
// Forward declaration of friend class.
|
||||
template <class ClockCache>
|
||||
class ClockCacheTest;
|
||||
|
||||
// HyperClockCache is an alternative to LRUCache specifically tailored for
|
||||
@@ -283,6 +281,29 @@ class ClockCacheTest;
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
// The load factor p is a real number in (0, 1) such that at all
|
||||
// times at most a fraction p of all slots, without counting tombstones,
|
||||
// are occupied by elements. This means that the probability that a random
|
||||
// probe hits an occupied slot is at most p, and thus at most 1/p probes
|
||||
// are required on average. For example, p = 70% implies that between 1 and 2
|
||||
// probes are needed on average (bear in mind that this reasoning doesn't
|
||||
// consider the effects of clustering over time, which should be negligible
|
||||
// with double hashing).
|
||||
// Because the size of the hash table is always rounded up to the next
|
||||
// power of 2, p is really an upper bound on the actual load factor---the
|
||||
// actual load factor is anywhere between p/2 and p. This is a bit wasteful,
|
||||
// but bear in mind that slots only hold metadata, not actual values.
|
||||
// Since space cost is dominated by the values (the LSM blocks),
|
||||
// overprovisioning the table with metadata only increases the total cache space
|
||||
// usage by a tiny fraction.
|
||||
constexpr double kLoadFactor = 0.7;
|
||||
|
||||
// The user can exceed kLoadFactor if the sizes of the inserted values don't
|
||||
// match estimated_value_size, or in some rare cases with
|
||||
// strict_capacity_limit == false. To avoid degenerate performance, we set a
|
||||
// strict upper bound on the load factor.
|
||||
constexpr double kStrictLoadFactor = 0.84;
|
||||
|
||||
struct ClockHandleBasicData {
|
||||
Cache::ObjectPtr value = nullptr;
|
||||
const Cache::CacheItemHelper* helper = nullptr;
|
||||
@@ -305,7 +326,7 @@ struct ClockHandle : public ClockHandleBasicData {
|
||||
// state of the handle. The meta word looks like this:
|
||||
// low bits high bits
|
||||
// -----------------------------------------------------------------------
|
||||
// | acquire counter | release counter | hit bit | state marker |
|
||||
// | acquire counter | release counter | state marker |
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
// For reading or updating counters in meta word.
|
||||
@@ -319,13 +340,8 @@ struct ClockHandle : public ClockHandleBasicData {
|
||||
static constexpr uint64_t kReleaseIncrement = uint64_t{1}
|
||||
<< kReleaseCounterShift;
|
||||
|
||||
// For setting the hit bit
|
||||
static constexpr uint8_t kHitBitShift = 2U * kCounterNumBits;
|
||||
static constexpr uint64_t kHitBitMask = uint64_t{1} << kHitBitShift;
|
||||
;
|
||||
|
||||
// For reading or updating the state marker in meta word
|
||||
static constexpr uint8_t kStateShift = kHitBitShift + 1;
|
||||
static constexpr uint8_t kStateShift = 2U * kCounterNumBits;
|
||||
|
||||
// Bits contribution to state marker.
|
||||
// Occupied means any state other than empty
|
||||
@@ -355,124 +371,14 @@ struct ClockHandle : public ClockHandleBasicData {
|
||||
static constexpr uint8_t kMaxCountdown = kHighCountdown;
|
||||
// TODO: make these coundown values tuning parameters for eviction?
|
||||
|
||||
// See above. Mutable for read reference counting.
|
||||
mutable std::atomic<uint64_t> meta{};
|
||||
// See above
|
||||
std::atomic<uint64_t> meta{};
|
||||
|
||||
// Anticipating use for SecondaryCache support
|
||||
void* reserved_for_future_use = nullptr;
|
||||
}; // struct ClockHandle
|
||||
|
||||
class BaseClockTable {
|
||||
public:
|
||||
BaseClockTable(CacheMetadataChargePolicy metadata_charge_policy,
|
||||
MemoryAllocator* allocator,
|
||||
const Cache::EvictionCallback* eviction_callback,
|
||||
const uint32_t* hash_seed)
|
||||
: metadata_charge_policy_(metadata_charge_policy),
|
||||
allocator_(allocator),
|
||||
eviction_callback_(*eviction_callback),
|
||||
hash_seed_(*hash_seed) {}
|
||||
|
||||
template <class Table>
|
||||
typename Table::HandleImpl* CreateStandalone(ClockHandleBasicData& proto,
|
||||
size_t capacity,
|
||||
bool strict_capacity_limit,
|
||||
bool allow_uncharged);
|
||||
|
||||
template <class Table>
|
||||
Status Insert(const ClockHandleBasicData& proto,
|
||||
typename Table::HandleImpl** handle, Cache::Priority priority,
|
||||
size_t capacity, bool strict_capacity_limit);
|
||||
|
||||
void Ref(ClockHandle& handle);
|
||||
|
||||
size_t GetOccupancy() const {
|
||||
return occupancy_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
size_t GetUsage() const { return usage_.load(std::memory_order_relaxed); }
|
||||
|
||||
size_t GetStandaloneUsage() const {
|
||||
return standalone_usage_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
uint32_t GetHashSeed() const { return hash_seed_; }
|
||||
|
||||
struct EvictionData {
|
||||
size_t freed_charge = 0;
|
||||
size_t freed_count = 0;
|
||||
};
|
||||
|
||||
void TrackAndReleaseEvictedEntry(ClockHandle* h, EvictionData* data);
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Acquire N references
|
||||
void TEST_RefN(ClockHandle& handle, size_t n);
|
||||
// Helper for TEST_ReleaseN
|
||||
void TEST_ReleaseNMinus1(ClockHandle* handle, size_t n);
|
||||
#endif
|
||||
|
||||
private: // fns
|
||||
// Creates a "standalone" handle for returning from an Insert operation that
|
||||
// cannot be completed by actually inserting into the table.
|
||||
// Updates `standalone_usage_` but not `usage_` nor `occupancy_`.
|
||||
template <class HandleImpl>
|
||||
HandleImpl* StandaloneInsert(const ClockHandleBasicData& proto);
|
||||
|
||||
// Helper for updating `usage_` for new entry with given `total_charge`
|
||||
// and evicting if needed under strict_capacity_limit=true rules. This
|
||||
// means the operation might fail with Status::MemoryLimit. If
|
||||
// `need_evict_for_occupancy`, then eviction of at least one entry is
|
||||
// required, and the operation should fail if not possible.
|
||||
// NOTE: Otherwise, occupancy_ is not managed in this function
|
||||
template <class Table>
|
||||
Status ChargeUsageMaybeEvictStrict(size_t total_charge, size_t capacity,
|
||||
bool need_evict_for_occupancy,
|
||||
typename Table::InsertState& state);
|
||||
|
||||
// Helper for updating `usage_` for new entry with given `total_charge`
|
||||
// and evicting if needed under strict_capacity_limit=false rules. This
|
||||
// means that updating `usage_` always succeeds even if forced to exceed
|
||||
// capacity. If `need_evict_for_occupancy`, then eviction of at least one
|
||||
// entry is required, and the operation should return false if such eviction
|
||||
// is not possible. `usage_` is not updated in that case. Otherwise, returns
|
||||
// true, indicating success.
|
||||
// NOTE: occupancy_ is not managed in this function
|
||||
template <class Table>
|
||||
bool ChargeUsageMaybeEvictNonStrict(size_t total_charge, size_t capacity,
|
||||
bool need_evict_for_occupancy,
|
||||
typename Table::InsertState& state);
|
||||
|
||||
protected: // data
|
||||
// We partition the following members into different cache lines
|
||||
// to avoid false sharing among Lookup, Release, Erase and Insert
|
||||
// operations in ClockCacheShard.
|
||||
|
||||
// Clock algorithm sweep pointer.
|
||||
std::atomic<uint64_t> clock_pointer_{};
|
||||
|
||||
// TODO: is this separation needed if we don't do background evictions?
|
||||
ALIGN_AS(CACHE_LINE_SIZE)
|
||||
// Number of elements in the table.
|
||||
std::atomic<size_t> occupancy_{};
|
||||
|
||||
// Memory usage by entries tracked by the cache (including standalone)
|
||||
std::atomic<size_t> usage_{};
|
||||
|
||||
// Part of usage by standalone entries (not in table)
|
||||
std::atomic<size_t> standalone_usage_{};
|
||||
|
||||
ALIGN_AS(CACHE_LINE_SIZE)
|
||||
const CacheMetadataChargePolicy metadata_charge_policy_;
|
||||
|
||||
// From Cache, for deleter
|
||||
MemoryAllocator* const allocator_;
|
||||
|
||||
// A reference to Cache::eviction_callback_
|
||||
const Cache::EvictionCallback& eviction_callback_;
|
||||
|
||||
// A reference to ShardedCacheBase::hash_seed_
|
||||
const uint32_t& hash_seed_;
|
||||
};
|
||||
|
||||
class FixedHyperClockTable : public BaseClockTable {
|
||||
class HyperClockTable {
|
||||
public:
|
||||
// Target size to be exactly a common cache line size (see static_assert in
|
||||
// clock_cache.cc)
|
||||
@@ -494,104 +400,88 @@ class FixedHyperClockTable : public BaseClockTable {
|
||||
}; // struct HandleImpl
|
||||
|
||||
struct Opts {
|
||||
explicit Opts(size_t _estimated_value_size)
|
||||
: estimated_value_size(_estimated_value_size) {}
|
||||
explicit Opts(const HyperClockCacheOptions& opts) {
|
||||
assert(opts.estimated_entry_charge > 0);
|
||||
estimated_value_size = opts.estimated_entry_charge;
|
||||
}
|
||||
size_t estimated_value_size;
|
||||
};
|
||||
|
||||
FixedHyperClockTable(size_t capacity, bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
MemoryAllocator* allocator,
|
||||
const Cache::EvictionCallback* eviction_callback,
|
||||
const uint32_t* hash_seed, const Opts& opts);
|
||||
~FixedHyperClockTable();
|
||||
HyperClockTable(size_t capacity, bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
MemoryAllocator* allocator,
|
||||
const Cache::EvictionCallback* eviction_callback,
|
||||
const uint32_t* hash_seed, const Opts& opts);
|
||||
~HyperClockTable();
|
||||
|
||||
// For BaseClockTable::Insert
|
||||
struct InsertState {};
|
||||
Status Insert(const ClockHandleBasicData& proto, HandleImpl** handle,
|
||||
Cache::Priority priority, size_t capacity,
|
||||
bool strict_capacity_limit);
|
||||
|
||||
void StartInsert(InsertState& state);
|
||||
|
||||
// Returns true iff there is room for the proposed number of entries.
|
||||
bool GrowIfNeeded(size_t new_occupancy, InsertState& state);
|
||||
|
||||
HandleImpl* DoInsert(const ClockHandleBasicData& proto,
|
||||
uint64_t initial_countdown, bool take_ref,
|
||||
InsertState& state);
|
||||
|
||||
// Runs the clock eviction algorithm trying to reclaim at least
|
||||
// requested_charge. Returns how much is evicted, which could be less
|
||||
// if it appears impossible to evict the requested amount without blocking.
|
||||
void Evict(size_t requested_charge, InsertState& state, EvictionData* data);
|
||||
HandleImpl* CreateStandalone(ClockHandleBasicData& proto, size_t capacity,
|
||||
bool strict_capacity_limit,
|
||||
bool allow_uncharged);
|
||||
|
||||
HandleImpl* Lookup(const UniqueId64x2& hashed_key);
|
||||
|
||||
bool Release(HandleImpl* handle, bool useful, bool erase_if_last_ref);
|
||||
|
||||
void Ref(HandleImpl& handle);
|
||||
|
||||
void Erase(const UniqueId64x2& hashed_key);
|
||||
|
||||
void ConstApplyToEntriesRange(std::function<void(const HandleImpl&)> func,
|
||||
size_t index_begin, size_t index_end,
|
||||
bool apply_if_will_be_deleted) const;
|
||||
|
||||
void EraseUnRefEntries();
|
||||
|
||||
size_t GetTableSize() const { return size_t{1} << length_bits_; }
|
||||
|
||||
size_t GetOccupancyLimit() const { return occupancy_limit_; }
|
||||
int GetLengthBits() const { return length_bits_; }
|
||||
|
||||
const HandleImpl* HandlePtr(size_t idx) const { return &array_[idx]; }
|
||||
|
||||
#ifndef NDEBUG
|
||||
size_t& TEST_MutableOccupancyLimit() {
|
||||
return const_cast<size_t&>(occupancy_limit_);
|
||||
size_t GetOccupancy() const {
|
||||
return occupancy_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Release N references
|
||||
size_t GetOccupancyLimit() const { return occupancy_limit_; }
|
||||
|
||||
size_t GetUsage() const { return usage_.load(std::memory_order_relaxed); }
|
||||
|
||||
size_t GetStandaloneUsage() const {
|
||||
return standalone_usage_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
uint32_t GetHashSeed() const { return hash_seed_; }
|
||||
|
||||
// Acquire/release N references
|
||||
void TEST_RefN(HandleImpl& handle, size_t n);
|
||||
void TEST_ReleaseN(HandleImpl* handle, size_t n);
|
||||
#endif
|
||||
|
||||
// The load factor p is a real number in (0, 1) such that at all
|
||||
// times at most a fraction p of all slots, without counting tombstones,
|
||||
// are occupied by elements. This means that the probability that a random
|
||||
// probe hits an occupied slot is at most p, and thus at most 1/p probes
|
||||
// are required on average. For example, p = 70% implies that between 1 and 2
|
||||
// probes are needed on average (bear in mind that this reasoning doesn't
|
||||
// consider the effects of clustering over time, which should be negligible
|
||||
// with double hashing).
|
||||
// Because the size of the hash table is always rounded up to the next
|
||||
// power of 2, p is really an upper bound on the actual load factor---the
|
||||
// actual load factor is anywhere between p/2 and p. This is a bit wasteful,
|
||||
// but bear in mind that slots only hold metadata, not actual values.
|
||||
// Since space cost is dominated by the values (the LSM blocks),
|
||||
// overprovisioning the table with metadata only increases the total cache
|
||||
// space usage by a tiny fraction.
|
||||
static constexpr double kLoadFactor = 0.7;
|
||||
|
||||
// The user can exceed kLoadFactor if the sizes of the inserted values don't
|
||||
// match estimated_value_size, or in some rare cases with
|
||||
// strict_capacity_limit == false. To avoid degenerate performance, we set a
|
||||
// strict upper bound on the load factor.
|
||||
static constexpr double kStrictLoadFactor = 0.84;
|
||||
|
||||
private: // functions
|
||||
// Returns x mod 2^{length_bits_}.
|
||||
inline size_t ModTableSize(uint64_t x) {
|
||||
return BitwiseAnd(x, length_bits_mask_);
|
||||
return static_cast<size_t>(x) & length_bits_mask_;
|
||||
}
|
||||
|
||||
// Returns the first slot in the probe sequence with a handle e such that
|
||||
// match_fn(e) is true. At every step, the function first tests whether
|
||||
// match_fn(e) holds. If this is false, it evaluates abort_fn(e) to decide
|
||||
// whether the search should be aborted, and if so, FindSlot immediately
|
||||
// returns nullptr. For every handle e that is not a match and not aborted,
|
||||
// FindSlot runs update_fn(e, is_last) where is_last is set to true iff that
|
||||
// slot will be the last probed because the next would cycle back to the first
|
||||
// slot probed. This function uses templates instead of std::function to
|
||||
// minimize the risk of heap-allocated closures being created.
|
||||
template <typename MatchFn, typename AbortFn, typename UpdateFn>
|
||||
// Runs the clock eviction algorithm trying to reclaim at least
|
||||
// requested_charge. Returns how much is evicted, which could be less
|
||||
// if it appears impossible to evict the requested amount without blocking.
|
||||
inline void Evict(size_t requested_charge, size_t* freed_charge,
|
||||
size_t* freed_count);
|
||||
|
||||
// Returns the first slot in the probe sequence, starting from the given
|
||||
// probe number, with a handle e such that match(e) is true. At every
|
||||
// step, the function first tests whether match(e) holds. If this is false,
|
||||
// it evaluates abort(e) to decide whether the search should be aborted,
|
||||
// and in the affirmative returns -1. For every handle e probed except
|
||||
// the last one, the function runs update(e).
|
||||
// The probe parameter is modified as follows. We say a probe to a handle
|
||||
// e is aborting if match(e) is false and abort(e) is true. Then the final
|
||||
// value of probe is one more than the last non-aborting probe during the
|
||||
// call. This is so that that the variable can be used to keep track of
|
||||
// progress across consecutive calls to FindSlot.
|
||||
inline HandleImpl* FindSlot(const UniqueId64x2& hashed_key,
|
||||
const MatchFn& match_fn, const AbortFn& abort_fn,
|
||||
const UpdateFn& update_fn);
|
||||
std::function<bool(HandleImpl*)> match,
|
||||
std::function<bool(HandleImpl*)> stop,
|
||||
std::function<void(HandleImpl*)> update,
|
||||
size_t& probe);
|
||||
|
||||
// Re-decrement all displacements in probe path starting from beginning
|
||||
// until (not including) the given handle
|
||||
@@ -604,6 +494,33 @@ class FixedHyperClockTable : public BaseClockTable {
|
||||
// before releasing it so that it can be provided to this function.
|
||||
inline void ReclaimEntryUsage(size_t total_charge);
|
||||
|
||||
// Helper for updating `usage_` for new entry with given `total_charge`
|
||||
// and evicting if needed under strict_capacity_limit=true rules. This
|
||||
// means the operation might fail with Status::MemoryLimit. If
|
||||
// `need_evict_for_occupancy`, then eviction of at least one entry is
|
||||
// required, and the operation should fail if not possible.
|
||||
// NOTE: Otherwise, occupancy_ is not managed in this function
|
||||
inline Status ChargeUsageMaybeEvictStrict(size_t total_charge,
|
||||
size_t capacity,
|
||||
bool need_evict_for_occupancy);
|
||||
|
||||
// Helper for updating `usage_` for new entry with given `total_charge`
|
||||
// and evicting if needed under strict_capacity_limit=false rules. This
|
||||
// means that updating `usage_` always succeeds even if forced to exceed
|
||||
// capacity. If `need_evict_for_occupancy`, then eviction of at least one
|
||||
// entry is required, and the operation should return false if such eviction
|
||||
// is not possible. `usage_` is not updated in that case. Otherwise, returns
|
||||
// true, indicating success.
|
||||
// NOTE: occupancy_ is not managed in this function
|
||||
inline bool ChargeUsageMaybeEvictNonStrict(size_t total_charge,
|
||||
size_t capacity,
|
||||
bool need_evict_for_occupancy);
|
||||
|
||||
// Creates a "standalone" handle for returning from an Insert operation that
|
||||
// cannot be completed by actually inserting into the table.
|
||||
// Updates `standalone_usage_` but not `usage_` nor `occupancy_`.
|
||||
inline HandleImpl* StandaloneInsert(const ClockHandleBasicData& proto);
|
||||
|
||||
MemoryAllocator* GetAllocator() const { return allocator_; }
|
||||
|
||||
// Returns the number of bits used to hash an element in the hash
|
||||
@@ -624,20 +541,39 @@ class FixedHyperClockTable : public BaseClockTable {
|
||||
|
||||
// Array of slots comprising the hash table.
|
||||
const std::unique_ptr<HandleImpl[]> array_;
|
||||
}; // class FixedHyperClockTable
|
||||
|
||||
// Placeholder for future automatic table variant
|
||||
// For now, just use FixedHyperClockTable.
|
||||
class AutoHyperClockTable : public FixedHyperClockTable {
|
||||
public:
|
||||
using FixedHyperClockTable::FixedHyperClockTable;
|
||||
}; // class AutoHyperClockTable
|
||||
// From Cache, for deleter
|
||||
MemoryAllocator* const allocator_;
|
||||
|
||||
// A reference to Cache::eviction_callback_
|
||||
const Cache::EvictionCallback& eviction_callback_;
|
||||
|
||||
// A reference to ShardedCacheBase::hash_seed_
|
||||
const uint32_t& hash_seed_;
|
||||
|
||||
// We partition the following members into different cache lines
|
||||
// to avoid false sharing among Lookup, Release, Erase and Insert
|
||||
// operations in ClockCacheShard.
|
||||
|
||||
ALIGN_AS(CACHE_LINE_SIZE)
|
||||
// Clock algorithm sweep pointer.
|
||||
std::atomic<uint64_t> clock_pointer_{};
|
||||
|
||||
ALIGN_AS(CACHE_LINE_SIZE)
|
||||
// Number of elements in the table.
|
||||
std::atomic<size_t> occupancy_{};
|
||||
|
||||
// Memory usage by entries tracked by the cache (including standalone)
|
||||
std::atomic<size_t> usage_{};
|
||||
|
||||
// Part of usage by standalone entries (not in table)
|
||||
std::atomic<size_t> standalone_usage_{};
|
||||
}; // class HyperClockTable
|
||||
|
||||
// A single shard of sharded cache.
|
||||
template <class TableT>
|
||||
template <class Table>
|
||||
class ALIGN_AS(CACHE_LINE_SIZE) ClockCacheShard final : public CacheShardBase {
|
||||
public:
|
||||
using Table = TableT;
|
||||
ClockCacheShard(size_t capacity, bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
MemoryAllocator* allocator,
|
||||
@@ -730,17 +666,9 @@ class ALIGN_AS(CACHE_LINE_SIZE) ClockCacheShard final : public CacheShardBase {
|
||||
return Lookup(key, hashed_key);
|
||||
}
|
||||
|
||||
Table& GetTable() { return table_; }
|
||||
const Table& GetTable() const { return table_; }
|
||||
|
||||
#ifndef NDEBUG
|
||||
size_t& TEST_MutableOccupancyLimit() {
|
||||
return table_.TEST_MutableOccupancyLimit();
|
||||
}
|
||||
// Acquire/release N references
|
||||
void TEST_RefN(HandleImpl* handle, size_t n);
|
||||
void TEST_ReleaseN(HandleImpl* handle, size_t n);
|
||||
#endif
|
||||
|
||||
private: // data
|
||||
Table table_;
|
||||
@@ -752,14 +680,17 @@ class ALIGN_AS(CACHE_LINE_SIZE) ClockCacheShard final : public CacheShardBase {
|
||||
std::atomic<bool> strict_capacity_limit_;
|
||||
}; // class ClockCacheShard
|
||||
|
||||
template <class Table>
|
||||
class BaseHyperClockCache : public ShardedCache<ClockCacheShard<Table>> {
|
||||
class HyperClockCache
|
||||
#ifdef NDEBUG
|
||||
final
|
||||
#endif
|
||||
: public ShardedCache<ClockCacheShard<HyperClockTable>> {
|
||||
public:
|
||||
using Shard = ClockCacheShard<Table>;
|
||||
using Handle = Cache::Handle;
|
||||
using CacheItemHelper = Cache::CacheItemHelper;
|
||||
using Shard = ClockCacheShard<HyperClockTable>;
|
||||
|
||||
explicit BaseHyperClockCache(const HyperClockCacheOptions& opts);
|
||||
explicit HyperClockCache(const HyperClockCacheOptions& opts);
|
||||
|
||||
const char* Name() const override { return "HyperClockCache"; }
|
||||
|
||||
Cache::ObjectPtr Value(Handle* handle) override;
|
||||
|
||||
@@ -769,33 +700,7 @@ class BaseHyperClockCache : public ShardedCache<ClockCacheShard<Table>> {
|
||||
|
||||
void ReportProblems(
|
||||
const std::shared_ptr<Logger>& /*info_log*/) const override;
|
||||
};
|
||||
|
||||
class FixedHyperClockCache
|
||||
#ifdef NDEBUG
|
||||
final
|
||||
#endif
|
||||
: public BaseHyperClockCache<FixedHyperClockTable> {
|
||||
public:
|
||||
using BaseHyperClockCache::BaseHyperClockCache;
|
||||
|
||||
const char* Name() const override { return "FixedHyperClockCache"; }
|
||||
|
||||
void ReportProblems(
|
||||
const std::shared_ptr<Logger>& /*info_log*/) const override;
|
||||
}; // class FixedHyperClockCache
|
||||
|
||||
// Placeholder for future automatic HCC variant
|
||||
class AutoHyperClockCache
|
||||
#ifdef NDEBUG
|
||||
final
|
||||
#endif
|
||||
: public BaseHyperClockCache<AutoHyperClockTable> {
|
||||
public:
|
||||
using BaseHyperClockCache::BaseHyperClockCache;
|
||||
|
||||
const char* Name() const override { return "AutoHyperClockCache"; }
|
||||
}; // class AutoHyperClockCache
|
||||
}; // class HyperClockCache
|
||||
|
||||
} // namespace clock_cache
|
||||
|
||||
|
||||
Vendored
+11
-25
@@ -22,8 +22,7 @@ CompressedSecondaryCache::CompressedSecondaryCache(
|
||||
cache_options_(opts),
|
||||
cache_res_mgr_(std::make_shared<ConcurrentCacheReservationManager>(
|
||||
std::make_shared<CacheReservationManagerImpl<CacheEntryRole::kMisc>>(
|
||||
cache_))),
|
||||
disable_cache_(opts.capacity == 0) {}
|
||||
cache_))) {}
|
||||
|
||||
CompressedSecondaryCache::~CompressedSecondaryCache() {
|
||||
assert(cache_res_mgr_->GetTotalReservedCacheSize() == 0);
|
||||
@@ -34,10 +33,6 @@ std::unique_ptr<SecondaryCacheResultHandle> CompressedSecondaryCache::Lookup(
|
||||
Cache::CreateContext* create_context, bool /*wait*/, bool advise_erase,
|
||||
bool& kept_in_sec_cache) {
|
||||
assert(helper);
|
||||
if (disable_cache_) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle;
|
||||
kept_in_sec_cache = false;
|
||||
Cache::Handle* lru_handle = cache_->Lookup(key);
|
||||
@@ -114,27 +109,20 @@ std::unique_ptr<SecondaryCacheResultHandle> CompressedSecondaryCache::Lookup(
|
||||
|
||||
Status CompressedSecondaryCache::Insert(const Slice& key,
|
||||
Cache::ObjectPtr value,
|
||||
const Cache::CacheItemHelper* helper,
|
||||
bool force_insert) {
|
||||
const Cache::CacheItemHelper* helper) {
|
||||
if (value == nullptr) {
|
||||
return Status::InvalidArgument();
|
||||
}
|
||||
|
||||
if (disable_cache_) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Cache::Handle* lru_handle = cache_->Lookup(key);
|
||||
auto internal_helper = GetHelper(cache_options_.enable_custom_split_merge);
|
||||
if (!force_insert) {
|
||||
Cache::Handle* lru_handle = cache_->Lookup(key);
|
||||
if (lru_handle == nullptr) {
|
||||
PERF_COUNTER_ADD(compressed_sec_cache_insert_dummy_count, 1);
|
||||
// Insert a dummy handle if the handle is evicted for the first time.
|
||||
return cache_->Insert(key, /*obj=*/nullptr, internal_helper,
|
||||
/*charge=*/0);
|
||||
} else {
|
||||
cache_->Release(lru_handle, /*erase_if_last_ref=*/false);
|
||||
}
|
||||
if (lru_handle == nullptr) {
|
||||
PERF_COUNTER_ADD(compressed_sec_cache_insert_dummy_count, 1);
|
||||
// Insert a dummy handle if the handle is evicted for the first time.
|
||||
return cache_->Insert(key, /*obj=*/nullptr, internal_helper,
|
||||
/*charge=*/0);
|
||||
} else {
|
||||
cache_->Release(lru_handle, /*erase_if_last_ref=*/false);
|
||||
}
|
||||
|
||||
size_t size = (*helper->size_cb)(value);
|
||||
@@ -152,8 +140,7 @@ Status CompressedSecondaryCache::Insert(const Slice& key,
|
||||
!cache_options_.do_not_compress_roles.Contains(helper->role)) {
|
||||
PERF_COUNTER_ADD(compressed_sec_cache_uncompressed_bytes, size);
|
||||
CompressionOptions compression_opts;
|
||||
CompressionContext compression_context(cache_options_.compression_type,
|
||||
compression_opts);
|
||||
CompressionContext compression_context(cache_options_.compression_type);
|
||||
uint64_t sample_for_compression{0};
|
||||
CompressionInfo compression_info(
|
||||
compression_opts, compression_context, CompressionDict::GetEmptyDict(),
|
||||
@@ -195,7 +182,6 @@ Status CompressedSecondaryCache::SetCapacity(size_t capacity) {
|
||||
MutexLock l(&capacity_mutex_);
|
||||
cache_options_.capacity = capacity;
|
||||
cache_->SetCapacity(capacity);
|
||||
disable_cache_ = capacity == 0;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
|
||||
Vendored
+1
-3
@@ -77,8 +77,7 @@ class CompressedSecondaryCache : public SecondaryCache {
|
||||
const char* Name() const override { return "CompressedSecondaryCache"; }
|
||||
|
||||
Status Insert(const Slice& key, Cache::ObjectPtr value,
|
||||
const Cache::CacheItemHelper* helper,
|
||||
bool force_insert) override;
|
||||
const Cache::CacheItemHelper* helper) override;
|
||||
|
||||
std::unique_ptr<SecondaryCacheResultHandle> Lookup(
|
||||
const Slice& key, const Cache::CacheItemHelper* helper,
|
||||
@@ -136,7 +135,6 @@ class CompressedSecondaryCache : public SecondaryCache {
|
||||
CompressedSecondaryCacheOptions cache_options_;
|
||||
mutable port::Mutex capacity_mutex_;
|
||||
std::shared_ptr<ConcurrentCacheReservationManager> cache_res_mgr_;
|
||||
bool disable_cache_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+22
-128
@@ -12,7 +12,6 @@
|
||||
|
||||
#include "cache/secondary_cache_adapter.h"
|
||||
#include "memory/jemalloc_nodump_allocator.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "test_util/secondary_cache_test_util.h"
|
||||
#include "test_util/testharness.h"
|
||||
@@ -52,7 +51,7 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
std::string str1(rnd.RandomString(1000));
|
||||
TestItem item1(str1.data(), str1.length());
|
||||
// A dummy handle is inserted if the item is inserted for the first time.
|
||||
ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper(), false));
|
||||
ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper()));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 1);
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0);
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0);
|
||||
@@ -63,7 +62,7 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
ASSERT_EQ(handle1_1, nullptr);
|
||||
|
||||
// Insert and Lookup the item k1 for the second time and advise erasing it.
|
||||
ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper(), false));
|
||||
ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper()));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 1);
|
||||
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle1_2 =
|
||||
@@ -95,14 +94,14 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
// Insert and Lookup the item k2.
|
||||
std::string str2(rnd.RandomString(1000));
|
||||
TestItem item2(str2.data(), str2.length());
|
||||
ASSERT_OK(sec_cache->Insert(key2, &item2, GetHelper(), false));
|
||||
ASSERT_OK(sec_cache->Insert(key2, &item2, GetHelper()));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 2);
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle2_1 =
|
||||
sec_cache->Lookup(key2, GetHelper(), this, true, /*advise_erase=*/false,
|
||||
kept_in_sec_cache);
|
||||
ASSERT_EQ(handle2_1, nullptr);
|
||||
|
||||
ASSERT_OK(sec_cache->Insert(key2, &item2, GetHelper(), false));
|
||||
ASSERT_OK(sec_cache->Insert(key2, &item2, GetHelper()));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 2);
|
||||
if (sec_cache_is_compressed) {
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes,
|
||||
@@ -184,15 +183,15 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
std::string str1(rnd.RandomString(1000));
|
||||
TestItem item1(str1.data(), str1.length());
|
||||
// Insert a dummy handle.
|
||||
ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper(), false));
|
||||
ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper()));
|
||||
// Insert k1.
|
||||
ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper(), false));
|
||||
ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper()));
|
||||
|
||||
// Insert and Lookup the second item.
|
||||
std::string str2(rnd.RandomString(200));
|
||||
TestItem item2(str2.data(), str2.length());
|
||||
// Insert a dummy handle, k1 is not evicted.
|
||||
ASSERT_OK(sec_cache->Insert(key2, &item2, GetHelper(), false));
|
||||
ASSERT_OK(sec_cache->Insert(key2, &item2, GetHelper()));
|
||||
bool kept_in_sec_cache{false};
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle1 =
|
||||
sec_cache->Lookup(key1, GetHelper(), this, true, /*advise_erase=*/false,
|
||||
@@ -200,7 +199,7 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
ASSERT_EQ(handle1, nullptr);
|
||||
|
||||
// Insert k2 and k1 is evicted.
|
||||
ASSERT_OK(sec_cache->Insert(key2, &item2, GetHelper(), false));
|
||||
ASSERT_OK(sec_cache->Insert(key2, &item2, GetHelper()));
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle2 =
|
||||
sec_cache->Lookup(key2, GetHelper(), this, true, /*advise_erase=*/false,
|
||||
kept_in_sec_cache);
|
||||
@@ -211,7 +210,7 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
ASSERT_EQ(memcmp(val2->Buf(), item2.Buf(), item2.Size()), 0);
|
||||
|
||||
// Insert k1 again and a dummy handle is inserted.
|
||||
ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper(), false));
|
||||
ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper()));
|
||||
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle1_1 =
|
||||
sec_cache->Lookup(key1, GetHelper(), this, true, /*advise_erase=*/false,
|
||||
@@ -229,8 +228,8 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
std::string str3 = rnd.RandomString(10);
|
||||
TestItem item3(str3.data(), str3.length());
|
||||
// The Status is OK because a dummy handle is inserted.
|
||||
ASSERT_OK(sec_cache->Insert(key3, &item3, GetHelperFail(), false));
|
||||
ASSERT_NOK(sec_cache->Insert(key3, &item3, GetHelperFail(), false));
|
||||
ASSERT_OK(sec_cache->Insert(key3, &item3, GetHelperFail()));
|
||||
ASSERT_NOK(sec_cache->Insert(key3, &item3, GetHelperFail()));
|
||||
|
||||
sec_cache.reset();
|
||||
}
|
||||
@@ -738,7 +737,7 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
class CompressedSecondaryCacheTest
|
||||
: public CompressedSecondaryCacheTestBase,
|
||||
public testing::WithParamInterface<std::string> {
|
||||
const std::string& Type() const override { return GetParam(); }
|
||||
const std::string& Type() override { return GetParam(); }
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(CompressedSecondaryCacheTest,
|
||||
@@ -753,7 +752,7 @@ class CompressedSecCacheTestWithCompressAndAllocatorParam
|
||||
sec_cache_is_compressed_ = std::get<0>(GetParam());
|
||||
use_jemalloc_ = std::get<1>(GetParam());
|
||||
}
|
||||
const std::string& Type() const override { return std::get<2>(GetParam()); }
|
||||
const std::string& Type() override { return std::get<2>(GetParam()); }
|
||||
bool sec_cache_is_compressed_;
|
||||
bool use_jemalloc_;
|
||||
};
|
||||
@@ -774,7 +773,7 @@ class CompressedSecondaryCacheTestWithCompressionParam
|
||||
CompressedSecondaryCacheTestWithCompressionParam() {
|
||||
sec_cache_is_compressed_ = std::get<0>(GetParam());
|
||||
}
|
||||
const std::string& Type() const override { return std::get<1>(GetParam()); }
|
||||
const std::string& Type() override { return std::get<1>(GetParam()); }
|
||||
bool sec_cache_is_compressed_;
|
||||
};
|
||||
|
||||
@@ -905,10 +904,10 @@ TEST_P(CompressedSecondaryCacheTestWithCompressionParam, EntryRoles) {
|
||||
Slice ith_key = Slice(junk.data(), 16);
|
||||
|
||||
get_perf_context()->Reset();
|
||||
ASSERT_OK(sec_cache->Insert(ith_key, &item, GetHelper(role), false));
|
||||
ASSERT_OK(sec_cache->Insert(ith_key, &item, GetHelper(role)));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 1U);
|
||||
|
||||
ASSERT_OK(sec_cache->Insert(ith_key, &item, GetHelper(role), false));
|
||||
ASSERT_OK(sec_cache->Insert(ith_key, &item, GetHelper(role)));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 1U);
|
||||
|
||||
bool kept_in_sec_cache{true};
|
||||
@@ -951,7 +950,7 @@ class CompressedSecCacheTestWithCompressAndSplitParam
|
||||
sec_cache_is_compressed_ = std::get<0>(GetParam());
|
||||
enable_custom_split_merge_ = std::get<1>(GetParam());
|
||||
}
|
||||
const std::string& Type() const override { return std::get<2>(GetParam()); }
|
||||
const std::string& Type() override { return std::get<2>(GetParam()); }
|
||||
bool sec_cache_is_compressed_;
|
||||
bool enable_custom_split_merge_;
|
||||
};
|
||||
@@ -977,54 +976,24 @@ TEST_P(CompressedSecondaryCacheTest, SplictValueAndMergeChunksTest) {
|
||||
SplictValueAndMergeChunksTest();
|
||||
}
|
||||
|
||||
using secondary_cache_test_util::WithCacheType;
|
||||
|
||||
class CompressedSecCacheTestWithTiered
|
||||
: public testing::Test,
|
||||
public WithCacheType,
|
||||
public testing::WithParamInterface<
|
||||
std::tuple<PrimaryCacheType, TieredAdmissionPolicy>> {
|
||||
class CompressedSecCacheTestWithTiered : public ::testing::Test {
|
||||
public:
|
||||
using secondary_cache_test_util::WithCacheType::TestItem;
|
||||
CompressedSecCacheTestWithTiered() {
|
||||
LRUCacheOptions lru_opts;
|
||||
HyperClockCacheOptions hcc_opts(
|
||||
/*_capacity=*/70 << 20,
|
||||
/*_estimated_entry_charge=*/256 << 10,
|
||||
/*_num_shard_bits=*/0);
|
||||
TieredVolatileCacheOptions opts;
|
||||
lru_opts.capacity = 70 << 20;
|
||||
lru_opts.num_shard_bits = 0;
|
||||
lru_opts.high_pri_pool_ratio = 0;
|
||||
opts.cache_type = std::get<0>(GetParam());
|
||||
if (opts.cache_type == PrimaryCacheType::kCacheTypeLRU) {
|
||||
opts.cache_opts = &lru_opts;
|
||||
} else {
|
||||
opts.cache_opts = &hcc_opts;
|
||||
}
|
||||
opts.adm_policy = std::get<1>(GetParam());
|
||||
;
|
||||
opts.cache_opts = &lru_opts;
|
||||
opts.cache_type = PrimaryCacheType::kCacheTypeLRU;
|
||||
opts.comp_cache_opts.capacity = 30 << 20;
|
||||
opts.comp_cache_opts.num_shard_bits = 0;
|
||||
cache_ = NewTieredVolatileCache(opts);
|
||||
cache_res_mgr_ =
|
||||
std::make_shared<CacheReservationManagerImpl<CacheEntryRole::kMisc>>(
|
||||
cache_);
|
||||
}
|
||||
|
||||
const std::string& Type() const override {
|
||||
if (std::get<0>(GetParam()) == PrimaryCacheType::kCacheTypeLRU) {
|
||||
return lru_str;
|
||||
} else {
|
||||
return hcc_str;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
CacheReservationManager* cache_res_mgr() { return cache_res_mgr_.get(); }
|
||||
|
||||
Cache* GetTieredCache() { return cache_.get(); }
|
||||
|
||||
Cache* GetCache() {
|
||||
return static_cast_with_check<CacheWithSecondaryAdapter, Cache>(
|
||||
cache_.get())
|
||||
@@ -1044,19 +1013,13 @@ class CompressedSecCacheTestWithTiered
|
||||
private:
|
||||
std::shared_ptr<Cache> cache_;
|
||||
std::shared_ptr<CacheReservationManager> cache_res_mgr_;
|
||||
static std::string lru_str;
|
||||
static std::string hcc_str;
|
||||
};
|
||||
|
||||
std::string CompressedSecCacheTestWithTiered::lru_str(WithCacheType::kLRU);
|
||||
std::string CompressedSecCacheTestWithTiered::hcc_str(
|
||||
WithCacheType::kFixedHyperClock);
|
||||
|
||||
bool CacheUsageWithinBounds(size_t val1, size_t val2, size_t error) {
|
||||
return ((val1 < (val2 + error)) && (val1 > (val2 - error)));
|
||||
}
|
||||
|
||||
TEST_P(CompressedSecCacheTestWithTiered, CacheReservationManager) {
|
||||
TEST_F(CompressedSecCacheTestWithTiered, CacheReservationManager) {
|
||||
CompressedSecondaryCache* sec_cache =
|
||||
reinterpret_cast<CompressedSecondaryCache*>(GetSecondaryCache());
|
||||
|
||||
@@ -1078,7 +1041,7 @@ TEST_P(CompressedSecCacheTestWithTiered, CacheReservationManager) {
|
||||
EXPECT_EQ(sec_cache->TEST_GetUsage(), 0);
|
||||
}
|
||||
|
||||
TEST_P(CompressedSecCacheTestWithTiered,
|
||||
TEST_F(CompressedSecCacheTestWithTiered,
|
||||
CacheReservationManagerMultipleUpdate) {
|
||||
CompressedSecondaryCache* sec_cache =
|
||||
reinterpret_cast<CompressedSecondaryCache*>(GetSecondaryCache());
|
||||
@@ -1104,75 +1067,6 @@ TEST_P(CompressedSecCacheTestWithTiered,
|
||||
EXPECT_EQ(sec_cache->TEST_GetUsage(), 0);
|
||||
}
|
||||
|
||||
TEST_P(CompressedSecCacheTestWithTiered, AdmissionPolicy) {
|
||||
if (!LZ4_Supported()) {
|
||||
ROCKSDB_GTEST_BYPASS("This test requires LZ4 support\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Cache* tiered_cache = GetTieredCache();
|
||||
Cache* cache = GetCache();
|
||||
std::vector<CacheKey> keys;
|
||||
std::vector<std::string> vals;
|
||||
// Make the item size slightly less than 10MB to ensure we can fit the
|
||||
// expected number of items in the cache
|
||||
int item_size = (10 << 20) - (1 << 18);
|
||||
int i;
|
||||
Random rnd(301);
|
||||
for (i = 0; i < 14; ++i) {
|
||||
keys.emplace_back(CacheKey::CreateUniqueForCacheLifetime(cache));
|
||||
vals.emplace_back(rnd.RandomString(item_size));
|
||||
}
|
||||
|
||||
for (i = 0; i < 7; ++i) {
|
||||
TestItem* item = new TestItem(vals[i].data(), vals[i].length());
|
||||
ASSERT_OK(tiered_cache->Insert(keys[i].AsSlice(), item, GetHelper(),
|
||||
vals[i].length()));
|
||||
}
|
||||
|
||||
Cache::Handle* handle1;
|
||||
handle1 = tiered_cache->Lookup(keys[0].AsSlice(), GetHelper(),
|
||||
/*context*/ this, Cache::Priority::LOW);
|
||||
ASSERT_NE(handle1, nullptr);
|
||||
Cache::Handle* handle2;
|
||||
handle2 = tiered_cache->Lookup(keys[1].AsSlice(), GetHelper(),
|
||||
/*context*/ this, Cache::Priority::LOW);
|
||||
ASSERT_NE(handle2, nullptr);
|
||||
tiered_cache->Release(handle1);
|
||||
tiered_cache->Release(handle2);
|
||||
|
||||
// Flush all previous entries out of the primary cache
|
||||
for (i = 7; i < 14; ++i) {
|
||||
TestItem* item = new TestItem(vals[i].data(), vals[i].length());
|
||||
ASSERT_OK(tiered_cache->Insert(keys[i].AsSlice(), item, GetHelper(),
|
||||
vals[i].length()));
|
||||
}
|
||||
// keys 0 and 1 should be found as they had the hit bit set
|
||||
handle1 = tiered_cache->Lookup(keys[0].AsSlice(), GetHelper(),
|
||||
/*context*/ this, Cache::Priority::LOW);
|
||||
ASSERT_NE(handle1, nullptr);
|
||||
handle2 = tiered_cache->Lookup(keys[1].AsSlice(), GetHelper(),
|
||||
/*context*/ this, Cache::Priority::LOW);
|
||||
ASSERT_NE(handle2, nullptr);
|
||||
tiered_cache->Release(handle1);
|
||||
tiered_cache->Release(handle2);
|
||||
|
||||
handle1 = tiered_cache->Lookup(keys[2].AsSlice(), GetHelper(),
|
||||
/*context*/ this, Cache::Priority::LOW);
|
||||
ASSERT_EQ(handle1, nullptr);
|
||||
handle1 = tiered_cache->Lookup(keys[3].AsSlice(), GetHelper(),
|
||||
/*context*/ this, Cache::Priority::LOW);
|
||||
ASSERT_EQ(handle1, nullptr);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
CompressedSecCacheTests, CompressedSecCacheTestWithTiered,
|
||||
::testing::Values(
|
||||
std::make_tuple(PrimaryCacheType::kCacheTypeLRU,
|
||||
TieredAdmissionPolicy::kAdmPolicyAllowCacheHits),
|
||||
std::make_tuple(PrimaryCacheType::kCacheTypeHCC,
|
||||
TieredAdmissionPolicy::kAdmPolicyAllowCacheHits)));
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
Vendored
+5
-7
@@ -340,8 +340,7 @@ void LRUCacheShard::NotifyEvicted(
|
||||
for (LRUHandle* entry : evicted_handles) {
|
||||
if (eviction_callback_ &&
|
||||
eviction_callback_(entry->key(),
|
||||
reinterpret_cast<Cache::Handle*>(entry),
|
||||
entry->HasHit())) {
|
||||
reinterpret_cast<Cache::Handle*>(entry))) {
|
||||
// Callback took ownership of obj; just free handle
|
||||
free(entry);
|
||||
} else {
|
||||
@@ -506,8 +505,7 @@ bool LRUCacheShard::Release(LRUHandle* e, bool /*useful*/,
|
||||
// Only call eviction callback if we're sure no one requested erasure
|
||||
// FIXME: disabled because of test churn
|
||||
if (false && was_in_cache && !erase_if_last_ref && eviction_callback_ &&
|
||||
eviction_callback_(e->key(), reinterpret_cast<Cache::Handle*>(e),
|
||||
e->HasHit())) {
|
||||
eviction_callback_(e->key(), reinterpret_cast<Cache::Handle*>(e))) {
|
||||
// Callback took ownership of obj; just free handle
|
||||
free(e);
|
||||
} else {
|
||||
@@ -714,12 +712,12 @@ std::shared_ptr<Cache> LRUCacheOptions::MakeSharedCache() const {
|
||||
return cache;
|
||||
}
|
||||
|
||||
std::shared_ptr<RowCache> LRUCacheOptions::MakeSharedRowCache() const {
|
||||
std::shared_ptr<GeneralCache> LRUCacheOptions::MakeSharedGeneralCache() const {
|
||||
if (secondary_cache) {
|
||||
// Not allowed for a RowCache
|
||||
// Not allowed for a GeneralCache
|
||||
return nullptr;
|
||||
}
|
||||
// Works while RowCache is an alias for Cache
|
||||
// Works while GeneralCache is an alias for Cache
|
||||
return MakeSharedCache();
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+194
-274
@@ -371,12 +371,11 @@ TEST_F(LRUCacheTest, EntriesWithPriority) {
|
||||
|
||||
namespace clock_cache {
|
||||
|
||||
template <class ClockCache>
|
||||
class ClockCacheTest : public testing::Test {
|
||||
public:
|
||||
using Shard = typename ClockCache::Shard;
|
||||
using Table = typename Shard::Table;
|
||||
using TableOpts = typename Table::Opts;
|
||||
using Shard = HyperClockCache::Shard;
|
||||
using Table = HyperClockTable;
|
||||
using HandleImpl = Shard::HandleImpl;
|
||||
|
||||
ClockCacheTest() {}
|
||||
~ClockCacheTest() override { DeleteShard(); }
|
||||
@@ -394,7 +393,8 @@ class ClockCacheTest : public testing::Test {
|
||||
shard_ =
|
||||
reinterpret_cast<Shard*>(port::cacheline_aligned_alloc(sizeof(Shard)));
|
||||
|
||||
TableOpts opts{1 /*value_size*/};
|
||||
Table::Opts opts;
|
||||
opts.estimated_value_size = 1;
|
||||
new (shard_)
|
||||
Shard(capacity, strict_capacity_limit, kDontChargeCacheMetadata,
|
||||
/*allocator*/ nullptr, &eviction_callback_, &hash_seed_, opts);
|
||||
@@ -458,53 +458,43 @@ class ClockCacheTest : public testing::Test {
|
||||
uint32_t hash_seed_ = 0;
|
||||
};
|
||||
|
||||
using ClockCacheTypes =
|
||||
::testing::Types<AutoHyperClockCache, FixedHyperClockCache>;
|
||||
TYPED_TEST_CASE(ClockCacheTest, ClockCacheTypes);
|
||||
|
||||
TYPED_TEST(ClockCacheTest, Misc) {
|
||||
this->NewShard(3);
|
||||
// NOTE: templated base class prevents simple naming of inherited members,
|
||||
// so lots of `this->`
|
||||
auto& shard = *this->shard_;
|
||||
TEST_F(ClockCacheTest, Misc) {
|
||||
NewShard(3);
|
||||
|
||||
// Key size stuff
|
||||
EXPECT_OK(this->InsertWithLen('a', 16));
|
||||
EXPECT_NOK(this->InsertWithLen('b', 15));
|
||||
EXPECT_OK(this->InsertWithLen('b', 16));
|
||||
EXPECT_NOK(this->InsertWithLen('c', 17));
|
||||
EXPECT_NOK(this->InsertWithLen('d', 1000));
|
||||
EXPECT_NOK(this->InsertWithLen('e', 11));
|
||||
EXPECT_NOK(this->InsertWithLen('f', 0));
|
||||
EXPECT_OK(InsertWithLen('a', 16));
|
||||
EXPECT_NOK(InsertWithLen('b', 15));
|
||||
EXPECT_OK(InsertWithLen('b', 16));
|
||||
EXPECT_NOK(InsertWithLen('c', 17));
|
||||
EXPECT_NOK(InsertWithLen('d', 1000));
|
||||
EXPECT_NOK(InsertWithLen('e', 11));
|
||||
EXPECT_NOK(InsertWithLen('f', 0));
|
||||
|
||||
// Some of this is motivated by code coverage
|
||||
std::string wrong_size_key(15, 'x');
|
||||
EXPECT_FALSE(this->Lookup(wrong_size_key, this->TestHashedKey('x')));
|
||||
EXPECT_FALSE(shard.Ref(nullptr));
|
||||
EXPECT_FALSE(shard.Release(nullptr));
|
||||
shard.Erase(wrong_size_key, this->TestHashedKey('x')); // no-op
|
||||
EXPECT_FALSE(Lookup(wrong_size_key, TestHashedKey('x')));
|
||||
EXPECT_FALSE(shard_->Ref(nullptr));
|
||||
EXPECT_FALSE(shard_->Release(nullptr));
|
||||
shard_->Erase(wrong_size_key, TestHashedKey('x')); // no-op
|
||||
}
|
||||
|
||||
TYPED_TEST(ClockCacheTest, Limits) {
|
||||
constexpr size_t kCapacity = 64;
|
||||
this->NewShard(kCapacity, false /*strict_capacity_limit*/);
|
||||
auto& shard = *this->shard_;
|
||||
using HandleImpl = typename ClockCacheTest<TypeParam>::Shard::HandleImpl;
|
||||
|
||||
TEST_F(ClockCacheTest, Limits) {
|
||||
constexpr size_t kCapacity = 3;
|
||||
NewShard(kCapacity, false /*strict_capacity_limit*/);
|
||||
for (bool strict_capacity_limit : {false, true, false}) {
|
||||
SCOPED_TRACE("strict_capacity_limit = " +
|
||||
std::to_string(strict_capacity_limit));
|
||||
|
||||
// Also tests switching between strict limit and not
|
||||
shard.SetStrictCapacityLimit(strict_capacity_limit);
|
||||
shard_->SetStrictCapacityLimit(strict_capacity_limit);
|
||||
|
||||
UniqueId64x2 hkey = this->TestHashedKey('x');
|
||||
UniqueId64x2 hkey = TestHashedKey('x');
|
||||
|
||||
// Single entry charge beyond capacity
|
||||
{
|
||||
Status s = shard.Insert(this->TestKey(hkey), hkey, nullptr /*value*/,
|
||||
&kNoopCacheItemHelper, kCapacity + 2 /*charge*/,
|
||||
nullptr /*handle*/, Cache::Priority::LOW);
|
||||
Status s = shard_->Insert(TestKey(hkey), hkey, nullptr /*value*/,
|
||||
&kNoopCacheItemHelper, 5 /*charge*/,
|
||||
nullptr /*handle*/, Cache::Priority::LOW);
|
||||
if (strict_capacity_limit) {
|
||||
EXPECT_TRUE(s.IsMemoryLimit());
|
||||
} else {
|
||||
@@ -515,11 +505,11 @@ TYPED_TEST(ClockCacheTest, Limits) {
|
||||
// Single entry fills capacity
|
||||
{
|
||||
HandleImpl* h;
|
||||
ASSERT_OK(shard.Insert(this->TestKey(hkey), hkey, nullptr /*value*/,
|
||||
&kNoopCacheItemHelper, kCapacity /*charge*/, &h,
|
||||
Cache::Priority::LOW));
|
||||
ASSERT_OK(shard_->Insert(TestKey(hkey), hkey, nullptr /*value*/,
|
||||
&kNoopCacheItemHelper, 3 /*charge*/, &h,
|
||||
Cache::Priority::LOW));
|
||||
// Try to insert more
|
||||
Status s = this->Insert('a');
|
||||
Status s = Insert('a');
|
||||
if (strict_capacity_limit) {
|
||||
EXPECT_TRUE(s.IsMemoryLimit());
|
||||
} else {
|
||||
@@ -527,22 +517,22 @@ TYPED_TEST(ClockCacheTest, Limits) {
|
||||
}
|
||||
// Release entry filling capacity.
|
||||
// Cover useful = false case.
|
||||
shard.Release(h, false /*useful*/, false /*erase_if_last_ref*/);
|
||||
shard_->Release(h, false /*useful*/, false /*erase_if_last_ref*/);
|
||||
}
|
||||
|
||||
// Insert more than table size can handle to exceed occupancy limit.
|
||||
// (Cleverly using mostly zero-charge entries, but some non-zero to
|
||||
// verify usage tracking on detached entries.)
|
||||
{
|
||||
size_t n = shard.GetTableAddressCount() + 1;
|
||||
size_t n = shard_->GetTableAddressCount() + 1;
|
||||
std::unique_ptr<HandleImpl* []> ha { new HandleImpl* [n] {} };
|
||||
Status s;
|
||||
for (size_t i = 0; i < n && s.ok(); ++i) {
|
||||
hkey[1] = i;
|
||||
s = shard.Insert(this->TestKey(hkey), hkey, nullptr /*value*/,
|
||||
&kNoopCacheItemHelper,
|
||||
(i + kCapacity < n) ? 0 : 1 /*charge*/, &ha[i],
|
||||
Cache::Priority::LOW);
|
||||
s = shard_->Insert(TestKey(hkey), hkey, nullptr /*value*/,
|
||||
&kNoopCacheItemHelper,
|
||||
(i + kCapacity < n) ? 0 : 1 /*charge*/, &ha[i],
|
||||
Cache::Priority::LOW);
|
||||
if (i == 0) {
|
||||
EXPECT_OK(s);
|
||||
}
|
||||
@@ -553,7 +543,7 @@ TYPED_TEST(ClockCacheTest, Limits) {
|
||||
EXPECT_OK(s);
|
||||
}
|
||||
// Same result if not keeping a reference
|
||||
s = this->Insert('a');
|
||||
s = Insert('a');
|
||||
if (strict_capacity_limit) {
|
||||
EXPECT_TRUE(s.IsMemoryLimit());
|
||||
} else {
|
||||
@@ -561,123 +551,122 @@ TYPED_TEST(ClockCacheTest, Limits) {
|
||||
}
|
||||
|
||||
// Regardless, we didn't allow table to actually get full
|
||||
EXPECT_LT(shard.GetOccupancyCount(), shard.GetTableAddressCount());
|
||||
EXPECT_LT(shard_->GetOccupancyCount(), shard_->GetTableAddressCount());
|
||||
|
||||
// Release handles
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
if (ha[i]) {
|
||||
shard.Release(ha[i]);
|
||||
shard_->Release(ha[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TYPED_TEST(ClockCacheTest, ClockEvictionTest) {
|
||||
TEST_F(ClockCacheTest, ClockEvictionTest) {
|
||||
for (bool strict_capacity_limit : {false, true}) {
|
||||
SCOPED_TRACE("strict_capacity_limit = " +
|
||||
std::to_string(strict_capacity_limit));
|
||||
|
||||
this->NewShard(6, strict_capacity_limit);
|
||||
auto& shard = *this->shard_;
|
||||
EXPECT_OK(this->Insert('a', Cache::Priority::BOTTOM));
|
||||
EXPECT_OK(this->Insert('b', Cache::Priority::LOW));
|
||||
EXPECT_OK(this->Insert('c', Cache::Priority::HIGH));
|
||||
EXPECT_OK(this->Insert('d', Cache::Priority::BOTTOM));
|
||||
EXPECT_OK(this->Insert('e', Cache::Priority::LOW));
|
||||
EXPECT_OK(this->Insert('f', Cache::Priority::HIGH));
|
||||
NewShard(6, strict_capacity_limit);
|
||||
EXPECT_OK(Insert('a', Cache::Priority::BOTTOM));
|
||||
EXPECT_OK(Insert('b', Cache::Priority::LOW));
|
||||
EXPECT_OK(Insert('c', Cache::Priority::HIGH));
|
||||
EXPECT_OK(Insert('d', Cache::Priority::BOTTOM));
|
||||
EXPECT_OK(Insert('e', Cache::Priority::LOW));
|
||||
EXPECT_OK(Insert('f', Cache::Priority::HIGH));
|
||||
|
||||
EXPECT_TRUE(this->Lookup('a', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('b', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('c', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('d', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('e', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('f', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('a', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('b', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('c', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('d', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('e', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('f', /*use*/ false));
|
||||
|
||||
// Ensure bottom are evicted first, even if new entries are low
|
||||
EXPECT_OK(this->Insert('g', Cache::Priority::LOW));
|
||||
EXPECT_OK(this->Insert('h', Cache::Priority::LOW));
|
||||
EXPECT_OK(Insert('g', Cache::Priority::LOW));
|
||||
EXPECT_OK(Insert('h', Cache::Priority::LOW));
|
||||
|
||||
EXPECT_FALSE(this->Lookup('a', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('b', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('c', /*use*/ false));
|
||||
EXPECT_FALSE(this->Lookup('d', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('e', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('f', /*use*/ false));
|
||||
EXPECT_FALSE(Lookup('a', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('b', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('c', /*use*/ false));
|
||||
EXPECT_FALSE(Lookup('d', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('e', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('f', /*use*/ false));
|
||||
// Mark g & h useful
|
||||
EXPECT_TRUE(this->Lookup('g', /*use*/ true));
|
||||
EXPECT_TRUE(this->Lookup('h', /*use*/ true));
|
||||
EXPECT_TRUE(Lookup('g', /*use*/ true));
|
||||
EXPECT_TRUE(Lookup('h', /*use*/ true));
|
||||
|
||||
// Then old LOW entries
|
||||
EXPECT_OK(this->Insert('i', Cache::Priority::LOW));
|
||||
EXPECT_OK(this->Insert('j', Cache::Priority::LOW));
|
||||
EXPECT_OK(Insert('i', Cache::Priority::LOW));
|
||||
EXPECT_OK(Insert('j', Cache::Priority::LOW));
|
||||
|
||||
EXPECT_FALSE(this->Lookup('b', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('c', /*use*/ false));
|
||||
EXPECT_FALSE(this->Lookup('e', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('f', /*use*/ false));
|
||||
EXPECT_FALSE(Lookup('b', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('c', /*use*/ false));
|
||||
EXPECT_FALSE(Lookup('e', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('f', /*use*/ false));
|
||||
// Mark g & h useful once again
|
||||
EXPECT_TRUE(this->Lookup('g', /*use*/ true));
|
||||
EXPECT_TRUE(this->Lookup('h', /*use*/ true));
|
||||
EXPECT_TRUE(this->Lookup('i', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('j', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('g', /*use*/ true));
|
||||
EXPECT_TRUE(Lookup('h', /*use*/ true));
|
||||
EXPECT_TRUE(Lookup('i', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('j', /*use*/ false));
|
||||
|
||||
// Then old HIGH entries
|
||||
EXPECT_OK(this->Insert('k', Cache::Priority::LOW));
|
||||
EXPECT_OK(this->Insert('l', Cache::Priority::LOW));
|
||||
EXPECT_OK(Insert('k', Cache::Priority::LOW));
|
||||
EXPECT_OK(Insert('l', Cache::Priority::LOW));
|
||||
|
||||
EXPECT_FALSE(this->Lookup('c', /*use*/ false));
|
||||
EXPECT_FALSE(this->Lookup('f', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('g', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('h', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('i', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('j', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('k', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('l', /*use*/ false));
|
||||
EXPECT_FALSE(Lookup('c', /*use*/ false));
|
||||
EXPECT_FALSE(Lookup('f', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('g', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('h', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('i', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('j', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('k', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('l', /*use*/ false));
|
||||
|
||||
// Then the (roughly) least recently useful
|
||||
EXPECT_OK(this->Insert('m', Cache::Priority::HIGH));
|
||||
EXPECT_OK(this->Insert('n', Cache::Priority::HIGH));
|
||||
EXPECT_OK(Insert('m', Cache::Priority::HIGH));
|
||||
EXPECT_OK(Insert('n', Cache::Priority::HIGH));
|
||||
|
||||
EXPECT_TRUE(this->Lookup('g', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('h', /*use*/ false));
|
||||
EXPECT_FALSE(this->Lookup('i', /*use*/ false));
|
||||
EXPECT_FALSE(this->Lookup('j', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('k', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('l', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('g', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('h', /*use*/ false));
|
||||
EXPECT_FALSE(Lookup('i', /*use*/ false));
|
||||
EXPECT_FALSE(Lookup('j', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('k', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('l', /*use*/ false));
|
||||
|
||||
// Now try changing capacity down
|
||||
shard.SetCapacity(4);
|
||||
shard_->SetCapacity(4);
|
||||
// Insert to ensure evictions happen
|
||||
EXPECT_OK(this->Insert('o', Cache::Priority::LOW));
|
||||
EXPECT_OK(this->Insert('p', Cache::Priority::LOW));
|
||||
EXPECT_OK(Insert('o', Cache::Priority::LOW));
|
||||
EXPECT_OK(Insert('p', Cache::Priority::LOW));
|
||||
|
||||
EXPECT_FALSE(this->Lookup('g', /*use*/ false));
|
||||
EXPECT_FALSE(this->Lookup('h', /*use*/ false));
|
||||
EXPECT_FALSE(this->Lookup('k', /*use*/ false));
|
||||
EXPECT_FALSE(this->Lookup('l', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('m', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('n', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('o', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('p', /*use*/ false));
|
||||
EXPECT_FALSE(Lookup('g', /*use*/ false));
|
||||
EXPECT_FALSE(Lookup('h', /*use*/ false));
|
||||
EXPECT_FALSE(Lookup('k', /*use*/ false));
|
||||
EXPECT_FALSE(Lookup('l', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('m', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('n', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('o', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('p', /*use*/ false));
|
||||
|
||||
// Now try changing capacity up
|
||||
EXPECT_TRUE(this->Lookup('m', /*use*/ true));
|
||||
EXPECT_TRUE(this->Lookup('n', /*use*/ true));
|
||||
shard.SetCapacity(6);
|
||||
EXPECT_OK(this->Insert('q', Cache::Priority::HIGH));
|
||||
EXPECT_OK(this->Insert('r', Cache::Priority::HIGH));
|
||||
EXPECT_OK(this->Insert('s', Cache::Priority::HIGH));
|
||||
EXPECT_OK(this->Insert('t', Cache::Priority::HIGH));
|
||||
EXPECT_TRUE(Lookup('m', /*use*/ true));
|
||||
EXPECT_TRUE(Lookup('n', /*use*/ true));
|
||||
shard_->SetCapacity(6);
|
||||
EXPECT_OK(Insert('q', Cache::Priority::HIGH));
|
||||
EXPECT_OK(Insert('r', Cache::Priority::HIGH));
|
||||
EXPECT_OK(Insert('s', Cache::Priority::HIGH));
|
||||
EXPECT_OK(Insert('t', Cache::Priority::HIGH));
|
||||
|
||||
EXPECT_FALSE(this->Lookup('o', /*use*/ false));
|
||||
EXPECT_FALSE(this->Lookup('p', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('m', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('n', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('q', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('r', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('s', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('t', /*use*/ false));
|
||||
EXPECT_FALSE(Lookup('o', /*use*/ false));
|
||||
EXPECT_FALSE(Lookup('p', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('m', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('n', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('q', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('r', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('s', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('t', /*use*/ false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -693,178 +682,113 @@ const Cache::CacheItemHelper kDeleteCounterHelper{
|
||||
} // namespace
|
||||
|
||||
// Testing calls to CorrectNearOverflow in Release
|
||||
TYPED_TEST(ClockCacheTest, ClockCounterOverflowTest) {
|
||||
this->NewShard(6, /*strict_capacity_limit*/ false);
|
||||
auto& shard = *this->shard_;
|
||||
using HandleImpl = typename ClockCacheTest<TypeParam>::Shard::HandleImpl;
|
||||
|
||||
TEST_F(ClockCacheTest, ClockCounterOverflowTest) {
|
||||
NewShard(6, /*strict_capacity_limit*/ false);
|
||||
HandleImpl* h;
|
||||
DeleteCounter val;
|
||||
UniqueId64x2 hkey = this->TestHashedKey('x');
|
||||
ASSERT_OK(shard.Insert(this->TestKey(hkey), hkey, &val, &kDeleteCounterHelper,
|
||||
1, &h, Cache::Priority::HIGH));
|
||||
UniqueId64x2 hkey = TestHashedKey('x');
|
||||
ASSERT_OK(shard_->Insert(TestKey(hkey), hkey, &val, &kDeleteCounterHelper, 1,
|
||||
&h, Cache::Priority::HIGH));
|
||||
|
||||
// Some large number outstanding
|
||||
shard.TEST_RefN(h, 123456789);
|
||||
shard_->TEST_RefN(h, 123456789);
|
||||
// Simulate many lookup/ref + release, plenty to overflow counters
|
||||
for (int i = 0; i < 10000; ++i) {
|
||||
shard.TEST_RefN(h, 1234567);
|
||||
shard.TEST_ReleaseN(h, 1234567);
|
||||
shard_->TEST_RefN(h, 1234567);
|
||||
shard_->TEST_ReleaseN(h, 1234567);
|
||||
}
|
||||
// Mark it invisible (to reach a different CorrectNearOverflow() in Release)
|
||||
shard.Erase(this->TestKey(hkey), hkey);
|
||||
shard_->Erase(TestKey(hkey), hkey);
|
||||
// Simulate many more lookup/ref + release (one-by-one would be too
|
||||
// expensive for unit test)
|
||||
for (int i = 0; i < 10000; ++i) {
|
||||
shard.TEST_RefN(h, 1234567);
|
||||
shard.TEST_ReleaseN(h, 1234567);
|
||||
shard_->TEST_RefN(h, 1234567);
|
||||
shard_->TEST_ReleaseN(h, 1234567);
|
||||
}
|
||||
// Free all but last 1
|
||||
shard.TEST_ReleaseN(h, 123456789);
|
||||
shard_->TEST_ReleaseN(h, 123456789);
|
||||
// Still alive
|
||||
ASSERT_EQ(val.deleted, 0);
|
||||
// Free last ref, which will finalize erasure
|
||||
shard.Release(h);
|
||||
shard_->Release(h);
|
||||
// Deleted
|
||||
ASSERT_EQ(val.deleted, 1);
|
||||
}
|
||||
|
||||
TYPED_TEST(ClockCacheTest, ClockTableFull) {
|
||||
// Force clock cache table to fill up (not usually allowed) in order
|
||||
// to test full probe sequence that is theoretically possible due to
|
||||
// parallel operations
|
||||
this->NewShard(6, /*strict_capacity_limit*/ false);
|
||||
auto& shard = *this->shard_;
|
||||
using HandleImpl = typename ClockCacheTest<TypeParam>::Shard::HandleImpl;
|
||||
|
||||
size_t size = shard.GetTableAddressCount();
|
||||
ASSERT_LE(size + 3, 256); // for using char keys
|
||||
// Modify occupancy and capacity limits to attempt insert on full
|
||||
shard.TEST_MutableOccupancyLimit() = size + 100;
|
||||
shard.SetCapacity(size + 100);
|
||||
|
||||
DeleteCounter val;
|
||||
std::vector<HandleImpl*> handles;
|
||||
// NOTE: the three extra insertions should create standalone entries
|
||||
for (size_t i = 0; i < size + 3; ++i) {
|
||||
UniqueId64x2 hkey = this->TestHashedKey(static_cast<char>(i));
|
||||
ASSERT_OK(shard.Insert(this->TestKey(hkey), hkey, &val,
|
||||
&kDeleteCounterHelper, 1, &handles.emplace_back(),
|
||||
Cache::Priority::HIGH));
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < size + 3; ++i) {
|
||||
UniqueId64x2 hkey = this->TestHashedKey(static_cast<char>(i));
|
||||
HandleImpl* h = shard.Lookup(this->TestKey(hkey), hkey);
|
||||
if (i < size) {
|
||||
ASSERT_NE(h, nullptr);
|
||||
shard.Release(h);
|
||||
} else {
|
||||
// Standalone entries not visible by lookup
|
||||
ASSERT_EQ(h, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < size + 3; ++i) {
|
||||
ASSERT_NE(handles[i], nullptr);
|
||||
shard.Release(handles[i]);
|
||||
if (i < size) {
|
||||
// Everything still in cache
|
||||
ASSERT_EQ(val.deleted, 0);
|
||||
} else {
|
||||
// Standalone entries freed on release
|
||||
ASSERT_EQ(val.deleted, i + 1 - size);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = size + 3; i > 0; --i) {
|
||||
UniqueId64x2 hkey = this->TestHashedKey(static_cast<char>(i - 1));
|
||||
shard.Erase(this->TestKey(hkey), hkey);
|
||||
if (i - 1 > size) {
|
||||
ASSERT_EQ(val.deleted, 3);
|
||||
} else {
|
||||
ASSERT_EQ(val.deleted, 3 + size - (i - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This test is mostly to exercise some corner case logic, by forcing two
|
||||
// keys to have the same hash, and more
|
||||
TYPED_TEST(ClockCacheTest, CollidingInsertEraseTest) {
|
||||
this->NewShard(6, /*strict_capacity_limit*/ false);
|
||||
auto& shard = *this->shard_;
|
||||
using HandleImpl = typename ClockCacheTest<TypeParam>::Shard::HandleImpl;
|
||||
|
||||
TEST_F(ClockCacheTest, CollidingInsertEraseTest) {
|
||||
NewShard(6, /*strict_capacity_limit*/ false);
|
||||
DeleteCounter val;
|
||||
UniqueId64x2 hkey1 = this->TestHashedKey('x');
|
||||
Slice key1 = this->TestKey(hkey1);
|
||||
UniqueId64x2 hkey2 = this->TestHashedKey('y');
|
||||
Slice key2 = this->TestKey(hkey2);
|
||||
UniqueId64x2 hkey3 = this->TestHashedKey('z');
|
||||
Slice key3 = this->TestKey(hkey3);
|
||||
UniqueId64x2 hkey1 = TestHashedKey('x');
|
||||
Slice key1 = TestKey(hkey1);
|
||||
UniqueId64x2 hkey2 = TestHashedKey('y');
|
||||
Slice key2 = TestKey(hkey2);
|
||||
UniqueId64x2 hkey3 = TestHashedKey('z');
|
||||
Slice key3 = TestKey(hkey3);
|
||||
HandleImpl* h1;
|
||||
ASSERT_OK(shard.Insert(key1, hkey1, &val, &kDeleteCounterHelper, 1, &h1,
|
||||
Cache::Priority::HIGH));
|
||||
ASSERT_OK(shard_->Insert(key1, hkey1, &val, &kDeleteCounterHelper, 1, &h1,
|
||||
Cache::Priority::HIGH));
|
||||
HandleImpl* h2;
|
||||
ASSERT_OK(shard.Insert(key2, hkey2, &val, &kDeleteCounterHelper, 1, &h2,
|
||||
Cache::Priority::HIGH));
|
||||
ASSERT_OK(shard_->Insert(key2, hkey2, &val, &kDeleteCounterHelper, 1, &h2,
|
||||
Cache::Priority::HIGH));
|
||||
HandleImpl* h3;
|
||||
ASSERT_OK(shard.Insert(key3, hkey3, &val, &kDeleteCounterHelper, 1, &h3,
|
||||
Cache::Priority::HIGH));
|
||||
ASSERT_OK(shard_->Insert(key3, hkey3, &val, &kDeleteCounterHelper, 1, &h3,
|
||||
Cache::Priority::HIGH));
|
||||
|
||||
// Can repeatedly lookup+release despite the hash collision
|
||||
HandleImpl* tmp_h;
|
||||
for (bool erase_if_last_ref : {true, false}) { // but not last ref
|
||||
tmp_h = shard.Lookup(key1, hkey1);
|
||||
tmp_h = shard_->Lookup(key1, hkey1);
|
||||
ASSERT_EQ(h1, tmp_h);
|
||||
ASSERT_FALSE(shard.Release(tmp_h, erase_if_last_ref));
|
||||
ASSERT_FALSE(shard_->Release(tmp_h, erase_if_last_ref));
|
||||
|
||||
tmp_h = shard.Lookup(key2, hkey2);
|
||||
tmp_h = shard_->Lookup(key2, hkey2);
|
||||
ASSERT_EQ(h2, tmp_h);
|
||||
ASSERT_FALSE(shard.Release(tmp_h, erase_if_last_ref));
|
||||
ASSERT_FALSE(shard_->Release(tmp_h, erase_if_last_ref));
|
||||
|
||||
tmp_h = shard.Lookup(key3, hkey3);
|
||||
tmp_h = shard_->Lookup(key3, hkey3);
|
||||
ASSERT_EQ(h3, tmp_h);
|
||||
ASSERT_FALSE(shard.Release(tmp_h, erase_if_last_ref));
|
||||
ASSERT_FALSE(shard_->Release(tmp_h, erase_if_last_ref));
|
||||
}
|
||||
|
||||
// Make h1 invisible
|
||||
shard.Erase(key1, hkey1);
|
||||
shard_->Erase(key1, hkey1);
|
||||
// Redundant erase
|
||||
shard.Erase(key1, hkey1);
|
||||
shard_->Erase(key1, hkey1);
|
||||
|
||||
// All still alive
|
||||
ASSERT_EQ(val.deleted, 0);
|
||||
|
||||
// Invisible to Lookup
|
||||
tmp_h = shard.Lookup(key1, hkey1);
|
||||
tmp_h = shard_->Lookup(key1, hkey1);
|
||||
ASSERT_EQ(nullptr, tmp_h);
|
||||
|
||||
// Can still find h2, h3
|
||||
for (bool erase_if_last_ref : {true, false}) { // but not last ref
|
||||
tmp_h = shard.Lookup(key2, hkey2);
|
||||
tmp_h = shard_->Lookup(key2, hkey2);
|
||||
ASSERT_EQ(h2, tmp_h);
|
||||
ASSERT_FALSE(shard.Release(tmp_h, erase_if_last_ref));
|
||||
ASSERT_FALSE(shard_->Release(tmp_h, erase_if_last_ref));
|
||||
|
||||
tmp_h = shard.Lookup(key3, hkey3);
|
||||
tmp_h = shard_->Lookup(key3, hkey3);
|
||||
ASSERT_EQ(h3, tmp_h);
|
||||
ASSERT_FALSE(shard.Release(tmp_h, erase_if_last_ref));
|
||||
ASSERT_FALSE(shard_->Release(tmp_h, erase_if_last_ref));
|
||||
}
|
||||
|
||||
// Also Insert with invisible entry there
|
||||
ASSERT_OK(shard.Insert(key1, hkey1, &val, &kDeleteCounterHelper, 1, nullptr,
|
||||
Cache::Priority::HIGH));
|
||||
tmp_h = shard.Lookup(key1, hkey1);
|
||||
ASSERT_OK(shard_->Insert(key1, hkey1, &val, &kDeleteCounterHelper, 1, nullptr,
|
||||
Cache::Priority::HIGH));
|
||||
tmp_h = shard_->Lookup(key1, hkey1);
|
||||
// Found but distinct handle
|
||||
ASSERT_NE(nullptr, tmp_h);
|
||||
ASSERT_NE(h1, tmp_h);
|
||||
ASSERT_TRUE(shard.Release(tmp_h, /*erase_if_last_ref*/ true));
|
||||
ASSERT_TRUE(shard_->Release(tmp_h, /*erase_if_last_ref*/ true));
|
||||
|
||||
// tmp_h deleted
|
||||
ASSERT_EQ(val.deleted--, 1);
|
||||
|
||||
// Release last ref on h1 (already invisible)
|
||||
ASSERT_TRUE(shard.Release(h1, /*erase_if_last_ref*/ false));
|
||||
ASSERT_TRUE(shard_->Release(h1, /*erase_if_last_ref*/ false));
|
||||
|
||||
// h1 deleted
|
||||
ASSERT_EQ(val.deleted--, 1);
|
||||
@@ -872,57 +796,57 @@ TYPED_TEST(ClockCacheTest, CollidingInsertEraseTest) {
|
||||
|
||||
// Can still find h2, h3
|
||||
for (bool erase_if_last_ref : {true, false}) { // but not last ref
|
||||
tmp_h = shard.Lookup(key2, hkey2);
|
||||
tmp_h = shard_->Lookup(key2, hkey2);
|
||||
ASSERT_EQ(h2, tmp_h);
|
||||
ASSERT_FALSE(shard.Release(tmp_h, erase_if_last_ref));
|
||||
ASSERT_FALSE(shard_->Release(tmp_h, erase_if_last_ref));
|
||||
|
||||
tmp_h = shard.Lookup(key3, hkey3);
|
||||
tmp_h = shard_->Lookup(key3, hkey3);
|
||||
ASSERT_EQ(h3, tmp_h);
|
||||
ASSERT_FALSE(shard.Release(tmp_h, erase_if_last_ref));
|
||||
ASSERT_FALSE(shard_->Release(tmp_h, erase_if_last_ref));
|
||||
}
|
||||
|
||||
// Release last ref on h2
|
||||
ASSERT_FALSE(shard.Release(h2, /*erase_if_last_ref*/ false));
|
||||
ASSERT_FALSE(shard_->Release(h2, /*erase_if_last_ref*/ false));
|
||||
|
||||
// h2 still not deleted (unreferenced in cache)
|
||||
ASSERT_EQ(val.deleted, 0);
|
||||
|
||||
// Can still find it
|
||||
tmp_h = shard.Lookup(key2, hkey2);
|
||||
tmp_h = shard_->Lookup(key2, hkey2);
|
||||
ASSERT_EQ(h2, tmp_h);
|
||||
|
||||
// Release last ref on h2, with erase
|
||||
ASSERT_TRUE(shard.Release(h2, /*erase_if_last_ref*/ true));
|
||||
ASSERT_TRUE(shard_->Release(h2, /*erase_if_last_ref*/ true));
|
||||
|
||||
// h2 deleted
|
||||
ASSERT_EQ(val.deleted--, 1);
|
||||
tmp_h = shard.Lookup(key2, hkey2);
|
||||
tmp_h = shard_->Lookup(key2, hkey2);
|
||||
ASSERT_EQ(nullptr, tmp_h);
|
||||
|
||||
// Can still find h3
|
||||
for (bool erase_if_last_ref : {true, false}) { // but not last ref
|
||||
tmp_h = shard.Lookup(key3, hkey3);
|
||||
tmp_h = shard_->Lookup(key3, hkey3);
|
||||
ASSERT_EQ(h3, tmp_h);
|
||||
ASSERT_FALSE(shard.Release(tmp_h, erase_if_last_ref));
|
||||
ASSERT_FALSE(shard_->Release(tmp_h, erase_if_last_ref));
|
||||
}
|
||||
|
||||
// Release last ref on h3, without erase
|
||||
ASSERT_FALSE(shard.Release(h3, /*erase_if_last_ref*/ false));
|
||||
ASSERT_FALSE(shard_->Release(h3, /*erase_if_last_ref*/ false));
|
||||
|
||||
// h3 still not deleted (unreferenced in cache)
|
||||
ASSERT_EQ(val.deleted, 0);
|
||||
|
||||
// Explicit erase
|
||||
shard.Erase(key3, hkey3);
|
||||
shard_->Erase(key3, hkey3);
|
||||
|
||||
// h3 deleted
|
||||
ASSERT_EQ(val.deleted--, 1);
|
||||
tmp_h = shard.Lookup(key3, hkey3);
|
||||
tmp_h = shard_->Lookup(key3, hkey3);
|
||||
ASSERT_EQ(nullptr, tmp_h);
|
||||
}
|
||||
|
||||
// This uses the public API to effectively test CalcHashBits etc.
|
||||
TYPED_TEST(ClockCacheTest, TableSizesTest) {
|
||||
TEST_F(ClockCacheTest, TableSizesTest) {
|
||||
for (size_t est_val_size : {1U, 5U, 123U, 2345U, 345678U}) {
|
||||
SCOPED_TRACE("est_val_size = " + std::to_string(est_val_size));
|
||||
for (double est_count : {1.1, 2.2, 511.9, 512.1, 2345.0}) {
|
||||
@@ -935,10 +859,8 @@ TYPED_TEST(ClockCacheTest, TableSizesTest) {
|
||||
/*memory_allocator*/ nullptr, kDontChargeCacheMetadata)
|
||||
.MakeSharedCache();
|
||||
// Table sizes are currently only powers of two
|
||||
EXPECT_GE(cache->GetTableAddressCount(),
|
||||
est_count / FixedHyperClockTable::kLoadFactor);
|
||||
EXPECT_LE(cache->GetTableAddressCount(),
|
||||
est_count / FixedHyperClockTable::kLoadFactor * 2.0);
|
||||
EXPECT_GE(cache->GetTableAddressCount(), est_count / kLoadFactor);
|
||||
EXPECT_LE(cache->GetTableAddressCount(), est_count / kLoadFactor * 2.0);
|
||||
EXPECT_EQ(cache->GetUsage(), 0);
|
||||
|
||||
// kFullChargeMetaData
|
||||
@@ -955,10 +877,9 @@ TYPED_TEST(ClockCacheTest, TableSizesTest) {
|
||||
double est_count_after_meta =
|
||||
(capacity - cache->GetUsage()) * 1.0 / est_val_size;
|
||||
EXPECT_GE(cache->GetTableAddressCount(),
|
||||
est_count_after_meta / FixedHyperClockTable::kLoadFactor);
|
||||
EXPECT_LE(
|
||||
cache->GetTableAddressCount(),
|
||||
est_count_after_meta / FixedHyperClockTable::kLoadFactor * 2.0);
|
||||
est_count_after_meta / kLoadFactor);
|
||||
EXPECT_LE(cache->GetTableAddressCount(),
|
||||
est_count_after_meta / kLoadFactor * 2.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -996,8 +917,7 @@ class TestSecondaryCache : public SecondaryCache {
|
||||
void ResetInjectFailure() { inject_failure_ = false; }
|
||||
|
||||
Status Insert(const Slice& key, Cache::ObjectPtr value,
|
||||
const Cache::CacheItemHelper* helper,
|
||||
bool /*force_insert*/) override {
|
||||
const Cache::CacheItemHelper* helper) override {
|
||||
if (inject_failure_) {
|
||||
return Status::Corruption("Insertion Data Corrupted");
|
||||
}
|
||||
@@ -1447,7 +1367,7 @@ TEST_P(BasicSecondaryCacheTest, FullCapacityTest) {
|
||||
k2.AsSlice(),
|
||||
GetHelper(CacheEntryRole::kDataBlock, /*secondary_compatible=*/false),
|
||||
/*context*/ this, Cache::Priority::LOW);
|
||||
if (strict_capacity_limit || IsHyperClock()) {
|
||||
if (strict_capacity_limit || GetParam() == kHyperClock) {
|
||||
ASSERT_NE(handle2, nullptr);
|
||||
cache->Release(handle2);
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
|
||||
@@ -1472,12 +1392,12 @@ TEST_P(BasicSecondaryCacheTest, FullCapacityTest) {
|
||||
// CORRECTION: this is not quite right. block_1 can be inserted into the block
|
||||
// cache because strict_capacity_limit=false, but it is removed from the cache
|
||||
// in Release() because of being over-capacity, without demoting to secondary
|
||||
// cache. FixedHyperClockCache doesn't check capacity on release (for
|
||||
// efficiency) so can demote the over-capacity item to secondary cache. Also, we
|
||||
// intend to add support for demotion in Release, but that currently causes too
|
||||
// much unit test churn.
|
||||
// cache. HyperClockCache doesn't check capacity on release (for efficiency)
|
||||
// so can demote the over-capacity item to secondary cache. Also, we intend to
|
||||
// add support for demotion in Release, but that currently causes too much
|
||||
// unit test churn.
|
||||
TEST_P(DBSecondaryCacheTest, TestSecondaryCacheCorrectness1) {
|
||||
if (IsHyperClock()) {
|
||||
if (GetParam() == kHyperClock) {
|
||||
// See CORRECTION above
|
||||
ROCKSDB_GTEST_BYPASS("Test depends on LRUCache-specific behaviors");
|
||||
return;
|
||||
@@ -1575,7 +1495,7 @@ TEST_P(DBSecondaryCacheTest, TestSecondaryCacheCorrectness1) {
|
||||
// insert and cache block_1 in the block cache (this is the different place
|
||||
// from TestSecondaryCacheCorrectness1)
|
||||
TEST_P(DBSecondaryCacheTest, TestSecondaryCacheCorrectness2) {
|
||||
if (IsHyperClock()) {
|
||||
if (GetParam() == kHyperClock) {
|
||||
ROCKSDB_GTEST_BYPASS("Test depends on LRUCache-specific behaviors");
|
||||
return;
|
||||
}
|
||||
@@ -1763,7 +1683,7 @@ TEST_P(DBSecondaryCacheTest, SecondaryCacheIntensiveTesting) {
|
||||
// if we try to insert block_1 to the block cache, it will always fails. Only
|
||||
// block_2 will be successfully inserted into the block cache.
|
||||
TEST_P(DBSecondaryCacheTest, SecondaryCacheFailureTest) {
|
||||
if (IsHyperClock()) {
|
||||
if (GetParam() == kHyperClock) {
|
||||
ROCKSDB_GTEST_BYPASS("Test depends on LRUCache-specific behaviors");
|
||||
return;
|
||||
}
|
||||
@@ -1873,7 +1793,7 @@ TEST_P(BasicSecondaryCacheTest, BasicWaitAllTest) {
|
||||
str.length()));
|
||||
}
|
||||
// Force all entries to be evicted to the secondary cache
|
||||
if (IsHyperClock()) {
|
||||
if (GetParam() == kHyperClock) {
|
||||
// HCC doesn't respond immediately to SetCapacity
|
||||
for (int i = 9000; i < 9030; ++i) {
|
||||
ASSERT_OK(cache->Insert(ock.WithOffset(i).AsSlice(), nullptr,
|
||||
@@ -1928,7 +1848,7 @@ TEST_P(BasicSecondaryCacheTest, BasicWaitAllTest) {
|
||||
// a sync point callback in TestSecondaryCache::Lookup. We then control the
|
||||
// lookup result by setting the ResultMap.
|
||||
TEST_P(DBSecondaryCacheTest, TestSecondaryCacheMultiGet) {
|
||||
if (IsHyperClock()) {
|
||||
if (GetParam() == kHyperClock) {
|
||||
ROCKSDB_GTEST_BYPASS("Test depends on LRUCache-specific behaviors");
|
||||
return;
|
||||
}
|
||||
@@ -2429,7 +2349,7 @@ TEST_P(DBSecondaryCacheTest, TestSecondaryCacheOptionBasic) {
|
||||
// with new options, which set the lowest_used_cache_tier to
|
||||
// kNonVolatileBlockTier. So secondary cache will be used.
|
||||
TEST_P(DBSecondaryCacheTest, TestSecondaryCacheOptionChange) {
|
||||
if (IsHyperClock()) {
|
||||
if (GetParam() == kHyperClock) {
|
||||
ROCKSDB_GTEST_BYPASS("Test depends on LRUCache-specific behaviors");
|
||||
return;
|
||||
}
|
||||
@@ -2524,7 +2444,7 @@ TEST_P(DBSecondaryCacheTest, TestSecondaryCacheOptionChange) {
|
||||
// Two DB test. We create 2 DBs sharing the same block cache and secondary
|
||||
// cache. We diable the secondary cache option for DB2.
|
||||
TEST_P(DBSecondaryCacheTest, TestSecondaryCacheOptionTwoDB) {
|
||||
if (IsHyperClock()) {
|
||||
if (GetParam() == kHyperClock) {
|
||||
ROCKSDB_GTEST_BYPASS("Test depends on LRUCache-specific behaviors");
|
||||
return;
|
||||
}
|
||||
|
||||
Vendored
+1
-2
@@ -38,8 +38,7 @@ Status SecondaryCache::InsertSaved(const Slice& key, const Slice& saved) {
|
||||
CacheEntryRole::kMisc, &NoopDelete, &SliceSize,
|
||||
&SliceSaveTo, &FailCreate, &helper_no_secondary};
|
||||
// NOTE: depends on Insert() being synchronous, not keeping pointer `&saved`
|
||||
return Insert(key, const_cast<Slice*>(&saved), &helper,
|
||||
/*force_insert=*/true);
|
||||
return Insert(key, const_cast<Slice*>(&saved), &helper);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+8
-21
@@ -73,16 +73,13 @@ Cache::ObjectPtr const kDummyObj = const_cast<Dummy*>(&kDummy);
|
||||
//
|
||||
CacheWithSecondaryAdapter::CacheWithSecondaryAdapter(
|
||||
std::shared_ptr<Cache> target,
|
||||
std::shared_ptr<SecondaryCache> secondary_cache,
|
||||
TieredAdmissionPolicy adm_policy, bool distribute_cache_res)
|
||||
std::shared_ptr<SecondaryCache> secondary_cache, bool distribute_cache_res)
|
||||
: CacheWrapper(std::move(target)),
|
||||
secondary_cache_(std::move(secondary_cache)),
|
||||
adm_policy_(adm_policy),
|
||||
distribute_cache_res_(distribute_cache_res) {
|
||||
target_->SetEvictionCallback(
|
||||
[this](const Slice& key, Handle* handle, bool was_hit) {
|
||||
return EvictionHandler(key, handle, was_hit);
|
||||
});
|
||||
target_->SetEvictionCallback([this](const Slice& key, Handle* handle) {
|
||||
return EvictionHandler(key, handle);
|
||||
});
|
||||
if (distribute_cache_res_) {
|
||||
size_t sec_capacity = 0;
|
||||
pri_cache_res_ = std::make_shared<ConcurrentCacheReservationManager>(
|
||||
@@ -117,18 +114,14 @@ CacheWithSecondaryAdapter::~CacheWithSecondaryAdapter() {
|
||||
}
|
||||
|
||||
bool CacheWithSecondaryAdapter::EvictionHandler(const Slice& key,
|
||||
Handle* handle, bool was_hit) {
|
||||
Handle* handle) {
|
||||
auto helper = GetCacheItemHelper(handle);
|
||||
if (helper->IsSecondaryCacheCompatible()) {
|
||||
auto obj = target_->Value(handle);
|
||||
// Ignore dummy entry
|
||||
if (obj != kDummyObj) {
|
||||
bool hit = false;
|
||||
if (adm_policy_ == TieredAdmissionPolicy::kAdmPolicyAllowCacheHits) {
|
||||
hit = was_hit;
|
||||
}
|
||||
// Spill into secondary cache.
|
||||
secondary_cache_->Insert(key, obj, helper, hit).PermitUncheckedError();
|
||||
secondary_cache_->Insert(key, obj, helper).PermitUncheckedError();
|
||||
}
|
||||
}
|
||||
// Never takes ownership of obj
|
||||
@@ -383,7 +376,7 @@ void CacheWithSecondaryAdapter::WaitAll(AsyncLookupHandle* async_handles,
|
||||
for (AsyncLookupHandle* cur : my_pending) {
|
||||
my_secondary_handles.push_back(cur->pending_handle);
|
||||
}
|
||||
secondary_cache_->WaitAll(std::move(my_secondary_handles));
|
||||
secondary_cache_->WaitAll(my_secondary_handles);
|
||||
}
|
||||
|
||||
// Process results
|
||||
@@ -417,10 +410,6 @@ std::shared_ptr<Cache> NewTieredVolatileCache(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (opts.adm_policy >= TieredAdmissionPolicy::kAdmPolicyMax) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<Cache> cache;
|
||||
if (opts.cache_type == PrimaryCacheType::kCacheTypeLRU) {
|
||||
LRUCacheOptions cache_opts =
|
||||
@@ -432,7 +421,6 @@ std::shared_ptr<Cache> NewTieredVolatileCache(
|
||||
HyperClockCacheOptions cache_opts =
|
||||
*(static_cast_with_check<HyperClockCacheOptions, ShardedCacheOptions>(
|
||||
opts.cache_opts));
|
||||
cache_opts.capacity += opts.comp_cache_opts.capacity;
|
||||
cache = cache_opts.MakeSharedCache();
|
||||
} else {
|
||||
return nullptr;
|
||||
@@ -440,7 +428,6 @@ std::shared_ptr<Cache> NewTieredVolatileCache(
|
||||
std::shared_ptr<SecondaryCache> sec_cache;
|
||||
sec_cache = NewCompressedSecondaryCache(opts.comp_cache_opts);
|
||||
|
||||
return std::make_shared<CacheWithSecondaryAdapter>(
|
||||
cache, sec_cache, opts.adm_policy, /*distribute_cache_res=*/true);
|
||||
return std::make_shared<CacheWithSecondaryAdapter>(cache, sec_cache, true);
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+1
-3
@@ -15,7 +15,6 @@ class CacheWithSecondaryAdapter : public CacheWrapper {
|
||||
explicit CacheWithSecondaryAdapter(
|
||||
std::shared_ptr<Cache> target,
|
||||
std::shared_ptr<SecondaryCache> secondary_cache,
|
||||
TieredAdmissionPolicy adm_policy = TieredAdmissionPolicy::kAdmPolicyAuto,
|
||||
bool distribute_cache_res = false);
|
||||
|
||||
~CacheWithSecondaryAdapter() override;
|
||||
@@ -48,7 +47,7 @@ class CacheWithSecondaryAdapter : public CacheWrapper {
|
||||
SecondaryCache* TEST_GetSecondaryCache() { return secondary_cache_.get(); }
|
||||
|
||||
private:
|
||||
bool EvictionHandler(const Slice& key, Handle* handle, bool was_hit);
|
||||
bool EvictionHandler(const Slice& key, Handle* handle);
|
||||
|
||||
void StartAsyncLookupOnMySecondary(AsyncLookupHandle& async_handle);
|
||||
|
||||
@@ -62,7 +61,6 @@ class CacheWithSecondaryAdapter : public CacheWrapper {
|
||||
void CleanupCacheObject(ObjectPtr obj, const CacheItemHelper* helper);
|
||||
|
||||
std::shared_ptr<SecondaryCache> secondary_cache_;
|
||||
TieredAdmissionPolicy adm_policy_;
|
||||
// Whether to proportionally distribute cache memory reservations, i.e
|
||||
// placeholder entries with null value and a non-zero charge, across
|
||||
// the primary and secondary caches.
|
||||
|
||||
Vendored
+1
-1
@@ -38,7 +38,7 @@ uint32_t DetermineSeed(int32_t hash_seed_option) {
|
||||
return GetSliceHash(hostname) & kSeedMask;
|
||||
} else {
|
||||
// Fall back on something stable within the process.
|
||||
return BitwiseAnd(gen.GetBaseUpper(), kSeedMask);
|
||||
return static_cast<uint32_t>(gen.GetBaseUpper()) & kSeedMask;
|
||||
}
|
||||
} else {
|
||||
// for kQuasiRandomHashSeed and fallback
|
||||
|
||||
Vendored
-8
@@ -273,14 +273,6 @@ class ShardedCache : public ShardedCacheBase {
|
||||
}
|
||||
}
|
||||
|
||||
inline void ForEachShard(
|
||||
const std::function<void(const CacheShard*)>& fn) const {
|
||||
uint32_t num_shards = GetNumShards();
|
||||
for (uint32_t i = 0; i < num_shards; i++) {
|
||||
fn(shards_ + i);
|
||||
}
|
||||
}
|
||||
|
||||
inline size_t SumOverShards(
|
||||
const std::function<size_t(CacheShard&)>& fn) const {
|
||||
uint32_t num_shards = GetNumShards();
|
||||
|
||||
@@ -261,7 +261,7 @@ Status BlobFileBuilder::CompressBlobIfNeeded(
|
||||
|
||||
// TODO: allow user CompressionOptions, including max_compressed_bytes_per_kb
|
||||
CompressionOptions opts;
|
||||
CompressionContext context(blob_compression_type_, opts);
|
||||
CompressionContext context(blob_compression_type_);
|
||||
constexpr uint64_t sample_for_compression = 0;
|
||||
|
||||
CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(),
|
||||
|
||||
@@ -406,7 +406,7 @@ TEST_F(BlobFileBuilderTest, Compression) {
|
||||
ASSERT_EQ(blob_file_addition.GetTotalBlobCount(), 1);
|
||||
|
||||
CompressionOptions opts;
|
||||
CompressionContext context(kSnappyCompression, opts);
|
||||
CompressionContext context(kSnappyCompression);
|
||||
constexpr uint64_t sample_for_compression = 0;
|
||||
|
||||
CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(),
|
||||
|
||||
+19
-16
@@ -154,9 +154,11 @@ Status BlobFileReader::ReadHeader(const RandomAccessFileReader* file_reader,
|
||||
constexpr uint64_t read_offset = 0;
|
||||
constexpr size_t read_size = BlobLogHeader::kSize;
|
||||
|
||||
// TODO: rate limit reading headers from blob files.
|
||||
const Status s =
|
||||
ReadFromFile(file_reader, read_options, read_offset, read_size,
|
||||
statistics, &header_slice, &buf, &aligned_buf);
|
||||
statistics, &header_slice, &buf, &aligned_buf,
|
||||
Env::IO_TOTAL /* rate_limiter_priority */);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -205,9 +207,11 @@ Status BlobFileReader::ReadFooter(const RandomAccessFileReader* file_reader,
|
||||
const uint64_t read_offset = file_size - BlobLogFooter::kSize;
|
||||
constexpr size_t read_size = BlobLogFooter::kSize;
|
||||
|
||||
// TODO: rate limit reading footers from blob files.
|
||||
const Status s =
|
||||
ReadFromFile(file_reader, read_options, read_offset, read_size,
|
||||
statistics, &footer_slice, &buf, &aligned_buf);
|
||||
statistics, &footer_slice, &buf, &aligned_buf,
|
||||
Env::IO_TOTAL /* rate_limiter_priority */);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -238,7 +242,8 @@ Status BlobFileReader::ReadFromFile(const RandomAccessFileReader* file_reader,
|
||||
const ReadOptions& read_options,
|
||||
uint64_t read_offset, size_t read_size,
|
||||
Statistics* statistics, Slice* slice,
|
||||
Buffer* buf, AlignedBuf* aligned_buf) {
|
||||
Buffer* buf, AlignedBuf* aligned_buf,
|
||||
Env::IOPriority rate_limiter_priority) {
|
||||
assert(slice);
|
||||
assert(buf);
|
||||
assert(aligned_buf);
|
||||
@@ -259,13 +264,13 @@ Status BlobFileReader::ReadFromFile(const RandomAccessFileReader* file_reader,
|
||||
constexpr char* scratch = nullptr;
|
||||
|
||||
s = file_reader->Read(io_options, read_offset, read_size, slice, scratch,
|
||||
aligned_buf);
|
||||
aligned_buf, rate_limiter_priority);
|
||||
} else {
|
||||
buf->reset(new char[read_size]);
|
||||
constexpr AlignedBuf* aligned_scratch = nullptr;
|
||||
|
||||
s = file_reader->Read(io_options, read_offset, read_size, slice, buf->get(),
|
||||
aligned_scratch);
|
||||
aligned_scratch, rate_limiter_priority);
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
@@ -340,7 +345,8 @@ Status BlobFileReader::GetBlob(
|
||||
}
|
||||
prefetched = prefetch_buffer->TryReadFromCache(
|
||||
io_options, file_reader_.get(), record_offset,
|
||||
static_cast<size_t>(record_size), &record_slice, &s, for_compaction);
|
||||
static_cast<size_t>(record_size), &record_slice, &s,
|
||||
read_options.rate_limiter_priority, for_compaction);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -351,10 +357,10 @@ Status BlobFileReader::GetBlob(
|
||||
PERF_COUNTER_ADD(blob_read_count, 1);
|
||||
PERF_COUNTER_ADD(blob_read_byte, record_size);
|
||||
PERF_TIMER_GUARD(blob_read_time);
|
||||
const Status s =
|
||||
ReadFromFile(file_reader_.get(), read_options, record_offset,
|
||||
static_cast<size_t>(record_size), statistics_,
|
||||
&record_slice, &buf, &aligned_buf);
|
||||
const Status s = ReadFromFile(
|
||||
file_reader_.get(), read_options, record_offset,
|
||||
static_cast<size_t>(record_size), statistics_, &record_slice, &buf,
|
||||
&aligned_buf, read_options.rate_limiter_priority);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -462,12 +468,9 @@ void BlobFileReader::MultiGetBlob(
|
||||
TEST_SYNC_POINT("BlobFileReader::MultiGetBlob:ReadFromFile");
|
||||
PERF_COUNTER_ADD(blob_read_count, num_blobs);
|
||||
PERF_COUNTER_ADD(blob_read_byte, total_len);
|
||||
IOOptions opts;
|
||||
s = file_reader_->PrepareIOOptions(read_options, opts);
|
||||
if (s.ok()) {
|
||||
s = file_reader_->MultiRead(opts, read_reqs.data(), read_reqs.size(),
|
||||
direct_io ? &aligned_buf : nullptr);
|
||||
}
|
||||
s = file_reader_->MultiRead(IOOptions(), read_reqs.data(), read_reqs.size(),
|
||||
direct_io ? &aligned_buf : nullptr,
|
||||
read_options.rate_limiter_priority);
|
||||
if (!s.ok()) {
|
||||
for (auto& req : read_reqs) {
|
||||
req.status.PermitUncheckedError();
|
||||
|
||||
@@ -89,7 +89,8 @@ class BlobFileReader {
|
||||
const ReadOptions& read_options,
|
||||
uint64_t read_offset, size_t read_size,
|
||||
Statistics* statistics, Slice* slice, Buffer* buf,
|
||||
AlignedBuf* aligned_buf);
|
||||
AlignedBuf* aligned_buf,
|
||||
Env::IOPriority rate_limiter_priority);
|
||||
|
||||
static Status VerifyBlob(const Slice& record_slice, const Slice& user_key,
|
||||
uint64_t value_size);
|
||||
|
||||
@@ -74,7 +74,7 @@ void WriteBlobFile(const ImmutableOptions& immutable_options,
|
||||
}
|
||||
} else {
|
||||
CompressionOptions opts;
|
||||
CompressionContext context(compression, opts);
|
||||
CompressionContext context(compression);
|
||||
constexpr uint64_t sample_for_compression = 0;
|
||||
CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(),
|
||||
compression, sample_for_compression);
|
||||
|
||||
@@ -29,8 +29,9 @@ Status BlobLogSequentialReader::ReadSlice(uint64_t size, Slice* slice,
|
||||
|
||||
StopWatch read_sw(clock_, statistics_, BLOB_DB_BLOB_FILE_READ_MICROS);
|
||||
// TODO: rate limit `BlobLogSequentialReader` reads (it appears unused?)
|
||||
Status s = file_->Read(IOOptions(), next_byte_, static_cast<size_t>(size),
|
||||
slice, buf, nullptr);
|
||||
Status s =
|
||||
file_->Read(IOOptions(), next_byte_, static_cast<size_t>(size), slice,
|
||||
buf, nullptr, Env::IO_TOTAL /* rate_limiter_priority */);
|
||||
next_byte_ += size;
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
|
||||
@@ -76,7 +76,7 @@ void WriteBlobFile(const ImmutableOptions& immutable_options,
|
||||
}
|
||||
} else {
|
||||
CompressionOptions opts;
|
||||
CompressionContext context(compression, opts);
|
||||
CompressionContext context(compression);
|
||||
constexpr uint64_t sample_for_compression = 0;
|
||||
CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(),
|
||||
compression, sample_for_compression);
|
||||
|
||||
+7
-30
@@ -15,7 +15,6 @@
|
||||
|
||||
#include "db/blob/blob_file_builder.h"
|
||||
#include "db/compaction/compaction_iterator.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/event_helpers.h"
|
||||
#include "db/internal_stats.h"
|
||||
#include "db/merge_helper.h"
|
||||
@@ -203,42 +202,24 @@ Status BuildTable(
|
||||
blob_file_builder.get(), ioptions.allow_data_in_errors,
|
||||
ioptions.enforce_single_del_contracts,
|
||||
/*manual_compaction_canceled=*/kManualCompactionCanceledFalse,
|
||||
true /* must_count_input_entries */,
|
||||
/*compaction=*/nullptr, compaction_filter.get(),
|
||||
/*shutting_down=*/nullptr, db_options.info_log, full_history_ts_low);
|
||||
|
||||
const size_t ts_sz = ucmp->timestamp_size();
|
||||
const bool strip_timestamp =
|
||||
ts_sz > 0 && !ioptions.persist_user_defined_timestamps;
|
||||
|
||||
std::string key_after_flush_buf;
|
||||
c_iter.SeekToFirst();
|
||||
for (; c_iter.Valid(); c_iter.Next()) {
|
||||
const Slice& key = c_iter.key();
|
||||
const Slice& value = c_iter.value();
|
||||
const ParsedInternalKey& ikey = c_iter.ikey();
|
||||
Slice key_after_flush = key;
|
||||
// If user defined timestamps will be stripped from user key after flush,
|
||||
// the in memory version of the key act logically the same as one with a
|
||||
// minimum timestamp. We update the timestamp here so file boundary and
|
||||
// output validator, block builder all see the effect of the stripping.
|
||||
if (strip_timestamp) {
|
||||
key_after_flush_buf.clear();
|
||||
ReplaceInternalKeyWithMinTimestamp(&key_after_flush_buf, key, ts_sz);
|
||||
key_after_flush = key_after_flush_buf;
|
||||
}
|
||||
|
||||
// Generate a rolling 64-bit hash of the key and values
|
||||
// Note :
|
||||
// Here "key" integrates 'sequence_number'+'kType'+'user key'.
|
||||
s = output_validator.Add(key_after_flush, value);
|
||||
// Generate a rolling 64-bit hash of the key and values
|
||||
// Note :
|
||||
// Here "key" integrates 'sequence_number'+'kType'+'user key'.
|
||||
s = output_validator.Add(key, value);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
builder->Add(key_after_flush, value);
|
||||
builder->Add(key, value);
|
||||
|
||||
s = meta->UpdateBoundaries(key_after_flush, value, ikey.sequence,
|
||||
ikey.type);
|
||||
s = meta->UpdateBoundaries(key, value, ikey.sequence, ikey.type);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
@@ -263,7 +244,6 @@ Status BuildTable(
|
||||
range_del_it->Next()) {
|
||||
auto tombstone = range_del_it->Tombstone();
|
||||
auto kv = tombstone.Serialize();
|
||||
// TODO(yuzhangyu): handle range deletion for UDT in memtables only.
|
||||
builder->Add(kv.first.Encode(), kv.second);
|
||||
InternalKey tombstone_end = tombstone.SerializeEndKey();
|
||||
meta->UpdateBoundariesForRange(kv.first, tombstone_end, tombstone.seq_,
|
||||
@@ -287,9 +267,8 @@ Status BuildTable(
|
||||
TEST_SYNC_POINT("BuildTable:BeforeFinishBuildTable");
|
||||
const bool empty = builder->IsEmpty();
|
||||
if (num_input_entries != nullptr) {
|
||||
assert(c_iter.HasNumInputEntryScanned());
|
||||
*num_input_entries =
|
||||
c_iter.NumInputEntryScanned() + num_unfragmented_tombstones;
|
||||
c_iter.num_input_entry_scanned() + num_unfragmented_tombstones;
|
||||
}
|
||||
if (!s.ok() || empty) {
|
||||
builder->Abandon();
|
||||
@@ -375,8 +354,6 @@ Status BuildTable(
|
||||
s = *io_status;
|
||||
}
|
||||
|
||||
// TODO(yuzhangyu): handle the key copy in the blob when ts should be
|
||||
// stripped.
|
||||
if (blob_file_builder) {
|
||||
if (s.ok()) {
|
||||
s = blob_file_builder->Finish();
|
||||
|
||||
@@ -77,7 +77,6 @@ using ROCKSDB_NAMESPACE::EnvOptions;
|
||||
using ROCKSDB_NAMESPACE::FileLock;
|
||||
using ROCKSDB_NAMESPACE::FilterPolicy;
|
||||
using ROCKSDB_NAMESPACE::FlushOptions;
|
||||
using ROCKSDB_NAMESPACE::HistogramData;
|
||||
using ROCKSDB_NAMESPACE::HyperClockCacheOptions;
|
||||
using ROCKSDB_NAMESPACE::InfoLogLevel;
|
||||
using ROCKSDB_NAMESPACE::IngestExternalFileOptions;
|
||||
@@ -280,11 +279,6 @@ struct rocksdb_compactionfiltercontext_t {
|
||||
CompactionFilter::Context rep;
|
||||
};
|
||||
|
||||
struct rocksdb_statistics_histogram_data_t {
|
||||
rocksdb_statistics_histogram_data_t() : rep() {}
|
||||
HistogramData rep;
|
||||
};
|
||||
|
||||
struct rocksdb_compactionfilter_t : public CompactionFilter {
|
||||
void* state_;
|
||||
void (*destructor_)(void*);
|
||||
@@ -1063,36 +1057,6 @@ rocksdb_column_family_handle_t* rocksdb_create_column_family(
|
||||
return handle;
|
||||
}
|
||||
|
||||
rocksdb_column_family_handle_t** rocksdb_create_column_families(
|
||||
rocksdb_t* db, const rocksdb_options_t* column_family_options,
|
||||
int num_column_families, const char* const* column_family_names,
|
||||
size_t* lencfs, char** errptr) {
|
||||
std::vector<ColumnFamilyHandle*> handles;
|
||||
std::vector<std::string> names;
|
||||
for (int i = 0; i != num_column_families; ++i) {
|
||||
names.push_back(std::string(column_family_names[i]));
|
||||
}
|
||||
SaveError(errptr, db->rep->CreateColumnFamilies(
|
||||
ColumnFamilyOptions(column_family_options->rep), names,
|
||||
&handles));
|
||||
|
||||
*lencfs = handles.size();
|
||||
rocksdb_column_family_handle_t** c_handles =
|
||||
static_cast<rocksdb_column_family_handle_t**>(
|
||||
malloc(sizeof(rocksdb_column_family_handle_t*) * handles.size()));
|
||||
for (size_t i = 0; i != handles.size(); ++i) {
|
||||
c_handles[i] = new rocksdb_column_family_handle_t;
|
||||
c_handles[i]->rep = handles[i];
|
||||
}
|
||||
|
||||
return c_handles;
|
||||
}
|
||||
|
||||
void rocksdb_create_column_families_destroy(
|
||||
rocksdb_column_family_handle_t** list) {
|
||||
free(list);
|
||||
}
|
||||
|
||||
rocksdb_column_family_handle_t* rocksdb_create_column_family_with_ttl(
|
||||
rocksdb_t* db, const rocksdb_options_t* column_family_options,
|
||||
const char* column_family_name, int ttl, char** errptr) {
|
||||
@@ -3029,29 +2993,6 @@ void rocksdb_options_enable_statistics(rocksdb_options_t* opt) {
|
||||
opt->rep.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
}
|
||||
|
||||
void rocksdb_options_set_statistics_level(rocksdb_options_t* opt, int level) {
|
||||
if (!opt->rep.statistics) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (level < rocksdb_statistics_level_disable_all) {
|
||||
level = rocksdb_statistics_level_disable_all;
|
||||
}
|
||||
if (level > rocksdb_statistics_level_all) {
|
||||
level = rocksdb_statistics_level_all;
|
||||
}
|
||||
opt->rep.statistics->set_stats_level(
|
||||
static_cast<ROCKSDB_NAMESPACE::StatsLevel>(level));
|
||||
}
|
||||
|
||||
int rocksdb_options_get_statistics_level(rocksdb_options_t* opt) {
|
||||
if (!opt->rep.statistics) {
|
||||
return ROCKSDB_NAMESPACE::StatsLevel::kDisableAll;
|
||||
}
|
||||
|
||||
return static_cast<int>(opt->rep.statistics->get_stats_level());
|
||||
}
|
||||
|
||||
void rocksdb_options_set_skip_stats_update_on_db_open(rocksdb_options_t* opt,
|
||||
unsigned char val) {
|
||||
opt->rep.skip_stats_update_on_db_open = val;
|
||||
@@ -3799,21 +3740,16 @@ void rocksdb_options_set_hash_link_list_rep(rocksdb_options_t* opt,
|
||||
ROCKSDB_NAMESPACE::NewHashLinkListRepFactory(bucket_count));
|
||||
}
|
||||
|
||||
void rocksdb_options_set_plain_table_factory(
|
||||
rocksdb_options_t* opt, uint32_t user_key_len, int bloom_bits_per_key,
|
||||
double hash_table_ratio, size_t index_sparseness, size_t huge_page_tlb_size,
|
||||
char encoding_type, unsigned char full_scan_mode,
|
||||
unsigned char store_index_in_file) {
|
||||
void rocksdb_options_set_plain_table_factory(rocksdb_options_t* opt,
|
||||
uint32_t user_key_len,
|
||||
int bloom_bits_per_key,
|
||||
double hash_table_ratio,
|
||||
size_t index_sparseness) {
|
||||
ROCKSDB_NAMESPACE::PlainTableOptions options;
|
||||
options.user_key_len = user_key_len;
|
||||
options.bloom_bits_per_key = bloom_bits_per_key;
|
||||
options.hash_table_ratio = hash_table_ratio;
|
||||
options.index_sparseness = index_sparseness;
|
||||
options.huge_page_tlb_size = huge_page_tlb_size;
|
||||
options.encoding_type =
|
||||
static_cast<ROCKSDB_NAMESPACE::EncodingType>(encoding_type);
|
||||
options.full_scan_mode = full_scan_mode;
|
||||
options.store_index_in_file = store_index_in_file;
|
||||
|
||||
ROCKSDB_NAMESPACE::TableFactory* factory =
|
||||
ROCKSDB_NAMESPACE::NewPlainTableFactory(options);
|
||||
@@ -3891,26 +3827,6 @@ char* rocksdb_options_statistics_get_string(rocksdb_options_t* opt) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_options_statistics_get_ticker_count(rocksdb_options_t* opt,
|
||||
uint32_t ticker_type) {
|
||||
ROCKSDB_NAMESPACE::Statistics* statistics = opt->rep.statistics.get();
|
||||
if (statistics) {
|
||||
return statistics->getTickerCount(ticker_type);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void rocksdb_options_statistics_get_histogram_data(
|
||||
rocksdb_options_t* opt, uint32_t type,
|
||||
rocksdb_statistics_histogram_data_t* const data) {
|
||||
ROCKSDB_NAMESPACE::Statistics* statistics = opt->rep.statistics.get();
|
||||
if (statistics) {
|
||||
statistics->histogramData(type, &data->rep);
|
||||
} else {
|
||||
*data = rocksdb_statistics_histogram_data_t{};
|
||||
}
|
||||
}
|
||||
|
||||
void rocksdb_options_set_ratelimiter(rocksdb_options_t* opt,
|
||||
rocksdb_ratelimiter_t* limiter) {
|
||||
if (limiter) {
|
||||
@@ -5243,8 +5159,7 @@ rocksdb_fifo_compaction_options_t* rocksdb_fifo_compaction_options_create() {
|
||||
}
|
||||
|
||||
void rocksdb_fifo_compaction_options_set_allow_compaction(
|
||||
rocksdb_fifo_compaction_options_t* fifo_opts,
|
||||
unsigned char allow_compaction) {
|
||||
rocksdb_fifo_compaction_options_t* fifo_opts, unsigned char allow_compaction) {
|
||||
fifo_opts->rep.allow_compaction = allow_compaction;
|
||||
}
|
||||
|
||||
@@ -5673,20 +5588,6 @@ int rocksdb_transactiondb_property_int(rocksdb_transactiondb_t* db,
|
||||
}
|
||||
}
|
||||
|
||||
rocksdb_t* rocksdb_transactiondb_get_base_db(rocksdb_transactiondb_t* txn_db) {
|
||||
DB* base_db = txn_db->rep->GetBaseDB();
|
||||
|
||||
if (base_db != nullptr) {
|
||||
rocksdb_t* result = new rocksdb_t;
|
||||
result->rep = base_db;
|
||||
return result;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void rocksdb_transactiondb_close_base_db(rocksdb_t* base_db) { delete base_db; }
|
||||
|
||||
rocksdb_transaction_t* rocksdb_transaction_begin(
|
||||
rocksdb_transactiondb_t* txn_db,
|
||||
const rocksdb_writeoptions_t* write_options,
|
||||
@@ -6664,59 +6565,4 @@ void rocksdb_enable_manual_compaction(rocksdb_t* db) {
|
||||
db->rep->EnableManualCompaction();
|
||||
}
|
||||
|
||||
rocksdb_statistics_histogram_data_t*
|
||||
rocksdb_statistics_histogram_data_create() {
|
||||
return new rocksdb_statistics_histogram_data_t{};
|
||||
}
|
||||
|
||||
void rocksdb_statistics_histogram_data_destroy(
|
||||
rocksdb_statistics_histogram_data_t* data) {
|
||||
delete data;
|
||||
}
|
||||
|
||||
double rocksdb_statistics_histogram_data_get_median(
|
||||
rocksdb_statistics_histogram_data_t* data) {
|
||||
return data->rep.median;
|
||||
}
|
||||
|
||||
double rocksdb_statistics_histogram_data_get_p95(
|
||||
rocksdb_statistics_histogram_data_t* data) {
|
||||
return data->rep.percentile95;
|
||||
}
|
||||
|
||||
double rocksdb_statistics_histogram_data_get_p99(
|
||||
rocksdb_statistics_histogram_data_t* data) {
|
||||
return data->rep.percentile99;
|
||||
}
|
||||
|
||||
double rocksdb_statistics_histogram_data_get_average(
|
||||
rocksdb_statistics_histogram_data_t* data) {
|
||||
return data->rep.average;
|
||||
}
|
||||
|
||||
double rocksdb_statistics_histogram_data_get_std_dev(
|
||||
rocksdb_statistics_histogram_data_t* data) {
|
||||
return data->rep.standard_deviation;
|
||||
}
|
||||
|
||||
double rocksdb_statistics_histogram_data_get_max(
|
||||
rocksdb_statistics_histogram_data_t* data) {
|
||||
return data->rep.max;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_statistics_histogram_data_get_count(
|
||||
rocksdb_statistics_histogram_data_t* data) {
|
||||
return data->rep.count;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_statistics_histogram_data_get_sum(
|
||||
rocksdb_statistics_histogram_data_t* data) {
|
||||
return data->rep.sum;
|
||||
}
|
||||
|
||||
double rocksdb_statistics_histogram_data_get_min(
|
||||
rocksdb_statistics_histogram_data_t* data) {
|
||||
return data->rep.min;
|
||||
}
|
||||
|
||||
} // end extern "C"
|
||||
+6
-102
@@ -3,14 +3,15 @@
|
||||
found in the LICENSE file. See the AUTHORS file for names of contributors. */
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
|
||||
#include "rocksdb/c.h"
|
||||
#include <stdio.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "rocksdb/c.h"
|
||||
#ifndef OS_WIN
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
@@ -1673,8 +1674,7 @@ int main(int argc, char** argv) {
|
||||
rocksdb_options_set_prefix_extractor(
|
||||
options, rocksdb_slicetransform_create_fixed_prefix(3));
|
||||
rocksdb_options_set_hash_skip_list_rep(options, 5000, 4, 4);
|
||||
rocksdb_options_set_plain_table_factory(options, 4, 10, 0.75, 16, 0, 0, 0,
|
||||
0);
|
||||
rocksdb_options_set_plain_table_factory(options, 4, 10, 0.75, 16);
|
||||
rocksdb_options_set_allow_concurrent_memtable_write(options, 0);
|
||||
|
||||
db = rocksdb_open(options, dbname, &err);
|
||||
@@ -2060,15 +2060,6 @@ int main(int argc, char** argv) {
|
||||
CheckCondition(29.0 ==
|
||||
rocksdb_options_get_experimental_mempurge_threshold(o));
|
||||
|
||||
CheckCondition(rocksdb_statistics_level_disable_all ==
|
||||
rocksdb_options_get_statistics_level(o));
|
||||
rocksdb_options_enable_statistics(o);
|
||||
CheckCondition(rocksdb_statistics_level_disable_all !=
|
||||
rocksdb_options_get_statistics_level(o));
|
||||
rocksdb_options_set_statistics_level(o, rocksdb_statistics_level_all);
|
||||
CheckCondition(rocksdb_statistics_level_all ==
|
||||
rocksdb_options_get_statistics_level(o));
|
||||
|
||||
/* Blob Options */
|
||||
rocksdb_options_set_enable_blob_files(o, 1);
|
||||
CheckCondition(1 == rocksdb_options_get_enable_blob_files(o));
|
||||
@@ -3130,17 +3121,6 @@ int main(int argc, char** argv) {
|
||||
CheckTxnDBGetCF(txn_db, roptions, cfh, "cf_foo", NULL);
|
||||
CheckTxnDBPinGetCF(txn_db, roptions, cfh, "cf_foo", NULL);
|
||||
|
||||
// memory usage
|
||||
rocksdb_t* base_db = rocksdb_transactiondb_get_base_db(txn_db);
|
||||
rocksdb_memory_consumers_t* consumers = rocksdb_memory_consumers_create();
|
||||
rocksdb_memory_consumers_add_db(consumers, base_db);
|
||||
rocksdb_memory_usage_t* usage =
|
||||
rocksdb_approximate_memory_usage_create(consumers, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_approximate_memory_usage_destroy(usage);
|
||||
rocksdb_memory_consumers_destroy(consumers);
|
||||
rocksdb_transactiondb_close_base_db(base_db);
|
||||
|
||||
// flush
|
||||
rocksdb_flushoptions_t* flush_options = rocksdb_flushoptions_create();
|
||||
rocksdb_flushoptions_set_wait(flush_options, 1);
|
||||
@@ -3396,19 +3376,8 @@ int main(int argc, char** argv) {
|
||||
rocksdb_put(db, woptions, "key", 3, "value", 5, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_column_family_handle_t *cfh1, *cfh2;
|
||||
char** list_const_cf_names = (char**)malloc(2 * sizeof(char*));
|
||||
list_const_cf_names[0] = "txn_db_cf1";
|
||||
list_const_cf_names[1] = "txn_db_cf2";
|
||||
size_t cflen;
|
||||
rocksdb_column_family_handle_t** list_cfh = rocksdb_create_column_families(
|
||||
db, db_options, 2, (const char* const*)list_const_cf_names, &cflen,
|
||||
&err);
|
||||
free(list_const_cf_names);
|
||||
CheckNoError(err);
|
||||
assert(cflen == 2);
|
||||
cfh1 = list_cfh[0];
|
||||
cfh2 = list_cfh[1];
|
||||
rocksdb_create_column_families_destroy(list_cfh);
|
||||
cfh1 = rocksdb_create_column_family(db, db_options, "txn_db_cf1", &err);
|
||||
cfh2 = rocksdb_create_column_family(db, db_options, "txn_db_cf2", &err);
|
||||
txn = rocksdb_optimistictransaction_begin(otxn_db, woptions, otxn_options,
|
||||
NULL);
|
||||
rocksdb_transaction_put_cf(txn, cfh1, "key_cf1", 7, "val_cf1", 7, &err);
|
||||
@@ -3622,71 +3591,6 @@ int main(int argc, char** argv) {
|
||||
rocksdb_readoptions_destroy(ropts);
|
||||
}
|
||||
|
||||
StartPhase("statistics");
|
||||
{
|
||||
const uint32_t BYTES_WRITTEN_TICKER = 40;
|
||||
const uint32_t DB_WRITE_HIST = 1;
|
||||
|
||||
rocksdb_statistics_histogram_data_t* hist =
|
||||
rocksdb_statistics_histogram_data_create();
|
||||
{
|
||||
// zero by default
|
||||
CheckCondition(0.0 == rocksdb_statistics_histogram_data_get_median(hist));
|
||||
CheckCondition(0.0 == rocksdb_statistics_histogram_data_get_p95(hist));
|
||||
CheckCondition(0.0 == rocksdb_statistics_histogram_data_get_p99(hist));
|
||||
CheckCondition(0.0 ==
|
||||
rocksdb_statistics_histogram_data_get_average(hist));
|
||||
CheckCondition(0.0 ==
|
||||
rocksdb_statistics_histogram_data_get_std_dev(hist));
|
||||
CheckCondition(0.0 == rocksdb_statistics_histogram_data_get_max(hist));
|
||||
CheckCondition(0 == rocksdb_statistics_histogram_data_get_count(hist));
|
||||
CheckCondition(0 == rocksdb_statistics_histogram_data_get_sum(hist));
|
||||
CheckCondition(0.0 == rocksdb_statistics_histogram_data_get_min(hist));
|
||||
}
|
||||
|
||||
rocksdb_close(db);
|
||||
rocksdb_destroy_db(options, dbname, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
rocksdb_options_enable_statistics(options);
|
||||
rocksdb_options_set_statistics_level(options, rocksdb_statistics_level_all);
|
||||
|
||||
db = rocksdb_open(options, dbname, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
CheckCondition(0 == rocksdb_options_statistics_get_ticker_count(
|
||||
options, BYTES_WRITTEN_TICKER));
|
||||
rocksdb_options_statistics_get_histogram_data(options, DB_WRITE_HIST, hist);
|
||||
CheckCondition(0.0 == rocksdb_statistics_histogram_data_get_median(hist));
|
||||
CheckCondition(0.0 == rocksdb_statistics_histogram_data_get_p95(hist));
|
||||
CheckCondition(0.0 == rocksdb_statistics_histogram_data_get_p99(hist));
|
||||
CheckCondition(0.0 == rocksdb_statistics_histogram_data_get_average(hist));
|
||||
CheckCondition(0.0 == rocksdb_statistics_histogram_data_get_std_dev(hist));
|
||||
CheckCondition(0.0 == rocksdb_statistics_histogram_data_get_max(hist));
|
||||
CheckCondition(0 == rocksdb_statistics_histogram_data_get_count(hist));
|
||||
CheckCondition(0 == rocksdb_statistics_histogram_data_get_sum(hist));
|
||||
|
||||
int i;
|
||||
for (i = 0; i < 10; ++i) {
|
||||
char key = '0' + (char)i;
|
||||
rocksdb_put(db, woptions, &key, 1, "", 1, &err);
|
||||
CheckNoError(err);
|
||||
}
|
||||
CheckCondition(0 != rocksdb_options_statistics_get_ticker_count(
|
||||
options, BYTES_WRITTEN_TICKER));
|
||||
rocksdb_options_statistics_get_histogram_data(options, DB_WRITE_HIST, hist);
|
||||
CheckCondition(0.0 != rocksdb_statistics_histogram_data_get_median(hist));
|
||||
CheckCondition(0.0 != rocksdb_statistics_histogram_data_get_p95(hist));
|
||||
CheckCondition(0.0 != rocksdb_statistics_histogram_data_get_p99(hist));
|
||||
CheckCondition(0.0 != rocksdb_statistics_histogram_data_get_average(hist));
|
||||
CheckCondition(0.0 != rocksdb_statistics_histogram_data_get_std_dev(hist));
|
||||
CheckCondition(0.0 != rocksdb_statistics_histogram_data_get_max(hist));
|
||||
CheckCondition(0 != rocksdb_statistics_histogram_data_get_count(hist));
|
||||
CheckCondition(0 != rocksdb_statistics_histogram_data_get_sum(hist));
|
||||
|
||||
rocksdb_statistics_histogram_data_destroy(hist);
|
||||
}
|
||||
|
||||
StartPhase("cancel_all_background_work");
|
||||
rocksdb_cancel_all_background_work(db, 1);
|
||||
|
||||
|
||||
+25
-85
@@ -382,9 +382,8 @@ ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
|
||||
|
||||
const uint64_t kAdjustedTtl = 30 * 24 * 60 * 60;
|
||||
if (result.ttl == kDefaultTtl) {
|
||||
if (is_block_based_table) {
|
||||
// FIFO also requires max_open_files=-1, which is checked in
|
||||
// ValidateOptions().
|
||||
if (is_block_based_table &&
|
||||
result.compaction_style != kCompactionStyleFIFO) {
|
||||
result.ttl = kAdjustedTtl;
|
||||
} else {
|
||||
result.ttl = 0;
|
||||
@@ -392,35 +391,40 @@ ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
|
||||
}
|
||||
|
||||
const uint64_t kAdjustedPeriodicCompSecs = 30 * 24 * 60 * 60;
|
||||
if (result.compaction_style == kCompactionStyleLevel) {
|
||||
|
||||
// Turn on periodic compactions and set them to occur once every 30 days if
|
||||
// compaction filters are used and periodic_compaction_seconds is set to the
|
||||
// default value.
|
||||
if (result.compaction_style != kCompactionStyleFIFO) {
|
||||
if ((result.compaction_filter != nullptr ||
|
||||
result.compaction_filter_factory != nullptr) &&
|
||||
result.periodic_compaction_seconds == kDefaultPeriodicCompSecs &&
|
||||
is_block_based_table) {
|
||||
result.periodic_compaction_seconds = kAdjustedPeriodicCompSecs;
|
||||
}
|
||||
} else if (result.compaction_style == kCompactionStyleUniversal) {
|
||||
if (result.periodic_compaction_seconds == kDefaultPeriodicCompSecs &&
|
||||
is_block_based_table) {
|
||||
result.periodic_compaction_seconds = kAdjustedPeriodicCompSecs;
|
||||
}
|
||||
} else if (result.compaction_style == kCompactionStyleFIFO) {
|
||||
if (result.periodic_compaction_seconds != kDefaultPeriodicCompSecs) {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options.info_log.get(),
|
||||
"periodic_compaction_seconds does not support FIFO compaction. You"
|
||||
"may want to set option TTL instead.");
|
||||
} else {
|
||||
// result.compaction_style == kCompactionStyleFIFO
|
||||
if (result.ttl == 0) {
|
||||
if (is_block_based_table) {
|
||||
if (result.periodic_compaction_seconds == kDefaultPeriodicCompSecs) {
|
||||
result.periodic_compaction_seconds = kAdjustedPeriodicCompSecs;
|
||||
}
|
||||
result.ttl = result.periodic_compaction_seconds;
|
||||
}
|
||||
} else if (result.periodic_compaction_seconds != 0) {
|
||||
result.ttl = std::min(result.ttl, result.periodic_compaction_seconds);
|
||||
}
|
||||
}
|
||||
|
||||
// For universal compaction, `ttl` and `periodic_compaction_seconds` mean the
|
||||
// same thing, take the stricter value.
|
||||
if (result.compaction_style == kCompactionStyleUniversal) {
|
||||
if (result.periodic_compaction_seconds == 0) {
|
||||
result.periodic_compaction_seconds = result.ttl;
|
||||
} else if (result.ttl != 0) {
|
||||
// TTL compactions would work similar to Periodic Compactions in Universal in
|
||||
// most of the cases. So, if ttl is set, execute the periodic compaction
|
||||
// codepath.
|
||||
if (result.compaction_style == kCompactionStyleUniversal && result.ttl != 0) {
|
||||
if (result.periodic_compaction_seconds != 0) {
|
||||
result.periodic_compaction_seconds =
|
||||
std::min(result.ttl, result.periodic_compaction_seconds);
|
||||
} else {
|
||||
result.periodic_compaction_seconds = result.ttl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1376,33 +1380,6 @@ Status ColumnFamilyData::ValidateOptions(
|
||||
}
|
||||
}
|
||||
|
||||
const auto* ucmp = cf_options.comparator;
|
||||
assert(ucmp);
|
||||
if (ucmp->timestamp_size() > 0 &&
|
||||
!cf_options.persist_user_defined_timestamps) {
|
||||
if (db_options.atomic_flush) {
|
||||
return Status::NotSupported(
|
||||
"Not persisting user-defined timestamps feature is not supported"
|
||||
"in combination with atomic flush.");
|
||||
}
|
||||
if (db_options.allow_concurrent_memtable_write) {
|
||||
return Status::NotSupported(
|
||||
"Not persisting user-defined timestamps feature is not supported"
|
||||
" in combination with concurrent memtable write.");
|
||||
}
|
||||
const char* comparator_name = cf_options.comparator->Name();
|
||||
size_t name_size = strlen(comparator_name);
|
||||
const char* suffix = ".u64ts";
|
||||
size_t suffix_size = strlen(suffix);
|
||||
if (name_size <= suffix_size ||
|
||||
strcmp(comparator_name + name_size - suffix_size, suffix) != 0) {
|
||||
return Status::NotSupported(
|
||||
"Not persisting user-defined timestamps"
|
||||
"feature only support user-defined timestamps formatted as "
|
||||
"uint64_t.");
|
||||
}
|
||||
}
|
||||
|
||||
if (cf_options.enable_blob_garbage_collection) {
|
||||
if (cf_options.blob_garbage_collection_age_cutoff < 0.0 ||
|
||||
cf_options.blob_garbage_collection_age_cutoff > 1.0) {
|
||||
@@ -1542,43 +1519,6 @@ FSDirectory* ColumnFamilyData::GetDataDir(size_t path_id) const {
|
||||
return data_dirs_[path_id].get();
|
||||
}
|
||||
|
||||
bool ColumnFamilyData::ShouldPostponeFlushToRetainUDT(
|
||||
uint64_t max_memtable_id) {
|
||||
const Comparator* ucmp = user_comparator();
|
||||
const size_t ts_sz = ucmp->timestamp_size();
|
||||
if (ts_sz == 0 || ioptions_.persist_user_defined_timestamps) {
|
||||
return false;
|
||||
}
|
||||
// If users set the `persist_user_defined_timestamps` flag to false, they
|
||||
// should also set the `full_history_ts_low` flag to indicate the range of
|
||||
// user-defined timestamps to retain in memory. Otherwise, we do not
|
||||
// explicitly postpone flush to retain UDTs.
|
||||
const std::string& full_history_ts_low = GetFullHistoryTsLow();
|
||||
if (full_history_ts_low.empty()) {
|
||||
return false;
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
Slice last_table_newest_udt;
|
||||
#endif /* !NDEBUG */
|
||||
for (const Slice& table_newest_udt :
|
||||
imm()->GetTablesNewestUDT(max_memtable_id)) {
|
||||
assert(table_newest_udt.size() == full_history_ts_low.size());
|
||||
assert(last_table_newest_udt.empty() ||
|
||||
ucmp->CompareTimestamp(table_newest_udt, last_table_newest_udt) >=
|
||||
0);
|
||||
// Checking the newest UDT contained in MemTable with ascending ID up to
|
||||
// `max_memtable_id`. MemTable with bigger ID will have newer UDT, return
|
||||
// immediately on finding the first MemTable that needs postponing.
|
||||
if (ucmp->CompareTimestamp(table_newest_udt, full_history_ts_low) >= 0) {
|
||||
return true;
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
last_table_newest_udt = table_newest_udt;
|
||||
#endif /* !NDEBUG */
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ColumnFamilyData::RecoverEpochNumbers() {
|
||||
assert(current_);
|
||||
auto* vstorage = current_->storage_info();
|
||||
|
||||
+2
-6
@@ -506,12 +506,6 @@ class ColumnFamilyData {
|
||||
return full_history_ts_low_;
|
||||
}
|
||||
|
||||
// REQUIRES: DB mutex held.
|
||||
// Return true if flushing up to MemTables with ID `max_memtable_id`
|
||||
// should be postponed to retain user-defined timestamps according to the
|
||||
// user's setting. Called by background flush job.
|
||||
bool ShouldPostponeFlushToRetainUDT(uint64_t max_memtable_id);
|
||||
|
||||
ThreadLocalPtr* TEST_GetLocalSV() { return local_sv_.get(); }
|
||||
WriteBufferManager* write_buffer_mgr() { return write_buffer_manager_; }
|
||||
std::shared_ptr<CacheReservationManager>
|
||||
@@ -519,6 +513,8 @@ class ColumnFamilyData {
|
||||
return file_metadata_cache_res_mgr_;
|
||||
}
|
||||
|
||||
SequenceNumber GetFirstMemtableSequenceNumber() const;
|
||||
|
||||
static const uint32_t kDummyColumnFamilyDataId;
|
||||
|
||||
// Keep track of whether the mempurge feature was ever used.
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include "options/options_parser.h"
|
||||
#include "port/port.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
@@ -64,9 +63,6 @@ class ColumnFamilyTestBase : public testing::Test {
|
||||
db_options_.create_if_missing = true;
|
||||
db_options_.fail_if_options_file_error = true;
|
||||
db_options_.env = env_;
|
||||
}
|
||||
|
||||
void SetUp() override {
|
||||
EXPECT_OK(DestroyDB(dbname_, Options(db_options_, column_family_options_)));
|
||||
}
|
||||
|
||||
@@ -3384,205 +3380,6 @@ TEST(ColumnFamilyTest, ValidateMemtableKVChecksumOption) {
|
||||
ASSERT_OK(ColumnFamilyData::ValidateOptions(db_options, cf_options));
|
||||
}
|
||||
|
||||
// Tests the flushing behavior of a column family to retain user-defined
|
||||
// timestamp when `persist_user_defined_timestamp` is false.
|
||||
class ColumnFamilyRetainUDTTest : public ColumnFamilyTestBase {
|
||||
public:
|
||||
ColumnFamilyRetainUDTTest() : ColumnFamilyTestBase(kLatestFormatVersion) {}
|
||||
|
||||
void SetUp() override {
|
||||
db_options_.allow_concurrent_memtable_write = false;
|
||||
column_family_options_.comparator =
|
||||
test::BytewiseComparatorWithU64TsWrapper();
|
||||
column_family_options_.persist_user_defined_timestamps = false;
|
||||
ColumnFamilyTestBase::SetUp();
|
||||
}
|
||||
|
||||
Status Put(int cf, const std::string& key, const std::string& ts,
|
||||
const std::string& value) {
|
||||
return db_->Put(WriteOptions(), handles_[cf], Slice(key), Slice(ts),
|
||||
Slice(value));
|
||||
}
|
||||
};
|
||||
|
||||
class TestTsComparator : public Comparator {
|
||||
public:
|
||||
TestTsComparator() : Comparator(8 /*ts_sz*/) {}
|
||||
|
||||
int Compare(const ROCKSDB_NAMESPACE::Slice& /*a*/,
|
||||
const ROCKSDB_NAMESPACE::Slice& /*b*/) const override {
|
||||
return 0;
|
||||
}
|
||||
const char* Name() const override { return "TestTs"; }
|
||||
void FindShortestSeparator(
|
||||
std::string* /*start*/,
|
||||
const ROCKSDB_NAMESPACE::Slice& /*limit*/) const override {}
|
||||
void FindShortSuccessor(std::string* /*key*/) const override {}
|
||||
};
|
||||
|
||||
TEST_F(ColumnFamilyRetainUDTTest, SanityCheck) {
|
||||
Open();
|
||||
ColumnFamilyOptions cf_options;
|
||||
cf_options.persist_user_defined_timestamps = false;
|
||||
TestTsComparator test_comparator;
|
||||
cf_options.comparator = &test_comparator;
|
||||
ColumnFamilyHandle* handle;
|
||||
// Not persisting user-defined timestamps feature only supports user-defined
|
||||
// timestamps formatted as uint64_t.
|
||||
ASSERT_TRUE(
|
||||
db_->CreateColumnFamily(cf_options, "pikachu", &handle).IsNotSupported());
|
||||
|
||||
Destroy();
|
||||
// Not persisting user-defined timestamps feature doesn't work in combination
|
||||
// with atomic flush.
|
||||
db_options_.atomic_flush = true;
|
||||
ASSERT_TRUE(TryOpen({"default"}).IsNotSupported());
|
||||
|
||||
// Not persisting user-defined timestamps feature doesn't work in combination
|
||||
// with concurrent memtable write.
|
||||
db_options_.atomic_flush = false;
|
||||
db_options_.allow_concurrent_memtable_write = true;
|
||||
ASSERT_TRUE(TryOpen({"default"}).IsNotSupported());
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyRetainUDTTest, FullHistoryTsLowNotSet) {
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundFlush:CheckFlushRequest:cb", [&](void* arg) {
|
||||
ASSERT_NE(nullptr, arg);
|
||||
auto reschedule_count = *static_cast<int*>(arg);
|
||||
ASSERT_EQ(1, reschedule_count);
|
||||
});
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Open();
|
||||
std::string write_ts;
|
||||
PutFixed64(&write_ts, 1);
|
||||
ASSERT_OK(Put(0, "foo", write_ts, "v1"));
|
||||
// No `full_history_ts_low` explicitly set by user, flush is continued
|
||||
// without checking if its UDTs expired.
|
||||
ASSERT_OK(Flush(0));
|
||||
|
||||
// After flush, `full_history_ts_low` should be automatically advanced to
|
||||
// the effective cutoff timestamp: write_ts + 1
|
||||
std::string cutoff_ts;
|
||||
PutFixed64(&cutoff_ts, 2);
|
||||
std::string effective_full_history_ts_low;
|
||||
ASSERT_OK(
|
||||
db_->GetFullHistoryTsLow(handles_[0], &effective_full_history_ts_low));
|
||||
ASSERT_EQ(cutoff_ts, effective_full_history_ts_low);
|
||||
Close();
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyRetainUDTTest, AllKeysExpired) {
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundFlush:CheckFlushRequest:cb", [&](void* arg) {
|
||||
ASSERT_NE(nullptr, arg);
|
||||
auto reschedule_count = *static_cast<int*>(arg);
|
||||
ASSERT_EQ(1, reschedule_count);
|
||||
});
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Open();
|
||||
std::string write_ts;
|
||||
PutFixed64(&write_ts, 1);
|
||||
ASSERT_OK(Put(0, "foo", write_ts, "v1"));
|
||||
std::string cutoff_ts;
|
||||
PutFixed64(&cutoff_ts, 3);
|
||||
ASSERT_OK(db_->IncreaseFullHistoryTsLow(handles_[0], cutoff_ts));
|
||||
// All keys expired w.r.t the configured `full_history_ts_low`, flush continue
|
||||
// without the need for a re-schedule.
|
||||
ASSERT_OK(Flush(0));
|
||||
|
||||
// `full_history_ts_low` stays unchanged after flush.
|
||||
std::string effective_full_history_ts_low;
|
||||
ASSERT_OK(
|
||||
db_->GetFullHistoryTsLow(handles_[0], &effective_full_history_ts_low));
|
||||
ASSERT_EQ(cutoff_ts, effective_full_history_ts_low);
|
||||
Close();
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
TEST_F(ColumnFamilyRetainUDTTest, NotAllKeysExpiredFlushToAvoidWriteStall) {
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundFlush:CheckFlushRequest:cb", [&](void* arg) {
|
||||
ASSERT_NE(nullptr, arg);
|
||||
auto reschedule_count = *static_cast<int*>(arg);
|
||||
ASSERT_EQ(1, reschedule_count);
|
||||
});
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Open();
|
||||
std::string cutoff_ts;
|
||||
std::string write_ts;
|
||||
PutFixed64(&write_ts, 1);
|
||||
ASSERT_OK(Put(0, "foo", write_ts, "v1"));
|
||||
PutFixed64(&cutoff_ts, 1);
|
||||
ASSERT_OK(db_->IncreaseFullHistoryTsLow(handles_[0], cutoff_ts));
|
||||
ASSERT_OK(db_->SetOptions(handles_[0], {{"max_write_buffer_number", "1"}}));
|
||||
// Not all keys expired, but flush is continued without a re-schedule because
|
||||
// of risk of write stall.
|
||||
ASSERT_OK(Flush(0));
|
||||
|
||||
// After flush, `full_history_ts_low` should be automatically advanced to
|
||||
// the effective cutoff timestamp: write_ts + 1
|
||||
std::string effective_full_history_ts_low;
|
||||
ASSERT_OK(
|
||||
db_->GetFullHistoryTsLow(handles_[0], &effective_full_history_ts_low));
|
||||
|
||||
cutoff_ts.clear();
|
||||
PutFixed64(&cutoff_ts, 2);
|
||||
ASSERT_EQ(cutoff_ts, effective_full_history_ts_low);
|
||||
Close();
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyRetainUDTTest, NotAllKeysExpiredFlushRescheduled) {
|
||||
std::string cutoff_ts;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::AfterRetainUDTReschedule:cb", [&](void* /*arg*/) {
|
||||
// Increasing full_history_ts_low so all keys expired after the initial
|
||||
// FlushRequest is rescheduled
|
||||
cutoff_ts.clear();
|
||||
PutFixed64(&cutoff_ts, 3);
|
||||
ASSERT_OK(db_->IncreaseFullHistoryTsLow(handles_[0], cutoff_ts));
|
||||
});
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundFlush:CheckFlushRequest:cb", [&](void* arg) {
|
||||
ASSERT_NE(nullptr, arg);
|
||||
auto reschedule_count = *static_cast<int*>(arg);
|
||||
ASSERT_EQ(2, reschedule_count);
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Open();
|
||||
std::string write_ts;
|
||||
PutFixed64(&write_ts, 1);
|
||||
ASSERT_OK(Put(0, "foo", write_ts, "v1"));
|
||||
PutFixed64(&cutoff_ts, 1);
|
||||
ASSERT_OK(db_->IncreaseFullHistoryTsLow(handles_[0], cutoff_ts));
|
||||
// Not all keys expired, and there is no risk of write stall. Flush is
|
||||
// rescheduled. The actual flush happens after `full_history_ts_low` is
|
||||
// increased to mark all keys expired.
|
||||
ASSERT_OK(Flush(0));
|
||||
|
||||
std::string effective_full_history_ts_low;
|
||||
ASSERT_OK(
|
||||
db_->GetFullHistoryTsLow(handles_[0], &effective_full_history_ts_low));
|
||||
// `full_history_ts_low` stays unchanged.
|
||||
ASSERT_EQ(cutoff_ts, effective_full_history_ts_low);
|
||||
Close();
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -345,7 +345,7 @@ TEST_F(CompactFilesTest, CompactionFilterWithGetSv) {
|
||||
return true;
|
||||
}
|
||||
std::string res;
|
||||
EXPECT_TRUE(db_->Get(ReadOptions(), "", &res).IsNotFound());
|
||||
db_->Get(ReadOptions(), "", &res);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include <vector>
|
||||
|
||||
#include "db/column_family.h"
|
||||
#include "logging/logging.h"
|
||||
#include "rocksdb/compaction_filter.h"
|
||||
#include "rocksdb/sst_partitioner.h"
|
||||
#include "test_util/sync_point.h"
|
||||
@@ -24,13 +23,14 @@ namespace ROCKSDB_NAMESPACE {
|
||||
const uint64_t kRangeTombstoneSentinel =
|
||||
PackSequenceAndType(kMaxSequenceNumber, kTypeRangeDeletion);
|
||||
|
||||
int sstableKeyCompare(const Comparator* uc, const Slice& a, const Slice& b) {
|
||||
auto c = uc->CompareWithoutTimestamp(ExtractUserKey(a), ExtractUserKey(b));
|
||||
int sstableKeyCompare(const Comparator* user_cmp, const InternalKey& a,
|
||||
const InternalKey& b) {
|
||||
auto c = user_cmp->CompareWithoutTimestamp(a.user_key(), b.user_key());
|
||||
if (c != 0) {
|
||||
return c;
|
||||
}
|
||||
auto a_footer = ExtractInternalKeyFooter(a);
|
||||
auto b_footer = ExtractInternalKeyFooter(b);
|
||||
auto a_footer = ExtractInternalKeyFooter(a.Encode());
|
||||
auto b_footer = ExtractInternalKeyFooter(b.Encode());
|
||||
if (a_footer == kRangeTombstoneSentinel) {
|
||||
if (b_footer != kRangeTombstoneSentinel) {
|
||||
return -1;
|
||||
@@ -204,34 +204,6 @@ bool Compaction::IsFullCompaction(
|
||||
return num_files_in_compaction == total_num_files;
|
||||
}
|
||||
|
||||
const TablePropertiesCollection& Compaction::GetTableProperties() {
|
||||
if (!input_table_properties_initialized_) {
|
||||
const ReadOptions read_options(Env::IOActivity::kCompaction);
|
||||
for (size_t i = 0; i < num_input_levels(); ++i) {
|
||||
for (const FileMetaData* fmd : *(this->inputs(i))) {
|
||||
std::shared_ptr<const TableProperties> tp;
|
||||
std::string file_name =
|
||||
TableFileName(immutable_options_.cf_paths, fmd->fd.GetNumber(),
|
||||
fmd->fd.GetPathId());
|
||||
Status s = input_version_->GetTableProperties(read_options, &tp, fmd,
|
||||
&file_name);
|
||||
if (s.ok()) {
|
||||
table_properties_[file_name] = tp;
|
||||
} else {
|
||||
ROCKS_LOG_ERROR(immutable_options_.info_log,
|
||||
"Unable to load table properties for file %" PRIu64
|
||||
" --- %s\n",
|
||||
fmd->fd.GetNumber(), s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input_table_properties_initialized_ = true;
|
||||
};
|
||||
|
||||
return table_properties_;
|
||||
}
|
||||
|
||||
Compaction::Compaction(
|
||||
VersionStorageInfo* vstorage, const ImmutableOptions& _immutable_options,
|
||||
const MutableCFOptions& _mutable_cf_options,
|
||||
|
||||
@@ -31,19 +31,8 @@ namespace ROCKSDB_NAMESPACE {
|
||||
// that key never appears in the database. We don't want adjacent sstables to
|
||||
// be considered overlapping if they are separated by the range tombstone
|
||||
// sentinel.
|
||||
int sstableKeyCompare(const Comparator* user_cmp, const Slice&, const Slice&);
|
||||
inline int sstableKeyCompare(const Comparator* user_cmp, const Slice& a,
|
||||
const InternalKey& b) {
|
||||
return sstableKeyCompare(user_cmp, a, b.Encode());
|
||||
}
|
||||
inline int sstableKeyCompare(const Comparator* user_cmp, const InternalKey& a,
|
||||
const Slice& b) {
|
||||
return sstableKeyCompare(user_cmp, a.Encode(), b);
|
||||
}
|
||||
inline int sstableKeyCompare(const Comparator* user_cmp, const InternalKey& a,
|
||||
const InternalKey& b) {
|
||||
return sstableKeyCompare(user_cmp, a.Encode(), b.Encode());
|
||||
}
|
||||
int sstableKeyCompare(const Comparator* user_cmp, const InternalKey& a,
|
||||
const InternalKey& b);
|
||||
int sstableKeyCompare(const Comparator* user_cmp, const InternalKey* a,
|
||||
const InternalKey& b);
|
||||
int sstableKeyCompare(const Comparator* user_cmp, const InternalKey& a,
|
||||
@@ -326,16 +315,12 @@ class Compaction {
|
||||
int output_level, VersionStorageInfo* vstorage,
|
||||
const std::vector<CompactionInputFiles>& inputs);
|
||||
|
||||
// If called before a compaction finishes, will return
|
||||
// table properties of all compaction input files.
|
||||
// If called after a compaction finished, will return
|
||||
// table properties of all compaction input and output files.
|
||||
const TablePropertiesCollection& GetTableProperties();
|
||||
TablePropertiesCollection GetOutputTableProperties() const {
|
||||
return output_table_properties_;
|
||||
}
|
||||
|
||||
void SetOutputTableProperties(
|
||||
const std::string& file_name,
|
||||
const std::shared_ptr<const TableProperties>& tp) {
|
||||
table_properties_[file_name] = tp;
|
||||
void SetOutputTableProperties(TablePropertiesCollection tp) {
|
||||
output_table_properties_ = std::move(tp);
|
||||
}
|
||||
|
||||
Slice GetSmallestUserKey() const { return smallest_user_key_; }
|
||||
@@ -522,9 +507,8 @@ class Compaction {
|
||||
// Does input compression match the output compression?
|
||||
bool InputCompressionMatchesOutput() const;
|
||||
|
||||
bool input_table_properties_initialized_ = false;
|
||||
// table properties of output files
|
||||
TablePropertiesCollection table_properties_;
|
||||
TablePropertiesCollection output_table_properties_;
|
||||
|
||||
// smallest user keys in compaction
|
||||
// includes timestamp if user-defined timestamp is enabled.
|
||||
|
||||
@@ -31,8 +31,7 @@ CompactionIterator::CompactionIterator(
|
||||
BlobFileBuilder* blob_file_builder, bool allow_data_in_errors,
|
||||
bool enforce_single_del_contracts,
|
||||
const std::atomic<bool>& manual_compaction_canceled,
|
||||
bool must_count_input_entries, const Compaction* compaction,
|
||||
const CompactionFilter* compaction_filter,
|
||||
const Compaction* compaction, const CompactionFilter* compaction_filter,
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const std::shared_ptr<Logger> info_log,
|
||||
const std::string* full_history_ts_low,
|
||||
@@ -46,9 +45,8 @@ CompactionIterator::CompactionIterator(
|
||||
manual_compaction_canceled,
|
||||
std::unique_ptr<CompactionProxy>(
|
||||
compaction ? new RealCompaction(compaction) : nullptr),
|
||||
must_count_input_entries, compaction_filter, shutting_down, info_log,
|
||||
full_history_ts_low, preserve_time_min_seqno,
|
||||
preclude_last_level_min_seqno) {}
|
||||
compaction_filter, shutting_down, info_log, full_history_ts_low,
|
||||
preserve_time_min_seqno, preclude_last_level_min_seqno) {}
|
||||
|
||||
CompactionIterator::CompactionIterator(
|
||||
InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
|
||||
@@ -60,14 +58,15 @@ CompactionIterator::CompactionIterator(
|
||||
BlobFileBuilder* blob_file_builder, bool allow_data_in_errors,
|
||||
bool enforce_single_del_contracts,
|
||||
const std::atomic<bool>& manual_compaction_canceled,
|
||||
std::unique_ptr<CompactionProxy> compaction, bool must_count_input_entries,
|
||||
std::unique_ptr<CompactionProxy> compaction,
|
||||
const CompactionFilter* compaction_filter,
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const std::shared_ptr<Logger> info_log,
|
||||
const std::string* full_history_ts_low,
|
||||
const SequenceNumber preserve_time_min_seqno,
|
||||
const SequenceNumber preclude_last_level_min_seqno)
|
||||
: input_(input, cmp, must_count_input_entries),
|
||||
: input_(input, cmp,
|
||||
!compaction || compaction->DoesInputReferenceBlobFiles()),
|
||||
cmp_(cmp),
|
||||
merge_helper_(merge_helper),
|
||||
snapshots_(snapshots),
|
||||
|
||||
@@ -38,18 +38,15 @@ class SequenceIterWrapper : public InternalIterator {
|
||||
bool Valid() const override { return inner_iter_->Valid(); }
|
||||
Status status() const override { return inner_iter_->status(); }
|
||||
void Next() override {
|
||||
if (!inner_iter_->IsDeleteRangeSentinelKey()) {
|
||||
num_itered_++;
|
||||
}
|
||||
num_itered_++;
|
||||
inner_iter_->Next();
|
||||
}
|
||||
void Seek(const Slice& target) override {
|
||||
if (!need_count_entries_) {
|
||||
has_num_itered_ = false;
|
||||
inner_iter_->Seek(target);
|
||||
} else {
|
||||
// Need to count total number of entries,
|
||||
// so we do Next() rather than Seek().
|
||||
// For flush cases, we need to count total number of entries, so we
|
||||
// do Next() rather than Seek().
|
||||
while (inner_iter_->Valid() &&
|
||||
icmp_.Compare(inner_iter_->key(), target) < 0) {
|
||||
Next();
|
||||
@@ -65,8 +62,7 @@ class SequenceIterWrapper : public InternalIterator {
|
||||
void SeekForPrev(const Slice& /* target */) override { assert(false); }
|
||||
void SeekToLast() override { assert(false); }
|
||||
|
||||
uint64_t NumItered() const { return num_itered_; }
|
||||
bool HasNumItered() const { return has_num_itered_; }
|
||||
uint64_t num_itered() const { return num_itered_; }
|
||||
bool IsDeleteRangeSentinelKey() const override {
|
||||
assert(Valid());
|
||||
return inner_iter_->IsDeleteRangeSentinelKey();
|
||||
@@ -77,7 +73,6 @@ class SequenceIterWrapper : public InternalIterator {
|
||||
InternalIterator* inner_iter_; // not owned
|
||||
uint64_t num_itered_ = 0;
|
||||
bool need_count_entries_;
|
||||
bool has_num_itered_ = true;
|
||||
};
|
||||
|
||||
class CompactionIterator {
|
||||
@@ -194,10 +189,6 @@ class CompactionIterator {
|
||||
const Compaction* compaction_;
|
||||
};
|
||||
|
||||
// @param must_count_input_entries if true, `NumInputEntryScanned()` will
|
||||
// return the number of input keys scanned. If false, `NumInputEntryScanned()`
|
||||
// will return this number if no Seek was called on `input`. User should call
|
||||
// `HasNumInputEntryScanned()` first in this case.
|
||||
CompactionIterator(
|
||||
InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
|
||||
SequenceNumber last_sequence, std::vector<SequenceNumber>* snapshots,
|
||||
@@ -208,7 +199,7 @@ class CompactionIterator {
|
||||
BlobFileBuilder* blob_file_builder, bool allow_data_in_errors,
|
||||
bool enforce_single_del_contracts,
|
||||
const std::atomic<bool>& manual_compaction_canceled,
|
||||
bool must_count_input_entries, const Compaction* compaction = nullptr,
|
||||
const Compaction* compaction = nullptr,
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
const std::atomic<bool>* shutting_down = nullptr,
|
||||
const std::shared_ptr<Logger> info_log = nullptr,
|
||||
@@ -228,7 +219,6 @@ class CompactionIterator {
|
||||
bool enforce_single_del_contracts,
|
||||
const std::atomic<bool>& manual_compaction_canceled,
|
||||
std::unique_ptr<CompactionProxy> compaction,
|
||||
bool must_count_input_entries,
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
const std::atomic<bool>* shutting_down = nullptr,
|
||||
const std::shared_ptr<Logger> info_log = nullptr,
|
||||
@@ -263,8 +253,7 @@ class CompactionIterator {
|
||||
return current_user_key_;
|
||||
}
|
||||
const CompactionIterationStats& iter_stats() const { return iter_stats_; }
|
||||
bool HasNumInputEntryScanned() const { return input_.HasNumItered(); }
|
||||
uint64_t NumInputEntryScanned() const { return input_.NumItered(); }
|
||||
uint64_t num_input_entry_scanned() const { return input_.num_itered(); }
|
||||
// If the current key should be placed on penultimate level, only valid if
|
||||
// per_key_placement is supported
|
||||
bool output_to_penultimate_level() const {
|
||||
|
||||
@@ -293,8 +293,8 @@ class CompactionIteratorTest : public testing::TestWithParam<bool> {
|
||||
nullptr /* blob_file_builder */, true /*allow_data_in_errors*/,
|
||||
true /*enforce_single_del_contracts*/,
|
||||
/*manual_compaction_canceled=*/kManualCompactionCanceledFalse_,
|
||||
std::move(compaction), /*must_count_input_entries=*/false, filter,
|
||||
&shutting_down_, /*info_log=*/nullptr, full_history_ts_low));
|
||||
std::move(compaction), filter, &shutting_down_, /*info_log=*/nullptr,
|
||||
full_history_ts_low));
|
||||
}
|
||||
|
||||
void AddSnapshot(SequenceNumber snapshot,
|
||||
|
||||
+41
-105
@@ -796,46 +796,15 @@ Status CompactionJob::Run() {
|
||||
auto fn =
|
||||
TableFileName(state.compaction->immutable_options()->cf_paths,
|
||||
output.meta.fd.GetNumber(), output.meta.fd.GetPathId());
|
||||
compact_->compaction->SetOutputTableProperties(fn,
|
||||
output.table_properties);
|
||||
tp[fn] = output.table_properties;
|
||||
}
|
||||
}
|
||||
compact_->compaction->SetOutputTableProperties(std::move(tp));
|
||||
|
||||
// Finish up all bookkeeping to unify the subcompaction results.
|
||||
// Finish up all book-keeping to unify the subcompaction results
|
||||
compact_->AggregateCompactionStats(compaction_stats_, *compaction_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()) &&
|
||||
db_options_.compaction_verify_record_count) {
|
||||
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) {
|
||||
std::string msg =
|
||||
"Total number of input records: " + std::to_string(expected) +
|
||||
", but processed " + std::to_string(actual) + " records.";
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log, "[%s] [JOB %d] Compaction %s",
|
||||
compact_->compaction->column_family_data()->GetName().c_str(),
|
||||
job_context_->job_id, msg.c_str());
|
||||
status = Status::Corruption(
|
||||
"Compaction number of input keys does not match number of keys "
|
||||
"processed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
UpdateCompactionStats();
|
||||
|
||||
RecordCompactionIOStats();
|
||||
LogFlush(db_options_.info_log);
|
||||
TEST_SYNC_POINT("CompactionJob::Run():End");
|
||||
@@ -1127,17 +1096,17 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
// GenSubcompactionBoundaries doesn't strip away the timestamp.
|
||||
size_t ts_sz = cfd->user_comparator()->timestamp_size();
|
||||
if (start.has_value()) {
|
||||
read_options.iterate_lower_bound = &(*start);
|
||||
read_options.iterate_lower_bound = &start.value();
|
||||
if (ts_sz > 0) {
|
||||
start_without_ts = StripTimestampFromUserKey(*start, ts_sz);
|
||||
read_options.iterate_lower_bound = &(*start_without_ts);
|
||||
start_without_ts = StripTimestampFromUserKey(start.value(), ts_sz);
|
||||
read_options.iterate_lower_bound = &start_without_ts.value();
|
||||
}
|
||||
}
|
||||
if (end.has_value()) {
|
||||
read_options.iterate_upper_bound = &(*end);
|
||||
read_options.iterate_upper_bound = &end.value();
|
||||
if (ts_sz > 0) {
|
||||
end_without_ts = StripTimestampFromUserKey(*end, ts_sz);
|
||||
read_options.iterate_upper_bound = &(*end_without_ts);
|
||||
end_without_ts = StripTimestampFromUserKey(end.value(), ts_sz);
|
||||
read_options.iterate_upper_bound = &end_without_ts.value();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1169,7 +1138,8 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
}
|
||||
|
||||
if (start.has_value()) {
|
||||
start_ikey.SetInternalKey(*start, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
start_ikey.SetInternalKey(start.value(), kMaxSequenceNumber,
|
||||
kValueTypeForSeek);
|
||||
if (ts_sz > 0) {
|
||||
start_ikey.UpdateInternalKey(kMaxSequenceNumber, kValueTypeForSeek,
|
||||
&ts_slice);
|
||||
@@ -1178,7 +1148,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
start_user_key = start_ikey.GetUserKey();
|
||||
}
|
||||
if (end.has_value()) {
|
||||
end_ikey.SetInternalKey(*end, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
end_ikey.SetInternalKey(end.value(), kMaxSequenceNumber, kValueTypeForSeek);
|
||||
if (ts_sz > 0) {
|
||||
end_ikey.UpdateInternalKey(kMaxSequenceNumber, kValueTypeForSeek,
|
||||
&ts_slice);
|
||||
@@ -1283,8 +1253,6 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
/*expect_valid_internal_key=*/true, range_del_agg.get(),
|
||||
blob_file_builder.get(), db_options_.allow_data_in_errors,
|
||||
db_options_.enforce_single_del_contracts, manual_compaction_canceled_,
|
||||
sub_compact->compaction
|
||||
->DoesInputReferenceBlobFiles() /* must_count_input_entries */,
|
||||
sub_compact->compaction, compaction_filter, shutting_down_,
|
||||
db_options_.info_log, full_history_ts_low, preserve_time_min_seqno_,
|
||||
preclude_last_level_min_seqno_);
|
||||
@@ -1321,8 +1289,8 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
while (status.ok() && !cfd->IsDropped() && c_iter->Valid()) {
|
||||
// Invariant: c_iter.status() is guaranteed to be OK if c_iter->Valid()
|
||||
// returns true.
|
||||
assert(!end.has_value() ||
|
||||
cfd->user_comparator()->Compare(c_iter->user_key(), *end) < 0);
|
||||
assert(!end.has_value() || cfd->user_comparator()->Compare(
|
||||
c_iter->user_key(), end.value()) < 0);
|
||||
|
||||
if (c_iter_stats.num_input_records % kRecordStatsEvery ==
|
||||
kRecordStatsEvery - 1) {
|
||||
@@ -1349,25 +1317,8 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
if (c_iter->status().IsManualCompactionPaused()) {
|
||||
break;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool stop = false;
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionJob::ProcessKeyValueCompaction()::stop",
|
||||
static_cast<void*>(&stop));
|
||||
if (stop) {
|
||||
break;
|
||||
}
|
||||
#endif // NDEBUG
|
||||
}
|
||||
|
||||
// This number may not be accurate when CompactionIterator was created
|
||||
// with `must_count_input_entries=false`.
|
||||
assert(!sub_compact->compaction->DoesInputReferenceBlobFiles() ||
|
||||
c_iter->HasNumInputEntryScanned());
|
||||
sub_compact->compaction_job_stats.has_num_input_records =
|
||||
c_iter->HasNumInputEntryScanned();
|
||||
sub_compact->compaction_job_stats.num_input_records =
|
||||
c_iter->NumInputEntryScanned();
|
||||
sub_compact->compaction_job_stats.num_blobs_read =
|
||||
c_iter_stats.num_blobs_read;
|
||||
sub_compact->compaction_job_stats.total_blob_bytes_read =
|
||||
@@ -1867,10 +1818,10 @@ Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact,
|
||||
uint64_t current_time = static_cast<uint64_t>(temp_current_time);
|
||||
InternalKey tmp_start, tmp_end;
|
||||
if (sub_compact->start.has_value()) {
|
||||
tmp_start.SetMinPossibleForUserKey(*(sub_compact->start));
|
||||
tmp_start.SetMinPossibleForUserKey(sub_compact->start.value());
|
||||
}
|
||||
if (sub_compact->end.has_value()) {
|
||||
tmp_end.SetMinPossibleForUserKey(*(sub_compact->end));
|
||||
tmp_end.SetMinPossibleForUserKey(sub_compact->end.value());
|
||||
}
|
||||
uint64_t oldest_ancester_time =
|
||||
sub_compact->compaction->MinInputFileOldestAncesterTime(
|
||||
@@ -1953,53 +1904,24 @@ void CopyPrefix(const Slice& src, size_t prefix_length, std::string* dst) {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool CompactionJob::UpdateCompactionStats(uint64_t* num_input_range_del) {
|
||||
|
||||
void CompactionJob::UpdateCompactionStats() {
|
||||
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;
|
||||
|
||||
bool has_error = false;
|
||||
const ReadOptions read_options(Env::IOActivity::kCompaction);
|
||||
const auto& input_table_properties = compaction->GetTableProperties();
|
||||
for (int input_level = 0;
|
||||
input_level < static_cast<int>(compaction->num_input_levels());
|
||||
++input_level) {
|
||||
size_t num_input_files = compaction->num_input_files(input_level);
|
||||
uint64_t* bytes_read;
|
||||
if (compaction->level(input_level) != compaction->output_level()) {
|
||||
compaction_stats_.stats.num_input_files_in_non_output_levels +=
|
||||
static_cast<int>(num_input_files);
|
||||
bytes_read = &compaction_stats_.stats.bytes_read_non_output_levels;
|
||||
UpdateCompactionInputStatsHelper(
|
||||
&compaction_stats_.stats.num_input_files_in_non_output_levels,
|
||||
&compaction_stats_.stats.bytes_read_non_output_levels, input_level);
|
||||
} else {
|
||||
compaction_stats_.stats.num_input_files_in_output_level +=
|
||||
static_cast<int>(num_input_files);
|
||||
bytes_read = &compaction_stats_.stats.bytes_read_output_level;
|
||||
}
|
||||
for (size_t i = 0; i < num_input_files; ++i) {
|
||||
const FileMetaData* file_meta = compaction->input(input_level, i);
|
||||
*bytes_read += file_meta->fd.GetFileSize();
|
||||
uint64_t file_input_entries = file_meta->num_entries;
|
||||
uint64_t file_num_range_del = file_meta->num_range_deletions;
|
||||
if (file_input_entries == 0) {
|
||||
uint64_t file_number = file_meta->fd.GetNumber();
|
||||
// Try getting info from table property
|
||||
std::string fn =
|
||||
TableFileName(compaction->immutable_options()->cf_paths,
|
||||
file_number, file_meta->fd.GetPathId());
|
||||
const auto& tp = input_table_properties.find(fn);
|
||||
if (tp != input_table_properties.end()) {
|
||||
file_input_entries = tp->second->num_entries;
|
||||
file_num_range_del = tp->second->num_range_deletions;
|
||||
} else {
|
||||
has_error = true;
|
||||
}
|
||||
}
|
||||
compaction_stats_.stats.num_input_records += file_input_entries;
|
||||
if (num_input_range_del) {
|
||||
*num_input_range_del += file_num_range_del;
|
||||
}
|
||||
UpdateCompactionInputStatsHelper(
|
||||
&compaction_stats_.stats.num_input_files_in_output_level,
|
||||
&compaction_stats_.stats.bytes_read_output_level, input_level);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2009,7 +1931,21 @@ bool CompactionJob::UpdateCompactionStats(uint64_t* num_input_range_del) {
|
||||
|
||||
compaction_stats_.stats.num_dropped_records =
|
||||
compaction_stats_.DroppedRecords();
|
||||
return !has_error;
|
||||
}
|
||||
|
||||
void CompactionJob::UpdateCompactionInputStatsHelper(int* num_files,
|
||||
uint64_t* bytes_read,
|
||||
int input_level) {
|
||||
const Compaction* compaction = compact_->compaction;
|
||||
auto num_input_files = compaction->num_input_files(input_level);
|
||||
*num_files += static_cast<int>(num_input_files);
|
||||
|
||||
for (size_t i = 0; i < num_input_files; ++i) {
|
||||
const auto* file_meta = compaction->input(input_level, i);
|
||||
*bytes_read += file_meta->fd.GetFileSize();
|
||||
compaction_stats_.stats.num_input_records +=
|
||||
static_cast<uint64_t>(file_meta->num_entries);
|
||||
}
|
||||
}
|
||||
|
||||
void CompactionJob::UpdateCompactionJobStats(
|
||||
|
||||
@@ -192,21 +192,7 @@ 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);
|
||||
void UpdateCompactionStats();
|
||||
void LogCompaction();
|
||||
virtual void RecordCompactionIOStats();
|
||||
void CleanupCompaction();
|
||||
@@ -281,6 +267,9 @@ class CompactionJob {
|
||||
void RecordDroppedKeys(const CompactionIterationStats& c_iter_stats,
|
||||
CompactionJobStats* compaction_job_stats = nullptr);
|
||||
|
||||
void UpdateCompactionInputStatsHelper(int* num_files, uint64_t* bytes_read,
|
||||
int input_level);
|
||||
|
||||
void NotifyOnSubcompactionBegin(SubcompactionState* sub_compact);
|
||||
|
||||
void NotifyOnSubcompactionCompleted(SubcompactionState* sub_compact);
|
||||
|
||||
@@ -655,12 +655,11 @@ class CompactionJobTestBase : public testing::Test {
|
||||
ASSERT_TRUE(full_history_ts_low_.empty() ||
|
||||
ucmp_->timestamp_size() == full_history_ts_low_.size());
|
||||
const std::atomic<bool> kManualCompactionCanceledFalse{false};
|
||||
JobContext job_context(1, false /* create_superversion */);
|
||||
CompactionJob compaction_job(
|
||||
0, &compaction, db_options_, mutable_db_options_, env_options_,
|
||||
versions_.get(), &shutting_down_, &log_buffer, nullptr, nullptr,
|
||||
nullptr, nullptr, &mutex_, &error_handler_, snapshots,
|
||||
earliest_write_conflict_snapshot, snapshot_checker, &job_context,
|
||||
earliest_write_conflict_snapshot, snapshot_checker, nullptr,
|
||||
table_cache_, &event_logger, false, false, dbname_,
|
||||
&compaction_job_stats_, Env::Priority::USER, nullptr /* IOTracer */,
|
||||
/*manual_compaction_canceled=*/kManualCompactionCanceledFalse,
|
||||
|
||||
@@ -127,6 +127,11 @@ size_t CompactionOutputs::UpdateGrandparentBoundaryInfo(
|
||||
if (grandparents.empty()) {
|
||||
return curr_key_boundary_switched_num;
|
||||
}
|
||||
assert(!internal_key.empty());
|
||||
InternalKey ikey;
|
||||
ikey.DecodeFrom(internal_key);
|
||||
assert(ikey.Valid());
|
||||
|
||||
const Comparator* ucmp = compaction_->column_family_data()->user_comparator();
|
||||
|
||||
// Move the grandparent_index_ to the file containing the current user_key.
|
||||
@@ -134,7 +139,7 @@ size_t CompactionOutputs::UpdateGrandparentBoundaryInfo(
|
||||
// index points to the last file containing the key.
|
||||
while (grandparent_index_ < grandparents.size()) {
|
||||
if (being_grandparent_gap_) {
|
||||
if (sstableKeyCompare(ucmp, internal_key,
|
||||
if (sstableKeyCompare(ucmp, ikey,
|
||||
grandparents[grandparent_index_]->smallest) < 0) {
|
||||
break;
|
||||
}
|
||||
@@ -147,13 +152,13 @@ size_t CompactionOutputs::UpdateGrandparentBoundaryInfo(
|
||||
being_grandparent_gap_ = false;
|
||||
} else {
|
||||
int cmp_result = sstableKeyCompare(
|
||||
ucmp, internal_key, grandparents[grandparent_index_]->largest);
|
||||
ucmp, ikey, grandparents[grandparent_index_]->largest);
|
||||
// If it's same key, make sure grandparent_index_ is pointing to the last
|
||||
// one.
|
||||
if (cmp_result < 0 ||
|
||||
(cmp_result == 0 &&
|
||||
(grandparent_index_ == grandparents.size() - 1 ||
|
||||
sstableKeyCompare(ucmp, internal_key,
|
||||
sstableKeyCompare(ucmp, ikey,
|
||||
grandparents[grandparent_index_ + 1]->smallest) <
|
||||
0))) {
|
||||
break;
|
||||
|
||||
@@ -206,10 +206,10 @@ class CompactionOutputs {
|
||||
// We may only split the output when the cursor is in the range. Split
|
||||
if ((!end.has_value() ||
|
||||
icmp->user_comparator()->Compare(
|
||||
ExtractUserKey(output_split_key->Encode()), *end) < 0) &&
|
||||
(!start.has_value() ||
|
||||
icmp->user_comparator()->Compare(
|
||||
ExtractUserKey(output_split_key->Encode()), *start) > 0)) {
|
||||
ExtractUserKey(output_split_key->Encode()), end.value()) < 0) &&
|
||||
(!start.has_value() || icmp->user_comparator()->Compare(
|
||||
ExtractUserKey(output_split_key->Encode()),
|
||||
start.value()) > 0)) {
|
||||
local_output_split_key_ = output_split_key;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,8 +84,7 @@ class CompactionPickerTestBase : public testing::Test {
|
||||
options_.num_levels = num_levels;
|
||||
vstorage_.reset(new VersionStorageInfo(
|
||||
&icmp_, ucmp_, options_.num_levels, style, nullptr, false,
|
||||
EpochNumberRequirement::kMustPresent, ioptions_.clock,
|
||||
options_.bottommost_file_compaction_delay));
|
||||
EpochNumberRequirement::kMustPresent));
|
||||
vstorage_->PrepareForVersionAppend(ioptions_, mutable_cf_options_);
|
||||
}
|
||||
|
||||
@@ -94,8 +93,7 @@ class CompactionPickerTestBase : public testing::Test {
|
||||
void AddVersionStorage() {
|
||||
temp_vstorage_.reset(new VersionStorageInfo(
|
||||
&icmp_, ucmp_, options_.num_levels, ioptions_.compaction_style,
|
||||
vstorage_.get(), false, EpochNumberRequirement::kMustPresent,
|
||||
ioptions_.clock, options_.bottommost_file_compaction_delay));
|
||||
vstorage_.get(), false, EpochNumberRequirement::kMustPresent));
|
||||
}
|
||||
|
||||
void DeleteVersionStorage() {
|
||||
@@ -1970,15 +1968,6 @@ TEST_F(CompactionPickerTest, OverlappingUserKeys11) {
|
||||
ASSERT_EQ(7U, compaction->input(1, 0)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, FileTtlBoosterLargeNumLevels) {
|
||||
const uint64_t kCurrentTime = 1000000;
|
||||
FileTtlBooster booster(kCurrentTime, /*ttl=*/2048,
|
||||
/*num_non_empty_levels=*/100, /*level=*/1);
|
||||
FileMetaData meta;
|
||||
meta.oldest_ancester_time = kCurrentTime - 1023;
|
||||
ASSERT_EQ(1, booster.GetBoostScore(&meta));
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, FileTtlBooster) {
|
||||
// Set TTL to 2048
|
||||
// TTL boosting for all levels starts at 1024,
|
||||
|
||||
@@ -53,10 +53,8 @@ class FileTtlBooster {
|
||||
enabled_ = true;
|
||||
uint64_t all_boost_start_age = ttl / 2;
|
||||
uint64_t all_boost_age_range = (ttl / 32) * 31 - all_boost_start_age;
|
||||
// TODO(cbi): more reasonable algorithm that gives different values
|
||||
// when num_non_empty_levels - level - 1 > 63.
|
||||
uint64_t boost_age_range =
|
||||
all_boost_age_range >> std::min(63, num_non_empty_levels - level - 1);
|
||||
all_boost_age_range >> (num_non_empty_levels - level - 1);
|
||||
boost_age_start_ = all_boost_start_age + boost_age_range;
|
||||
const uint64_t kBoostRatio = 16;
|
||||
// prevent 0 value to avoid divide 0 error.
|
||||
|
||||
@@ -77,7 +77,7 @@ void DoRandomIteraratorTest(DB* db, std::vector<std::string> source_strings,
|
||||
|
||||
for (int i = 0; i < num_writes; i++) {
|
||||
if (num_trigger_flush > 0 && i != 0 && i % num_trigger_flush == 0) {
|
||||
ASSERT_OK(db->Flush(FlushOptions()));
|
||||
db->Flush(FlushOptions());
|
||||
}
|
||||
|
||||
int type = rnd->Uniform(2);
|
||||
@@ -156,7 +156,6 @@ void DoRandomIteraratorTest(DB* db, std::vector<std::string> source_strings,
|
||||
if (map.find(key) == map.end()) {
|
||||
ASSERT_TRUE(status.IsNotFound());
|
||||
} else {
|
||||
ASSERT_OK(status);
|
||||
ASSERT_EQ(map[key], result);
|
||||
}
|
||||
break;
|
||||
|
||||
+3
-20
@@ -7,7 +7,6 @@
|
||||
|
||||
#include "rocksdb/convenience.h"
|
||||
|
||||
#include "db/convenience_impl.h"
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "util/cast_util.h"
|
||||
|
||||
@@ -40,25 +39,9 @@ Status VerifySstFileChecksum(const Options& options,
|
||||
}
|
||||
Status VerifySstFileChecksum(const Options& options,
|
||||
const EnvOptions& env_options,
|
||||
const ReadOptions& _read_options,
|
||||
const ReadOptions& read_options,
|
||||
const std::string& file_path,
|
||||
const SequenceNumber& largest_seqno) {
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown) {
|
||||
return Status::InvalidArgument(
|
||||
"Can only call VerifySstFileChecksum with `ReadOptions::io_activity` "
|
||||
"is "
|
||||
"`Env::IOActivity::kUnknown`");
|
||||
}
|
||||
ReadOptions read_options(_read_options);
|
||||
return VerifySstFileChecksumInternal(options, env_options, read_options,
|
||||
file_path, largest_seqno);
|
||||
}
|
||||
|
||||
Status VerifySstFileChecksumInternal(const Options& options,
|
||||
const EnvOptions& env_options,
|
||||
const ReadOptions& read_options,
|
||||
const std::string& file_path,
|
||||
const SequenceNumber& largest_seqno) {
|
||||
std::unique_ptr<FSRandomAccessFile> file;
|
||||
uint64_t file_size;
|
||||
InternalKeyComparator internal_comparator(options.comparator);
|
||||
@@ -85,8 +68,8 @@ Status VerifySstFileChecksumInternal(const Options& options,
|
||||
!kImmortal, false /* force_direct_prefetch */, -1 /* level */);
|
||||
reader_options.largest_seqno = largest_seqno;
|
||||
s = ioptions.table_factory->NewTableReader(
|
||||
read_options, reader_options, std::move(file_reader), file_size,
|
||||
&table_reader, false /* prefetch_index_and_filter_in_cache */);
|
||||
reader_options, std::move(file_reader), file_size, &table_reader,
|
||||
false /* prefetch_index_and_filter_in_cache */);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#pragma once
|
||||
#include "rocksdb/db.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
Status VerifySstFileChecksumInternal(const Options& options,
|
||||
const EnvOptions& env_options,
|
||||
const ReadOptions& read_options,
|
||||
const std::string& file_path,
|
||||
const SequenceNumber& largest_seqno = 0);
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+3
-17
@@ -1204,23 +1204,9 @@ TEST_F(DBBasicTest, DBClose) {
|
||||
delete db;
|
||||
ASSERT_EQ(env->GetCloseCount(), 2);
|
||||
|
||||
// close by WaitForCompact() with close_db option
|
||||
options.create_if_missing = false;
|
||||
s = DB::Open(options, dbname, &db);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_TRUE(db != nullptr);
|
||||
WaitForCompactOptions wait_for_compact_options = WaitForCompactOptions();
|
||||
wait_for_compact_options.close_db = true;
|
||||
s = db->WaitForCompact(wait_for_compact_options);
|
||||
ASSERT_EQ(env->GetCloseCount(), 3);
|
||||
// see TestLogger::CloseHelper()
|
||||
ASSERT_EQ(s, Status::IOError());
|
||||
|
||||
delete db;
|
||||
ASSERT_EQ(env->GetCloseCount(), 3);
|
||||
|
||||
// Provide our own logger and ensure DB::Close() does not close it
|
||||
options.info_log.reset(new TestEnv::TestLogger(env));
|
||||
options.create_if_missing = false;
|
||||
s = DB::Open(options, dbname, &db);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_TRUE(db != nullptr);
|
||||
@@ -1228,9 +1214,9 @@ TEST_F(DBBasicTest, DBClose) {
|
||||
s = db->Close();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
delete db;
|
||||
ASSERT_EQ(env->GetCloseCount(), 3);
|
||||
ASSERT_EQ(env->GetCloseCount(), 2);
|
||||
options.info_log.reset();
|
||||
ASSERT_EQ(env->GetCloseCount(), 4);
|
||||
ASSERT_EQ(env->GetCloseCount(), 3);
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, DBCloseAllDirectoryFDs) {
|
||||
|
||||
@@ -741,15 +741,10 @@ TEST_F(DBBlockCacheTest, AddRedundantStats) {
|
||||
int iterations_tested = 0;
|
||||
for (std::shared_ptr<Cache> base_cache :
|
||||
{NewLRUCache(capacity, num_shard_bits),
|
||||
// FixedHyperClockCache
|
||||
HyperClockCacheOptions(
|
||||
capacity,
|
||||
BlockBasedTableOptions().block_size /*estimated_value_size*/,
|
||||
num_shard_bits)
|
||||
.MakeSharedCache(),
|
||||
// AutoHyperClockCache
|
||||
HyperClockCacheOptions(capacity, 0 /*estimated_value_size*/,
|
||||
num_shard_bits)
|
||||
.MakeSharedCache()}) {
|
||||
if (!base_cache) {
|
||||
// Skip clock cache when not supported
|
||||
|
||||
+14
-14
@@ -654,7 +654,7 @@ TEST_P(DBBloomFilterTestWithParam, SkipFilterOnEssentiallyZeroBpk) {
|
||||
for (i = 0; i < maxKey; i++) {
|
||||
ASSERT_OK(Put(Key(i), Key(i)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
Flush();
|
||||
};
|
||||
auto GetFn = [&]() {
|
||||
int i;
|
||||
@@ -792,7 +792,7 @@ TEST_F(DBBloomFilterTest, BloomFilterRate) {
|
||||
}
|
||||
// Add a large key to make the file contain wide range
|
||||
ASSERT_OK(Put(1, Key(maxKey + 55555), Key(maxKey + 55555)));
|
||||
ASSERT_OK(Flush(1));
|
||||
Flush(1);
|
||||
|
||||
// Check if they can be found
|
||||
for (int i = 0; i < maxKey; i++) {
|
||||
@@ -1696,7 +1696,7 @@ TEST_F(DBBloomFilterTest, ContextCustomFilterPolicy) {
|
||||
table_options.format_version = 5;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
ASSERT_OK(TryReopen(options));
|
||||
TryReopen(options);
|
||||
CreateAndReopenWithCF({fifo ? "abe" : "bob"}, options);
|
||||
|
||||
const int maxKey = 10000;
|
||||
@@ -1705,7 +1705,7 @@ TEST_F(DBBloomFilterTest, ContextCustomFilterPolicy) {
|
||||
}
|
||||
// Add a large key to make the file contain wide range
|
||||
ASSERT_OK(Put(1, Key(maxKey + 55555), Key(maxKey + 55555)));
|
||||
ASSERT_OK(Flush(1));
|
||||
Flush(1);
|
||||
EXPECT_EQ(policy->DumpTestReport(),
|
||||
fifo ? "cf=abe,s=kCompactionStyleFIFO,n=7,l=0,b=0,r=kFlush\n"
|
||||
: "cf=bob,s=kCompactionStyleLevel,n=7,l=0,b=0,r=kFlush\n");
|
||||
@@ -1713,7 +1713,7 @@ TEST_F(DBBloomFilterTest, ContextCustomFilterPolicy) {
|
||||
for (int i = maxKey / 2; i < maxKey; i++) {
|
||||
ASSERT_OK(Put(1, Key(i), Key(i)));
|
||||
}
|
||||
ASSERT_OK(Flush(1));
|
||||
Flush(1);
|
||||
EXPECT_EQ(policy->DumpTestReport(),
|
||||
fifo ? "cf=abe,s=kCompactionStyleFIFO,n=7,l=0,b=0,r=kFlush\n"
|
||||
: "cf=bob,s=kCompactionStyleLevel,n=7,l=0,b=0,r=kFlush\n");
|
||||
@@ -2261,7 +2261,7 @@ TEST_P(BloomStatsTestWithParam, BloomStatsTest) {
|
||||
ASSERT_EQ(0, get_perf_context()->bloom_sst_hit_count);
|
||||
ASSERT_EQ(0, get_perf_context()->bloom_sst_miss_count);
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
Flush();
|
||||
|
||||
// sanity checks
|
||||
ASSERT_EQ(0, get_perf_context()->bloom_sst_hit_count);
|
||||
@@ -2311,7 +2311,7 @@ TEST_P(BloomStatsTestWithParam, BloomStatsTestWithIter) {
|
||||
ASSERT_EQ(1, get_perf_context()->bloom_memtable_miss_count);
|
||||
ASSERT_EQ(2, get_perf_context()->bloom_memtable_hit_count);
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
Flush();
|
||||
|
||||
iter.reset(dbfull()->NewIterator(ReadOptions()));
|
||||
|
||||
@@ -2379,7 +2379,7 @@ void PrefixScanInit(DBBloomFilterTest* dbtest) {
|
||||
snprintf(buf, sizeof(buf), "%02d______:end", i + 1);
|
||||
keystr = std::string(buf);
|
||||
ASSERT_OK(dbtest->Put(keystr, keystr));
|
||||
ASSERT_OK(dbtest->Flush());
|
||||
dbtest->Flush();
|
||||
}
|
||||
|
||||
// GROUP 2
|
||||
@@ -2390,7 +2390,7 @@ void PrefixScanInit(DBBloomFilterTest* dbtest) {
|
||||
snprintf(buf, sizeof(buf), "%02d______:end", small_range_sstfiles + i + 1);
|
||||
keystr = std::string(buf);
|
||||
ASSERT_OK(dbtest->Put(keystr, keystr));
|
||||
ASSERT_OK(dbtest->Flush());
|
||||
dbtest->Flush();
|
||||
}
|
||||
}
|
||||
} // anonymous namespace
|
||||
@@ -2853,7 +2853,7 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterMultipleSST) {
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ASSERT_OK(Put("foq1", "bar1"));
|
||||
ASSERT_OK(Put("fpa", "0"));
|
||||
ASSERT_OK(dbfull()->Flush(FlushOptions()));
|
||||
dbfull()->Flush(FlushOptions());
|
||||
std::unique_ptr<Iterator> iter_old(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter_old, "foo"), 4);
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTERED), 0);
|
||||
@@ -2981,7 +2981,7 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterNewColumnFamily) {
|
||||
ASSERT_OK(Put(2, "foo5", "bar5"));
|
||||
ASSERT_OK(Put(2, "foq6", "bar6"));
|
||||
ASSERT_OK(Put(2, "fpq7", "bar7"));
|
||||
ASSERT_OK(dbfull()->Flush(FlushOptions()));
|
||||
dbfull()->Flush(FlushOptions());
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(
|
||||
db_->NewIterator(read_options, handles_[2]));
|
||||
@@ -3031,17 +3031,17 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterOptions) {
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ASSERT_OK(Put("foo1", "bar1"));
|
||||
ASSERT_OK(Put("fpa", "0"));
|
||||
ASSERT_OK(dbfull()->Flush(FlushOptions()));
|
||||
dbfull()->Flush(FlushOptions());
|
||||
ASSERT_OK(Put("foo3", "bar3"));
|
||||
ASSERT_OK(Put("foo4", "bar4"));
|
||||
ASSERT_OK(Put("foo5", "bar5"));
|
||||
ASSERT_OK(Put("fpb", "1"));
|
||||
ASSERT_OK(dbfull()->Flush(FlushOptions()));
|
||||
dbfull()->Flush(FlushOptions());
|
||||
ASSERT_OK(Put("foo6", "bar6"));
|
||||
ASSERT_OK(Put("foo7", "bar7"));
|
||||
ASSERT_OK(Put("foo8", "bar8"));
|
||||
ASSERT_OK(Put("fpc", "2"));
|
||||
ASSERT_OK(dbfull()->Flush(FlushOptions()));
|
||||
dbfull()->Flush(FlushOptions());
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.prefix_same_as_start = true;
|
||||
|
||||
+31
-267
@@ -153,23 +153,17 @@ class DBCompactionDirectIOTest : public DBCompactionTest,
|
||||
DBCompactionDirectIOTest() : DBCompactionTest() {}
|
||||
};
|
||||
|
||||
// Params: See WaitForCompactOptions for details
|
||||
class DBCompactionWaitForCompactTest
|
||||
: public DBTestBase,
|
||||
public testing::WithParamInterface<
|
||||
std::tuple<bool, bool, bool, std::chrono::microseconds>> {
|
||||
public testing::WithParamInterface<std::tuple<bool, bool>> {
|
||||
public:
|
||||
DBCompactionWaitForCompactTest()
|
||||
: DBTestBase("db_compaction_test", /*env_do_fsync=*/true) {
|
||||
abort_on_pause_ = std::get<0>(GetParam());
|
||||
flush_ = std::get<1>(GetParam());
|
||||
close_db_ = std::get<2>(GetParam());
|
||||
timeout_ = std::get<3>(GetParam());
|
||||
}
|
||||
bool abort_on_pause_;
|
||||
bool flush_;
|
||||
bool close_db_;
|
||||
std::chrono::microseconds timeout_;
|
||||
Options options_;
|
||||
WaitForCompactOptions wait_for_compact_options_;
|
||||
|
||||
@@ -185,8 +179,6 @@ class DBCompactionWaitForCompactTest
|
||||
wait_for_compact_options_ = WaitForCompactOptions();
|
||||
wait_for_compact_options_.abort_on_pause = abort_on_pause_;
|
||||
wait_for_compact_options_.flush = flush_;
|
||||
wait_for_compact_options_.close_db = close_db_;
|
||||
wait_for_compact_options_.timeout = timeout_;
|
||||
|
||||
DestroyAndReopen(options_);
|
||||
|
||||
@@ -3339,19 +3331,12 @@ TEST_F(DBCompactionTest, SuggestCompactRangeNoTwoLevel0Compactions) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
DBCompactionWaitForCompactTest, DBCompactionWaitForCompactTest,
|
||||
::testing::Combine(
|
||||
testing::Bool() /* abort_on_pause */, testing::Bool() /* flush */,
|
||||
testing::Bool() /* close_db */,
|
||||
testing::Values(
|
||||
std::chrono::microseconds::zero(),
|
||||
std::chrono::microseconds{
|
||||
60 * 60 *
|
||||
1000000ULL} /* timeout */))); // 1 hour (long enough to
|
||||
// make sure that tests
|
||||
// don't fail unexpectedly
|
||||
// when running slow)
|
||||
INSTANTIATE_TEST_CASE_P(DBCompactionWaitForCompactTest,
|
||||
DBCompactionWaitForCompactTest,
|
||||
::testing::Values(std::make_tuple(false, false),
|
||||
std::make_tuple(false, true),
|
||||
std::make_tuple(true, false),
|
||||
std::make_tuple(true, true)));
|
||||
|
||||
TEST_P(DBCompactionWaitForCompactTest,
|
||||
WaitForCompactWaitsOnCompactionToFinish) {
|
||||
@@ -3491,19 +3476,19 @@ TEST_P(DBCompactionWaitForCompactTest, WaitForCompactWithOptionToFlush) {
|
||||
ASSERT_EQ("2", FilesPerLevel());
|
||||
|
||||
ASSERT_OK(dbfull()->WaitForCompact(wait_for_compact_options_));
|
||||
ASSERT_EQ(flush_, compaction_finished);
|
||||
ASSERT_EQ(flush_, flush_finished);
|
||||
|
||||
if (!close_db_) {
|
||||
std::string expected_files_per_level = flush_ ? "1,2" : "2";
|
||||
ASSERT_EQ(expected_files_per_level, FilesPerLevel());
|
||||
if (flush_) {
|
||||
ASSERT_EQ("1,2", FilesPerLevel());
|
||||
ASSERT_EQ(1, compaction_finished);
|
||||
ASSERT_EQ(1, flush_finished);
|
||||
} else {
|
||||
ASSERT_EQ(0, compaction_finished);
|
||||
ASSERT_EQ(0, flush_finished);
|
||||
ASSERT_EQ("2", FilesPerLevel());
|
||||
}
|
||||
|
||||
compaction_finished = 0;
|
||||
flush_finished = 0;
|
||||
if (!close_db_) {
|
||||
Close();
|
||||
}
|
||||
Close();
|
||||
Reopen(options_);
|
||||
|
||||
ASSERT_EQ(0, flush_finished);
|
||||
@@ -3518,123 +3503,12 @@ TEST_P(DBCompactionWaitForCompactTest, WaitForCompactWithOptionToFlush) {
|
||||
ASSERT_EQ(1, compaction_finished);
|
||||
}
|
||||
|
||||
if (!close_db_) {
|
||||
ASSERT_EQ("1,2", FilesPerLevel());
|
||||
}
|
||||
ASSERT_EQ("1,2", FilesPerLevel());
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_P(DBCompactionWaitForCompactTest,
|
||||
WaitForCompactWithOptionToFlushAndCloseDB) {
|
||||
// After creating enough L0 files that one more file will trigger the
|
||||
// compaction, write some data in memtable (WAL disabled). Calls
|
||||
// WaitForCompact. If flush option is true, WaitForCompact will flush the
|
||||
// memtable to a new L0 file which will trigger compaction. We expect the
|
||||
// no-op second flush upon closing because WAL is disabled
|
||||
// (has_unpersisted_data_ true) Check to make sure there's no extra L0 file
|
||||
// created from WAL. Re-opening DB won't trigger any flush or compaction
|
||||
|
||||
int compaction_finished = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction:Finish",
|
||||
[&](void*) { compaction_finished++; });
|
||||
|
||||
int flush_finished = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"FlushJob::End", [&](void*) { flush_finished++; });
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_FALSE(options_.avoid_flush_during_shutdown);
|
||||
|
||||
// write to memtable, but no flush is needed at this point.
|
||||
WriteOptions write_without_wal;
|
||||
write_without_wal.disableWAL = true;
|
||||
ASSERT_OK(Put(Key(0), "some random string", write_without_wal));
|
||||
ASSERT_EQ(0, compaction_finished);
|
||||
ASSERT_EQ(0, flush_finished);
|
||||
ASSERT_EQ("2", FilesPerLevel());
|
||||
|
||||
ASSERT_OK(dbfull()->WaitForCompact(wait_for_compact_options_));
|
||||
|
||||
int expected_flush_count = flush_ || close_db_;
|
||||
ASSERT_EQ(expected_flush_count, flush_finished);
|
||||
|
||||
if (!close_db_) {
|
||||
// During CancelAllBackgroundWork(), a flush can be initiated due to
|
||||
// unpersisted data (data that's still in the memtable when WAL is off).
|
||||
// This results in an additional L0 file which can trigger a compaction.
|
||||
// However, the compaction may not complete if the background thread's
|
||||
// execution is slow enough for the front thread to set the 'shutting_down_'
|
||||
// flag to true before the compaction job even starts.
|
||||
ASSERT_EQ(expected_flush_count, compaction_finished);
|
||||
Close();
|
||||
}
|
||||
|
||||
// Because we had has_unpersisted_data_ = true, flush must have been triggered
|
||||
// upon closing regardless of WaitForCompact. Reopen should have no flush
|
||||
// debt.
|
||||
flush_finished = 0;
|
||||
Reopen(options_);
|
||||
ASSERT_EQ(0, flush_finished);
|
||||
|
||||
// However, if db was closed directly by calling Close(), instead
|
||||
// of WaitForCompact with close_db option or we are in the scenario commented
|
||||
// above, it's possible that the last compaction triggered by flushing
|
||||
// unpersisted data was cancelled. Call WaitForCompact() here again to finish
|
||||
// the compaction
|
||||
if (compaction_finished == 0) {
|
||||
ASSERT_OK(dbfull()->WaitForCompact(wait_for_compact_options_));
|
||||
}
|
||||
ASSERT_EQ(1, compaction_finished);
|
||||
if (!close_db_) {
|
||||
ASSERT_EQ("1,2", FilesPerLevel());
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_P(DBCompactionWaitForCompactTest, WaitForCompactToTimeout) {
|
||||
// When timeout is set, this test makes CompactionJob hangs forever
|
||||
// using sync point. This test also sets the timeout to be 1 ms for
|
||||
// WaitForCompact to time out early. WaitForCompact() is expected to return
|
||||
// Status::TimedOut.
|
||||
// When timeout is not set, we expect WaitForCompact() to wait indefinitely.
|
||||
// We don't want the test to hang forever. When timeout = 0, this test is not
|
||||
// much different from WaitForCompactWaitsOnCompactionToFinish
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBCompactionTest::WaitForCompactToTimeout",
|
||||
"CompactionJob::Run():Start"}});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Now trigger L0 compaction by adding a file
|
||||
Random rnd(123);
|
||||
GenerateNewRandomFile(&rnd, /* nowait */ true);
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
if (wait_for_compact_options_.timeout.count()) {
|
||||
// Make timeout shorter to finish test early
|
||||
wait_for_compact_options_.timeout = std::chrono::microseconds{1000};
|
||||
} else {
|
||||
// if timeout is not set, WaitForCompact() will wait forever. We don't
|
||||
// want test to hang forever. Just let compaction go through
|
||||
TEST_SYNC_POINT("DBCompactionTest::WaitForCompactToTimeout");
|
||||
}
|
||||
Status s = dbfull()->WaitForCompact(wait_for_compact_options_);
|
||||
if (wait_for_compact_options_.timeout.count()) {
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_TRUE(s.IsTimedOut());
|
||||
} else {
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
static std::string ShortKey(int i) {
|
||||
assert(i < 10000);
|
||||
char buf[100];
|
||||
@@ -4178,6 +4052,11 @@ TEST_F(DBCompactionTest, CompactBottomLevelFilesWithDeletions) {
|
||||
// files does not need to be preserved in case of a future snapshot.
|
||||
ASSERT_OK(Put(Key(0), "val"));
|
||||
ASSERT_NE(kMaxSequenceNumber, dbfull()->bottommost_files_mark_threshold_);
|
||||
// release snapshot and wait for compactions to finish. Single-file
|
||||
// compactions should be triggered, which reduce the size of each bottom-level
|
||||
// file without changing file count.
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
ASSERT_EQ(kMaxSequenceNumber, dbfull()->bottommost_files_mark_threshold_);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"LevelCompactionPicker::PickCompaction:Return", [&](void* arg) {
|
||||
Compaction* compaction = reinterpret_cast<Compaction*>(arg);
|
||||
@@ -4185,11 +4064,6 @@ TEST_F(DBCompactionTest, CompactBottomLevelFilesWithDeletions) {
|
||||
CompactionReason::kBottommostFiles);
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
// release snapshot and wait for compactions to finish. Single-file
|
||||
// compactions should be triggered, which reduce the size of each bottom-level
|
||||
// file without changing file count.
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
ASSERT_EQ(kMaxSequenceNumber, dbfull()->bottommost_files_mark_threshold_);
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
db_->GetLiveFilesMetaData(&post_release_metadata);
|
||||
ASSERT_EQ(pre_release_metadata.size(), post_release_metadata.size());
|
||||
@@ -4206,78 +4080,6 @@ TEST_F(DBCompactionTest, CompactBottomLevelFilesWithDeletions) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, DelayCompactBottomLevelFilesWithDeletions) {
|
||||
// bottom-level files may contain deletions due to snapshots protecting the
|
||||
// deleted keys. Once the snapshot is released and the files are old enough,
|
||||
// we should see them undergo single-file compactions.
|
||||
Options options = CurrentOptions();
|
||||
env_->SetMockSleep();
|
||||
options.bottommost_file_compaction_delay = 3600;
|
||||
DestroyAndReopen(options);
|
||||
CreateColumnFamilies({"one"}, options);
|
||||
const int kNumKey = 100;
|
||||
const int kValLen = 100;
|
||||
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < kNumKey; ++i) {
|
||||
ASSERT_OK(Put(Key(i), rnd.RandomString(kValLen)));
|
||||
}
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
for (int i = 0; i < kNumKey; i += 2) {
|
||||
ASSERT_OK(Delete(Key(i)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(1);
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(1));
|
||||
|
||||
std::vector<LiveFileMetaData> pre_release_metadata;
|
||||
db_->GetLiveFilesMetaData(&pre_release_metadata);
|
||||
ASSERT_EQ(1, pre_release_metadata.size());
|
||||
std::atomic_int compaction_count = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"LevelCompactionPicker::PickCompaction:Return", [&](void* arg) {
|
||||
Compaction* compaction = reinterpret_cast<Compaction*>(arg);
|
||||
ASSERT_TRUE(compaction->compaction_reason() ==
|
||||
CompactionReason::kBottommostFiles);
|
||||
compaction_count++;
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
// just need to bump seqnum so ReleaseSnapshot knows the newest key in the SST
|
||||
// files does not need to be preserved in case of a future snapshot.
|
||||
ASSERT_OK(Put(Key(0), "val"));
|
||||
ASSERT_NE(kMaxSequenceNumber, dbfull()->bottommost_files_mark_threshold_);
|
||||
// release snapshot will not trigger compaction.
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
ASSERT_EQ(kMaxSequenceNumber, dbfull()->bottommost_files_mark_threshold_);
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(0, compaction_count);
|
||||
// Now the file is old enough for compaction.
|
||||
env_->MockSleepForSeconds(3600);
|
||||
// Another flush will trigger re-computation of the compaction score
|
||||
// to find out that the file is qualified for compaction.
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(1, compaction_count);
|
||||
|
||||
std::vector<LiveFileMetaData> post_release_metadata;
|
||||
db_->GetLiveFilesMetaData(&post_release_metadata);
|
||||
ASSERT_EQ(2, post_release_metadata.size());
|
||||
|
||||
const auto& pre_file = pre_release_metadata[0];
|
||||
// Get the L1 (bottommost level) file.
|
||||
const auto& post_file = post_release_metadata[0].level == 0
|
||||
? post_release_metadata[1]
|
||||
: post_release_metadata[0];
|
||||
|
||||
ASSERT_EQ(1, pre_file.level);
|
||||
ASSERT_EQ(1, post_file.level);
|
||||
// the file is smaller than it was before as it was rewritten without
|
||||
// deletion markers/deleted keys.
|
||||
ASSERT_LT(post_file.size, pre_file.size);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, NoCompactBottomLevelFilesWithDeletions) {
|
||||
// bottom-level files may contain deletions due to snapshots protecting the
|
||||
// deleted keys. Once the snapshot is released, we should see files with many
|
||||
@@ -4798,7 +4600,6 @@ TEST_F(DBCompactionTest, LevelTtlCascadingCompactions) {
|
||||
PutVarint64(encoded_fieled, 0);
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
options.env = env_;
|
||||
|
||||
@@ -4815,6 +4616,7 @@ TEST_F(DBCompactionTest, LevelTtlCascadingCompactions) {
|
||||
ttl_compactions++;
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Add two L6 files with key ranges: [1 .. 100], [101 .. 200].
|
||||
Random rnd(301);
|
||||
@@ -4946,7 +4748,6 @@ TEST_F(DBCompactionTest, LevelPeriodicCompaction) {
|
||||
PutVarint64(encoded_fieled, 0);
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
options.env = env_;
|
||||
|
||||
@@ -4963,6 +4764,7 @@ TEST_F(DBCompactionTest, LevelPeriodicCompaction) {
|
||||
periodic_compactions++;
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < kNumLevelFiles; ++i) {
|
||||
@@ -5429,12 +5231,6 @@ TEST_F(DBCompactionTest, CompactRangeDelayedByImmMemTableCount) {
|
||||
}
|
||||
|
||||
auto manual_compaction_thread = port::Thread([this]() {
|
||||
// Write something to make the current Memtable non-empty, so an extra
|
||||
// immutable Memtable will be created upon manual flush requested by
|
||||
// CompactRange, triggering a write stall mode to be entered because of
|
||||
// accumulation of write buffers due to manual flush.
|
||||
Random compact_rnd(301);
|
||||
ASSERT_OK(Put(Key(0), compact_rnd.RandomString(1024)));
|
||||
CompactRangeOptions cro;
|
||||
cro.allow_write_stall = false;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
@@ -6025,18 +5821,23 @@ TEST_P(DBCompactionDirectIOTest, DirectIO) {
|
||||
options.use_direct_io_for_flush_and_compaction = GetParam();
|
||||
options.env = MockEnv::Create(Env::Default());
|
||||
Reopen(options);
|
||||
bool readahead = false;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionJob::OpenCompactionOutputFile", [&](void* arg) {
|
||||
bool* use_direct_writes = static_cast<bool*>(arg);
|
||||
ASSERT_EQ(*use_direct_writes,
|
||||
options.use_direct_io_for_flush_and_compaction);
|
||||
});
|
||||
if (options.use_direct_io_for_flush_and_compaction) {
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"SanitizeOptions:direct_io", [&](void* /*arg*/) { readahead = true; });
|
||||
}
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
MakeTables(3, "p", "q", 1);
|
||||
ASSERT_EQ("1,1,1", FilesPerLevel(1));
|
||||
Compact(1, "p", "q");
|
||||
ASSERT_EQ(false, options.use_direct_reads);
|
||||
ASSERT_EQ(readahead, options.use_direct_reads);
|
||||
ASSERT_EQ("0,0,1", FilesPerLevel(1));
|
||||
Destroy(options);
|
||||
delete options.env;
|
||||
@@ -9985,7 +9786,7 @@ TEST_F(DBCompactionTest, NumberOfSubcompactions) {
|
||||
SubCompactionEventListener* listener = new SubCompactionEventListener();
|
||||
options.listeners.clear();
|
||||
options.listeners.emplace_back(listener);
|
||||
ASSERT_OK(TryReopen(options));
|
||||
TryReopen(options);
|
||||
|
||||
for (int file = 0; file < kLevel0CompactTrigger; ++file) {
|
||||
for (int key = file; key < 2 * kNumKeyPerFile; key += 2) {
|
||||
@@ -10000,43 +9801,6 @@ TEST_F(DBCompactionTest, NumberOfSubcompactions) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, VerifyRecordCount) {
|
||||
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());
|
||||
|
||||
for (int i = 0; i < 20; i += 2) {
|
||||
ASSERT_OK(Put(Key(i), rnd.RandomString(100)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// 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();
|
||||
|
||||
Status s = db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
const char* expect =
|
||||
"Compaction number of input keys does not match number of keys "
|
||||
"processed.";
|
||||
ASSERT_TRUE(std::strstr(s.getState(), expect));
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+1
-274
@@ -222,7 +222,7 @@ TEST_F(DBFlushTest, CloseDBWhenFlushInLowPri) {
|
||||
sleeping_task_low.WaitUntilDone();
|
||||
ASSERT_EQ(0, num_flushes);
|
||||
|
||||
ASSERT_OK(TryReopenWithColumnFamilies({"default", "cf1", "cf2"}, options));
|
||||
TryReopenWithColumnFamilies({"default", "cf1", "cf2"}, options);
|
||||
ASSERT_OK(Put(0, "key3", DummyString(8192)));
|
||||
ASSERT_OK(Flush(0));
|
||||
ASSERT_EQ(1, num_flushes);
|
||||
@@ -3193,279 +3193,6 @@ INSTANTIATE_TEST_CASE_P(DBFlushDirectIOTest, DBFlushDirectIOTest,
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBAtomicFlushTest, DBAtomicFlushTest, testing::Bool());
|
||||
|
||||
TEST_F(DBFlushTest, NonAtomicFlushRollbackPendingFlushes) {
|
||||
// Fix a bug in when atomic_flush=false.
|
||||
// The bug can happen as follows:
|
||||
// Start Flush0 for memtable M0 to SST0
|
||||
// Start Flush1 for memtable M1 to SST1
|
||||
// Flush1 returns OK, but don't install to MANIFEST and let whoever flushes
|
||||
// M0 to take care of it
|
||||
// Flush0 finishes with a retryable IOError
|
||||
// - It rollbacks M0, (incorrectly) not M1
|
||||
// - Deletes SST1 and SST2
|
||||
//
|
||||
// Auto-recovery will start Flush2 for M0, it does not pick up M1 since it
|
||||
// thinks that M1 is flushed
|
||||
// Flush2 writes SST3 and finishes OK, tries to install SST3 and SST2
|
||||
// Error opening SST2 since it's already deleted
|
||||
//
|
||||
// The fix is to let Flush0 also rollback M1.
|
||||
Options opts = CurrentOptions();
|
||||
opts.atomic_flush = false;
|
||||
opts.memtable_factory.reset(test::NewSpecialSkipListFactory(1));
|
||||
opts.max_write_buffer_number = 64;
|
||||
opts.max_background_flushes = 4;
|
||||
env_->SetBackgroundThreads(4, Env::HIGH);
|
||||
DestroyAndReopen(opts);
|
||||
std::atomic_int flush_count = 0;
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"FlushJob::WriteLevel0Table:s", [&](void* s_ptr) {
|
||||
int c = flush_count.fetch_add(1);
|
||||
if (c == 0) {
|
||||
Status* s = (Status*)(s_ptr);
|
||||
IOStatus io_error = IOStatus::IOError("injected foobar");
|
||||
io_error.SetRetryable(true);
|
||||
*s = io_error;
|
||||
TEST_SYNC_POINT("Let mem1 flush start");
|
||||
TEST_SYNC_POINT("Wait for mem1 flush to finish");
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"Let mem1 flush start", "Mem1 flush starts"},
|
||||
{"DBImpl::BGWorkFlush:done", "Wait for mem1 flush to finish"},
|
||||
{"RecoverFromRetryableBGIOError:RecoverSuccess",
|
||||
"Wait for error recover"}});
|
||||
// Need first flush to wait for the second flush to finish
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
ASSERT_OK(Put(Key(1), "val1"));
|
||||
// trigger bg flush mem0
|
||||
ASSERT_OK(Put(Key(2), "val2"));
|
||||
TEST_SYNC_POINT("Mem1 flush starts");
|
||||
// trigger bg flush mem1
|
||||
ASSERT_OK(Put(Key(3), "val3"));
|
||||
|
||||
TEST_SYNC_POINT("Wait for error recover");
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBFlushTest, AbortNonAtomicFlushWhenBGError) {
|
||||
// Fix a bug in when atomic_flush=false.
|
||||
// The bug can happen as follows:
|
||||
// Start Flush0 for memtable M0 to SST0
|
||||
// Start Flush1 for memtable M1 to SST1
|
||||
// Flush1 returns OK, but doesn't install output MANIFEST and let whoever
|
||||
// flushes M0 to take care of it
|
||||
// Start Flush2 for memtable M2 to SST2
|
||||
// Flush0 finishes with a retryable IOError
|
||||
// - It rollbacks M0 AND M1
|
||||
// - Deletes SST1 and SST2
|
||||
// Flush2 finishes, does not rollback M2,
|
||||
// - releases the pending file number that keeps SST2 alive
|
||||
// - deletes SST2
|
||||
//
|
||||
// Then auto-recovery starts, error opening SST2 when try to install
|
||||
// flush result
|
||||
//
|
||||
// The fix is to let Flush2 rollback M2 if it finds that
|
||||
// there is a background error.
|
||||
Options opts = CurrentOptions();
|
||||
opts.atomic_flush = false;
|
||||
opts.memtable_factory.reset(test::NewSpecialSkipListFactory(1));
|
||||
opts.max_write_buffer_number = 64;
|
||||
opts.max_background_flushes = 4;
|
||||
env_->SetBackgroundThreads(4, Env::HIGH);
|
||||
DestroyAndReopen(opts);
|
||||
std::atomic_int flush_count = 0;
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"FlushJob::WriteLevel0Table:s", [&](void* s_ptr) {
|
||||
int c = flush_count.fetch_add(1);
|
||||
if (c == 0) {
|
||||
Status* s = (Status*)(s_ptr);
|
||||
IOStatus io_error = IOStatus::IOError("injected foobar");
|
||||
io_error.SetRetryable(true);
|
||||
*s = io_error;
|
||||
TEST_SYNC_POINT("Let mem1 flush start");
|
||||
TEST_SYNC_POINT("Wait for mem1 flush to finish");
|
||||
|
||||
TEST_SYNC_POINT("Let mem2 flush start");
|
||||
TEST_SYNC_POINT("Wait for mem2 to start writing table");
|
||||
}
|
||||
});
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"FlushJob::WriteLevel0Table", [&](void* mems) {
|
||||
autovector<MemTable*>* mems_ptr = (autovector<MemTable*>*)mems;
|
||||
if ((*mems_ptr)[0]->GetID() == 3) {
|
||||
TEST_SYNC_POINT("Mem2 flush starts writing table");
|
||||
TEST_SYNC_POINT("Mem2 flush waits until rollback");
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"Let mem1 flush start", "Mem1 flush starts"},
|
||||
{"DBImpl::BGWorkFlush:done", "Wait for mem1 flush to finish"},
|
||||
{"Let mem2 flush start", "Mem2 flush starts"},
|
||||
{"Mem2 flush starts writing table",
|
||||
"Wait for mem2 to start writing table"},
|
||||
{"RollbackMemtableFlush", "Mem2 flush waits until rollback"},
|
||||
{"RecoverFromRetryableBGIOError:RecoverSuccess",
|
||||
"Wait for error recover"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(Put(Key(1), "val1"));
|
||||
// trigger bg flush mem0
|
||||
ASSERT_OK(Put(Key(2), "val2"));
|
||||
TEST_SYNC_POINT("Mem1 flush starts");
|
||||
// trigger bg flush mem1
|
||||
ASSERT_OK(Put(Key(3), "val3"));
|
||||
|
||||
TEST_SYNC_POINT("Mem2 flush starts");
|
||||
ASSERT_OK(Put(Key(4), "val4"));
|
||||
|
||||
TEST_SYNC_POINT("Wait for error recover");
|
||||
// Recovery flush writes 3 memtables together into 1 file.
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBFlushTest, NonAtomicNormalFlushAbortWhenBGError) {
|
||||
Options opts = CurrentOptions();
|
||||
opts.atomic_flush = false;
|
||||
opts.memtable_factory.reset(test::NewSpecialSkipListFactory(1));
|
||||
opts.max_write_buffer_number = 64;
|
||||
opts.max_background_flushes = 1;
|
||||
env_->SetBackgroundThreads(2, Env::HIGH);
|
||||
DestroyAndReopen(opts);
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
std::atomic_int flush_write_table_count = 0;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"FlushJob::WriteLevel0Table:s", [&](void* s_ptr) {
|
||||
int c = flush_write_table_count.fetch_add(1);
|
||||
if (c == 0) {
|
||||
Status* s = (Status*)(s_ptr);
|
||||
IOStatus io_error = IOStatus::IOError("injected foobar");
|
||||
io_error.SetRetryable(true);
|
||||
*s = io_error;
|
||||
}
|
||||
});
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"Let error recovery start",
|
||||
"RecoverFromRetryableBGIOError:BeforeStart"},
|
||||
{"RecoverFromRetryableBGIOError:RecoverSuccess",
|
||||
"Wait for error recover"}});
|
||||
|
||||
ASSERT_OK(Put(Key(1), "val1"));
|
||||
// trigger bg flush0 for mem0
|
||||
ASSERT_OK(Put(Key(2), "val2"));
|
||||
// Not checking status since this wait can finish before flush starts.
|
||||
dbfull()->TEST_WaitForFlushMemTable().PermitUncheckedError();
|
||||
|
||||
// trigger bg flush1 for mem1, should see bg error and abort
|
||||
// before picking a memtable to flush
|
||||
ASSERT_OK(Put(Key(3), "val3"));
|
||||
ASSERT_NOK(dbfull()->TEST_WaitForFlushMemTable());
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
|
||||
TEST_SYNC_POINT("Let error recovery start");
|
||||
TEST_SYNC_POINT("Wait for error recover");
|
||||
// Recovery flush writes 2 memtables together into 1 file.
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
// 1 for flush 0 and 1 for recovery flush
|
||||
ASSERT_EQ(2, flush_write_table_count);
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBFlushTest, DBStuckAfterAtomicFlushError) {
|
||||
// Test for a bug with atomic flush where DB can become stuck
|
||||
// after a flush error. A repro timeline:
|
||||
//
|
||||
// Start Flush0 for mem0
|
||||
// Start Flush1 for mem1
|
||||
// Now Flush1 will wait for Flush0 to install mem0
|
||||
// Flush0 finishes with retryable IOError, rollbacks mem0
|
||||
// Resume starts and waits for background job to finish, i.e., Flush1
|
||||
// Fill memtable again, trigger Flush2 for mem0
|
||||
// Flush2 will get error status, and not rollback mem0, see code in
|
||||
// https://github.com/facebook/rocksdb/blob/b927ba5936216861c2c35ab68f50ba4a78e65747/db/db_impl/db_impl_compaction_flush.cc#L725
|
||||
//
|
||||
// DB is stuck since mem0 can never be picked now
|
||||
//
|
||||
// The fix is to rollback mem0 in Flush2, and let Flush1 also abort upon
|
||||
// background error besides waiting for older memtables to be installed.
|
||||
// The recovery flush in this case should pick up all memtables
|
||||
// and write them to a single L0 file.
|
||||
Options opts = CurrentOptions();
|
||||
opts.atomic_flush = true;
|
||||
opts.memtable_factory.reset(test::NewSpecialSkipListFactory(1));
|
||||
opts.max_write_buffer_number = 64;
|
||||
opts.max_background_flushes = 4;
|
||||
env_->SetBackgroundThreads(4, Env::HIGH);
|
||||
DestroyAndReopen(opts);
|
||||
|
||||
std::atomic_int flush_count = 0;
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"FlushJob::WriteLevel0Table:s", [&](void* s_ptr) {
|
||||
int c = flush_count.fetch_add(1);
|
||||
if (c == 0) {
|
||||
Status* s = (Status*)(s_ptr);
|
||||
IOStatus io_error = IOStatus::IOError("injected foobar");
|
||||
io_error.SetRetryable(true);
|
||||
*s = io_error;
|
||||
TEST_SYNC_POINT("Let flush for mem1 start");
|
||||
// Wait for Flush1 to start waiting to install flush result
|
||||
TEST_SYNC_POINT("Wait for flush for mem1");
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"Let flush for mem1 start", "Flush for mem1"},
|
||||
{"DBImpl::AtomicFlushMemTablesToOutputFiles:WaitCV",
|
||||
"Wait for flush for mem1"},
|
||||
{"RecoverFromRetryableBGIOError:BeforeStart",
|
||||
"Wait for resume to start"},
|
||||
{"Recovery should continue here",
|
||||
"RecoverFromRetryableBGIOError:BeforeStart2"},
|
||||
{"RecoverFromRetryableBGIOError:RecoverSuccess",
|
||||
"Wait for error recover"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
ASSERT_OK(Put(Key(1), "val1"));
|
||||
// trigger Flush0 for mem0
|
||||
ASSERT_OK(Put(Key(2), "val2"));
|
||||
|
||||
// trigger Flush1 for mem1
|
||||
TEST_SYNC_POINT("Flush for mem1");
|
||||
ASSERT_OK(Put(Key(3), "val3"));
|
||||
|
||||
// Wait until resume started to schedule another flush
|
||||
TEST_SYNC_POINT("Wait for resume to start");
|
||||
// This flush should not be scheduled due to bg error
|
||||
ASSERT_OK(Put(Key(4), "val4"));
|
||||
|
||||
// TEST_WaitForBackgroundWork() returns background error
|
||||
// after all background work is done.
|
||||
ASSERT_NOK(dbfull()->TEST_WaitForBackgroundWork());
|
||||
// Flush should abort and not writing any table
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
|
||||
// Wait until this flush is done.
|
||||
TEST_SYNC_POINT("Recovery should continue here");
|
||||
TEST_SYNC_POINT("Wait for error recover");
|
||||
// error recovery can schedule new flushes, but should not
|
||||
// encounter error
|
||||
ASSERT_OK(dbfull()->TEST_WaitForBackgroundWork());
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -43,25 +43,18 @@ Status CompactedDBImpl::Get(const ReadOptions& options, ColumnFamilyHandle*,
|
||||
/*timestamp*/ nullptr);
|
||||
}
|
||||
|
||||
Status CompactedDBImpl::Get(const ReadOptions& _read_options,
|
||||
ColumnFamilyHandle*, const Slice& key,
|
||||
PinnableSlice* value, std::string* timestamp) {
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kGet) {
|
||||
Status CompactedDBImpl::Get(const ReadOptions& options, ColumnFamilyHandle*,
|
||||
const Slice& key, PinnableSlice* value,
|
||||
std::string* timestamp) {
|
||||
if (options.io_activity != Env::IOActivity::kUnknown) {
|
||||
return Status::InvalidArgument(
|
||||
"Can only call Get with `ReadOptions::io_activity` is "
|
||||
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kGet`");
|
||||
"Cannot call Get with `ReadOptions::io_activity` != "
|
||||
"`Env::IOActivity::kUnknown`");
|
||||
}
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kGet;
|
||||
}
|
||||
|
||||
assert(user_comparator_);
|
||||
if (read_options.timestamp) {
|
||||
const Status s =
|
||||
FailIfTsMismatchCf(DefaultColumnFamily(), *(read_options.timestamp),
|
||||
/*ts_for_read=*/true);
|
||||
if (options.timestamp) {
|
||||
const Status s = FailIfTsMismatchCf(
|
||||
DefaultColumnFamily(), *(options.timestamp), /*ts_for_read=*/true);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -81,7 +74,7 @@ Status CompactedDBImpl::Get(const ReadOptions& _read_options,
|
||||
GetWithTimestampReadCallback read_cb(kMaxSequenceNumber);
|
||||
std::string* ts =
|
||||
user_comparator_->timestamp_size() > 0 ? timestamp : nullptr;
|
||||
LookupKey lkey(key, kMaxSequenceNumber, read_options.timestamp);
|
||||
LookupKey lkey(key, kMaxSequenceNumber, options.timestamp);
|
||||
GetContext get_context(user_comparator_, nullptr, nullptr, nullptr,
|
||||
GetContext::kNotFound, lkey.user_key(), value,
|
||||
/*columns=*/nullptr, ts, nullptr, nullptr, true,
|
||||
@@ -95,8 +88,8 @@ Status CompactedDBImpl::Get(const ReadOptions& _read_options,
|
||||
/*b_has_ts=*/false) < 0) {
|
||||
return Status::NotFound();
|
||||
}
|
||||
Status s = f.fd.table_reader->Get(read_options, lkey.internal_key(),
|
||||
&get_context, nullptr);
|
||||
Status s = f.fd.table_reader->Get(options, lkey.internal_key(), &get_context,
|
||||
nullptr);
|
||||
if (!s.ok() && !s.IsNotFound()) {
|
||||
return s;
|
||||
}
|
||||
@@ -113,28 +106,15 @@ std::vector<Status> CompactedDBImpl::MultiGet(
|
||||
}
|
||||
|
||||
std::vector<Status> CompactedDBImpl::MultiGet(
|
||||
const ReadOptions& _read_options, const std::vector<ColumnFamilyHandle*>&,
|
||||
const ReadOptions& options, const std::vector<ColumnFamilyHandle*>&,
|
||||
const std::vector<Slice>& keys, std::vector<std::string>* values,
|
||||
std::vector<std::string>* timestamps) {
|
||||
assert(user_comparator_);
|
||||
size_t num_keys = keys.size();
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kMultiGet) {
|
||||
Status s = Status::InvalidArgument(
|
||||
"Can only call MultiGet with `ReadOptions::io_activity` is "
|
||||
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kMultiGet`");
|
||||
return std::vector<Status>(num_keys, s);
|
||||
}
|
||||
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kMultiGet;
|
||||
}
|
||||
|
||||
if (read_options.timestamp) {
|
||||
Status s =
|
||||
FailIfTsMismatchCf(DefaultColumnFamily(), *(read_options.timestamp),
|
||||
/*ts_for_read=*/true);
|
||||
if (options.timestamp) {
|
||||
Status s = FailIfTsMismatchCf(DefaultColumnFamily(), *(options.timestamp),
|
||||
/*ts_for_read=*/true);
|
||||
if (!s.ok()) {
|
||||
return std::vector<Status>(num_keys, s);
|
||||
}
|
||||
@@ -156,7 +136,7 @@ std::vector<Status> CompactedDBImpl::MultiGet(
|
||||
GetWithTimestampReadCallback read_cb(kMaxSequenceNumber);
|
||||
autovector<TableReader*, 16> reader_list;
|
||||
for (const auto& key : keys) {
|
||||
LookupKey lkey(key, kMaxSequenceNumber, read_options.timestamp);
|
||||
LookupKey lkey(key, kMaxSequenceNumber, options.timestamp);
|
||||
const FdWithKeyRange& f = files_.files[FindFile(lkey.user_key())];
|
||||
if (user_comparator_->CompareWithoutTimestamp(
|
||||
key, /*a_has_ts=*/false,
|
||||
@@ -179,15 +159,14 @@ std::vector<Status> CompactedDBImpl::MultiGet(
|
||||
if (r != nullptr) {
|
||||
PinnableSlice pinnable_val;
|
||||
std::string& value = (*values)[idx];
|
||||
LookupKey lkey(keys[idx], kMaxSequenceNumber, read_options.timestamp);
|
||||
LookupKey lkey(keys[idx], kMaxSequenceNumber, options.timestamp);
|
||||
std::string* timestamp = timestamps ? &(*timestamps)[idx] : nullptr;
|
||||
GetContext get_context(
|
||||
user_comparator_, nullptr, nullptr, nullptr, GetContext::kNotFound,
|
||||
lkey.user_key(), &pinnable_val, /*columns=*/nullptr,
|
||||
user_comparator_->timestamp_size() > 0 ? timestamp : nullptr, nullptr,
|
||||
nullptr, true, nullptr, nullptr, nullptr, nullptr, &read_cb);
|
||||
Status s =
|
||||
r->Get(read_options, lkey.internal_key(), &get_context, nullptr);
|
||||
Status s = r->Get(options, lkey.internal_key(), &get_context, nullptr);
|
||||
assert(static_cast<size_t>(idx) < statuses.size());
|
||||
if (!s.ok() && !s.IsNotFound()) {
|
||||
statuses[idx] = s;
|
||||
|
||||
@@ -30,9 +30,9 @@ class CompactedDBImpl : public DBImpl {
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value) override;
|
||||
|
||||
Status Get(const ReadOptions& _read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value, std::string* timestamp) override;
|
||||
Status Get(const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
const Slice& key, PinnableSlice* value,
|
||||
std::string* timestamp) override;
|
||||
|
||||
using DB::MultiGet;
|
||||
// Note that CompactedDBImpl::MultiGet is not the optimized version of
|
||||
@@ -43,7 +43,7 @@ class CompactedDBImpl : public DBImpl {
|
||||
const std::vector<Slice>& keys,
|
||||
std::vector<std::string>* values) override;
|
||||
|
||||
std::vector<Status> MultiGet(const ReadOptions& _read_options,
|
||||
std::vector<Status> MultiGet(const ReadOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>&,
|
||||
const std::vector<Slice>& keys,
|
||||
std::vector<std::string>* values,
|
||||
|
||||
+111
-235
@@ -28,7 +28,6 @@
|
||||
#include "db/arena_wrapped_db_iter.h"
|
||||
#include "db/builder.h"
|
||||
#include "db/compaction/compaction_job.h"
|
||||
#include "db/convenience_impl.h"
|
||||
#include "db/db_info_dumper.h"
|
||||
#include "db/db_iter.h"
|
||||
#include "db/dbformat.h"
|
||||
@@ -75,6 +74,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"
|
||||
@@ -149,24 +149,6 @@ void DumpSupportInfo(Logger* logger) {
|
||||
|
||||
ROCKS_LOG_HEADER(logger, "DMutex implementation: %s", DMutex::kName());
|
||||
}
|
||||
|
||||
// `start` is the inclusive lower user key bound without user-defined timestamp
|
||||
// `limit` is the exclusive upper user key bound without user-defined timestamp
|
||||
std::tuple<Slice, Slice> MaybeAddTimestampsToRange(const Slice& start,
|
||||
const Slice& limit,
|
||||
size_t ts_sz,
|
||||
std::string* start_with_ts,
|
||||
std::string* limit_with_ts) {
|
||||
if (ts_sz == 0) {
|
||||
return std::make_tuple(start, limit);
|
||||
}
|
||||
// Maximum timestamp means including all key with any timestamp
|
||||
AppendKeyWithMaxTimestamp(start_with_ts, start, ts_sz);
|
||||
// Append a maximum timestamp as the range limit is exclusive:
|
||||
// [start, limit)
|
||||
AppendKeyWithMaxTimestamp(limit_with_ts, limit, ts_sz);
|
||||
return std::make_tuple(Slice(*start_with_ts), Slice(*limit_with_ts));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
@@ -201,7 +183,6 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
batch_per_txn_(batch_per_txn),
|
||||
next_job_id_(1),
|
||||
shutting_down_(false),
|
||||
reject_new_background_jobs_(false),
|
||||
db_lock_(nullptr),
|
||||
manual_compaction_paused_(false),
|
||||
bg_cv_(&mutex_),
|
||||
@@ -411,7 +392,22 @@ Status DBImpl::ResumeImpl(DBRecoverContext context) {
|
||||
FlushOptions flush_opts;
|
||||
// We allow flush to stall write since we are trying to resume from error.
|
||||
flush_opts.allow_write_stall = true;
|
||||
s = FlushAllColumnFamilies(flush_opts, context.flush_reason);
|
||||
if (immutable_db_options_.atomic_flush) {
|
||||
mutex_.Unlock();
|
||||
s = AtomicFlushMemTables(flush_opts, context.flush_reason);
|
||||
mutex_.Lock();
|
||||
} else {
|
||||
for (auto cfd : versions_->GetRefedColumnFamilySet()) {
|
||||
if (cfd->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
InstrumentedMutexUnlock u(&mutex_);
|
||||
s = FlushMemTable(cfd, flush_opts, context.flush_reason);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"DB resume requested but failed due to Flush failure [%s]",
|
||||
@@ -471,28 +467,6 @@ Status DBImpl::ResumeImpl(DBRecoverContext context) {
|
||||
if (shutdown_initiated_) {
|
||||
s = Status::ShutdownInProgress();
|
||||
}
|
||||
if (s.ok() && context.flush_after_recovery) {
|
||||
// Since we drop all non-recovery flush requests during recovery,
|
||||
// and new memtable may fill up during recovery,
|
||||
// schedule one more round of flush.
|
||||
FlushOptions flush_opts;
|
||||
flush_opts.allow_write_stall = false;
|
||||
flush_opts.wait = false;
|
||||
Status status = FlushAllColumnFamilies(
|
||||
flush_opts, FlushReason::kCatchUpAfterErrorRecovery);
|
||||
if (!status.ok()) {
|
||||
// FlushAllColumnFamilies internally should take care of setting
|
||||
// background error if needed.
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"The catch up flush after successful recovery failed [%s]",
|
||||
s.ToString().c_str());
|
||||
}
|
||||
// FlushAllColumnFamilies releases and re-acquires mutex.
|
||||
if (shutdown_initiated_) {
|
||||
s = Status::ShutdownInProgress();
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
SchedulePendingCompaction(cfd);
|
||||
@@ -520,15 +494,36 @@ void DBImpl::WaitForBackgroundWork() {
|
||||
void DBImpl::CancelAllBackgroundWork(bool wait) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Shutdown: canceling all background work");
|
||||
Status s = CancelPeriodicTaskScheduler();
|
||||
s.PermitUncheckedError();
|
||||
|
||||
for (uint8_t task_type = 0;
|
||||
task_type < static_cast<uint8_t>(PeriodicTaskType::kMax); task_type++) {
|
||||
Status s = periodic_task_scheduler_.Unregister(
|
||||
static_cast<PeriodicTaskType>(task_type));
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"Failed to unregister periodic task %d, status: %s",
|
||||
task_type, s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
if (!shutting_down_.load(std::memory_order_acquire) &&
|
||||
has_unpersisted_data_.load(std::memory_order_relaxed) &&
|
||||
!mutable_db_options_.avoid_flush_during_shutdown) {
|
||||
s = DBImpl::FlushAllColumnFamilies(FlushOptions(), FlushReason::kShutDown);
|
||||
s.PermitUncheckedError(); //**TODO: What to do on error?
|
||||
if (immutable_db_options_.atomic_flush) {
|
||||
mutex_.Unlock();
|
||||
Status s = AtomicFlushMemTables(FlushOptions(), FlushReason::kShutDown);
|
||||
s.PermitUncheckedError(); //**TODO: What to do on error?
|
||||
mutex_.Lock();
|
||||
} else {
|
||||
for (auto cfd : versions_->GetRefedColumnFamilySet()) {
|
||||
if (!cfd->IsDropped() && cfd->initialized() && !cfd->mem()->IsEmpty()) {
|
||||
InstrumentedMutexUnlock u(&mutex_);
|
||||
Status s = FlushMemTable(cfd, FlushOptions(), FlushReason::kShutDown);
|
||||
s.PermitUncheckedError(); //**TODO: What to do on error?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shutting_down_.store(true, std::memory_order_release);
|
||||
@@ -877,21 +872,6 @@ Status DBImpl::RegisterRecordSeqnoTimeWorker() {
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::CancelPeriodicTaskScheduler() {
|
||||
Status s = Status::OK();
|
||||
for (uint8_t task_type = 0;
|
||||
task_type < static_cast<uint8_t>(PeriodicTaskType::kMax); task_type++) {
|
||||
s = periodic_task_scheduler_.Unregister(
|
||||
static_cast<PeriodicTaskType>(task_type));
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"Failed to unregister periodic task %d, status: %s",
|
||||
task_type, s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// esitmate the total size of stats_history_
|
||||
size_t DBImpl::EstimateInMemoryStatsHistorySize() const {
|
||||
size_t size_total =
|
||||
@@ -990,7 +970,7 @@ void DBImpl::PersistStats() {
|
||||
"Storing %" ROCKSDB_PRIszt " stats with timestamp %" PRIu64
|
||||
" to in-memory stats history",
|
||||
stats_slice_.size(), now_seconds);
|
||||
stats_history_[now_seconds] = std::move(stats_delta);
|
||||
stats_history_[now_seconds] = stats_delta;
|
||||
}
|
||||
stats_slice_initialized_ = true;
|
||||
std::swap(stats_slice_, stats_map);
|
||||
@@ -1942,43 +1922,15 @@ Status DBImpl::Get(const ReadOptions& read_options,
|
||||
return Get(read_options, column_family, key, value, /*timestamp=*/nullptr);
|
||||
}
|
||||
|
||||
Status DBImpl::GetImpl(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value) {
|
||||
return GetImpl(read_options, column_family, key, value,
|
||||
/*timestamp=*/nullptr);
|
||||
}
|
||||
|
||||
Status DBImpl::Get(const ReadOptions& _read_options,
|
||||
Status DBImpl::Get(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value, std::string* timestamp) {
|
||||
assert(value != nullptr);
|
||||
value->Reset();
|
||||
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kGet) {
|
||||
return Status::InvalidArgument(
|
||||
"Can only call Get with `ReadOptions::io_activity` is "
|
||||
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kGet`");
|
||||
}
|
||||
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kGet;
|
||||
}
|
||||
|
||||
Status s = GetImpl(read_options, column_family, key, value, timestamp);
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::GetImpl(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value, std::string* timestamp) {
|
||||
GetImplOptions get_impl_options;
|
||||
get_impl_options.column_family = column_family;
|
||||
get_impl_options.value = value;
|
||||
get_impl_options.timestamp = timestamp;
|
||||
|
||||
Status s = GetImpl(read_options, key, get_impl_options);
|
||||
return s;
|
||||
}
|
||||
@@ -2047,6 +1999,11 @@ Status DBImpl::GetImpl(const ReadOptions& read_options, const Slice& key,
|
||||
|
||||
assert(get_impl_options.column_family);
|
||||
|
||||
if (read_options.io_activity != Env::IOActivity::kUnknown) {
|
||||
return Status::InvalidArgument(
|
||||
"Cannot call Get with `ReadOptions::io_activity` != "
|
||||
"`Env::IOActivity::kUnknown`");
|
||||
}
|
||||
|
||||
if (read_options.timestamp) {
|
||||
const Status s = FailIfTsMismatchCf(get_impl_options.column_family,
|
||||
@@ -2340,7 +2297,7 @@ std::vector<Status> DBImpl::MultiGet(
|
||||
}
|
||||
|
||||
std::vector<Status> DBImpl::MultiGet(
|
||||
const ReadOptions& _read_options,
|
||||
const ReadOptions& read_options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_family,
|
||||
const std::vector<Slice>& keys, std::vector<std::string>* values,
|
||||
std::vector<std::string>* timestamps) {
|
||||
@@ -2352,30 +2309,12 @@ std::vector<Status> DBImpl::MultiGet(
|
||||
assert(column_family.size() == num_keys);
|
||||
std::vector<Status> stat_list(num_keys);
|
||||
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kMultiGet) {
|
||||
Status s = Status::InvalidArgument(
|
||||
"Can only call MultiGet with `ReadOptions::io_activity` is "
|
||||
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kMultiGet`");
|
||||
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
stat_list[i] = s;
|
||||
}
|
||||
return stat_list;
|
||||
}
|
||||
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kMultiGet;
|
||||
}
|
||||
|
||||
bool should_fail = false;
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
assert(column_family[i]);
|
||||
if (read_options.timestamp) {
|
||||
stat_list[i] =
|
||||
FailIfTsMismatchCf(column_family[i], *(read_options.timestamp),
|
||||
/*ts_for_read=*/true);
|
||||
stat_list[i] = FailIfTsMismatchCf(
|
||||
column_family[i], *(read_options.timestamp), /*ts_for_read=*/true);
|
||||
if (!stat_list[i].ok()) {
|
||||
should_fail = true;
|
||||
}
|
||||
@@ -2688,26 +2627,10 @@ void DBImpl::MultiGet(const ReadOptions& read_options, const size_t num_keys,
|
||||
/* timestamps */ nullptr, statuses, sorted_input);
|
||||
}
|
||||
|
||||
void DBImpl::MultiGet(const ReadOptions& _read_options, const size_t num_keys,
|
||||
void DBImpl::MultiGet(const ReadOptions& read_options, const size_t num_keys,
|
||||
ColumnFamilyHandle** column_families, const Slice* keys,
|
||||
PinnableSlice* values, std::string* timestamps,
|
||||
Status* statuses, const bool sorted_input) {
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kMultiGet) {
|
||||
Status s = Status::InvalidArgument(
|
||||
"Can only call MultiGet with `ReadOptions::io_activity` is "
|
||||
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kMultiGet`");
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
if (statuses[i].ok()) {
|
||||
statuses[i] = s;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kMultiGet;
|
||||
}
|
||||
MultiGetCommon(read_options, num_keys, column_families, keys, values,
|
||||
/* columns */ nullptr, timestamps, statuses, sorted_input);
|
||||
}
|
||||
@@ -2722,6 +2645,7 @@ void DBImpl::MultiGetCommon(const ReadOptions& read_options,
|
||||
if (num_keys == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool should_fail = false;
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
ColumnFamilyHandle* cfh = column_families[i];
|
||||
@@ -2904,28 +2828,11 @@ void DBImpl::MultiGet(const ReadOptions& read_options,
|
||||
/* timestamps */ nullptr, statuses, sorted_input);
|
||||
}
|
||||
|
||||
void DBImpl::MultiGet(const ReadOptions& _read_options,
|
||||
void DBImpl::MultiGet(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const size_t num_keys,
|
||||
const Slice* keys, PinnableSlice* values,
|
||||
std::string* timestamps, Status* statuses,
|
||||
const bool sorted_input) {
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kMultiGet) {
|
||||
Status s = Status::InvalidArgument(
|
||||
"Can only call MultiGet with `ReadOptions::io_activity` is "
|
||||
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kMultiGet`");
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
if (statuses[i].ok()) {
|
||||
statuses[i] = s;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kMultiGet;
|
||||
}
|
||||
MultiGetCommon(read_options, column_family, num_keys, keys, values,
|
||||
/* columns */ nullptr, timestamps, statuses, sorted_input);
|
||||
}
|
||||
@@ -2974,20 +2881,9 @@ void DBImpl::MultiGetCommon(const ReadOptions& read_options,
|
||||
}
|
||||
|
||||
void DBImpl::MultiGetWithCallback(
|
||||
const ReadOptions& _read_options, ColumnFamilyHandle* column_family,
|
||||
const ReadOptions& read_options, ColumnFamilyHandle* column_family,
|
||||
ReadCallback* callback,
|
||||
autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE>* sorted_keys) {
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kMultiGet) {
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kMultiGet;
|
||||
}
|
||||
|
||||
std::array<MultiGetColumnFamilyData, 1> multiget_cf_data;
|
||||
multiget_cf_data[0] = MultiGetColumnFamilyData(column_family, nullptr);
|
||||
std::function<MultiGetColumnFamilyData*(
|
||||
@@ -3058,6 +2954,11 @@ Status DBImpl::MultiGetImpl(
|
||||
autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE>* sorted_keys,
|
||||
SuperVersion* super_version, SequenceNumber snapshot,
|
||||
ReadCallback* callback) {
|
||||
if (read_options.io_activity != Env::IOActivity::kUnknown) {
|
||||
return Status::InvalidArgument(
|
||||
"Cannot call MultiGet with `ReadOptions::io_activity` != "
|
||||
"`Env::IOActivity::kUnknown`");
|
||||
}
|
||||
PERF_CPU_TIMER_GUARD(get_cpu_nanos, immutable_db_options_.clock);
|
||||
StopWatch sw(immutable_db_options_.clock, stats_, DB_MULTIGET);
|
||||
|
||||
@@ -3290,8 +3191,6 @@ Status DBImpl::CreateColumnFamilyImpl(const ColumnFamilyOptions& cf_options,
|
||||
edit.SetColumnFamily(new_id);
|
||||
edit.SetLogNumber(logfile_number_);
|
||||
edit.SetComparatorName(cf_options.comparator->Name());
|
||||
edit.SetPersistUserDefinedTimestamps(
|
||||
cf_options.persist_user_defined_timestamps);
|
||||
|
||||
// LogAndApply will both write the creation in MANIFEST and create
|
||||
// ColumnFamilyData object
|
||||
@@ -3482,19 +3381,8 @@ bool DBImpl::KeyMayExist(const ReadOptions& read_options,
|
||||
return s.ok() || s.IsIncomplete();
|
||||
}
|
||||
|
||||
Iterator* DBImpl::NewIterator(const ReadOptions& _read_options,
|
||||
Iterator* DBImpl::NewIterator(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family) {
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kDBIterator) {
|
||||
return NewErrorIterator(Status::InvalidArgument(
|
||||
"Can only call NewIterator with `ReadOptions::io_activity` is "
|
||||
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kDBIterator`"));
|
||||
}
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kDBIterator;
|
||||
}
|
||||
|
||||
if (read_options.managed) {
|
||||
return NewErrorIterator(
|
||||
Status::NotSupported("Managed iterator is not supported anymore."));
|
||||
@@ -3504,12 +3392,17 @@ Iterator* DBImpl::NewIterator(const ReadOptions& _read_options,
|
||||
return NewErrorIterator(Status::NotSupported(
|
||||
"ReadTier::kPersistedData is not yet supported in iterators."));
|
||||
}
|
||||
if (read_options.io_activity != Env::IOActivity::kUnknown) {
|
||||
return NewErrorIterator(Status::InvalidArgument(
|
||||
"Cannot call NewIterator with `ReadOptions::io_activity` != "
|
||||
"`Env::IOActivity::kUnknown`"));
|
||||
}
|
||||
|
||||
assert(column_family);
|
||||
|
||||
if (read_options.timestamp) {
|
||||
const Status s =
|
||||
FailIfTsMismatchCf(column_family, *(read_options.timestamp),
|
||||
/*ts_for_read=*/true);
|
||||
const Status s = FailIfTsMismatchCf(
|
||||
column_family, *(read_options.timestamp), /*ts_for_read=*/true);
|
||||
if (!s.ok()) {
|
||||
return NewErrorIterator(s);
|
||||
}
|
||||
@@ -3629,19 +3522,9 @@ ArenaWrappedDBIter* DBImpl::NewIteratorImpl(const ReadOptions& read_options,
|
||||
}
|
||||
|
||||
Status DBImpl::NewIterators(
|
||||
const ReadOptions& _read_options,
|
||||
const ReadOptions& read_options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
std::vector<Iterator*>* iterators) {
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kDBIterator) {
|
||||
return Status::InvalidArgument(
|
||||
"Can only call NewIterators with `ReadOptions::io_activity` is "
|
||||
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kDBIterator`");
|
||||
}
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kDBIterator;
|
||||
}
|
||||
if (read_options.managed) {
|
||||
return Status::NotSupported("Managed iterator is not supported anymore.");
|
||||
}
|
||||
@@ -3649,6 +3532,11 @@ Status DBImpl::NewIterators(
|
||||
return Status::NotSupported(
|
||||
"ReadTier::kPersistedData is not yet supported in iterators.");
|
||||
}
|
||||
if (read_options.io_activity != Env::IOActivity::kUnknown) {
|
||||
return Status::InvalidArgument(
|
||||
"Cannot call NewIterators with `ReadOptions::io_activity` != "
|
||||
"`Env::IOActivity::kUnknown`");
|
||||
}
|
||||
|
||||
if (read_options.timestamp) {
|
||||
for (auto* cf : column_families) {
|
||||
@@ -4295,17 +4183,9 @@ void DBImpl::GetApproximateMemTableStats(ColumnFamilyHandle* column_family,
|
||||
ColumnFamilyData* cfd = cfh->cfd();
|
||||
SuperVersion* sv = GetAndRefSuperVersion(cfd);
|
||||
|
||||
const Comparator* const ucmp = column_family->GetComparator();
|
||||
assert(ucmp);
|
||||
size_t ts_sz = ucmp->timestamp_size();
|
||||
|
||||
// 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);
|
||||
// Convert user_key into a corresponding internal key.
|
||||
InternalKey k1(start, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
InternalKey k2(limit, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
InternalKey k1(range.start, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
InternalKey k2(range.limit, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
MemTable::MemTableStats memStats =
|
||||
sv->mem->ApproximateStats(k1.Encode(), k2.Encode());
|
||||
MemTable::MemTableStats immStats =
|
||||
@@ -4336,10 +4216,20 @@ Status DBImpl::GetApproximateSizes(const SizeApproximationOptions& options,
|
||||
// TODO: plumb Env::IOActivity
|
||||
const ReadOptions read_options;
|
||||
for (int i = 0; i < n; i++) {
|
||||
Slice start = range[i].start;
|
||||
Slice limit = range[i].limit;
|
||||
|
||||
// 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);
|
||||
if (ts_sz > 0) {
|
||||
// Maximum timestamp means including all key with any timestamp
|
||||
AppendKeyWithMaxTimestamp(&start_with_ts, start, ts_sz);
|
||||
// Append a maximum timestamp as the range limit is exclusive:
|
||||
// [start, limit)
|
||||
AppendKeyWithMaxTimestamp(&limit_with_ts, limit, ts_sz);
|
||||
start = start_with_ts;
|
||||
limit = limit_with_ts;
|
||||
}
|
||||
// Convert user_key into a corresponding internal key.
|
||||
InternalKey k1(start, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
InternalKey k2(limit, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
@@ -4555,12 +4445,10 @@ Status DBImpl::DeleteFilesInRanges(ColumnFamilyHandle* column_family,
|
||||
deleted_files.insert(level_file);
|
||||
level_file->being_compacted = true;
|
||||
}
|
||||
vstorage->ComputeCompactionScore(*cfd->ioptions(),
|
||||
*cfd->GetLatestMutableCFOptions());
|
||||
}
|
||||
}
|
||||
if (!deleted_files.empty()) {
|
||||
vstorage->ComputeCompactionScore(*cfd->ioptions(),
|
||||
*cfd->GetLatestMutableCFOptions());
|
||||
}
|
||||
if (edit.GetDeletedFiles().empty()) {
|
||||
job_context.Clean();
|
||||
return status;
|
||||
@@ -5901,35 +5789,12 @@ Status DBImpl::ClipColumnFamily(ColumnFamilyHandle* column_family,
|
||||
return status;
|
||||
}
|
||||
|
||||
Status DBImpl::VerifyFileChecksums(const ReadOptions& _read_options) {
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kVerifyFileChecksums) {
|
||||
return Status::InvalidArgument(
|
||||
"Can only call VerifyFileChecksums with `ReadOptions::io_activity` is "
|
||||
"`Env::IOActivity::kUnknown` or "
|
||||
"`Env::IOActivity::kVerifyFileChecksums`");
|
||||
}
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kVerifyFileChecksums;
|
||||
}
|
||||
return VerifyChecksumInternal(read_options,
|
||||
/*use_file_checksum=*/true);
|
||||
Status DBImpl::VerifyFileChecksums(const ReadOptions& read_options) {
|
||||
return VerifyChecksumInternal(read_options, /*use_file_checksum=*/true);
|
||||
}
|
||||
|
||||
Status DBImpl::VerifyChecksum(const ReadOptions& _read_options) {
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kVerifyDBChecksum) {
|
||||
return Status::InvalidArgument(
|
||||
"Can only call VerifyChecksum with `ReadOptions::io_activity` is "
|
||||
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kVerifyDBChecksum`");
|
||||
}
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kVerifyDBChecksum;
|
||||
}
|
||||
return VerifyChecksumInternal(read_options,
|
||||
/*use_file_checksum=*/false);
|
||||
Status DBImpl::VerifyChecksum(const ReadOptions& read_options) {
|
||||
return VerifyChecksumInternal(read_options, /*use_file_checksum=*/false);
|
||||
}
|
||||
|
||||
Status DBImpl::VerifyChecksumInternal(const ReadOptions& read_options,
|
||||
@@ -5941,6 +5806,12 @@ Status DBImpl::VerifyChecksumInternal(const ReadOptions& read_options,
|
||||
|
||||
Status s;
|
||||
|
||||
if (read_options.io_activity != Env::IOActivity::kUnknown) {
|
||||
s = Status::InvalidArgument(
|
||||
"Cannot verify file checksum with `ReadOptions::io_activity` != "
|
||||
"`Env::IOActivity::kUnknown`");
|
||||
return s;
|
||||
}
|
||||
if (use_file_checksum) {
|
||||
FileChecksumGenFactory* const file_checksum_gen_factory =
|
||||
immutable_db_options_.file_checksum_gen_factory.get();
|
||||
@@ -5951,7 +5822,6 @@ Status DBImpl::VerifyChecksumInternal(const ReadOptions& read_options,
|
||||
return s;
|
||||
}
|
||||
}
|
||||
// FIXME? What does it mean if read_options.verify_checksums == false?
|
||||
|
||||
// TODO: simplify using GetRefedColumnFamilySet?
|
||||
std::vector<ColumnFamilyData*> cfd_list;
|
||||
@@ -5992,7 +5862,7 @@ Status DBImpl::VerifyChecksumInternal(const ReadOptions& read_options,
|
||||
fmeta->file_checksum_func_name, fname,
|
||||
read_options);
|
||||
} else {
|
||||
s = ROCKSDB_NAMESPACE::VerifySstFileChecksumInternal(
|
||||
s = ROCKSDB_NAMESPACE::VerifySstFileChecksum(
|
||||
opts, file_options_, read_options, fname, fd.largest_seqno);
|
||||
}
|
||||
RecordTick(stats_, VERIFY_CHECKSUM_READ_BYTES,
|
||||
@@ -6055,6 +5925,12 @@ Status DBImpl::VerifyFullFileChecksum(const std::string& file_checksum_expected,
|
||||
const std::string& func_name_expected,
|
||||
const std::string& fname,
|
||||
const ReadOptions& read_options) {
|
||||
if (read_options.io_activity != Env::IOActivity::kUnknown) {
|
||||
return Status::InvalidArgument(
|
||||
"Cannot call VerifyChecksum with `ReadOptions::io_activity` != "
|
||||
"`Env::IOActivity::kUnknown`");
|
||||
}
|
||||
|
||||
Status s;
|
||||
if (file_checksum_expected == kUnknownFileChecksum) {
|
||||
return s;
|
||||
@@ -6065,8 +5941,8 @@ Status DBImpl::VerifyFullFileChecksum(const std::string& file_checksum_expected,
|
||||
fs_.get(), fname, immutable_db_options_.file_checksum_gen_factory.get(),
|
||||
func_name_expected, &file_checksum, &func_name,
|
||||
read_options.readahead_size, immutable_db_options_.allow_mmap_reads,
|
||||
io_tracer_, immutable_db_options_.rate_limiter.get(), read_options,
|
||||
immutable_db_options_.stats, immutable_db_options_.clock);
|
||||
io_tracer_, immutable_db_options_.rate_limiter.get(),
|
||||
read_options.rate_limiter_priority);
|
||||
if (s.ok()) {
|
||||
assert(func_name_expected == func_name);
|
||||
if (file_checksum != file_checksum_expected) {
|
||||
|
||||
+10
-40
@@ -234,7 +234,7 @@ class DBImpl : public DB {
|
||||
virtual Status Get(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value) override;
|
||||
virtual Status Get(const ReadOptions& _read_options,
|
||||
virtual Status Get(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value, std::string* timestamp) override;
|
||||
|
||||
@@ -265,7 +265,7 @@ class DBImpl : public DB {
|
||||
const std::vector<Slice>& keys,
|
||||
std::vector<std::string>* values) override;
|
||||
virtual std::vector<Status> MultiGet(
|
||||
const ReadOptions& _read_options,
|
||||
const ReadOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_family,
|
||||
const std::vector<Slice>& keys, std::vector<std::string>* values,
|
||||
std::vector<std::string>* timestamps) override;
|
||||
@@ -280,9 +280,8 @@ class DBImpl : public DB {
|
||||
void MultiGet(const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
const size_t num_keys, const Slice* keys, PinnableSlice* values,
|
||||
Status* statuses, const bool sorted_input = false) override;
|
||||
void MultiGet(const ReadOptions& _read_options,
|
||||
ColumnFamilyHandle* column_family, const size_t num_keys,
|
||||
const Slice* keys, PinnableSlice* values,
|
||||
void MultiGet(const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
const size_t num_keys, const Slice* keys, PinnableSlice* values,
|
||||
std::string* timestamps, Status* statuses,
|
||||
const bool sorted_input = false) override;
|
||||
|
||||
@@ -290,13 +289,13 @@ class DBImpl : public DB {
|
||||
ColumnFamilyHandle** column_families, const Slice* keys,
|
||||
PinnableSlice* values, Status* statuses,
|
||||
const bool sorted_input = false) override;
|
||||
void MultiGet(const ReadOptions& _read_options, const size_t num_keys,
|
||||
void MultiGet(const ReadOptions& options, const size_t num_keys,
|
||||
ColumnFamilyHandle** column_families, const Slice* keys,
|
||||
PinnableSlice* values, std::string* timestamps,
|
||||
Status* statuses, const bool sorted_input = false) override;
|
||||
|
||||
void MultiGetWithCallback(
|
||||
const ReadOptions& _read_options, ColumnFamilyHandle* column_family,
|
||||
const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
ReadCallback* callback,
|
||||
autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE>* sorted_keys);
|
||||
|
||||
@@ -337,10 +336,10 @@ class DBImpl : public DB {
|
||||
bool* value_found = nullptr) override;
|
||||
|
||||
using DB::NewIterator;
|
||||
virtual Iterator* NewIterator(const ReadOptions& _read_options,
|
||||
virtual Iterator* NewIterator(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family) override;
|
||||
virtual Status NewIterators(
|
||||
const ReadOptions& _read_options,
|
||||
const ReadOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
std::vector<Iterator*>* iterators) override;
|
||||
|
||||
@@ -628,14 +627,6 @@ class DBImpl : public DB {
|
||||
int* number_of_operands = nullptr;
|
||||
};
|
||||
|
||||
Status GetImpl(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value);
|
||||
|
||||
Status GetImpl(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value, std::string* timestamp);
|
||||
|
||||
// Function that Get and KeyMayExist call with no_io true or false
|
||||
// Note: 'value_found' from KeyMayExist propagates here
|
||||
// This function is also called by GetMergeOperands
|
||||
@@ -1349,11 +1340,6 @@ class DBImpl : public DB {
|
||||
|
||||
std::atomic<bool> shutting_down_;
|
||||
|
||||
// No new background jobs can be queued if true. This is used to prevent new
|
||||
// background jobs from being queued after WaitForCompact() completes waiting
|
||||
// all background jobs then attempts to close when close_db_ option is true.
|
||||
bool reject_new_background_jobs_;
|
||||
|
||||
// RecoveryContext struct stores the context about version edits along
|
||||
// with corresponding column_family_data and column_family_options.
|
||||
class RecoveryContext {
|
||||
@@ -1580,14 +1566,12 @@ class DBImpl : public DB {
|
||||
friend class ForwardIterator;
|
||||
friend struct SuperVersion;
|
||||
friend class CompactedDBImpl;
|
||||
#ifndef NDEBUG
|
||||
friend class DBTest_ConcurrentFlushWAL_Test;
|
||||
friend class DBTest_MixedSlowdownOptionsStop_Test;
|
||||
friend class DBCompactionTest_CompactBottomLevelFilesWithDeletions_Test;
|
||||
friend class DBCompactionTest_CompactionDuringShutdown_Test;
|
||||
friend class DBCompactionTest_DelayCompactBottomLevelFilesWithDeletions_Test;
|
||||
friend class DBCompactionTest_DisableCompactBottomLevelFiles_Test;
|
||||
friend class StatsHistoryTest_PersistentStatsCreateColumnFamilies_Test;
|
||||
#ifndef NDEBUG
|
||||
friend class DBTest2_ReadCallbackTest_Test;
|
||||
friend class WriteCallbackPTest_WriteWithCallbackTest_Test;
|
||||
friend class XFTransactionWriteHandler;
|
||||
@@ -2079,10 +2063,6 @@ class DBImpl : public DB {
|
||||
// flush is considered complete.
|
||||
std::unordered_map<ColumnFamilyData*, uint64_t>
|
||||
cfd_to_max_mem_id_to_persist;
|
||||
|
||||
#ifndef NDEBUG
|
||||
int reschedule_count = 1;
|
||||
#endif /* !NDEBUG */
|
||||
};
|
||||
|
||||
void GenerateFlushRequest(const autovector<ColumnFamilyData*>& cfds,
|
||||
@@ -2111,7 +2091,6 @@ class DBImpl : public DB {
|
||||
Env::Priority thread_pri);
|
||||
Status BackgroundFlush(bool* madeProgress, JobContext* job_context,
|
||||
LogBuffer* log_buffer, FlushReason* reason,
|
||||
bool* flush_rescheduled_to_retain_udt,
|
||||
Env::Priority thread_pri);
|
||||
|
||||
bool EnoughRoomForCompaction(ColumnFamilyData* cfd,
|
||||
@@ -2124,18 +2103,9 @@ class DBImpl : public DB {
|
||||
std::unique_ptr<TaskLimiterToken>* token,
|
||||
LogBuffer* log_buffer);
|
||||
|
||||
// Return true if the `FlushRequest` can be rescheduled to retain the UDT.
|
||||
// Only true if there are user-defined timestamps in the involved MemTables
|
||||
// with newer than cutoff timestamp `full_history_ts_low` and not flushing
|
||||
// immediately will not cause entering write stall mode.
|
||||
bool ShouldRescheduleFlushRequestToRetainUDT(const FlushRequest& flush_req);
|
||||
|
||||
// Schedule background tasks
|
||||
Status StartPeriodicTaskScheduler();
|
||||
|
||||
// Cancel scheduled periodic tasks
|
||||
Status CancelPeriodicTaskScheduler();
|
||||
|
||||
Status RegisterRecordSeqnoTimeWorker();
|
||||
|
||||
void PrintStatistics();
|
||||
@@ -2219,7 +2189,7 @@ class DBImpl : public DB {
|
||||
void BuildCompactionJobInfo(const ColumnFamilyData* cfd, Compaction* c,
|
||||
const Status& st,
|
||||
const CompactionJobStats& compaction_job_stats,
|
||||
const int job_id,
|
||||
const int job_id, const Version* current,
|
||||
CompactionJobInfo* compaction_job_info) const;
|
||||
// Reserve the next 'num' file numbers for to-be-ingested external SST files,
|
||||
// and return the current file_number in 'next_file_number'.
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
#include "monitoring/thread_status_util.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "util/cast_util.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/concurrent_task_limiter_impl.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -77,40 +76,6 @@ bool DBImpl::RequestCompactionToken(ColumnFamilyData* cfd, bool force,
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DBImpl::ShouldRescheduleFlushRequestToRetainUDT(
|
||||
const FlushRequest& flush_req) {
|
||||
mutex_.AssertHeld();
|
||||
assert(flush_req.cfd_to_max_mem_id_to_persist.size() == 1);
|
||||
ColumnFamilyData* cfd = flush_req.cfd_to_max_mem_id_to_persist.begin()->first;
|
||||
uint64_t max_memtable_id =
|
||||
flush_req.cfd_to_max_mem_id_to_persist.begin()->second;
|
||||
if (cfd->IsDropped() ||
|
||||
!cfd->ShouldPostponeFlushToRetainUDT(max_memtable_id)) {
|
||||
return false;
|
||||
}
|
||||
// Check if holding on the flush will cause entering write stall mode.
|
||||
// Write stall entered because of the accumulation of write buffers can be
|
||||
// alleviated if we continue with the flush instead of postponing it.
|
||||
const auto& mutable_cf_options = *cfd->GetLatestMutableCFOptions();
|
||||
|
||||
// Taking the status of the active Memtable into consideration so that we are
|
||||
// not just checking if DB is currently already in write stall mode.
|
||||
int mem_to_flush = cfd->mem()->ApproximateMemoryUsageFast() >=
|
||||
cfd->mem()->write_buffer_size() / 2
|
||||
? 1
|
||||
: 0;
|
||||
WriteStallCondition write_stall =
|
||||
ColumnFamilyData::GetWriteStallConditionAndCause(
|
||||
cfd->imm()->NumNotFlushed() + mem_to_flush, /*num_l0_files=*/0,
|
||||
/*num_compaction_needed_bytes=*/0, mutable_cf_options,
|
||||
*cfd->ioptions())
|
||||
.first;
|
||||
if (write_stall != WriteStallCondition::kNormal) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
IOStatus DBImpl::SyncClosedLogs(JobContext* job_context,
|
||||
VersionEdit* synced_wals) {
|
||||
TEST_SYNC_POINT("DBImpl::SyncClosedLogs:Start");
|
||||
@@ -283,24 +248,6 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
// If the log sync failed, we do not need to pick memtable. Otherwise,
|
||||
// num_flush_not_started_ needs to be rollback.
|
||||
TEST_SYNC_POINT("DBImpl::FlushMemTableToOutputFile:BeforePickMemtables");
|
||||
// Exit a flush due to bg error should not set bg error again.
|
||||
bool skip_set_bg_error = false;
|
||||
if (s.ok() && !error_handler_.GetBGError().ok() &&
|
||||
error_handler_.IsBGWorkStopped() &&
|
||||
flush_reason != FlushReason::kErrorRecovery &&
|
||||
flush_reason != FlushReason::kErrorRecoveryRetryFlush) {
|
||||
// Error recovery in progress, should not pick memtable which excludes
|
||||
// them from being picked up by recovery flush.
|
||||
// This ensures that when bg error is set, no new flush can pick
|
||||
// memtables.
|
||||
skip_set_bg_error = true;
|
||||
s = error_handler_.GetBGError();
|
||||
assert(!s.ok());
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"[JOB %d] Skip flush due to background error %s",
|
||||
job_context->job_id, s.ToString().c_str());
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
flush_job.PickMemTable();
|
||||
need_cancel = true;
|
||||
@@ -321,8 +268,7 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
// is unlocked by the current thread.
|
||||
if (s.ok()) {
|
||||
s = flush_job.Run(&logs_with_prep_tracker_, &file_meta,
|
||||
&switched_to_mempurge, &skip_set_bg_error,
|
||||
&error_handler_);
|
||||
&switched_to_mempurge);
|
||||
need_cancel = false;
|
||||
}
|
||||
|
||||
@@ -363,8 +309,7 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
}
|
||||
}
|
||||
|
||||
if (!s.ok() && !s.IsShutdownInProgress() && !s.IsColumnFamilyDropped() &&
|
||||
!skip_set_bg_error) {
|
||||
if (!s.ok() && !s.IsShutdownInProgress() && !s.IsColumnFamilyDropped()) {
|
||||
if (log_io_s.ok()) {
|
||||
// Error while writing to MANIFEST.
|
||||
// In fact, versions_->io_status() can also be the result of renaming
|
||||
@@ -576,21 +521,6 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
pick_status.push_back(false);
|
||||
}
|
||||
|
||||
bool flush_for_recovery =
|
||||
bg_flush_args[0].flush_reason_ == FlushReason::kErrorRecovery ||
|
||||
bg_flush_args[0].flush_reason_ == FlushReason::kErrorRecoveryRetryFlush;
|
||||
bool skip_set_bg_error = false;
|
||||
|
||||
if (s.ok() && !error_handler_.GetBGError().ok() &&
|
||||
error_handler_.IsBGWorkStopped() && !flush_for_recovery) {
|
||||
s = error_handler_.GetBGError();
|
||||
skip_set_bg_error = true;
|
||||
assert(!s.ok());
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"[JOB %d] Skip flush due to background error %s",
|
||||
job_context->job_id, s.ToString().c_str());
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
for (int i = 0; i != num_cfs; ++i) {
|
||||
jobs[i]->PickMemTable();
|
||||
@@ -655,10 +585,7 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!skip_set_bg_error) {
|
||||
// When `skip_set_bg_error` is true, no memtable is picked so
|
||||
// there is no need to call Cancel() or RollbackMemtableFlush().
|
||||
//
|
||||
} else {
|
||||
// Need to undo atomic flush if something went wrong, i.e. s is not OK and
|
||||
// it is not because of CF drop.
|
||||
// Have to cancel the flush jobs that have NOT executed because we need to
|
||||
@@ -671,8 +598,8 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
for (int i = 0; i != num_cfs; ++i) {
|
||||
if (exec_status[i].second.ok() && exec_status[i].first) {
|
||||
auto& mems = jobs[i]->GetMemTables();
|
||||
cfds[i]->imm()->RollbackMemtableFlush(
|
||||
mems, /*rollback_succeeding_memtables=*/false);
|
||||
cfds[i]->imm()->RollbackMemtableFlush(mems,
|
||||
file_meta[i].fd.GetNumber());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -714,7 +641,10 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
};
|
||||
|
||||
bool resuming_from_bg_err =
|
||||
error_handler_.IsDBStopped() || flush_for_recovery;
|
||||
error_handler_.IsDBStopped() ||
|
||||
(bg_flush_args[0].flush_reason_ == FlushReason::kErrorRecovery ||
|
||||
bg_flush_args[0].flush_reason_ ==
|
||||
FlushReason::kErrorRecoveryRetryFlush);
|
||||
while ((!resuming_from_bg_err || error_handler_.GetRecoveryError().ok())) {
|
||||
std::pair<Status, bool> res = wait_to_install_func();
|
||||
|
||||
@@ -725,27 +655,15 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
s = res.first;
|
||||
break;
|
||||
} else if (!res.second) {
|
||||
// we are the oldest immutable memtable
|
||||
break;
|
||||
}
|
||||
// We are not the oldest immutable memtable
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"DBImpl::AtomicFlushMemTablesToOutputFiles:WaitCV", &res);
|
||||
//
|
||||
// If bg work is stopped, recovery thread first calls
|
||||
// WaitForBackgroundWork() before proceeding to flush for recovery. This
|
||||
// flush can block WaitForBackgroundWork() while waiting for recovery
|
||||
// flush to install result. To avoid this deadlock, we should abort here
|
||||
// if there is background error.
|
||||
if (!flush_for_recovery && error_handler_.IsBGWorkStopped() &&
|
||||
!error_handler_.GetBGError().ok()) {
|
||||
s = error_handler_.GetBGError();
|
||||
assert(!s.ok());
|
||||
break;
|
||||
}
|
||||
atomic_flush_install_cv_.Wait();
|
||||
|
||||
resuming_from_bg_err = error_handler_.IsDBStopped() || flush_for_recovery;
|
||||
resuming_from_bg_err =
|
||||
error_handler_.IsDBStopped() ||
|
||||
(bg_flush_args[0].flush_reason_ == FlushReason::kErrorRecovery ||
|
||||
bg_flush_args[0].flush_reason_ ==
|
||||
FlushReason::kErrorRecoveryRetryFlush);
|
||||
}
|
||||
|
||||
if (!resuming_from_bg_err) {
|
||||
@@ -761,17 +679,6 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
// installation.
|
||||
s = error_handler_.GetRecoveryError();
|
||||
}
|
||||
// Since we are not installing these memtables, need to rollback
|
||||
// to allow future flush job to pick up these memtables.
|
||||
if (!s.ok()) {
|
||||
for (int i = 0; i != num_cfs; ++i) {
|
||||
assert(exec_status[i].first);
|
||||
assert(exec_status[i].second.ok());
|
||||
auto& mems = jobs[i]->GetMemTables();
|
||||
cfds[i]->imm()->RollbackMemtableFlush(
|
||||
mems, /*rollback_succeeding_memtables=*/false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
@@ -875,7 +782,7 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
|
||||
// Need to undo atomic flush if something went wrong, i.e. s is not OK and
|
||||
// it is not because of CF drop.
|
||||
if (!s.ok() && !s.IsColumnFamilyDropped() && !skip_set_bg_error) {
|
||||
if (!s.ok() && !s.IsColumnFamilyDropped()) {
|
||||
if (log_io_s.ok()) {
|
||||
// Error while writing to MANIFEST.
|
||||
// In fact, versions_->io_status() can also be the result of renaming
|
||||
@@ -945,8 +852,8 @@ void DBImpl::NotifyOnFlushBegin(ColumnFamilyData* cfd, FileMetaData* file_meta,
|
||||
}
|
||||
}
|
||||
mutex_.Lock();
|
||||
// no need to signal bg_cv_ as it will be signaled at the end of the
|
||||
// flush process.
|
||||
// no need to signal bg_cv_ as it will be signaled at the end of the
|
||||
// flush process.
|
||||
}
|
||||
|
||||
void DBImpl::NotifyOnFlushCompleted(
|
||||
@@ -1604,7 +1511,7 @@ Status DBImpl::CompactFilesImpl(
|
||||
|
||||
if (compaction_job_info != nullptr) {
|
||||
BuildCompactionJobInfo(cfd, c.get(), s, compaction_job_stats,
|
||||
job_context->job_id, compaction_job_info);
|
||||
job_context->job_id, version, compaction_job_info);
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
@@ -1701,18 +1608,21 @@ void DBImpl::NotifyOnCompactionBegin(ColumnFamilyData* cfd, Compaction* c,
|
||||
}
|
||||
|
||||
c->SetNotifyOnCompactionCompleted();
|
||||
Version* current = cfd->current();
|
||||
current->Ref();
|
||||
// release lock while notifying events
|
||||
mutex_.Unlock();
|
||||
TEST_SYNC_POINT("DBImpl::NotifyOnCompactionBegin::UnlockMutex");
|
||||
{
|
||||
CompactionJobInfo info{};
|
||||
BuildCompactionJobInfo(cfd, c, st, job_stats, job_id, &info);
|
||||
BuildCompactionJobInfo(cfd, c, st, job_stats, job_id, current, &info);
|
||||
for (auto listener : immutable_db_options_.listeners) {
|
||||
listener->OnCompactionBegin(this, info);
|
||||
}
|
||||
info.status.PermitUncheckedError();
|
||||
}
|
||||
mutex_.Lock();
|
||||
current->Unref();
|
||||
}
|
||||
|
||||
void DBImpl::NotifyOnCompactionCompleted(
|
||||
@@ -1730,17 +1640,21 @@ void DBImpl::NotifyOnCompactionCompleted(
|
||||
return;
|
||||
}
|
||||
|
||||
Version* current = cfd->current();
|
||||
current->Ref();
|
||||
// release lock while notifying events
|
||||
mutex_.Unlock();
|
||||
TEST_SYNC_POINT("DBImpl::NotifyOnCompactionCompleted::UnlockMutex");
|
||||
{
|
||||
CompactionJobInfo info{};
|
||||
BuildCompactionJobInfo(cfd, c, st, compaction_job_stats, job_id, &info);
|
||||
BuildCompactionJobInfo(cfd, c, st, compaction_job_stats, job_id, current,
|
||||
&info);
|
||||
for (auto listener : immutable_db_options_.listeners) {
|
||||
listener->OnCompactionCompleted(this, info);
|
||||
}
|
||||
}
|
||||
mutex_.Lock();
|
||||
current->Unref();
|
||||
// no need to signal bg_cv_ as it will be signaled at the end of the
|
||||
// flush process.
|
||||
}
|
||||
@@ -2281,13 +2195,9 @@ Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
|
||||
WaitForPendingWrites();
|
||||
|
||||
if (flush_reason != FlushReason::kErrorRecoveryRetryFlush &&
|
||||
flush_reason != FlushReason::kCatchUpAfterErrorRecovery &&
|
||||
(!cfd->mem()->IsEmpty() || !cached_recoverable_state_empty_.load())) {
|
||||
// Note that, when flush reason is kErrorRecoveryRetryFlush, during the
|
||||
// auto retry resume, we want to avoid creating new small memtables.
|
||||
// If flush reason is kCatchUpAfterErrorRecovery, we try to flush any new
|
||||
// memtable that filled up during recovery, and we also want to avoid
|
||||
// switching memtable to create small memtables.
|
||||
// Therefore, SwitchMemtable will not be called. Also, since ResumeImpl
|
||||
// will iterate through all the CFs and call FlushMemtable during auto
|
||||
// retry resume, it is possible that in some CFs,
|
||||
@@ -2478,8 +2388,7 @@ Status DBImpl::AtomicFlushMemTables(
|
||||
|
||||
for (auto cfd : cfds) {
|
||||
if ((cfd->mem()->IsEmpty() && cached_recoverable_state_empty_.load()) ||
|
||||
flush_reason == FlushReason::kErrorRecoveryRetryFlush ||
|
||||
flush_reason == FlushReason::kCatchUpAfterErrorRecovery) {
|
||||
flush_reason == FlushReason::kErrorRecoveryRetryFlush) {
|
||||
continue;
|
||||
}
|
||||
cfd->Ref();
|
||||
@@ -2597,11 +2506,8 @@ Status DBImpl::WaitUntilFlushWouldNotStallWrites(ColumnFamilyData* cfd,
|
||||
// check whether one extra immutable memtable or an extra L0 file would
|
||||
// cause write stalling mode to be entered. It could still enter stall
|
||||
// mode due to pending compaction bytes, but that's less common
|
||||
// No extra immutable Memtable will be created if the current Memtable is
|
||||
// empty.
|
||||
int mem_to_flush = cfd->mem()->IsEmpty() ? 0 : 1;
|
||||
write_stall_condition = ColumnFamilyData::GetWriteStallConditionAndCause(
|
||||
cfd->imm()->NumNotFlushed() + mem_to_flush,
|
||||
cfd->imm()->NumNotFlushed() + 1,
|
||||
vstorage->l0_delay_trigger_count() + 1,
|
||||
vstorage->estimated_compaction_needed_bytes(),
|
||||
mutable_cf_options, *cfd->ioptions())
|
||||
@@ -2747,11 +2653,6 @@ void DBImpl::MaybeScheduleFlushOrCompaction() {
|
||||
// There has been a hard error and this call is not part of the recovery
|
||||
// sequence. Bail out here so we don't get into an endless loop of
|
||||
// scheduling BG work which will again call this function
|
||||
//
|
||||
// Note that a non-recovery flush can still be scheduled if
|
||||
// error_handler_.IsRecoveryInProgress() returns true. We rely on
|
||||
// BackgroundCallFlush() to check flush reason and drop non-recovery
|
||||
// flushes.
|
||||
return;
|
||||
} else if (shutting_down_.load(std::memory_order_acquire)) {
|
||||
// DB is being deleted; no more background compactions
|
||||
@@ -2874,7 +2775,7 @@ ColumnFamilyData* DBImpl::PopFirstFromCompactionQueue() {
|
||||
|
||||
DBImpl::FlushRequest DBImpl::PopFirstFromFlushQueue() {
|
||||
assert(!flush_queue_.empty());
|
||||
FlushRequest flush_req = std::move(flush_queue_.front());
|
||||
FlushRequest flush_req = flush_queue_.front();
|
||||
flush_queue_.pop_front();
|
||||
if (!immutable_db_options_.atomic_flush) {
|
||||
assert(flush_req.cfd_to_max_mem_id_to_persist.size() == 1);
|
||||
@@ -2918,9 +2819,6 @@ ColumnFamilyData* DBImpl::PickCompactionFromQueue(
|
||||
|
||||
void DBImpl::SchedulePendingFlush(const FlushRequest& flush_req) {
|
||||
mutex_.AssertHeld();
|
||||
if (reject_new_background_jobs_) {
|
||||
return;
|
||||
}
|
||||
if (flush_req.cfd_to_max_mem_id_to_persist.empty()) {
|
||||
return;
|
||||
}
|
||||
@@ -2950,9 +2848,6 @@ void DBImpl::SchedulePendingFlush(const FlushRequest& flush_req) {
|
||||
|
||||
void DBImpl::SchedulePendingCompaction(ColumnFamilyData* cfd) {
|
||||
mutex_.AssertHeld();
|
||||
if (reject_new_background_jobs_) {
|
||||
return;
|
||||
}
|
||||
if (!cfd->queued_for_compaction() && cfd->NeedsCompaction()) {
|
||||
AddToCompactionQueue(cfd);
|
||||
++unscheduled_compactions_;
|
||||
@@ -2962,9 +2857,6 @@ void DBImpl::SchedulePendingCompaction(ColumnFamilyData* cfd) {
|
||||
void DBImpl::SchedulePendingPurge(std::string fname, std::string dir_to_sync,
|
||||
FileType type, uint64_t number, int job_id) {
|
||||
mutex_.AssertHeld();
|
||||
if (reject_new_background_jobs_) {
|
||||
return;
|
||||
}
|
||||
PurgeFileInfo file_info(fname, dir_to_sync, type, number, job_id);
|
||||
purge_files_.insert({{number, std::move(file_info)}});
|
||||
}
|
||||
@@ -3053,7 +2945,6 @@ void DBImpl::UnscheduleFlushCallback(void* arg) {
|
||||
|
||||
Status DBImpl::BackgroundFlush(bool* made_progress, JobContext* job_context,
|
||||
LogBuffer* log_buffer, FlushReason* reason,
|
||||
bool* flush_rescheduled_to_retain_udt,
|
||||
Env::Priority thread_pri) {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
@@ -3079,61 +2970,14 @@ Status DBImpl::BackgroundFlush(bool* made_progress, JobContext* job_context,
|
||||
autovector<ColumnFamilyData*> column_families_not_to_flush;
|
||||
while (!flush_queue_.empty()) {
|
||||
// This cfd is already referenced
|
||||
FlushRequest flush_req = PopFirstFromFlushQueue();
|
||||
const FlushRequest& flush_req = PopFirstFromFlushQueue();
|
||||
FlushReason flush_reason = flush_req.flush_reason;
|
||||
if (!error_handler_.GetBGError().ok() && error_handler_.IsBGWorkStopped() &&
|
||||
flush_reason != FlushReason::kErrorRecovery &&
|
||||
flush_reason != FlushReason::kErrorRecoveryRetryFlush) {
|
||||
// Stop non-recovery flush when bg work is stopped
|
||||
// Note that we drop the flush request here.
|
||||
// Recovery thread should schedule further flushes after bg error
|
||||
// is cleared.
|
||||
status = error_handler_.GetBGError();
|
||||
assert(!status.ok());
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"[JOB %d] Abort flush due to background error %s",
|
||||
job_context->job_id, status.ToString().c_str());
|
||||
*reason = flush_reason;
|
||||
for (auto item : flush_req.cfd_to_max_mem_id_to_persist) {
|
||||
item.first->UnrefAndTryDelete();
|
||||
}
|
||||
return status;
|
||||
}
|
||||
if (!immutable_db_options_.atomic_flush &&
|
||||
ShouldRescheduleFlushRequestToRetainUDT(flush_req)) {
|
||||
assert(flush_req.cfd_to_max_mem_id_to_persist.size() == 1);
|
||||
ColumnFamilyData* cfd =
|
||||
flush_req.cfd_to_max_mem_id_to_persist.begin()->first;
|
||||
if (cfd->UnrefAndTryDelete()) {
|
||||
return Status::OK();
|
||||
}
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"FlushRequest for column family %s is re-scheduled to "
|
||||
"retain user-defined timestamps.",
|
||||
cfd->GetName().c_str());
|
||||
// Reschedule the `FlushRequest` as is without checking dropped column
|
||||
// family etc. The follow-up job will do the check anyways, so save the
|
||||
// duplication. Column family is deduplicated by `SchdulePendingFlush` and
|
||||
// `PopFirstFromFlushQueue` contains at flush request enqueueing and
|
||||
// dequeueing time.
|
||||
// This flush request is rescheduled right after it's popped from the
|
||||
// queue while the db mutex is held, so there should be no other
|
||||
// FlushRequest for the same column family with higher `max_memtable_id`
|
||||
// in the queue to block the reschedule from succeeding.
|
||||
#ifndef NDEBUG
|
||||
flush_req.reschedule_count += 1;
|
||||
#endif /* !NDEBUG */
|
||||
SchedulePendingFlush(flush_req);
|
||||
*reason = flush_reason;
|
||||
*flush_rescheduled_to_retain_udt = true;
|
||||
return Status::TryAgain();
|
||||
}
|
||||
superversion_contexts.clear();
|
||||
superversion_contexts.reserve(
|
||||
flush_req.cfd_to_max_mem_id_to_persist.size());
|
||||
|
||||
for (const auto& [cfd, max_memtable_id] :
|
||||
flush_req.cfd_to_max_mem_id_to_persist) {
|
||||
for (const auto& iter : flush_req.cfd_to_max_mem_id_to_persist) {
|
||||
ColumnFamilyData* cfd = iter.first;
|
||||
if (cfd->GetMempurgeUsed()) {
|
||||
// If imm() contains silent memtables (e.g.: because
|
||||
// MemPurge was activated), requesting a flush will
|
||||
@@ -3147,16 +2991,10 @@ Status DBImpl::BackgroundFlush(bool* made_progress, JobContext* job_context,
|
||||
continue;
|
||||
}
|
||||
superversion_contexts.emplace_back(SuperVersionContext(true));
|
||||
bg_flush_args.emplace_back(cfd, max_memtable_id,
|
||||
bg_flush_args.emplace_back(cfd, iter.second,
|
||||
&(superversion_contexts.back()), flush_reason);
|
||||
}
|
||||
// `MaybeScheduleFlushOrCompaction` schedules as many `BackgroundCallFlush`
|
||||
// jobs as the number of `FlushRequest` in the `flush_queue_`, a.k.a
|
||||
// `unscheduled_flushes_`. So it's sufficient to make each `BackgroundFlush`
|
||||
// handle one `FlushRequest` and each have a Status returned.
|
||||
if (!bg_flush_args.empty() || !column_families_not_to_flush.empty()) {
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundFlush:CheckFlushRequest:cb",
|
||||
const_cast<int*>(&flush_req.reschedule_count));
|
||||
if (!bg_flush_args.empty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -3218,20 +3056,11 @@ void DBImpl::BackgroundCallFlush(Env::Priority thread_pri) {
|
||||
pending_outputs_inserted_elem(new std::list<uint64_t>::iterator(
|
||||
CaptureCurrentFileNumberInPendingOutputs()));
|
||||
FlushReason reason;
|
||||
bool flush_rescheduled_to_retain_udt = false;
|
||||
Status s =
|
||||
BackgroundFlush(&made_progress, &job_context, &log_buffer, &reason,
|
||||
&flush_rescheduled_to_retain_udt, thread_pri);
|
||||
if (s.IsTryAgain() && flush_rescheduled_to_retain_udt) {
|
||||
bg_cv_.SignalAll(); // In case a waiter can proceed despite the error
|
||||
mutex_.Unlock();
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::AfterRetainUDTReschedule:cb", nullptr);
|
||||
immutable_db_options_.clock->SleepForMicroseconds(
|
||||
100000); // prevent hot loop
|
||||
mutex_.Lock();
|
||||
} else if (!s.ok() && !s.IsShutdownInProgress() &&
|
||||
!s.IsColumnFamilyDropped() &&
|
||||
reason != FlushReason::kErrorRecovery) {
|
||||
|
||||
Status s = BackgroundFlush(&made_progress, &job_context, &log_buffer,
|
||||
&reason, thread_pri);
|
||||
if (!s.ok() && !s.IsShutdownInProgress() && !s.IsColumnFamilyDropped() &&
|
||||
reason != FlushReason::kErrorRecovery) {
|
||||
// Wait a little bit before retrying background flush in
|
||||
// case this is an environmental problem and we do not want to
|
||||
// chew up resources for failed flushes for the duration of
|
||||
@@ -3241,9 +3070,9 @@ void DBImpl::BackgroundCallFlush(Env::Priority thread_pri) {
|
||||
bg_cv_.SignalAll(); // In case a waiter can proceed despite the error
|
||||
mutex_.Unlock();
|
||||
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
|
||||
"[JOB %d] Waiting after background flush error: %s"
|
||||
"Waiting after background flush error: %s"
|
||||
"Accumulated background error counts: %" PRIu64,
|
||||
job_context.job_id, s.ToString().c_str(), error_cnt);
|
||||
s.ToString().c_str(), error_cnt);
|
||||
log_buffer.FlushBufferToLog();
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
immutable_db_options_.clock->SleepForMicroseconds(1000000);
|
||||
@@ -3252,33 +3081,29 @@ void DBImpl::BackgroundCallFlush(Env::Priority thread_pri) {
|
||||
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCallFlush:FlushFinish:0");
|
||||
ReleaseFileNumberFromPendingOutputs(pending_outputs_inserted_elem);
|
||||
// There is no need to do these clean up if the flush job is rescheduled
|
||||
// to retain user-defined timestamps because the job doesn't get to the
|
||||
// stage of actually flushing the MemTables.
|
||||
if (!flush_rescheduled_to_retain_udt) {
|
||||
// If flush failed, we want to delete all temporary files that we might
|
||||
// have created. Thus, we force full scan in FindObsoleteFiles()
|
||||
FindObsoleteFiles(&job_context, !s.ok() && !s.IsShutdownInProgress() &&
|
||||
!s.IsColumnFamilyDropped());
|
||||
// delete unnecessary files if any, this is done outside the mutex
|
||||
if (job_context.HaveSomethingToClean() ||
|
||||
job_context.HaveSomethingToDelete() || !log_buffer.IsEmpty()) {
|
||||
mutex_.Unlock();
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCallFlush:FilesFound");
|
||||
// Have to flush the info logs before bg_flush_scheduled_--
|
||||
// because if bg_flush_scheduled_ becomes 0 and the lock is
|
||||
// released, the deconstructor of DB can kick in and destroy all the
|
||||
// states of DB so info_log might not be available after that point.
|
||||
// It also applies to access other states that DB owns.
|
||||
log_buffer.FlushBufferToLog();
|
||||
if (job_context.HaveSomethingToDelete()) {
|
||||
PurgeObsoleteFiles(job_context);
|
||||
}
|
||||
job_context.Clean();
|
||||
mutex_.Lock();
|
||||
|
||||
// If flush failed, we want to delete all temporary files that we might have
|
||||
// created. Thus, we force full scan in FindObsoleteFiles()
|
||||
FindObsoleteFiles(&job_context, !s.ok() && !s.IsShutdownInProgress() &&
|
||||
!s.IsColumnFamilyDropped());
|
||||
// delete unnecessary files if any, this is done outside the mutex
|
||||
if (job_context.HaveSomethingToClean() ||
|
||||
job_context.HaveSomethingToDelete() || !log_buffer.IsEmpty()) {
|
||||
mutex_.Unlock();
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCallFlush:FilesFound");
|
||||
// Have to flush the info logs before bg_flush_scheduled_--
|
||||
// because if bg_flush_scheduled_ becomes 0 and the lock is
|
||||
// released, the deconstructor of DB can kick in and destroy all the
|
||||
// states of DB so info_log might not be available after that point.
|
||||
// It also applies to access other states that DB owns.
|
||||
log_buffer.FlushBufferToLog();
|
||||
if (job_context.HaveSomethingToDelete()) {
|
||||
PurgeObsoleteFiles(job_context);
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCallFlush:ContextCleanedUp");
|
||||
job_context.Clean();
|
||||
mutex_.Lock();
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCallFlush:ContextCleanedUp");
|
||||
|
||||
assert(num_running_flushes_ > 0);
|
||||
num_running_flushes_--;
|
||||
@@ -4011,7 +3836,7 @@ bool DBImpl::MCOverlap(ManualCompactionState* m, ManualCompactionState* m1) {
|
||||
void DBImpl::BuildCompactionJobInfo(
|
||||
const ColumnFamilyData* cfd, Compaction* c, const Status& st,
|
||||
const CompactionJobStats& compaction_job_stats, const int job_id,
|
||||
CompactionJobInfo* compaction_job_info) const {
|
||||
const Version* current, CompactionJobInfo* compaction_job_info) const {
|
||||
assert(compaction_job_info != nullptr);
|
||||
compaction_job_info->cf_id = cfd->GetID();
|
||||
compaction_job_info->cf_name = cfd->GetName();
|
||||
@@ -4021,7 +3846,7 @@ void DBImpl::BuildCompactionJobInfo(
|
||||
compaction_job_info->base_input_level = c->start_level();
|
||||
compaction_job_info->output_level = c->output_level();
|
||||
compaction_job_info->stats = compaction_job_stats;
|
||||
compaction_job_info->table_properties = c->GetTableProperties();
|
||||
compaction_job_info->table_properties = c->GetOutputTableProperties();
|
||||
compaction_job_info->compaction_reason = c->compaction_reason();
|
||||
compaction_job_info->compression = c->output_compression();
|
||||
|
||||
@@ -4035,9 +3860,15 @@ void DBImpl::BuildCompactionJobInfo(
|
||||
compaction_job_info->input_files.push_back(fn);
|
||||
compaction_job_info->input_file_infos.push_back(CompactionFileInfo{
|
||||
static_cast<int>(i), file_number, fmd->oldest_blob_file_number});
|
||||
if (compaction_job_info->table_properties.count(fn) == 0) {
|
||||
std::shared_ptr<const TableProperties> tp;
|
||||
auto s = current->GetTableProperties(read_options, &tp, fmd, &fn);
|
||||
if (s.ok()) {
|
||||
compaction_job_info->table_properties[fn] = tp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& newf : c->edit()->GetNewFiles()) {
|
||||
const FileMetaData& meta = newf.second;
|
||||
const FileDescriptor& desc = meta.fd;
|
||||
@@ -4190,18 +4021,8 @@ Status DBImpl::WaitForCompact(
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
} else if (wait_for_compact_options.close_db &&
|
||||
has_unpersisted_data_.load(std::memory_order_relaxed) &&
|
||||
!mutable_db_options_.avoid_flush_during_shutdown) {
|
||||
Status s =
|
||||
DBImpl::FlushAllColumnFamilies(FlushOptions(), FlushReason::kShutDown);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::WaitForCompact:StartWaiting");
|
||||
const auto deadline = immutable_db_options_.clock->NowMicros() +
|
||||
wait_for_compact_options.timeout.count();
|
||||
for (;;) {
|
||||
if (shutting_down_.load(std::memory_order_acquire)) {
|
||||
return Status::ShutdownInProgress();
|
||||
@@ -4211,24 +4032,9 @@ Status DBImpl::WaitForCompact(
|
||||
}
|
||||
if ((bg_bottom_compaction_scheduled_ || bg_compaction_scheduled_ ||
|
||||
bg_flush_scheduled_ || unscheduled_compactions_ ||
|
||||
unscheduled_flushes_ || error_handler_.IsRecoveryInProgress()) &&
|
||||
unscheduled_flushes_) &&
|
||||
(error_handler_.GetBGError().ok())) {
|
||||
if (wait_for_compact_options.timeout.count()) {
|
||||
if (bg_cv_.TimedWait(deadline)) {
|
||||
return Status::TimedOut();
|
||||
}
|
||||
} else {
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
} else if (wait_for_compact_options.close_db) {
|
||||
reject_new_background_jobs_ = true;
|
||||
mutex_.Unlock();
|
||||
Status s = Close();
|
||||
mutex_.Lock();
|
||||
if (!s.ok()) {
|
||||
reject_new_background_jobs_ = false;
|
||||
}
|
||||
return s;
|
||||
bg_cv_.Wait();
|
||||
} else {
|
||||
return error_handler_.GetBGError();
|
||||
}
|
||||
|
||||
+15
-28
@@ -143,6 +143,11 @@ DBOptions SanitizeOptions(const std::string& dbname, const DBOptions& src,
|
||||
result.wal_dir = result.wal_dir.substr(0, result.wal_dir.size() - 1);
|
||||
}
|
||||
|
||||
if (result.use_direct_reads && result.compaction_readahead_size == 0) {
|
||||
TEST_SYNC_POINT_CALLBACK("SanitizeOptions:direct_io", nullptr);
|
||||
result.compaction_readahead_size = 1024 * 1024 * 2;
|
||||
}
|
||||
|
||||
// Force flush on DB open if 2PC is enabled, since with 2PC we have no
|
||||
// guarantee that consecutive log files have consecutive sequence id, which
|
||||
// make recovery complicated.
|
||||
@@ -1198,10 +1203,10 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& wal_numbers,
|
||||
Status::Corruption("log record too small"));
|
||||
continue;
|
||||
}
|
||||
|
||||
// We create a new batch and initialize with a valid prot_info_ to store
|
||||
// the data checksums
|
||||
WriteBatch batch;
|
||||
std::unique_ptr<WriteBatch> new_batch;
|
||||
|
||||
status = WriteBatchInternal::SetContents(&batch, record);
|
||||
if (!status.ok()) {
|
||||
@@ -1210,29 +1215,26 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& wal_numbers,
|
||||
|
||||
const UnorderedMap<uint32_t, size_t>& record_ts_sz =
|
||||
reader.GetRecordedTimestampSize();
|
||||
// TODO(yuzhangyu): update mode to kReconcileInconsistency when user
|
||||
// comparator can be changed.
|
||||
status = HandleWriteBatchTimestampSizeDifference(
|
||||
&batch, running_ts_sz, record_ts_sz,
|
||||
TimestampSizeConsistencyMode::kReconcileInconsistency, &new_batch);
|
||||
TimestampSizeConsistencyMode::kVerifyConsistency);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
bool batch_updated = new_batch != nullptr;
|
||||
WriteBatch* batch_to_use = batch_updated ? new_batch.get() : &batch;
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"DBImpl::RecoverLogFiles:BeforeUpdateProtectionInfo:batch",
|
||||
batch_to_use);
|
||||
"DBImpl::RecoverLogFiles:BeforeUpdateProtectionInfo:batch", &batch);
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"DBImpl::RecoverLogFiles:BeforeUpdateProtectionInfo:checksum",
|
||||
&record_checksum);
|
||||
status = WriteBatchInternal::UpdateProtectionInfo(
|
||||
batch_to_use, 8 /* bytes_per_key */,
|
||||
batch_updated ? nullptr : &record_checksum);
|
||||
&batch, 8 /* bytes_per_key */, &record_checksum);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
SequenceNumber sequence = WriteBatchInternal::Sequence(batch_to_use);
|
||||
SequenceNumber sequence = WriteBatchInternal::Sequence(&batch);
|
||||
|
||||
if (immutable_db_options_.wal_recovery_mode ==
|
||||
WALRecoveryMode::kPointInTimeRecovery) {
|
||||
@@ -1253,7 +1255,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& wal_numbers,
|
||||
// and returns true.
|
||||
if (!InvokeWalFilterIfNeededOnWalRecord(wal_number, fname, reporter,
|
||||
status, stop_replay_by_wal_filter,
|
||||
*batch_to_use)) {
|
||||
batch)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1264,7 +1266,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& wal_numbers,
|
||||
// That's why we set ignore missing column families to true
|
||||
bool has_valid_writes = false;
|
||||
status = WriteBatchInternal::InsertInto(
|
||||
batch_to_use, column_family_memtables_.get(), &flush_scheduler_,
|
||||
&batch, column_family_memtables_.get(), &flush_scheduler_,
|
||||
&trim_history_scheduler_, true, wal_number, this,
|
||||
false /* concurrent_memtable_writes */, next_sequence,
|
||||
&has_valid_writes, seq_per_batch_, batch_per_txn_);
|
||||
@@ -1653,7 +1655,6 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
Version* version = cfd->current();
|
||||
version->Ref();
|
||||
const ReadOptions read_option(Env::IOActivity::kDBOpen);
|
||||
uint64_t num_input_entries = 0;
|
||||
s = BuildTable(
|
||||
dbname_, versions_.get(), immutable_db_options_, tboptions,
|
||||
file_options_for_compaction_, read_option, cfd->table_cache(),
|
||||
@@ -1663,8 +1664,7 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
io_tracer_, BlobFileCreationReason::kRecovery,
|
||||
empty_seqno_time_mapping, &event_logger_, job_id, Env::IO_HIGH,
|
||||
nullptr /* table_properties */, write_hint,
|
||||
nullptr /*full_history_ts_low*/, &blob_callback_, version,
|
||||
&num_input_entries);
|
||||
nullptr /*full_history_ts_low*/, &blob_callback_, version);
|
||||
version->Unref();
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
ROCKS_LOG_DEBUG(immutable_db_options_.info_log,
|
||||
@@ -1678,19 +1678,6 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
if (!io_s.ok() && s.ok()) {
|
||||
s = io_s;
|
||||
}
|
||||
|
||||
uint64_t total_num_entries = mem->num_entries();
|
||||
if (s.ok() && total_num_entries != num_input_entries) {
|
||||
std::string msg = "Expected " + std::to_string(total_num_entries) +
|
||||
" entries in memtable, but read " +
|
||||
std::to_string(num_input_entries);
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"[%s] [JOB %d] Level-0 flush during recover: %s",
|
||||
cfd->GetName().c_str(), job_id, msg.c_str());
|
||||
if (immutable_db_options_.flush_verify_memtable_count) {
|
||||
s = Status::Corruption(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ReleaseFileNumberFromPendingOutputs(pending_outputs_inserted_elem);
|
||||
|
||||
@@ -36,19 +36,14 @@ Status DBImplReadOnly::Get(const ReadOptions& read_options,
|
||||
/*timestamp*/ nullptr);
|
||||
}
|
||||
|
||||
Status DBImplReadOnly::Get(const ReadOptions& _read_options,
|
||||
Status DBImplReadOnly::Get(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* pinnable_val,
|
||||
std::string* timestamp) {
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kGet) {
|
||||
if (read_options.io_activity != Env::IOActivity::kUnknown) {
|
||||
return Status::InvalidArgument(
|
||||
"Can only call Get with `ReadOptions::io_activity` is "
|
||||
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kGet`");
|
||||
}
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kGet;
|
||||
"Cannot call Get with `ReadOptions::io_activity` != "
|
||||
"`Env::IOActivity::kUnknown`");
|
||||
}
|
||||
assert(pinnable_val != nullptr);
|
||||
PERF_CPU_TIMER_GUARD(get_cpu_nanos, immutable_db_options_.clock);
|
||||
@@ -57,9 +52,8 @@ Status DBImplReadOnly::Get(const ReadOptions& _read_options,
|
||||
|
||||
assert(column_family);
|
||||
if (read_options.timestamp) {
|
||||
const Status s =
|
||||
FailIfTsMismatchCf(column_family, *(read_options.timestamp),
|
||||
/*ts_for_read=*/true);
|
||||
const Status s = FailIfTsMismatchCf(
|
||||
column_family, *(read_options.timestamp), /*ts_for_read=*/true);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -122,23 +116,17 @@ Status DBImplReadOnly::Get(const ReadOptions& _read_options,
|
||||
return s;
|
||||
}
|
||||
|
||||
Iterator* DBImplReadOnly::NewIterator(const ReadOptions& _read_options,
|
||||
Iterator* DBImplReadOnly::NewIterator(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family) {
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kDBIterator) {
|
||||
if (read_options.io_activity != Env::IOActivity::kUnknown) {
|
||||
return NewErrorIterator(Status::InvalidArgument(
|
||||
"Can only call NewIterator with `ReadOptions::io_activity` is "
|
||||
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kDBIterator`"));
|
||||
}
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kDBIterator;
|
||||
"Cannot call NewIterator with `ReadOptions::io_activity` != "
|
||||
"`Env::IOActivity::kUnknown`"));
|
||||
}
|
||||
assert(column_family);
|
||||
if (read_options.timestamp) {
|
||||
const Status s =
|
||||
FailIfTsMismatchCf(column_family, *(read_options.timestamp),
|
||||
/*ts_for_read=*/true);
|
||||
const Status s = FailIfTsMismatchCf(
|
||||
column_family, *(read_options.timestamp), /*ts_for_read=*/true);
|
||||
if (!s.ok()) {
|
||||
return NewErrorIterator(s);
|
||||
}
|
||||
|
||||
@@ -28,14 +28,14 @@ class DBImplReadOnly : public DBImpl {
|
||||
virtual Status Get(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value) override;
|
||||
Status Get(const ReadOptions& _read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value, std::string* timestamp) override;
|
||||
Status Get(const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
const Slice& key, PinnableSlice* value,
|
||||
std::string* timestamp) override;
|
||||
|
||||
// TODO: Implement ReadOnly MultiGet?
|
||||
|
||||
using DBImpl::NewIterator;
|
||||
virtual Iterator* NewIterator(const ReadOptions& _read_options,
|
||||
virtual Iterator* NewIterator(const ReadOptions&,
|
||||
ColumnFamilyHandle* column_family) override;
|
||||
|
||||
virtual Status NewIterators(
|
||||
@@ -179,3 +179,4 @@ class DBImplReadOnly : public DBImpl {
|
||||
friend class DB;
|
||||
};
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -340,36 +340,16 @@ Status DBImplSecondary::RecoverLogFiles(
|
||||
}
|
||||
|
||||
// Implementation of the DB interface
|
||||
Status DBImplSecondary::Get(const ReadOptions& _read_options,
|
||||
Status DBImplSecondary::Get(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value) {
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kGet) {
|
||||
return Status::InvalidArgument(
|
||||
"Can only call Get with `ReadOptions::io_activity` is "
|
||||
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kGet`");
|
||||
}
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kGet;
|
||||
}
|
||||
return GetImpl(read_options, column_family, key, value,
|
||||
/*timestamp*/ nullptr);
|
||||
}
|
||||
|
||||
Status DBImplSecondary::Get(const ReadOptions& _read_options,
|
||||
Status DBImplSecondary::Get(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value, std::string* timestamp) {
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kGet) {
|
||||
return Status::InvalidArgument(
|
||||
"Can only call Get with `ReadOptions::io_activity` is "
|
||||
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kGet`");
|
||||
}
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kGet;
|
||||
}
|
||||
return GetImpl(read_options, column_family, key, value, timestamp);
|
||||
}
|
||||
|
||||
@@ -377,6 +357,11 @@ Status DBImplSecondary::GetImpl(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice& key, PinnableSlice* pinnable_val,
|
||||
std::string* timestamp) {
|
||||
if (read_options.io_activity != Env::IOActivity::kUnknown) {
|
||||
return Status::InvalidArgument(
|
||||
"Cannot call Get with `ReadOptions::io_activity` != "
|
||||
"`Env::IOActivity::kUnknown`");
|
||||
}
|
||||
assert(pinnable_val != nullptr);
|
||||
PERF_CPU_TIMER_GUARD(get_cpu_nanos, immutable_db_options_.clock);
|
||||
StopWatch sw(immutable_db_options_.clock, stats_, DB_GET);
|
||||
@@ -467,18 +452,8 @@ Status DBImplSecondary::GetImpl(const ReadOptions& read_options,
|
||||
return s;
|
||||
}
|
||||
|
||||
Iterator* DBImplSecondary::NewIterator(const ReadOptions& _read_options,
|
||||
Iterator* DBImplSecondary::NewIterator(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family) {
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kDBIterator) {
|
||||
return NewErrorIterator(Status::InvalidArgument(
|
||||
"Can only call NewIterator with `ReadOptions::io_activity` is "
|
||||
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kDBIterator`"));
|
||||
}
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kDBIterator;
|
||||
}
|
||||
if (read_options.managed) {
|
||||
return NewErrorIterator(
|
||||
Status::NotSupported("Managed iterator is not supported anymore."));
|
||||
@@ -487,12 +462,16 @@ Iterator* DBImplSecondary::NewIterator(const ReadOptions& _read_options,
|
||||
return NewErrorIterator(Status::NotSupported(
|
||||
"ReadTier::kPersistedData is not yet supported in iterators."));
|
||||
}
|
||||
if (read_options.io_activity != Env::IOActivity::kUnknown) {
|
||||
return NewErrorIterator(Status::InvalidArgument(
|
||||
"Cannot call NewIterator with `ReadOptions::io_activity` != "
|
||||
"`Env::IOActivity::kUnknown`"));
|
||||
}
|
||||
|
||||
assert(column_family);
|
||||
if (read_options.timestamp) {
|
||||
const Status s =
|
||||
FailIfTsMismatchCf(column_family, *(read_options.timestamp),
|
||||
/*ts_for_read=*/true);
|
||||
const Status s = FailIfTsMismatchCf(
|
||||
column_family, *(read_options.timestamp), /*ts_for_read=*/true);
|
||||
if (!s.ok()) {
|
||||
return NewErrorIterator(s);
|
||||
}
|
||||
@@ -544,19 +523,9 @@ ArenaWrappedDBIter* DBImplSecondary::NewIteratorImpl(
|
||||
}
|
||||
|
||||
Status DBImplSecondary::NewIterators(
|
||||
const ReadOptions& _read_options,
|
||||
const ReadOptions& read_options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
std::vector<Iterator*>* iterators) {
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kDBIterator) {
|
||||
return Status::InvalidArgument(
|
||||
"Can only call NewIterators with `ReadOptions::io_activity` is "
|
||||
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kDBIterator`");
|
||||
}
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kDBIterator;
|
||||
}
|
||||
if (read_options.managed) {
|
||||
return Status::NotSupported("Managed iterator is not supported anymore.");
|
||||
}
|
||||
@@ -564,6 +533,11 @@ Status DBImplSecondary::NewIterators(
|
||||
return Status::NotSupported(
|
||||
"ReadTier::kPersistedData is not yet supported in iterators.");
|
||||
}
|
||||
if (read_options.io_activity != Env::IOActivity::kUnknown) {
|
||||
return Status::InvalidArgument(
|
||||
"Cannot call NewIterators with `ReadOptions::io_activity` != "
|
||||
"`Env::IOActivity::kUnknown`");
|
||||
}
|
||||
ReadCallback* read_callback = nullptr; // No read callback provided.
|
||||
if (iterators == nullptr) {
|
||||
return Status::InvalidArgument("iterators not allowed to be nullptr");
|
||||
@@ -986,8 +960,6 @@ Status DB::OpenAndCompact(
|
||||
delete db;
|
||||
if (s.ok()) {
|
||||
return serialization_status;
|
||||
} else {
|
||||
serialization_status.PermitUncheckedError();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -96,13 +96,12 @@ class DBImplSecondary : public DBImpl {
|
||||
// workaround, the secondaries can be opened with `max_open_files=-1` so that
|
||||
// it eagerly keeps all talbe files open and is able to access the contents of
|
||||
// deleted files via prior open fd.
|
||||
Status Get(const ReadOptions& _read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value) override;
|
||||
Status Get(const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
const Slice& key, PinnableSlice* value) override;
|
||||
|
||||
Status Get(const ReadOptions& _read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value, std::string* timestamp) override;
|
||||
Status Get(const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
const Slice& key, PinnableSlice* value,
|
||||
std::string* timestamp) override;
|
||||
|
||||
Status GetImpl(const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
const Slice& key, PinnableSlice* value,
|
||||
@@ -118,7 +117,7 @@ class DBImplSecondary : public DBImpl {
|
||||
// deleted. As a partial hacky workaround, the secondaries can be opened with
|
||||
// `max_open_files=-1` so that it eagerly keeps all talbe files open and is
|
||||
// able to access the contents of deleted files via prior open fd.
|
||||
Iterator* NewIterator(const ReadOptions& _read_options,
|
||||
Iterator* NewIterator(const ReadOptions&,
|
||||
ColumnFamilyHandle* column_family) override;
|
||||
|
||||
ArenaWrappedDBIter* NewIteratorImpl(const ReadOptions& read_options,
|
||||
@@ -128,7 +127,7 @@ class DBImplSecondary : public DBImpl {
|
||||
bool expose_blob_index = false,
|
||||
bool allow_refresh = true);
|
||||
|
||||
Status NewIterators(const ReadOptions& _read_options,
|
||||
Status NewIterators(const ReadOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
std::vector<Iterator*>* iterators) override;
|
||||
|
||||
@@ -325,3 +324,4 @@ class DBImplSecondary : public DBImpl {
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
+2
-2
@@ -77,7 +77,6 @@ DBIter::DBIter(Env* _env, const ReadOptions& read_options,
|
||||
expose_blob_index_(expose_blob_index),
|
||||
is_blob_(false),
|
||||
arena_mode_(arena_mode),
|
||||
io_activity_(read_options.io_activity),
|
||||
db_impl_(db_impl),
|
||||
cfd_(cfd),
|
||||
timestamp_ub_(read_options.timestamp),
|
||||
@@ -197,11 +196,12 @@ bool DBIter::SetBlobValueIfNeeded(const Slice& user_key,
|
||||
|
||||
// TODO: consider moving ReadOptions from ArenaWrappedDBIter to DBIter to
|
||||
// avoid having to copy options back and forth.
|
||||
// TODO: plumb Env::IOActivity
|
||||
ReadOptions read_options;
|
||||
read_options.read_tier = read_tier_;
|
||||
read_options.fill_cache = fill_cache_;
|
||||
read_options.verify_checksums = verify_checksums_;
|
||||
read_options.io_activity = io_activity_;
|
||||
|
||||
constexpr FilePrefetchBuffer* prefetch_buffer = nullptr;
|
||||
constexpr uint64_t* bytes_read = nullptr;
|
||||
|
||||
|
||||
@@ -384,7 +384,6 @@ class DBIter final : public Iterator {
|
||||
bool expose_blob_index_;
|
||||
bool is_blob_;
|
||||
bool arena_mode_;
|
||||
const Env::IOActivity io_activity_;
|
||||
// List of operands for merge operator.
|
||||
MergeContext merge_context_;
|
||||
LocalStatistics local_stats_;
|
||||
|
||||
+28
-37
@@ -880,13 +880,9 @@ TEST_F(DBOptionsTest, SanitizeFIFOPeriodicCompaction) {
|
||||
Options options;
|
||||
options.compaction_style = kCompactionStyleFIFO;
|
||||
options.env = CurrentOptions().env;
|
||||
// Default value allows RocksDB to set ttl to 30 days.
|
||||
ASSERT_EQ(30 * 24 * 60 * 60, dbfull()->GetOptions().ttl);
|
||||
|
||||
// Disable
|
||||
options.ttl = 0;
|
||||
Reopen(options);
|
||||
ASSERT_EQ(0, dbfull()->GetOptions().ttl);
|
||||
ASSERT_EQ(30 * 24 * 60 * 60, dbfull()->GetOptions().ttl);
|
||||
|
||||
options.ttl = 100;
|
||||
Reopen(options);
|
||||
@@ -896,12 +892,15 @@ TEST_F(DBOptionsTest, SanitizeFIFOPeriodicCompaction) {
|
||||
Reopen(options);
|
||||
ASSERT_EQ(100 * 24 * 60 * 60, dbfull()->GetOptions().ttl);
|
||||
|
||||
// periodic_compaction_seconds should have no effect
|
||||
// on FIFO compaction.
|
||||
options.ttl = 200;
|
||||
options.periodic_compaction_seconds = 300;
|
||||
Reopen(options);
|
||||
ASSERT_EQ(200, dbfull()->GetOptions().ttl);
|
||||
|
||||
options.ttl = 500;
|
||||
options.periodic_compaction_seconds = 300;
|
||||
Reopen(options);
|
||||
ASSERT_EQ(500, dbfull()->GetOptions().ttl);
|
||||
ASSERT_EQ(300, dbfull()->GetOptions().ttl);
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, SetFIFOCompactionOptions) {
|
||||
@@ -1034,37 +1033,29 @@ TEST_F(DBOptionsTest, SetFIFOCompactionOptions) {
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, CompactionReadaheadSizeChange) {
|
||||
for (bool use_direct_reads : {true, false}) {
|
||||
SpecialEnv env(env_);
|
||||
Options options;
|
||||
options.env = &env;
|
||||
SpecialEnv env(env_);
|
||||
Options options;
|
||||
options.env = &env;
|
||||
|
||||
options.use_direct_reads = use_direct_reads;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
const std::string kValue(1024, 'v');
|
||||
Status s = TryReopen(options);
|
||||
if (use_direct_reads && (s.IsNotSupported() || s.IsInvalidArgument())) {
|
||||
continue;
|
||||
} else {
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
options.compaction_readahead_size = 0;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
const std::string kValue(1024, 'v');
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_EQ(1024 * 1024 * 2,
|
||||
dbfull()->GetDBOptions().compaction_readahead_size);
|
||||
ASSERT_OK(dbfull()->SetDBOptions({{"compaction_readahead_size", "256"}}));
|
||||
ASSERT_EQ(256, dbfull()->GetDBOptions().compaction_readahead_size);
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
ASSERT_OK(Put(Key(i), kValue));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
for (int i = 0; i < 1024 * 2; i++) {
|
||||
ASSERT_OK(Put(Key(i), kValue));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(256, env_->compaction_readahead_size_);
|
||||
Close();
|
||||
ASSERT_EQ(0, dbfull()->GetDBOptions().compaction_readahead_size);
|
||||
ASSERT_OK(dbfull()->SetDBOptions({{"compaction_readahead_size", "256"}}));
|
||||
ASSERT_EQ(256, dbfull()->GetDBOptions().compaction_readahead_size);
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
ASSERT_OK(Put(Key(i), kValue));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
for (int i = 0; i < 1024 * 2; i++) {
|
||||
ASSERT_OK(Put(Key(i), kValue));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(256, env_->compaction_readahead_size_);
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, FIFOTtlBackwardCompatible) {
|
||||
@@ -1315,7 +1306,7 @@ TEST_F(DBOptionsTest, TempOptionsFailTest) {
|
||||
[&](void* /*arg*/) { fs->SetFilesystemActive(true); });
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
ASSERT_NOK(TryReopen(options));
|
||||
TryReopen(options);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
std::vector<std::string> filenames;
|
||||
|
||||
@@ -2348,9 +2348,6 @@ TEST_F(DBPropertiesTest, TableMetaIndexKeys) {
|
||||
EXPECT_EQ("rocksdb.hashindex.prefixes",
|
||||
PopMetaIndexKey(meta_iter.get()));
|
||||
}
|
||||
if (bbto->format_version >= 6) {
|
||||
EXPECT_EQ("rocksdb.index", PopMetaIndexKey(meta_iter.get()));
|
||||
}
|
||||
}
|
||||
EXPECT_EQ("rocksdb.properties", PopMetaIndexKey(meta_iter.get()));
|
||||
EXPECT_EQ("NOT_FOUND", PopMetaIndexKey(meta_iter.get()));
|
||||
|
||||
@@ -3475,42 +3475,6 @@ TEST_F(DBRangeDelTest, NonBottommostCompactionDropRangetombstone) {
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, MemtableMaxRangeDeletions) {
|
||||
// Tests option `memtable_max_range_deletions`.
|
||||
Options options = CurrentOptions();
|
||||
options.level_compaction_dynamic_file_size = false;
|
||||
options.memtable_max_range_deletions = 50;
|
||||
options.level0_file_num_compaction_trigger = 5;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
for (int i = 0; i < 50; ++i) {
|
||||
// Intentionally delete overlapping ranges to see if the option
|
||||
// checks number of range tombstone fragments instead.
|
||||
ASSERT_OK(Put(Key(i), "val1"));
|
||||
ASSERT_OK(Put(Key(i + 1), "val2"));
|
||||
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
|
||||
Key(i), Key(i + 2)));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
}
|
||||
// One more write to trigger flush.
|
||||
ASSERT_OK(Put(Key(50), "val"));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
|
||||
// This should take effect for the next new memtable.
|
||||
ASSERT_OK(db_->SetOptions({{"memtable_max_range_deletions", "1"}}));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(0));
|
||||
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
|
||||
Key(50), Key(100)));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(0));
|
||||
// One more write to trigger flush.
|
||||
ASSERT_OK(Put(Key(50), "new val"));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
|
||||
ASSERT_EQ(3, NumTableFilesAtLevel(0));
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -235,18 +235,9 @@ TEST_P(DBRateLimiterOnReadTest, VerifyChecksum) {
|
||||
ASSERT_EQ(0, options_.rate_limiter->GetTotalRequests(Env::IO_USER));
|
||||
|
||||
ASSERT_OK(db_->VerifyChecksum(GetReadOptions()));
|
||||
// In BufferedIO,
|
||||
// there are 7 reads per file, each of which will be rate-limited.
|
||||
// During open: read footer, meta index block, properties block, index block.
|
||||
// During actual checksum verification: read meta index block, verify checksum
|
||||
// in meta blocks and verify checksum in file blocks.
|
||||
//
|
||||
// In DirectIO, where we support tail prefetching, during table open, we only
|
||||
// do 1 read instead of 4 as described above. Actual checksum verification
|
||||
// reads stay the same.
|
||||
int num_read_per_file = (!use_direct_io_) ? 7 : 4;
|
||||
int expected = kNumFiles * num_read_per_file;
|
||||
|
||||
// There are 3 reads per file: ReadMetaIndexBlock,
|
||||
// VerifyChecksumInMetaBlocks, VerifyChecksumInBlocks
|
||||
int expected = kNumFiles * 3;
|
||||
ASSERT_EQ(expected, options_.rate_limiter->GetTotalRequests(Env::IO_USER));
|
||||
}
|
||||
|
||||
|
||||
@@ -219,7 +219,7 @@ TEST_F(DBStatisticsTest, VerifyChecksumReadStat) {
|
||||
ASSERT_OK(Flush());
|
||||
std::unordered_map<std::string, uint64_t> table_files;
|
||||
uint64_t table_files_size = 0;
|
||||
ASSERT_OK(GetAllDataFiles(kTableFile, &table_files, &table_files_size));
|
||||
GetAllDataFiles(kTableFile, &table_files, &table_files_size);
|
||||
|
||||
{
|
||||
// Scenario 1: Table verified in `VerifyFileChecksums()`. This should read
|
||||
|
||||
+20
-41
@@ -697,7 +697,7 @@ TEST_F(DBTest, ReadFromPersistedTier) {
|
||||
|
||||
// 3rd round: delete and flush
|
||||
ASSERT_OK(db_->Delete(wopt, handles_[1], "foo"));
|
||||
ASSERT_OK(Flush(1));
|
||||
Flush(1);
|
||||
ASSERT_OK(db_->Delete(wopt, handles_[1], "bar"));
|
||||
|
||||
ASSERT_TRUE(db_->Get(ropt, handles_[1], "foo", &value).IsNotFound());
|
||||
@@ -860,7 +860,7 @@ TEST_F(DBTest, DISABLED_VeryLargeValue) {
|
||||
ASSERT_EQ('w', value[0]);
|
||||
|
||||
// Compact all files.
|
||||
ASSERT_OK(Flush());
|
||||
Flush();
|
||||
db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
|
||||
// Check DB is not in read-only state.
|
||||
@@ -1300,7 +1300,7 @@ TEST_F(DBTest, MetaDataTest) {
|
||||
options.disable_auto_compactions = true;
|
||||
|
||||
int64_t temp_time = 0;
|
||||
ASSERT_OK(options.env->GetCurrentTime(&temp_time));
|
||||
options.env->GetCurrentTime(&temp_time);
|
||||
uint64_t start_time = static_cast<uint64_t>(temp_time);
|
||||
|
||||
DestroyAndReopen(options);
|
||||
@@ -1329,7 +1329,7 @@ TEST_F(DBTest, MetaDataTest) {
|
||||
std::vector<std::vector<FileMetaData>> files_by_level;
|
||||
dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(), &files_by_level);
|
||||
|
||||
ASSERT_OK(options.env->GetCurrentTime(&temp_time));
|
||||
options.env->GetCurrentTime(&temp_time);
|
||||
uint64_t end_time = static_cast<uint64_t>(temp_time);
|
||||
|
||||
ColumnFamilyMetaData cf_meta;
|
||||
@@ -3648,7 +3648,7 @@ TEST_F(DBTest, BlockBasedTablePrefixHashIndexTest) {
|
||||
ASSERT_OK(Put("kk2", "v2"));
|
||||
ASSERT_OK(Put("kk", "v3"));
|
||||
ASSERT_OK(Put("k", "v4"));
|
||||
ASSERT_OK(Flush());
|
||||
Flush();
|
||||
|
||||
ASSERT_EQ("v1", Get("kk1"));
|
||||
ASSERT_EQ("v2", Get("kk2"));
|
||||
@@ -4280,8 +4280,8 @@ TEST_F(DBTest, ConcurrentMemtableNotSupported) {
|
||||
options.soft_pending_compaction_bytes_limit = 0;
|
||||
options.hard_pending_compaction_bytes_limit = 100;
|
||||
options.create_if_missing = true;
|
||||
Close();
|
||||
ASSERT_OK(DestroyDB(dbname_, options));
|
||||
|
||||
DestroyDB(dbname_, options);
|
||||
options.memtable_factory.reset(NewHashLinkListRepFactory(4, 0, 3, true, 4));
|
||||
ASSERT_NOK(TryReopen(options));
|
||||
|
||||
@@ -4622,7 +4622,7 @@ TEST_F(DBTest, GetThreadStatus) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.enable_thread_tracking = true;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
TryReopen(options);
|
||||
|
||||
std::vector<ThreadStatus> thread_list;
|
||||
Status s = env_->GetThreadList(&thread_list);
|
||||
@@ -4693,7 +4693,7 @@ TEST_F(DBTest, DisableThreadStatus) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.enable_thread_tracking = false;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
TryReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu", "about-to-remove"}, options);
|
||||
// Verify non of the column family info exists
|
||||
env_->GetThreadStatusUpdater()->TEST_VerifyColumnFamilyInfoMap(handles_,
|
||||
@@ -4902,7 +4902,7 @@ TEST_P(DBTestWithParam, PreShutdownMultipleCompaction) {
|
||||
options.level0_slowdown_writes_trigger = 1 << 10;
|
||||
options.max_subcompactions = max_subcompactions_;
|
||||
|
||||
ASSERT_OK(TryReopen(options));
|
||||
TryReopen(options);
|
||||
Random rnd(301);
|
||||
|
||||
std::vector<ThreadStatus> thread_list;
|
||||
@@ -4991,7 +4991,7 @@ TEST_P(DBTestWithParam, PreShutdownCompactionMiddle) {
|
||||
options.level0_slowdown_writes_trigger = 1 << 10;
|
||||
options.max_subcompactions = max_subcompactions_;
|
||||
|
||||
ASSERT_OK(TryReopen(options));
|
||||
TryReopen(options);
|
||||
Random rnd(301);
|
||||
|
||||
std::vector<ThreadStatus> thread_list;
|
||||
@@ -6902,16 +6902,16 @@ TEST_F(DBTest, RowCache) {
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
LRUCacheOptions cache_options;
|
||||
cache_options.capacity = 8192;
|
||||
options.row_cache = cache_options.MakeSharedRowCache();
|
||||
options.row_cache = cache_options.MakeSharedGeneralCache();
|
||||
// BEGIN check that Cache classes as aliases of each other.
|
||||
// Currently, RowCache and BlockCache are aliases for Cache.
|
||||
// Currently, GeneralCache and BlockCache are aliases for Cache.
|
||||
// This is expected to change (carefully, intentionally)
|
||||
std::shared_ptr<RowCache> row_cache = options.row_cache;
|
||||
std::shared_ptr<Cache> cache = row_cache;
|
||||
std::shared_ptr<BlockCache> block_cache = row_cache;
|
||||
row_cache = cache;
|
||||
std::shared_ptr<GeneralCache> general_cache = options.row_cache;
|
||||
std::shared_ptr<Cache> cache = general_cache;
|
||||
std::shared_ptr<BlockCache> block_cache = general_cache;
|
||||
general_cache = cache;
|
||||
block_cache = cache;
|
||||
row_cache = block_cache;
|
||||
general_cache = block_cache;
|
||||
cache = block_cache;
|
||||
// END check that Cache classes as aliases of each other.
|
||||
DestroyAndReopen(options);
|
||||
@@ -6927,27 +6927,6 @@ TEST_F(DBTest, RowCache) {
|
||||
ASSERT_EQ(Get("foo"), "bar");
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), 1);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), 1);
|
||||
|
||||
// Also test non-OK cache insertion (would be ASAN failure on memory leak)
|
||||
class FailInsertionCache : public CacheWrapper {
|
||||
public:
|
||||
using CacheWrapper::CacheWrapper;
|
||||
const char* Name() const override { return "FailInsertionCache"; }
|
||||
Status Insert(const Slice&, Cache::ObjectPtr, const CacheItemHelper*,
|
||||
size_t, Handle** = nullptr,
|
||||
Priority = Priority::LOW) override {
|
||||
return Status::MemoryLimit();
|
||||
}
|
||||
};
|
||||
options.row_cache = std::make_shared<FailInsertionCache>(options.row_cache);
|
||||
ASSERT_OK(options.statistics->Reset());
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_EQ(Get("foo"), "bar");
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), 1);
|
||||
ASSERT_EQ(Get("foo"), "bar");
|
||||
// Test condition requires row cache insertion to fail
|
||||
ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), 2);
|
||||
}
|
||||
|
||||
TEST_F(DBTest, PinnableSliceAndRowCache) {
|
||||
@@ -7206,14 +7185,14 @@ TEST_F(DBTest, CreationTimeOfOldestFile) {
|
||||
int idx = 0;
|
||||
|
||||
int64_t time_1 = 0;
|
||||
ASSERT_OK(env_->GetCurrentTime(&time_1));
|
||||
env_->GetCurrentTime(&time_1);
|
||||
const uint64_t uint_time_1 = static_cast<uint64_t>(time_1);
|
||||
|
||||
// Add 50 hours
|
||||
env_->MockSleepForSeconds(50 * 60 * 60);
|
||||
|
||||
int64_t time_2 = 0;
|
||||
ASSERT_OK(env_->GetCurrentTime(&time_2));
|
||||
env_->GetCurrentTime(&time_2);
|
||||
const uint64_t uint_time_2 = static_cast<uint64_t>(time_2);
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
|
||||
+7
-75
@@ -266,7 +266,7 @@ TEST_F(DBTest2, CacheIndexAndFilterWithDBRestart) {
|
||||
ASSERT_OK(Put(1, "a", "begin"));
|
||||
ASSERT_OK(Put(1, "z", "end"));
|
||||
ASSERT_OK(Flush(1));
|
||||
ASSERT_OK(TryReopenWithColumnFamilies({"default", "pikachu"}, options));
|
||||
TryReopenWithColumnFamilies({"default", "pikachu"}, options);
|
||||
|
||||
std::string value;
|
||||
value = Get(1, "a");
|
||||
@@ -357,10 +357,10 @@ TEST_P(DBTestSharedWriteBufferAcrossCFs, SharedWriteBufferAcrossCFs) {
|
||||
// are newer CFs created.
|
||||
flush_listener->expected_flush_reason = FlushReason::kManualFlush;
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(1), wo));
|
||||
ASSERT_OK(Flush(3));
|
||||
Flush(3);
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(1), wo));
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(1), wo));
|
||||
ASSERT_OK(Flush(0));
|
||||
Flush(0);
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
|
||||
static_cast<uint64_t>(1));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
|
||||
@@ -2068,7 +2068,7 @@ class PinL0IndexAndFilterBlocksTest
|
||||
// reset block cache
|
||||
table_options.block_cache = NewLRUCache(64 * 1024);
|
||||
options->table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
ASSERT_OK(TryReopenWithColumnFamilies({"default", "pikachu"}, *options));
|
||||
TryReopenWithColumnFamilies({"default", "pikachu"}, *options);
|
||||
// create new table at L0
|
||||
ASSERT_OK(Put(1, "a2", "begin2"));
|
||||
ASSERT_OK(Put(1, "z2", "end2"));
|
||||
@@ -2188,7 +2188,7 @@ TEST_P(PinL0IndexAndFilterBlocksTest, DisablePrefetchingNonL0IndexAndFilter) {
|
||||
// Reopen database. If max_open_files is set as -1, table readers will be
|
||||
// preloaded. This will trigger a BlockBasedTable::Open() and prefetch
|
||||
// L0 index and filter. Level 1's prefetching is disabled in DB::Open()
|
||||
ASSERT_OK(TryReopenWithColumnFamilies({"default", "pikachu"}, options));
|
||||
TryReopenWithColumnFamilies({"default", "pikachu"}, options);
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
@@ -3804,7 +3804,7 @@ TEST_F(DBTest2, MemtableOnlyIterator) {
|
||||
ASSERT_EQ(2, count);
|
||||
delete it;
|
||||
|
||||
ASSERT_OK(Flush(1));
|
||||
Flush(1);
|
||||
|
||||
// After flushing
|
||||
// point lookups
|
||||
@@ -4112,7 +4112,7 @@ TEST_F(DBTest2, LiveFilesOmitObsoleteFiles) {
|
||||
ASSERT_OK(Put("key", "val"));
|
||||
FlushOptions flush_opts;
|
||||
flush_opts.wait = false;
|
||||
ASSERT_OK(db_->Flush(flush_opts));
|
||||
db_->Flush(flush_opts);
|
||||
TEST_SYNC_POINT("DBTest2::LiveFilesOmitObsoleteFiles:FlushTriggered");
|
||||
|
||||
ASSERT_OK(db_->DisableFileDeletions());
|
||||
@@ -6876,7 +6876,6 @@ TEST_F(DBTest2, LastLevelTemperatureUniversal) {
|
||||
TEST_F(DBTest2, LastLevelStatistics) {
|
||||
Options options = CurrentOptions();
|
||||
options.bottommost_temperature = Temperature::kWarm;
|
||||
options.default_temperature = Temperature::kHot;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
options.statistics = CreateDBStatistics();
|
||||
@@ -6890,10 +6889,6 @@ TEST_F(DBTest2, LastLevelStatistics) {
|
||||
|
||||
ASSERT_GT(options.statistics->getTickerCount(NON_LAST_LEVEL_READ_BYTES), 0);
|
||||
ASSERT_GT(options.statistics->getTickerCount(NON_LAST_LEVEL_READ_COUNT), 0);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NON_LAST_LEVEL_READ_BYTES),
|
||||
options.statistics->getTickerCount(HOT_FILE_READ_BYTES));
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NON_LAST_LEVEL_READ_COUNT),
|
||||
options.statistics->getTickerCount(HOT_FILE_READ_COUNT));
|
||||
ASSERT_EQ(options.statistics->getTickerCount(LAST_LEVEL_READ_BYTES), 0);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(LAST_LEVEL_READ_COUNT), 0);
|
||||
|
||||
@@ -6904,10 +6899,6 @@ TEST_F(DBTest2, LastLevelStatistics) {
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ("bar", Get("bar"));
|
||||
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NON_LAST_LEVEL_READ_BYTES),
|
||||
options.statistics->getTickerCount(HOT_FILE_READ_BYTES));
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NON_LAST_LEVEL_READ_COUNT),
|
||||
options.statistics->getTickerCount(HOT_FILE_READ_COUNT));
|
||||
ASSERT_EQ(options.statistics->getTickerCount(LAST_LEVEL_READ_BYTES),
|
||||
options.statistics->getTickerCount(WARM_FILE_READ_BYTES));
|
||||
ASSERT_EQ(options.statistics->getTickerCount(LAST_LEVEL_READ_COUNT),
|
||||
@@ -6928,30 +6919,6 @@ TEST_F(DBTest2, LastLevelStatistics) {
|
||||
pre_bytes);
|
||||
ASSERT_GT(options.statistics->getTickerCount(NON_LAST_LEVEL_READ_COUNT),
|
||||
pre_count);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NON_LAST_LEVEL_READ_BYTES),
|
||||
options.statistics->getTickerCount(HOT_FILE_READ_BYTES));
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NON_LAST_LEVEL_READ_COUNT),
|
||||
options.statistics->getTickerCount(HOT_FILE_READ_COUNT));
|
||||
ASSERT_EQ(options.statistics->getTickerCount(LAST_LEVEL_READ_BYTES),
|
||||
options.statistics->getTickerCount(WARM_FILE_READ_BYTES));
|
||||
ASSERT_EQ(options.statistics->getTickerCount(LAST_LEVEL_READ_COUNT),
|
||||
options.statistics->getTickerCount(WARM_FILE_READ_COUNT));
|
||||
|
||||
// Not a realistic setting to make last level kWarm and default temp kCold.
|
||||
// This is just for testing default temp can be reset on reopen while the
|
||||
// last level temp is consistent across DB reopen because those file's temp
|
||||
// are persisted in manifest.
|
||||
options.default_temperature = Temperature::kCold;
|
||||
ASSERT_OK(options.statistics->Reset());
|
||||
Reopen(options);
|
||||
ASSERT_EQ("bar", Get("bar"));
|
||||
|
||||
ASSERT_EQ(0, options.statistics->getTickerCount(HOT_FILE_READ_BYTES));
|
||||
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NON_LAST_LEVEL_READ_BYTES),
|
||||
options.statistics->getTickerCount(COLD_FILE_READ_BYTES));
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NON_LAST_LEVEL_READ_COUNT),
|
||||
options.statistics->getTickerCount(COLD_FILE_READ_COUNT));
|
||||
ASSERT_EQ(options.statistics->getTickerCount(LAST_LEVEL_READ_BYTES),
|
||||
options.statistics->getTickerCount(WARM_FILE_READ_BYTES));
|
||||
ASSERT_EQ(options.statistics->getTickerCount(LAST_LEVEL_READ_COUNT),
|
||||
@@ -7703,41 +7670,6 @@ TEST_F(DBTest2, GetLatestSeqAndTsForKey) {
|
||||
ASSERT_EQ(0, options.statistics->getTickerCount(GET_HIT_L0));
|
||||
}
|
||||
|
||||
#if defined(ZSTD_ADVANCED)
|
||||
TEST_F(DBTest2, ZSTDChecksum) {
|
||||
// Verify that corruption during decompression is caught.
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.compression = kZSTD;
|
||||
options.compression_opts.max_compressed_bytes_per_kb = 1024;
|
||||
options.compression_opts.checksum = true;
|
||||
DestroyAndReopen(options);
|
||||
Random rnd(33);
|
||||
ASSERT_OK(Put(Key(0), rnd.RandomString(4 << 10)));
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlockBasedTableBuilder::WriteBlock:TamperWithCompressedData",
|
||||
[&](void* arg) {
|
||||
std::string* output = static_cast<std::string*>(arg);
|
||||
// https://github.com/facebook/zstd/blob/dev/doc/zstd_compression_format.md#zstandard-frames
|
||||
// Checksum is the last 4 bytes, corrupting that part in unit test is
|
||||
// more controllable.
|
||||
output->data()[output->size() - 1]++;
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
ASSERT_OK(Flush());
|
||||
PinnableSlice val;
|
||||
Status s = Get(Key(0), &val);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
|
||||
// Corruption caught during flush.
|
||||
options.paranoid_file_checks = true;
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put(Key(0), rnd.RandomString(4 << 10)));
|
||||
s = Flush();
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+6
-6
@@ -259,7 +259,7 @@ bool DBTestBase::ChangeFilterOptions() {
|
||||
|
||||
auto options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
EXPECT_OK(TryReopen(options));
|
||||
TryReopen(options);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -270,34 +270,34 @@ bool DBTestBase::ChangeOptionsForFileIngestionTest() {
|
||||
Destroy(last_options_);
|
||||
auto options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
EXPECT_OK(TryReopen(options));
|
||||
TryReopen(options);
|
||||
return true;
|
||||
} else if (option_config_ == kUniversalCompaction) {
|
||||
option_config_ = kUniversalCompactionMultiLevel;
|
||||
Destroy(last_options_);
|
||||
auto options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
EXPECT_OK(TryReopen(options));
|
||||
TryReopen(options);
|
||||
return true;
|
||||
} else if (option_config_ == kUniversalCompactionMultiLevel) {
|
||||
option_config_ = kLevelSubcompactions;
|
||||
Destroy(last_options_);
|
||||
auto options = CurrentOptions();
|
||||
assert(options.max_subcompactions > 1);
|
||||
EXPECT_OK(TryReopen(options));
|
||||
TryReopen(options);
|
||||
return true;
|
||||
} else if (option_config_ == kLevelSubcompactions) {
|
||||
option_config_ = kUniversalSubcompactions;
|
||||
Destroy(last_options_);
|
||||
auto options = CurrentOptions();
|
||||
assert(options.max_subcompactions > 1);
|
||||
EXPECT_OK(TryReopen(options));
|
||||
TryReopen(options);
|
||||
return true;
|
||||
} else if (option_config_ == kUniversalSubcompactions) {
|
||||
option_config_ = kDirectIO;
|
||||
Destroy(last_options_);
|
||||
auto options = CurrentOptions();
|
||||
EXPECT_OK(TryReopen(options));
|
||||
TryReopen(options);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
|
||||
@@ -2145,19 +2145,7 @@ TEST_F(DBTestUniversalCompaction2, PeriodicCompactionDefault) {
|
||||
options.ttl = 60 * 24 * 60 * 60;
|
||||
options.compaction_filter = nullptr;
|
||||
Reopen(options);
|
||||
ASSERT_EQ(30 * 24 * 60 * 60,
|
||||
dbfull()->GetOptions().periodic_compaction_seconds);
|
||||
|
||||
options.periodic_compaction_seconds = 45 * 24 * 60 * 60;
|
||||
options.ttl = 50 * 24 * 60 * 60;
|
||||
Reopen(options);
|
||||
ASSERT_EQ(45 * 24 * 60 * 60,
|
||||
dbfull()->GetOptions().periodic_compaction_seconds);
|
||||
|
||||
options.periodic_compaction_seconds = 0;
|
||||
options.ttl = 50 * 24 * 60 * 60;
|
||||
Reopen(options);
|
||||
ASSERT_EQ(50 * 24 * 60 * 60,
|
||||
ASSERT_EQ(60 * 24 * 60 * 60,
|
||||
dbfull()->GetOptions().periodic_compaction_seconds);
|
||||
}
|
||||
|
||||
|
||||
+42
-175
@@ -311,33 +311,21 @@ TEST_F(DBWALTest, Recover) {
|
||||
} while (ChangeWalOptions());
|
||||
}
|
||||
|
||||
class DBWALTestWithTimestamp
|
||||
: public DBBasicTestWithTimestampBase,
|
||||
public testing::WithParamInterface<test::UserDefinedTimestampTestMode> {
|
||||
class DBWALTestWithTimestamp : public DBBasicTestWithTimestampBase {
|
||||
public:
|
||||
DBWALTestWithTimestamp()
|
||||
: DBBasicTestWithTimestampBase("db_wal_test_with_timestamp") {}
|
||||
|
||||
Status CreateAndReopenWithTs(const std::vector<std::string>& cfs,
|
||||
const Options& ts_options, bool persist_udt,
|
||||
bool avoid_flush_during_recovery = false) {
|
||||
Options default_options = CurrentOptions();
|
||||
default_options.allow_concurrent_memtable_write =
|
||||
persist_udt ? true : false;
|
||||
DestroyAndReopen(default_options);
|
||||
CreateColumnFamilies(cfs, ts_options);
|
||||
return ReopenColumnFamiliesWithTs(cfs, ts_options, persist_udt,
|
||||
avoid_flush_during_recovery);
|
||||
Status CreateAndReopenWithCFWithTs(const std::vector<std::string>& cfs,
|
||||
const Options& options) {
|
||||
CreateColumnFamilies(cfs, options);
|
||||
return ReopenColumnFamiliesWithTs(cfs, options);
|
||||
}
|
||||
|
||||
Status ReopenColumnFamiliesWithTs(const std::vector<std::string>& cfs,
|
||||
Options ts_options, bool persist_udt,
|
||||
bool avoid_flush_during_recovery = false) {
|
||||
Options ts_options) {
|
||||
Options default_options = CurrentOptions();
|
||||
default_options.create_if_missing = false;
|
||||
default_options.allow_concurrent_memtable_write =
|
||||
persist_udt ? true : false;
|
||||
default_options.avoid_flush_during_recovery = avoid_flush_during_recovery;
|
||||
ts_options.create_if_missing = false;
|
||||
|
||||
std::vector<Options> cf_options(cfs.size(), ts_options);
|
||||
@@ -355,183 +343,62 @@ class DBWALTestWithTimestamp
|
||||
}
|
||||
|
||||
void CheckGet(const ReadOptions& read_opts, uint32_t cf, const Slice& key,
|
||||
const std::string& expected_value,
|
||||
const std::string& expected_ts) {
|
||||
const std::string& expected_value) {
|
||||
std::string actual_value;
|
||||
std::string actual_ts;
|
||||
ASSERT_OK(
|
||||
db_->Get(read_opts, handles_[cf], key, &actual_value, &actual_ts));
|
||||
ASSERT_OK(db_->Get(read_opts, handles_[cf], key, &actual_value));
|
||||
ASSERT_EQ(expected_value, actual_value);
|
||||
ASSERT_EQ(expected_ts, actual_ts);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_P(DBWALTestWithTimestamp, RecoverAndNoFlush) {
|
||||
TEST_F(DBWALTestWithTimestamp, Recover) {
|
||||
// Set up the option that enables user defined timestmp size.
|
||||
std::string ts1;
|
||||
PutFixed64(&ts1, 1);
|
||||
std::string ts = Timestamp(1, 0);
|
||||
const size_t kTimestampSize = ts.size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
Options ts_options;
|
||||
ts_options.create_if_missing = true;
|
||||
ts_options.comparator = test::BytewiseComparatorWithU64TsWrapper();
|
||||
// Test that user-defined timestamps are recovered from WAL regardless of
|
||||
// the value of this flag because UDTs are saved in WAL nonetheless.
|
||||
// We however need to explicitly disable flush during recovery by setting
|
||||
// `avoid_flush_during_recovery=true` so that we can avoid timestamps getting
|
||||
// stripped when the `persist_user_defined_timestamps` flag is false, so that
|
||||
// all written timestamps are available for testing user-defined time travel
|
||||
// read.
|
||||
bool persist_udt = test::ShouldPersistUDT(GetParam());
|
||||
ts_options.persist_user_defined_timestamps = persist_udt;
|
||||
bool avoid_flush_during_recovery = true;
|
||||
ts_options.comparator = &test_cmp;
|
||||
|
||||
ReadOptions read_opts;
|
||||
Slice ts_slice = ts;
|
||||
read_opts.timestamp = &ts_slice;
|
||||
do {
|
||||
Slice ts_slice = ts1;
|
||||
read_opts.timestamp = &ts_slice;
|
||||
ASSERT_OK(CreateAndReopenWithTs({"pikachu"}, ts_options, persist_udt,
|
||||
avoid_flush_during_recovery));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"), 0U);
|
||||
ASSERT_OK(Put(1, "foo", ts1, "v1"));
|
||||
ASSERT_OK(Put(1, "baz", ts1, "v5"));
|
||||
ASSERT_OK(CreateAndReopenWithCFWithTs({"pikachu"}, ts_options));
|
||||
ASSERT_OK(Put(1, "foo", ts, "v1"));
|
||||
ASSERT_OK(Put(1, "baz", ts, "v5"));
|
||||
|
||||
ASSERT_OK(ReopenColumnFamiliesWithTs({"pikachu"}, ts_options, persist_udt,
|
||||
avoid_flush_during_recovery));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"), 0U);
|
||||
// Do a timestamped read with ts1 after second reopen.
|
||||
CheckGet(read_opts, 1, "foo", "v1", ts1);
|
||||
CheckGet(read_opts, 1, "baz", "v5", ts1);
|
||||
ASSERT_OK(ReopenColumnFamiliesWithTs({"pikachu"}, ts_options));
|
||||
CheckGet(read_opts, 1, "foo", "v1");
|
||||
CheckGet(read_opts, 1, "baz", "v5");
|
||||
ASSERT_OK(Put(1, "bar", ts, "v2"));
|
||||
ASSERT_OK(Put(1, "foo", ts, "v3"));
|
||||
|
||||
// Write more value versions for key "foo" and "bar" before and after second
|
||||
// reopen.
|
||||
std::string ts2;
|
||||
PutFixed64(&ts2, 2);
|
||||
ASSERT_OK(Put(1, "bar", ts2, "v2"));
|
||||
ASSERT_OK(Put(1, "foo", ts2, "v3"));
|
||||
|
||||
ASSERT_OK(ReopenColumnFamiliesWithTs({"pikachu"}, ts_options, persist_udt,
|
||||
avoid_flush_during_recovery));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"), 0U);
|
||||
std::string ts3;
|
||||
PutFixed64(&ts3, 3);
|
||||
ASSERT_OK(Put(1, "foo", ts3, "v4"));
|
||||
|
||||
// All the key value pairs available for read:
|
||||
// "foo" -> [(ts1, "v1"), (ts2, "v3"), (ts3, "v4")]
|
||||
// "bar" -> [(ts2, "v2")]
|
||||
// "baz" -> [(ts1, "v5")]
|
||||
// Do a timestamped read with ts1 after third reopen.
|
||||
// read_opts.timestamp is set to ts1 for below reads
|
||||
CheckGet(read_opts, 1, "foo", "v1", ts1);
|
||||
std::string value;
|
||||
ASSERT_TRUE(db_->Get(read_opts, handles_[1], "bar", &value).IsNotFound());
|
||||
CheckGet(read_opts, 1, "baz", "v5", ts1);
|
||||
|
||||
// Do a timestamped read with ts2 after third reopen.
|
||||
ts_slice = ts2;
|
||||
// read_opts.timestamp is set to ts2 for below reads.
|
||||
CheckGet(read_opts, 1, "foo", "v3", ts2);
|
||||
CheckGet(read_opts, 1, "bar", "v2", ts2);
|
||||
CheckGet(read_opts, 1, "baz", "v5", ts1);
|
||||
|
||||
// Do a timestamped read with ts3 after third reopen.
|
||||
ts_slice = ts3;
|
||||
// read_opts.timestamp is set to ts3 for below reads.
|
||||
CheckGet(read_opts, 1, "foo", "v4", ts3);
|
||||
CheckGet(read_opts, 1, "bar", "v2", ts2);
|
||||
CheckGet(read_opts, 1, "baz", "v5", ts1);
|
||||
ASSERT_OK(ReopenColumnFamiliesWithTs({"pikachu"}, ts_options));
|
||||
CheckGet(read_opts, 1, "foo", "v3");
|
||||
ASSERT_OK(Put(1, "foo", ts, "v4"));
|
||||
CheckGet(read_opts, 1, "foo", "v4");
|
||||
CheckGet(read_opts, 1, "bar", "v2");
|
||||
CheckGet(read_opts, 1, "baz", "v5");
|
||||
} while (ChangeWalOptions());
|
||||
}
|
||||
|
||||
TEST_P(DBWALTestWithTimestamp, RecoverAndFlush) {
|
||||
// Set up the option that enables user defined timestamp size.
|
||||
std::string min_ts;
|
||||
std::string write_ts;
|
||||
PutFixed64(&min_ts, 0);
|
||||
PutFixed64(&write_ts, 1);
|
||||
TEST_F(DBWALTestWithTimestamp, RecoverInconsistentTimestamp) {
|
||||
// Set up the option that enables user defined timestmp size.
|
||||
std::string ts = Timestamp(1, 0);
|
||||
const size_t kTimestampSize = ts.size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
Options ts_options;
|
||||
ts_options.create_if_missing = true;
|
||||
ts_options.comparator = test::BytewiseComparatorWithU64TsWrapper();
|
||||
bool persist_udt = test::ShouldPersistUDT(GetParam());
|
||||
ts_options.persist_user_defined_timestamps = persist_udt;
|
||||
ts_options.comparator = &test_cmp;
|
||||
|
||||
std::string smallest_ukey_without_ts = "baz";
|
||||
std::string largest_ukey_without_ts = "foo";
|
||||
ASSERT_OK(CreateAndReopenWithCFWithTs({"pikachu"}, ts_options));
|
||||
ASSERT_OK(Put(1, "foo", ts, "v1"));
|
||||
ASSERT_OK(Put(1, "baz", ts, "v5"));
|
||||
|
||||
ASSERT_OK(CreateAndReopenWithTs({"pikachu"}, ts_options, persist_udt));
|
||||
// No flush, no sst files, because of no data.
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"), 0U);
|
||||
ASSERT_OK(Put(1, largest_ukey_without_ts, write_ts, "v1"));
|
||||
ASSERT_OK(Put(1, smallest_ukey_without_ts, write_ts, "v5"));
|
||||
|
||||
ASSERT_OK(ReopenColumnFamiliesWithTs({"pikachu"}, ts_options, persist_udt));
|
||||
// Memtable recovered from WAL flushed because `avoid_flush_during_recovery`
|
||||
// defaults to false, created one L0 file.
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"), 1U);
|
||||
|
||||
std::vector<std::vector<FileMetaData>> level_to_files;
|
||||
dbfull()->TEST_GetFilesMetaData(handles_[1], &level_to_files);
|
||||
ASSERT_GT(level_to_files.size(), 1);
|
||||
// L0 only has one SST file.
|
||||
ASSERT_EQ(level_to_files[0].size(), 1);
|
||||
auto meta = level_to_files[0][0];
|
||||
if (persist_udt) {
|
||||
ASSERT_EQ(smallest_ukey_without_ts + write_ts, meta.smallest.user_key());
|
||||
ASSERT_EQ(largest_ukey_without_ts + write_ts, meta.largest.user_key());
|
||||
} else {
|
||||
ASSERT_EQ(smallest_ukey_without_ts + min_ts, meta.smallest.user_key());
|
||||
ASSERT_EQ(largest_ukey_without_ts + min_ts, meta.largest.user_key());
|
||||
}
|
||||
}
|
||||
|
||||
// Param 0: test mode for the user-defined timestamp feature
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
P, DBWALTestWithTimestamp,
|
||||
::testing::Values(
|
||||
test::UserDefinedTimestampTestMode::kStripUserDefinedTimestamp,
|
||||
test::UserDefinedTimestampTestMode::kNormal));
|
||||
|
||||
TEST_F(DBWALTestWithTimestamp, EnableDisableUDT) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.comparator = BytewiseComparator();
|
||||
bool avoid_flush_during_recovery = true;
|
||||
ASSERT_OK(CreateAndReopenWithTs({"pikachu"}, options, true /* persist_udt */,
|
||||
avoid_flush_during_recovery));
|
||||
|
||||
ASSERT_OK(db_->Put(WriteOptions(), handles_[1], "foo", "v1"));
|
||||
ASSERT_OK(db_->Put(WriteOptions(), handles_[1], "baz", "v5"));
|
||||
|
||||
options.comparator = test::BytewiseComparatorWithU64TsWrapper();
|
||||
options.persist_user_defined_timestamps = false;
|
||||
// Test handle timestamp size inconsistency in WAL when enabling user-defined
|
||||
// timestamps.
|
||||
ASSERT_OK(ReopenColumnFamiliesWithTs({"pikachu"}, options,
|
||||
false /* persist_udt */,
|
||||
avoid_flush_during_recovery));
|
||||
|
||||
std::string ts;
|
||||
PutFixed64(&ts, 0);
|
||||
Slice ts_slice = ts;
|
||||
ReadOptions read_opts;
|
||||
read_opts.timestamp = &ts_slice;
|
||||
// Pre-existing entries are treated as if they have the min timestamp.
|
||||
CheckGet(read_opts, 1, "foo", "v1", ts);
|
||||
CheckGet(read_opts, 1, "baz", "v5", ts);
|
||||
ts.clear();
|
||||
PutFixed64(&ts, 1);
|
||||
ASSERT_OK(db_->Put(WriteOptions(), handles_[1], "foo", ts, "v2"));
|
||||
ASSERT_OK(db_->Put(WriteOptions(), handles_[1], "baz", ts, "v6"));
|
||||
CheckGet(read_opts, 1, "foo", "v2", ts);
|
||||
CheckGet(read_opts, 1, "baz", "v6", ts);
|
||||
|
||||
options.comparator = BytewiseComparator();
|
||||
// Open the column family again with the UDT feature disabled. Test handle
|
||||
// timestamp size inconsistency in WAL when disabling user-defined timestamps
|
||||
ASSERT_OK(ReopenColumnFamiliesWithTs({"pikachu"}, options,
|
||||
true /* persist_udt */,
|
||||
avoid_flush_during_recovery));
|
||||
ASSERT_EQ("v2", Get(1, "foo"));
|
||||
ASSERT_EQ("v6", Get(1, "baz"));
|
||||
TestComparator diff_test_cmp(kTimestampSize + 1);
|
||||
ts_options.comparator = &diff_test_cmp;
|
||||
ASSERT_TRUE(
|
||||
ReopenColumnFamiliesWithTs({"pikachu"}, ts_options).IsInvalidArgument());
|
||||
}
|
||||
|
||||
TEST_F(DBWALTest, RecoverWithTableHandle) {
|
||||
|
||||
@@ -459,13 +459,6 @@ TEST_F(DBBasicTestWithTimestamp, GetApproximateSizes) {
|
||||
db_->GetApproximateSizes(size_approx_options, default_cf, &r, 1, &size));
|
||||
ASSERT_GT(size, 0);
|
||||
|
||||
uint64_t total_mem_count;
|
||||
uint64_t total_mem_size;
|
||||
db_->GetApproximateMemTableStats(default_cf, r, &total_mem_count,
|
||||
&total_mem_size);
|
||||
ASSERT_GT(total_mem_count, 0);
|
||||
ASSERT_GT(total_mem_size, 0);
|
||||
|
||||
// Should exclude end key
|
||||
start = Key(900);
|
||||
end = Key(1000);
|
||||
@@ -3280,142 +3273,6 @@ TEST_F(UpdateFullHistoryTsLowTest, ConcurrentUpdate) {
|
||||
Close();
|
||||
}
|
||||
|
||||
// Tests the effect of flag `persist_user_defined_timestamps` on the file
|
||||
// boundaries contained in the Manifest, a.k.a FileMetaData.smallest,
|
||||
// FileMetaData.largest.
|
||||
class HandleFileBoundariesTest
|
||||
: public DBBasicTestWithTimestampBase,
|
||||
public testing::WithParamInterface<test::UserDefinedTimestampTestMode> {
|
||||
public:
|
||||
HandleFileBoundariesTest()
|
||||
: DBBasicTestWithTimestampBase("/handle_file_boundaries") {}
|
||||
};
|
||||
|
||||
TEST_P(HandleFileBoundariesTest, ConfigurePersistUdt) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
// Write a timestamp that is not the min timestamp to help test the behavior
|
||||
// of flag `persist_user_defined_timestamps`.
|
||||
std::string write_ts;
|
||||
std::string min_ts;
|
||||
PutFixed64(&write_ts, 1);
|
||||
PutFixed64(&min_ts, 0);
|
||||
std::string smallest_ukey_without_ts = "bar";
|
||||
std::string largest_ukey_without_ts = "foo";
|
||||
options.comparator = test::BytewiseComparatorWithU64TsWrapper();
|
||||
bool persist_udt = test::ShouldPersistUDT(GetParam());
|
||||
options.persist_user_defined_timestamps = persist_udt;
|
||||
if (!persist_udt) {
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
}
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(
|
||||
db_->Put(WriteOptions(), smallest_ukey_without_ts, write_ts, "val1"));
|
||||
ASSERT_OK(
|
||||
db_->Put(WriteOptions(), largest_ukey_without_ts, write_ts, "val2"));
|
||||
|
||||
// Create a L0 SST file and its record is added to the Manfiest.
|
||||
ASSERT_OK(Flush());
|
||||
Close();
|
||||
|
||||
options.create_if_missing = false;
|
||||
// Reopen the DB and process manifest file.
|
||||
Reopen(options);
|
||||
|
||||
std::vector<std::vector<FileMetaData>> level_to_files;
|
||||
dbfull()->TEST_GetFilesMetaData(dbfull()->DefaultColumnFamily(),
|
||||
&level_to_files);
|
||||
ASSERT_GT(level_to_files.size(), 1);
|
||||
// L0 only has one SST file.
|
||||
ASSERT_EQ(level_to_files[0].size(), 1);
|
||||
auto file_meta = level_to_files[0][0];
|
||||
if (persist_udt) {
|
||||
ASSERT_EQ(smallest_ukey_without_ts + write_ts,
|
||||
file_meta.smallest.user_key());
|
||||
ASSERT_EQ(largest_ukey_without_ts + write_ts, file_meta.largest.user_key());
|
||||
} else {
|
||||
// If `persist_user_defined_timestamps` is false, the file boundaries should
|
||||
// have the min timestamp. Behind the scenes, when file boundaries in
|
||||
// FileMetaData is persisted to Manifest, the original user-defined
|
||||
// timestamps in user key are stripped. When manifest is read and processed
|
||||
// during DB open, a min timestamp is padded to the file boundaries. This
|
||||
// test's writes contain non min timestamp to verify this logic end-to-end.
|
||||
ASSERT_EQ(smallest_ukey_without_ts + min_ts, file_meta.smallest.user_key());
|
||||
ASSERT_EQ(largest_ukey_without_ts + min_ts, file_meta.largest.user_key());
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
ConfigurePersistUdt, HandleFileBoundariesTest,
|
||||
::testing::Values(
|
||||
test::UserDefinedTimestampTestMode::kStripUserDefinedTimestamp,
|
||||
test::UserDefinedTimestampTestMode::kNormal));
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, EnableDisableUDT) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
// Create a column family without user-defined timestamps.
|
||||
options.comparator = BytewiseComparator();
|
||||
options.persist_user_defined_timestamps = true;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Create one SST file, its user keys have no user-defined timestamps.
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "foo", "val1"));
|
||||
ASSERT_OK(Flush(0));
|
||||
Close();
|
||||
|
||||
// Reopen the existing column family and enable user-defined timestamps
|
||||
// feature for it.
|
||||
options.comparator = test::BytewiseComparatorWithU64TsWrapper();
|
||||
options.persist_user_defined_timestamps = false;
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
Reopen(options);
|
||||
|
||||
std::string value;
|
||||
ASSERT_TRUE(db_->Get(ReadOptions(), "foo", &value).IsInvalidArgument());
|
||||
std::string read_ts;
|
||||
PutFixed64(&read_ts, 0);
|
||||
ReadOptions ropts;
|
||||
Slice read_ts_slice = read_ts;
|
||||
ropts.timestamp = &read_ts_slice;
|
||||
std::string key_ts;
|
||||
// Entries in pre-existing SST files are treated as if they have minimum
|
||||
// user-defined timestamps.
|
||||
ASSERT_OK(db_->Get(ropts, "foo", &value, &key_ts));
|
||||
ASSERT_EQ("val1", value);
|
||||
ASSERT_EQ(read_ts, key_ts);
|
||||
|
||||
// Do timestamped read / write.
|
||||
std::string write_ts;
|
||||
PutFixed64(&write_ts, 1);
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "foo", write_ts, "val2"));
|
||||
read_ts.clear();
|
||||
PutFixed64(&read_ts, 1);
|
||||
ASSERT_OK(db_->Get(ropts, "foo", &value, &key_ts));
|
||||
ASSERT_EQ("val2", value);
|
||||
ASSERT_EQ(write_ts, key_ts);
|
||||
// The user keys in this SST file don't have user-defined timestamps either,
|
||||
// because `persist_user_defined_timestamps` flag is set to false.
|
||||
ASSERT_OK(Flush(0));
|
||||
Close();
|
||||
|
||||
// Reopen the existing column family while disabling user-defined timestamps.
|
||||
options.comparator = BytewiseComparator();
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_TRUE(db_->Get(ropts, "foo", &value).IsInvalidArgument());
|
||||
ASSERT_OK(db_->Get(ReadOptions(), "foo", &value));
|
||||
ASSERT_EQ("val2", value);
|
||||
|
||||
// Continue to write / read the column family without user-defined timestamps.
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "foo", "val3"));
|
||||
ASSERT_OK(db_->Get(ReadOptions(), "foo", &value));
|
||||
ASSERT_EQ("val3", value);
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp,
|
||||
GCPreserveRangeTombstoneWhenNoOrSmallFullHistoryLow) {
|
||||
Options options = CurrentOptions();
|
||||
|
||||
@@ -42,10 +42,10 @@ TEST_P(DBWriteBufferManagerTest, SharedBufferAcrossCFs1) {
|
||||
|
||||
CreateAndReopenWithCF({"cf1", "cf2", "cf3"}, options);
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(1), wo));
|
||||
ASSERT_OK(Flush(3));
|
||||
Flush(3);
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(1), wo));
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(1), wo));
|
||||
ASSERT_OK(Flush(0));
|
||||
Flush(0);
|
||||
|
||||
// Write to "Default", "cf2" and "cf3".
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(30000), wo));
|
||||
@@ -84,10 +84,10 @@ TEST_P(DBWriteBufferManagerTest, SharedWriteBufferAcrossCFs2) {
|
||||
|
||||
CreateAndReopenWithCF({"cf1", "cf2", "cf3"}, options);
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(1), wo));
|
||||
ASSERT_OK(Flush(3));
|
||||
Flush(3);
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(1), wo));
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(1), wo));
|
||||
ASSERT_OK(Flush(0));
|
||||
Flush(0);
|
||||
|
||||
// Write to "Default", "cf2" and "cf3". No flush will be triggered.
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(30000), wo));
|
||||
@@ -471,10 +471,10 @@ TEST_P(DBWriteBufferManagerTest, MixedSlowDownOptionsSingleDB) {
|
||||
CreateAndReopenWithCF({"cf1", "cf2", "cf3"}, options);
|
||||
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(1), wo));
|
||||
ASSERT_OK(Flush(3));
|
||||
Flush(3);
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(1), wo));
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(1), wo));
|
||||
ASSERT_OK(Flush(0));
|
||||
Flush(0);
|
||||
|
||||
// Write to "Default", "cf2" and "cf3". No flush will be triggered.
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(30000), wo));
|
||||
|
||||
+2
-2
@@ -495,7 +495,7 @@ TEST_P(DBWriteTest, UnflushedPutRaceWithTrackedWalSync) {
|
||||
|
||||
// Simulate full loss of unsynced data. This drops "key2" -> "val2" from the
|
||||
// DB WAL.
|
||||
ASSERT_OK(fault_env->DropUnsyncedFileData());
|
||||
fault_env->DropUnsyncedFileData();
|
||||
|
||||
Reopen(options);
|
||||
|
||||
@@ -536,7 +536,7 @@ TEST_P(DBWriteTest, InactiveWalFullySyncedBeforeUntracked) {
|
||||
|
||||
// Simulate full loss of unsynced data. This should drop nothing since we did
|
||||
// `FlushWAL(true /* sync */)` before `Close()`.
|
||||
ASSERT_OK(fault_env->DropUnsyncedFileData());
|
||||
fault_env->DropUnsyncedFileData();
|
||||
|
||||
Reopen(options);
|
||||
|
||||
|
||||
@@ -88,13 +88,6 @@ void AppendKeyWithMaxTimestamp(std::string* result, const Slice& key,
|
||||
result->append(kTsMax.data(), ts_sz);
|
||||
}
|
||||
|
||||
void AppendUserKeyWithMinTimestamp(std::string* result, const Slice& key,
|
||||
size_t ts_sz) {
|
||||
assert(ts_sz > 0);
|
||||
result->append(key.data(), key.size() - ts_sz);
|
||||
result->append(ts_sz, static_cast<unsigned char>(0));
|
||||
}
|
||||
|
||||
void AppendUserKeyWithMaxTimestamp(std::string* result, const Slice& key,
|
||||
size_t ts_sz) {
|
||||
assert(ts_sz > 0);
|
||||
@@ -127,16 +120,6 @@ void StripTimestampFromInternalKey(std::string* result, const Slice& key,
|
||||
kNumInternalBytes);
|
||||
}
|
||||
|
||||
void ReplaceInternalKeyWithMinTimestamp(std::string* result, const Slice& key,
|
||||
size_t ts_sz) {
|
||||
const size_t key_sz = key.size();
|
||||
assert(key_sz >= ts_sz + kNumInternalBytes);
|
||||
result->reserve(key_sz);
|
||||
result->append(key.data(), key_sz - kNumInternalBytes - ts_sz);
|
||||
result->append(ts_sz, static_cast<unsigned char>(0));
|
||||
result->append(key.data() + key_sz - kNumInternalBytes, kNumInternalBytes);
|
||||
}
|
||||
|
||||
std::string ParsedInternalKey::DebugString(bool log_err_key, bool hex) const {
|
||||
std::string result = "'";
|
||||
if (log_err_key) {
|
||||
|
||||
+23
-87
@@ -168,113 +168,58 @@ inline void UnPackSequenceAndType(uint64_t packed, uint64_t* seq,
|
||||
EntryType GetEntryType(ValueType value_type);
|
||||
|
||||
// Append the serialization of "key" to *result.
|
||||
//
|
||||
// input [internal key]: <user_key | seqno + type>
|
||||
// output before: empty
|
||||
// output: <user_key | seqno + type>
|
||||
void AppendInternalKey(std::string* result, const ParsedInternalKey& key);
|
||||
extern void AppendInternalKey(std::string* result,
|
||||
const ParsedInternalKey& key);
|
||||
|
||||
// Append the serialization of "key" to *result, replacing the original
|
||||
// timestamp with argument ts.
|
||||
//
|
||||
// input [internal key]: <user_provided_key | original_ts | seqno + type>
|
||||
// output before: empty
|
||||
// output after: <user_provided_key | ts | seqno + type>
|
||||
void AppendInternalKeyWithDifferentTimestamp(std::string* result,
|
||||
const ParsedInternalKey& key,
|
||||
const Slice& ts);
|
||||
extern void AppendInternalKeyWithDifferentTimestamp(
|
||||
std::string* result, const ParsedInternalKey& key, const Slice& ts);
|
||||
|
||||
// Serialized internal key consists of user key followed by footer.
|
||||
// This function appends the footer to *result, assuming that *result already
|
||||
// contains the user key at the end.
|
||||
//
|
||||
// output before: <user_key>
|
||||
// output after: <user_key | seqno + type>
|
||||
void AppendInternalKeyFooter(std::string* result, SequenceNumber s,
|
||||
ValueType t);
|
||||
extern void AppendInternalKeyFooter(std::string* result, SequenceNumber s,
|
||||
ValueType t);
|
||||
|
||||
// Append the key and a minimal timestamp to *result
|
||||
//
|
||||
// input [user key without ts]: <user_provided_key>
|
||||
// output before: empty
|
||||
// output after: <user_provided_key | min_ts>
|
||||
void AppendKeyWithMinTimestamp(std::string* result, const Slice& key,
|
||||
size_t ts_sz);
|
||||
extern void AppendKeyWithMinTimestamp(std::string* result, const Slice& key,
|
||||
size_t ts_sz);
|
||||
|
||||
// Append the key and a maximal timestamp to *result
|
||||
//
|
||||
// input [user key without ts]: <user_provided_key>
|
||||
// output before: empty
|
||||
// output after: <user_provided_key | max_ts>
|
||||
void AppendKeyWithMaxTimestamp(std::string* result, const Slice& key,
|
||||
size_t ts_sz);
|
||||
|
||||
// `key` is a user key with timestamp. Append the user key without timestamp
|
||||
// and the minimum timestamp to *result.
|
||||
//
|
||||
// input [user key]: <user_provided_key | original_ts>
|
||||
// output before: empty
|
||||
// output after: <user_provided_key | min_ts>
|
||||
void AppendUserKeyWithMinTimestamp(std::string* result, const Slice& key,
|
||||
size_t ts_sz);
|
||||
extern void AppendKeyWithMaxTimestamp(std::string* result, const Slice& key,
|
||||
size_t ts_sz);
|
||||
|
||||
// `key` is a user key with timestamp. Append the user key without timestamp
|
||||
// and the maximal timestamp to *result.
|
||||
//
|
||||
// input [user key]: <user_provided_key | original_ts>
|
||||
// output before: empty
|
||||
// output after: <user_provided_key | max_ts>
|
||||
void AppendUserKeyWithMaxTimestamp(std::string* result, const Slice& key,
|
||||
size_t ts_sz);
|
||||
extern void AppendUserKeyWithMaxTimestamp(std::string* result, const Slice& key,
|
||||
size_t ts_sz);
|
||||
|
||||
// `key` is an internal key containing a user key without timestamp. Create a
|
||||
// new key in *result by padding a min timestamp of size `ts_sz` to the user key
|
||||
// and copying the remaining internal key bytes.
|
||||
//
|
||||
// input [internal key]: <user_provided_key | seqno + type>
|
||||
// output before: empty
|
||||
// output after: <user_provided_key | min_ts | seqno + type>
|
||||
void PadInternalKeyWithMinTimestamp(std::string* result, const Slice& key,
|
||||
size_t ts_sz);
|
||||
extern void PadInternalKeyWithMinTimestamp(std::string* result,
|
||||
const Slice& key, size_t ts_sz);
|
||||
|
||||
// `key` is an internal key containing a user key with timestamp of size
|
||||
// `ts_sz`. Create a new internal key in *result by stripping the timestamp from
|
||||
// the user key and copying the remaining internal key bytes.
|
||||
//
|
||||
// input [internal key]: <user_provided_key | original_ts | seqno + type>
|
||||
// output before: empty
|
||||
// output after: <user_provided_key | seqno + type>
|
||||
void StripTimestampFromInternalKey(std::string* result, const Slice& key,
|
||||
size_t ts_sz);
|
||||
|
||||
// `key` is an internal key containing a user key with timestamp of size
|
||||
// `ts_sz`. Create a new internal key in *result while replace the original
|
||||
// timestamp with min timestamp.
|
||||
//
|
||||
// input [internal key]: <user_provided_key | original_ts | seqno + type>
|
||||
// output before: empty
|
||||
// output after: <user_provided_key | min_ts | seqno + type>
|
||||
void ReplaceInternalKeyWithMinTimestamp(std::string* result, const Slice& key,
|
||||
size_t ts_sz);
|
||||
extern void StripTimestampFromInternalKey(std::string* result, const Slice& key,
|
||||
size_t ts_sz);
|
||||
|
||||
// Attempt to parse an internal key from "internal_key". On success,
|
||||
// stores the parsed data in "*result", and returns true.
|
||||
//
|
||||
// On error, returns false, leaves "*result" in an undefined state.
|
||||
Status ParseInternalKey(const Slice& internal_key, ParsedInternalKey* result,
|
||||
bool log_err_key);
|
||||
extern Status ParseInternalKey(const Slice& internal_key,
|
||||
ParsedInternalKey* result, bool log_err_key);
|
||||
|
||||
// Returns the user key portion of an internal key.
|
||||
//
|
||||
// input [internal key]: <user_key | seqno + type>
|
||||
// output: <user_key>
|
||||
inline Slice ExtractUserKey(const Slice& internal_key) {
|
||||
assert(internal_key.size() >= kNumInternalBytes);
|
||||
return Slice(internal_key.data(), internal_key.size() - kNumInternalBytes);
|
||||
}
|
||||
|
||||
// input [internal key]: <user_provided_key | ts | seqno + type>
|
||||
// output : <user_provided_key>
|
||||
inline Slice ExtractUserKeyAndStripTimestamp(const Slice& internal_key,
|
||||
size_t ts_sz) {
|
||||
Slice ret = internal_key;
|
||||
@@ -282,23 +227,17 @@ inline Slice ExtractUserKeyAndStripTimestamp(const Slice& internal_key,
|
||||
return ret;
|
||||
}
|
||||
|
||||
// input [user key]: <user_provided_key | ts>
|
||||
// output: <user_provided_key>
|
||||
inline Slice StripTimestampFromUserKey(const Slice& user_key, size_t ts_sz) {
|
||||
Slice ret = user_key;
|
||||
ret.remove_suffix(ts_sz);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// input [user key]: <user_provided_key | ts>
|
||||
// output: <ts>
|
||||
inline Slice ExtractTimestampFromUserKey(const Slice& user_key, size_t ts_sz) {
|
||||
assert(user_key.size() >= ts_sz);
|
||||
return Slice(user_key.data() + user_key.size() - ts_sz, ts_sz);
|
||||
}
|
||||
|
||||
// input [internal key]: <user_provided_key | ts | seqno + type>
|
||||
// output: <ts>
|
||||
inline Slice ExtractTimestampFromKey(const Slice& internal_key, size_t ts_sz) {
|
||||
const size_t key_size = internal_key.size();
|
||||
assert(key_size >= kNumInternalBytes + ts_sz);
|
||||
@@ -306,16 +245,12 @@ inline Slice ExtractTimestampFromKey(const Slice& internal_key, size_t ts_sz) {
|
||||
ts_sz);
|
||||
}
|
||||
|
||||
// input [internal key]: <user_provided_key | ts | seqno + type>
|
||||
// output: <seqno + type>
|
||||
inline uint64_t ExtractInternalKeyFooter(const Slice& internal_key) {
|
||||
assert(internal_key.size() >= kNumInternalBytes);
|
||||
const size_t n = internal_key.size();
|
||||
return DecodeFixed64(internal_key.data() + n - kNumInternalBytes);
|
||||
}
|
||||
|
||||
// input [internal key]: <user_provided_key | ts | seqno + type>
|
||||
// output: <type>
|
||||
inline ValueType ExtractValueType(const Slice& internal_key) {
|
||||
uint64_t num = ExtractInternalKeyFooter(internal_key);
|
||||
unsigned char c = num & 0xff;
|
||||
@@ -848,7 +783,8 @@ class InternalKeySliceTransform : public SliceTransform {
|
||||
// Read the key of a record from a write batch.
|
||||
// if this record represent the default column family then cf_record
|
||||
// must be passed as false, otherwise it must be passed as true.
|
||||
bool ReadKeyFromWriteBatchEntry(Slice* input, Slice* key, bool cf_record);
|
||||
extern bool ReadKeyFromWriteBatchEntry(Slice* input, Slice* key,
|
||||
bool cf_record);
|
||||
|
||||
// Read record from a write batch piece from input.
|
||||
// tag, column_family, key, value and blob are return values. Callers own the
|
||||
@@ -857,9 +793,9 @@ bool ReadKeyFromWriteBatchEntry(Slice* input, Slice* key, bool cf_record);
|
||||
// input will be advanced to after the record.
|
||||
// If user-defined timestamp is enabled for a column family, then the `key`
|
||||
// resulting from this call will include timestamp.
|
||||
Status ReadRecordFromWriteBatch(Slice* input, char* tag,
|
||||
uint32_t* column_family, Slice* key,
|
||||
Slice* value, Slice* blob, Slice* xid);
|
||||
extern Status ReadRecordFromWriteBatch(Slice* input, char* tag,
|
||||
uint32_t* column_family, Slice* key,
|
||||
Slice* value, Slice* blob, Slice* xid);
|
||||
|
||||
// When user call DeleteRange() to delete a range of keys,
|
||||
// we will store a serialized RangeTombstone in MemTable and SST.
|
||||
|
||||
@@ -312,27 +312,6 @@ TEST_F(FormatTest, StripTimestampFromInternalKey) {
|
||||
ASSERT_EQ(kTypeValue, key_without_timestamp.type);
|
||||
}
|
||||
|
||||
TEST_F(FormatTest, ReplaceInternalKeyWithMinTimestamp) {
|
||||
std::string orig_user_key = "foo";
|
||||
size_t ts_sz = 8;
|
||||
orig_user_key.append(ts_sz, static_cast<unsigned char>(1));
|
||||
std::string orig_internal_key = IKey(orig_user_key, 100, kTypeValue);
|
||||
|
||||
std::string key_buf;
|
||||
ReplaceInternalKeyWithMinTimestamp(&key_buf, orig_internal_key, ts_sz);
|
||||
ParsedInternalKey new_key;
|
||||
Slice in(key_buf);
|
||||
ASSERT_OK(ParseInternalKey(in, &new_key, true /*log_err_key*/));
|
||||
|
||||
std::string min_timestamp(ts_sz, static_cast<unsigned char>(0));
|
||||
size_t ukey_diff_offset = new_key.user_key.difference_offset(orig_user_key);
|
||||
ASSERT_EQ(min_timestamp,
|
||||
Slice(new_key.user_key.data() + ukey_diff_offset, ts_sz));
|
||||
ASSERT_EQ(orig_user_key.size(), new_key.user_key.size());
|
||||
ASSERT_EQ(100, new_key.sequence);
|
||||
ASSERT_EQ(kTypeValue, new_key.type);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -653,7 +653,6 @@ const Status& ErrorHandler::StartRecoverFromRetryableBGIOError(
|
||||
}
|
||||
|
||||
recovery_in_prog_ = true;
|
||||
TEST_SYNC_POINT("StartRecoverFromRetryableBGIOError::in_progress");
|
||||
recovery_thread_.reset(
|
||||
new port::Thread(&ErrorHandler::RecoverFromRetryableBGIOError, this));
|
||||
|
||||
@@ -668,7 +667,6 @@ const Status& ErrorHandler::StartRecoverFromRetryableBGIOError(
|
||||
// mutex is released.
|
||||
void ErrorHandler::RecoverFromRetryableBGIOError() {
|
||||
TEST_SYNC_POINT("RecoverFromRetryableBGIOError:BeforeStart");
|
||||
TEST_SYNC_POINT("RecoverFromRetryableBGIOError:BeforeStart2");
|
||||
InstrumentedMutexLock l(db_mutex_);
|
||||
if (end_recovery_) {
|
||||
EventHelpers::NotifyOnErrorRecoveryEnd(db_options_.listeners, bg_error_,
|
||||
@@ -677,7 +675,6 @@ void ErrorHandler::RecoverFromRetryableBGIOError() {
|
||||
return;
|
||||
}
|
||||
DBRecoverContext context = recover_context_;
|
||||
context.flush_after_recovery = true;
|
||||
int resume_count = db_options_.max_bgerror_resume_count;
|
||||
uint64_t wait_interval = db_options_.bgerror_resume_retry_interval;
|
||||
uint64_t retry_count = 0;
|
||||
|
||||
+3
-6
@@ -19,13 +19,10 @@ class DBImpl;
|
||||
// FlushReason, which tells the flush job why this flush is called.
|
||||
struct DBRecoverContext {
|
||||
FlushReason flush_reason;
|
||||
bool flush_after_recovery;
|
||||
|
||||
DBRecoverContext()
|
||||
: flush_reason(FlushReason::kErrorRecovery),
|
||||
flush_after_recovery(false) {}
|
||||
DBRecoverContext(FlushReason reason)
|
||||
: flush_reason(reason), flush_after_recovery(false) {}
|
||||
DBRecoverContext() : flush_reason(FlushReason::kErrorRecovery) {}
|
||||
|
||||
DBRecoverContext(FlushReason reason) : flush_reason(reason) {}
|
||||
};
|
||||
|
||||
class ErrorHandler {
|
||||
|
||||
@@ -2565,8 +2565,8 @@ TEST_F(DBErrorHandlingFSTest, AtomicFlushReadError) {
|
||||
s = dbfull()->TEST_GetBGError();
|
||||
ASSERT_OK(s);
|
||||
|
||||
ASSERT_OK(TryReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"},
|
||||
GetDefaultOptions()));
|
||||
TryReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"},
|
||||
GetDefaultOptions());
|
||||
ASSERT_EQ("val", Get(Key(0)));
|
||||
}
|
||||
|
||||
@@ -2606,8 +2606,8 @@ TEST_F(DBErrorHandlingFSTest, AtomicFlushNoSpaceError) {
|
||||
s = dbfull()->TEST_GetBGError();
|
||||
ASSERT_OK(s);
|
||||
|
||||
ASSERT_OK(TryReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"},
|
||||
GetDefaultOptions()));
|
||||
TryReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"},
|
||||
GetDefaultOptions());
|
||||
ASSERT_EQ("val", Get(Key(0)));
|
||||
}
|
||||
|
||||
|
||||
@@ -122,8 +122,6 @@ void EventHelpers::LogAndNotifyTableFileCreationFinished(
|
||||
<< "column_family_name" << table_properties.column_family_name
|
||||
<< "column_family_id" << table_properties.column_family_id
|
||||
<< "comparator" << table_properties.comparator_name
|
||||
<< "user_defined_timestamps_persisted"
|
||||
<< table_properties.user_defined_timestamps_persisted
|
||||
<< "merge_operator" << table_properties.merge_operator_name
|
||||
<< "prefix_extractor_name"
|
||||
<< table_properties.prefix_extractor_name << "property_collectors"
|
||||
@@ -240,8 +238,6 @@ void EventHelpers::NotifyOnErrorRecoveryEnd(
|
||||
info.new_bg_error.PermitUncheckedError();
|
||||
}
|
||||
db_mutex->Lock();
|
||||
} else {
|
||||
old_bg_error.PermitUncheckedError();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1346,7 +1346,7 @@ TEST_P(ExternalSSTFileBasicTest, IngestionWithRangeDeletions) {
|
||||
// range del [0, 50) in L6 file, [50, 100) in L0 file, [100, 150) in memtable
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (i != 0) {
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
db_->Flush(FlushOptions());
|
||||
if (i == 1) {
|
||||
MoveFilesToLevel(kNumLevels - 1);
|
||||
}
|
||||
@@ -1747,11 +1747,11 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileAfterDBPut) {
|
||||
Options options = CurrentOptions();
|
||||
|
||||
ASSERT_OK(Put("k", "a"));
|
||||
ASSERT_OK(Flush());
|
||||
Flush();
|
||||
ASSERT_OK(Put("k", "a"));
|
||||
ASSERT_OK(Flush());
|
||||
Flush();
|
||||
ASSERT_OK(Put("k", "a"));
|
||||
ASSERT_OK(Flush());
|
||||
Flush();
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
|
||||
// Current file size should be 0 after sst_file_writer init and before open a
|
||||
|
||||
@@ -217,8 +217,6 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
std::string requested_checksum_func_name;
|
||||
// TODO: rate limit file reads for checksum calculation during file
|
||||
// ingestion.
|
||||
// TODO: plumb Env::IOActivity
|
||||
ReadOptions ro;
|
||||
IOStatus io_s = GenerateOneFileChecksum(
|
||||
fs_.get(), files_to_ingest_[i].internal_file_path,
|
||||
db_options_.file_checksum_gen_factory.get(),
|
||||
@@ -226,8 +224,8 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
&generated_checksum_func_name,
|
||||
ingestion_options_.verify_checksums_readahead_size,
|
||||
db_options_.allow_mmap_reads, io_tracer_,
|
||||
db_options_.rate_limiter.get(), ro, db_options_.stats,
|
||||
db_options_.clock);
|
||||
db_options_.rate_limiter.get(),
|
||||
Env::IO_TOTAL /* rate_limiter_priority */);
|
||||
if (!io_s.ok()) {
|
||||
status = io_s;
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
@@ -350,7 +348,7 @@ Status ExternalSstFileIngestionJob::NeedsFlush(bool* flush_needed,
|
||||
std::string end_str;
|
||||
AppendUserKeyWithMaxTimestamp(
|
||||
&begin_str, file_to_ingest.smallest_internal_key.user_key(), ts_sz);
|
||||
AppendUserKeyWithMinTimestamp(
|
||||
AppendKeyWithMinTimestamp(
|
||||
&end_str, file_to_ingest.largest_internal_key.user_key(), ts_sz);
|
||||
keys.emplace_back(std::move(begin_str));
|
||||
keys.emplace_back(std::move(end_str));
|
||||
@@ -1060,15 +1058,13 @@ IOStatus ExternalSstFileIngestionJob::GenerateChecksumForIngestedFile(
|
||||
std::string file_checksum_func_name;
|
||||
std::string requested_checksum_func_name;
|
||||
// TODO: rate limit file reads for checksum calculation during file ingestion.
|
||||
// TODO: plumb Env::IOActivity
|
||||
ReadOptions ro;
|
||||
IOStatus io_s = GenerateOneFileChecksum(
|
||||
fs_.get(), file_to_ingest->internal_file_path,
|
||||
db_options_.file_checksum_gen_factory.get(), requested_checksum_func_name,
|
||||
&file_checksum, &file_checksum_func_name,
|
||||
ingestion_options_.verify_checksums_readahead_size,
|
||||
db_options_.allow_mmap_reads, io_tracer_, db_options_.rate_limiter.get(),
|
||||
ro, db_options_.stats, db_options_.clock);
|
||||
Env::IO_TOTAL /* rate_limiter_priority */);
|
||||
if (!io_s.ok()) {
|
||||
return io_s;
|
||||
}
|
||||
|
||||
@@ -538,56 +538,6 @@ TEST_F(ExternalSSTFileTest, Basic) {
|
||||
kRangeDelSkipConfigs));
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileTest, BasicWideColumn) {
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
|
||||
// Current file size should be 0 after sst_file_writer init and before open
|
||||
// a file.
|
||||
ASSERT_EQ(sst_file_writer.FileSize(), 0);
|
||||
|
||||
std::string file = sst_files_dir_ + "wide_column_file.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file));
|
||||
for (int k = 0; k < 10; k++) {
|
||||
std::string val1 = Key(k) + "_attr_1_val";
|
||||
std::string val2 = Key(k) + "_attr_2_val";
|
||||
WideColumns columns{{"attr_1", val1}, {"attr_2", val2}};
|
||||
ASSERT_OK(sst_file_writer.PutEntity(Key(k), columns));
|
||||
}
|
||||
ExternalSstFileInfo file_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file_info));
|
||||
|
||||
// Current file size should be non-zero after success write.
|
||||
ASSERT_GT(sst_file_writer.FileSize(), 0);
|
||||
|
||||
ASSERT_EQ(file_info.file_path, file);
|
||||
ASSERT_EQ(file_info.num_entries, 10);
|
||||
ASSERT_EQ(file_info.smallest_key, Key(0));
|
||||
ASSERT_EQ(file_info.largest_key, Key(9));
|
||||
ASSERT_EQ(file_info.num_range_del_entries, 0);
|
||||
ASSERT_EQ(file_info.smallest_range_del_key, "");
|
||||
ASSERT_EQ(file_info.largest_range_del_key, "");
|
||||
|
||||
DestroyAndReopen(options);
|
||||
// Add file using file path
|
||||
ASSERT_OK(DeprecatedAddFile({file}));
|
||||
ASSERT_EQ(db_->GetLatestSequenceNumber(), 0U);
|
||||
for (int k = 0; k < 10; k++) {
|
||||
PinnableWideColumns result;
|
||||
ASSERT_OK(db_->GetEntity(ReadOptions(), db_->DefaultColumnFamily(),
|
||||
Key(k), &result));
|
||||
std::string val1 = Key(k) + "_attr_1_val";
|
||||
std::string val2 = Key(k) + "_attr_2_val";
|
||||
WideColumns expected_columns{{"attr_1", val1}, {"attr_2", val2}};
|
||||
ASSERT_EQ(result.columns(), expected_columns);
|
||||
}
|
||||
|
||||
} while (ChangeOptions(kSkipPlainTable | kSkipFIFOCompaction |
|
||||
kRangeDelSkipConfigs));
|
||||
}
|
||||
|
||||
class SstFileWriterCollector : public TablePropertiesCollector {
|
||||
public:
|
||||
explicit SstFileWriterCollector(const std::string prefix) : prefix_(prefix) {
|
||||
@@ -2273,7 +2223,7 @@ TEST_P(ExternalSSTFileTest, IngestBehind) {
|
||||
// Trigger compaction if size amplification exceeds 110%.
|
||||
options.compaction_options_universal.max_size_amplification_percent = 110;
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
TryReopen(options);
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
@@ -2289,7 +2239,7 @@ TEST_P(ExternalSSTFileTest, IngestBehind) {
|
||||
|
||||
// Turning off the option allows DB to compact ingested files.
|
||||
options.allow_ingest_behind = false;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
TryReopen(options);
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(), &level_to_files);
|
||||
ASSERT_EQ(1, level_to_files[2].size());
|
||||
|
||||
@@ -443,7 +443,7 @@ TEST_P(FaultInjectionTest, UninstalledCompaction) {
|
||||
options_.level0_stop_writes_trigger = 1 << 10;
|
||||
options_.level0_slowdown_writes_trigger = 1 << 10;
|
||||
options_.max_background_compactions = 1;
|
||||
ASSERT_OK(OpenDB());
|
||||
OpenDB();
|
||||
|
||||
if (!sequential_order_) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({
|
||||
|
||||
+4
-2
@@ -56,15 +56,17 @@ void FileIndexer::GetNextLevelIndex(const size_t level, const size_t file_index,
|
||||
} else if (cmp_smallest == 0) {
|
||||
*left_bound = index.smallest_lb;
|
||||
*right_bound = index.smallest_rb;
|
||||
} else if (cmp_largest < 0) {
|
||||
} else if (cmp_smallest > 0 && cmp_largest < 0) {
|
||||
*left_bound = index.smallest_lb;
|
||||
*right_bound = index.largest_rb;
|
||||
} else if (cmp_largest == 0) {
|
||||
*left_bound = index.largest_lb;
|
||||
*right_bound = index.largest_rb;
|
||||
} else {
|
||||
} else if (cmp_largest > 0) {
|
||||
*left_bound = index.largest_lb;
|
||||
*right_bound = level_rb_[level + 1];
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
assert(*left_bound >= 0);
|
||||
|
||||
+10
-97
@@ -79,8 +79,6 @@ const char* GetFlushReasonString(FlushReason flush_reason) {
|
||||
return "Error Recovery Retry Flush";
|
||||
case FlushReason::kWalFull:
|
||||
return "WAL Full";
|
||||
case FlushReason::kCatchUpAfterErrorRecovery:
|
||||
return "Catch Up After Error Recovery";
|
||||
default:
|
||||
return "Invalid";
|
||||
}
|
||||
@@ -191,10 +189,6 @@ void FlushJob::PickMemTable() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Track effective cutoff user-defined timestamp during flush if
|
||||
// user-defined timestamps can be stripped.
|
||||
GetEffectiveCutoffUDTForPickedMemTables();
|
||||
|
||||
ReportFlushInputSize(mems_);
|
||||
|
||||
// entries mems are (implicitly) sorted in ascending order by their created
|
||||
@@ -217,8 +211,7 @@ void FlushJob::PickMemTable() {
|
||||
}
|
||||
|
||||
Status FlushJob::Run(LogsWithPrepTracker* prep_tracker, FileMetaData* file_meta,
|
||||
bool* switched_to_mempurge, bool* skipped_since_bg_error,
|
||||
ErrorHandler* error_handler) {
|
||||
bool* switched_to_mempurge) {
|
||||
TEST_SYNC_POINT("FlushJob::Start");
|
||||
db_mutex_->AssertHeld();
|
||||
assert(pick_memtable_called);
|
||||
@@ -301,37 +294,18 @@ Status FlushJob::Run(LogsWithPrepTracker* prep_tracker, FileMetaData* file_meta,
|
||||
s = Status::ShutdownInProgress("Database shutdown");
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
s = MaybeIncreaseFullHistoryTsLowToAboveCutoffUDT();
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
cfd_->imm()->RollbackMemtableFlush(
|
||||
mems_, /*rollback_succeeding_memtables=*/!db_options_.atomic_flush);
|
||||
cfd_->imm()->RollbackMemtableFlush(mems_, meta_.fd.GetNumber());
|
||||
} else if (write_manifest_) {
|
||||
assert(!db_options_.atomic_flush);
|
||||
if (!db_options_.atomic_flush &&
|
||||
flush_reason_ != FlushReason::kErrorRecovery &&
|
||||
flush_reason_ != FlushReason::kErrorRecoveryRetryFlush &&
|
||||
error_handler && !error_handler->GetBGError().ok() &&
|
||||
error_handler->IsBGWorkStopped()) {
|
||||
cfd_->imm()->RollbackMemtableFlush(
|
||||
mems_, /*rollback_succeeding_memtables=*/!db_options_.atomic_flush);
|
||||
s = error_handler->GetBGError();
|
||||
if (skipped_since_bg_error) {
|
||||
*skipped_since_bg_error = true;
|
||||
}
|
||||
} else {
|
||||
TEST_SYNC_POINT("FlushJob::InstallResults");
|
||||
// Replace immutable memtable with the generated Table
|
||||
s = cfd_->imm()->TryInstallMemtableFlushResults(
|
||||
cfd_, mutable_cf_options_, mems_, prep_tracker, versions_, db_mutex_,
|
||||
meta_.fd.GetNumber(), &job_context_->memtables_to_free, db_directory_,
|
||||
log_buffer_, &committed_flush_jobs_info_,
|
||||
!(mempurge_s.ok()) /* write_edit : true if no mempurge happened (or if aborted),
|
||||
TEST_SYNC_POINT("FlushJob::InstallResults");
|
||||
// Replace immutable memtable with the generated Table
|
||||
s = cfd_->imm()->TryInstallMemtableFlushResults(
|
||||
cfd_, mutable_cf_options_, mems_, prep_tracker, versions_, db_mutex_,
|
||||
meta_.fd.GetNumber(), &job_context_->memtables_to_free, db_directory_,
|
||||
log_buffer_, &committed_flush_jobs_info_,
|
||||
!(mempurge_s.ok()) /* write_edit : true if no mempurge happened (or if aborted),
|
||||
but 'false' if mempurge successful: no new min log number
|
||||
or new level 0 file path to write to manifest. */);
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok() && file_meta != nullptr) {
|
||||
@@ -508,7 +482,6 @@ Status FlushJob::MemPurge() {
|
||||
nullptr, ioptions->allow_data_in_errors,
|
||||
ioptions->enforce_single_del_contracts,
|
||||
/*manual_compaction_canceled=*/kManualCompactionCanceledFalse,
|
||||
false /* must_count_input_entries */,
|
||||
/*compaction=*/nullptr, compaction_filter.get(),
|
||||
/*shutting_down=*/nullptr, ioptions->info_log, full_history_ts_low);
|
||||
|
||||
@@ -879,7 +852,6 @@ Status FlushJob::WriteLevel0Table() {
|
||||
uint64_t total_num_entries = 0, total_num_deletes = 0;
|
||||
uint64_t total_data_size = 0;
|
||||
size_t total_memory_usage = 0;
|
||||
uint64_t total_num_range_deletes = 0;
|
||||
// Used for testing:
|
||||
uint64_t mems_size = mems_.size();
|
||||
(void)mems_size; // avoids unused variable error when
|
||||
@@ -902,20 +874,15 @@ Status FlushJob::WriteLevel0Table() {
|
||||
total_num_deletes += m->num_deletes();
|
||||
total_data_size += m->get_data_size();
|
||||
total_memory_usage += m->ApproximateMemoryUsage();
|
||||
total_num_range_deletes += m->num_range_deletes();
|
||||
}
|
||||
|
||||
// TODO(cbi): when memtable is flushed due to number of range deletions
|
||||
// hitting limit memtable_max_range_deletions, flush_reason_ is still
|
||||
// "Write Buffer Full", should make update flush_reason_ accordingly.
|
||||
event_logger_->Log() << "job" << job_context_->job_id << "event"
|
||||
<< "flush_started"
|
||||
<< "num_memtables" << mems_.size() << "num_entries"
|
||||
<< total_num_entries << "num_deletes"
|
||||
<< total_num_deletes << "total_data_size"
|
||||
<< total_data_size << "memory_usage"
|
||||
<< total_memory_usage << "num_range_deletes"
|
||||
<< total_num_range_deletes << "flush_reason"
|
||||
<< total_memory_usage << "flush_reason"
|
||||
<< GetFlushReasonString(flush_reason_);
|
||||
|
||||
{
|
||||
@@ -983,7 +950,6 @@ Status FlushJob::WriteLevel0Table() {
|
||||
&table_properties_, write_hint, full_history_ts_low,
|
||||
blob_callback_, base_, &num_input_entries,
|
||||
&memtable_payload_bytes, &memtable_garbage_bytes);
|
||||
TEST_SYNC_POINT_CALLBACK("FlushJob::WriteLevel0Table:s", &s);
|
||||
// TODO: Cleanup io_status in BuildTable and table builders
|
||||
assert(!s.ok() || io_s.ok());
|
||||
io_s.PermitUncheckedError();
|
||||
@@ -1131,57 +1097,4 @@ std::unique_ptr<FlushJobInfo> FlushJob::GetFlushJobInfo() const {
|
||||
return info;
|
||||
}
|
||||
|
||||
void FlushJob::GetEffectiveCutoffUDTForPickedMemTables() {
|
||||
db_mutex_->AssertHeld();
|
||||
assert(pick_memtable_called);
|
||||
const auto* ucmp = cfd_->internal_comparator().user_comparator();
|
||||
assert(ucmp);
|
||||
const size_t ts_sz = ucmp->timestamp_size();
|
||||
if (db_options_.atomic_flush || ts_sz == 0 ||
|
||||
cfd_->ioptions()->persist_user_defined_timestamps) {
|
||||
return;
|
||||
}
|
||||
for (MemTable* m : mems_) {
|
||||
Slice table_newest_udt = m->GetNewestUDT();
|
||||
// The picked Memtables should have ascending ID, and should have
|
||||
// non-decreasing newest user-defined timestamps.
|
||||
if (!cutoff_udt_.empty()) {
|
||||
assert(table_newest_udt.size() == cutoff_udt_.size());
|
||||
assert(ucmp->CompareTimestamp(table_newest_udt, cutoff_udt_) >= 0);
|
||||
cutoff_udt_.clear();
|
||||
}
|
||||
cutoff_udt_.assign(table_newest_udt.data(), table_newest_udt.size());
|
||||
}
|
||||
}
|
||||
|
||||
Status FlushJob::MaybeIncreaseFullHistoryTsLowToAboveCutoffUDT() {
|
||||
db_mutex_->AssertHeld();
|
||||
const auto* ucmp = cfd_->user_comparator();
|
||||
assert(ucmp);
|
||||
const std::string& full_history_ts_low = cfd_->GetFullHistoryTsLow();
|
||||
// Update full_history_ts_low to right above cutoff udt only if that would
|
||||
// increase it.
|
||||
if (cutoff_udt_.empty() ||
|
||||
(!full_history_ts_low.empty() &&
|
||||
ucmp->CompareTimestamp(cutoff_udt_, full_history_ts_low) < 0)) {
|
||||
return Status::OK();
|
||||
}
|
||||
Slice cutoff_udt_slice = cutoff_udt_;
|
||||
uint64_t cutoff_udt_ts = 0;
|
||||
bool format_res = GetFixed64(&cutoff_udt_slice, &cutoff_udt_ts);
|
||||
assert(format_res);
|
||||
(void)format_res;
|
||||
std::string new_full_history_ts_low;
|
||||
// TODO(yuzhangyu): Add a member to AdvancedColumnFamilyOptions for an
|
||||
// operation to get the next immediately larger user-defined timestamp to
|
||||
// expand this feature to other user-defined timestamp formats.
|
||||
PutFixed64(&new_full_history_ts_low, cutoff_udt_ts + 1);
|
||||
VersionEdit edit;
|
||||
edit.SetColumnFamily(cfd_->GetID());
|
||||
edit.SetFullHistoryTsLow(new_full_history_ts_low);
|
||||
return versions_->LogAndApply(cfd_, *cfd_->GetLatestMutableCFOptions(),
|
||||
ReadOptions(), &edit, db_mutex_,
|
||||
output_file_directory_);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+1
-24
@@ -83,14 +83,9 @@ class FlushJob {
|
||||
// Require db_mutex held.
|
||||
// Once PickMemTable() is called, either Run() or Cancel() has to be called.
|
||||
void PickMemTable();
|
||||
// @param skip_since_bg_error If not nullptr and if atomic_flush=false,
|
||||
// then it is set to true if flush installation is skipped and memtable
|
||||
// is rolled back due to existing background error.
|
||||
Status Run(LogsWithPrepTracker* prep_tracker = nullptr,
|
||||
FileMetaData* file_meta = nullptr,
|
||||
bool* switched_to_mempurge = nullptr,
|
||||
bool* skipped_since_bg_error = nullptr,
|
||||
ErrorHandler* error_handler = nullptr);
|
||||
bool* switched_to_mempurge = nullptr);
|
||||
void Cancel();
|
||||
const autovector<MemTable*>& GetMemTables() const { return mems_; }
|
||||
|
||||
@@ -132,20 +127,6 @@ class FlushJob {
|
||||
Env::IOPriority GetRateLimiterPriorityForWrite();
|
||||
std::unique_ptr<FlushJobInfo> GetFlushJobInfo() const;
|
||||
|
||||
// Require db_mutex held.
|
||||
// Called only when UDT feature is enabled and
|
||||
// `persist_user_defined_timestamps` flag is false. Because we will refrain
|
||||
// from flushing as long as there are still UDTs in a memtable that hasn't
|
||||
// expired w.r.t `full_history_ts_low`. However, flush is continued if there
|
||||
// is risk of entering write stall mode. In that case, we need
|
||||
// to track the effective cutoff timestamp below which all the udts are
|
||||
// removed because of flush, and use it to increase `full_history_ts_low` if
|
||||
// the effective cutoff timestamp is newer. See
|
||||
// `MaybeIncreaseFullHistoryTsLowToAboveCutoffUDT` for details.
|
||||
void GetEffectiveCutoffUDTForPickedMemTables();
|
||||
|
||||
Status MaybeIncreaseFullHistoryTsLowToAboveCutoffUDT();
|
||||
|
||||
const std::string& dbname_;
|
||||
const std::string db_id_;
|
||||
const std::string db_session_id_;
|
||||
@@ -214,10 +195,6 @@ class FlushJob {
|
||||
// db mutex
|
||||
const SeqnoToTimeMapping& db_impl_seqno_time_mapping_;
|
||||
SeqnoToTimeMapping seqno_to_time_mapping_;
|
||||
|
||||
// Keeps track of the newest user-defined timestamp for this flush job if
|
||||
// `persist_user_defined_timestamps` flag is false.
|
||||
std::string cutoff_udt_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+11
-88
@@ -70,7 +70,6 @@ class FlushJobTestBase : public testing::Test {
|
||||
new_cf.AddColumnFamily(column_family_names_[i]);
|
||||
new_cf.SetColumnFamily(cf_id++);
|
||||
new_cf.SetComparatorName(ucmp_->Name());
|
||||
new_cf.SetPersistUserDefinedTimestamps(persist_udt_);
|
||||
new_cf.SetLogNumber(0);
|
||||
new_cf.SetNextFile(2);
|
||||
new_cf.SetLastSequence(last_seq++);
|
||||
@@ -118,8 +117,6 @@ class FlushJobTestBase : public testing::Test {
|
||||
db_options_.statistics = CreateDBStatistics();
|
||||
|
||||
cf_options_.comparator = ucmp_;
|
||||
cf_options_.persist_user_defined_timestamps = persist_udt_;
|
||||
cf_options_.paranoid_file_checks = paranoid_file_checks_;
|
||||
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
cf_options_.table_factory = mock_table_factory_;
|
||||
@@ -152,9 +149,6 @@ class FlushJobTestBase : public testing::Test {
|
||||
std::atomic<bool> shutting_down_;
|
||||
std::shared_ptr<mock::MockTableFactory> mock_table_factory_;
|
||||
|
||||
bool persist_udt_ = true;
|
||||
bool paranoid_file_checks_ = false;
|
||||
|
||||
SeqnoToTimeMapping empty_seqno_to_time_mapping_;
|
||||
};
|
||||
|
||||
@@ -606,13 +600,7 @@ TEST_F(FlushJobTest, GetRateLimiterPriorityForWrite) {
|
||||
}
|
||||
}
|
||||
|
||||
// Test parameters:
|
||||
// param 0): paranoid file check
|
||||
// param 1): user-defined timestamp test mode
|
||||
class FlushJobTimestampTest
|
||||
: public FlushJobTestBase,
|
||||
public testing::WithParamInterface<
|
||||
std::tuple<bool, test::UserDefinedTimestampTestMode>> {
|
||||
class FlushJobTimestampTest : public FlushJobTestBase {
|
||||
public:
|
||||
FlushJobTimestampTest()
|
||||
: FlushJobTestBase(test::PerThreadDBPath("flush_job_ts_gc_test"),
|
||||
@@ -628,40 +616,13 @@ class FlushJobTimestampTest
|
||||
}
|
||||
|
||||
protected:
|
||||
void SetUp() override {
|
||||
paranoid_file_checks_ = std::get<0>(GetParam());
|
||||
auto udt_test_mode = std::get<1>(GetParam());
|
||||
persist_udt_ = test::ShouldPersistUDT(udt_test_mode);
|
||||
FlushJobTestBase::SetUp();
|
||||
}
|
||||
static constexpr uint64_t kStartTs = 10;
|
||||
static constexpr SequenceNumber kStartSeq = 0;
|
||||
SequenceNumber curr_seq_{kStartSeq};
|
||||
std::atomic<uint64_t> curr_ts_{kStartTs};
|
||||
|
||||
void CheckFileMetaData(ColumnFamilyData* cfd,
|
||||
const InternalKey& expected_smallest,
|
||||
const InternalKey& expected_largest,
|
||||
const FileMetaData* meta_from_flush) const {
|
||||
ASSERT_EQ(expected_smallest.Encode(), meta_from_flush->smallest.Encode());
|
||||
ASSERT_EQ(expected_largest.Encode(), meta_from_flush->largest.Encode());
|
||||
|
||||
const VersionStorageInfo* storage_info = cfd->current()->storage_info();
|
||||
const std::vector<FileMetaData*>& l0_files = storage_info->LevelFiles(0);
|
||||
|
||||
ASSERT_EQ(l0_files.size(), 1);
|
||||
auto installed_file_meta = l0_files[0];
|
||||
ASSERT_EQ(expected_smallest.Encode(),
|
||||
installed_file_meta->smallest.Encode());
|
||||
ASSERT_EQ(expected_largest.Encode(), installed_file_meta->largest.Encode());
|
||||
}
|
||||
void CheckFullHistoryTsLow(ColumnFamilyData* cfd,
|
||||
const std::string& expected_full_history_ts_low) {
|
||||
ASSERT_EQ(expected_full_history_ts_low, cfd->GetFullHistoryTsLow());
|
||||
}
|
||||
};
|
||||
|
||||
TEST_P(FlushJobTimestampTest, AllKeysExpired) {
|
||||
TEST_F(FlushJobTimestampTest, AllKeysExpired) {
|
||||
ColumnFamilyData* cfd = versions_->GetColumnFamilySet()->GetDefault();
|
||||
autovector<MemTable*> to_delete;
|
||||
|
||||
@@ -689,7 +650,6 @@ TEST_P(FlushJobTimestampTest, AllKeysExpired) {
|
||||
EventLogger event_logger(db_options_.info_log.get());
|
||||
std::string full_history_ts_low;
|
||||
PutFixed64(&full_history_ts_low, std::numeric_limits<uint64_t>::max());
|
||||
cfd->SetFullHistoryTsLow(full_history_ts_low);
|
||||
FlushJob flush_job(
|
||||
dbname_, cfd, db_options_, *cfd->GetLatestMutableCFOptions(),
|
||||
std::numeric_limits<uint64_t>::max() /* memtable_id */, env_options_,
|
||||
@@ -709,25 +669,17 @@ TEST_P(FlushJobTimestampTest, AllKeysExpired) {
|
||||
|
||||
{
|
||||
std::string key = test::EncodeInt(0);
|
||||
if (!persist_udt_) {
|
||||
// When `AdvancedColumnFamilyOptions.persist_user_defined_timestamps` flag
|
||||
// is set to false. The user-defined timestamp is stripped from user key
|
||||
// during flush, making the user key logically containing the minimum
|
||||
// timestamp.
|
||||
key.append(test::EncodeInt(0));
|
||||
} else {
|
||||
key.append(test::EncodeInt(curr_ts_.load(std::memory_order_relaxed) - 1));
|
||||
}
|
||||
key.append(test::EncodeInt(curr_ts_.load(std::memory_order_relaxed) - 1));
|
||||
InternalKey ikey(key, curr_seq_ - 1, ValueType::kTypeDeletionWithTimestamp);
|
||||
CheckFileMetaData(cfd, ikey, ikey, &fmeta);
|
||||
CheckFullHistoryTsLow(cfd, full_history_ts_low);
|
||||
ASSERT_EQ(ikey.Encode(), fmeta.smallest.Encode());
|
||||
ASSERT_EQ(ikey.Encode(), fmeta.largest.Encode());
|
||||
}
|
||||
|
||||
job_context.Clean();
|
||||
ASSERT_TRUE(to_delete.empty());
|
||||
}
|
||||
|
||||
TEST_P(FlushJobTimestampTest, NoKeyExpired) {
|
||||
TEST_F(FlushJobTimestampTest, NoKeyExpired) {
|
||||
ColumnFamilyData* cfd = versions_->GetColumnFamilySet()->GetDefault();
|
||||
autovector<MemTable*> to_delete;
|
||||
|
||||
@@ -751,7 +703,6 @@ TEST_P(FlushJobTimestampTest, NoKeyExpired) {
|
||||
EventLogger event_logger(db_options_.info_log.get());
|
||||
std::string full_history_ts_low;
|
||||
PutFixed64(&full_history_ts_low, 0);
|
||||
cfd->SetFullHistoryTsLow(full_history_ts_low);
|
||||
FlushJob flush_job(
|
||||
dbname_, cfd, db_options_, *cfd->GetLatestMutableCFOptions(),
|
||||
std::numeric_limits<uint64_t>::max() /* memtable_id */, env_options_,
|
||||
@@ -771,46 +722,18 @@ TEST_P(FlushJobTimestampTest, NoKeyExpired) {
|
||||
|
||||
{
|
||||
std::string ukey = test::EncodeInt(0);
|
||||
std::string smallest_key;
|
||||
std::string largest_key;
|
||||
std::string expected_full_history_ts_low;
|
||||
if (!persist_udt_) {
|
||||
// When `AdvancedColumnFamilyOptions.persist_user_defined_timestamps` flag
|
||||
// is set to false. The user-defined timestamp is stripped from user key
|
||||
// during flush, making the user key logically containing the minimum
|
||||
// timestamp, which is hardcoded to be all zeros for now.
|
||||
smallest_key = ukey + test::EncodeInt(0);
|
||||
largest_key = ukey + test::EncodeInt(0);
|
||||
// When not all keys have expired and `persist_user_defined_timestamps` is
|
||||
// false. UDTs will be removed during flush, `full_history_ts_low` should
|
||||
// be automatically increased to above the effective cutoff UDT in the
|
||||
// flush.
|
||||
PutFixed64(&expected_full_history_ts_low, curr_ts_.fetch_add(1));
|
||||
} else {
|
||||
smallest_key =
|
||||
ukey + test::EncodeInt(curr_ts_.load(std::memory_order_relaxed) - 1);
|
||||
largest_key = ukey + test::EncodeInt(kStartTs);
|
||||
expected_full_history_ts_low = full_history_ts_low;
|
||||
}
|
||||
std::string smallest_key =
|
||||
ukey + test::EncodeInt(curr_ts_.load(std::memory_order_relaxed) - 1);
|
||||
std::string largest_key = ukey + test::EncodeInt(kStartTs);
|
||||
InternalKey smallest(smallest_key, curr_seq_ - 1, ValueType::kTypeValue);
|
||||
InternalKey largest(largest_key, kStartSeq, ValueType::kTypeValue);
|
||||
CheckFileMetaData(cfd, smallest, largest, &fmeta);
|
||||
CheckFullHistoryTsLow(cfd, expected_full_history_ts_low);
|
||||
ASSERT_EQ(smallest.Encode(), fmeta.smallest.Encode());
|
||||
ASSERT_EQ(largest.Encode(), fmeta.largest.Encode());
|
||||
}
|
||||
job_context.Clean();
|
||||
ASSERT_TRUE(to_delete.empty());
|
||||
}
|
||||
|
||||
// Param 0: paranoid file check
|
||||
// Param 1: test mode for the user-defined timestamp feature
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
FlushJobTimestampTest, FlushJobTimestampTest,
|
||||
::testing::Combine(
|
||||
::testing::Bool(),
|
||||
::testing::Values(
|
||||
test::UserDefinedTimestampTestMode::kStripUserDefinedTimestamp,
|
||||
test::UserDefinedTimestampTestMode::kNormal)));
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -185,8 +185,7 @@ Status ImportColumnFamilyJob::Run() {
|
||||
&cfd_->internal_comparator(), cfd_->user_comparator(),
|
||||
cfd_->NumberLevels(), cfd_->ioptions()->compaction_style,
|
||||
nullptr /* src_vstorage */, cfd_->ioptions()->force_consistency_checks,
|
||||
EpochNumberRequirement::kMightMissing, cfd_->ioptions()->clock,
|
||||
cfd_->GetLatestMutableCFOptions()->bottommost_file_compaction_delay);
|
||||
EpochNumberRequirement::kMightMissing);
|
||||
Status s;
|
||||
|
||||
for (size_t i = 0; s.ok() && i < files_to_import_.size(); ++i) {
|
||||
|
||||
+1
-1
@@ -582,7 +582,7 @@ TEST_F(EventListenerTest, CompactionReasonLevel) {
|
||||
for (int k = 1; k <= 30; k++) {
|
||||
ASSERT_OK(Put(Key(k), Key(k)));
|
||||
if (k % 10 == 0) {
|
||||
ASSERT_OK(Flush());
|
||||
Flush();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+13
-52
@@ -95,7 +95,6 @@ MemTable::MemTable(const InternalKeyComparator& cmp,
|
||||
data_size_(0),
|
||||
num_entries_(0),
|
||||
num_deletes_(0),
|
||||
num_range_deletes_(0),
|
||||
write_buffer_size_(mutable_cf_options.write_buffer_size),
|
||||
flush_in_progress_(false),
|
||||
flush_completed_(false),
|
||||
@@ -115,9 +114,7 @@ MemTable::MemTable(const InternalKeyComparator& cmp,
|
||||
ioptions.memtable_insert_with_hint_prefix_extractor.get()),
|
||||
oldest_key_time_(std::numeric_limits<uint64_t>::max()),
|
||||
atomic_flush_seqno_(kMaxSequenceNumber),
|
||||
approximate_memory_usage_(0),
|
||||
memtable_max_range_deletions_(
|
||||
mutable_cf_options.memtable_max_range_deletions) {
|
||||
approximate_memory_usage_(0) {
|
||||
UpdateFlushState();
|
||||
// something went wrong if we need to flush before inserting anything
|
||||
assert(!ShouldScheduleFlush());
|
||||
@@ -146,10 +143,6 @@ MemTable::MemTable(const InternalKeyComparator& cmp,
|
||||
new_cache.get()),
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
const Comparator* ucmp = cmp.user_comparator();
|
||||
assert(ucmp);
|
||||
ts_sz_ = ucmp->timestamp_size();
|
||||
persist_user_defined_timestamps_ = ioptions.persist_user_defined_timestamps;
|
||||
}
|
||||
|
||||
MemTable::~MemTable() {
|
||||
@@ -177,14 +170,6 @@ size_t MemTable::ApproximateMemoryUsage() {
|
||||
}
|
||||
|
||||
bool MemTable::ShouldFlushNow() {
|
||||
// This is set if memtable_max_range_deletions is > 0,
|
||||
// and that many range deletions are done
|
||||
if (memtable_max_range_deletions_ > 0 &&
|
||||
num_range_deletes_.load(std::memory_order_relaxed) >=
|
||||
static_cast<uint64_t>(memtable_max_range_deletions_)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t write_buffer_size = write_buffer_size_.load(std::memory_order_relaxed);
|
||||
// In a lot of times, we cannot allocate arena blocks that exactly matches the
|
||||
// buffer size. Thus we have to decide if we should over-allocate or
|
||||
@@ -372,8 +357,7 @@ class MemTableIterator : public InternalIterator {
|
||||
!mem.GetImmutableMemTableOptions()->inplace_update_support),
|
||||
protection_bytes_per_key_(mem.moptions_.protection_bytes_per_key),
|
||||
status_(Status::OK()),
|
||||
logger_(mem.moptions_.info_log),
|
||||
ts_sz_(mem.ts_sz_) {
|
||||
logger_(mem.moptions_.info_log) {
|
||||
if (use_range_del_table) {
|
||||
iter_ = mem.range_del_table_->GetIterator(arena);
|
||||
} else if (prefix_extractor_ != nullptr && !read_options.total_order_seek &&
|
||||
@@ -416,7 +400,8 @@ class MemTableIterator : public InternalIterator {
|
||||
PERF_COUNTER_ADD(seek_on_memtable_count, 1);
|
||||
if (bloom_) {
|
||||
// iterator should only use prefix bloom filter
|
||||
Slice user_k_without_ts(ExtractUserKeyAndStripTimestamp(k, ts_sz_));
|
||||
auto ts_sz = comparator_.comparator.user_comparator()->timestamp_size();
|
||||
Slice user_k_without_ts(ExtractUserKeyAndStripTimestamp(k, ts_sz));
|
||||
if (prefix_extractor_->InDomain(user_k_without_ts)) {
|
||||
if (!bloom_->MayContain(
|
||||
prefix_extractor_->Transform(user_k_without_ts))) {
|
||||
@@ -436,7 +421,8 @@ class MemTableIterator : public InternalIterator {
|
||||
PERF_TIMER_GUARD(seek_on_memtable_time);
|
||||
PERF_COUNTER_ADD(seek_on_memtable_count, 1);
|
||||
if (bloom_) {
|
||||
Slice user_k_without_ts(ExtractUserKeyAndStripTimestamp(k, ts_sz_));
|
||||
auto ts_sz = comparator_.comparator.user_comparator()->timestamp_size();
|
||||
Slice user_k_without_ts(ExtractUserKeyAndStripTimestamp(k, ts_sz));
|
||||
if (prefix_extractor_->InDomain(user_k_without_ts)) {
|
||||
if (!bloom_->MayContain(
|
||||
prefix_extractor_->Transform(user_k_without_ts))) {
|
||||
@@ -526,7 +512,6 @@ class MemTableIterator : public InternalIterator {
|
||||
uint32_t protection_bytes_per_key_;
|
||||
Status status_;
|
||||
Logger* logger_;
|
||||
size_t ts_sz_;
|
||||
|
||||
void VerifyEntryChecksum() {
|
||||
if (protection_bytes_per_key_ > 0 && Valid()) {
|
||||
@@ -640,7 +625,8 @@ Status MemTable::VerifyEncodedEntry(Slice encoded,
|
||||
if (!GetVarint32(&encoded, &ikey_len)) {
|
||||
return Status::Corruption("Unable to parse internal key length");
|
||||
}
|
||||
if (ikey_len < 8 + ts_sz_) {
|
||||
size_t ts_sz = GetInternalKeyComparator().user_comparator()->timestamp_size();
|
||||
if (ikey_len < 8 + ts_sz) {
|
||||
return Status::Corruption("Internal key length too short");
|
||||
}
|
||||
if (ikey_len > encoded.size()) {
|
||||
@@ -739,7 +725,8 @@ Status MemTable::Add(SequenceNumber s, ValueType type,
|
||||
}
|
||||
}
|
||||
|
||||
Slice key_without_ts = StripTimestampFromUserKey(key, ts_sz_);
|
||||
size_t ts_sz = GetInternalKeyComparator().user_comparator()->timestamp_size();
|
||||
Slice key_without_ts = StripTimestampFromUserKey(key, ts_sz);
|
||||
|
||||
if (!allow_concurrent) {
|
||||
// Extract prefix for insert with hint.
|
||||
@@ -767,9 +754,6 @@ Status MemTable::Add(SequenceNumber s, ValueType type,
|
||||
type == kTypeDeletionWithTimestamp) {
|
||||
num_deletes_.store(num_deletes_.load(std::memory_order_relaxed) + 1,
|
||||
std::memory_order_relaxed);
|
||||
} else if (type == kTypeRangeDeletion) {
|
||||
uint64_t val = num_range_deletes_.load(std::memory_order_relaxed) + 1;
|
||||
num_range_deletes_.store(val, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
if (bloom_filter_ && prefix_extractor_ &&
|
||||
@@ -792,9 +776,6 @@ Status MemTable::Add(SequenceNumber s, ValueType type,
|
||||
assert(first_seqno_.load() >= earliest_seqno_.load());
|
||||
}
|
||||
assert(post_process_info == nullptr);
|
||||
// TODO(yuzhangyu): support updating newest UDT for when `allow_concurrent`
|
||||
// is true.
|
||||
MaybeUpdateNewestUDT(key_slice);
|
||||
UpdateFlushState();
|
||||
} else {
|
||||
bool res = (hint == nullptr)
|
||||
@@ -829,14 +810,13 @@ Status MemTable::Add(SequenceNumber s, ValueType type,
|
||||
earliest_seqno_.load(std::memory_order_relaxed);
|
||||
while (
|
||||
(cur_earliest_seqno == kMaxSequenceNumber || s < cur_earliest_seqno) &&
|
||||
!earliest_seqno_.compare_exchange_weak(cur_earliest_seqno, s)) {
|
||||
!first_seqno_.compare_exchange_weak(cur_earliest_seqno, s)) {
|
||||
}
|
||||
}
|
||||
if (type == kTypeRangeDeletion) {
|
||||
auto new_cache = std::make_shared<FragmentedRangeTombstoneListCache>();
|
||||
size_t size = cached_range_tombstone_.Size();
|
||||
if (allow_concurrent) {
|
||||
post_process_info->num_range_deletes++;
|
||||
range_del_mutex_.lock();
|
||||
}
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
@@ -855,7 +835,6 @@ Status MemTable::Add(SequenceNumber s, ValueType type,
|
||||
new_local_cache_ref, new_cache.get()),
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
if (allow_concurrent) {
|
||||
range_del_mutex_.unlock();
|
||||
}
|
||||
@@ -1284,7 +1263,6 @@ bool MemTable::Get(const LookupKey& key, std::string* value,
|
||||
// Avoiding recording stats for speed.
|
||||
return false;
|
||||
}
|
||||
|
||||
PERF_TIMER_GUARD(get_from_memtable_time);
|
||||
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> range_del_iter(
|
||||
@@ -1308,7 +1286,8 @@ bool MemTable::Get(const LookupKey& key, std::string* value,
|
||||
bool found_final_value = false;
|
||||
bool merge_in_progress = s->IsMergeInProgress();
|
||||
bool may_contain = true;
|
||||
Slice user_key_without_ts = StripTimestampFromUserKey(key.user_key(), ts_sz_);
|
||||
size_t ts_sz = GetInternalKeyComparator().user_comparator()->timestamp_size();
|
||||
Slice user_key_without_ts = StripTimestampFromUserKey(key.user_key(), ts_sz);
|
||||
bool bloom_checked = false;
|
||||
if (bloom_filter_) {
|
||||
// when both memtable_whole_key_filtering and prefix_extractor_ are set,
|
||||
@@ -1693,22 +1672,4 @@ uint64_t MemTable::GetMinLogContainingPrepSection() {
|
||||
return min_prep_log_referenced_.load();
|
||||
}
|
||||
|
||||
void MemTable::MaybeUpdateNewestUDT(const Slice& user_key) {
|
||||
if (ts_sz_ == 0 || persist_user_defined_timestamps_) {
|
||||
return;
|
||||
}
|
||||
const Comparator* ucmp = GetInternalKeyComparator().user_comparator();
|
||||
Slice udt = ExtractTimestampFromUserKey(user_key, ts_sz_);
|
||||
if (newest_udt_.empty() || ucmp->CompareTimestamp(udt, newest_udt_) > 0) {
|
||||
newest_udt_ = udt;
|
||||
}
|
||||
}
|
||||
|
||||
const Slice& MemTable::GetNewestUDT() const {
|
||||
// This path should not be invoked for MemTables that does not enable the UDT
|
||||
// in Memtable only feature.
|
||||
assert(ts_sz_ > 0 && !persist_user_defined_timestamps_);
|
||||
return newest_udt_;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -68,7 +68,6 @@ struct MemTablePostProcessInfo {
|
||||
uint64_t data_size = 0;
|
||||
uint64_t num_entries = 0;
|
||||
uint64_t num_deletes = 0;
|
||||
uint64_t num_range_deletes = 0;
|
||||
};
|
||||
|
||||
using MultiGetRange = MultiGetContext::Range;
|
||||
@@ -333,10 +332,6 @@ class MemTable {
|
||||
num_deletes_.fetch_add(update_counters.num_deletes,
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
if (update_counters.num_range_deletes > 0) {
|
||||
num_range_deletes_.fetch_add(update_counters.num_range_deletes,
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
UpdateFlushState();
|
||||
}
|
||||
|
||||
@@ -354,21 +349,10 @@ class MemTable {
|
||||
return num_deletes_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Get total number of range deletions in the mem table.
|
||||
// REQUIRES: external synchronization to prevent simultaneous
|
||||
// operations on the same MemTable (unless this Memtable is immutable).
|
||||
uint64_t num_range_deletes() const {
|
||||
return num_range_deletes_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
uint64_t get_data_size() const {
|
||||
return data_size_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
size_t write_buffer_size() const {
|
||||
return write_buffer_size_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Dynamically change the memtable's capacity. If set below the current usage,
|
||||
// the next key added will trigger a flush. Can only increase size when
|
||||
// memtable prefix bloom is disabled, since we can't easily allocate more
|
||||
@@ -543,14 +527,6 @@ class MemTable {
|
||||
}
|
||||
}
|
||||
|
||||
// Get the newest user-defined timestamp contained in this MemTable. Check
|
||||
// `newest_udt_` for what newer means. This method should only be invoked for
|
||||
// an MemTable that has enabled user-defined timestamp feature and set
|
||||
// `persist_user_defined_timestamps` to false. The tracked newest UDT will be
|
||||
// used by flush job in the background to help check the MemTable's
|
||||
// eligibility for Flush.
|
||||
const Slice& GetNewestUDT() const;
|
||||
|
||||
// Returns Corruption status if verification fails.
|
||||
static Status VerifyEntryChecksum(const char* entry,
|
||||
uint32_t protection_bytes_per_key,
|
||||
@@ -577,7 +553,6 @@ class MemTable {
|
||||
std::atomic<uint64_t> data_size_;
|
||||
std::atomic<uint64_t> num_entries_;
|
||||
std::atomic<uint64_t> num_deletes_;
|
||||
std::atomic<uint64_t> num_range_deletes_;
|
||||
|
||||
// Dynamically changeable memtable option
|
||||
std::atomic<size_t> write_buffer_size_;
|
||||
@@ -639,26 +614,9 @@ class MemTable {
|
||||
// Gets refreshed inside `ApproximateMemoryUsage()` or `ShouldFlushNow`
|
||||
std::atomic<uint64_t> approximate_memory_usage_;
|
||||
|
||||
// max range deletions in a memtable, before automatic flushing, 0 for
|
||||
// unlimited.
|
||||
uint32_t memtable_max_range_deletions_ = 0;
|
||||
|
||||
// Flush job info of the current memtable.
|
||||
std::unique_ptr<FlushJobInfo> flush_job_info_;
|
||||
|
||||
// Size in bytes for the user-defined timestamps.
|
||||
size_t ts_sz_;
|
||||
|
||||
// Whether to persist user-defined timestamps
|
||||
bool persist_user_defined_timestamps_;
|
||||
|
||||
// Newest user-defined timestamp contained in this MemTable. For ts1, and ts2
|
||||
// if Comparator::CompareTimestamp(ts1, ts2) > 0, ts1 is considered newer than
|
||||
// ts2. We track this field for a MemTable if its column family has UDT
|
||||
// feature enabled and the `persist_user_defined_timestamp` flag is false.
|
||||
// Otherwise, this field just contains an empty Slice.
|
||||
Slice newest_udt_;
|
||||
|
||||
// Updates flush_state_ using ShouldFlushNow()
|
||||
void UpdateFlushState();
|
||||
|
||||
@@ -695,8 +653,6 @@ class MemTable {
|
||||
void UpdateEntryChecksum(const ProtectionInfoKVOS64* kv_prot_info,
|
||||
const Slice& key, const Slice& value, ValueType type,
|
||||
SequenceNumber s, char* checksum_ptr);
|
||||
|
||||
void MaybeUpdateNewestUDT(const Slice& user_key);
|
||||
};
|
||||
|
||||
extern const char* EncodeKey(std::string* scratch, const Slice& target);
|
||||
|
||||
+10
-44
@@ -434,57 +434,23 @@ void MemTableList::PickMemtablesToFlush(uint64_t max_memtable_id,
|
||||
}
|
||||
|
||||
void MemTableList::RollbackMemtableFlush(const autovector<MemTable*>& mems,
|
||||
bool rollback_succeeding_memtables) {
|
||||
TEST_SYNC_POINT("RollbackMemtableFlush");
|
||||
uint64_t /*file_number*/) {
|
||||
AutoThreadOperationStageUpdater stage_updater(
|
||||
ThreadStatus::STAGE_MEMTABLE_ROLLBACK);
|
||||
#ifndef NDEBUG
|
||||
assert(!mems.empty());
|
||||
|
||||
// If the flush was not successful, then just reset state.
|
||||
// Maybe a succeeding attempt to flush will be successful.
|
||||
for (MemTable* m : mems) {
|
||||
assert(m->flush_in_progress_);
|
||||
assert(m->file_number_ == 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (rollback_succeeding_memtables && !mems.empty()) {
|
||||
std::list<MemTable*>& memlist = current_->memlist_;
|
||||
auto it = memlist.rbegin();
|
||||
for (; *it != mems[0] && it != memlist.rend(); ++it) {
|
||||
}
|
||||
// mems should be in memlist
|
||||
assert(*it == mems[0]);
|
||||
if (*it == mems[0]) {
|
||||
++it;
|
||||
}
|
||||
while (it != memlist.rend()) {
|
||||
MemTable* m = *it;
|
||||
// Only rollback complete, not in-progress,
|
||||
// in_progress can be flushes that are still writing SSTs
|
||||
if (m->flush_completed_) {
|
||||
m->flush_in_progress_ = false;
|
||||
m->flush_completed_ = false;
|
||||
m->edit_.Clear();
|
||||
m->file_number_ = 0;
|
||||
num_flush_not_started_++;
|
||||
++it;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (MemTable* m : mems) {
|
||||
if (m->flush_in_progress_) {
|
||||
assert(m->file_number_ == 0);
|
||||
m->file_number_ = 0;
|
||||
m->flush_in_progress_ = false;
|
||||
m->flush_completed_ = false;
|
||||
m->edit_.Clear();
|
||||
num_flush_not_started_++;
|
||||
}
|
||||
}
|
||||
if (!mems.empty()) {
|
||||
imm_flush_needed.store(true, std::memory_order_release);
|
||||
m->flush_in_progress_ = false;
|
||||
m->flush_completed_ = false;
|
||||
m->edit_.Clear();
|
||||
num_flush_not_started_++;
|
||||
}
|
||||
imm_flush_needed.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
// Try record a successful flush in the manifest file. It might just return
|
||||
|
||||
+1
-32
@@ -271,20 +271,8 @@ class MemTableList {
|
||||
|
||||
// Reset status of the given memtable list back to pending state so that
|
||||
// they can get picked up again on the next round of flush.
|
||||
//
|
||||
// @param rollback_succeeding_memtables If true, will rollback adjacent
|
||||
// younger memtables whose flush is completed. Specifically, suppose the
|
||||
// current immutable memtables are M_0,M_1...M_N ordered from youngest to
|
||||
// oldest. Suppose that the youngest memtable in `mems` is M_K. We will try to
|
||||
// rollback M_K-1, M_K-2... until the first memtable whose flush is
|
||||
// not completed. These are the memtables that would have been installed
|
||||
// by this flush job if it were to succeed. This flag is currently used
|
||||
// by non atomic_flush rollback.
|
||||
// Note that we also do rollback in `write_manifest_cb` by calling
|
||||
// `RemoveMemTablesOrRestoreFlags()`. There we rollback the entire batch so
|
||||
// it is similar to what we do here with rollback_succeeding_memtables=true.
|
||||
void RollbackMemtableFlush(const autovector<MemTable*>& mems,
|
||||
bool rollback_succeeding_memtables);
|
||||
uint64_t file_number);
|
||||
|
||||
// Try commit a successful flush in the manifest file. It might just return
|
||||
// Status::OK letting a concurrent flush to do the actual the recording.
|
||||
@@ -394,25 +382,6 @@ class MemTableList {
|
||||
return memlist.front()->GetID();
|
||||
}
|
||||
|
||||
// DB mutex held.
|
||||
// Gets the newest user-defined timestamp for the Memtables in ascending ID
|
||||
// order, up to the `max_memtable_id`. Used by background flush job
|
||||
// to check Memtables' eligibility for flush w.r.t retaining UDTs.
|
||||
std::vector<Slice> GetTablesNewestUDT(uint64_t max_memtable_id) {
|
||||
std::vector<Slice> newest_udts;
|
||||
auto& memlist = current_->memlist_;
|
||||
// Iterating through the memlist starting at the end, the vector<MemTable*>
|
||||
// ret is filled with memtables already sorted in increasing MemTable ID.
|
||||
for (auto it = memlist.rbegin(); it != memlist.rend(); ++it) {
|
||||
MemTable* m = *it;
|
||||
if (m->GetID() > max_memtable_id) {
|
||||
break;
|
||||
}
|
||||
newest_udts.push_back(m->GetNewestUDT());
|
||||
}
|
||||
return newest_udts;
|
||||
}
|
||||
|
||||
void AssignAtomicFlushSeq(const SequenceNumber& seq) {
|
||||
const auto& memlist = current_->memlist_;
|
||||
// Scan the memtable list from new to old
|
||||
|
||||
@@ -43,9 +43,6 @@ class MemTableListTest : public testing::Test {
|
||||
// Open DB only with default column family
|
||||
ColumnFamilyOptions cf_options;
|
||||
std::vector<ColumnFamilyDescriptor> cf_descs;
|
||||
if (udt_enabled_) {
|
||||
cf_options.comparator = test::BytewiseComparatorWithU64TsWrapper();
|
||||
}
|
||||
cf_descs.emplace_back(kDefaultColumnFamilyName, cf_options);
|
||||
Status s = DB::Open(options, dbname, cf_descs, &handles, &db);
|
||||
EXPECT_OK(s);
|
||||
@@ -203,9 +200,6 @@ class MemTableListTest : public testing::Test {
|
||||
nullptr /* prep_tracker */, &mutex, file_meta_ptrs,
|
||||
committed_flush_jobs_info, to_delete, nullptr, &log_buffer);
|
||||
}
|
||||
|
||||
protected:
|
||||
bool udt_enabled_ = false;
|
||||
};
|
||||
|
||||
TEST_F(MemTableListTest, Empty) {
|
||||
@@ -682,7 +676,7 @@ TEST_F(MemTableListTest, FlushPendingTest) {
|
||||
ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire));
|
||||
|
||||
// Revert flush
|
||||
list.RollbackMemtableFlush(to_flush, false);
|
||||
list.RollbackMemtableFlush(to_flush, 0);
|
||||
ASSERT_FALSE(list.IsFlushPending());
|
||||
ASSERT_TRUE(list.imm_flush_needed.load(std::memory_order_acquire));
|
||||
to_flush.clear();
|
||||
@@ -732,7 +726,7 @@ TEST_F(MemTableListTest, FlushPendingTest) {
|
||||
ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire));
|
||||
|
||||
// Rollback first pick of tables
|
||||
list.RollbackMemtableFlush(to_flush, false);
|
||||
list.RollbackMemtableFlush(to_flush, 0);
|
||||
ASSERT_TRUE(list.IsFlushPending());
|
||||
ASSERT_TRUE(list.imm_flush_needed.load(std::memory_order_acquire));
|
||||
to_flush.clear();
|
||||
@@ -874,7 +868,7 @@ TEST_F(MemTableListTest, FlushPendingTest) {
|
||||
to_delete.clear();
|
||||
}
|
||||
|
||||
TEST_F(MemTableListTest, EmptyAtomicFlushTest) {
|
||||
TEST_F(MemTableListTest, EmptyAtomicFlusTest) {
|
||||
autovector<MemTableList*> lists;
|
||||
autovector<uint32_t> cf_ids;
|
||||
autovector<const MutableCFOptions*> options_list;
|
||||
@@ -886,7 +880,7 @@ TEST_F(MemTableListTest, EmptyAtomicFlushTest) {
|
||||
ASSERT_TRUE(to_delete.empty());
|
||||
}
|
||||
|
||||
TEST_F(MemTableListTest, AtomicFlushTest) {
|
||||
TEST_F(MemTableListTest, AtomicFlusTest) {
|
||||
const int num_cfs = 3;
|
||||
const int num_tables_per_cf = 2;
|
||||
SequenceNumber seq = 1;
|
||||
@@ -1034,86 +1028,6 @@ TEST_F(MemTableListTest, AtomicFlushTest) {
|
||||
}
|
||||
}
|
||||
|
||||
class MemTableListWithTimestampTest : public MemTableListTest {
|
||||
public:
|
||||
MemTableListWithTimestampTest() : MemTableListTest() {}
|
||||
|
||||
void SetUp() override { udt_enabled_ = true; }
|
||||
};
|
||||
|
||||
TEST_F(MemTableListWithTimestampTest, GetTableNewestUDT) {
|
||||
const int num_tables = 3;
|
||||
const int num_entries = 5;
|
||||
SequenceNumber seq = 1;
|
||||
|
||||
auto factory = std::make_shared<SkipListFactory>();
|
||||
options.memtable_factory = factory;
|
||||
options.persist_user_defined_timestamps = false;
|
||||
ImmutableOptions ioptions(options);
|
||||
const Comparator* ucmp = test::BytewiseComparatorWithU64TsWrapper();
|
||||
InternalKeyComparator cmp(ucmp);
|
||||
WriteBufferManager wb(options.db_write_buffer_size);
|
||||
|
||||
// Create MemTableList
|
||||
int min_write_buffer_number_to_merge = 1;
|
||||
int max_write_buffer_number_to_maintain = 4;
|
||||
int64_t max_write_buffer_size_to_maintain =
|
||||
4 * static_cast<int>(options.write_buffer_size);
|
||||
MemTableList list(min_write_buffer_number_to_merge,
|
||||
max_write_buffer_number_to_maintain,
|
||||
max_write_buffer_size_to_maintain);
|
||||
|
||||
// Create some MemTables
|
||||
uint64_t memtable_id = 0;
|
||||
std::vector<MemTable*> tables;
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
uint64_t current_ts = 0;
|
||||
autovector<MemTable*> to_delete;
|
||||
std::vector<std::string> newest_udts;
|
||||
|
||||
std::string key;
|
||||
std::string write_ts;
|
||||
for (int i = 0; i < num_tables; i++) {
|
||||
MemTable* mem = new MemTable(cmp, ioptions, mutable_cf_options, &wb,
|
||||
kMaxSequenceNumber, 0 /* column_family_id */);
|
||||
mem->SetID(memtable_id++);
|
||||
mem->Ref();
|
||||
|
||||
std::string value;
|
||||
MergeContext merge_context;
|
||||
|
||||
for (int j = 0; j < num_entries; j++) {
|
||||
key = "key1";
|
||||
write_ts.clear();
|
||||
PutFixed64(&write_ts, current_ts);
|
||||
key.append(write_ts);
|
||||
ASSERT_OK(mem->Add(++seq, kTypeValue, key, std::to_string(i),
|
||||
nullptr /* kv_prot_info */));
|
||||
current_ts++;
|
||||
}
|
||||
|
||||
tables.push_back(mem);
|
||||
list.Add(tables.back(), &to_delete);
|
||||
newest_udts.push_back(write_ts);
|
||||
}
|
||||
|
||||
ASSERT_EQ(num_tables, list.NumNotFlushed());
|
||||
ASSERT_TRUE(list.IsFlushPending());
|
||||
std::vector<Slice> tables_newest_udts = list.GetTablesNewestUDT(num_tables);
|
||||
ASSERT_EQ(newest_udts.size(), tables_newest_udts.size());
|
||||
for (size_t i = 0; i < tables_newest_udts.size(); i++) {
|
||||
const Slice& table_newest_udt = tables_newest_udts[i];
|
||||
const Slice expected_newest_udt = newest_udts[i];
|
||||
ASSERT_EQ(expected_newest_udt, table_newest_udt);
|
||||
}
|
||||
|
||||
list.current()->Unref(&to_delete);
|
||||
for (MemTable* m : to_delete) {
|
||||
delete m;
|
||||
}
|
||||
to_delete.clear();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -262,7 +262,7 @@ void ProfileQueries(bool enabled_time = false) {
|
||||
for (const int i : keys) {
|
||||
if (i == kFlushFlag) {
|
||||
FlushOptions fo;
|
||||
ASSERT_OK(db->Flush(fo));
|
||||
db->Flush(fo);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1111,7 +1111,7 @@ TEST_F(PerfContextTest, MergeOperandCount) {
|
||||
verify();
|
||||
|
||||
// Verify counters when reading from table files
|
||||
ASSERT_OK(db->Flush(FlushOptions()));
|
||||
db->Flush(FlushOptions());
|
||||
|
||||
verify();
|
||||
}
|
||||
|
||||
Loaded 100 of 290 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user