mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 11:02:36 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fcd07e0f78 | ||
|
|
0efb576ca3 | ||
|
|
53649ce36b | ||
|
|
6b389b43ff | ||
|
|
49ce8a1064 | ||
|
|
b8d41c55e7 | ||
|
|
3b1ce121c2 | ||
|
|
17e3628cb5 | ||
|
|
8bcbed2e93 | ||
|
|
84a54e1e28 | ||
|
|
d3e015fe06 | ||
|
|
04cbc77b90 | ||
|
|
336a74db60 | ||
|
|
fb5c8c7ea3 | ||
|
|
39d33475da | ||
|
|
b059c5680e | ||
|
|
7780e98268 | ||
|
|
4e58cc6437 | ||
|
|
2f9ea8193f | ||
|
|
a9bd525b52 | ||
|
|
6d10f8d690 | ||
|
|
9202db1867 | ||
|
|
2222caec9e | ||
|
|
a660e074cd | ||
|
|
37064d631b | ||
|
|
65d71ee371 | ||
|
|
e7896f03ad | ||
|
|
8b8f6c63ef | ||
|
|
b3ffca0e29 | ||
|
|
9fb6851918 | ||
|
|
509947ce2c | ||
|
|
0ffc0c7db1 | ||
|
|
c6c683a0ca | ||
|
|
5ef92b8ea4 | ||
|
|
73d223c4e2 | ||
|
|
dfaf4dc111 | ||
|
|
c4c62c2304 | ||
|
|
e90e9825b4 | ||
|
|
f9b7877cf3 | ||
|
|
f337533b6f | ||
|
|
65cde19f40 | ||
|
|
e406c26c4e | ||
|
|
9af25a392b | ||
|
|
58f2a29fb4 | ||
|
|
c181667c4f | ||
|
|
c06309c832 | ||
|
|
16ae3548a2 | ||
|
|
2adef5367a | ||
|
|
92dc5f3e67 | ||
|
|
0ecfc4fbb4 | ||
|
|
2dab137182 | ||
|
|
a399bbc037 | ||
|
|
6979e9dc6a | ||
|
|
520c64fd2e | ||
|
|
19768a923a | ||
|
|
b48480cfd0 | ||
|
|
8505b26db1 | ||
|
|
2648e0a747 | ||
|
|
a42910537d | ||
|
|
8e1adab5ce | ||
|
|
4b013dcbed | ||
|
|
04225a2cfa | ||
|
|
0b057a7acc | ||
|
|
e0c45c15a7 | ||
|
|
2818a74b95 | ||
|
|
76402c034e | ||
|
|
b3fd3838d4 | ||
|
|
60df39e530 | ||
|
|
212b5bf826 | ||
|
|
e230e4d248 | ||
|
|
526f36b483 | ||
|
|
52be8f54f2 | ||
|
|
8ee009f0d8 | ||
|
|
0f141352d8 | ||
|
|
f2c9075d16 | ||
|
|
dc87847e65 | ||
|
|
0ff7665c95 | ||
|
|
ab15d33566 | ||
|
|
917fd87513 | ||
|
|
99b371b417 |
No files matched your search
+32
-4
@@ -54,6 +54,7 @@ commands:
|
||||
command: |
|
||||
echo "Installing CMake..."
|
||||
choco install cmake --installargs 'ADD_CMAKE_TO_PATH=System' -y
|
||||
choco install liberica8jdk -y
|
||||
mkdir $Env:THIRDPARTY_HOME
|
||||
cd $Env:THIRDPARTY_HOME
|
||||
echo "Building Snappy dependency..."
|
||||
@@ -67,9 +68,10 @@ commands:
|
||||
- run:
|
||||
name: "Build RocksDB"
|
||||
command: |
|
||||
$env:Path = $env:JAVA_HOME + ";" + $env:Path
|
||||
mkdir build
|
||||
cd build
|
||||
& $Env:CMAKE_BIN -G "$Env:CMAKE_GENERATOR" -DCMAKE_BUILD_TYPE=Debug -DOPTDBG=1 -DPORTABLE=1 -DSNAPPY=1 -DJNI=1 ..
|
||||
& $Env:CMAKE_BIN -G "$Env:CMAKE_GENERATOR" -DCMAKE_BUILD_TYPE=Debug -DOPTDBG=1 -DPORTABLE="$Env:CMAKE_PORTABLE" -DSNAPPY=1 -DJNI=1 ..
|
||||
cd ..
|
||||
echo "Building with VS version: $Env:CMAKE_GENERATOR"
|
||||
msbuild.exe build/rocksdb.sln -maxCpuCount -property:Configuration=Debug -property:Platform=x64
|
||||
@@ -227,6 +229,9 @@ executors:
|
||||
# $ docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -it zjay437/rocksdb:0.5 bash
|
||||
# option `--cap-add=SYS_PTRACE --security-opt seccomp=unconfined` is used to enable gdb to attach an existing process
|
||||
- image: zjay437/rocksdb:0.6
|
||||
linux-java-docker:
|
||||
docker:
|
||||
- image: evolvedbinary/rocksjava:centos6_x64-be
|
||||
|
||||
jobs:
|
||||
build-macos:
|
||||
@@ -563,6 +568,24 @@ jobs:
|
||||
no_output_timeout: 100m
|
||||
- post-steps
|
||||
|
||||
build-windows-vs2022-avx2:
|
||||
executor:
|
||||
name: win/server-2022
|
||||
size: 2xlarge
|
||||
environment:
|
||||
THIRDPARTY_HOME: C:/Users/circleci/thirdparty
|
||||
CMAKE_HOME: C:/Program Files/CMake
|
||||
CMAKE_BIN: C:/Program Files/CMake/bin/cmake.exe
|
||||
CTEST_BIN: C:/Program Files/CMake/bin/ctest.exe
|
||||
JAVA_HOME: C:/Program Files/BellSoft/LibericaJDK-8
|
||||
SNAPPY_HOME: C:/Users/circleci/thirdparty/snappy-1.1.8
|
||||
SNAPPY_INCLUDE: C:/Users/circleci/thirdparty/snappy-1.1.8;C:/Users/circleci/thirdparty/snappy-1.1.8/build
|
||||
SNAPPY_LIB_DEBUG: C:/Users/circleci/thirdparty/snappy-1.1.8/build/Debug/snappy.lib
|
||||
CMAKE_GENERATOR: Visual Studio 17 2022
|
||||
CMAKE_PORTABLE: AVX2
|
||||
steps:
|
||||
- windows-build-steps
|
||||
|
||||
build-windows-vs2022:
|
||||
executor:
|
||||
name: win/server-2022
|
||||
@@ -572,10 +595,12 @@ jobs:
|
||||
CMAKE_HOME: C:/Program Files/CMake
|
||||
CMAKE_BIN: C:/Program Files/CMake/bin/cmake.exe
|
||||
CTEST_BIN: C:/Program Files/CMake/bin/ctest.exe
|
||||
JAVA_HOME: C:/Program Files/BellSoft/LibericaJDK-8
|
||||
SNAPPY_HOME: C:/Users/circleci/thirdparty/snappy-1.1.8
|
||||
SNAPPY_INCLUDE: C:/Users/circleci/thirdparty/snappy-1.1.8;C:/Users/circleci/thirdparty/snappy-1.1.8/build
|
||||
SNAPPY_LIB_DEBUG: C:/Users/circleci/thirdparty/snappy-1.1.8/build/Debug/snappy.lib
|
||||
CMAKE_GENERATOR: Visual Studio 17 2022
|
||||
CMAKE_PORTABLE: 1
|
||||
steps:
|
||||
- windows-build-steps
|
||||
|
||||
@@ -588,10 +613,12 @@ jobs:
|
||||
CMAKE_HOME: C:/Program Files/CMake
|
||||
CMAKE_BIN: C:/Program Files/CMake/bin/cmake.exe
|
||||
CTEST_BIN: C:/Program Files/CMake/bin/ctest.exe
|
||||
JAVA_HOME: C:/Program Files/BellSoft/LibericaJDK-8
|
||||
SNAPPY_HOME: C:/Users/circleci/thirdparty/snappy-1.1.8
|
||||
SNAPPY_INCLUDE: C:/Users/circleci/thirdparty/snappy-1.1.8;C:/Users/circleci/thirdparty/snappy-1.1.8/build
|
||||
SNAPPY_LIB_DEBUG: C:/Users/circleci/thirdparty/snappy-1.1.8/build/Debug/snappy.lib
|
||||
CMAKE_GENERATOR: Visual Studio 16 2019
|
||||
CMAKE_PORTABLE: 1
|
||||
steps:
|
||||
- windows-build-steps
|
||||
|
||||
@@ -613,7 +640,7 @@ jobs:
|
||||
- post-steps
|
||||
|
||||
build-linux-java-pmd:
|
||||
machine:
|
||||
machine:
|
||||
image: ubuntu-2004:202111-02
|
||||
resource_class: large
|
||||
environment:
|
||||
@@ -634,7 +661,7 @@ jobs:
|
||||
- post-pmd-steps
|
||||
|
||||
build-linux-java-static:
|
||||
executor: linux-docker
|
||||
executor: linux-java-docker
|
||||
resource_class: large
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -647,7 +674,7 @@ jobs:
|
||||
which javac && javac -version
|
||||
- run:
|
||||
name: "Build RocksDBJava Static Library"
|
||||
command: make V=1 J=8 -j8 rocksdbjavastatic
|
||||
command: scl enable devtoolset-7 'make V=1 J=8 -j8 rocksdbjavastatic'
|
||||
- post-steps
|
||||
|
||||
build-macos-java:
|
||||
@@ -887,6 +914,7 @@ workflows:
|
||||
- build-linux-mini-crashtest
|
||||
jobs-windows:
|
||||
jobs:
|
||||
- build-windows-vs2022-avx2
|
||||
- build-windows-vs2022
|
||||
- build-windows-vs2019
|
||||
- build-cmake-mingw
|
||||
|
||||
+5
-3
@@ -772,6 +772,7 @@ set(SOURCES
|
||||
options/configurable.cc
|
||||
options/customizable.cc
|
||||
options/db_options.cc
|
||||
options/offpeak_time_info.cc
|
||||
options/options.cc
|
||||
options/options_helper.cc
|
||||
options/options_parser.cc
|
||||
@@ -1105,11 +1106,15 @@ set(BUILD_VERSION_CC ${CMAKE_BINARY_DIR}/build_version.cc)
|
||||
configure_file(util/build_version.cc.in ${BUILD_VERSION_CC} @ONLY)
|
||||
|
||||
add_library(${ROCKSDB_STATIC_LIB} STATIC ${SOURCES} ${BUILD_VERSION_CC})
|
||||
target_include_directories(${ROCKSDB_STATIC_LIB} PUBLIC
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
|
||||
target_link_libraries(${ROCKSDB_STATIC_LIB} PRIVATE
|
||||
${THIRDPARTY_LIBS} ${SYSTEM_LIBS})
|
||||
|
||||
if(ROCKSDB_BUILD_SHARED)
|
||||
add_library(${ROCKSDB_SHARED_LIB} SHARED ${SOURCES} ${BUILD_VERSION_CC})
|
||||
target_include_directories(${ROCKSDB_SHARED_LIB} PUBLIC
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
|
||||
target_link_libraries(${ROCKSDB_SHARED_LIB} PRIVATE
|
||||
${THIRDPARTY_LIBS} ${SYSTEM_LIBS})
|
||||
|
||||
@@ -1596,6 +1601,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>)
|
||||
+57
@@ -1,6 +1,63 @@
|
||||
# Rocksdb Change Log
|
||||
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
|
||||
|
||||
## 8.9.2 (12/12/2023)
|
||||
### Bug Fixes
|
||||
* A lookup by MultiGet in a TieredCache that goes to the local flash cache and finishes with very low latency, i.e before the subsequent call to WaitAll, is ignored, resulting in a false negative and a memory leak.
|
||||
* Sanitize the cache_opts->secondary_cache field in TieredCacheOptions. It can be mistakenly set by the user, which would conflict with nvm_sec_cache in TieredCacheOptions if not sanitized to nullptr.
|
||||
|
||||
## 8.9.1 (12/8/2023)
|
||||
### Bug Fixes
|
||||
* Avoid destroying the periodic task scheduler's default timer in order to prevent static destruction order issues.
|
||||
|
||||
## 8.9.0 (11/17/2023)
|
||||
### New Features
|
||||
* Add GetEntity() and PutEntity() API implementation for Attribute Group support. Through the use of Column Families, AttributeGroup enables users to logically group wide-column entities.
|
||||
|
||||
### Public API Changes
|
||||
* Added rocksdb_ratelimiter_create_auto_tuned API to create an auto-tuned GenericRateLimiter.
|
||||
* Added clipColumnFamily() to the Java API to clip the entries in the CF according to the range [begin_key, end_key).
|
||||
* Make the `EnableFileDeletion` API not default to force enabling. For users that rely on this default behavior and still
|
||||
want to continue to use force enabling, they need to explicitly pass a `true` to `EnableFileDeletion`.
|
||||
* Add new Cache APIs GetSecondaryCacheCapacity() and GetSecondaryCachePinnedUsage() to return the configured capacity, and cache reservation charged to the secondary cache.
|
||||
|
||||
### Behavior Changes
|
||||
* During off-peak hours defined by `daily_offpeak_time_utc`, the compaction picker will select a larger number of files for periodic compaction. This selection will include files that are projected to expire by the next off-peak start time, ensuring that these files are not chosen for periodic compaction outside of off-peak hours.
|
||||
* If an error occurs when writing to a trace file after `DB::StartTrace()`, the subsequent trace writes are skipped to avoid writing to a file that has previously seen error. In this case, `DB::EndTrace()` will also return a non-ok status with info about the error occured previously in its status message.
|
||||
* Deleting stale files upon recovery are delegated to SstFileManger if available so they can be rate limited.
|
||||
* Make RocksDB only call `TablePropertiesCollector::Finish()` once.
|
||||
* When `WAL_ttl_seconds > 0`, we now process archived WALs for deletion at least every `WAL_ttl_seconds / 2` seconds. Previously it could be less frequent in case of small `WAL_ttl_seconds` values when size-based expiration (`WAL_size_limit_MB > 0 `) was simultaneously enabled.
|
||||
|
||||
### Bug Fixes
|
||||
* Fixed a crash or assertion failure bug in experimental new HyperClockCache variant, especially when running with a SecondaryCache.
|
||||
* Fix a race between flush error recovery and db destruction that can lead to db crashing.
|
||||
* Fixed some bugs in the index builder/reader path for user-defined timestamps in Memtable only feature.
|
||||
|
||||
## 8.8.0 (10/23/2023)
|
||||
### New Features
|
||||
* Introduce AttributeGroup by adding the first AttributeGroup support API, MultiGetEntity(). Through the use of Column Families, AttributeGroup enables users to logically group wide-column entities. More APIs to support AttributeGroup will come soon, including GetEntity, PutEntity, and others.
|
||||
* Added new tickers `rocksdb.fifo.{max.size|ttl}.compactions` to count FIFO compactions that drop files for different reasons
|
||||
* Add an experimental offpeak duration awareness by setting `DBOptions::daily_offpeak_time_utc` in "HH:mm-HH:mm" format. This information will be used for resource optimization in the future
|
||||
* Users can now change the max bytes granted in a single refill period (i.e, burst) during runtime by `SetSingleBurstBytes()` for RocksDB rate limiter
|
||||
|
||||
### Public API Changes
|
||||
* The default value of `DBOptions::fail_if_options_file_error` changed from `false` to `true`. Operations that set in-memory options (e.g., `DB::Open*()`, `DB::SetOptions()`, `DB::CreateColumnFamily*()`, and `DB::DropColumnFamily()`) but fail to persist the change will now return a non-OK `Status` by default.
|
||||
|
||||
### Behavior Changes
|
||||
* For non direct IO, eliminate the file system prefetching attempt for compaction read when `Options::compaction_readahead_size` is 0
|
||||
* During a write stop, writes now block on in-progress recovery attempts
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a bug in auto_readahead_size where first_internal_key of index blocks wasn't copied properly resulting in corruption error when first_internal_key was used for comparison.
|
||||
* 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()`
|
||||
* Add bounds check in WBWIIteratorImpl and make BaseDeltaIterator, WriteUnpreparedTxn and WritePreparedTxn respect the upper bound and lower bound in ReadOption. See 11680.
|
||||
* Fixed the handling of wide-column base values in the `max_successive_merges` logic.
|
||||
* Fixed a rare race bug involving a concurrent combination of Create/DropColumnFamily and/or Set(DB)Options that could lead to inconsistency between (a) the DB's reported options state, (b) the DB options in effect, and (c) the latest persisted OPTIONS file.
|
||||
* Fixed a possible underflow when computing the compressed secondary cache share of memory reservations while updating the compressed secondary to total block cache ratio.
|
||||
|
||||
### Performance Improvements
|
||||
* Improved the I/O efficiency of DB::Open a new DB with `create_missing_column_families=true` and many column families.
|
||||
|
||||
## 8.7.0 (09/22/2023)
|
||||
### New Features
|
||||
* Added an experimental new "automatic" variant of HyperClockCache that does not require a prior estimate of the average size of cache entries. This variant is activated when HyperClockCacheOptions::estimated\_entry\_charge = 0 and has essentially the same concurrency benefits as the existing HyperClockCache.
|
||||
|
||||
@@ -539,7 +539,7 @@ endif
|
||||
|
||||
ifdef USE_CLANG
|
||||
# Used by some teams in Facebook
|
||||
WARNING_FLAGS += -Wshift-sign-overflow -Wambiguous-reversed-operator
|
||||
WARNING_FLAGS += -Wshift-sign-overflow -Wambiguous-reversed-operator -Wimplicit-fallthrough
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), OS_OPENBSD)
|
||||
@@ -994,7 +994,7 @@ endif
|
||||
|
||||
.PHONY: check_0
|
||||
check_0:
|
||||
printf '%s\n' '' \
|
||||
@printf '%s\n' '' \
|
||||
'To monitor subtest <duration,pass/fail,name>,' \
|
||||
' run "make watch-log" in a separate window' ''; \
|
||||
{ \
|
||||
@@ -1016,7 +1016,7 @@ valgrind-exclude-regexp = InlineSkipTest.ConcurrentInsert|TransactionStressTest.
|
||||
.PHONY: valgrind_check_0
|
||||
valgrind_check_0: test_log_prefix := valgrind_
|
||||
valgrind_check_0:
|
||||
printf '%s\n' '' \
|
||||
@printf '%s\n' '' \
|
||||
'To monitor subtest <duration,pass/fail,name>,' \
|
||||
' run "make watch-log" in a separate window' ''; \
|
||||
{ \
|
||||
@@ -2107,7 +2107,7 @@ LZ4_VER ?= 1.9.4
|
||||
LZ4_SHA256 ?= 0b0e3aa07c8c063ddf40b082bdf7e37a1562bda40a0ff5272957f3e987e0e54b
|
||||
LZ4_DOWNLOAD_BASE ?= https://github.com/lz4/lz4/archive
|
||||
ZSTD_VER ?= 1.5.5
|
||||
ZSTD_SHA256 ?= 9c4396cc829cfae319a6e2615202e82aad41372073482fce286fac78646d3ee4
|
||||
ZSTD_SHA256 ?= 98e9c3d949d1b924e28e01eccb7deed865eefebf25c2f21c702e5cd5b63b85e1
|
||||
ZSTD_DOWNLOAD_BASE ?= https://github.com/facebook/zstd/archive
|
||||
CURL_SSL_OPTS ?= --tlsv1
|
||||
|
||||
|
||||
+2
-1
@@ -5,4 +5,5 @@ This is the list of all known third-party plugins for RocksDB. If something is m
|
||||
* [ZenFS](https://github.com/westerndigitalcorporation/zenfs): a file system for zoned block devices
|
||||
* [RADOS](https://github.com/riversand963/rocksdb-rados-env): an Env used for interacting with RADOS. Migrated from RocksDB main repo.
|
||||
* [PMEM](https://github.com/pmem/pmem-rocksdb-plugin): a collection of plugins to enable Persistent Memory on RocksDB.
|
||||
* [IPPCP](https://github.com/intel/ippcp-plugin-rocksdb): a plugin to enable encryption on RocksDB based on Intel optimized open source IPP-Crypto library.
|
||||
* [IPPCP](https://github.com/intel/ippcp-plugin-rocksdb): a plugin to enable encryption on RocksDB based on Intel optimized open source IPP-Crypto library.
|
||||
* [encfs](https://github.com/pegasus-kv/encfs): a plugin to enable encryption on RocksDB based on OpenSSL library.
|
||||
@@ -163,6 +163,7 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"options/configurable.cc",
|
||||
"options/customizable.cc",
|
||||
"options/db_options.cc",
|
||||
"options/offpeak_time_info.cc",
|
||||
"options/options.cc",
|
||||
"options/options_helper.cc",
|
||||
"options/options_parser.cc",
|
||||
@@ -5620,3 +5621,5 @@ cpp_unittest_wrapper(name="write_unprepared_transaction_test",
|
||||
deps=[":rocksdb_test_lib"],
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
export_file(name = "tools/db_crashtest.py")
|
||||
@@ -152,6 +152,9 @@ LzLabs is using RocksDB as a storage engine in their multi-database distributed
|
||||
## ArangoDB
|
||||
[ArangoDB](https://www.arangodb.com/) is a native multi-model database with flexible data models for documents, graphs, and key-values, for building high performance applications using a convenient SQL-like query language or JavaScript extensions. It uses RocksDB as its storage engine.
|
||||
|
||||
## Qdrant
|
||||
[Qdrant](https://qdrant.tech/) is an open source vector database, it [uses](https://qdrant.tech/documentation/concepts/storage/) RocksDB as its persistent storage.
|
||||
|
||||
## Milvus
|
||||
[Milvus](https://milvus.io/) is an open source vector database for unstructured data. It uses RocksDB not only as one of the supported kv storage engines, but also as a message queue.
|
||||
|
||||
|
||||
@@ -303,6 +303,7 @@ def generate_targets(repo_path, deps_map):
|
||||
deps=json.dumps(deps["extra_deps"] + [":rocksdb_test_lib"]),
|
||||
extra_compiler_flags=json.dumps(deps["extra_compiler_flags"]),
|
||||
)
|
||||
TARGETS.export_file("tools/db_crashtest.py")
|
||||
|
||||
print(ColorString.info("Generated TARGETS Summary:"))
|
||||
print(ColorString.info("- %d libs" % TARGETS.total_lib))
|
||||
|
||||
@@ -148,3 +148,9 @@ add_c_test_wrapper()
|
||||
).encode("utf-8")
|
||||
)
|
||||
self.total_test = self.total_test + 1
|
||||
|
||||
def export_file(self, name):
|
||||
with open(self.path, "a") as targets_file:
|
||||
targets_file.write(
|
||||
targets_cfg.export_file_template.format(name=name)
|
||||
)
|
||||
@@ -37,3 +37,7 @@ fancy_bench_template = """
|
||||
fancy_bench_wrapper(suite_name="{name}", binary_to_bench_to_metric_list_map={bench_config}, slow={slow}, expected_runtime={expected_runtime}, sl_iterations={sl_iterations}, regression_threshold={regression_threshold})
|
||||
|
||||
"""
|
||||
|
||||
export_file_template = """
|
||||
export_file(name = "{name}")
|
||||
"""
|
||||
Vendored
+88
-40
@@ -63,11 +63,22 @@ DEFINE_int32(
|
||||
DEFINE_uint32(skew, 5, "Degree of skew in key selection. 0 = no skew");
|
||||
DEFINE_bool(populate_cache, true, "Populate cache before operations");
|
||||
|
||||
DEFINE_uint32(lookup_insert_percent, 87,
|
||||
DEFINE_double(pinned_ratio, 0.25,
|
||||
"Keep roughly this portion of entries pinned in cache.");
|
||||
DEFINE_double(
|
||||
vary_capacity_ratio, 0.0,
|
||||
"If greater than 0.0, will periodically vary the capacity between this "
|
||||
"ratio less than full size and full size. If vary_capacity_ratio + "
|
||||
"pinned_ratio is close to or exceeds 1.0, the cache might thrash.");
|
||||
|
||||
DEFINE_uint32(lookup_insert_percent, 82,
|
||||
"Ratio of lookup (+ insert on not found) to total workload "
|
||||
"(expressed as a percentage)");
|
||||
DEFINE_uint32(insert_percent, 2,
|
||||
"Ratio of insert to total workload (expressed as a percentage)");
|
||||
DEFINE_uint32(blind_insert_percent, 5,
|
||||
"Ratio of insert without keeping handle to total workload "
|
||||
"(expressed as a percentage)");
|
||||
DEFINE_uint32(lookup_percent, 10,
|
||||
"Ratio of lookup to total workload (expressed as a percentage)");
|
||||
DEFINE_uint32(erase_percent, 1,
|
||||
@@ -101,7 +112,6 @@ 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;
|
||||
|
||||
DEFINE_string(cache_type, "lru_cache", "Type of block cache.");
|
||||
|
||||
@@ -200,16 +210,19 @@ class SharedState {
|
||||
|
||||
bool Started() const { return start_; }
|
||||
|
||||
void AddLookupStats(uint64_t hits, uint64_t misses) {
|
||||
void AddLookupStats(uint64_t hits, uint64_t misses, size_t pinned_count) {
|
||||
MutexLock l(&mu_);
|
||||
lookup_count_ += hits + misses;
|
||||
lookup_hits_ += hits;
|
||||
pinned_count_ += pinned_count;
|
||||
}
|
||||
|
||||
double GetLookupHitRatio() const {
|
||||
return 1.0 * lookup_hits_ / lookup_count_;
|
||||
}
|
||||
|
||||
size_t GetPinnedCount() const { return pinned_count_; }
|
||||
|
||||
private:
|
||||
port::Mutex mu_;
|
||||
port::CondVar cv_;
|
||||
@@ -221,6 +234,7 @@ class SharedState {
|
||||
uint64_t num_done_ = 0;
|
||||
uint64_t lookup_count_ = 0;
|
||||
uint64_t lookup_hits_ = 0;
|
||||
size_t pinned_count_ = 0;
|
||||
};
|
||||
|
||||
// Per-thread state for concurrent executions of the same benchmark.
|
||||
@@ -313,6 +327,28 @@ Cache::CacheItemHelper helper2(CacheEntryRole::kIndexBlock, DeleteFn, SizeFn,
|
||||
Cache::CacheItemHelper helper3_wos(CacheEntryRole::kFilterBlock, DeleteFn);
|
||||
Cache::CacheItemHelper helper3(CacheEntryRole::kFilterBlock, DeleteFn, SizeFn,
|
||||
SaveToFn, CreateFn, &helper3_wos);
|
||||
|
||||
void ConfigureSecondaryCache(ShardedCacheOptions& opts) {
|
||||
if (!FLAGS_secondary_cache_uri.empty()) {
|
||||
std::shared_ptr<SecondaryCache> secondary_cache;
|
||||
Status s = SecondaryCache::CreateFromString(
|
||||
ConfigOptions(), FLAGS_secondary_cache_uri, &secondary_cache);
|
||||
if (secondary_cache == nullptr) {
|
||||
fprintf(stderr,
|
||||
"No secondary cache registered matching string: %s status=%s\n",
|
||||
FLAGS_secondary_cache_uri.c_str(), s.ToString().c_str());
|
||||
exit(1);
|
||||
}
|
||||
opts.secondary_cache = secondary_cache;
|
||||
}
|
||||
}
|
||||
|
||||
ShardedCacheBase* AsShardedCache(Cache* c) {
|
||||
if (!FLAGS_secondary_cache_uri.empty()) {
|
||||
c = static_cast_with_check<CacheWrapper>(c)->GetTarget().get();
|
||||
}
|
||||
return static_cast_with_check<ShardedCacheBase>(c);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
class CacheBench {
|
||||
@@ -327,7 +363,9 @@ class CacheBench {
|
||||
FLAGS_lookup_insert_percent),
|
||||
insert_threshold_(lookup_insert_threshold_ +
|
||||
kHundredthUint64 * FLAGS_insert_percent),
|
||||
lookup_threshold_(insert_threshold_ +
|
||||
blind_insert_threshold_(insert_threshold_ +
|
||||
kHundredthUint64 * FLAGS_blind_insert_percent),
|
||||
lookup_threshold_(blind_insert_threshold_ +
|
||||
kHundredthUint64 * FLAGS_lookup_percent),
|
||||
erase_threshold_(lookup_threshold_ +
|
||||
kHundredthUint64 * FLAGS_erase_percent) {
|
||||
@@ -366,6 +404,7 @@ class CacheBench {
|
||||
fprintf(stderr, "Cache type not supported.\n");
|
||||
exit(1);
|
||||
}
|
||||
ConfigureSecondaryCache(opts);
|
||||
cache_ = opts.MakeSharedCache();
|
||||
} else if (FLAGS_cache_type == "lru_cache") {
|
||||
LRUCacheOptions opts(FLAGS_cache_size, FLAGS_num_shard_bits,
|
||||
@@ -373,19 +412,7 @@ class CacheBench {
|
||||
0.5 /* high_pri_pool_ratio */);
|
||||
opts.hash_seed = BitwiseAnd(FLAGS_seed, INT32_MAX);
|
||||
opts.memory_allocator = allocator;
|
||||
if (!FLAGS_secondary_cache_uri.empty()) {
|
||||
Status s = SecondaryCache::CreateFromString(
|
||||
ConfigOptions(), FLAGS_secondary_cache_uri, &secondary_cache);
|
||||
if (secondary_cache == nullptr) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"No secondary cache registered matching string: %s status=%s\n",
|
||||
FLAGS_secondary_cache_uri.c_str(), s.ToString().c_str());
|
||||
exit(1);
|
||||
}
|
||||
opts.secondary_cache = secondary_cache;
|
||||
}
|
||||
|
||||
ConfigureSecondaryCache(opts);
|
||||
cache_ = NewLRUCache(opts);
|
||||
} else {
|
||||
fprintf(stderr, "Cache type not supported.\n");
|
||||
@@ -505,6 +532,8 @@ class CacheBench {
|
||||
size_t slot = cache_->GetTableAddressCount();
|
||||
printf("Final load factor: %g (%zu / %zu)\n", 1.0 * occ / slot, occ, slot);
|
||||
|
||||
printf("Final pinned count: %zu\n", shared.GetPinnedCount());
|
||||
|
||||
if (FLAGS_histograms) {
|
||||
printf("\nOperation latency (ns):\n");
|
||||
HistogramImpl combined;
|
||||
@@ -536,6 +565,7 @@ class CacheBench {
|
||||
// Cumulative thresholds in the space of a random uint64_t
|
||||
const uint64_t lookup_insert_threshold_;
|
||||
const uint64_t insert_threshold_;
|
||||
const uint64_t blind_insert_threshold_;
|
||||
const uint64_t lookup_threshold_;
|
||||
const uint64_t erase_threshold_;
|
||||
|
||||
@@ -651,29 +681,44 @@ class CacheBench {
|
||||
uint64_t lookup_misses = 0;
|
||||
uint64_t lookup_hits = 0;
|
||||
// To hold handles for a non-trivial amount of time
|
||||
Cache::Handle* handle = nullptr;
|
||||
std::deque<Cache::Handle*> pinned;
|
||||
size_t total_pin_count = static_cast<size_t>(
|
||||
(FLAGS_cache_size * FLAGS_pinned_ratio) / FLAGS_value_bytes + 0.999999);
|
||||
// For this thread. Some round up, some round down, as appropriate
|
||||
size_t pin_count = (total_pin_count + thread->tid) / FLAGS_threads;
|
||||
|
||||
KeyGen gen;
|
||||
const auto clock = SystemClock::Default().get();
|
||||
uint64_t start_time = clock->NowMicros();
|
||||
StopWatchNano timer(clock);
|
||||
auto system_clock = SystemClock::Default();
|
||||
size_t steps_to_next_capacity_change = 0;
|
||||
|
||||
for (uint64_t i = 0; i < FLAGS_ops_per_thread; i++) {
|
||||
Slice key = gen.GetRand(thread->rnd, max_key_, FLAGS_skew);
|
||||
uint64_t random_op = thread->rnd.Next();
|
||||
|
||||
if (FLAGS_vary_capacity_ratio > 0.0 && thread->tid == 0) {
|
||||
if (steps_to_next_capacity_change == 0) {
|
||||
double cut_ratio = static_cast<double>(thread->rnd.Next()) /
|
||||
static_cast<double>(UINT64_MAX) *
|
||||
FLAGS_vary_capacity_ratio;
|
||||
cache_->SetCapacity(FLAGS_cache_size * (1.0 - cut_ratio));
|
||||
steps_to_next_capacity_change =
|
||||
static_cast<size_t>(FLAGS_ops_per_thread / 100);
|
||||
} else {
|
||||
--steps_to_next_capacity_change;
|
||||
}
|
||||
}
|
||||
|
||||
if (FLAGS_histograms) {
|
||||
timer.Start();
|
||||
}
|
||||
|
||||
if (random_op < lookup_insert_threshold_) {
|
||||
if (handle) {
|
||||
cache_->Release(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
// do lookup
|
||||
handle = cache_->Lookup(key, &helper2, /*context*/ nullptr,
|
||||
Cache::Priority::LOW);
|
||||
auto handle = cache_->Lookup(key, &helper2, /*context*/ nullptr,
|
||||
Cache::Priority::LOW);
|
||||
if (handle) {
|
||||
++lookup_hits;
|
||||
if (!FLAGS_lean) {
|
||||
@@ -681,32 +726,31 @@ class CacheBench {
|
||||
result += NPHash64(static_cast<char*>(cache_->Value(handle)),
|
||||
FLAGS_value_bytes);
|
||||
}
|
||||
pinned.push_back(handle);
|
||||
} else {
|
||||
++lookup_misses;
|
||||
// do insert
|
||||
Status s = cache_->Insert(
|
||||
key, createValue(thread->rnd, cache_->memory_allocator()),
|
||||
&helper2, FLAGS_value_bytes, &handle);
|
||||
&helper2, FLAGS_value_bytes, &pinned.emplace_back());
|
||||
assert(s.ok());
|
||||
}
|
||||
} else if (random_op < insert_threshold_) {
|
||||
if (handle) {
|
||||
cache_->Release(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
// do insert
|
||||
Status s = cache_->Insert(
|
||||
key, createValue(thread->rnd, cache_->memory_allocator()), &helper3,
|
||||
FLAGS_value_bytes, &handle);
|
||||
FLAGS_value_bytes, &pinned.emplace_back());
|
||||
assert(s.ok());
|
||||
} else if (random_op < blind_insert_threshold_) {
|
||||
// insert without keeping a handle
|
||||
Status s = cache_->Insert(
|
||||
key, createValue(thread->rnd, cache_->memory_allocator()), &helper3,
|
||||
FLAGS_value_bytes);
|
||||
assert(s.ok());
|
||||
} else if (random_op < lookup_threshold_) {
|
||||
if (handle) {
|
||||
cache_->Release(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
// do lookup
|
||||
handle = cache_->Lookup(key, &helper2, /*context*/ nullptr,
|
||||
Cache::Priority::LOW);
|
||||
auto handle = cache_->Lookup(key, &helper2, /*context*/ nullptr,
|
||||
Cache::Priority::LOW);
|
||||
if (handle) {
|
||||
++lookup_hits;
|
||||
if (!FLAGS_lean) {
|
||||
@@ -714,6 +758,7 @@ class CacheBench {
|
||||
result += NPHash64(static_cast<char*>(cache_->Value(handle)),
|
||||
FLAGS_value_bytes);
|
||||
}
|
||||
pinned.push_back(handle);
|
||||
} else {
|
||||
++lookup_misses;
|
||||
}
|
||||
@@ -727,7 +772,6 @@ class CacheBench {
|
||||
if (FLAGS_histograms) {
|
||||
thread->latency_ns_hist.Add(timer.ElapsedNanos());
|
||||
}
|
||||
thread->shared->AddLookupStats(lookup_hits, lookup_misses);
|
||||
if (FLAGS_usleep > 0) {
|
||||
unsigned us =
|
||||
static_cast<unsigned>(thread->rnd.Uniform(FLAGS_usleep + 1));
|
||||
@@ -735,12 +779,17 @@ class CacheBench {
|
||||
system_clock->SleepForMicroseconds(us);
|
||||
}
|
||||
}
|
||||
while (pinned.size() > pin_count) {
|
||||
cache_->Release(pinned.front());
|
||||
pinned.pop_front();
|
||||
}
|
||||
}
|
||||
if (FLAGS_early_exit) {
|
||||
MutexLock l(thread->shared->GetMutex());
|
||||
exit(0);
|
||||
}
|
||||
if (handle) {
|
||||
thread->shared->AddLookupStats(lookup_hits, lookup_misses, pinned.size());
|
||||
for (auto handle : pinned) {
|
||||
cache_->Release(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
@@ -769,8 +818,7 @@ class CacheBench {
|
||||
printf("Cache size : %s\n",
|
||||
BytesToHumanString(FLAGS_cache_size).c_str());
|
||||
printf("Num shard bits : %d\n",
|
||||
static_cast_with_check<ShardedCacheBase>(cache_.get())
|
||||
->GetNumShardBits());
|
||||
AsShardedCache(cache_.get())->GetNumShardBits());
|
||||
printf("Max key : %" PRIu64 "\n", max_key_);
|
||||
printf("Resident ratio : %g\n", FLAGS_resident_ratio);
|
||||
printf("Skew degree : %u\n", FLAGS_skew);
|
||||
|
||||
Vendored
+288
-353
File diff suppressed because it is too large.
Load diff
Vendored
+39
-32
@@ -24,6 +24,7 @@
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
#include "util/atomic.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/math.h"
|
||||
|
||||
@@ -368,7 +369,7 @@ struct ClockHandle : public ClockHandleBasicData {
|
||||
// TODO: make these coundown values tuning parameters for eviction?
|
||||
|
||||
// See above. Mutable for read reference counting.
|
||||
mutable std::atomic<uint64_t> meta{};
|
||||
mutable AcqRelAtomic<uint64_t> meta{};
|
||||
}; // struct ClockHandle
|
||||
|
||||
class BaseClockTable {
|
||||
@@ -395,19 +396,15 @@ class BaseClockTable {
|
||||
|
||||
void Ref(ClockHandle& handle);
|
||||
|
||||
size_t GetOccupancy() const {
|
||||
return occupancy_.load(std::memory_order_relaxed);
|
||||
}
|
||||
size_t GetOccupancy() const { return occupancy_.LoadRelaxed(); }
|
||||
|
||||
size_t GetUsage() const { return usage_.load(std::memory_order_relaxed); }
|
||||
size_t GetUsage() const { return usage_.LoadRelaxed(); }
|
||||
|
||||
size_t GetStandaloneUsage() const {
|
||||
return standalone_usage_.load(std::memory_order_relaxed);
|
||||
}
|
||||
size_t GetStandaloneUsage() const { return standalone_usage_.LoadRelaxed(); }
|
||||
|
||||
uint32_t GetHashSeed() const { return hash_seed_; }
|
||||
|
||||
uint64_t GetYieldCount() const { return yield_count_.load(); }
|
||||
uint64_t GetYieldCount() const { return yield_count_.LoadRelaxed(); }
|
||||
|
||||
struct EvictionData {
|
||||
size_t freed_charge = 0;
|
||||
@@ -460,21 +457,23 @@ class BaseClockTable {
|
||||
// operations in ClockCacheShard.
|
||||
|
||||
// Clock algorithm sweep pointer.
|
||||
std::atomic<uint64_t> clock_pointer_{};
|
||||
// (Relaxed: only needs to be consistent with itself.)
|
||||
RelaxedAtomic<uint64_t> clock_pointer_{};
|
||||
|
||||
// Counter for number of times we yield to wait on another thread.
|
||||
std::atomic<uint64_t> yield_count_{};
|
||||
// (Relaxed: a simple stat counter.)
|
||||
RelaxedAtomic<uint64_t> yield_count_{};
|
||||
|
||||
// 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_{};
|
||||
AcqRelAtomic<size_t> occupancy_{};
|
||||
|
||||
// Memory usage by entries tracked by the cache (including standalone)
|
||||
std::atomic<size_t> usage_{};
|
||||
AcqRelAtomic<size_t> usage_{};
|
||||
|
||||
// Part of usage by standalone entries (not in table)
|
||||
std::atomic<size_t> standalone_usage_{};
|
||||
AcqRelAtomic<size_t> standalone_usage_{};
|
||||
|
||||
ALIGN_AS(CACHE_LINE_SIZE)
|
||||
const CacheMetadataChargePolicy metadata_charge_policy_;
|
||||
@@ -500,7 +499,11 @@ class FixedHyperClockTable : public BaseClockTable {
|
||||
struct ALIGN_AS(64U) HandleImpl : public ClockHandle {
|
||||
// The number of elements that hash to this slot or a lower one, but wind
|
||||
// up in this slot or a higher one.
|
||||
std::atomic<uint32_t> displacements{};
|
||||
// (Relaxed: within a Cache op, does not need consistency with entries
|
||||
// inserted/removed during that op. For example, a Lookup() that
|
||||
// happens-after an Insert() will see an appropriate displacements value
|
||||
// for the entry to be in a published state.)
|
||||
RelaxedAtomic<uint32_t> displacements{};
|
||||
|
||||
// Whether this is a "deteched" handle that is independently allocated
|
||||
// with `new` (so must be deleted with `delete`).
|
||||
@@ -787,17 +790,16 @@ class AutoHyperClockTable : public BaseClockTable {
|
||||
|
||||
// See above. The head pointer is logically independent of the rest of
|
||||
// the entry, including the chain next pointer.
|
||||
std::atomic<uint64_t> head_next_with_shift{kUnusedMarker};
|
||||
std::atomic<uint64_t> chain_next_with_shift{kUnusedMarker};
|
||||
AcqRelAtomic<uint64_t> head_next_with_shift{kUnusedMarker};
|
||||
AcqRelAtomic<uint64_t> chain_next_with_shift{kUnusedMarker};
|
||||
|
||||
// For supporting CreateStandalone and some fallback cases.
|
||||
inline bool IsStandalone() const {
|
||||
return head_next_with_shift.load(std::memory_order_acquire) ==
|
||||
kStandaloneMarker;
|
||||
return head_next_with_shift.Load() == kStandaloneMarker;
|
||||
}
|
||||
|
||||
inline void SetStandalone() {
|
||||
head_next_with_shift.store(kStandaloneMarker, std::memory_order_release);
|
||||
head_next_with_shift.Store(kStandaloneMarker);
|
||||
}
|
||||
}; // struct HandleImpl
|
||||
|
||||
@@ -939,22 +941,25 @@ class AutoHyperClockTable : public BaseClockTable {
|
||||
// log time to find the correct chain, but normally this value enables
|
||||
// readers to find the correct chain on the first try.
|
||||
//
|
||||
// NOTES: length_info_ is only updated at the end of a Grow operation,
|
||||
// so that waiting in Grow operations isn't done while entries are pinned
|
||||
// for internal operation purposes. Thus, Lookup and Insert have to
|
||||
// detect and support cases where length_info hasn't caught up to updated
|
||||
// chains. Winning grow thread is the one that transitions
|
||||
// head_next_with_shift from zeros. Grow threads can spin/yield wait for
|
||||
// preconditions and postconditions to be met.
|
||||
std::atomic<uint64_t> length_info_;
|
||||
// To maximize parallelization of Grow() operations, this field is only
|
||||
// updated opportunistically after Grow() operations and in DoInsert() where
|
||||
// it is found to be out-of-date. See CatchUpLengthInfoNoWait().
|
||||
AcqRelAtomic<uint64_t> length_info_;
|
||||
|
||||
// An already-computed version of the usable length times the max load
|
||||
// factor. Could be slightly out of date but GrowIfNeeded()/Grow() handle
|
||||
// that internally.
|
||||
std::atomic<size_t> occupancy_limit_;
|
||||
// (Relaxed: allowed to lag behind length_info_ by a little)
|
||||
RelaxedAtomic<size_t> occupancy_limit_;
|
||||
|
||||
// The next index to use from array_ upon the next Grow(). Might be ahead of
|
||||
// length_info_.
|
||||
// (Relaxed: self-contained source of truth for next grow home)
|
||||
RelaxedAtomic<size_t> grow_frontier_;
|
||||
|
||||
// See explanation in AutoHyperClockTable::Evict
|
||||
std::atomic<size_t> clock_pointer_mask_;
|
||||
// (Relaxed: allowed to lag behind clock_pointer_ and length_info_ state)
|
||||
RelaxedAtomic<size_t> clock_pointer_mask_;
|
||||
}; // class AutoHyperClockTable
|
||||
|
||||
// A single shard of sharded cache.
|
||||
@@ -1070,10 +1075,12 @@ class ALIGN_AS(CACHE_LINE_SIZE) ClockCacheShard final : public CacheShardBase {
|
||||
Table table_;
|
||||
|
||||
// Maximum total charge of all elements stored in the table.
|
||||
std::atomic<size_t> capacity_;
|
||||
// (Relaxed: eventual consistency/update is OK)
|
||||
RelaxedAtomic<size_t> capacity_;
|
||||
|
||||
// Whether to reject insertion if cache reaches its full capacity.
|
||||
std::atomic<bool> strict_capacity_limit_;
|
||||
// (Relaxed: eventual consistency/update is OK)
|
||||
RelaxedAtomic<bool> strict_capacity_limit_;
|
||||
}; // class ClockCacheShard
|
||||
|
||||
template <class Table>
|
||||
|
||||
+49
-22
@@ -1224,13 +1224,11 @@ TEST_P(CompressedSecCacheTestWithTiered, DynamicUpdate) {
|
||||
ASSERT_OK(sec_cache->GetCapacity(sec_capacity));
|
||||
ASSERT_EQ(sec_capacity, 0);
|
||||
|
||||
ASSERT_NOK(UpdateTieredCache(tiered_cache, -1, 0.3));
|
||||
// Only check usage for LRU cache. HCC shows a 64KB usage for some reason
|
||||
if (std::get<0>(GetParam()) == PrimaryCacheType::kCacheTypeLRU) {
|
||||
ASSERT_EQ(GetCache()->GetUsage(), 0);
|
||||
}
|
||||
ASSERT_OK(UpdateTieredCache(tiered_cache, -1, 0.3));
|
||||
EXPECT_PRED3(CacheUsageWithinBounds, GetCache()->GetUsage(), (30 << 20),
|
||||
GetPercent(30 << 20, 1));
|
||||
ASSERT_OK(sec_cache->GetCapacity(sec_capacity));
|
||||
ASSERT_EQ(sec_capacity, 0);
|
||||
ASSERT_EQ(sec_capacity, (30 << 20));
|
||||
}
|
||||
|
||||
TEST_P(CompressedSecCacheTestWithTiered, DynamicUpdateWithReservation) {
|
||||
@@ -1276,6 +1274,13 @@ TEST_P(CompressedSecCacheTestWithTiered, DynamicUpdateWithReservation) {
|
||||
ASSERT_OK(sec_cache->GetCapacity(sec_capacity));
|
||||
ASSERT_EQ(sec_capacity, (30 << 20));
|
||||
|
||||
ASSERT_OK(tiered_cache->GetSecondaryCacheCapacity(sec_capacity));
|
||||
ASSERT_EQ(sec_capacity, 30 << 20);
|
||||
size_t sec_usage;
|
||||
ASSERT_OK(tiered_cache->GetSecondaryCachePinnedUsage(sec_usage));
|
||||
EXPECT_PRED3(CacheUsageWithinBounds, sec_usage, 3 << 20,
|
||||
GetPercent(3 << 20, 1));
|
||||
|
||||
ASSERT_OK(UpdateTieredCache(tiered_cache, -1, 0.39));
|
||||
EXPECT_PRED3(CacheUsageWithinBounds, GetCache()->GetUsage(), (45 << 20),
|
||||
GetPercent(45 << 20, 1));
|
||||
@@ -1309,28 +1314,50 @@ TEST_P(CompressedSecCacheTestWithTiered, DynamicUpdateWithReservation) {
|
||||
ASSERT_OK(sec_cache->GetCapacity(sec_capacity));
|
||||
ASSERT_EQ(sec_capacity, 0);
|
||||
|
||||
ASSERT_OK(UpdateTieredCache(tiered_cache, -1, 0.3));
|
||||
EXPECT_PRED3(CacheUsageWithinBounds, GetCache()->GetUsage(), (37 << 20),
|
||||
GetPercent(37 << 20, 1));
|
||||
EXPECT_PRED3(CacheUsageWithinBounds, sec_cache->TEST_GetUsage(), (3 << 20),
|
||||
GetPercent(3 << 20, 1));
|
||||
ASSERT_OK(sec_cache->GetCapacity(sec_capacity));
|
||||
ASSERT_EQ(sec_capacity, 30 << 20);
|
||||
|
||||
ASSERT_OK(cache_res_mgr()->UpdateCacheReservation(0));
|
||||
}
|
||||
|
||||
TEST_P(CompressedSecCacheTestWithTiered,
|
||||
DynamicUpdateWithReservationUnderflow) {
|
||||
TEST_P(CompressedSecCacheTestWithTiered, ReservationOverCapacity) {
|
||||
CompressedSecondaryCache* sec_cache =
|
||||
reinterpret_cast<CompressedSecondaryCache*>(GetSecondaryCache());
|
||||
std::shared_ptr<Cache> tiered_cache = GetTieredCache();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"CacheWithSecondaryAdapter::Release:ChargeSecCache1",
|
||||
"CacheWithSecondaryAdapter::UpdateCacheReservationRatio:Begin"},
|
||||
{"CacheWithSecondaryAdapter::UpdateCacheReservationRatio:End",
|
||||
"CacheWithSecondaryAdapter::Release:ChargeSecCache2"}});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
port::Thread reserve_release_thread([&]() {
|
||||
EXPECT_EQ(cache_res_mgr()->UpdateCacheReservation(50), Status::OK());
|
||||
EXPECT_EQ(cache_res_mgr()->UpdateCacheReservation(0), Status::OK());
|
||||
});
|
||||
ASSERT_OK(UpdateTieredCache(tiered_cache, 100 << 20, 0.01));
|
||||
reserve_release_thread.join();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ASSERT_OK(cache_res_mgr()->UpdateCacheReservation(110 << 20));
|
||||
// Use EXPECT_PRED3 instead of EXPECT_NEAR to void too many size_t to
|
||||
// double explicit casts
|
||||
EXPECT_PRED3(CacheUsageWithinBounds, GetCache()->GetUsage(), (110 << 20),
|
||||
GetPercent(110 << 20, 1));
|
||||
EXPECT_PRED3(CacheUsageWithinBounds, sec_cache->TEST_GetUsage(), (30 << 20),
|
||||
GetPercent(30 << 20, 1));
|
||||
size_t sec_capacity;
|
||||
ASSERT_OK(sec_cache->GetCapacity(sec_capacity));
|
||||
ASSERT_EQ(sec_capacity, (30 << 20));
|
||||
|
||||
ASSERT_OK(UpdateTieredCache(tiered_cache, 100 << 20, 0.3));
|
||||
ASSERT_OK(UpdateTieredCache(tiered_cache, -1, 0.39));
|
||||
EXPECT_PRED3(CacheUsageWithinBounds, GetCache()->GetUsage(), (110 << 20),
|
||||
GetPercent(110 << 20, 1));
|
||||
EXPECT_PRED3(CacheUsageWithinBounds, sec_cache->TEST_GetUsage(), (39 << 20),
|
||||
GetPercent(39 << 20, 1));
|
||||
ASSERT_OK(sec_cache->GetCapacity(sec_capacity));
|
||||
ASSERT_EQ(sec_capacity, (39 << 20));
|
||||
|
||||
ASSERT_OK(cache_res_mgr()->UpdateCacheReservation(90 << 20));
|
||||
EXPECT_PRED3(CacheUsageWithinBounds, GetCache()->GetUsage(), (94 << 20),
|
||||
GetPercent(94 << 20, 1));
|
||||
EXPECT_PRED3(CacheUsageWithinBounds, sec_cache->TEST_GetUsage(), (35 << 20),
|
||||
GetPercent(35 << 20, 1));
|
||||
ASSERT_OK(sec_cache->GetCapacity(sec_capacity));
|
||||
ASSERT_EQ(sec_capacity, (39 << 20));
|
||||
|
||||
ASSERT_OK(cache_res_mgr()->UpdateCacheReservation(0));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
|
||||
Vendored
+105
-65
@@ -83,7 +83,10 @@ CacheWithSecondaryAdapter::CacheWithSecondaryAdapter(
|
||||
: CacheWrapper(std::move(target)),
|
||||
secondary_cache_(std::move(secondary_cache)),
|
||||
adm_policy_(adm_policy),
|
||||
distribute_cache_res_(distribute_cache_res) {
|
||||
distribute_cache_res_(distribute_cache_res),
|
||||
placeholder_usage_(0),
|
||||
reserved_usage_(0),
|
||||
sec_reserved_(0) {
|
||||
target_->SetEvictionCallback(
|
||||
[this](const Slice& key, Handle* handle, bool was_hit) {
|
||||
return EvictionHandler(key, handle, was_hit);
|
||||
@@ -103,8 +106,7 @@ CacheWithSecondaryAdapter::CacheWithSecondaryAdapter(
|
||||
// secondary cache is freed from the reservation.
|
||||
s = pri_cache_res_->UpdateCacheReservation(sec_capacity);
|
||||
assert(s.ok());
|
||||
sec_cache_res_ratio_.store((double)sec_capacity / target_->GetCapacity(),
|
||||
std::memory_order_relaxed);
|
||||
sec_cache_res_ratio_ = (double)sec_capacity / target_->GetCapacity();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,10 +115,12 @@ CacheWithSecondaryAdapter::~CacheWithSecondaryAdapter() {
|
||||
// use after free
|
||||
target_->SetEvictionCallback({});
|
||||
#ifndef NDEBUG
|
||||
if (distribute_cache_res_ && !ratio_changed_) {
|
||||
if (distribute_cache_res_) {
|
||||
size_t sec_capacity = 0;
|
||||
Status s = secondary_cache_->GetCapacity(sec_capacity);
|
||||
assert(s.ok());
|
||||
assert(placeholder_usage_ == 0);
|
||||
assert(reserved_usage_ == 0);
|
||||
assert(pri_cache_res_->GetTotalMemoryUsed() == sec_capacity);
|
||||
}
|
||||
#endif // NDEBUG
|
||||
@@ -236,13 +240,31 @@ Status CacheWithSecondaryAdapter::Insert(const Slice& key, ObjectPtr value,
|
||||
const Slice& compressed_value,
|
||||
CompressionType type) {
|
||||
Status s = target_->Insert(key, value, helper, charge, handle, priority);
|
||||
if (s.ok() && value == nullptr && distribute_cache_res_) {
|
||||
size_t sec_charge = static_cast<size_t>(
|
||||
charge * (sec_cache_res_ratio_.load(std::memory_order_relaxed)));
|
||||
s = secondary_cache_->Deflate(sec_charge);
|
||||
assert(s.ok());
|
||||
s = pri_cache_res_->UpdateCacheReservation(sec_charge, /*increase=*/false);
|
||||
assert(s.ok());
|
||||
if (s.ok() && value == nullptr && distribute_cache_res_ && handle) {
|
||||
charge = target_->GetCharge(*handle);
|
||||
|
||||
MutexLock l(&cache_res_mutex_);
|
||||
placeholder_usage_ += charge;
|
||||
// Check if total placeholder reservation is more than the overall
|
||||
// cache capacity. If it is, then we don't try to charge the
|
||||
// secondary cache because we don't want to overcharge it (beyond
|
||||
// its capacity).
|
||||
// In order to make this a bit more lightweight, we also check if
|
||||
// the difference between placeholder_usage_ and reserved_usage_ is
|
||||
// atleast kReservationChunkSize and avoid any adjustments if not.
|
||||
if ((placeholder_usage_ <= target_->GetCapacity()) &&
|
||||
((placeholder_usage_ - reserved_usage_) >= kReservationChunkSize)) {
|
||||
reserved_usage_ = placeholder_usage_ & ~(kReservationChunkSize - 1);
|
||||
size_t new_sec_reserved =
|
||||
static_cast<size_t>(reserved_usage_ * sec_cache_res_ratio_);
|
||||
size_t sec_charge = new_sec_reserved - sec_reserved_;
|
||||
s = secondary_cache_->Deflate(sec_charge);
|
||||
assert(s.ok());
|
||||
s = pri_cache_res_->UpdateCacheReservation(sec_charge,
|
||||
/*increase=*/false);
|
||||
assert(s.ok());
|
||||
sec_reserved_ += sec_charge;
|
||||
}
|
||||
}
|
||||
// Warm up the secondary cache with the compressed block. The secondary
|
||||
// cache may choose to ignore it based on the admission policy.
|
||||
@@ -287,14 +309,27 @@ bool CacheWithSecondaryAdapter::Release(Handle* handle,
|
||||
ObjectPtr v = target_->Value(handle);
|
||||
if (v == nullptr && distribute_cache_res_) {
|
||||
size_t charge = target_->GetCharge(handle);
|
||||
size_t sec_charge = static_cast<size_t>(
|
||||
charge * (sec_cache_res_ratio_.load(std::memory_order_relaxed)));
|
||||
TEST_SYNC_POINT("CacheWithSecondaryAdapter::Release:ChargeSecCache1");
|
||||
TEST_SYNC_POINT("CacheWithSecondaryAdapter::Release:ChargeSecCache2");
|
||||
Status s = secondary_cache_->Inflate(sec_charge);
|
||||
assert(s.ok());
|
||||
s = pri_cache_res_->UpdateCacheReservation(sec_charge, /*increase=*/true);
|
||||
assert(s.ok());
|
||||
|
||||
MutexLock l(&cache_res_mutex_);
|
||||
placeholder_usage_ -= charge;
|
||||
// Check if total placeholder reservation is more than the overall
|
||||
// cache capacity. If it is, then we do nothing as reserved_usage_ must
|
||||
// be already maxed out
|
||||
if ((placeholder_usage_ <= target_->GetCapacity()) &&
|
||||
(placeholder_usage_ < reserved_usage_)) {
|
||||
// Adjust reserved_usage_ in chunks of kReservationChunkSize, so
|
||||
// we don't hit this slow path too often.
|
||||
reserved_usage_ = placeholder_usage_ & ~(kReservationChunkSize - 1);
|
||||
size_t new_sec_reserved =
|
||||
static_cast<size_t>(reserved_usage_ * sec_cache_res_ratio_);
|
||||
size_t sec_charge = sec_reserved_ - new_sec_reserved;
|
||||
Status s = secondary_cache_->Inflate(sec_charge);
|
||||
assert(s.ok());
|
||||
s = pri_cache_res_->UpdateCacheReservation(sec_charge,
|
||||
/*increase=*/true);
|
||||
assert(s.ok());
|
||||
sec_reserved_ -= sec_charge;
|
||||
}
|
||||
}
|
||||
}
|
||||
return target_->Release(handle, erase_if_last_ref);
|
||||
@@ -441,13 +476,11 @@ const char* CacheWithSecondaryAdapter::Name() const {
|
||||
// where the new capacity < total cache reservations.
|
||||
void CacheWithSecondaryAdapter::SetCapacity(size_t capacity) {
|
||||
size_t sec_capacity = static_cast<size_t>(
|
||||
capacity * (distribute_cache_res_
|
||||
? sec_cache_res_ratio_.load(std::memory_order_relaxed)
|
||||
: 0.0));
|
||||
capacity * (distribute_cache_res_ ? sec_cache_res_ratio_ : 0.0));
|
||||
size_t old_sec_capacity = 0;
|
||||
|
||||
if (distribute_cache_res_) {
|
||||
MutexLock m(&mutex_);
|
||||
MutexLock m(&cache_res_mutex_);
|
||||
|
||||
Status s = secondary_cache_->GetCapacity(old_sec_capacity);
|
||||
if (!s.ok()) {
|
||||
@@ -462,9 +495,17 @@ void CacheWithSecondaryAdapter::SetCapacity(size_t capacity) {
|
||||
// 3. Decrease the primary cache capacity to the total budget
|
||||
s = secondary_cache_->SetCapacity(sec_capacity);
|
||||
if (s.ok()) {
|
||||
if (placeholder_usage_ > capacity) {
|
||||
// Adjust reserved_usage_ down
|
||||
reserved_usage_ = capacity & ~(kReservationChunkSize - 1);
|
||||
}
|
||||
size_t new_sec_reserved =
|
||||
static_cast<size_t>(reserved_usage_ * sec_cache_res_ratio_);
|
||||
s = pri_cache_res_->UpdateCacheReservation(
|
||||
old_sec_capacity - sec_capacity,
|
||||
(old_sec_capacity - sec_capacity) -
|
||||
(sec_reserved_ - new_sec_reserved),
|
||||
/*increase=*/false);
|
||||
sec_reserved_ = new_sec_reserved;
|
||||
assert(s.ok());
|
||||
target_->SetCapacity(capacity);
|
||||
}
|
||||
@@ -489,6 +530,29 @@ void CacheWithSecondaryAdapter::SetCapacity(size_t capacity) {
|
||||
}
|
||||
}
|
||||
|
||||
Status CacheWithSecondaryAdapter::GetSecondaryCacheCapacity(
|
||||
size_t& size) const {
|
||||
return secondary_cache_->GetCapacity(size);
|
||||
}
|
||||
|
||||
Status CacheWithSecondaryAdapter::GetSecondaryCachePinnedUsage(
|
||||
size_t& size) const {
|
||||
Status s;
|
||||
if (distribute_cache_res_) {
|
||||
MutexLock m(&cache_res_mutex_);
|
||||
size_t capacity = 0;
|
||||
s = secondary_cache_->GetCapacity(capacity);
|
||||
if (s.ok()) {
|
||||
size = capacity - pri_cache_res_->GetTotalMemoryUsed();
|
||||
} else {
|
||||
size = 0;
|
||||
}
|
||||
} else {
|
||||
size = 0;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// Update the secondary/primary allocation ratio (remember, the primary
|
||||
// capacity is the total memory budget when distribute_cache_res_ is true).
|
||||
// When the ratio changes, we may accumulate some error in the calculations
|
||||
@@ -503,12 +567,11 @@ void CacheWithSecondaryAdapter::SetCapacity(size_t capacity) {
|
||||
// in the future.
|
||||
Status CacheWithSecondaryAdapter::UpdateCacheReservationRatio(
|
||||
double compressed_secondary_ratio) {
|
||||
if (!distribute_cache_res_ ||
|
||||
sec_cache_res_ratio_.load(std::memory_order_relaxed) == 0.0) {
|
||||
if (!distribute_cache_res_) {
|
||||
return Status::NotSupported();
|
||||
}
|
||||
|
||||
MutexLock m(&mutex_);
|
||||
MutexLock m(&cache_res_mutex_);
|
||||
size_t pri_capacity = target_->GetCapacity();
|
||||
size_t sec_capacity =
|
||||
static_cast<size_t>(pri_capacity * compressed_secondary_ratio);
|
||||
@@ -518,38 +581,17 @@ Status CacheWithSecondaryAdapter::UpdateCacheReservationRatio(
|
||||
return s;
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT(
|
||||
"CacheWithSecondaryAdapter::UpdateCacheReservationRatio:Begin");
|
||||
|
||||
// There's a possible race condition here. Since the read of pri_cache_res_
|
||||
// memory used (secondary cache usage charged to primary cache), and the
|
||||
// change to sec_cache_res_ratio_ are not guarded by a mutex, its possible
|
||||
// that an Insert/Release in another thread might decrease/increase the
|
||||
// pri_cache_res_ reservation by the wrong amount. This should not be a
|
||||
// problem because updating the sec/pri ratio is a rare operation, and
|
||||
// the worst that can happen is we may over/under charge the secondary
|
||||
// cache usage by a little bit. But we do need to protect against
|
||||
// underflow of old_sec_reserved.
|
||||
// TODO: Make the accounting more accurate by tracking the total memory
|
||||
// reservation on the primary cache. This will also allow us to remove
|
||||
// the restriction of not being able to change the sec/pri ratio from
|
||||
// 0.0 to higher.
|
||||
size_t sec_charge_to_pri = pri_cache_res_->GetTotalMemoryUsed();
|
||||
size_t old_sec_reserved = (old_sec_capacity > sec_charge_to_pri)
|
||||
? (old_sec_capacity - sec_charge_to_pri)
|
||||
: 0;
|
||||
// Calculate the new secondary cache reservation
|
||||
size_t sec_reserved = static_cast<size_t>(
|
||||
old_sec_reserved *
|
||||
(double)(compressed_secondary_ratio /
|
||||
sec_cache_res_ratio_.load(std::memory_order_relaxed)));
|
||||
sec_cache_res_ratio_.store(compressed_secondary_ratio,
|
||||
std::memory_order_relaxed);
|
||||
// reserved_usage_ will never be > the cache capacity, so we don't
|
||||
// have to worry about adjusting it here.
|
||||
sec_cache_res_ratio_ = compressed_secondary_ratio;
|
||||
size_t new_sec_reserved =
|
||||
static_cast<size_t>(reserved_usage_ * sec_cache_res_ratio_);
|
||||
if (sec_capacity > old_sec_capacity) {
|
||||
// We're increasing the ratio, thus ending up with a larger secondary
|
||||
// cache and a smaller usable primary cache capacity. Similar to
|
||||
// SetCapacity(), we try to avoid a temporary increase in total usage
|
||||
// beyond teh configured capacity -
|
||||
// beyond the configured capacity -
|
||||
// 1. A higher secondary cache ratio means it gets a higher share of
|
||||
// cache reservations. So first account for that by deflating the
|
||||
// secondary cache
|
||||
@@ -557,12 +599,13 @@ Status CacheWithSecondaryAdapter::UpdateCacheReservationRatio(
|
||||
// cache utilization (increase in capacity - increase in share of cache
|
||||
// reservation)
|
||||
// 3. Increase secondary cache capacity
|
||||
s = secondary_cache_->Deflate(sec_reserved - old_sec_reserved);
|
||||
s = secondary_cache_->Deflate(new_sec_reserved - sec_reserved_);
|
||||
assert(s.ok());
|
||||
s = pri_cache_res_->UpdateCacheReservation(
|
||||
(sec_capacity - old_sec_capacity) - (sec_reserved - old_sec_reserved),
|
||||
(sec_capacity - old_sec_capacity) - (new_sec_reserved - sec_reserved_),
|
||||
/*increase=*/true);
|
||||
assert(s.ok());
|
||||
sec_reserved_ = new_sec_reserved;
|
||||
s = secondary_cache_->SetCapacity(sec_capacity);
|
||||
assert(s.ok());
|
||||
} else {
|
||||
@@ -576,21 +619,16 @@ Status CacheWithSecondaryAdapter::UpdateCacheReservationRatio(
|
||||
s = secondary_cache_->SetCapacity(sec_capacity);
|
||||
if (s.ok()) {
|
||||
s = pri_cache_res_->UpdateCacheReservation(
|
||||
(old_sec_capacity - sec_capacity) - (old_sec_reserved - sec_reserved),
|
||||
(old_sec_capacity - sec_capacity) -
|
||||
(sec_reserved_ - new_sec_reserved),
|
||||
/*increase=*/false);
|
||||
assert(s.ok());
|
||||
s = secondary_cache_->Inflate(old_sec_reserved - sec_reserved);
|
||||
s = secondary_cache_->Inflate(sec_reserved_ - new_sec_reserved);
|
||||
assert(s.ok());
|
||||
sec_reserved_ = new_sec_reserved;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT("CacheWithSecondaryAdapter::UpdateCacheReservationRatio:End");
|
||||
#ifndef NDEBUG
|
||||
// As mentioned in the function comments, we may accumulate some erros when
|
||||
// the ratio is changed. We set a flag here which disables some assertions
|
||||
// in the destructor
|
||||
ratio_changed_ = true;
|
||||
#endif
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -645,12 +683,14 @@ std::shared_ptr<Cache> NewTieredCache(const TieredCacheOptions& _opts) {
|
||||
*(static_cast_with_check<LRUCacheOptions, ShardedCacheOptions>(
|
||||
opts.cache_opts));
|
||||
cache_opts.capacity = opts.total_capacity;
|
||||
cache_opts.secondary_cache = nullptr;
|
||||
cache = cache_opts.MakeSharedCache();
|
||||
} else if (opts.cache_type == PrimaryCacheType::kCacheTypeHCC) {
|
||||
HyperClockCacheOptions cache_opts =
|
||||
*(static_cast_with_check<HyperClockCacheOptions, ShardedCacheOptions>(
|
||||
opts.cache_opts));
|
||||
cache_opts.capacity = opts.total_capacity;
|
||||
cache_opts.secondary_cache = nullptr;
|
||||
cache = cache_opts.MakeSharedCache();
|
||||
} else {
|
||||
return nullptr;
|
||||
|
||||
Vendored
+18
-5
@@ -47,6 +47,10 @@ class CacheWithSecondaryAdapter : public CacheWrapper {
|
||||
|
||||
void SetCapacity(size_t capacity) override;
|
||||
|
||||
Status GetSecondaryCacheCapacity(size_t& size) const override;
|
||||
|
||||
Status GetSecondaryCachePinnedUsage(size_t& size) const override;
|
||||
|
||||
Status UpdateCacheReservationRatio(double ratio);
|
||||
|
||||
Status UpdateAdmissionPolicy(TieredAdmissionPolicy adm_policy);
|
||||
@@ -56,6 +60,8 @@ class CacheWithSecondaryAdapter : public CacheWrapper {
|
||||
SecondaryCache* TEST_GetSecondaryCache() { return secondary_cache_.get(); }
|
||||
|
||||
private:
|
||||
static constexpr size_t kReservationChunkSize = 1 << 20;
|
||||
|
||||
bool EvictionHandler(const Slice& key, Handle* handle, bool was_hit);
|
||||
|
||||
void StartAsyncLookupOnMySecondary(AsyncLookupHandle& async_handle);
|
||||
@@ -80,11 +86,18 @@ class CacheWithSecondaryAdapter : public CacheWrapper {
|
||||
std::shared_ptr<ConcurrentCacheReservationManager> pri_cache_res_;
|
||||
// Fraction of a cache memory reservation to be assigned to the secondary
|
||||
// cache
|
||||
std::atomic<double> sec_cache_res_ratio_;
|
||||
port::Mutex mutex_;
|
||||
#ifndef NDEBUG
|
||||
bool ratio_changed_ = false;
|
||||
#endif
|
||||
double sec_cache_res_ratio_;
|
||||
// Mutex for use when managing cache memory reservations. Should not be used
|
||||
// for other purposes, as it may risk causing deadlocks.
|
||||
mutable port::Mutex cache_res_mutex_;
|
||||
// Total memory reserved by placeholder entriesin the cache
|
||||
size_t placeholder_usage_;
|
||||
// Total placeholoder memory charged to both the primary and secondary
|
||||
// caches. Will be <= placeholder_usage_.
|
||||
size_t reserved_usage_;
|
||||
// Amount of memory reserved in the secondary cache. This should be
|
||||
// reserved_usage_ * sec_cache_res_ratio_ in steady state.
|
||||
size_t sec_reserved_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+10
@@ -83,6 +83,16 @@ size_t ShardedCacheBase::GetCapacity() const {
|
||||
return capacity_;
|
||||
}
|
||||
|
||||
Status ShardedCacheBase::GetSecondaryCacheCapacity(size_t& size) const {
|
||||
size = 0;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status ShardedCacheBase::GetSecondaryCachePinnedUsage(size_t& size) const {
|
||||
size = 0;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
bool ShardedCacheBase::HasStrictCapacityLimit() const {
|
||||
MutexLock l(&config_mutex_);
|
||||
return strict_capacity_limit_;
|
||||
|
||||
Vendored
+2
@@ -99,6 +99,8 @@ class ShardedCacheBase : public Cache {
|
||||
|
||||
bool HasStrictCapacityLimit() const override;
|
||||
size_t GetCapacity() const override;
|
||||
Status GetSecondaryCacheCapacity(size_t& size) const override;
|
||||
Status GetSecondaryCachePinnedUsage(size_t& size) const override;
|
||||
|
||||
using Cache::GetUsage;
|
||||
size_t GetUsage(Handle* handle) const override;
|
||||
|
||||
Vendored
+2
-4
@@ -109,10 +109,8 @@ void TieredSecondaryCache::WaitAll(
|
||||
}
|
||||
nvm_sec_cache_->WaitAll(nvm_handles);
|
||||
for (auto handle : my_handles) {
|
||||
assert(handle->IsReady());
|
||||
auto nvm_handle = handle->inner_handle();
|
||||
handle->SetSize(nvm_handle->Size());
|
||||
handle->SetValue(nvm_handle->Value());
|
||||
assert(handle->inner_handle()->IsReady());
|
||||
handle->Complete();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Vendored
+6
-2
@@ -79,7 +79,10 @@ class TieredSecondaryCache : public SecondaryCacheWrapper {
|
||||
~ResultHandle() override {}
|
||||
|
||||
bool IsReady() override {
|
||||
return !inner_handle_ || inner_handle_->IsReady();
|
||||
if (inner_handle_ && inner_handle_->IsReady()) {
|
||||
Complete();
|
||||
}
|
||||
return ready_;
|
||||
}
|
||||
|
||||
void Wait() override {
|
||||
@@ -92,10 +95,10 @@ class TieredSecondaryCache : public SecondaryCacheWrapper {
|
||||
Cache::ObjectPtr Value() override { return value_; }
|
||||
|
||||
void Complete() {
|
||||
assert(IsReady());
|
||||
size_ = inner_handle_->Size();
|
||||
value_ = inner_handle_->Value();
|
||||
inner_handle_.reset();
|
||||
ready_ = true;
|
||||
}
|
||||
|
||||
void SetInnerHandle(std::unique_ptr<SecondaryCacheResultHandle>&& handle) {
|
||||
@@ -115,6 +118,7 @@ class TieredSecondaryCache : public SecondaryCacheWrapper {
|
||||
CreateContext ctx_;
|
||||
size_t size_;
|
||||
Cache::ObjectPtr value_;
|
||||
bool ready_ = false;
|
||||
};
|
||||
|
||||
static void NoopDelete(Cache::ObjectPtr /*obj*/,
|
||||
|
||||
Vendored
+129
-9
@@ -15,10 +15,11 @@ namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class TestSecondaryCache : public SecondaryCache {
|
||||
public:
|
||||
explicit TestSecondaryCache(size_t capacity)
|
||||
explicit TestSecondaryCache(size_t capacity, bool ready_before_wait)
|
||||
: cache_(NewLRUCache(capacity, 0, false, 0.5 /* high_pri_pool_ratio */,
|
||||
nullptr, kDefaultToAdaptiveMutex,
|
||||
kDontChargeCacheMetadata)),
|
||||
ready_before_wait_(ready_before_wait),
|
||||
num_insert_saved_(0),
|
||||
num_hits_(0),
|
||||
num_misses_(0) {}
|
||||
@@ -88,7 +89,8 @@ class TestSecondaryCache : public SecondaryCache {
|
||||
/*alloc*/ nullptr, &value, &charge);
|
||||
if (s.ok()) {
|
||||
secondary_handle.reset(new TestSecondaryCacheResultHandle(
|
||||
cache_.get(), handle, value, charge, /*ready=*/wait));
|
||||
cache_.get(), handle, value, charge,
|
||||
/*ready=*/wait || ready_before_wait_));
|
||||
kept_in_sec_cache = true;
|
||||
} else {
|
||||
cache_.Release(handle);
|
||||
@@ -168,6 +170,7 @@ class TestSecondaryCache : public SecondaryCache {
|
||||
BasicTypedSharedCacheInterface<char[], CacheEntryRole::kMisc>;
|
||||
using TypedHandle = SharedCache::TypedHandle;
|
||||
SharedCache cache_;
|
||||
bool ready_before_wait_;
|
||||
uint32_t num_insert_saved_;
|
||||
uint32_t num_hits_;
|
||||
uint32_t num_misses_;
|
||||
@@ -179,11 +182,10 @@ class DBTieredSecondaryCacheTest : public DBTestBase {
|
||||
DBTieredSecondaryCacheTest()
|
||||
: DBTestBase("db_tiered_secondary_cache_test", /*env_do_fsync=*/true) {}
|
||||
|
||||
std::shared_ptr<Cache> NewCache(size_t pri_capacity,
|
||||
size_t compressed_capacity,
|
||||
size_t nvm_capacity,
|
||||
TieredAdmissionPolicy adm_policy =
|
||||
TieredAdmissionPolicy::kAdmPolicyAuto) {
|
||||
std::shared_ptr<Cache> NewCache(
|
||||
size_t pri_capacity, size_t compressed_capacity, size_t nvm_capacity,
|
||||
TieredAdmissionPolicy adm_policy = TieredAdmissionPolicy::kAdmPolicyAuto,
|
||||
bool ready_before_wait = false) {
|
||||
LRUCacheOptions lru_opts;
|
||||
TieredCacheOptions opts;
|
||||
lru_opts.capacity = 0;
|
||||
@@ -194,10 +196,11 @@ class DBTieredSecondaryCacheTest : public DBTestBase {
|
||||
opts.comp_cache_opts.capacity = 0;
|
||||
opts.comp_cache_opts.num_shard_bits = 0;
|
||||
opts.total_capacity = pri_capacity + compressed_capacity;
|
||||
opts.compressed_secondary_ratio =
|
||||
opts.compressed_secondary_ratio = compressed_secondary_ratio_ =
|
||||
(double)compressed_capacity / opts.total_capacity;
|
||||
if (nvm_capacity > 0) {
|
||||
nvm_sec_cache_.reset(new TestSecondaryCache(nvm_capacity));
|
||||
nvm_sec_cache_.reset(
|
||||
new TestSecondaryCache(nvm_capacity, ready_before_wait));
|
||||
opts.nvm_sec_cache = nvm_sec_cache_;
|
||||
}
|
||||
opts.adm_policy = adm_policy;
|
||||
@@ -207,6 +210,12 @@ class DBTieredSecondaryCacheTest : public DBTestBase {
|
||||
return cache_;
|
||||
}
|
||||
|
||||
void ClearPrimaryCache() {
|
||||
ASSERT_EQ(UpdateTieredCache(cache_, -1, 1.0), Status::OK());
|
||||
ASSERT_EQ(UpdateTieredCache(cache_, -1, compressed_secondary_ratio_),
|
||||
Status::OK());
|
||||
}
|
||||
|
||||
TestSecondaryCache* nvm_sec_cache() { return nvm_sec_cache_.get(); }
|
||||
|
||||
CompressedSecondaryCache* compressed_secondary_cache() {
|
||||
@@ -218,6 +227,7 @@ class DBTieredSecondaryCacheTest : public DBTestBase {
|
||||
private:
|
||||
std::shared_ptr<Cache> cache_;
|
||||
std::shared_ptr<TestSecondaryCache> nvm_sec_cache_;
|
||||
double compressed_secondary_ratio_;
|
||||
};
|
||||
|
||||
// In this test, the block size is set to 4096. Each value is 1007 bytes, so
|
||||
@@ -582,6 +592,116 @@ TEST_F(DBTieredSecondaryCacheTest, WaitAllTest) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBTieredSecondaryCacheTest, ReadyBeforeWaitAllTest) {
|
||||
if (!LZ4_Supported()) {
|
||||
ROCKSDB_GTEST_SKIP("This test requires LZ4 support.");
|
||||
return;
|
||||
}
|
||||
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_cache = NewCache(250 * 1024, 20 * 1024, 256 * 1024,
|
||||
TieredAdmissionPolicy::kAdmPolicyAuto,
|
||||
/*ready_before_wait=*/true);
|
||||
table_options.block_size = 4 * 1024;
|
||||
table_options.cache_index_and_filter_blocks = false;
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.statistics = CreateDBStatistics();
|
||||
|
||||
options.paranoid_file_checks = false;
|
||||
DestroyAndReopen(options);
|
||||
Random rnd(301);
|
||||
const int N = 256;
|
||||
for (int i = 0; i < N; i++) {
|
||||
std::string p_v;
|
||||
test::CompressibleString(&rnd, 0.5, 1007, &p_v);
|
||||
ASSERT_OK(Put(Key(i), p_v));
|
||||
}
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
std::vector<std::string> keys;
|
||||
std::vector<std::string> values;
|
||||
|
||||
keys.push_back(Key(0));
|
||||
keys.push_back(Key(4));
|
||||
keys.push_back(Key(8));
|
||||
values = MultiGet(keys, /*snapshot=*/nullptr, /*async=*/true);
|
||||
ASSERT_EQ(values.size(), keys.size());
|
||||
for (auto value : values) {
|
||||
ASSERT_EQ(1007, value.size());
|
||||
}
|
||||
ASSERT_EQ(nvm_sec_cache()->num_insert_saved(), 3u);
|
||||
ASSERT_EQ(nvm_sec_cache()->num_misses(), 3u);
|
||||
ASSERT_EQ(nvm_sec_cache()->num_hits(), 0u);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(BLOCK_CACHE_MISS), 3u);
|
||||
|
||||
keys.clear();
|
||||
values.clear();
|
||||
keys.push_back(Key(12));
|
||||
keys.push_back(Key(16));
|
||||
keys.push_back(Key(20));
|
||||
values = MultiGet(keys, /*snapshot=*/nullptr, /*async=*/true);
|
||||
ASSERT_EQ(values.size(), keys.size());
|
||||
for (auto value : values) {
|
||||
ASSERT_EQ(1007, value.size());
|
||||
}
|
||||
ASSERT_EQ(nvm_sec_cache()->num_insert_saved(), 6u);
|
||||
ASSERT_EQ(nvm_sec_cache()->num_misses(), 6u);
|
||||
ASSERT_EQ(nvm_sec_cache()->num_hits(), 0u);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(BLOCK_CACHE_MISS), 6u);
|
||||
|
||||
keys.clear();
|
||||
values.clear();
|
||||
keys.push_back(Key(0));
|
||||
keys.push_back(Key(4));
|
||||
keys.push_back(Key(8));
|
||||
values = MultiGet(keys, /*snapshot=*/nullptr, /*async=*/true);
|
||||
ASSERT_EQ(values.size(), keys.size());
|
||||
for (auto value : values) {
|
||||
ASSERT_EQ(1007, value.size());
|
||||
}
|
||||
ASSERT_EQ(nvm_sec_cache()->num_insert_saved(), 6u);
|
||||
ASSERT_EQ(nvm_sec_cache()->num_misses(), 6u);
|
||||
ASSERT_EQ(nvm_sec_cache()->num_hits(), 3u);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(BLOCK_CACHE_MISS), 6u);
|
||||
|
||||
ClearPrimaryCache();
|
||||
|
||||
keys.clear();
|
||||
values.clear();
|
||||
keys.push_back(Key(0));
|
||||
keys.push_back(Key(32));
|
||||
keys.push_back(Key(36));
|
||||
values = MultiGet(keys, /*snapshot=*/nullptr, /*async=*/true);
|
||||
ASSERT_EQ(values.size(), keys.size());
|
||||
for (auto value : values) {
|
||||
ASSERT_EQ(1007, value.size());
|
||||
}
|
||||
ASSERT_EQ(nvm_sec_cache()->num_insert_saved(), 8u);
|
||||
ASSERT_EQ(nvm_sec_cache()->num_misses(), 8u);
|
||||
ASSERT_EQ(nvm_sec_cache()->num_hits(), 4u);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(BLOCK_CACHE_MISS), 8u);
|
||||
|
||||
keys.clear();
|
||||
values.clear();
|
||||
keys.push_back(Key(0));
|
||||
keys.push_back(Key(32));
|
||||
keys.push_back(Key(36));
|
||||
values = MultiGet(keys, /*snapshot=*/nullptr, /*async=*/true);
|
||||
ASSERT_EQ(values.size(), keys.size());
|
||||
for (auto value : values) {
|
||||
ASSERT_EQ(1007, value.size());
|
||||
}
|
||||
ASSERT_EQ(nvm_sec_cache()->num_insert_saved(), 8u);
|
||||
ASSERT_EQ(nvm_sec_cache()->num_misses(), 8u);
|
||||
ASSERT_EQ(nvm_sec_cache()->num_hits(), 4u);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(BLOCK_CACHE_MISS), 8u);
|
||||
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
// This test is for iteration. It iterates through a set of keys in two
|
||||
// passes. First pass loads the compressed blocks into the nvm tier, and
|
||||
// the second pass should hit all of those blocks.
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
#include "rocksdb/utilities/transaction_db.h"
|
||||
#include "rocksdb/utilities/write_batch_with_index.h"
|
||||
#include "rocksdb/write_batch.h"
|
||||
#include "rocksdb/write_buffer_manager.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
|
||||
using ROCKSDB_NAMESPACE::BackupEngine;
|
||||
@@ -125,6 +126,7 @@ using ROCKSDB_NAMESPACE::WALRecoveryMode;
|
||||
using ROCKSDB_NAMESPACE::WritableFile;
|
||||
using ROCKSDB_NAMESPACE::WriteBatch;
|
||||
using ROCKSDB_NAMESPACE::WriteBatchWithIndex;
|
||||
using ROCKSDB_NAMESPACE::WriteBufferManager;
|
||||
using ROCKSDB_NAMESPACE::WriteOptions;
|
||||
|
||||
using std::unordered_set;
|
||||
@@ -219,6 +221,9 @@ struct rocksdb_memory_allocator_t {
|
||||
struct rocksdb_cache_t {
|
||||
std::shared_ptr<Cache> rep;
|
||||
};
|
||||
struct rocksdb_write_buffer_manager_t {
|
||||
std::shared_ptr<WriteBufferManager> rep;
|
||||
};
|
||||
struct rocksdb_livefiles_t {
|
||||
std::vector<LiveFileMetaData> rep;
|
||||
};
|
||||
@@ -2904,6 +2909,16 @@ void rocksdb_options_set_db_paths(rocksdb_options_t* opt,
|
||||
opt->rep.db_paths = db_paths;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_cf_paths(rocksdb_options_t* opt,
|
||||
const rocksdb_dbpath_t** dbpath_values,
|
||||
size_t num_paths) {
|
||||
std::vector<DbPath> cf_paths(num_paths);
|
||||
for (size_t i = 0; i < num_paths; ++i) {
|
||||
cf_paths[i] = dbpath_values[i]->rep;
|
||||
}
|
||||
opt->rep.cf_paths = cf_paths;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_env(rocksdb_options_t* opt, rocksdb_env_t* env) {
|
||||
opt->rep.env = (env ? env->rep : nullptr);
|
||||
}
|
||||
@@ -2935,6 +2950,11 @@ void rocksdb_options_set_write_buffer_size(rocksdb_options_t* opt, size_t s) {
|
||||
opt->rep.write_buffer_size = s;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_write_buffer_manager(
|
||||
rocksdb_options_t* opt, rocksdb_write_buffer_manager_t* wbm) {
|
||||
opt->rep.write_buffer_manager = wbm->rep;
|
||||
}
|
||||
|
||||
size_t rocksdb_options_get_write_buffer_size(rocksdb_options_t* opt) {
|
||||
return opt->rep.write_buffer_size;
|
||||
}
|
||||
@@ -3029,6 +3049,16 @@ void rocksdb_options_set_max_bytes_for_level_multiplier_additional(
|
||||
}
|
||||
}
|
||||
|
||||
void rocksdb_options_set_periodic_compaction_seconds(rocksdb_options_t* opt,
|
||||
uint64_t seconds) {
|
||||
opt->rep.periodic_compaction_seconds = seconds;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_options_get_periodic_compaction_seconds(
|
||||
rocksdb_options_t* opt) {
|
||||
return opt->rep.periodic_compaction_seconds;
|
||||
}
|
||||
|
||||
void rocksdb_options_enable_statistics(rocksdb_options_t* opt) {
|
||||
opt->rep.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
}
|
||||
@@ -3957,6 +3987,16 @@ rocksdb_ratelimiter_t* rocksdb_ratelimiter_create(int64_t rate_bytes_per_sec,
|
||||
return rate_limiter;
|
||||
}
|
||||
|
||||
rocksdb_ratelimiter_t* rocksdb_ratelimiter_create_auto_tuned(
|
||||
int64_t rate_bytes_per_sec, int64_t refill_period_us, int32_t fairness) {
|
||||
rocksdb_ratelimiter_t* rate_limiter = new rocksdb_ratelimiter_t;
|
||||
rate_limiter->rep.reset(NewGenericRateLimiter(rate_bytes_per_sec,
|
||||
refill_period_us, fairness,
|
||||
RateLimiter::Mode::kWritesOnly,
|
||||
true)); // auto_tuned
|
||||
return rate_limiter;
|
||||
}
|
||||
|
||||
void rocksdb_ratelimiter_destroy(rocksdb_ratelimiter_t* limiter) {
|
||||
delete limiter;
|
||||
}
|
||||
@@ -4866,6 +4906,60 @@ size_t rocksdb_cache_get_occupancy_count(const rocksdb_cache_t* cache) {
|
||||
return cache->rep->GetOccupancyCount();
|
||||
}
|
||||
|
||||
rocksdb_write_buffer_manager_t* rocksdb_write_buffer_manager_create(
|
||||
size_t buffer_size, bool allow_stall) {
|
||||
rocksdb_write_buffer_manager_t* wbm = new rocksdb_write_buffer_manager_t;
|
||||
wbm->rep.reset(new WriteBufferManager(buffer_size, {}, allow_stall));
|
||||
return wbm;
|
||||
}
|
||||
|
||||
rocksdb_write_buffer_manager_t* rocksdb_write_buffer_manager_create_with_cache(
|
||||
size_t buffer_size, const rocksdb_cache_t* cache, bool allow_stall) {
|
||||
rocksdb_write_buffer_manager_t* wbm = new rocksdb_write_buffer_manager_t;
|
||||
wbm->rep.reset(new WriteBufferManager(buffer_size, cache->rep, allow_stall));
|
||||
return wbm;
|
||||
}
|
||||
|
||||
void rocksdb_write_buffer_manager_destroy(rocksdb_write_buffer_manager_t* wbm) {
|
||||
delete wbm;
|
||||
}
|
||||
|
||||
bool rocksdb_write_buffer_manager_enabled(rocksdb_write_buffer_manager_t* wbm) {
|
||||
return wbm->rep->enabled();
|
||||
}
|
||||
|
||||
bool rocksdb_write_buffer_manager_cost_to_cache(
|
||||
rocksdb_write_buffer_manager_t* wbm) {
|
||||
return wbm->rep->cost_to_cache();
|
||||
}
|
||||
|
||||
size_t rocksdb_write_buffer_manager_memory_usage(
|
||||
rocksdb_write_buffer_manager_t* wbm) {
|
||||
return wbm->rep->memory_usage();
|
||||
}
|
||||
|
||||
size_t rocksdb_write_buffer_manager_mutable_memtable_memory_usage(
|
||||
rocksdb_write_buffer_manager_t* wbm) {
|
||||
return wbm->rep->mutable_memtable_memory_usage();
|
||||
}
|
||||
|
||||
size_t rocksdb_write_buffer_manager_dummy_entries_in_cache_usage(
|
||||
rocksdb_write_buffer_manager_t* wbm) {
|
||||
return wbm->rep->dummy_entries_in_cache_usage();
|
||||
}
|
||||
size_t rocksdb_write_buffer_manager_buffer_size(
|
||||
rocksdb_write_buffer_manager_t* wbm) {
|
||||
return wbm->rep->buffer_size();
|
||||
}
|
||||
void rocksdb_write_buffer_manager_set_buffer_size(
|
||||
rocksdb_write_buffer_manager_t* wbm, size_t new_size) {
|
||||
wbm->rep->SetBufferSize(new_size);
|
||||
}
|
||||
ROCKSDB_LIBRARY_API void rocksdb_write_buffer_manager_set_allow_stall(
|
||||
rocksdb_write_buffer_manager_t* wbm, bool new_allow_stall) {
|
||||
wbm->rep->SetAllowStall(new_allow_stall);
|
||||
}
|
||||
|
||||
rocksdb_dbpath_t* rocksdb_dbpath_create(const char* path,
|
||||
uint64_t target_size) {
|
||||
rocksdb_dbpath_t* result = new rocksdb_dbpath_t;
|
||||
@@ -5464,6 +5558,11 @@ char* rocksdb_sst_file_metadata_get_relative_filename(
|
||||
return strdup(file_meta->rep->relative_filename.c_str());
|
||||
}
|
||||
|
||||
char* rocksdb_sst_file_metadata_get_directory(
|
||||
rocksdb_sst_file_metadata_t* file_meta) {
|
||||
return strdup(file_meta->rep->directory.c_str());
|
||||
}
|
||||
|
||||
uint64_t rocksdb_sst_file_metadata_get_size(
|
||||
rocksdb_sst_file_metadata_t* file_meta) {
|
||||
return file_meta->rep->size;
|
||||
|
||||
+85
-3
@@ -713,6 +713,11 @@ int main(int argc, char** argv) {
|
||||
rocksdb_options_set_ratelimiter(options, rate_limiter);
|
||||
rocksdb_ratelimiter_destroy(rate_limiter);
|
||||
|
||||
rate_limiter =
|
||||
rocksdb_ratelimiter_create_auto_tuned(1000 * 1024 * 1024, 100 * 1000, 10);
|
||||
rocksdb_options_set_ratelimiter(options, rate_limiter);
|
||||
rocksdb_ratelimiter_destroy(rate_limiter);
|
||||
|
||||
roptions = rocksdb_readoptions_create();
|
||||
rocksdb_readoptions_set_verify_checksums(roptions, 1);
|
||||
rocksdb_readoptions_set_fill_cache(roptions, 1);
|
||||
@@ -1474,10 +1479,20 @@ int main(int argc, char** argv) {
|
||||
CheckCondition(cflen == 2);
|
||||
rocksdb_list_column_families_destroy(column_fams, cflen);
|
||||
|
||||
rocksdb_options_t* cf_options = rocksdb_options_create();
|
||||
rocksdb_options_t* cf_options_1 = rocksdb_options_create();
|
||||
rocksdb_options_t* cf_options_2 = rocksdb_options_create();
|
||||
|
||||
// use dbpathname2 as the cf_path for "cf1"
|
||||
rocksdb_dbpath_t* dbpath2;
|
||||
char dbpathname2[200];
|
||||
snprintf(dbpathname2, sizeof(dbpathname2), "%s/rocksdb_c_test-%d-dbpath2",
|
||||
GetTempDir(), ((int)geteuid()));
|
||||
dbpath2 = rocksdb_dbpath_create(dbpathname2, 1024 * 1024);
|
||||
const rocksdb_dbpath_t* cf_paths[1] = {dbpath2};
|
||||
rocksdb_options_set_cf_paths(cf_options_2, cf_paths, 1);
|
||||
|
||||
const char* cf_names[2] = {"default", "cf1"};
|
||||
const rocksdb_options_t* cf_opts[2] = {cf_options, cf_options};
|
||||
const rocksdb_options_t* cf_opts[2] = {cf_options_1, cf_options_2};
|
||||
rocksdb_column_family_handle_t* handles[2];
|
||||
|
||||
LoadAndCheckLatestOptions(dbname, env, false, cache, NULL, 2, cf_names,
|
||||
@@ -1505,6 +1520,37 @@ int main(int argc, char** argv) {
|
||||
rocksdb_flushoptions_t* flush_options = rocksdb_flushoptions_create();
|
||||
rocksdb_flushoptions_set_wait(flush_options, 1);
|
||||
rocksdb_flush_cf(db, flush_options, handles[1], &err);
|
||||
|
||||
// make sure all files in "cf1" are under the specified cf path
|
||||
{
|
||||
rocksdb_column_family_metadata_t* cf_meta =
|
||||
rocksdb_get_column_family_metadata_cf(db, handles[1]);
|
||||
size_t cf_file_count = rocksdb_column_family_metadata_get_size(cf_meta);
|
||||
assert(cf_file_count > 0);
|
||||
size_t level_count =
|
||||
rocksdb_column_family_metadata_get_level_count(cf_meta);
|
||||
assert(level_count > 0);
|
||||
for (size_t l = 0; l < level_count; ++l) {
|
||||
rocksdb_level_metadata_t* level_meta =
|
||||
rocksdb_column_family_metadata_get_level_metadata(cf_meta, l);
|
||||
assert(level_meta);
|
||||
|
||||
size_t file_count = rocksdb_level_metadata_get_file_count(level_meta);
|
||||
for (size_t f = 0; f < file_count; ++f) {
|
||||
rocksdb_sst_file_metadata_t* file_meta =
|
||||
rocksdb_level_metadata_get_sst_file_metadata(level_meta, f);
|
||||
assert(file_meta);
|
||||
char* file_path = rocksdb_sst_file_metadata_get_directory(file_meta);
|
||||
assert(strcmp(file_path, dbpathname2) == 0);
|
||||
Free(&file_path);
|
||||
rocksdb_sst_file_metadata_destroy(file_meta);
|
||||
}
|
||||
rocksdb_level_metadata_destroy(level_meta);
|
||||
}
|
||||
|
||||
rocksdb_column_family_metadata_destroy(cf_meta);
|
||||
}
|
||||
|
||||
CheckNoError(err) rocksdb_flushoptions_destroy(flush_options);
|
||||
|
||||
CheckGetCF(db, roptions, handles[1], "foo", "hello");
|
||||
@@ -1668,7 +1714,9 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
rocksdb_destroy_db(options, dbname, &err);
|
||||
rocksdb_options_destroy(db_options);
|
||||
rocksdb_options_destroy(cf_options);
|
||||
rocksdb_options_destroy(cf_options_1);
|
||||
rocksdb_options_destroy(cf_options_2);
|
||||
rocksdb_dbpath_destroy(dbpath2);
|
||||
}
|
||||
|
||||
StartPhase("prefix");
|
||||
@@ -1873,6 +1921,10 @@ int main(int argc, char** argv) {
|
||||
CheckCondition(2.0 ==
|
||||
rocksdb_options_get_max_bytes_for_level_multiplier(o));
|
||||
|
||||
rocksdb_options_set_periodic_compaction_seconds(o, 100000);
|
||||
CheckCondition(100000 ==
|
||||
rocksdb_options_get_periodic_compaction_seconds(o));
|
||||
|
||||
rocksdb_options_set_skip_stats_update_on_db_open(o, 1);
|
||||
CheckCondition(1 == rocksdb_options_get_skip_stats_update_on_db_open(o));
|
||||
|
||||
@@ -2303,6 +2355,12 @@ int main(int argc, char** argv) {
|
||||
CheckCondition(2.0 ==
|
||||
rocksdb_options_get_max_bytes_for_level_multiplier(o));
|
||||
|
||||
rocksdb_options_set_periodic_compaction_seconds(copy, 8000);
|
||||
CheckCondition(8000 ==
|
||||
rocksdb_options_get_periodic_compaction_seconds(copy));
|
||||
CheckCondition(100000 ==
|
||||
rocksdb_options_get_periodic_compaction_seconds(o));
|
||||
|
||||
rocksdb_options_set_skip_stats_update_on_db_open(copy, 0);
|
||||
CheckCondition(0 == rocksdb_options_get_skip_stats_update_on_db_open(copy));
|
||||
CheckCondition(1 == rocksdb_options_get_skip_stats_update_on_db_open(o));
|
||||
@@ -3723,6 +3781,30 @@ int main(int argc, char** argv) {
|
||||
rocksdb_wait_for_compact_options_destroy(wco);
|
||||
}
|
||||
|
||||
StartPhase("write_buffer_manager");
|
||||
{
|
||||
rocksdb_cache_t* lru;
|
||||
lru = rocksdb_cache_create_lru(100);
|
||||
|
||||
rocksdb_write_buffer_manager_t* write_buffer_manager;
|
||||
write_buffer_manager =
|
||||
rocksdb_write_buffer_manager_create_with_cache(200, lru, false);
|
||||
|
||||
CheckCondition(true ==
|
||||
rocksdb_write_buffer_manager_enabled(write_buffer_manager));
|
||||
CheckCondition(true == rocksdb_write_buffer_manager_cost_to_cache(
|
||||
write_buffer_manager));
|
||||
CheckCondition(
|
||||
200 == rocksdb_write_buffer_manager_buffer_size(write_buffer_manager));
|
||||
|
||||
rocksdb_write_buffer_manager_set_buffer_size(write_buffer_manager, 300);
|
||||
CheckCondition(
|
||||
300 == rocksdb_write_buffer_manager_buffer_size(write_buffer_manager));
|
||||
|
||||
rocksdb_write_buffer_manager_destroy(write_buffer_manager);
|
||||
rocksdb_cache_destroy(lru);
|
||||
}
|
||||
|
||||
StartPhase("cancel_all_background_work");
|
||||
rocksdb_cancel_all_background_work(db, 1);
|
||||
|
||||
|
||||
+62
-14
@@ -115,6 +115,42 @@ void Compaction::GetBoundaryKeys(
|
||||
}
|
||||
}
|
||||
|
||||
void Compaction::GetBoundaryInternalKeys(
|
||||
VersionStorageInfo* vstorage,
|
||||
const std::vector<CompactionInputFiles>& inputs, InternalKey* smallest_key,
|
||||
InternalKey* largest_key, int exclude_level) {
|
||||
bool initialized = false;
|
||||
const InternalKeyComparator* icmp = vstorage->InternalComparator();
|
||||
for (size_t i = 0; i < inputs.size(); ++i) {
|
||||
if (inputs[i].files.empty() || inputs[i].level == exclude_level) {
|
||||
continue;
|
||||
}
|
||||
if (inputs[i].level == 0) {
|
||||
// we need to consider all files on level 0
|
||||
for (const auto* f : inputs[i].files) {
|
||||
if (!initialized || icmp->Compare(f->smallest, *smallest_key) < 0) {
|
||||
*smallest_key = f->smallest;
|
||||
}
|
||||
if (!initialized || icmp->Compare(f->largest, *largest_key) > 0) {
|
||||
*largest_key = f->largest;
|
||||
}
|
||||
initialized = true;
|
||||
}
|
||||
} else {
|
||||
// we only need to consider the first and last file
|
||||
if (!initialized ||
|
||||
icmp->Compare(inputs[i].files[0]->smallest, *smallest_key) < 0) {
|
||||
*smallest_key = inputs[i].files[0]->smallest;
|
||||
}
|
||||
if (!initialized ||
|
||||
icmp->Compare(inputs[i].files.back()->largest, *largest_key) > 0) {
|
||||
*largest_key = inputs[i].files.back()->largest;
|
||||
}
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<CompactionInputFiles> Compaction::PopulateWithAtomicBoundaries(
|
||||
VersionStorageInfo* vstorage, std::vector<CompactionInputFiles> inputs) {
|
||||
const Comparator* ucmp = vstorage->InternalComparator()->user_comparator();
|
||||
@@ -399,9 +435,14 @@ void Compaction::PopulatePenultimateLevelOutputRange() {
|
||||
}
|
||||
}
|
||||
|
||||
GetBoundaryKeys(input_vstorage_, inputs_,
|
||||
&penultimate_level_smallest_user_key_,
|
||||
&penultimate_level_largest_user_key_, exclude_level);
|
||||
// FIXME: should make use of `penultimate_output_range_type_`.
|
||||
// FIXME: when last level's input range does not overlap with
|
||||
// penultimate level, and penultimate level input is empty,
|
||||
// this call will not set penultimate_level_smallest_ or
|
||||
// penultimate_level_largest_. No keys will be compacted up.
|
||||
GetBoundaryInternalKeys(input_vstorage_, inputs_,
|
||||
&penultimate_level_smallest_,
|
||||
&penultimate_level_largest_, exclude_level);
|
||||
}
|
||||
|
||||
Compaction::~Compaction() {
|
||||
@@ -426,33 +467,40 @@ bool Compaction::OverlapPenultimateLevelOutputRange(
|
||||
if (!SupportsPerKeyPlacement()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// See FIXME in Compaction::PopulatePenultimateLevelOutputRange().
|
||||
// We do not compact any key up in this case.
|
||||
if (penultimate_level_smallest_.size() == 0 ||
|
||||
penultimate_level_largest_.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Comparator* ucmp =
|
||||
input_vstorage_->InternalComparator()->user_comparator();
|
||||
|
||||
return ucmp->CompareWithoutTimestamp(
|
||||
smallest_key, penultimate_level_largest_user_key_) <= 0 &&
|
||||
smallest_key, penultimate_level_largest_.user_key()) <= 0 &&
|
||||
ucmp->CompareWithoutTimestamp(
|
||||
largest_key, penultimate_level_smallest_user_key_) >= 0;
|
||||
largest_key, penultimate_level_smallest_.user_key()) >= 0;
|
||||
}
|
||||
|
||||
// key includes timestamp if user-defined timestamp is enabled.
|
||||
bool Compaction::WithinPenultimateLevelOutputRange(const Slice& key) const {
|
||||
bool Compaction::WithinPenultimateLevelOutputRange(
|
||||
const ParsedInternalKey& ikey) const {
|
||||
if (!SupportsPerKeyPlacement()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (penultimate_level_smallest_user_key_.empty() ||
|
||||
penultimate_level_largest_user_key_.empty()) {
|
||||
if (penultimate_level_smallest_.size() == 0 ||
|
||||
penultimate_level_largest_.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Comparator* ucmp =
|
||||
input_vstorage_->InternalComparator()->user_comparator();
|
||||
const InternalKeyComparator* icmp = input_vstorage_->InternalComparator();
|
||||
|
||||
return ucmp->CompareWithoutTimestamp(
|
||||
key, penultimate_level_smallest_user_key_) >= 0 &&
|
||||
ucmp->CompareWithoutTimestamp(
|
||||
key, penultimate_level_largest_user_key_) <= 0;
|
||||
// op_type of a key can change during compaction, e.g. Merge -> Put.
|
||||
return icmp->CompareKeySeq(ikey, penultimate_level_smallest_.Encode()) >= 0 &&
|
||||
icmp->CompareKeySeq(ikey, penultimate_level_largest_.Encode()) <= 0;
|
||||
}
|
||||
|
||||
bool Compaction::InputCompressionMatchesOutput() const {
|
||||
|
||||
+10
-13
@@ -353,14 +353,6 @@ class Compaction {
|
||||
|
||||
Slice GetLargestUserKey() const { return largest_user_key_; }
|
||||
|
||||
Slice GetPenultimateLevelSmallestUserKey() const {
|
||||
return penultimate_level_smallest_user_key_;
|
||||
}
|
||||
|
||||
Slice GetPenultimateLevelLargestUserKey() const {
|
||||
return penultimate_level_largest_user_key_;
|
||||
}
|
||||
|
||||
PenultimateOutputRangeType GetPenultimateOutputRangeType() const {
|
||||
return penultimate_output_range_type_;
|
||||
}
|
||||
@@ -383,10 +375,8 @@ class Compaction {
|
||||
// per_key_placement feature, which is safe to place the key to the
|
||||
// penultimate level. different compaction strategy has different rules.
|
||||
// If per_key_placement is not supported, always return false.
|
||||
// TODO: currently it doesn't support moving data from the last level to the
|
||||
// penultimate level
|
||||
// key includes timestamp if user-defined timestamp is enabled.
|
||||
bool WithinPenultimateLevelOutputRange(const Slice& key) const;
|
||||
bool WithinPenultimateLevelOutputRange(const ParsedInternalKey& ikey) const;
|
||||
|
||||
CompactionReason compaction_reason() const { return compaction_reason_; }
|
||||
|
||||
@@ -456,6 +446,13 @@ class Compaction {
|
||||
Slice* smallest_key, Slice* largest_key,
|
||||
int exclude_level = -1);
|
||||
|
||||
// get the smallest and largest internal key present in files to be compacted
|
||||
static void GetBoundaryInternalKeys(
|
||||
VersionStorageInfo* vstorage,
|
||||
const std::vector<CompactionInputFiles>& inputs,
|
||||
InternalKey* smallest_key, InternalKey* largest_key,
|
||||
int exclude_level = -1);
|
||||
|
||||
// populate penultimate level output range, which will be used to determine if
|
||||
// a key is safe to output to the penultimate level (details see
|
||||
// `Compaction::WithinPenultimateLevelOutputRange()`.
|
||||
@@ -568,8 +565,8 @@ class Compaction {
|
||||
// Key range for penultimate level output
|
||||
// includes timestamp if user-defined timestamp is enabled.
|
||||
// penultimate_output_range_type_ shows the range type
|
||||
Slice penultimate_level_smallest_user_key_;
|
||||
Slice penultimate_level_largest_user_key_;
|
||||
InternalKey penultimate_level_smallest_;
|
||||
InternalKey penultimate_level_largest_;
|
||||
PenultimateOutputRangeType penultimate_output_range_type_ =
|
||||
PenultimateOutputRangeType::kNotSupported;
|
||||
};
|
||||
|
||||
@@ -1227,7 +1227,7 @@ void CompactionIterator::DecideOutputLevel() {
|
||||
// not from this compaction.
|
||||
// TODO: add statistic for declined output_to_penultimate_level
|
||||
bool safe_to_penultimate_level =
|
||||
compaction_->WithinPenultimateLevelOutputRange(ikey_.user_key);
|
||||
compaction_->WithinPenultimateLevelOutputRange(ikey_);
|
||||
if (!safe_to_penultimate_level) {
|
||||
output_to_penultimate_level_ = false;
|
||||
// It could happen when disable/enable `last_level_temperature` while
|
||||
@@ -1256,10 +1256,13 @@ void CompactionIterator::PrepareOutput() {
|
||||
} else if (ikey_.type == kTypeBlobIndex) {
|
||||
GarbageCollectBlobIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
if (compaction_ != nullptr && compaction_->SupportsPerKeyPlacement()) {
|
||||
DecideOutputLevel();
|
||||
// For range del sentinel, we don't use it to cut files for bottommost
|
||||
// compaction. So it should not make a difference which output level we
|
||||
// decide.
|
||||
if (compaction_ != nullptr && compaction_->SupportsPerKeyPlacement()) {
|
||||
DecideOutputLevel();
|
||||
}
|
||||
}
|
||||
|
||||
// Zeroing out the sequence number leads to better compression.
|
||||
|
||||
@@ -119,7 +119,8 @@ class CompactionIterator {
|
||||
virtual bool SupportsPerKeyPlacement() const = 0;
|
||||
|
||||
// `key` includes timestamp if user-defined timestamp is enabled.
|
||||
virtual bool WithinPenultimateLevelOutputRange(const Slice& key) const = 0;
|
||||
virtual bool WithinPenultimateLevelOutputRange(
|
||||
const ParsedInternalKey&) const = 0;
|
||||
};
|
||||
|
||||
class RealCompaction : public CompactionProxy {
|
||||
@@ -186,8 +187,9 @@ class CompactionIterator {
|
||||
// Check if key is within penultimate level output range, to see if it's
|
||||
// safe to output to the penultimate level for per_key_placement feature.
|
||||
// `key` includes timestamp if user-defined timestamp is enabled.
|
||||
bool WithinPenultimateLevelOutputRange(const Slice& key) const override {
|
||||
return compaction_->WithinPenultimateLevelOutputRange(key);
|
||||
bool WithinPenultimateLevelOutputRange(
|
||||
const ParsedInternalKey& ikey) const override {
|
||||
return compaction_->WithinPenultimateLevelOutputRange(ikey);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -184,8 +184,9 @@ class FakeCompaction : public CompactionIterator::CompactionProxy {
|
||||
return supports_per_key_placement;
|
||||
}
|
||||
|
||||
bool WithinPenultimateLevelOutputRange(const Slice& key) const override {
|
||||
return (!key.starts_with("unsafe_pb"));
|
||||
bool WithinPenultimateLevelOutputRange(
|
||||
const ParsedInternalKey& key) const override {
|
||||
return (!key.user_key.starts_with("unsafe_pb"));
|
||||
}
|
||||
|
||||
bool key_not_exists_beyond_output_level = false;
|
||||
|
||||
@@ -1900,6 +1900,8 @@ Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact,
|
||||
sub_compact->start.has_value() ? &tmp_start : nullptr,
|
||||
sub_compact->end.has_value() ? &tmp_end : nullptr);
|
||||
if (oldest_ancester_time == std::numeric_limits<uint64_t>::max()) {
|
||||
// TODO: fix DBSSTTest.GetTotalSstFilesSize and use
|
||||
// kUnknownOldestAncesterTime
|
||||
oldest_ancester_time = current_time;
|
||||
}
|
||||
|
||||
|
||||
@@ -215,7 +215,9 @@ class CompactionJobTestBase : public testing::Test {
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr,
|
||||
/*io_tracer=*/nullptr, /*db_id*/ "", /*db_session_id*/ "")),
|
||||
/*io_tracer=*/nullptr, /*db_id=*/"", /*db_session_id=*/"",
|
||||
/*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr)),
|
||||
shutting_down_(false),
|
||||
mock_table_factory_(new mock::MockTableFactory()),
|
||||
error_handler_(nullptr, db_options_, &mutex_),
|
||||
@@ -540,11 +542,12 @@ class CompactionJobTestBase : public testing::Test {
|
||||
ASSERT_OK(s);
|
||||
db_options_.info_log = info_log;
|
||||
|
||||
versions_.reset(
|
||||
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id*/ "", /*db_session_id*/ ""));
|
||||
versions_.reset(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr));
|
||||
compaction_job_stats_.Reset();
|
||||
ASSERT_OK(SetIdentityFile(env_, dbname_));
|
||||
|
||||
@@ -1524,13 +1527,15 @@ TEST_F(CompactionJobTest, VerifyPenultimateLevelOutput) {
|
||||
{files0, files1, files2, files3}, input_levels,
|
||||
/*verify_func=*/[&](Compaction& comp) {
|
||||
for (char c = 'a'; c <= 'z'; c++) {
|
||||
std::string c_str;
|
||||
c_str = c;
|
||||
const Slice key(c_str);
|
||||
if (c == 'a') {
|
||||
ASSERT_FALSE(comp.WithinPenultimateLevelOutputRange(key));
|
||||
ParsedInternalKey pik("a", 0U, kTypeValue);
|
||||
ASSERT_FALSE(comp.WithinPenultimateLevelOutputRange(pik));
|
||||
} else {
|
||||
ASSERT_TRUE(comp.WithinPenultimateLevelOutputRange(key));
|
||||
std::string c_str{c};
|
||||
// WithinPenultimateLevelOutputRange checks internal key range.
|
||||
// 'z' is the last key, so set seqno properly.
|
||||
ParsedInternalKey pik(c_str, c == 'z' ? 12U : 0U, kTypeValue);
|
||||
ASSERT_TRUE(comp.WithinPenultimateLevelOutputRange(pik));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -85,7 +85,8 @@ class CompactionPickerTestBase : public testing::Test {
|
||||
vstorage_.reset(new VersionStorageInfo(
|
||||
&icmp_, ucmp_, options_.num_levels, style, nullptr, false,
|
||||
EpochNumberRequirement::kMustPresent, ioptions_.clock,
|
||||
options_.bottommost_file_compaction_delay));
|
||||
options_.bottommost_file_compaction_delay,
|
||||
OffpeakTimeOption(mutable_db_options_.daily_offpeak_time_utc)));
|
||||
vstorage_->PrepareForVersionAppend(ioptions_, mutable_cf_options_);
|
||||
}
|
||||
|
||||
@@ -95,7 +96,8 @@ class CompactionPickerTestBase : public testing::Test {
|
||||
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));
|
||||
ioptions_.clock, options_.bottommost_file_compaction_delay,
|
||||
OffpeakTimeOption(mutable_db_options_.daily_offpeak_time_utc)));
|
||||
}
|
||||
|
||||
void DeleteVersionStorage() {
|
||||
|
||||
@@ -1202,15 +1202,120 @@ TEST_P(TieredCompactionTest, RangeBasedTieredStorageLevel) {
|
||||
ASSERT_EQ("0,0,0,0,0,0,1", FilesPerLevel());
|
||||
ASSERT_EQ(GetSstSizeHelper(Temperature::kUnknown), 0);
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
|
||||
|
||||
ASSERT_EQ(
|
||||
options.statistics->getTickerCount(COMPACTION_RANGE_DEL_DROP_OBSOLETE),
|
||||
1);
|
||||
|
||||
// Tests that we only compact keys up to penultimate level
|
||||
// that are within penultimate level input's internal key range.
|
||||
{
|
||||
MutexLock l(&mutex);
|
||||
hot_start = Key(0);
|
||||
hot_end = Key(100);
|
||||
}
|
||||
const Snapshot* temp_snap = db_->GetSnapshot();
|
||||
// Key(0) and Key(1) here are inserted with higher sequence number
|
||||
// than Key(0) and Key(1) inserted above.
|
||||
// Only Key(0) in last level will be compacted up, not Key(1).
|
||||
ASSERT_OK(Put(Key(0), "value" + std::to_string(0)));
|
||||
ASSERT_OK(Put(Key(1), "value" + std::to_string(100)));
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
ASSERT_EQ("0,0,0,0,0,1,1", FilesPerLevel());
|
||||
{
|
||||
std::vector<LiveFileMetaData> metas;
|
||||
db_->GetLiveFilesMetaData(&metas);
|
||||
for (const auto& f : metas) {
|
||||
if (f.temperature == Temperature::kUnknown) {
|
||||
// Expect Key(0), Key(0), Key(1)
|
||||
ASSERT_EQ(f.num_entries, 3);
|
||||
ASSERT_EQ(f.smallestkey, Key(0));
|
||||
ASSERT_EQ(f.largestkey, Key(1));
|
||||
} else {
|
||||
ASSERT_EQ(f.temperature, Temperature::kCold);
|
||||
// Key(2)-Key(49) and Key(100).
|
||||
ASSERT_EQ(f.num_entries, 50);
|
||||
}
|
||||
}
|
||||
}
|
||||
db_->ReleaseSnapshot(temp_snap);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(TieredCompactionTest, TieredCompactionTest,
|
||||
testing::Bool());
|
||||
|
||||
TEST_P(TieredCompactionTest, CheckInternalKeyRange) {
|
||||
// When compacting keys from the last level to penultimate level,
|
||||
// output to penultimate level should be within internal key range
|
||||
// of input files from penultimate level.
|
||||
// Set up:
|
||||
// L5:
|
||||
// File 1: DeleteRange[1, 3)@4, File 2: [3@5, 100@6]
|
||||
// L6:
|
||||
// File 3: [2@1, 3@2], File 4: [50@3]
|
||||
//
|
||||
// When File 1 and File 3 are being compacted,
|
||||
// Key(3) cannot be compacted up, otherwise it causes
|
||||
// inconsistency where File 3's Key(3) has a lower sequence number
|
||||
// than File 2's Key(3).
|
||||
const int kNumLevels = 7;
|
||||
auto options = CurrentOptions();
|
||||
SetColdTemperature(options);
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
options.num_levels = kNumLevels;
|
||||
options.statistics = CreateDBStatistics();
|
||||
options.max_subcompactions = 10;
|
||||
options.preclude_last_level_data_seconds = 10000;
|
||||
DestroyAndReopen(options);
|
||||
auto cmp = options.comparator;
|
||||
|
||||
std::string hot_start = Key(0);
|
||||
std::string hot_end = Key(0);
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionIterator::PrepareOutput.context", [&](void* arg) {
|
||||
auto context = static_cast<PerKeyPlacementContext*>(arg);
|
||||
context->output_to_penultimate_level =
|
||||
cmp->Compare(context->key, hot_start) >= 0 &&
|
||||
cmp->Compare(context->key, hot_end) < 0;
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
// File 1
|
||||
ASSERT_OK(Put(Key(2), "val2"));
|
||||
ASSERT_OK(Put(Key(3), "val3"));
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(6);
|
||||
// File 2
|
||||
ASSERT_OK(Put(Key(50), "val50"));
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(6);
|
||||
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
hot_end = Key(100);
|
||||
std::string start = Key(1);
|
||||
std::string end = Key(3);
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), start, end));
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(5);
|
||||
// File 3
|
||||
ASSERT_OK(Put(Key(3), "vall"));
|
||||
ASSERT_OK(Put(Key(100), "val100"));
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(5);
|
||||
// Try to compact keys up
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
start = Key(1);
|
||||
end = Key(2);
|
||||
Slice begin_slice(start);
|
||||
Slice end_slice(end);
|
||||
ASSERT_OK(db_->CompactRange(cro, &begin_slice, &end_slice));
|
||||
// Without internal key range checking, we get the following error:
|
||||
// Corruption: force_consistency_checks(DEBUG): VersionBuilder: L5 has
|
||||
// overlapping ranges: file #18 largest key: '6B6579303030303033' seq:102,
|
||||
// type:1 vs. file #15 smallest key: '6B6579303030303033' seq:104, type:1
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
|
||||
class PrecludeLastLevelTest : public DBTestBase {
|
||||
public:
|
||||
PrecludeLastLevelTest()
|
||||
@@ -1971,13 +2076,7 @@ TEST_F(PrecludeLastLevelTest, PartialPenultimateLevelCompaction) {
|
||||
Close();
|
||||
}
|
||||
|
||||
// FIXME broken test:
|
||||
// dbfull()->TEST_WaitForCompact()
|
||||
// Corruption: force_consistency_checks(DEBUG): VersionBuilder: L5 has
|
||||
// overlapping ranges:
|
||||
// file #14 largest key: '6B6579303030303134' seq:32, type:1 vs.
|
||||
// file #19 smallest key: '6B6579303030303130' seq:10, type:1
|
||||
TEST_F(PrecludeLastLevelTest, DISABLED_RangeDelsCauseFileEndpointsToOverlap) {
|
||||
TEST_F(PrecludeLastLevelTest, RangeDelsCauseFileEndpointsToOverlap) {
|
||||
const int kNumLevels = 7;
|
||||
const int kSecondsPerKey = 10;
|
||||
const int kNumFiles = 3;
|
||||
|
||||
@@ -4415,6 +4415,8 @@ TEST_F(DBBasicTest, ManifestWriteFailure) {
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
options.env = env_;
|
||||
options.enable_blob_files = true;
|
||||
options.blob_file_size = 0;
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ASSERT_OK(Flush());
|
||||
@@ -4435,6 +4437,11 @@ TEST_F(DBBasicTest, ManifestWriteFailure) {
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Reopen(options);
|
||||
// The IO error was a mocked one from the `AfterSyncManifest` callback. The
|
||||
// Flush's VersionEdit actually made it into the Manifest. So these keys can
|
||||
// be read back. Read them to check all live sst files and blob files.
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
ASSERT_EQ("value", Get("key"));
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, DestroyDefaultCfHandle) {
|
||||
|
||||
+107
-7
@@ -19,7 +19,7 @@
|
||||
#include "rocksdb/concurrent_task_limiter.h"
|
||||
#include "rocksdb/experimental.h"
|
||||
#include "rocksdb/sst_file_writer.h"
|
||||
#include "rocksdb/utilities/convenience.h"
|
||||
#include "test_util/mock_time_env.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/concurrent_task_limiter_impl.h"
|
||||
@@ -4794,9 +4794,9 @@ TEST_F(DBCompactionTest, LevelTtlCascadingCompactions) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionEdit::EncodeTo:VarintOldestAncesterTime", [&](void* arg) {
|
||||
if (if_restart && if_open_all_files) {
|
||||
std::string* encoded_fieled = static_cast<std::string*>(arg);
|
||||
*encoded_fieled = "";
|
||||
PutVarint64(encoded_fieled, 0);
|
||||
std::string* encoded_field = static_cast<std::string*>(arg);
|
||||
*encoded_field = "";
|
||||
PutVarint64(encoded_field, 0);
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
@@ -4942,9 +4942,9 @@ TEST_F(DBCompactionTest, LevelPeriodicCompaction) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionEdit::EncodeTo:VarintFileCreationTime", [&](void* arg) {
|
||||
if (if_restart && if_open_all_files) {
|
||||
std::string* encoded_fieled = static_cast<std::string*>(arg);
|
||||
*encoded_fieled = "";
|
||||
PutVarint64(encoded_fieled, 0);
|
||||
std::string* encoded_field = static_cast<std::string*>(arg);
|
||||
*encoded_field = "";
|
||||
PutVarint64(encoded_field, 0);
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
@@ -5020,6 +5020,106 @@ TEST_F(DBCompactionTest, LevelPeriodicCompaction) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, LevelPeriodicCompactionOffpeak) {
|
||||
// This test simply checks if offpeak adjustment works in Leveled
|
||||
// Compactions. For testing offpeak periodic compactions in various
|
||||
// scenarios, please refer to
|
||||
// DBTestUniversalCompaction2::PeriodicCompactionOffpeak
|
||||
constexpr int kNumKeysPerFile = 32;
|
||||
constexpr int kNumLevelFiles = 2;
|
||||
constexpr int kValueSize = 100;
|
||||
constexpr int kSecondsPerDay = 86400;
|
||||
constexpr int kSecondsPerHour = 3600;
|
||||
constexpr int kSecondsPerMinute = 60;
|
||||
|
||||
for (bool if_restart : {false, true}) {
|
||||
SCOPED_TRACE("if_restart=" + std::to_string(if_restart));
|
||||
Options options = CurrentOptions();
|
||||
options.ttl = 0;
|
||||
options.periodic_compaction_seconds = 5 * kSecondsPerDay; // 5 days
|
||||
// In the case where all files are opened and doing DB restart
|
||||
// forcing the file creation time in manifest file to be 0 to
|
||||
// simulate the case of reading from an old version.
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionEdit::EncodeTo:VarintFileCreationTime", [&](void* arg) {
|
||||
if (if_restart) {
|
||||
std::string* encoded_field = static_cast<std::string*>(arg);
|
||||
*encoded_field = "";
|
||||
PutVarint64(encoded_field, 0);
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Just to add some extra random days to current time
|
||||
Random rnd(test::RandomSeed());
|
||||
int days = rnd.Uniform(100);
|
||||
|
||||
int periodic_compactions = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"LevelCompactionPicker::PickCompaction:Return", [&](void* arg) {
|
||||
Compaction* compaction = static_cast<Compaction*>(arg);
|
||||
auto compaction_reason = compaction->compaction_reason();
|
||||
if (compaction_reason == CompactionReason::kPeriodicCompaction) {
|
||||
periodic_compactions++;
|
||||
}
|
||||
});
|
||||
|
||||
// Starting at 12:15AM
|
||||
int now_hour = 0;
|
||||
int now_minute = 15;
|
||||
auto mock_clock = std::make_shared<MockSystemClock>(env_->GetSystemClock());
|
||||
auto mock_env = std::make_unique<CompositeEnvWrapper>(env_, mock_clock);
|
||||
options.env = mock_env.get();
|
||||
mock_clock->SetCurrentTime(days * kSecondsPerDay +
|
||||
now_hour * kSecondsPerHour +
|
||||
now_minute * kSecondsPerMinute);
|
||||
// Offpeak is set from 12:30AM to 4:30AM
|
||||
options.daily_offpeak_time_utc = "00:30-04:30";
|
||||
Reopen(options);
|
||||
|
||||
for (int i = 0; i < kNumLevelFiles; ++i) {
|
||||
for (int j = 0; j < kNumKeysPerFile; ++j) {
|
||||
ASSERT_OK(
|
||||
Put(Key(i * kNumKeysPerFile + j), rnd.RandomString(kValueSize)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ("2", FilesPerLevel());
|
||||
ASSERT_EQ(0, periodic_compactions);
|
||||
|
||||
// Move clock forward by 1 hour. Now at 1:15AM Day 0. No compaction.
|
||||
mock_clock->MockSleepForSeconds(1 * kSecondsPerHour);
|
||||
ASSERT_OK(Put("a", "1"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
// Assert that the files stay in the same level
|
||||
ASSERT_EQ("3", FilesPerLevel());
|
||||
ASSERT_EQ(0, periodic_compactions);
|
||||
MoveFilesToLevel(1);
|
||||
ASSERT_EQ("0,3", FilesPerLevel());
|
||||
|
||||
// Move clock forward by 4 days and check if it triggers periodic
|
||||
// comapaction at 1:15AM Day 4. Files created on Day 0 at 12:15AM is
|
||||
// expected to expire before the offpeak starts next day at 12:30AM
|
||||
mock_clock->MockSleepForSeconds(4 * kSecondsPerDay);
|
||||
ASSERT_OK(Put("b", "2"));
|
||||
if (if_restart) {
|
||||
Reopen(options);
|
||||
} else {
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ("1,3", FilesPerLevel());
|
||||
// The two old files go through the periodic compaction process
|
||||
ASSERT_EQ(2, periodic_compactions);
|
||||
|
||||
Destroy(options);
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, LevelPeriodicCompactionWithOldDB) {
|
||||
// This test makes sure that periodic compactions are working with a DB
|
||||
// where file_creation_time of some files is 0.
|
||||
|
||||
@@ -121,7 +121,7 @@ Status DBImpl::GetSortedWalFiles(VectorLogPtr& files) {
|
||||
|
||||
// DisableFileDeletions / EnableFileDeletions not supported in read-only DB
|
||||
if (deletions_disabled.ok()) {
|
||||
Status s2 = EnableFileDeletions(/*force*/ false);
|
||||
Status s2 = EnableFileDeletions(/*force=*/false);
|
||||
assert(s2.ok());
|
||||
s2.PermitUncheckedError();
|
||||
} else {
|
||||
|
||||
+82
-10
@@ -276,10 +276,11 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
this->RecordSeqnoToTimeMapping(/*populate_historical_seconds=*/0);
|
||||
});
|
||||
|
||||
versions_.reset(new VersionSet(dbname_, &immutable_db_options_, file_options_,
|
||||
table_cache_.get(), write_buffer_manager_,
|
||||
&write_controller_, &block_cache_tracer_,
|
||||
io_tracer_, db_id_, db_session_id_));
|
||||
versions_.reset(new VersionSet(
|
||||
dbname_, &immutable_db_options_, file_options_, table_cache_.get(),
|
||||
write_buffer_manager_, &write_controller_, &block_cache_tracer_,
|
||||
io_tracer_, db_id_, db_session_id_, options.daily_offpeak_time_utc,
|
||||
&error_handler_));
|
||||
column_family_memtables_.reset(
|
||||
new ColumnFamilyMemTablesImpl(versions_->GetColumnFamilySet()));
|
||||
|
||||
@@ -359,10 +360,8 @@ Status DBImpl::ResumeImpl(DBRecoverContext context) {
|
||||
if (io_s.IsIOError()) {
|
||||
// If resuming from IOError resulted from MANIFEST write, then assert
|
||||
// that we must have already set the MANIFEST writer to nullptr during
|
||||
// clean-up phase MANIFEST writing. We must have also disabled file
|
||||
// deletions.
|
||||
// clean-up phase MANIFEST writing.
|
||||
assert(!versions_->descriptor_log_);
|
||||
assert(!IsFileDeletionsEnabled());
|
||||
// Since we are trying to recover from MANIFEST write error, we need to
|
||||
// switch to a new MANIFEST anyway. The old MANIFEST can be corrupted.
|
||||
// Therefore, force writing a dummy version edit because we do not know
|
||||
@@ -1328,17 +1327,24 @@ Status DBImpl::SetDBOptions(
|
||||
const bool max_compactions_increased =
|
||||
new_bg_job_limits.max_compactions >
|
||||
current_bg_job_limits.max_compactions;
|
||||
const bool offpeak_time_changed =
|
||||
versions_->offpeak_time_option().daily_offpeak_time_utc !=
|
||||
new_db_options.daily_offpeak_time_utc;
|
||||
|
||||
if (max_flushes_increased || max_compactions_increased) {
|
||||
if (max_flushes_increased || max_compactions_increased ||
|
||||
offpeak_time_changed) {
|
||||
if (max_flushes_increased) {
|
||||
env_->IncBackgroundThreadsIfNeeded(new_bg_job_limits.max_flushes,
|
||||
Env::Priority::HIGH);
|
||||
}
|
||||
|
||||
if (max_compactions_increased) {
|
||||
env_->IncBackgroundThreadsIfNeeded(new_bg_job_limits.max_compactions,
|
||||
Env::Priority::LOW);
|
||||
}
|
||||
if (offpeak_time_changed) {
|
||||
versions_->ChangeOffpeakTimeOption(
|
||||
new_db_options.daily_offpeak_time_utc);
|
||||
}
|
||||
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
@@ -2050,6 +2056,73 @@ Status DBImpl::GetEntity(const ReadOptions& _read_options,
|
||||
return GetImpl(read_options, key, get_impl_options);
|
||||
}
|
||||
|
||||
Status DBImpl::GetEntity(const ReadOptions& _read_options, const Slice& key,
|
||||
PinnableAttributeGroups* result) {
|
||||
if (!result) {
|
||||
return Status::InvalidArgument(
|
||||
"Cannot call GetEntity without PinnableAttributeGroups object");
|
||||
}
|
||||
Status s;
|
||||
const size_t num_column_families = result->size();
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kGetEntity) {
|
||||
s = Status::InvalidArgument(
|
||||
"Cannot call GetEntity with `ReadOptions::io_activity` != "
|
||||
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kGetEntity`");
|
||||
for (size_t i = 0; i < num_column_families; ++i) {
|
||||
(*result)[i].SetStatus(s);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
// return early if no CF was passed in
|
||||
if (num_column_families == 0) {
|
||||
return s;
|
||||
}
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kGetEntity;
|
||||
}
|
||||
std::vector<Slice> keys;
|
||||
std::vector<ColumnFamilyHandle*> column_families;
|
||||
for (size_t i = 0; i < num_column_families; ++i) {
|
||||
// If any of the CFH is null, break early since the entire query will fail
|
||||
if (!(*result)[i].column_family()) {
|
||||
s = Status::InvalidArgument(
|
||||
"DB failed to query because one or more group(s) have null column "
|
||||
"family handle");
|
||||
(*result)[i].SetStatus(
|
||||
Status::InvalidArgument("Column family handle cannot be null"));
|
||||
break;
|
||||
}
|
||||
// Adding the same key slice for different CFs
|
||||
keys.emplace_back(key);
|
||||
column_families.emplace_back((*result)[i].column_family());
|
||||
}
|
||||
if (!s.ok()) {
|
||||
for (size_t i = 0; i < num_column_families; ++i) {
|
||||
if ((*result)[i].status().ok()) {
|
||||
(*result)[i].SetStatus(
|
||||
Status::Incomplete("DB not queried due to invalid argument(s) in "
|
||||
"one or more of the attribute groups"));
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
std::vector<PinnableWideColumns> columns(num_column_families);
|
||||
std::vector<Status> statuses(num_column_families);
|
||||
MultiGetCommon(
|
||||
read_options, num_column_families, column_families.data(), keys.data(),
|
||||
/* values */ nullptr, columns.data(),
|
||||
/* timestamps */ nullptr, statuses.data(), /* sorted_input */ false);
|
||||
// Set results
|
||||
for (size_t i = 0; i < num_column_families; ++i) {
|
||||
(*result)[i].Reset();
|
||||
(*result)[i].SetStatus(statuses[i]);
|
||||
(*result)[i].SetColumns(std::move(columns[i]));
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
bool DBImpl::ShouldReferenceSuperVersion(const MergeContext& merge_context) {
|
||||
// If both thresholds are reached, a function returning merge operands as
|
||||
// `PinnableSlice`s should reference the `SuperVersion` to avoid large and/or
|
||||
@@ -2829,7 +2902,6 @@ void DBImpl::MultiGetCommon(const ReadOptions& read_options,
|
||||
bool should_fail = false;
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
ColumnFamilyHandle* cfh = column_families[i];
|
||||
assert(cfh);
|
||||
if (read_options.timestamp) {
|
||||
statuses[i] = FailIfTsMismatchCf(cfh, *(read_options.timestamp));
|
||||
if (!statuses[i].ok()) {
|
||||
|
||||
+11
-7
@@ -197,6 +197,8 @@ class DBImpl : public DB {
|
||||
Status PutEntity(const WriteOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
const WideColumns& columns) override;
|
||||
Status PutEntity(const WriteOptions& options, const Slice& key,
|
||||
const AttributeGroups& attribute_groups) override;
|
||||
|
||||
using DB::Merge;
|
||||
Status Merge(const WriteOptions& options, ColumnFamilyHandle* column_family,
|
||||
@@ -242,6 +244,8 @@ class DBImpl : public DB {
|
||||
Status GetEntity(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableWideColumns* columns) override;
|
||||
Status GetEntity(const ReadOptions& options, const Slice& key,
|
||||
PinnableAttributeGroups* result) override;
|
||||
|
||||
using DB::GetMergeOperands;
|
||||
Status GetMergeOperands(const ReadOptions& options,
|
||||
@@ -1186,6 +1190,7 @@ class DBImpl : public DB {
|
||||
size_t TEST_GetWalPreallocateBlockSize(uint64_t write_buffer_size) const;
|
||||
void TEST_WaitForPeriodicTaskRun(std::function<void()> callback) const;
|
||||
SeqnoToTimeMapping TEST_GetSeqnoToTimeMapping() const;
|
||||
const autovector<uint64_t>& TEST_GetFilesToQuarantine() const;
|
||||
size_t TEST_EstimateInMemoryStatsHistorySize() const;
|
||||
|
||||
uint64_t TEST_GetCurrentLogNumber() const {
|
||||
@@ -1393,8 +1398,9 @@ class DBImpl : public DB {
|
||||
autovector<ColumnFamilyData*> cfds_;
|
||||
autovector<const MutableCFOptions*> mutable_cf_opts_;
|
||||
autovector<autovector<VersionEdit*>> edit_lists_;
|
||||
// files_to_delete_ contains sst files
|
||||
std::unordered_set<std::string> files_to_delete_;
|
||||
// Stale SST files to delete found upon recovery. This stores a mapping from
|
||||
// such a file's absolute path to its parent directory.
|
||||
std::unordered_map<std::string, std::string> files_to_delete_;
|
||||
bool is_new_db_ = false;
|
||||
};
|
||||
|
||||
@@ -2375,10 +2381,6 @@ class DBImpl : public DB {
|
||||
|
||||
Status DisableFileDeletionsWithLock();
|
||||
|
||||
// Safely decrease `disable_delete_obsolete_files_` by one while holding lock
|
||||
// and return its remaning value.
|
||||
int EnableFileDeletionsWithLock();
|
||||
|
||||
Status IncreaseFullHistoryTsLowImpl(ColumnFamilyData* cfd,
|
||||
std::string ts_low);
|
||||
|
||||
@@ -2868,7 +2870,9 @@ static void ClipToRange(T* ptr, V minvalue, V maxvalue) {
|
||||
|
||||
inline Status DBImpl::FailIfCfHasTs(
|
||||
const ColumnFamilyHandle* column_family) const {
|
||||
column_family = column_family ? column_family : DefaultColumnFamily();
|
||||
if (!column_family) {
|
||||
return Status::InvalidArgument("column family handle cannot be null");
|
||||
}
|
||||
assert(column_family);
|
||||
const Comparator* const ucmp = column_family->GetComparator();
|
||||
assert(ucmp);
|
||||
|
||||
@@ -2783,6 +2783,7 @@ void DBImpl::EnableManualCompaction() {
|
||||
|
||||
void DBImpl::MaybeScheduleFlushOrCompaction() {
|
||||
mutex_.AssertHeld();
|
||||
TEST_SYNC_POINT("DBImpl::MaybeScheduleFlushOrCompaction:Start");
|
||||
if (!opened_successfully_) {
|
||||
// Compaction may introduce data race to DB open
|
||||
return;
|
||||
|
||||
@@ -309,6 +309,10 @@ SeqnoToTimeMapping DBImpl::TEST_GetSeqnoToTimeMapping() const {
|
||||
return seqno_to_time_mapping_;
|
||||
}
|
||||
|
||||
const autovector<uint64_t>& DBImpl::TEST_GetFilesToQuarantine() const {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
return error_handler_.GetFilesToQuarantine();
|
||||
}
|
||||
|
||||
size_t DBImpl::TEST_EstimateInMemoryStatsHistorySize() const {
|
||||
InstrumentedMutexLock l(&const_cast<DBImpl*>(this)->stats_history_mutex_);
|
||||
|
||||
@@ -100,14 +100,6 @@ Status DBImpl::EnableFileDeletions(bool force) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
int DBImpl::EnableFileDeletionsWithLock() {
|
||||
mutex_.AssertHeld();
|
||||
// In case others have called EnableFileDeletions(true /* force */) in between
|
||||
disable_delete_obsolete_files_ =
|
||||
std::max(0, disable_delete_obsolete_files_ - 1);
|
||||
return disable_delete_obsolete_files_;
|
||||
}
|
||||
|
||||
bool DBImpl::IsFileDeletionsEnabled() const {
|
||||
return 0 == disable_delete_obsolete_files_;
|
||||
}
|
||||
@@ -154,6 +146,7 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
// mutex_ cannot be released. Otherwise, we might see no min_pending_output
|
||||
// here but later find newer generated unfinalized files while scanning.
|
||||
job_context->min_pending_output = MinObsoleteSstNumberToKeep();
|
||||
job_context->files_to_quarantine = error_handler_.GetFilesToQuarantine();
|
||||
|
||||
// Get obsolete files. This function will also update the list of
|
||||
// pending files in VersionSet().
|
||||
@@ -427,6 +420,8 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
state.blob_live.end());
|
||||
std::unordered_set<uint64_t> log_recycle_files_set(
|
||||
state.log_recycle_files.begin(), state.log_recycle_files.end());
|
||||
std::unordered_set<uint64_t> quarantine_files_set(
|
||||
state.files_to_quarantine.begin(), state.files_to_quarantine.end());
|
||||
|
||||
auto candidate_files = state.full_scan_candidate_files;
|
||||
candidate_files.reserve(
|
||||
@@ -530,6 +525,10 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (quarantine_files_set.find(number) != quarantine_files_set.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool keep = true;
|
||||
switch (type) {
|
||||
case kWalFile:
|
||||
@@ -1003,7 +1002,7 @@ Status DBImpl::DeleteUnreferencedSstFiles(RecoveryContext* recovery_ctx) {
|
||||
if (type == kTableFile && number >= next_file_number &&
|
||||
recovery_ctx->files_to_delete_.find(normalized_fpath) ==
|
||||
recovery_ctx->files_to_delete_.end()) {
|
||||
recovery_ctx->files_to_delete_.emplace(normalized_fpath);
|
||||
recovery_ctx->files_to_delete_.emplace(normalized_fpath, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -942,8 +942,11 @@ Status DBImpl::LogAndApplyForRecovery(const RecoveryContext& recovery_ctx) {
|
||||
recovery_ctx.edit_lists_, &mutex_, directories_.GetDbDir());
|
||||
if (s.ok() && !(recovery_ctx.files_to_delete_.empty())) {
|
||||
mutex_.Unlock();
|
||||
for (const auto& fname : recovery_ctx.files_to_delete_) {
|
||||
s = env_->DeleteFile(fname);
|
||||
for (const auto& stale_sst_file : recovery_ctx.files_to_delete_) {
|
||||
s = DeleteDBFile(&immutable_db_options_, stale_sst_file.first,
|
||||
stale_sst_file.second,
|
||||
/*force_bg=*/false,
|
||||
/*force_fg=*/false);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
@@ -1248,6 +1251,13 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& wal_numbers,
|
||||
}
|
||||
|
||||
SequenceNumber sequence = WriteBatchInternal::Sequence(batch_to_use);
|
||||
if (sequence > kMaxSequenceNumber) {
|
||||
reporter.Corruption(
|
||||
record.size(),
|
||||
Status::Corruption("sequence " + std::to_string(sequence) +
|
||||
" is too large"));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (immutable_db_options_.wal_recovery_mode ==
|
||||
WALRecoveryMode::kPointInTimeRecovery) {
|
||||
|
||||
@@ -53,6 +53,10 @@ class DBImplReadOnly : public DBImpl {
|
||||
const WideColumns& /* columns */) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
Status PutEntity(const WriteOptions& /* options */, const Slice& /* key */,
|
||||
const AttributeGroups& /* attribute_groups */) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
using DBImpl::Merge;
|
||||
virtual Status Merge(const WriteOptions& /*options*/,
|
||||
|
||||
@@ -136,6 +136,10 @@ class DBImplSecondary : public DBImpl {
|
||||
const WideColumns& /* columns */) override {
|
||||
return Status::NotSupported("Not supported operation in secondary mode.");
|
||||
}
|
||||
Status PutEntity(const WriteOptions& /* options */, const Slice& /* key */,
|
||||
const AttributeGroups& /* attribute_groups */) override {
|
||||
return Status::NotSupported("Not supported operation in secondary mode.");
|
||||
}
|
||||
|
||||
using DBImpl::Merge;
|
||||
Status Merge(const WriteOptions& /*options*/,
|
||||
|
||||
@@ -48,6 +48,17 @@ Status DBImpl::PutEntity(const WriteOptions& options,
|
||||
return DB::PutEntity(options, column_family, key, columns);
|
||||
}
|
||||
|
||||
Status DBImpl::PutEntity(const WriteOptions& options, const Slice& key,
|
||||
const AttributeGroups& attribute_groups) {
|
||||
for (const AttributeGroup& ag : attribute_groups) {
|
||||
const Status s = FailIfCfHasTs(ag.column_family());
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return DB::PutEntity(options, key, attribute_groups);
|
||||
}
|
||||
|
||||
Status DBImpl::Merge(const WriteOptions& o, ColumnFamilyHandle* column_family,
|
||||
const Slice& key, const Slice& val) {
|
||||
const Status s = FailIfCfHasTs(column_family);
|
||||
@@ -1814,11 +1825,9 @@ uint64_t DBImpl::GetMaxTotalWalSize() const {
|
||||
Status DBImpl::DelayWrite(uint64_t num_bytes, WriteThread& write_thread,
|
||||
const WriteOptions& write_options) {
|
||||
mutex_.AssertHeld();
|
||||
uint64_t time_delayed = 0;
|
||||
uint64_t start_time = 0;
|
||||
bool delayed = false;
|
||||
{
|
||||
StopWatch sw(immutable_db_options_.clock, stats_, WRITE_STALL,
|
||||
Histograms::HISTOGRAM_ENUM_MAX, &time_delayed);
|
||||
// To avoid parallel timed delays (bad throttling), only support them
|
||||
// on the primary write queue.
|
||||
uint64_t delay;
|
||||
@@ -1834,6 +1843,7 @@ Status DBImpl::DelayWrite(uint64_t num_bytes, WriteThread& write_thread,
|
||||
if (write_options.no_slowdown) {
|
||||
return Status::Incomplete("Write stall");
|
||||
}
|
||||
start_time = immutable_db_options_.clock->NowMicros();
|
||||
TEST_SYNC_POINT("DBImpl::DelayWrite:Sleep");
|
||||
|
||||
// Notify write_thread about the stall so it can setup a barrier and
|
||||
@@ -1846,7 +1856,7 @@ Status DBImpl::DelayWrite(uint64_t num_bytes, WriteThread& write_thread,
|
||||
// (slightly longer because WriteController minimum delay is 1ms, in
|
||||
// case of sleep imprecision, rounding, etc.)
|
||||
const uint64_t kDelayInterval = 1001;
|
||||
uint64_t stall_end = sw.start_time() + delay;
|
||||
uint64_t stall_end = start_time + delay;
|
||||
while (write_controller_.NeedsDelay()) {
|
||||
if (immutable_db_options_.clock->NowMicros() >= stall_end) {
|
||||
// We already delayed this write `delay` microseconds
|
||||
@@ -1887,9 +1897,11 @@ Status DBImpl::DelayWrite(uint64_t num_bytes, WriteThread& write_thread,
|
||||
}
|
||||
assert(!delayed || !write_options.no_slowdown);
|
||||
if (delayed) {
|
||||
auto time_delayed = immutable_db_options_.clock->NowMicros() - start_time;
|
||||
default_cf_internal_stats_->AddDBStats(
|
||||
InternalStats::kIntStatsWriteStallMicros, time_delayed);
|
||||
RecordTick(stats_, STALL_MICROS, time_delayed);
|
||||
RecordInHistogram(stats_, WRITE_STALL, time_delayed);
|
||||
}
|
||||
|
||||
// If DB is not in read-only mode and write_controller is not stopping
|
||||
@@ -1956,9 +1968,13 @@ Status DBImpl::ThrottleLowPriWritesIfNeeded(const WriteOptions& write_options,
|
||||
// a chance to run. Now we guarantee we are still slowly making
|
||||
// progress.
|
||||
PERF_TIMER_GUARD(write_delay_time);
|
||||
write_controller_.low_pri_rate_limiter()->Request(
|
||||
my_batch->GetDataSize(), Env::IO_HIGH, nullptr /* stats */,
|
||||
RateLimiter::OpType::kWrite);
|
||||
auto data_size = my_batch->GetDataSize();
|
||||
while (data_size > 0) {
|
||||
size_t allowed = write_controller_.low_pri_rate_limiter()->RequestToken(
|
||||
data_size, 0 /* alignment */, Env::IO_HIGH, nullptr /* stats */,
|
||||
RateLimiter::OpType::kWrite);
|
||||
data_size -= allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
@@ -2381,6 +2397,22 @@ Status DB::PutEntity(const WriteOptions& options,
|
||||
return Write(options, &batch);
|
||||
}
|
||||
|
||||
Status DB::PutEntity(const WriteOptions& options, const Slice& key,
|
||||
const AttributeGroups& attribute_groups) {
|
||||
ColumnFamilyHandle* default_cf = DefaultColumnFamily();
|
||||
assert(default_cf);
|
||||
const Comparator* const default_cf_ucmp = default_cf->GetComparator();
|
||||
assert(default_cf_ucmp);
|
||||
WriteBatch batch(0 /* reserved_bytes */, 0 /* max_bytes */,
|
||||
options.protection_bytes_per_key,
|
||||
default_cf_ucmp->timestamp_size());
|
||||
const Status s = batch.PutEntity(key, attribute_groups);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
return Write(options, &batch);
|
||||
}
|
||||
|
||||
Status DB::Delete(const WriteOptions& opt, ColumnFamilyHandle* column_family,
|
||||
const Slice& key) {
|
||||
WriteBatch batch(0 /* reserved_bytes */, 0 /* max_bytes */,
|
||||
|
||||
@@ -114,6 +114,9 @@ Status DBIter::GetProperty(std::string prop_name, std::string* prop) {
|
||||
} else if (prop_name == "rocksdb.iterator.internal-key") {
|
||||
*prop = saved_key_.GetUserKey().ToString();
|
||||
return Status::OK();
|
||||
} else if (prop_name == "rocksdb.iterator.write-time") {
|
||||
// TODO(yuzhangyu): implement return the actual write time.
|
||||
return Status::NotSupported("write time property is under construction");
|
||||
}
|
||||
return Status::InvalidArgument("Unidentified property.");
|
||||
}
|
||||
|
||||
@@ -236,7 +236,7 @@ TEST_F(DBTestXactLogIterator, TransactionLogIteratorCorruptedLog) {
|
||||
ASSERT_OK(test::TruncateFile(env_, logfile_path,
|
||||
wal_files.front()->SizeFileBytes() / 2));
|
||||
|
||||
ASSERT_OK(db_->EnableFileDeletions());
|
||||
ASSERT_OK(db_->EnableFileDeletions(/*force=*/false));
|
||||
|
||||
// Insert a new entry to a new log file
|
||||
ASSERT_OK(Put("key1025", DummyString(10)));
|
||||
|
||||
+93
-107
@@ -1099,7 +1099,7 @@ TEST_F(DBOptionsTest, SetFIFOCompactionOptions) {
|
||||
ASSERT_EQ(fifo_temp_opt[1].age, 30000);
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, OffPeakTimes) {
|
||||
TEST_F(DBOptionsTest, OffpeakTimes) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
Random rnd(test::RandomSeed());
|
||||
@@ -1155,139 +1155,125 @@ TEST_F(DBOptionsTest, OffPeakTimes) {
|
||||
verify_valid();
|
||||
}
|
||||
|
||||
auto verify_is_now_offpeak = [&](bool expected, int now_utc_hour,
|
||||
int now_utc_minute, int now_utc_second = 0) {
|
||||
auto verify_offpeak_info = [&](bool expected_is_now_off_peak,
|
||||
int expected_seconds_till_next_offpeak_start,
|
||||
int now_utc_hour, int now_utc_minute,
|
||||
int now_utc_second = 0) {
|
||||
auto mock_clock = std::make_shared<MockSystemClock>(env_->GetSystemClock());
|
||||
// Add some extra random days to current time
|
||||
int days = rnd.Uniform(100);
|
||||
mock_clock->SetCurrentTime(days * 86400 + now_utc_hour * 3600 +
|
||||
now_utc_minute * 60 + now_utc_second);
|
||||
mock_clock->SetCurrentTime(
|
||||
days * OffpeakTimeOption::kSecondsPerDay +
|
||||
now_utc_hour * OffpeakTimeOption::kSecondsPerHour +
|
||||
now_utc_minute * OffpeakTimeOption::kSecondsPerMinute + now_utc_second);
|
||||
Status s = DBImpl::TEST_ValidateOptions(options);
|
||||
ASSERT_OK(s);
|
||||
auto db_options = MutableDBOptions(options);
|
||||
ASSERT_EQ(expected, db_options.IsNowOffPeak(mock_clock.get()));
|
||||
auto offpeak_option = OffpeakTimeOption(options.daily_offpeak_time_utc);
|
||||
int64_t now;
|
||||
ASSERT_OK(mock_clock.get()->GetCurrentTime(&now));
|
||||
auto offpeak_info = offpeak_option.GetOffpeakTimeInfo(now);
|
||||
ASSERT_EQ(expected_is_now_off_peak, offpeak_info.is_now_offpeak);
|
||||
ASSERT_EQ(expected_seconds_till_next_offpeak_start,
|
||||
offpeak_info.seconds_till_next_offpeak_start);
|
||||
};
|
||||
|
||||
options.daily_offpeak_time_utc = "";
|
||||
verify_is_now_offpeak(false, 12, 30);
|
||||
verify_offpeak_info(false, 0, 12, 30);
|
||||
|
||||
options.daily_offpeak_time_utc = "06:30-11:30";
|
||||
verify_is_now_offpeak(false, 5, 30);
|
||||
verify_is_now_offpeak(true, 6, 30);
|
||||
verify_is_now_offpeak(true, 10, 30);
|
||||
verify_is_now_offpeak(true, 11, 30);
|
||||
verify_is_now_offpeak(false, 13, 30);
|
||||
verify_offpeak_info(false, 1 * OffpeakTimeOption::kSecondsPerHour, 5, 30);
|
||||
verify_offpeak_info(true, 24 * OffpeakTimeOption::kSecondsPerHour, 6, 30);
|
||||
verify_offpeak_info(true, 20 * OffpeakTimeOption::kSecondsPerHour, 10, 30);
|
||||
verify_offpeak_info(true, 19 * OffpeakTimeOption::kSecondsPerHour, 11, 30);
|
||||
verify_offpeak_info(false, 17 * OffpeakTimeOption::kSecondsPerHour, 13, 30);
|
||||
|
||||
options.daily_offpeak_time_utc = "23:30-04:30";
|
||||
verify_is_now_offpeak(false, 6, 30);
|
||||
verify_is_now_offpeak(true, 23, 30);
|
||||
verify_is_now_offpeak(true, 0, 0);
|
||||
verify_is_now_offpeak(true, 1, 0);
|
||||
verify_is_now_offpeak(true, 4, 30);
|
||||
verify_is_now_offpeak(false, 4, 31);
|
||||
verify_offpeak_info(false, 17 * OffpeakTimeOption::kSecondsPerHour, 6, 30);
|
||||
verify_offpeak_info(true, 24 * OffpeakTimeOption::kSecondsPerHour, 23, 30);
|
||||
verify_offpeak_info(true,
|
||||
23 * OffpeakTimeOption::kSecondsPerHour +
|
||||
30 * OffpeakTimeOption::kSecondsPerMinute,
|
||||
0, 0);
|
||||
verify_offpeak_info(true,
|
||||
22 * OffpeakTimeOption::kSecondsPerHour +
|
||||
30 * OffpeakTimeOption::kSecondsPerMinute,
|
||||
1, 0);
|
||||
verify_offpeak_info(true, 19 * OffpeakTimeOption::kSecondsPerHour, 4, 30);
|
||||
verify_offpeak_info(false,
|
||||
18 * OffpeakTimeOption::kSecondsPerHour +
|
||||
59 * OffpeakTimeOption::kSecondsPerMinute,
|
||||
4, 31);
|
||||
|
||||
// Entire day offpeak
|
||||
options.daily_offpeak_time_utc = "00:00-23:59";
|
||||
verify_is_now_offpeak(true, 0, 0);
|
||||
verify_is_now_offpeak(true, 12, 00);
|
||||
verify_is_now_offpeak(true, 23, 59);
|
||||
verify_is_now_offpeak(true, 23, 59, 1);
|
||||
verify_is_now_offpeak(true, 23, 59, 59);
|
||||
verify_offpeak_info(true, 24 * OffpeakTimeOption::kSecondsPerHour, 0, 0);
|
||||
verify_offpeak_info(true, 12 * OffpeakTimeOption::kSecondsPerHour, 12, 00);
|
||||
verify_offpeak_info(true, 1 * OffpeakTimeOption::kSecondsPerMinute, 23, 59);
|
||||
verify_offpeak_info(true, 59, 23, 59, 1);
|
||||
verify_offpeak_info(true, 1, 23, 59, 59);
|
||||
|
||||
// Open the db and test by Get/SetDBOptions
|
||||
options.daily_offpeak_time_utc = "";
|
||||
// Start with a valid option
|
||||
options.daily_offpeak_time_utc = "01:30-04:15";
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_EQ("", dbfull()->GetDBOptions().daily_offpeak_time_utc);
|
||||
ASSERT_EQ("01:30-04:15", dbfull()->GetDBOptions().daily_offpeak_time_utc);
|
||||
|
||||
int may_schedule_compaction_called = 0;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::MaybeScheduleFlushOrCompaction:Start",
|
||||
[&](void*) { may_schedule_compaction_called++; });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Make sure calling SetDBOptions with invalid option does not change the
|
||||
// value nor call MaybeScheduleFlushOrCompaction()
|
||||
for (std::string invalid_case : invalid_cases) {
|
||||
ASSERT_NOK(
|
||||
dbfull()->SetDBOptions({{"daily_offpeak_time_utc", invalid_case}}));
|
||||
ASSERT_EQ("01:30-04:15", dbfull()
|
||||
->GetVersionSet()
|
||||
->offpeak_time_option()
|
||||
.daily_offpeak_time_utc);
|
||||
ASSERT_EQ(1 * kSecondInHour + 30 * kSecondInMinute,
|
||||
dbfull()
|
||||
->GetVersionSet()
|
||||
->offpeak_time_option()
|
||||
.daily_offpeak_start_time_utc);
|
||||
ASSERT_EQ(4 * kSecondInHour + 15 * kSecondInMinute,
|
||||
dbfull()
|
||||
->GetVersionSet()
|
||||
->offpeak_time_option()
|
||||
.daily_offpeak_end_time_utc);
|
||||
}
|
||||
ASSERT_EQ(0, may_schedule_compaction_called);
|
||||
|
||||
// Changing to new valid values should call MaybeScheduleFlushOrCompaction()
|
||||
// and sets the offpeak_time_option in VersionSet
|
||||
int expected_count = 0;
|
||||
for (std::string valid_case : valid_cases) {
|
||||
if (dbfull()
|
||||
->GetVersionSet()
|
||||
->offpeak_time_option()
|
||||
.daily_offpeak_time_utc != valid_case) {
|
||||
expected_count++;
|
||||
}
|
||||
ASSERT_OK(dbfull()->SetDBOptions({{"daily_offpeak_time_utc", valid_case}}));
|
||||
ASSERT_EQ(valid_case, dbfull()->GetDBOptions().daily_offpeak_time_utc);
|
||||
ASSERT_EQ(valid_case, dbfull()
|
||||
->GetVersionSet()
|
||||
->offpeak_time_option()
|
||||
.daily_offpeak_time_utc);
|
||||
}
|
||||
Close();
|
||||
ASSERT_EQ(expected_count, may_schedule_compaction_called);
|
||||
|
||||
// Sets off-peak time from 11:30PM to 4:30AM next day.
|
||||
// Starting at 1:30PM, use mock sleep to make time pass
|
||||
// and see if IsNowOffPeak() returns correctly per time changes
|
||||
int now_hour = 13;
|
||||
int now_minute = 30;
|
||||
options.daily_offpeak_time_utc = "23:30-04:30";
|
||||
auto mock_clock = std::make_shared<MockSystemClock>(env_->GetSystemClock());
|
||||
auto mock_env = std::make_unique<CompositeEnvWrapper>(env_, mock_clock);
|
||||
// Add some extra random days to current time
|
||||
int days = rnd.Uniform(100);
|
||||
mock_clock->SetCurrentTime(days * 86400 + now_hour * 3600 + now_minute * 60);
|
||||
options.env = mock_env.get();
|
||||
|
||||
// Starting at 1:30PM. It's not off-peak
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_FALSE(MutableDBOptions(dbfull()->GetDBOptions())
|
||||
.IsNowOffPeak(mock_clock.get()));
|
||||
|
||||
// Now it's at 4:30PM. Still not off-peak
|
||||
mock_clock->MockSleepForSeconds(3 * 3600);
|
||||
ASSERT_FALSE(MutableDBOptions(dbfull()->GetDBOptions())
|
||||
.IsNowOffPeak(mock_clock.get()));
|
||||
|
||||
// Now it's at 11:30PM. It's off-peak
|
||||
mock_clock->MockSleepForSeconds(7 * 3600);
|
||||
ASSERT_TRUE(MutableDBOptions(dbfull()->GetDBOptions())
|
||||
.IsNowOffPeak(mock_clock.get()));
|
||||
|
||||
// Now it's at 2:30AM next day. It's still off-peak
|
||||
mock_clock->MockSleepForSeconds(3 * 3600);
|
||||
ASSERT_TRUE(MutableDBOptions(dbfull()->GetDBOptions())
|
||||
.IsNowOffPeak(mock_clock.get()));
|
||||
|
||||
// Now it's at 4:30AM. It's still off-peak
|
||||
mock_clock->MockSleepForSeconds(2 * 3600);
|
||||
ASSERT_TRUE(MutableDBOptions(dbfull()->GetDBOptions())
|
||||
.IsNowOffPeak(mock_clock.get()));
|
||||
|
||||
// Sleep for one more minute. It's at 4:31AM It's no longer off-peak
|
||||
mock_clock->MockSleepForSeconds(60);
|
||||
ASSERT_FALSE(MutableDBOptions(dbfull()->GetDBOptions())
|
||||
.IsNowOffPeak(mock_clock.get()));
|
||||
Close();
|
||||
|
||||
// Entire day offpeak
|
||||
options.daily_offpeak_time_utc = "00:00-23:59";
|
||||
DestroyAndReopen(options);
|
||||
// It doesn't matter what time it is. It should be just offpeak.
|
||||
ASSERT_TRUE(MutableDBOptions(dbfull()->GetDBOptions())
|
||||
.IsNowOffPeak(mock_clock.get()));
|
||||
|
||||
// Mock Sleep for 3 hours. It's still off-peak
|
||||
mock_clock->MockSleepForSeconds(3 * 3600);
|
||||
ASSERT_TRUE(MutableDBOptions(dbfull()->GetDBOptions())
|
||||
.IsNowOffPeak(mock_clock.get()));
|
||||
|
||||
// Mock Sleep for 20 hours. It's still off-peak
|
||||
mock_clock->MockSleepForSeconds(20 * 3600);
|
||||
ASSERT_TRUE(MutableDBOptions(dbfull()->GetDBOptions())
|
||||
.IsNowOffPeak(mock_clock.get()));
|
||||
|
||||
// Mock Sleep for 59 minutes. It's still off-peak
|
||||
mock_clock->MockSleepForSeconds(59 * 60);
|
||||
ASSERT_TRUE(MutableDBOptions(dbfull()->GetDBOptions())
|
||||
.IsNowOffPeak(mock_clock.get()));
|
||||
|
||||
// Mock Sleep for 59 seconds. It's still off-peak
|
||||
mock_clock->MockSleepForSeconds(59);
|
||||
ASSERT_TRUE(MutableDBOptions(dbfull()->GetDBOptions())
|
||||
.IsNowOffPeak(mock_clock.get()));
|
||||
|
||||
// Mock Sleep for 1 second (exactly 24h passed). It's still off-peak
|
||||
mock_clock->MockSleepForSeconds(1);
|
||||
ASSERT_TRUE(MutableDBOptions(dbfull()->GetDBOptions())
|
||||
.IsNowOffPeak(mock_clock.get()));
|
||||
// Another second for sanity check
|
||||
mock_clock->MockSleepForSeconds(1);
|
||||
ASSERT_TRUE(MutableDBOptions(dbfull()->GetDBOptions())
|
||||
.IsNowOffPeak(mock_clock.get()));
|
||||
// Changing to the same value should not call MaybeScheduleFlushOrCompaction()
|
||||
ASSERT_OK(
|
||||
dbfull()->SetDBOptions({{"daily_offpeak_time_utc", "06:30-11:30"}}));
|
||||
may_schedule_compaction_called = 0;
|
||||
ASSERT_OK(
|
||||
dbfull()->SetDBOptions({{"daily_offpeak_time_utc", "06:30-11:30"}}));
|
||||
ASSERT_EQ(0, may_schedule_compaction_called);
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
Close();
|
||||
}
|
||||
|
||||
|
||||
@@ -107,12 +107,12 @@ TEST_F(DBPropertiesTest, Empty) {
|
||||
dbfull()->GetProperty("rocksdb.is-file-deletions-enabled", &num));
|
||||
ASSERT_EQ("0", num);
|
||||
|
||||
ASSERT_OK(db_->EnableFileDeletions(false));
|
||||
ASSERT_OK(db_->EnableFileDeletions(/*force=*/false));
|
||||
ASSERT_TRUE(
|
||||
dbfull()->GetProperty("rocksdb.is-file-deletions-enabled", &num));
|
||||
ASSERT_EQ("0", num);
|
||||
|
||||
ASSERT_OK(db_->EnableFileDeletions());
|
||||
ASSERT_OK(db_->EnableFileDeletions(/*force=*/true));
|
||||
ASSERT_TRUE(
|
||||
dbfull()->GetProperty("rocksdb.is-file-deletions-enabled", &num));
|
||||
ASSERT_EQ("1", num);
|
||||
@@ -1084,12 +1084,14 @@ class CountingUserTblPropCollector : public TablePropertiesCollector {
|
||||
const char* Name() const override { return "CountingUserTblPropCollector"; }
|
||||
|
||||
Status Finish(UserCollectedProperties* properties) override {
|
||||
assert(!finish_called_);
|
||||
std::string encoded;
|
||||
PutVarint32(&encoded, count_);
|
||||
*properties = UserCollectedProperties{
|
||||
{"CountingUserTblPropCollector", message_},
|
||||
{"Count", encoded},
|
||||
};
|
||||
finish_called_ = true;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -1101,12 +1103,14 @@ class CountingUserTblPropCollector : public TablePropertiesCollector {
|
||||
}
|
||||
|
||||
UserCollectedProperties GetReadableProperties() const override {
|
||||
assert(finish_called_);
|
||||
return UserCollectedProperties{};
|
||||
}
|
||||
|
||||
private:
|
||||
std::string message_ = "Rocksdb";
|
||||
uint32_t count_ = 0;
|
||||
bool finish_called_ = false;
|
||||
};
|
||||
|
||||
class CountingUserTblPropCollectorFactory
|
||||
@@ -1740,7 +1744,7 @@ TEST_F(DBPropertiesTest, SstFilesSize) {
|
||||
ASSERT_EQ(obsolete_sst_size, sst_size);
|
||||
|
||||
// Let the obsolete files be deleted.
|
||||
ASSERT_OK(db_->EnableFileDeletions());
|
||||
ASSERT_OK(db_->EnableFileDeletions(/*force=*/false));
|
||||
ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kObsoleteSstFilesSize,
|
||||
&obsolete_sst_size));
|
||||
ASSERT_EQ(obsolete_sst_size, 0);
|
||||
|
||||
+26
-3
@@ -937,12 +937,21 @@ INSTANTIATE_TEST_CASE_P(DBWALTestWithParam, DBWALTestWithParam,
|
||||
::testing::Values(std::make_tuple("", true),
|
||||
std::make_tuple("_wal_dir", false)));
|
||||
|
||||
TEST_F(DBSSTTest, OpenDBWithExistingTrash) {
|
||||
TEST_F(DBSSTTest, OpenDBWithExistingTrashAndObsoleteSstFile) {
|
||||
Options options = CurrentOptions();
|
||||
|
||||
options.sst_file_manager.reset(
|
||||
NewSstFileManager(env_, nullptr, "", 1024 * 1024 /* 1 MB/sec */));
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(options.sst_file_manager.get());
|
||||
// Set an extra high trash ratio to prevent immediate/non-rate limited
|
||||
// deletions
|
||||
sfm->SetDeleteRateBytesPerSecond(1024 * 1024);
|
||||
sfm->delete_scheduler()->SetMaxTrashDBRatio(1000.0);
|
||||
|
||||
int bg_delete_file = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DeleteScheduler::DeleteTrashFile:DeleteFile",
|
||||
[&](void* /*arg*/) { bg_delete_file++; });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Destroy(last_options_);
|
||||
|
||||
@@ -951,13 +960,22 @@ TEST_F(DBSSTTest, OpenDBWithExistingTrash) {
|
||||
ASSERT_OK(WriteStringToFile(env_, "abc", dbname_ + "/" + "001.sst.trash"));
|
||||
ASSERT_OK(WriteStringToFile(env_, "abc", dbname_ + "/" + "002.sst.trash"));
|
||||
ASSERT_OK(WriteStringToFile(env_, "abc", dbname_ + "/" + "003.sst.trash"));
|
||||
// Manually add an obsolete sst file. Obsolete SST files are discovered and
|
||||
// deleted upon recovery.
|
||||
constexpr uint64_t kSstFileNumber = 100;
|
||||
const std::string kObsoleteSstFile =
|
||||
MakeTableFileName(dbname_, kSstFileNumber);
|
||||
ASSERT_OK(WriteStringToFile(env_, "abc", kObsoleteSstFile));
|
||||
|
||||
// Reopen the DB and verify that it deletes existing trash files
|
||||
// Reopen the DB and verify that it deletes existing trash files and obsolete
|
||||
// SST files with rate limiting.
|
||||
Reopen(options);
|
||||
sfm->WaitForEmptyTrash();
|
||||
ASSERT_NOK(env_->FileExists(dbname_ + "/" + "001.sst.trash"));
|
||||
ASSERT_NOK(env_->FileExists(dbname_ + "/" + "002.sst.trash"));
|
||||
ASSERT_NOK(env_->FileExists(dbname_ + "/" + "003.sst.trash"));
|
||||
ASSERT_NOK(env_->FileExists(kObsoleteSstFile));
|
||||
ASSERT_EQ(bg_delete_file, 4);
|
||||
}
|
||||
|
||||
// Create a DB with 2 db_paths, and generate multiple files in the 2
|
||||
@@ -1520,6 +1538,11 @@ TEST_F(DBSSTTest, OpenDBWithInfiniteMaxOpenFilesSubjectToMemoryLimit) {
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, GetTotalSstFilesSize) {
|
||||
// FIXME: L0 file and L1+ file also differ in size of `oldest_key_time`.
|
||||
// L0 file has non-zero `oldest_key_time` while L1+ files have 0.
|
||||
// The test passes since L1+ file uses current time instead of 0
|
||||
// as oldest_ancestor_time.
|
||||
//
|
||||
// We don't propagate oldest-key-time table property on compaction and
|
||||
// just write 0 as default value. This affect the exact table size, since
|
||||
// we encode table properties as varint64. Force time to be 0 to work around
|
||||
|
||||
+13
-3
@@ -3882,16 +3882,26 @@ TEST_F(DBTest2, LowPriWrite) {
|
||||
ASSERT_OK(Put("", "", wo));
|
||||
ASSERT_EQ(1, rate_limit_count.load());
|
||||
|
||||
wo.low_pri = true;
|
||||
std::string big_value = std::string(1 * 1024 * 1024, 'x');
|
||||
ASSERT_OK(Put("", big_value, wo));
|
||||
ASSERT_LT(1, rate_limit_count.load());
|
||||
// Reset
|
||||
rate_limit_count = 0;
|
||||
wo.low_pri = false;
|
||||
ASSERT_OK(Put("", big_value, wo));
|
||||
ASSERT_EQ(0, rate_limit_count.load());
|
||||
|
||||
TEST_SYNC_POINT("DBTest.LowPriWrite:0");
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
wo.low_pri = true;
|
||||
ASSERT_OK(Put("", "", wo));
|
||||
ASSERT_EQ(1, rate_limit_count.load());
|
||||
ASSERT_EQ(0, rate_limit_count.load());
|
||||
wo.low_pri = false;
|
||||
ASSERT_OK(Put("", "", wo));
|
||||
ASSERT_EQ(1, rate_limit_count.load());
|
||||
ASSERT_EQ(0, rate_limit_count.load());
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, RateLimitedCompactionReads) {
|
||||
@@ -4125,7 +4135,7 @@ TEST_F(DBTest2, LiveFilesOmitObsoleteFiles) {
|
||||
ASSERT_OK(env_->FileExists(LogFileName(dbname_, log_file->LogNumber())));
|
||||
}
|
||||
|
||||
ASSERT_OK(db_->EnableFileDeletions());
|
||||
ASSERT_OK(db_->EnableFileDeletions(/*force=*/false));
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,12 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/utilities/table_properties_collectors.h"
|
||||
#include "test_util/mock_time_env.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/random.h"
|
||||
@@ -2230,6 +2233,141 @@ TEST_F(DBTestUniversalCompaction2, PeriodicCompaction) {
|
||||
ASSERT_EQ(4, output_level);
|
||||
}
|
||||
|
||||
TEST_F(DBTestUniversalCompaction2, PeriodicCompactionOffpeak) {
|
||||
constexpr int kSecondsPerDay = 86400;
|
||||
constexpr int kSecondsPerHour = 3600;
|
||||
constexpr int kSecondsPerMinute = 60;
|
||||
|
||||
Options opts = CurrentOptions();
|
||||
opts.compaction_style = kCompactionStyleUniversal;
|
||||
opts.level0_file_num_compaction_trigger = 10;
|
||||
opts.max_open_files = -1;
|
||||
opts.compaction_options_universal.size_ratio = 10;
|
||||
opts.compaction_options_universal.min_merge_width = 2;
|
||||
opts.compaction_options_universal.max_size_amplification_percent = 200;
|
||||
opts.periodic_compaction_seconds = 5 * kSecondsPerDay; // 5 days
|
||||
opts.num_levels = 5;
|
||||
|
||||
// Just to add some extra random days to current time
|
||||
Random rnd(test::RandomSeed());
|
||||
int days = rnd.Uniform(100);
|
||||
|
||||
int periodic_compactions = 0;
|
||||
int start_level = -1;
|
||||
int output_level = -1;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"UniversalCompactionPicker::PickPeriodicCompaction:Return",
|
||||
[&](void* arg) {
|
||||
Compaction* compaction = reinterpret_cast<Compaction*>(arg);
|
||||
ASSERT_TRUE(arg != nullptr);
|
||||
ASSERT_TRUE(compaction->compaction_reason() ==
|
||||
CompactionReason::kPeriodicCompaction);
|
||||
start_level = compaction->start_level();
|
||||
output_level = compaction->output_level();
|
||||
periodic_compactions++;
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
for (std::string preset_offpeak_time : {"", "00:30-04:30", "10:30-02:30"}) {
|
||||
SCOPED_TRACE("preset_offpeak_time=" + preset_offpeak_time);
|
||||
for (std::string new_offpeak_time : {"", "23:30-02:30"}) {
|
||||
SCOPED_TRACE("new_offpeak_time=" + new_offpeak_time);
|
||||
std::vector<std::pair<int, int>> times_to_test = {
|
||||
{0, 0}, {2, 30}, {3, 15}, {5, 10}, {13, 30}, {23, 30}};
|
||||
for (std::pair<int, int> now : times_to_test) {
|
||||
int now_hour = now.first;
|
||||
int now_minute = now.second;
|
||||
SCOPED_TRACE("now=" + std::to_string(now_hour) + ":" +
|
||||
std::to_string(now_minute));
|
||||
|
||||
auto mock_clock =
|
||||
std::make_shared<MockSystemClock>(env_->GetSystemClock());
|
||||
auto mock_env = std::make_unique<CompositeEnvWrapper>(env_, mock_clock);
|
||||
opts.env = mock_env.get();
|
||||
mock_clock->SetCurrentTime(days * kSecondsPerDay +
|
||||
now_hour * kSecondsPerHour +
|
||||
now_minute * kSecondsPerMinute);
|
||||
opts.daily_offpeak_time_utc = preset_offpeak_time;
|
||||
Reopen(opts);
|
||||
|
||||
ASSERT_OK(Put("foo", "bar1"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(0, periodic_compactions);
|
||||
|
||||
// Move clock forward by 8 hours. There should be no periodic
|
||||
// compaction, yet.
|
||||
mock_clock->MockSleepForSeconds(8 * kSecondsPerHour);
|
||||
ASSERT_OK(Put("foo", "bar2"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(0, periodic_compactions);
|
||||
|
||||
// Move clock forward by 4 days
|
||||
mock_clock->MockSleepForSeconds(4 * kSecondsPerDay);
|
||||
ASSERT_OK(Put("foo", "bar3"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
int64_t mock_now;
|
||||
ASSERT_OK(mock_clock->GetCurrentTime(&mock_now));
|
||||
|
||||
auto offpeak_time_info =
|
||||
dbfull()->GetVersionSet()->offpeak_time_option().GetOffpeakTimeInfo(
|
||||
mock_now);
|
||||
// At this point, the first file is 4 days and 8 hours old.
|
||||
// If it's offpeak now and the file is expected to expire before the
|
||||
// next offpeak starts
|
||||
if (offpeak_time_info.is_now_offpeak &&
|
||||
offpeak_time_info.seconds_till_next_offpeak_start /
|
||||
kSecondsPerHour >
|
||||
16) {
|
||||
ASSERT_EQ(1, periodic_compactions);
|
||||
} else {
|
||||
ASSERT_EQ(0, periodic_compactions);
|
||||
// Change offpeak option by SetDBOption()
|
||||
if (preset_offpeak_time != new_offpeak_time) {
|
||||
ASSERT_OK(dbfull()->SetDBOptions(
|
||||
{{"daily_offpeak_time_utc", new_offpeak_time}}));
|
||||
ASSERT_OK(Put("foo", "bar4"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
offpeak_time_info = dbfull()
|
||||
->GetVersionSet()
|
||||
->offpeak_time_option()
|
||||
.GetOffpeakTimeInfo(mock_now);
|
||||
// if the first file is now eligible to be picked up
|
||||
if (offpeak_time_info.is_now_offpeak &&
|
||||
offpeak_time_info.seconds_till_next_offpeak_start /
|
||||
kSecondsPerHour >
|
||||
16) {
|
||||
ASSERT_OK(Put("foo", "bar5"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(1, periodic_compactions);
|
||||
}
|
||||
}
|
||||
|
||||
// If the file has not been picked up yet (no offpeak set, or offpeak
|
||||
// set but then unset before the file becomes eligible)
|
||||
if (periodic_compactions == 0) {
|
||||
// move clock forward by one more day
|
||||
mock_clock->MockSleepForSeconds(1 * kSecondsPerDay);
|
||||
ASSERT_OK(Put("foo", "bar6"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(1, periodic_compactions);
|
||||
ASSERT_EQ(0, start_level);
|
||||
ASSERT_EQ(4, output_level);
|
||||
Destroy(opts);
|
||||
|
||||
periodic_compactions = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
|
||||
|
||||
+4
-2
@@ -1195,7 +1195,7 @@ TEST_F(DBWALTest, DISABLED_FullPurgePreservesLogPendingReuse) {
|
||||
ROCKSDB_NAMESPACE::port::Thread thread([&]() {
|
||||
TEST_SYNC_POINT(
|
||||
"DBWALTest::FullPurgePreservesLogPendingReuse:PreFullPurge");
|
||||
ASSERT_OK(db_->EnableFileDeletions(true));
|
||||
ASSERT_OK(db_->EnableFileDeletions(/*force=*/true));
|
||||
TEST_SYNC_POINT(
|
||||
"DBWALTest::FullPurgePreservesLogPendingReuse:PostFullPurge");
|
||||
});
|
||||
@@ -1541,7 +1541,9 @@ class RecoveryTestHelper {
|
||||
test->dbname_, &db_options, file_options, table_cache.get(),
|
||||
&write_buffer_manager, &write_controller,
|
||||
/*block_cache_tracer=*/nullptr,
|
||||
/*io_tracer=*/nullptr, /*db_id*/ "", /*db_session_id*/ ""));
|
||||
/*io_tracer=*/nullptr, /*db_id=*/"", /*db_session_id=*/"",
|
||||
options.daily_offpeak_time_utc,
|
||||
/*error_handler=*/nullptr));
|
||||
|
||||
wal_manager.reset(
|
||||
new WalManager(db_options, file_options, /*io_tracer=*/nullptr));
|
||||
|
||||
@@ -355,6 +355,7 @@ class InternalKeyComparator
|
||||
|
||||
// Same as Compare except that it excludes the value type from comparison
|
||||
int CompareKeySeq(const Slice& a, const Slice& b) const;
|
||||
int CompareKeySeq(const ParsedInternalKey& a, const Slice& b) const;
|
||||
|
||||
const Comparator* user_comparator() const {
|
||||
return user_comparator_.user_comparator();
|
||||
@@ -976,6 +977,26 @@ inline int InternalKeyComparator::CompareKeySeq(const Slice& akey,
|
||||
return r;
|
||||
}
|
||||
|
||||
inline int InternalKeyComparator::CompareKeySeq(const ParsedInternalKey& a,
|
||||
const Slice& b) const {
|
||||
// Order by:
|
||||
// increasing user key (according to user-supplied comparator)
|
||||
// decreasing sequence number
|
||||
int r = user_comparator_.Compare(a.user_key, ExtractUserKey(b));
|
||||
if (r == 0) {
|
||||
// Shift the number to exclude the last byte which contains the value type
|
||||
const uint64_t anum = a.sequence;
|
||||
const uint64_t bnum =
|
||||
DecodeFixed64(b.data() + b.size() - kNumInternalBytes) >> 8;
|
||||
if (anum > bnum) {
|
||||
r = -1;
|
||||
} else if (anum < bnum) {
|
||||
r = +1;
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
inline int InternalKeyComparator::Compare(const Slice& a,
|
||||
SequenceNumber a_global_seqno,
|
||||
const Slice& b,
|
||||
|
||||
+40
-32
@@ -396,15 +396,6 @@ const Status& ErrorHandler::SetBGError(const Status& bg_status,
|
||||
ROCKS_LOG_WARN(db_options_.info_log, "Background IO error %s",
|
||||
bg_io_err.ToString().c_str());
|
||||
|
||||
if (!recovery_disabled_file_deletion_ &&
|
||||
(BackgroundErrorReason::kManifestWrite == reason ||
|
||||
BackgroundErrorReason::kManifestWriteNoWAL == reason)) {
|
||||
// Always returns ok
|
||||
ROCKS_LOG_INFO(db_options_.info_log, "Disabling File Deletions");
|
||||
db_->DisableFileDeletionsWithLock().PermitUncheckedError();
|
||||
recovery_disabled_file_deletion_ = true;
|
||||
}
|
||||
|
||||
Status new_bg_io_err = bg_io_err;
|
||||
DBRecoverContext context;
|
||||
if (bg_io_err.GetScope() != IOStatus::IOErrorScope::kIOErrorScopeFile &&
|
||||
@@ -505,6 +496,31 @@ const Status& ErrorHandler::SetBGError(const Status& bg_status,
|
||||
}
|
||||
}
|
||||
|
||||
void ErrorHandler::AddFilesToQuarantine(
|
||||
autovector<const autovector<uint64_t>*> files_to_quarantine) {
|
||||
db_mutex_->AssertHeld();
|
||||
std::ostringstream quarantine_files_oss;
|
||||
bool is_first_one = true;
|
||||
for (const auto* files : files_to_quarantine) {
|
||||
assert(files);
|
||||
for (uint64_t file_number : *files) {
|
||||
files_to_quarantine_.push_back(file_number);
|
||||
quarantine_files_oss << (is_first_one ? "" : ", ") << file_number;
|
||||
is_first_one = false;
|
||||
}
|
||||
}
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
"ErrorHandler: added file numbers %s to quarantine.\n",
|
||||
quarantine_files_oss.str().c_str());
|
||||
}
|
||||
|
||||
void ErrorHandler::ClearFilesToQuarantine() {
|
||||
db_mutex_->AssertHeld();
|
||||
files_to_quarantine_.clear();
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
"ErrorHandler: cleared files in quarantine.\n");
|
||||
}
|
||||
|
||||
Status ErrorHandler::OverrideNoSpaceError(const Status& bg_error,
|
||||
bool* auto_recovery) {
|
||||
if (bg_error.severity() >= Status::Severity::kFatalError) {
|
||||
@@ -552,28 +568,18 @@ Status ErrorHandler::ClearBGError() {
|
||||
|
||||
// Signal that recovery succeeded
|
||||
if (recovery_error_.ok()) {
|
||||
assert(files_to_quarantine_.empty());
|
||||
Status old_bg_error = bg_error_;
|
||||
// old_bg_error is only for notifying listeners, so may not be checked
|
||||
old_bg_error.PermitUncheckedError();
|
||||
// Clear and check the recovery IO and BG error
|
||||
is_db_stopped_.store(false, std::memory_order_release);
|
||||
bg_error_ = Status::OK();
|
||||
recovery_error_ = IOStatus::OK();
|
||||
bg_error_.PermitUncheckedError();
|
||||
recovery_error_.PermitUncheckedError();
|
||||
recovery_in_prog_ = false;
|
||||
soft_error_no_bg_work_ = false;
|
||||
if (recovery_disabled_file_deletion_) {
|
||||
recovery_disabled_file_deletion_ = false;
|
||||
int remain_counter = db_->EnableFileDeletionsWithLock();
|
||||
if (remain_counter == 0) {
|
||||
ROCKS_LOG_INFO(db_options_.info_log, "File Deletions Enabled");
|
||||
} else {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log,
|
||||
"File Deletions Enable, but not really enabled. Counter: %d",
|
||||
remain_counter);
|
||||
}
|
||||
}
|
||||
EventHelpers::NotifyOnErrorRecoveryEnd(db_options_.listeners, old_bg_error,
|
||||
bg_error_, db_mutex_);
|
||||
}
|
||||
@@ -645,6 +651,13 @@ const Status& ErrorHandler::StartRecoverFromRetryableBGIOError(
|
||||
} else if (db_options_.max_bgerror_resume_count <= 0 || recovery_in_prog_) {
|
||||
// Auto resume BG error is not enabled, directly return bg_error_.
|
||||
return bg_error_;
|
||||
} else if (end_recovery_) {
|
||||
// Can temporarily release db mutex
|
||||
EventHelpers::NotifyOnErrorRecoveryEnd(db_options_.listeners, bg_error_,
|
||||
Status::ShutdownInProgress(),
|
||||
db_mutex_);
|
||||
db_mutex_->AssertHeld();
|
||||
return bg_error_;
|
||||
}
|
||||
if (bg_error_stats_ != nullptr) {
|
||||
RecordTick(bg_error_stats_.get(), ERROR_HANDLER_AUTORESUME_COUNT);
|
||||
@@ -664,11 +677,14 @@ const Status& ErrorHandler::StartRecoverFromRetryableBGIOError(
|
||||
// wait the previous recover thread to finish and create a new thread
|
||||
// to recover from the bg error.
|
||||
db_mutex_->Unlock();
|
||||
TEST_SYNC_POINT(
|
||||
"StartRecoverFromRetryableBGIOError:BeforeWaitingForOtherThread");
|
||||
old_recovery_thread->join();
|
||||
TEST_SYNC_POINT(
|
||||
"StartRecoverFromRetryableBGIOError:AfterWaitingForOtherThread");
|
||||
db_mutex_->Lock();
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT("StartRecoverFromRetryableBGIOError::in_progress");
|
||||
recovery_thread_.reset(
|
||||
new port::Thread(&ErrorHandler::RecoverFromRetryableBGIOError, this));
|
||||
|
||||
@@ -682,6 +698,7 @@ const Status& ErrorHandler::StartRecoverFromRetryableBGIOError(
|
||||
// Automatic recover from Retryable BG IO error. Must be called after db
|
||||
// mutex is released.
|
||||
void ErrorHandler::RecoverFromRetryableBGIOError() {
|
||||
assert(recovery_in_prog_);
|
||||
TEST_SYNC_POINT("RecoverFromRetryableBGIOError:BeforeStart");
|
||||
TEST_SYNC_POINT("RecoverFromRetryableBGIOError:BeforeStart2");
|
||||
InstrumentedMutexLock l(db_mutex_);
|
||||
@@ -747,22 +764,12 @@ void ErrorHandler::RecoverFromRetryableBGIOError() {
|
||||
// recover from the retryable IO error and no other BG errors. Clean
|
||||
// the bg_error and notify user.
|
||||
TEST_SYNC_POINT("RecoverFromRetryableBGIOError:RecoverSuccess");
|
||||
Status old_bg_error = bg_error_;
|
||||
is_db_stopped_.store(false, std::memory_order_release);
|
||||
bg_error_ = Status::OK();
|
||||
bg_error_.PermitUncheckedError();
|
||||
EventHelpers::NotifyOnErrorRecoveryEnd(
|
||||
db_options_.listeners, old_bg_error, bg_error_, db_mutex_);
|
||||
if (bg_error_stats_ != nullptr) {
|
||||
RecordTick(bg_error_stats_.get(),
|
||||
ERROR_HANDLER_AUTORESUME_SUCCESS_COUNT);
|
||||
RecordInHistogram(bg_error_stats_.get(),
|
||||
ERROR_HANDLER_AUTORESUME_RETRY_COUNT, retry_count);
|
||||
}
|
||||
recovery_in_prog_ = false;
|
||||
if (soft_error_no_bg_work_) {
|
||||
soft_error_no_bg_work_ = false;
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
// In this case: 1) recovery_error_ is more serious or not retryable
|
||||
@@ -819,6 +826,7 @@ void ErrorHandler::EndAutoRecovery() {
|
||||
old_recovery_thread->join();
|
||||
db_mutex_->Lock();
|
||||
}
|
||||
TEST_SYNC_POINT("PostEndAutoRecovery");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+20
-5
@@ -4,11 +4,14 @@
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
#pragma once
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "monitoring/instrumented_mutex.h"
|
||||
#include "options/db_options.h"
|
||||
#include "rocksdb/io_status.h"
|
||||
#include "rocksdb/listener.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "util/autovector.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -42,8 +45,7 @@ class ErrorHandler {
|
||||
recovery_in_prog_(false),
|
||||
soft_error_no_bg_work_(false),
|
||||
is_db_stopped_(false),
|
||||
bg_error_stats_(db_options.statistics),
|
||||
recovery_disabled_file_deletion_(false) {
|
||||
bg_error_stats_(db_options.statistics) {
|
||||
// Clear the checked flag for uninitialized errors
|
||||
bg_error_.PermitUncheckedError();
|
||||
recovery_error_.PermitUncheckedError();
|
||||
@@ -81,6 +83,16 @@ class ErrorHandler {
|
||||
|
||||
void EndAutoRecovery();
|
||||
|
||||
void AddFilesToQuarantine(
|
||||
autovector<const autovector<uint64_t>*> files_to_quarantine);
|
||||
|
||||
const autovector<uint64_t>& GetFilesToQuarantine() const {
|
||||
db_mutex_->AssertHeld();
|
||||
return files_to_quarantine_;
|
||||
}
|
||||
|
||||
void ClearFilesToQuarantine();
|
||||
|
||||
private:
|
||||
DBImpl* db_;
|
||||
const ImmutableDBOptions& db_options_;
|
||||
@@ -109,9 +121,12 @@ class ErrorHandler {
|
||||
// The pointer of DB statistics.
|
||||
std::shared_ptr<Statistics> bg_error_stats_;
|
||||
|
||||
// Tracks whether the recovery has disabled file deletion. This boolean flag
|
||||
// is updated while holding db mutex.
|
||||
bool recovery_disabled_file_deletion_;
|
||||
// During recovery from manifest IO errors, files whose VersionEdits entries
|
||||
// could be in an ambiguous state are quarantined and file deletion refrain
|
||||
// from deleting them. Successful recovery will clear this vector. Files are
|
||||
// added to this vector while DB mutex was locked, this data structure is
|
||||
// unsorted.
|
||||
autovector<uint64_t> files_to_quarantine_;
|
||||
|
||||
const Status& HandleKnownErrors(const Status& bg_err,
|
||||
BackgroundErrorReason reason);
|
||||
|
||||
+254
-99
@@ -7,12 +7,15 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "file/sst_file_manager_impl.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/io_status.h"
|
||||
#include "rocksdb/sst_file_manager.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "util/random.h"
|
||||
#include "utilities/fault_injection_env.h"
|
||||
#include "utilities/fault_injection_fs.h"
|
||||
@@ -152,9 +155,9 @@ class ErrorHandlerFSListener : public EventListener {
|
||||
FaultInjectionTestFS* fault_fs_;
|
||||
};
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWriteError) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
TEST_F(DBErrorHandlingFSTest, FlushWriteError) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -197,9 +200,9 @@ TEST_F(DBErrorHandlingFSTest, FLushWriteError) {
|
||||
// All the NoSpace IOError will be handled as the regular BG Error no matter the
|
||||
// retryable flag is set of not. So the auto resume for retryable IO Error will
|
||||
// not be triggered. Also, it is mapped as hard error.
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWriteNoSpaceError) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
TEST_F(DBErrorHandlingFSTest, FlushWriteNoSpaceError) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -241,9 +244,9 @@ TEST_F(DBErrorHandlingFSTest, FLushWriteNoSpaceError) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWriteRetryableError) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
TEST_F(DBErrorHandlingFSTest, FlushWriteRetryableError) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -315,9 +318,9 @@ TEST_F(DBErrorHandlingFSTest, FLushWriteRetryableError) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWriteFileScopeError) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
TEST_F(DBErrorHandlingFSTest, FlushWriteFileScopeError) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -399,9 +402,9 @@ TEST_F(DBErrorHandlingFSTest, FLushWriteFileScopeError) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWALWriteRetryableError) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
TEST_F(DBErrorHandlingFSTest, FlushWALWriteRetryableError) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -443,9 +446,9 @@ TEST_F(DBErrorHandlingFSTest, FLushWALWriteRetryableError) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWALAtomicWriteRetryableError) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
TEST_F(DBErrorHandlingFSTest, FlushWALAtomicWriteRetryableError) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -489,9 +492,9 @@ TEST_F(DBErrorHandlingFSTest, FLushWALAtomicWriteRetryableError) {
|
||||
}
|
||||
|
||||
// The flush error is injected before we finish the table build
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableError1) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
TEST_F(DBErrorHandlingFSTest, FlushWritNoWALRetryableError1) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -545,9 +548,9 @@ TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableError1) {
|
||||
}
|
||||
|
||||
// The retryable IO error is injected before we sync table
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWriteNoWALRetryableError2) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
TEST_F(DBErrorHandlingFSTest, FlushWriteNoWALRetryableError2) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -589,9 +592,9 @@ TEST_F(DBErrorHandlingFSTest, FLushWriteNoWALRetryableError2) {
|
||||
}
|
||||
|
||||
// The retryable IO error is injected before we close the table file
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWriteNoWALRetryableError3) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
TEST_F(DBErrorHandlingFSTest, FlushWriteNoWALRetryableError3) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -633,8 +636,8 @@ TEST_F(DBErrorHandlingFSTest, FLushWriteNoWALRetryableError3) {
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, ManifestWriteError) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -658,6 +661,7 @@ TEST_F(DBErrorHandlingFSTest, ManifestWriteError) {
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
ASSERT_FALSE(dbfull()->TEST_GetFilesToQuarantine().empty());
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
@@ -666,6 +670,7 @@ TEST_F(DBErrorHandlingFSTest, ManifestWriteError) {
|
||||
|
||||
new_manifest = GetManifestNameFromLiveFiles();
|
||||
ASSERT_NE(new_manifest, old_manifest);
|
||||
ASSERT_TRUE(dbfull()->TEST_GetFilesToQuarantine().empty());
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_EQ("val", Get(Key(0)));
|
||||
@@ -674,8 +679,8 @@ TEST_F(DBErrorHandlingFSTest, ManifestWriteError) {
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, ManifestWriteRetryableError) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -701,6 +706,7 @@ TEST_F(DBErrorHandlingFSTest, ManifestWriteRetryableError) {
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError);
|
||||
ASSERT_FALSE(dbfull()->TEST_GetFilesToQuarantine().empty());
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
@@ -709,6 +715,7 @@ TEST_F(DBErrorHandlingFSTest, ManifestWriteRetryableError) {
|
||||
|
||||
new_manifest = GetManifestNameFromLiveFiles();
|
||||
ASSERT_NE(new_manifest, old_manifest);
|
||||
ASSERT_TRUE(dbfull()->TEST_GetFilesToQuarantine().empty());
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_EQ("val", Get(Key(0)));
|
||||
@@ -717,8 +724,8 @@ TEST_F(DBErrorHandlingFSTest, ManifestWriteRetryableError) {
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, ManifestWriteFileScopeError) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -746,6 +753,7 @@ TEST_F(DBErrorHandlingFSTest, ManifestWriteFileScopeError) {
|
||||
[&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_FALSE(dbfull()->TEST_GetFilesToQuarantine().empty());
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError);
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
@@ -755,6 +763,7 @@ TEST_F(DBErrorHandlingFSTest, ManifestWriteFileScopeError) {
|
||||
|
||||
new_manifest = GetManifestNameFromLiveFiles();
|
||||
ASSERT_NE(new_manifest, old_manifest);
|
||||
ASSERT_TRUE(dbfull()->TEST_GetFilesToQuarantine().empty());
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_EQ("val", Get(Key(0)));
|
||||
@@ -763,8 +772,8 @@ TEST_F(DBErrorHandlingFSTest, ManifestWriteFileScopeError) {
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, ManifestWriteNoWALRetryableError) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -792,6 +801,7 @@ TEST_F(DBErrorHandlingFSTest, ManifestWriteNoWALRetryableError) {
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError);
|
||||
ASSERT_FALSE(dbfull()->TEST_GetFilesToQuarantine().empty());
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
@@ -800,6 +810,7 @@ TEST_F(DBErrorHandlingFSTest, ManifestWriteNoWALRetryableError) {
|
||||
|
||||
new_manifest = GetManifestNameFromLiveFiles();
|
||||
ASSERT_NE(new_manifest, old_manifest);
|
||||
ASSERT_TRUE(dbfull()->TEST_GetFilesToQuarantine().empty());
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_EQ("val", Get(Key(0)));
|
||||
@@ -808,8 +819,8 @@ TEST_F(DBErrorHandlingFSTest, ManifestWriteNoWALRetryableError) {
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, DoubleManifestWriteError) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -833,11 +844,13 @@ TEST_F(DBErrorHandlingFSTest, DoubleManifestWriteError) {
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
ASSERT_FALSE(dbfull()->TEST_GetFilesToQuarantine().empty());
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
|
||||
// This Resume() will attempt to create a new manifest file and fail again
|
||||
s = dbfull()->Resume();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
ASSERT_FALSE(dbfull()->TEST_GetFilesToQuarantine().empty());
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
@@ -848,6 +861,7 @@ TEST_F(DBErrorHandlingFSTest, DoubleManifestWriteError) {
|
||||
|
||||
new_manifest = GetManifestNameFromLiveFiles();
|
||||
ASSERT_NE(new_manifest, old_manifest);
|
||||
ASSERT_TRUE(dbfull()->TEST_GetFilesToQuarantine().empty());
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_EQ("val", Get(Key(0)));
|
||||
@@ -860,8 +874,8 @@ TEST_F(DBErrorHandlingFSTest, CompactionManifestWriteError) {
|
||||
ROCKSDB_GTEST_SKIP("Test requires non-mock environment");
|
||||
return;
|
||||
}
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -914,6 +928,7 @@ TEST_F(DBErrorHandlingFSTest, CompactionManifestWriteError) {
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
TEST_SYNC_POINT("CompactionManifestWriteError:1");
|
||||
ASSERT_FALSE(dbfull()->TEST_GetFilesToQuarantine().empty());
|
||||
TEST_SYNC_POINT("CompactionManifestWriteError:2");
|
||||
|
||||
s = dbfull()->TEST_WaitForCompact();
|
||||
@@ -922,6 +937,7 @@ TEST_F(DBErrorHandlingFSTest, CompactionManifestWriteError) {
|
||||
|
||||
new_manifest = GetManifestNameFromLiveFiles();
|
||||
ASSERT_NE(new_manifest, old_manifest);
|
||||
ASSERT_TRUE(dbfull()->TEST_GetFilesToQuarantine().empty());
|
||||
Reopen(options);
|
||||
ASSERT_EQ("val", Get(Key(0)));
|
||||
ASSERT_EQ("val", Get(Key(1)));
|
||||
@@ -930,8 +946,8 @@ TEST_F(DBErrorHandlingFSTest, CompactionManifestWriteError) {
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, CompactionManifestWriteRetryableError) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -980,6 +996,7 @@ TEST_F(DBErrorHandlingFSTest, CompactionManifestWriteRetryableError) {
|
||||
ASSERT_OK(s);
|
||||
|
||||
TEST_SYNC_POINT("CompactionManifestWriteError:0");
|
||||
ASSERT_FALSE(dbfull()->TEST_GetFilesToQuarantine().empty());
|
||||
TEST_SYNC_POINT("CompactionManifestWriteError:1");
|
||||
|
||||
s = dbfull()->TEST_WaitForCompact();
|
||||
@@ -993,6 +1010,7 @@ TEST_F(DBErrorHandlingFSTest, CompactionManifestWriteRetryableError) {
|
||||
|
||||
new_manifest = GetManifestNameFromLiveFiles();
|
||||
ASSERT_NE(new_manifest, old_manifest);
|
||||
ASSERT_TRUE(dbfull()->TEST_GetFilesToQuarantine().empty());
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_EQ("val", Get(Key(0)));
|
||||
@@ -1002,8 +1020,8 @@ TEST_F(DBErrorHandlingFSTest, CompactionManifestWriteRetryableError) {
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, CompactionWriteError) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -1044,8 +1062,8 @@ TEST_F(DBErrorHandlingFSTest, CompactionWriteError) {
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, DISABLED_CompactionWriteRetryableError) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -1091,8 +1109,8 @@ TEST_F(DBErrorHandlingFSTest, DISABLED_CompactionWriteRetryableError) {
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, DISABLED_CompactionWriteFileScopeError) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -1183,8 +1201,8 @@ TEST_F(DBErrorHandlingFSTest, AutoRecoverFlushError) {
|
||||
ROCKSDB_GTEST_SKIP("Test requires non-mock environment");
|
||||
return;
|
||||
}
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -1228,8 +1246,8 @@ TEST_F(DBErrorHandlingFSTest, AutoRecoverFlushError) {
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, FailRecoverFlushError) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -1257,8 +1275,8 @@ TEST_F(DBErrorHandlingFSTest, WALWriteError) {
|
||||
ROCKSDB_GTEST_SKIP("Test requires non-mock environment");
|
||||
return;
|
||||
}
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -1330,8 +1348,8 @@ TEST_F(DBErrorHandlingFSTest, WALWriteError) {
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, WALWriteRetryableError) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -1425,8 +1443,8 @@ TEST_F(DBErrorHandlingFSTest, MultiCFWALWriteError) {
|
||||
ROCKSDB_GTEST_SKIP("Test requires non-mock environment");
|
||||
return;
|
||||
}
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -1757,10 +1775,10 @@ TEST_F(DBErrorHandlingFSTest, MultiDBVariousErrors) {
|
||||
// to soft error and trigger auto resume. During auto resume, SwitchMemtable
|
||||
// is disabled to avoid small SST tables. Write can still be applied before
|
||||
// the bg error is cleaned unless the memtable is full.
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableErrorAutoRecover1) {
|
||||
TEST_F(DBErrorHandlingFSTest, FlushWritNoWALRetryableErrorAutoRecover1) {
|
||||
// Activate the FS before the first resume
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -1781,7 +1799,7 @@ TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableErrorAutoRecover1) {
|
||||
ASSERT_OK(Put(Key(1), "val1", wo));
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"RecoverFromRetryableBGIOError:LoopOut",
|
||||
"FLushWritNoWALRetryableeErrorAutoRecover1:1"}});
|
||||
"FlushWritNoWALRetryableeErrorAutoRecover1:1"}});
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BuildTable:BeforeFinishBuildTable",
|
||||
[&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); });
|
||||
@@ -1790,7 +1808,7 @@ TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableErrorAutoRecover1) {
|
||||
s = Flush();
|
||||
ASSERT_EQ("val1", Get(Key(1)));
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError);
|
||||
TEST_SYNC_POINT("FLushWritNoWALRetryableeErrorAutoRecover1:1");
|
||||
TEST_SYNC_POINT("FlushWritNoWALRetryableeErrorAutoRecover1:1");
|
||||
ASSERT_EQ("val1", Get(Key(1)));
|
||||
ASSERT_EQ("val1", Get(Key(1)));
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
@@ -1827,10 +1845,94 @@ TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableErrorAutoRecover1) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableErrorAutoRecover2) {
|
||||
TEST_F(DBErrorHandlingFSTest, MultipleRecoveryThreads) {
|
||||
// This test creates a scenario where second write's recovery can get started
|
||||
// while mutex is released for a short period during
|
||||
// NotifyOnErrorRecoveryEnd() from the first write's recovery. This is to make
|
||||
// sure RecoverFromRetryableBGIOError() from the second write's recovery
|
||||
// thread does not start with recovery_in_prog_ = false;
|
||||
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
options.listeners.emplace_back(listener);
|
||||
options.max_bgerror_resume_count = 100;
|
||||
options.bgerror_resume_retry_interval = 1000000; // 1 second
|
||||
options.statistics = CreateDBStatistics();
|
||||
|
||||
listener->EnableAutoRecovery(false);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
IOStatus error_msg = IOStatus::IOError("Retryable IO Error");
|
||||
error_msg.SetRetryable(true);
|
||||
|
||||
WriteOptions wo = WriteOptions();
|
||||
wo.disableWAL = true;
|
||||
fault_fs_->SetFilesystemActive(false, error_msg);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"NotifyOnErrorRecoveryEnd:MutexUnlocked:1",
|
||||
"MultipleRecoveryThreads:1"},
|
||||
{"MultipleRecoveryThreads:2",
|
||||
"NotifyOnErrorRecoveryEnd:MutexUnlocked:2"},
|
||||
{"StartRecoverFromRetryableBGIOError:BeforeWaitingForOtherThread",
|
||||
"MultipleRecoveryThreads:3"},
|
||||
{"RecoverFromRetryableBGIOError:RecoverSuccess",
|
||||
"MultipleRecoveryThreads:4"},
|
||||
{"MultipleRecoveryThreads:4",
|
||||
"StartRecoverFromRetryableBGIOError:AfterWaitingForOtherThread"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// First write with read fault injected and recovery will start
|
||||
{
|
||||
ASSERT_OK(Put(Key(1), "val1", wo));
|
||||
Status s = Flush();
|
||||
ASSERT_NOK(s);
|
||||
}
|
||||
// Remove read fault injection so that first recovery can go through
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
|
||||
// At this point, first recovery is now at NotifyOnErrorRecoveryEnd. Mutex is
|
||||
// released.
|
||||
TEST_SYNC_POINT("MultipleRecoveryThreads:1");
|
||||
|
||||
ROCKSDB_NAMESPACE::port::Thread second_write([&] {
|
||||
// Second write with read fault injected
|
||||
fault_fs_->SetFilesystemActive(false, error_msg);
|
||||
ASSERT_OK(Put(Key(2), "val2", wo));
|
||||
Status s = Flush();
|
||||
ASSERT_NOK(s);
|
||||
});
|
||||
// Second bg thread before waiting for the first thread's recovery thread
|
||||
TEST_SYNC_POINT("MultipleRecoveryThreads:3");
|
||||
// First thread's recovery thread continues
|
||||
TEST_SYNC_POINT("MultipleRecoveryThreads:2");
|
||||
// Wait for the first thread's recovery to finish
|
||||
// (this sets recovery_in_prog_ = false)
|
||||
// And second thread continues and starts recovery thread
|
||||
TEST_SYNC_POINT("MultipleRecoveryThreads:4");
|
||||
second_write.join();
|
||||
// Remove error injection so that second thread recovery can go through
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
|
||||
// Set up sync point so that we can wait for the recovery thread to finish
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"RecoverFromRetryableBGIOError:RecoverSuccess",
|
||||
"MultipleRecoveryThreads:6"}});
|
||||
|
||||
// Wait for the second thread's recovery to be done
|
||||
TEST_SYNC_POINT("MultipleRecoveryThreads:6");
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, FlushWritNoWALRetryableErrorAutoRecover2) {
|
||||
// Activate the FS before the first resume
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -1888,10 +1990,10 @@ TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableErrorAutoRecover2) {
|
||||
|
||||
// Auto resume fromt the flush retryable IO error. Activate the FS before the
|
||||
// first resume. Resume is successful
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWritRetryableErrorAutoRecover1) {
|
||||
TEST_F(DBErrorHandlingFSTest, FlushWritRetryableErrorAutoRecover1) {
|
||||
// Activate the FS before the first resume
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -1930,10 +2032,10 @@ TEST_F(DBErrorHandlingFSTest, FLushWritRetryableErrorAutoRecover1) {
|
||||
|
||||
// Auto resume fromt the flush retryable IO error and set the retry limit count.
|
||||
// Never activate the FS and auto resume should fail at the end
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWritRetryableErrorAutoRecover2) {
|
||||
TEST_F(DBErrorHandlingFSTest, FlushWritRetryableErrorAutoRecover2) {
|
||||
// Fail all the resume and let user to resume
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -1950,18 +2052,18 @@ TEST_F(DBErrorHandlingFSTest, FLushWritRetryableErrorAutoRecover2) {
|
||||
|
||||
ASSERT_OK(Put(Key(1), "val1"));
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"FLushWritRetryableeErrorAutoRecover2:0",
|
||||
{{"FlushWritRetryableeErrorAutoRecover2:0",
|
||||
"RecoverFromRetryableBGIOError:BeforeStart"},
|
||||
{"RecoverFromRetryableBGIOError:LoopOut",
|
||||
"FLushWritRetryableeErrorAutoRecover2:1"}});
|
||||
"FlushWritRetryableeErrorAutoRecover2:1"}});
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BuildTable:BeforeFinishBuildTable",
|
||||
[&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError);
|
||||
TEST_SYNC_POINT("FLushWritRetryableeErrorAutoRecover2:0");
|
||||
TEST_SYNC_POINT("FLushWritRetryableeErrorAutoRecover2:1");
|
||||
TEST_SYNC_POINT("FlushWritRetryableeErrorAutoRecover2:0");
|
||||
TEST_SYNC_POINT("FlushWritRetryableeErrorAutoRecover2:1");
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
@@ -1983,8 +2085,8 @@ TEST_F(DBErrorHandlingFSTest, FLushWritRetryableErrorAutoRecover2) {
|
||||
// Fail the first resume and let the second resume be successful.
|
||||
TEST_F(DBErrorHandlingFSTest, ManifestWriteRetryableErrorAutoRecover) {
|
||||
// Fail the first resume and let the second resume be successful
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -2036,8 +2138,8 @@ TEST_F(DBErrorHandlingFSTest, ManifestWriteRetryableErrorAutoRecover) {
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, ManifestWriteNoWALRetryableErrorAutoRecover) {
|
||||
// Fail the first resume and let the second resume be successful
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -2091,8 +2193,8 @@ TEST_F(DBErrorHandlingFSTest, ManifestWriteNoWALRetryableErrorAutoRecover) {
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest,
|
||||
CompactionManifestWriteRetryableErrorAutoRecover) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -2181,8 +2283,8 @@ TEST_F(DBErrorHandlingFSTest, CompactionWriteRetryableErrorAutoRecover) {
|
||||
// compaction, the FS is set to active and compaction is successful, so
|
||||
// the test will hit the CompactionJob::FinishCompactionOutputFile1 sync
|
||||
// point.
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -2235,8 +2337,8 @@ TEST_F(DBErrorHandlingFSTest, CompactionWriteRetryableErrorAutoRecover) {
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, WALWriteRetryableErrorAutoRecover1) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -2338,8 +2440,8 @@ TEST_F(DBErrorHandlingFSTest, WALWriteRetryableErrorAutoRecover1) {
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, WALWriteRetryableErrorAutoRecover2) {
|
||||
// Fail the first recover and try second time.
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -2439,10 +2541,10 @@ TEST_F(DBErrorHandlingFSTest, WALWriteRetryableErrorAutoRecover2) {
|
||||
|
||||
// Fail auto resume from a flush retryable error and verify that
|
||||
// OnErrorRecoveryEnd listener callback is called
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWritRetryableErrorAbortRecovery) {
|
||||
TEST_F(DBErrorHandlingFSTest, FlushWritRetryableErrorAbortRecovery) {
|
||||
// Activate the FS before the first resume
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -2473,6 +2575,59 @@ TEST_F(DBErrorHandlingFSTest, FLushWritRetryableErrorAbortRecovery) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, FlushErrorRecoveryRaceWithDBDestruction) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
options.listeners.emplace_back(listener);
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("k1", "val"));
|
||||
|
||||
// Inject retryable flush error
|
||||
bool error_set = false;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BuildTable:BeforeOutputValidation", [&](void*) {
|
||||
if (error_set) {
|
||||
return;
|
||||
}
|
||||
IOStatus st = IOStatus::IOError("Injected");
|
||||
st.SetRetryable(true);
|
||||
fault_fs_->SetFilesystemActive(false, st);
|
||||
error_set = true;
|
||||
});
|
||||
|
||||
port::Thread db_close_thread;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BuildTable:BeforeDeleteFile", [&](void*) {
|
||||
// Clear retryable flush error injection
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
|
||||
// Coerce race between ending auto recovery in db destruction and flush
|
||||
// error recovery
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"PostEndAutoRecovery", "FlushJob::WriteLevel0Table"}});
|
||||
db_close_thread = port::Thread([&] { Close(); });
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Status s = Flush();
|
||||
ASSERT_NOK(s);
|
||||
|
||||
int placeholder = 1;
|
||||
listener->WaitForRecovery(placeholder);
|
||||
ASSERT_TRUE(listener->new_bg_error().IsShutdownInProgress());
|
||||
|
||||
// Prior to the fix, the db close will crash due to the recovery thread for
|
||||
// flush error is not joined by the time of destruction.
|
||||
db_close_thread.join();
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, FlushReadError) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
@@ -2680,9 +2835,9 @@ TEST_F(DBErrorHandlingFSTest, CompactionReadRetryableErrorAutoRecover) {
|
||||
class DBErrorHandlingFencingTest : public DBErrorHandlingFSTest,
|
||||
public testing::WithParamInterface<bool> {};
|
||||
|
||||
TEST_P(DBErrorHandlingFencingTest, FLushWriteFenced) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
TEST_P(DBErrorHandlingFencingTest, FlushWriteFenced) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -2709,8 +2864,8 @@ TEST_P(DBErrorHandlingFencingTest, FLushWriteFenced) {
|
||||
}
|
||||
|
||||
TEST_P(DBErrorHandlingFencingTest, ManifestWriteFenced) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -2744,8 +2899,8 @@ TEST_P(DBErrorHandlingFencingTest, ManifestWriteFenced) {
|
||||
}
|
||||
|
||||
TEST_P(DBErrorHandlingFencingTest, CompactionWriteFenced) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
@@ -2785,8 +2940,8 @@ TEST_P(DBErrorHandlingFencingTest, CompactionWriteFenced) {
|
||||
}
|
||||
|
||||
TEST_P(DBErrorHandlingFencingTest, WALWriteFenced) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener =
|
||||
std::make_shared<ErrorHandlerFSListener>();
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
|
||||
@@ -230,6 +230,8 @@ void EventHelpers::NotifyOnErrorRecoveryEnd(
|
||||
db_mutex->AssertHeld();
|
||||
// release lock while notifying events
|
||||
db_mutex->Unlock();
|
||||
TEST_SYNC_POINT("NotifyOnErrorRecoveryEnd:MutexUnlocked:1");
|
||||
TEST_SYNC_POINT("NotifyOnErrorRecoveryEnd:MutexUnlocked:2");
|
||||
for (auto& listener : listeners) {
|
||||
BackgroundErrorRecoveryInfo info;
|
||||
info.old_bg_error = old_bg_error;
|
||||
|
||||
@@ -1548,6 +1548,11 @@ TEST_F(ExternalSSTFileBasicTest, RangeDeletionEndComesBeforeStart) {
|
||||
}
|
||||
|
||||
TEST_P(ExternalSSTFileBasicTest, IngestFileWithBadBlockChecksum) {
|
||||
bool verify_checksums_before_ingest = std::get<1>(GetParam());
|
||||
if (!verify_checksums_before_ingest) {
|
||||
ROCKSDB_GTEST_BYPASS("Bypassing test when !verify_checksums_before_ingest");
|
||||
return;
|
||||
}
|
||||
bool change_checksum_called = false;
|
||||
const auto& change_checksum = [&](void* arg) {
|
||||
if (!change_checksum_called) {
|
||||
@@ -1565,24 +1570,20 @@ TEST_P(ExternalSSTFileBasicTest, IngestFileWithBadBlockChecksum) {
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
int file_id = 0;
|
||||
bool write_global_seqno = std::get<0>(GetParam());
|
||||
bool verify_checksums_before_ingest = std::get<1>(GetParam());
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
std::map<std::string, std::string> true_data;
|
||||
Status s = GenerateAndAddExternalFile(
|
||||
options, {1, 2, 3, 4, 5, 6}, ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, verify_checksums_before_ingest, &true_data);
|
||||
if (verify_checksums_before_ingest) {
|
||||
ASSERT_NOK(s);
|
||||
} else {
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
write_global_seqno, /*verify_checksums_before_ingest=*/true,
|
||||
&true_data);
|
||||
ASSERT_NOK(s);
|
||||
change_checksum_called = false;
|
||||
} while (ChangeOptionsForFileIngestionTest());
|
||||
}
|
||||
|
||||
TEST_P(ExternalSSTFileBasicTest, IngestFileWithFirstByteTampered) {
|
||||
TEST_P(ExternalSSTFileBasicTest, IngestFileWithCorruptedDataBlock) {
|
||||
if (!random_rwfile_supported_) {
|
||||
ROCKSDB_GTEST_SKIP("Test requires NewRandomRWFile support");
|
||||
return;
|
||||
@@ -1590,15 +1591,21 @@ TEST_P(ExternalSSTFileBasicTest, IngestFileWithFirstByteTampered) {
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
int file_id = 0;
|
||||
EnvOptions env_options;
|
||||
Random rnd(301);
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
options.compression = kNoCompression;
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_size = 4 * 1024;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
std::string file_path = sst_files_dir_ + std::to_string(file_id++);
|
||||
SstFileWriter sst_file_writer(env_options, options);
|
||||
Status s = sst_file_writer.Open(file_path);
|
||||
ASSERT_OK(s);
|
||||
// This should write more than 2 data blocks.
|
||||
for (int i = 0; i != 100; ++i) {
|
||||
std::string key = Key(i);
|
||||
std::string value = Key(i) + std::to_string(0);
|
||||
std::string value = rnd.RandomString(200);
|
||||
ASSERT_OK(sst_file_writer.Put(key, value));
|
||||
}
|
||||
ASSERT_OK(sst_file_writer.Finish());
|
||||
@@ -1609,11 +1616,11 @@ TEST_P(ExternalSSTFileBasicTest, IngestFileWithFirstByteTampered) {
|
||||
ASSERT_GT(file_size, 8);
|
||||
std::unique_ptr<RandomRWFile> rwfile;
|
||||
ASSERT_OK(env_->NewRandomRWFile(file_path, &rwfile, EnvOptions()));
|
||||
// Manually corrupt the file
|
||||
// We deterministically corrupt the first byte because we currently
|
||||
// cannot choose a random offset. The reason for this limitation is that
|
||||
// we do not checksum property block at present.
|
||||
const uint64_t offset = 0;
|
||||
// Corrupt the second data block.
|
||||
// We need to corrupt a non-first and non-last data block
|
||||
// since we access them to get smallest and largest internal
|
||||
// key in the file in GetIngestedFileInfo().
|
||||
const uint64_t offset = 5000;
|
||||
char scratch[8] = {0};
|
||||
Slice buf;
|
||||
ASSERT_OK(rwfile->Read(offset, sizeof(scratch), &buf, scratch));
|
||||
|
||||
@@ -769,8 +769,6 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
std::unique_ptr<InternalIterator> iter(table_reader->NewIterator(
|
||||
ro, sv->mutable_cf_options.prefix_extractor.get(), /*arena=*/nullptr,
|
||||
/*skip_filters=*/false, TableReaderCaller::kExternalSSTIngestion));
|
||||
std::unique_ptr<InternalIterator> range_del_iter(
|
||||
table_reader->NewRangeTombstoneIterator(ro));
|
||||
|
||||
// Get first (smallest) and last (largest) key from file.
|
||||
file_to_ingest->smallest_internal_key =
|
||||
@@ -792,8 +790,33 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
}
|
||||
file_to_ingest->smallest_internal_key.SetFrom(key);
|
||||
|
||||
iter->SeekToLast();
|
||||
pik_status = ParseInternalKey(iter->key(), &key, allow_data_in_errors);
|
||||
Slice largest;
|
||||
if (strcmp(cfd_->ioptions()->table_factory->Name(), "PlainTable") == 0) {
|
||||
// PlainTable iterator does not support SeekToLast().
|
||||
largest = iter->key();
|
||||
for (; iter->Valid(); iter->Next()) {
|
||||
if (cfd_->internal_comparator().Compare(iter->key(), largest) > 0) {
|
||||
largest = iter->key();
|
||||
}
|
||||
}
|
||||
if (!iter->status().ok()) {
|
||||
return iter->status();
|
||||
}
|
||||
} else {
|
||||
iter->SeekToLast();
|
||||
if (!iter->Valid()) {
|
||||
if (iter->status().ok()) {
|
||||
// The file contains at least 1 key since iter is valid after
|
||||
// SeekToFirst().
|
||||
return Status::Corruption("Can not find largest key in sst file");
|
||||
} else {
|
||||
return iter->status();
|
||||
}
|
||||
}
|
||||
largest = iter->key();
|
||||
}
|
||||
|
||||
pik_status = ParseInternalKey(largest, &key, allow_data_in_errors);
|
||||
if (!pik_status.ok()) {
|
||||
return Status::Corruption("Corrupted key in external file. ",
|
||||
pik_status.getState());
|
||||
@@ -804,8 +827,12 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
file_to_ingest->largest_internal_key.SetFrom(key);
|
||||
|
||||
bounds_set = true;
|
||||
} else if (!iter->status().ok()) {
|
||||
return iter->status();
|
||||
}
|
||||
|
||||
std::unique_ptr<InternalIterator> range_del_iter(
|
||||
table_reader->NewRangeTombstoneIterator(ro));
|
||||
// We may need to adjust these key bounds, depending on whether any range
|
||||
// deletion tombstones extend past them.
|
||||
const Comparator* ucmp = cfd_->internal_comparator().user_comparator();
|
||||
|
||||
@@ -127,11 +127,12 @@ class FlushJobTestBase : public testing::Test {
|
||||
column_families.emplace_back(cf_name, cf_options_);
|
||||
}
|
||||
|
||||
versions_.reset(
|
||||
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id*/ "", /*db_session_id*/ ""));
|
||||
versions_.reset(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr));
|
||||
EXPECT_OK(versions_->Recover(column_families, false));
|
||||
}
|
||||
|
||||
|
||||
@@ -186,7 +186,8 @@ Status ImportColumnFamilyJob::Run() {
|
||||
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);
|
||||
cfd_->GetLatestMutableCFOptions()->bottommost_file_compaction_delay,
|
||||
cfd_->current()->version_set()->offpeak_time_option());
|
||||
Status s;
|
||||
|
||||
for (size_t i = 0; s.ok() && i < files_to_import_.size(); ++i) {
|
||||
@@ -365,9 +366,35 @@ Status ImportColumnFamilyJob::GetIngestedFileInfo(
|
||||
bool bound_set = false;
|
||||
if (iter->Valid()) {
|
||||
file_to_import->smallest_internal_key.DecodeFrom(iter->key());
|
||||
iter->SeekToLast();
|
||||
file_to_import->largest_internal_key.DecodeFrom(iter->key());
|
||||
Slice largest;
|
||||
if (strcmp(cfd_->ioptions()->table_factory->Name(), "PlainTable") == 0) {
|
||||
// PlainTable iterator does not support SeekToLast().
|
||||
largest = iter->key();
|
||||
for (; iter->Valid(); iter->Next()) {
|
||||
if (cfd_->internal_comparator().Compare(iter->key(), largest) > 0) {
|
||||
largest = iter->key();
|
||||
}
|
||||
}
|
||||
if (!iter->status().ok()) {
|
||||
return iter->status();
|
||||
}
|
||||
} else {
|
||||
iter->SeekToLast();
|
||||
if (!iter->Valid()) {
|
||||
if (iter->status().ok()) {
|
||||
// The file contains at least 1 key since iter is valid after
|
||||
// SeekToFirst().
|
||||
return Status::Corruption("Can not find largest key in sst file");
|
||||
} else {
|
||||
return iter->status();
|
||||
}
|
||||
}
|
||||
largest = iter->key();
|
||||
}
|
||||
file_to_import->largest_internal_key.DecodeFrom(largest);
|
||||
bound_set = true;
|
||||
} else if (!iter->status().ok()) {
|
||||
return iter->status();
|
||||
}
|
||||
|
||||
std::unique_ptr<InternalIterator> range_del_iter{
|
||||
@@ -429,4 +456,4 @@ Status ImportColumnFamilyJob::GetIngestedFileInfo(
|
||||
|
||||
return status;
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "db/column_family.h"
|
||||
#include "db/log_writer.h"
|
||||
#include "db/version_set.h"
|
||||
#include "util/autovector.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -170,6 +171,16 @@ struct JobContext {
|
||||
// will be reused later
|
||||
std::vector<uint64_t> log_recycle_files;
|
||||
|
||||
// Files quarantined from deletion. This list contains file numbers for files
|
||||
// that are in an ambiguous states. This includes newly generated SST files
|
||||
// and blob files from flush and compaction job whose VersionEdits' persist
|
||||
// state in Manifest are unclear. An old manifest file whose immediately
|
||||
// following new manifest file's CURRENT file creation is in an unclear state.
|
||||
// WAL logs don't have this premature deletion risk since
|
||||
// min_log_number_to_keep is only updated after successful manifest commits.
|
||||
// So this data structure doesn't track log files.
|
||||
autovector<uint64_t> files_to_quarantine;
|
||||
|
||||
// a list of manifest files that we need to delete
|
||||
std::vector<std::string> manifest_delete_files;
|
||||
|
||||
|
||||
+14
-7
@@ -469,12 +469,14 @@ unsigned int Reader::ReadPhysicalRecord(Slice* result, size_t* drop_size,
|
||||
const unsigned int type = header[6];
|
||||
const uint32_t length = a | (b << 8);
|
||||
int header_size = kHeaderSize;
|
||||
if ((type >= kRecyclableFullType && type <= kRecyclableLastType) ||
|
||||
type == kRecyclableUserDefinedTimestampSizeType) {
|
||||
const bool is_recyclable_type =
|
||||
((type >= kRecyclableFullType && type <= kRecyclableLastType) ||
|
||||
type == kRecyclableUserDefinedTimestampSizeType);
|
||||
if (is_recyclable_type) {
|
||||
header_size = kRecyclableHeaderSize;
|
||||
if (end_of_buffer_offset_ - buffer_.size() == 0) {
|
||||
recycled_ = true;
|
||||
}
|
||||
header_size = kRecyclableHeaderSize;
|
||||
// We need enough for the larger header
|
||||
if (buffer_.size() < static_cast<size_t>(kRecyclableHeaderSize)) {
|
||||
int r = kEof;
|
||||
@@ -483,11 +485,8 @@ unsigned int Reader::ReadPhysicalRecord(Slice* result, size_t* drop_size,
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const uint32_t log_num = DecodeFixed32(header + 7);
|
||||
if (log_num != log_number_) {
|
||||
return kOldRecord;
|
||||
}
|
||||
}
|
||||
|
||||
if (header_size + length > buffer_.size()) {
|
||||
assert(buffer_.size() >= static_cast<size_t>(header_size));
|
||||
*drop_size = buffer_.size();
|
||||
@@ -499,6 +498,14 @@ unsigned int Reader::ReadPhysicalRecord(Slice* result, size_t* drop_size,
|
||||
return kBadRecordLen;
|
||||
}
|
||||
|
||||
if (is_recyclable_type) {
|
||||
const uint32_t log_num = DecodeFixed32(header + 7);
|
||||
if (log_num != log_number_) {
|
||||
buffer_.remove_prefix(header_size + length);
|
||||
return kOldRecord;
|
||||
}
|
||||
}
|
||||
|
||||
if (type == kZeroType && length == 0) {
|
||||
// Skip zero length record without reporting any drops since
|
||||
// such records are produced by the mmap based writing code in
|
||||
|
||||
@@ -106,8 +106,9 @@ class MemTableListTest : public testing::Test {
|
||||
VersionSet versions(dbname, &immutable_db_options, env_options,
|
||||
table_cache.get(), &write_buffer_manager,
|
||||
&write_controller, /*block_cache_tracer=*/nullptr,
|
||||
/*io_tracer=*/nullptr, /*db_id*/ "",
|
||||
/*db_session_id*/ "");
|
||||
/*io_tracer=*/nullptr, /*db_id=*/"",
|
||||
/*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr);
|
||||
std::vector<ColumnFamilyDescriptor> cf_descs;
|
||||
cf_descs.emplace_back(kDefaultColumnFamilyName, ColumnFamilyOptions());
|
||||
cf_descs.emplace_back("one", ColumnFamilyOptions());
|
||||
@@ -157,8 +158,9 @@ class MemTableListTest : public testing::Test {
|
||||
VersionSet versions(dbname, &immutable_db_options, env_options,
|
||||
table_cache.get(), &write_buffer_manager,
|
||||
&write_controller, /*block_cache_tracer=*/nullptr,
|
||||
/*io_tracer=*/nullptr, /*db_id*/ "",
|
||||
/*db_session_id*/ "");
|
||||
/*io_tracer=*/nullptr, /*db_id=*/"",
|
||||
/*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr);
|
||||
std::vector<ColumnFamilyDescriptor> cf_descs;
|
||||
cf_descs.emplace_back(kDefaultColumnFamilyName, ColumnFamilyOptions());
|
||||
cf_descs.emplace_back("one", ColumnFamilyOptions());
|
||||
|
||||
@@ -165,7 +165,7 @@ TEST_F(ObsoleteFilesTest, DeleteObsoleteOptionsFile) {
|
||||
{{"paranoid_file_checks", "true"}}));
|
||||
}
|
||||
}
|
||||
ASSERT_OK(dbfull()->EnableFileDeletions(true /* force */));
|
||||
ASSERT_OK(dbfull()->EnableFileDeletions(/*force=*/false));
|
||||
|
||||
Close();
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ Status PeriodicTaskScheduler::Unregister(PeriodicTaskType task_type) {
|
||||
}
|
||||
|
||||
Timer* PeriodicTaskScheduler::Default() {
|
||||
static Timer timer(SystemClock::Default().get());
|
||||
STATIC_AVOID_DESTRUCTION(Timer, timer)(SystemClock::Default().get());
|
||||
return &timer;
|
||||
}
|
||||
|
||||
@@ -108,4 +108,3 @@ void PeriodicTaskScheduler::TEST_OverrideTimer(SystemClock* clock) {
|
||||
#endif // NDEBUG
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
+4
-2
@@ -122,7 +122,8 @@ class Repairer {
|
||||
vset_(dbname_, &immutable_db_options_, file_options_,
|
||||
raw_table_cache_.get(), &wb_, &wc_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", db_session_id_),
|
||||
/*db_id=*/"", db_session_id_, db_options.daily_offpeak_time_utc,
|
||||
/*error_handler=*/nullptr),
|
||||
next_file_number_(1),
|
||||
db_lock_(nullptr),
|
||||
closed_(false) {
|
||||
@@ -694,7 +695,8 @@ class Repairer {
|
||||
cfd->NumberLevels(), cfd->ioptions()->compaction_style,
|
||||
nullptr /* src_vstorage */, cfd->ioptions()->force_consistency_checks,
|
||||
EpochNumberRequirement::kMightMissing, cfd->ioptions()->clock,
|
||||
/*bottommost_file_compaction_delay=*/0);
|
||||
/*bottommost_file_compaction_delay=*/0,
|
||||
cfd->current()->version_set()->offpeak_time_option());
|
||||
Status s;
|
||||
VersionEdit dummy_edit;
|
||||
for (const auto* table : cf_id_and_tables.second) {
|
||||
|
||||
+33
-23
@@ -38,7 +38,8 @@ class VersionBuilderTest : public testing::Test {
|
||||
mutable_cf_options_(options_),
|
||||
vstorage_(&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel,
|
||||
nullptr, false, EpochNumberRequirement::kMustPresent,
|
||||
ioptions_.clock, options_.bottommost_file_compaction_delay),
|
||||
ioptions_.clock, options_.bottommost_file_compaction_delay,
|
||||
OffpeakTimeOption(options_.daily_offpeak_time_utc)),
|
||||
file_num_(1) {
|
||||
mutable_cf_options_.RefreshDerivedOptions(ioptions_);
|
||||
size_being_compacted_.resize(options_.num_levels);
|
||||
@@ -202,7 +203,8 @@ TEST_F(VersionBuilderTest, ApplyAndSaveTo) {
|
||||
|
||||
VersionStorageInfo new_vstorage(
|
||||
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, nullptr, false,
|
||||
EpochNumberRequirement::kMightMissing, nullptr, 0);
|
||||
EpochNumberRequirement::kMightMissing, nullptr, 0,
|
||||
OffpeakTimeOption(options_.daily_offpeak_time_utc));
|
||||
ASSERT_OK(version_builder.Apply(&version_edit));
|
||||
ASSERT_OK(version_builder.SaveTo(&new_vstorage));
|
||||
|
||||
@@ -253,7 +255,8 @@ TEST_F(VersionBuilderTest, ApplyAndSaveToDynamic) {
|
||||
|
||||
VersionStorageInfo new_vstorage(
|
||||
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, nullptr, false,
|
||||
EpochNumberRequirement::kMightMissing, nullptr, 0);
|
||||
EpochNumberRequirement::kMightMissing, nullptr, 0,
|
||||
OffpeakTimeOption(options_.daily_offpeak_time_utc));
|
||||
ASSERT_OK(version_builder.Apply(&version_edit));
|
||||
ASSERT_OK(version_builder.SaveTo(&new_vstorage));
|
||||
|
||||
@@ -308,7 +311,8 @@ TEST_F(VersionBuilderTest, ApplyAndSaveToDynamic2) {
|
||||
|
||||
VersionStorageInfo new_vstorage(
|
||||
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, nullptr, false,
|
||||
EpochNumberRequirement::kMightMissing, nullptr, 0);
|
||||
EpochNumberRequirement::kMightMissing, nullptr, 0,
|
||||
OffpeakTimeOption(options_.daily_offpeak_time_utc));
|
||||
ASSERT_OK(version_builder.Apply(&version_edit));
|
||||
ASSERT_OK(version_builder.SaveTo(&new_vstorage));
|
||||
|
||||
@@ -365,7 +369,8 @@ TEST_F(VersionBuilderTest, ApplyMultipleAndSaveTo) {
|
||||
|
||||
VersionStorageInfo new_vstorage(
|
||||
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, nullptr, false,
|
||||
EpochNumberRequirement::kMightMissing, nullptr, 0);
|
||||
EpochNumberRequirement::kMightMissing, nullptr, 0,
|
||||
OffpeakTimeOption(options_.daily_offpeak_time_utc));
|
||||
ASSERT_OK(version_builder.Apply(&version_edit));
|
||||
ASSERT_OK(version_builder.SaveTo(&new_vstorage));
|
||||
|
||||
@@ -388,7 +393,8 @@ TEST_F(VersionBuilderTest, ApplyDeleteAndSaveTo) {
|
||||
|
||||
VersionStorageInfo new_vstorage(
|
||||
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, nullptr, false,
|
||||
EpochNumberRequirement::kMightMissing, nullptr, 0);
|
||||
EpochNumberRequirement::kMightMissing, nullptr, 0,
|
||||
OffpeakTimeOption(options_.daily_offpeak_time_utc));
|
||||
|
||||
VersionEdit version_edit;
|
||||
version_edit.AddFile(
|
||||
@@ -557,7 +563,7 @@ TEST_F(VersionBuilderTest, ApplyFileDeletionAndAddition) {
|
||||
VersionStorageInfo new_vstorage(
|
||||
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, &vstorage_,
|
||||
force_consistency_checks, EpochNumberRequirement::kMightMissing, nullptr,
|
||||
0);
|
||||
0, OffpeakTimeOption(options_.daily_offpeak_time_utc));
|
||||
|
||||
ASSERT_OK(builder.SaveTo(&new_vstorage));
|
||||
|
||||
@@ -702,7 +708,7 @@ TEST_F(VersionBuilderTest, ApplyFileAdditionAndDeletion) {
|
||||
VersionStorageInfo new_vstorage(
|
||||
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, &vstorage_,
|
||||
force_consistency_checks, EpochNumberRequirement::kMightMissing, nullptr,
|
||||
0);
|
||||
0, OffpeakTimeOption(options_.daily_offpeak_time_utc));
|
||||
|
||||
ASSERT_OK(builder.SaveTo(&new_vstorage));
|
||||
|
||||
@@ -747,7 +753,7 @@ TEST_F(VersionBuilderTest, ApplyBlobFileAddition) {
|
||||
VersionStorageInfo new_vstorage(
|
||||
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, &vstorage_,
|
||||
force_consistency_checks, EpochNumberRequirement::kMightMissing, nullptr,
|
||||
0);
|
||||
0, OffpeakTimeOption(options_.daily_offpeak_time_utc));
|
||||
|
||||
ASSERT_OK(builder.SaveTo(&new_vstorage));
|
||||
|
||||
@@ -887,7 +893,7 @@ TEST_F(VersionBuilderTest, ApplyBlobFileGarbageFileInBase) {
|
||||
VersionStorageInfo new_vstorage(
|
||||
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, &vstorage_,
|
||||
force_consistency_checks, EpochNumberRequirement::kMightMissing, nullptr,
|
||||
0);
|
||||
0, OffpeakTimeOption(options_.daily_offpeak_time_utc));
|
||||
|
||||
ASSERT_OK(builder.SaveTo(&new_vstorage));
|
||||
|
||||
@@ -961,7 +967,7 @@ TEST_F(VersionBuilderTest, ApplyBlobFileGarbageFileAdditionApplied) {
|
||||
VersionStorageInfo new_vstorage(
|
||||
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, &vstorage_,
|
||||
force_consistency_checks, EpochNumberRequirement::kMightMissing, nullptr,
|
||||
0);
|
||||
0, OffpeakTimeOption(options_.daily_offpeak_time_utc));
|
||||
|
||||
ASSERT_OK(builder.SaveTo(&new_vstorage));
|
||||
|
||||
@@ -1142,7 +1148,7 @@ TEST_F(VersionBuilderTest, SaveBlobFilesTo) {
|
||||
VersionStorageInfo new_vstorage(
|
||||
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, &vstorage_,
|
||||
force_consistency_checks, EpochNumberRequirement::kMightMissing, nullptr,
|
||||
0);
|
||||
0, OffpeakTimeOption(options_.daily_offpeak_time_utc));
|
||||
|
||||
ASSERT_OK(builder.SaveTo(&new_vstorage));
|
||||
|
||||
@@ -1191,7 +1197,7 @@ TEST_F(VersionBuilderTest, SaveBlobFilesTo) {
|
||||
VersionStorageInfo newer_vstorage(
|
||||
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, &new_vstorage,
|
||||
force_consistency_checks, EpochNumberRequirement::kMightMissing, nullptr,
|
||||
0);
|
||||
0, OffpeakTimeOption(options_.daily_offpeak_time_utc));
|
||||
|
||||
ASSERT_OK(second_builder.SaveTo(&newer_vstorage));
|
||||
|
||||
@@ -1278,7 +1284,7 @@ TEST_F(VersionBuilderTest, SaveBlobFilesToConcurrentJobs) {
|
||||
VersionStorageInfo new_vstorage(
|
||||
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, &vstorage_,
|
||||
force_consistency_checks, EpochNumberRequirement::kMightMissing, nullptr,
|
||||
0);
|
||||
0, OffpeakTimeOption(options_.daily_offpeak_time_utc));
|
||||
|
||||
ASSERT_OK(builder.SaveTo(&new_vstorage));
|
||||
|
||||
@@ -1382,7 +1388,7 @@ TEST_F(VersionBuilderTest, CheckConsistencyForBlobFiles) {
|
||||
VersionStorageInfo new_vstorage(
|
||||
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, &vstorage_,
|
||||
force_consistency_checks, EpochNumberRequirement::kMightMissing, nullptr,
|
||||
0);
|
||||
0, OffpeakTimeOption(options_.daily_offpeak_time_utc));
|
||||
|
||||
ASSERT_OK(builder.SaveTo(&new_vstorage));
|
||||
|
||||
@@ -1422,7 +1428,7 @@ TEST_F(VersionBuilderTest, CheckConsistencyForBlobFilesInconsistentLinks) {
|
||||
VersionStorageInfo new_vstorage(
|
||||
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, &vstorage_,
|
||||
force_consistency_checks, EpochNumberRequirement::kMightMissing, nullptr,
|
||||
0);
|
||||
0, OffpeakTimeOption(options_.daily_offpeak_time_utc));
|
||||
|
||||
const Status s = builder.SaveTo(&new_vstorage);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
@@ -1464,7 +1470,7 @@ TEST_F(VersionBuilderTest, CheckConsistencyForBlobFilesAllGarbage) {
|
||||
VersionStorageInfo new_vstorage(
|
||||
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, &vstorage_,
|
||||
force_consistency_checks, EpochNumberRequirement::kMightMissing, nullptr,
|
||||
0);
|
||||
0, OffpeakTimeOption(options_.daily_offpeak_time_utc));
|
||||
|
||||
const Status s = builder.SaveTo(&new_vstorage);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
@@ -1514,7 +1520,7 @@ TEST_F(VersionBuilderTest, CheckConsistencyForBlobFilesAllGarbageLinkedSsts) {
|
||||
VersionStorageInfo new_vstorage(
|
||||
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, &vstorage_,
|
||||
force_consistency_checks, EpochNumberRequirement::kMightMissing, nullptr,
|
||||
0);
|
||||
0, OffpeakTimeOption(options_.daily_offpeak_time_utc));
|
||||
|
||||
const Status s = builder.SaveTo(&new_vstorage);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
@@ -1678,7 +1684,7 @@ TEST_F(VersionBuilderTest, MaintainLinkedSstsForBlobFiles) {
|
||||
VersionStorageInfo new_vstorage(
|
||||
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, &vstorage_,
|
||||
force_consistency_checks, EpochNumberRequirement::kMightMissing, nullptr,
|
||||
0);
|
||||
0, OffpeakTimeOption(options_.daily_offpeak_time_utc));
|
||||
|
||||
ASSERT_OK(builder.SaveTo(&new_vstorage));
|
||||
|
||||
@@ -1730,7 +1736,8 @@ TEST_F(VersionBuilderTest, CheckConsistencyForFileDeletedTwice) {
|
||||
VersionStorageInfo new_vstorage(
|
||||
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, nullptr,
|
||||
true /* force_consistency_checks */,
|
||||
EpochNumberRequirement::kMightMissing, nullptr, 0);
|
||||
EpochNumberRequirement::kMightMissing, nullptr, 0,
|
||||
OffpeakTimeOption(options_.daily_offpeak_time_utc));
|
||||
ASSERT_OK(version_builder.Apply(&version_edit));
|
||||
ASSERT_OK(version_builder.SaveTo(&new_vstorage));
|
||||
|
||||
@@ -1741,7 +1748,8 @@ TEST_F(VersionBuilderTest, CheckConsistencyForFileDeletedTwice) {
|
||||
VersionStorageInfo new_vstorage2(
|
||||
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, nullptr,
|
||||
true /* force_consistency_checks */,
|
||||
EpochNumberRequirement::kMightMissing, nullptr, 0);
|
||||
EpochNumberRequirement::kMightMissing, nullptr, 0,
|
||||
OffpeakTimeOption(options_.daily_offpeak_time_utc));
|
||||
ASSERT_NOK(version_builder2.Apply(&version_edit));
|
||||
|
||||
UnrefFilesInVersion(&new_vstorage);
|
||||
@@ -1780,7 +1788,8 @@ TEST_F(VersionBuilderTest, CheckConsistencyForL0FilesSortedByEpochNumber) {
|
||||
VersionStorageInfo new_vstorage_1(
|
||||
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel,
|
||||
nullptr /* src_vstorage */, true /* force_consistency_checks */,
|
||||
EpochNumberRequirement::kMightMissing, nullptr, 0);
|
||||
EpochNumberRequirement::kMightMissing, nullptr, 0,
|
||||
OffpeakTimeOption(options_.daily_offpeak_time_utc));
|
||||
|
||||
ASSERT_OK(version_builder_1.Apply(&version_edit_1));
|
||||
s = version_builder_1.SaveTo(&new_vstorage_1);
|
||||
@@ -1818,7 +1827,8 @@ TEST_F(VersionBuilderTest, CheckConsistencyForL0FilesSortedByEpochNumber) {
|
||||
VersionStorageInfo new_vstorage_2(
|
||||
&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel,
|
||||
nullptr /* src_vstorage */, true /* force_consistency_checks */,
|
||||
EpochNumberRequirement::kMightMissing, nullptr, 0);
|
||||
EpochNumberRequirement::kMightMissing, nullptr, 0,
|
||||
OffpeakTimeOption(options_.daily_offpeak_time_utc));
|
||||
|
||||
ASSERT_OK(version_builder_2.Apply(&version_edit_2));
|
||||
s = version_builder_2.SaveTo(&new_vstorage_2);
|
||||
|
||||
+29
-10
@@ -492,6 +492,7 @@ class VersionEdit {
|
||||
file_checksum_func_name, unique_id,
|
||||
compensated_range_deletion_size, tail_size,
|
||||
user_defined_timestamps_persisted));
|
||||
files_to_quarantine_.push_back(file);
|
||||
if (!HasLastSequence() || largest_seqno > GetLastSequence()) {
|
||||
SetLastSequence(largest_seqno);
|
||||
}
|
||||
@@ -500,6 +501,7 @@ class VersionEdit {
|
||||
void AddFile(int level, const FileMetaData& f) {
|
||||
assert(f.fd.smallest_seqno <= f.fd.largest_seqno);
|
||||
new_files_.emplace_back(level, f);
|
||||
files_to_quarantine_.push_back(f.fd.GetNumber());
|
||||
if (!HasLastSequence() || f.fd.largest_seqno > GetLastSequence()) {
|
||||
SetLastSequence(f.fd.largest_seqno);
|
||||
}
|
||||
@@ -536,10 +538,13 @@ class VersionEdit {
|
||||
blob_file_additions_.emplace_back(
|
||||
blob_file_number, total_blob_count, total_blob_bytes,
|
||||
std::move(checksum_method), std::move(checksum_value));
|
||||
files_to_quarantine_.push_back(blob_file_number);
|
||||
}
|
||||
|
||||
void AddBlobFile(BlobFileAddition blob_file_addition) {
|
||||
blob_file_additions_.emplace_back(std::move(blob_file_addition));
|
||||
files_to_quarantine_.push_back(
|
||||
blob_file_additions_.back().GetBlobFileNumber());
|
||||
}
|
||||
|
||||
// Retrieve all the blob files added.
|
||||
@@ -551,6 +556,11 @@ class VersionEdit {
|
||||
void SetBlobFileAdditions(BlobFileAdditions blob_file_additions) {
|
||||
assert(blob_file_additions_.empty());
|
||||
blob_file_additions_ = std::move(blob_file_additions);
|
||||
std::for_each(
|
||||
blob_file_additions_.begin(), blob_file_additions_.end(),
|
||||
[&](const BlobFileAddition& blob_file) {
|
||||
files_to_quarantine_.push_back(blob_file.GetBlobFileNumber());
|
||||
});
|
||||
}
|
||||
|
||||
// Add garbage for an existing blob file. Note: intentionally broken English
|
||||
@@ -618,6 +628,8 @@ class VersionEdit {
|
||||
}
|
||||
uint32_t GetColumnFamily() const { return column_family_; }
|
||||
|
||||
const std::string& GetColumnFamilyName() const { return column_family_name_; }
|
||||
|
||||
// set column family ID by calling SetColumnFamily()
|
||||
void AddColumnFamily(const std::string& name) {
|
||||
assert(!is_column_family_drop_);
|
||||
@@ -648,6 +660,9 @@ class VersionEdit {
|
||||
remaining_entries_ = remaining_entries;
|
||||
}
|
||||
bool IsInAtomicGroup() const { return is_in_atomic_group_; }
|
||||
void SetRemainingEntries(uint32_t remaining_entries) {
|
||||
remaining_entries_ = remaining_entries;
|
||||
}
|
||||
uint32_t GetRemainingEntries() const { return remaining_entries_; }
|
||||
|
||||
bool HasFullHistoryTsLow() const { return !full_history_ts_low_.empty(); }
|
||||
@@ -674,20 +689,14 @@ class VersionEdit {
|
||||
std::optional<size_t> ts_sz = std::nullopt) const;
|
||||
Status DecodeFrom(const Slice& src);
|
||||
|
||||
const autovector<uint64_t>* GetFilesToQuarantineIfCommitFail() const {
|
||||
return &files_to_quarantine_;
|
||||
}
|
||||
|
||||
std::string DebugString(bool hex_key = false) const;
|
||||
std::string DebugJSON(int edit_num, bool hex_key = false) const;
|
||||
|
||||
private:
|
||||
friend class ReactiveVersionSet;
|
||||
friend class VersionEditHandlerBase;
|
||||
friend class ListColumnFamiliesHandler;
|
||||
friend class VersionEditHandler;
|
||||
friend class VersionEditHandlerPointInTime;
|
||||
friend class DumpManifestHandler;
|
||||
friend class VersionSet;
|
||||
friend class Version;
|
||||
friend class AtomicGroupReadBuffer;
|
||||
|
||||
bool GetLevel(Slice* input, int* level, const char** msg);
|
||||
|
||||
const char* DecodeNewFile4From(Slice* input);
|
||||
@@ -745,6 +754,16 @@ class VersionEdit {
|
||||
|
||||
std::string full_history_ts_low_;
|
||||
bool persist_user_defined_timestamps_ = true;
|
||||
|
||||
// Newly created table files and blob files are eligible for deletion if they
|
||||
// are not registered as live files after the background jobs creating them
|
||||
// have finished. In case committing the VersionEdit containing such changes
|
||||
// to manifest encountered an error, we want to quarantine these files from
|
||||
// deletion to avoid prematurely deleting files that ended up getting recorded
|
||||
// in Manifest as live files.
|
||||
// Since table files and blob files share the same file number space, we just
|
||||
// record the file number here.
|
||||
autovector<uint64_t> files_to_quarantine_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+75
-76
@@ -43,7 +43,7 @@ void VersionEditHandlerBase::Iterate(log::Reader& reader,
|
||||
break;
|
||||
}
|
||||
ColumnFamilyData* cfd = nullptr;
|
||||
if (edit.is_in_atomic_group_) {
|
||||
if (edit.IsInAtomicGroup()) {
|
||||
if (read_buffer_.IsFull()) {
|
||||
for (auto& e : read_buffer_.replay_buffer()) {
|
||||
s = ApplyVersionEdit(e, &cfd);
|
||||
@@ -101,20 +101,18 @@ void VersionEditHandlerBase::Iterate(log::Reader& reader,
|
||||
Status ListColumnFamiliesHandler::ApplyVersionEdit(
|
||||
VersionEdit& edit, ColumnFamilyData** /*unused*/) {
|
||||
Status s;
|
||||
if (edit.is_column_family_add_) {
|
||||
if (column_family_names_.find(edit.column_family_) !=
|
||||
column_family_names_.end()) {
|
||||
uint32_t cf_id = edit.GetColumnFamily();
|
||||
if (edit.IsColumnFamilyAdd()) {
|
||||
if (column_family_names_.find(cf_id) != column_family_names_.end()) {
|
||||
s = Status::Corruption("Manifest adding the same column family twice");
|
||||
} else {
|
||||
column_family_names_.insert(
|
||||
{edit.column_family_, edit.column_family_name_});
|
||||
column_family_names_.insert({cf_id, edit.GetColumnFamilyName()});
|
||||
}
|
||||
} else if (edit.is_column_family_drop_) {
|
||||
if (column_family_names_.find(edit.column_family_) ==
|
||||
column_family_names_.end()) {
|
||||
} else if (edit.IsColumnFamilyDrop()) {
|
||||
if (column_family_names_.find(cf_id) == column_family_names_.end()) {
|
||||
s = Status::Corruption("Manifest - dropping non-existing column family");
|
||||
} else {
|
||||
column_family_names_.erase(edit.column_family_);
|
||||
column_family_names_.erase(cf_id);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
@@ -201,9 +199,9 @@ Status VersionEditHandler::Initialize() {
|
||||
Status VersionEditHandler::ApplyVersionEdit(VersionEdit& edit,
|
||||
ColumnFamilyData** cfd) {
|
||||
Status s;
|
||||
if (edit.is_column_family_add_) {
|
||||
if (edit.IsColumnFamilyAdd()) {
|
||||
s = OnColumnFamilyAdd(edit, cfd);
|
||||
} else if (edit.is_column_family_drop_) {
|
||||
} else if (edit.IsColumnFamilyDrop()) {
|
||||
s = OnColumnFamilyDrop(edit, cfd);
|
||||
} else if (edit.IsWalAddition()) {
|
||||
s = OnWalAddition(edit);
|
||||
@@ -227,22 +225,22 @@ Status VersionEditHandler::OnColumnFamilyAdd(VersionEdit& edit,
|
||||
|
||||
assert(cfd != nullptr);
|
||||
*cfd = nullptr;
|
||||
const std::string& cf_name = edit.GetColumnFamilyName();
|
||||
Status s;
|
||||
if (cf_in_builders || cf_in_not_found) {
|
||||
s = Status::Corruption("MANIFEST adding the same column family twice: " +
|
||||
edit.column_family_name_);
|
||||
cf_name);
|
||||
}
|
||||
if (s.ok()) {
|
||||
auto cf_options = name_to_options_.find(edit.column_family_name_);
|
||||
auto cf_options = name_to_options_.find(cf_name);
|
||||
// implicitly add persistent_stats column family without requiring user
|
||||
// to specify
|
||||
ColumnFamilyData* tmp_cfd = nullptr;
|
||||
bool is_persistent_stats_column_family =
|
||||
edit.column_family_name_.compare(kPersistentStatsColumnFamilyName) == 0;
|
||||
cf_name.compare(kPersistentStatsColumnFamilyName) == 0;
|
||||
if (cf_options == name_to_options_.end() &&
|
||||
!is_persistent_stats_column_family) {
|
||||
column_families_not_found_.emplace(edit.column_family_,
|
||||
edit.column_family_name_);
|
||||
column_families_not_found_.emplace(edit.GetColumnFamily(), cf_name);
|
||||
} else {
|
||||
if (is_persistent_stats_column_family) {
|
||||
ColumnFamilyOptions cfo;
|
||||
@@ -270,7 +268,7 @@ Status VersionEditHandler::OnColumnFamilyDrop(VersionEdit& edit,
|
||||
if (cf_in_builders) {
|
||||
tmp_cfd = DestroyCfAndCleanup(edit);
|
||||
} else if (cf_in_not_found) {
|
||||
column_families_not_found_.erase(edit.column_family_);
|
||||
column_families_not_found_.erase(edit.GetColumnFamily());
|
||||
} else {
|
||||
s = Status::Corruption("MANIFEST - dropping non-existing column family");
|
||||
}
|
||||
@@ -305,10 +303,10 @@ Status VersionEditHandler::OnNonCfOperation(VersionEdit& edit,
|
||||
}
|
||||
ColumnFamilyData* tmp_cfd = nullptr;
|
||||
if (s.ok()) {
|
||||
auto builder_iter = builders_.find(edit.column_family_);
|
||||
auto builder_iter = builders_.find(edit.GetColumnFamily());
|
||||
assert(builder_iter != builders_.end());
|
||||
tmp_cfd = version_set_->GetColumnFamilySet()->GetColumnFamily(
|
||||
edit.column_family_);
|
||||
edit.GetColumnFamily());
|
||||
assert(tmp_cfd != nullptr);
|
||||
// It's important to handle file boundaries before `MaybeCreateVersion`
|
||||
// because `VersionEditHandlerPointInTime::MaybeCreateVersion` does
|
||||
@@ -362,11 +360,12 @@ void VersionEditHandler::CheckColumnFamilyId(const VersionEdit& edit,
|
||||
// record. Once we encounter column family drop record,
|
||||
// we will delete the column family from
|
||||
// column_families_not_found.
|
||||
bool in_not_found = column_families_not_found_.find(edit.column_family_) !=
|
||||
uint32_t cf_id = edit.GetColumnFamily();
|
||||
bool in_not_found = column_families_not_found_.find(cf_id) !=
|
||||
column_families_not_found_.end();
|
||||
// in builders means that user supplied that column family
|
||||
// option AND that we encountered column family add record
|
||||
bool in_builders = builders_.find(edit.column_family_) != builders_.end();
|
||||
bool in_builders = builders_.find(cf_id) != builders_.end();
|
||||
// They cannot both be true
|
||||
assert(!(in_not_found && in_builders));
|
||||
*cf_in_not_found = in_not_found;
|
||||
@@ -378,17 +377,17 @@ void VersionEditHandler::CheckIterationResult(const log::Reader& reader,
|
||||
assert(s != nullptr);
|
||||
if (!s->ok()) {
|
||||
// Do nothing here.
|
||||
} else if (!version_edit_params_.has_log_number_ ||
|
||||
!version_edit_params_.has_next_file_number_ ||
|
||||
!version_edit_params_.has_last_sequence_) {
|
||||
} else if (!version_edit_params_.HasLogNumber() ||
|
||||
!version_edit_params_.HasNextFile() ||
|
||||
!version_edit_params_.HasLastSequence()) {
|
||||
std::string msg("no ");
|
||||
if (!version_edit_params_.has_log_number_) {
|
||||
if (!version_edit_params_.HasLogNumber()) {
|
||||
msg.append("log_file_number, ");
|
||||
}
|
||||
if (!version_edit_params_.has_next_file_number_) {
|
||||
if (!version_edit_params_.HasNextFile()) {
|
||||
msg.append("next_file_number, ");
|
||||
}
|
||||
if (!version_edit_params_.has_last_sequence_) {
|
||||
if (!version_edit_params_.HasLastSequence()) {
|
||||
msg.append("last_sequence, ");
|
||||
}
|
||||
msg = msg.substr(0, msg.size() - 2);
|
||||
@@ -409,11 +408,11 @@ void VersionEditHandler::CheckIterationResult(const log::Reader& reader,
|
||||
}
|
||||
if (s->ok()) {
|
||||
version_set_->GetColumnFamilySet()->UpdateMaxColumnFamily(
|
||||
version_edit_params_.max_column_family_);
|
||||
version_edit_params_.GetMaxColumnFamily());
|
||||
version_set_->MarkMinLogNumberToKeep(
|
||||
version_edit_params_.min_log_number_to_keep_);
|
||||
version_set_->MarkFileNumberUsed(version_edit_params_.prev_log_number_);
|
||||
version_set_->MarkFileNumberUsed(version_edit_params_.log_number_);
|
||||
version_edit_params_.GetMinLogNumberToKeep());
|
||||
version_set_->MarkFileNumberUsed(version_edit_params_.GetPrevLogNumber());
|
||||
version_set_->MarkFileNumberUsed(version_edit_params_.GetLogNumber());
|
||||
for (auto* cfd : *(version_set_->GetColumnFamilySet())) {
|
||||
if (cfd->IsDropped()) {
|
||||
continue;
|
||||
@@ -464,9 +463,9 @@ void VersionEditHandler::CheckIterationResult(const log::Reader& reader,
|
||||
if (s->ok()) {
|
||||
version_set_->manifest_file_size_ = reader.GetReadOffset();
|
||||
assert(version_set_->manifest_file_size_ > 0);
|
||||
version_set_->next_file_number_.store(
|
||||
version_edit_params_.next_file_number_ + 1);
|
||||
SequenceNumber last_seq = version_edit_params_.last_sequence_;
|
||||
version_set_->next_file_number_.store(version_edit_params_.GetNextFile() +
|
||||
1);
|
||||
SequenceNumber last_seq = version_edit_params_.GetLastSequence();
|
||||
assert(last_seq != kMaxSequenceNumber);
|
||||
if (last_seq != kMaxSequenceNumber &&
|
||||
last_seq > version_set_->last_allocated_sequence_.load()) {
|
||||
@@ -488,46 +487,46 @@ void VersionEditHandler::CheckIterationResult(const log::Reader& reader,
|
||||
// sequence number zeroed through compaction.
|
||||
version_set_->descriptor_last_sequence_ = last_seq;
|
||||
}
|
||||
version_set_->prev_log_number_ = version_edit_params_.prev_log_number_;
|
||||
version_set_->prev_log_number_ = version_edit_params_.GetPrevLogNumber();
|
||||
}
|
||||
}
|
||||
|
||||
ColumnFamilyData* VersionEditHandler::CreateCfAndInit(
|
||||
const ColumnFamilyOptions& cf_options, const VersionEdit& edit) {
|
||||
uint32_t cf_id = edit.GetColumnFamily();
|
||||
ColumnFamilyData* cfd =
|
||||
version_set_->CreateColumnFamily(cf_options, read_options_, &edit);
|
||||
assert(cfd != nullptr);
|
||||
cfd->set_initialized();
|
||||
assert(builders_.find(edit.column_family_) == builders_.end());
|
||||
builders_.emplace(edit.column_family_,
|
||||
assert(builders_.find(cf_id) == builders_.end());
|
||||
builders_.emplace(cf_id,
|
||||
VersionBuilderUPtr(new BaseReferencedVersionBuilder(cfd)));
|
||||
if (track_missing_files_) {
|
||||
cf_to_missing_files_.emplace(edit.column_family_,
|
||||
std::unordered_set<uint64_t>());
|
||||
cf_to_missing_blob_files_high_.emplace(edit.column_family_,
|
||||
kInvalidBlobFileNumber);
|
||||
cf_to_missing_files_.emplace(cf_id, std::unordered_set<uint64_t>());
|
||||
cf_to_missing_blob_files_high_.emplace(cf_id, kInvalidBlobFileNumber);
|
||||
}
|
||||
return cfd;
|
||||
}
|
||||
|
||||
ColumnFamilyData* VersionEditHandler::DestroyCfAndCleanup(
|
||||
const VersionEdit& edit) {
|
||||
auto builder_iter = builders_.find(edit.column_family_);
|
||||
uint32_t cf_id = edit.GetColumnFamily();
|
||||
auto builder_iter = builders_.find(cf_id);
|
||||
assert(builder_iter != builders_.end());
|
||||
builders_.erase(builder_iter);
|
||||
if (track_missing_files_) {
|
||||
auto missing_files_iter = cf_to_missing_files_.find(edit.column_family_);
|
||||
auto missing_files_iter = cf_to_missing_files_.find(cf_id);
|
||||
assert(missing_files_iter != cf_to_missing_files_.end());
|
||||
cf_to_missing_files_.erase(missing_files_iter);
|
||||
|
||||
auto missing_blob_files_high_iter =
|
||||
cf_to_missing_blob_files_high_.find(edit.column_family_);
|
||||
cf_to_missing_blob_files_high_.find(cf_id);
|
||||
assert(missing_blob_files_high_iter !=
|
||||
cf_to_missing_blob_files_high_.end());
|
||||
cf_to_missing_blob_files_high_.erase(missing_blob_files_high_iter);
|
||||
}
|
||||
ColumnFamilyData* ret =
|
||||
version_set_->GetColumnFamilySet()->GetColumnFamily(edit.column_family_);
|
||||
version_set_->GetColumnFamilySet()->GetColumnFamily(cf_id);
|
||||
assert(ret != nullptr);
|
||||
ret->SetDropped();
|
||||
ret->UnrefAndTryDelete();
|
||||
@@ -598,33 +597,33 @@ Status VersionEditHandler::LoadTables(ColumnFamilyData* cfd,
|
||||
Status VersionEditHandler::ExtractInfoFromVersionEdit(ColumnFamilyData* cfd,
|
||||
const VersionEdit& edit) {
|
||||
Status s;
|
||||
if (edit.has_db_id_) {
|
||||
if (edit.HasDbId()) {
|
||||
version_set_->db_id_ = edit.GetDbId();
|
||||
version_edit_params_.SetDBId(edit.db_id_);
|
||||
version_edit_params_.SetDBId(edit.GetDbId());
|
||||
}
|
||||
if (cfd != nullptr) {
|
||||
if (edit.has_log_number_) {
|
||||
if (cfd->GetLogNumber() > edit.log_number_) {
|
||||
if (edit.HasLogNumber()) {
|
||||
if (cfd->GetLogNumber() > edit.GetLogNumber()) {
|
||||
ROCKS_LOG_WARN(
|
||||
version_set_->db_options()->info_log,
|
||||
"MANIFEST corruption detected, but ignored - Log numbers in "
|
||||
"records NOT monotonically increasing");
|
||||
} else {
|
||||
cfd->SetLogNumber(edit.log_number_);
|
||||
version_edit_params_.SetLogNumber(edit.log_number_);
|
||||
cfd->SetLogNumber(edit.GetLogNumber());
|
||||
version_edit_params_.SetLogNumber(edit.GetLogNumber());
|
||||
}
|
||||
}
|
||||
if (edit.has_comparator_) {
|
||||
if (edit.HasComparatorName()) {
|
||||
bool mark_sst_files_has_no_udt = false;
|
||||
// If `persist_user_defined_timestamps` flag is recorded in manifest, it
|
||||
// is guaranteed to be in the same VersionEdit as comparator. Otherwise,
|
||||
// it's not recorded and it should have default value true.
|
||||
s = ValidateUserDefinedTimestampsOptions(
|
||||
cfd->user_comparator(), edit.comparator_,
|
||||
cfd->user_comparator(), edit.GetComparatorName(),
|
||||
cfd->ioptions()->persist_user_defined_timestamps,
|
||||
edit.persist_user_defined_timestamps_, &mark_sst_files_has_no_udt);
|
||||
edit.GetPersistUserDefinedTimestamps(), &mark_sst_files_has_no_udt);
|
||||
if (!s.ok() && cf_to_cmp_names_) {
|
||||
cf_to_cmp_names_->emplace(cfd->GetID(), edit.comparator_);
|
||||
cf_to_cmp_names_->emplace(cfd->GetID(), edit.GetComparatorName());
|
||||
}
|
||||
if (mark_sst_files_has_no_udt) {
|
||||
cfds_to_mark_no_udt_.insert(cfd->GetID());
|
||||
@@ -637,29 +636,29 @@ Status VersionEditHandler::ExtractInfoFromVersionEdit(ColumnFamilyData* cfd,
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
if (edit.has_prev_log_number_) {
|
||||
version_edit_params_.SetPrevLogNumber(edit.prev_log_number_);
|
||||
if (edit.HasPrevLogNumber()) {
|
||||
version_edit_params_.SetPrevLogNumber(edit.GetPrevLogNumber());
|
||||
}
|
||||
if (edit.has_next_file_number_) {
|
||||
version_edit_params_.SetNextFile(edit.next_file_number_);
|
||||
if (edit.HasNextFile()) {
|
||||
version_edit_params_.SetNextFile(edit.GetNextFile());
|
||||
}
|
||||
if (edit.has_max_column_family_) {
|
||||
version_edit_params_.SetMaxColumnFamily(edit.max_column_family_);
|
||||
if (edit.HasMaxColumnFamily()) {
|
||||
version_edit_params_.SetMaxColumnFamily(edit.GetMaxColumnFamily());
|
||||
}
|
||||
if (edit.has_min_log_number_to_keep_) {
|
||||
version_edit_params_.min_log_number_to_keep_ =
|
||||
std::max(version_edit_params_.min_log_number_to_keep_,
|
||||
edit.min_log_number_to_keep_);
|
||||
if (edit.HasMinLogNumberToKeep()) {
|
||||
version_edit_params_.SetMinLogNumberToKeep(
|
||||
std::max(version_edit_params_.GetMinLogNumberToKeep(),
|
||||
edit.GetMinLogNumberToKeep()));
|
||||
}
|
||||
if (edit.has_last_sequence_) {
|
||||
if (edit.HasLastSequence()) {
|
||||
// `VersionEdit::last_sequence_`s are assumed to be non-decreasing. This
|
||||
// is legacy behavior that cannot change without breaking downgrade
|
||||
// compatibility.
|
||||
assert(!version_edit_params_.has_last_sequence_ ||
|
||||
version_edit_params_.last_sequence_ <= edit.last_sequence_);
|
||||
version_edit_params_.SetLastSequence(edit.last_sequence_);
|
||||
assert(!version_edit_params_.HasLastSequence() ||
|
||||
version_edit_params_.GetLastSequence() <= edit.GetLastSequence());
|
||||
version_edit_params_.SetLastSequence(edit.GetLastSequence());
|
||||
}
|
||||
if (!version_edit_params_.has_prev_log_number_) {
|
||||
if (!version_edit_params_.HasPrevLogNumber()) {
|
||||
version_edit_params_.SetPrevLogNumber(0);
|
||||
}
|
||||
}
|
||||
@@ -765,7 +764,7 @@ void VersionEditHandlerPointInTime::CheckIterationResult(
|
||||
ColumnFamilyData* VersionEditHandlerPointInTime::DestroyCfAndCleanup(
|
||||
const VersionEdit& edit) {
|
||||
ColumnFamilyData* cfd = VersionEditHandler::DestroyCfAndCleanup(edit);
|
||||
auto v_iter = versions_.find(edit.column_family_);
|
||||
auto v_iter = versions_.find(edit.GetColumnFamily());
|
||||
if (v_iter != versions_.end()) {
|
||||
delete v_iter->second;
|
||||
versions_.erase(v_iter);
|
||||
@@ -777,7 +776,7 @@ Status VersionEditHandlerPointInTime::MaybeCreateVersion(
|
||||
const VersionEdit& edit, ColumnFamilyData* cfd, bool force_create_version) {
|
||||
assert(cfd != nullptr);
|
||||
if (!force_create_version) {
|
||||
assert(edit.column_family_ == cfd->GetID());
|
||||
assert(edit.GetColumnFamily() == cfd->GetID());
|
||||
}
|
||||
auto missing_files_iter = cf_to_missing_files_.find(cfd->GetID());
|
||||
assert(missing_files_iter != cf_to_missing_files_.end());
|
||||
@@ -860,9 +859,9 @@ Status VersionEditHandlerPointInTime::MaybeCreateVersion(
|
||||
const bool has_missing_files =
|
||||
!missing_files.empty() || has_missing_blob_files;
|
||||
|
||||
bool missing_info = !version_edit_params_.has_log_number_ ||
|
||||
!version_edit_params_.has_next_file_number_ ||
|
||||
!version_edit_params_.has_last_sequence_;
|
||||
bool missing_info = !version_edit_params_.HasLogNumber() ||
|
||||
!version_edit_params_.HasNextFile() ||
|
||||
!version_edit_params_.HasLastSequence();
|
||||
|
||||
// Create version before apply edit. The version will represent the state
|
||||
// before applying the version edit.
|
||||
|
||||
@@ -133,8 +133,8 @@ class VersionEditHandler : public VersionEditHandlerBase {
|
||||
bool HasMissingFiles() const;
|
||||
|
||||
void GetDbId(std::string* db_id) const {
|
||||
if (db_id && version_edit_params_.has_db_id_) {
|
||||
*db_id = version_edit_params_.db_id_;
|
||||
if (db_id && version_edit_params_.HasDbId()) {
|
||||
*db_id = version_edit_params_.GetDbId();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+90
-57
@@ -2124,7 +2124,8 @@ VersionStorageInfo::VersionStorageInfo(
|
||||
CompactionStyle compaction_style, VersionStorageInfo* ref_vstorage,
|
||||
bool _force_consistency_checks,
|
||||
EpochNumberRequirement epoch_number_requirement, SystemClock* clock,
|
||||
uint32_t bottommost_file_compaction_delay)
|
||||
uint32_t bottommost_file_compaction_delay,
|
||||
OffpeakTimeOption offpeak_time_option)
|
||||
: internal_comparator_(internal_comparator),
|
||||
user_comparator_(user_comparator),
|
||||
// cfd is nullptr if Version is dummy
|
||||
@@ -2156,7 +2157,8 @@ VersionStorageInfo::VersionStorageInfo(
|
||||
bottommost_file_compaction_delay_(bottommost_file_compaction_delay),
|
||||
finalized_(false),
|
||||
force_consistency_checks_(_force_consistency_checks),
|
||||
epoch_number_requirement_(epoch_number_requirement) {
|
||||
epoch_number_requirement_(epoch_number_requirement),
|
||||
offpeak_time_option_(std::move(offpeak_time_option)) {
|
||||
if (ref_vstorage != nullptr) {
|
||||
accumulated_file_size_ = ref_vstorage->accumulated_file_size_;
|
||||
accumulated_raw_key_size_ = ref_vstorage->accumulated_raw_key_size_;
|
||||
@@ -2200,9 +2202,9 @@ Version::Version(ColumnFamilyData* column_family_data, VersionSet* vset,
|
||||
cfd_ == nullptr ? false : cfd_->ioptions()->force_consistency_checks,
|
||||
epoch_number_requirement,
|
||||
cfd_ == nullptr ? nullptr : cfd_->ioptions()->clock,
|
||||
cfd_ == nullptr
|
||||
? 0
|
||||
: mutable_cf_options.bottommost_file_compaction_delay),
|
||||
cfd_ == nullptr ? 0
|
||||
: mutable_cf_options.bottommost_file_compaction_delay,
|
||||
vset->offpeak_time_option()),
|
||||
vset_(vset),
|
||||
next_(this),
|
||||
prev_(this),
|
||||
@@ -3670,6 +3672,16 @@ void VersionStorageInfo::ComputeFilesMarkedForPeriodicCompaction(
|
||||
const uint64_t allowed_time_limit =
|
||||
current_time - periodic_compaction_seconds;
|
||||
|
||||
// Find the adjust_allowed_time_limit such that it includes files that are
|
||||
// going to expire by the time next daily offpeak starts.
|
||||
const OffpeakTimeInfo offpeak_time_info =
|
||||
offpeak_time_option_.GetOffpeakTimeInfo(current_time);
|
||||
const uint64_t adjusted_allowed_time_limit =
|
||||
allowed_time_limit +
|
||||
(offpeak_time_info.is_now_offpeak
|
||||
? offpeak_time_info.seconds_till_next_offpeak_start
|
||||
: 0);
|
||||
|
||||
for (int level = 0; level <= last_level; level++) {
|
||||
for (auto f : files_[level]) {
|
||||
if (!f->being_compacted) {
|
||||
@@ -3696,7 +3708,7 @@ void VersionStorageInfo::ComputeFilesMarkedForPeriodicCompaction(
|
||||
}
|
||||
}
|
||||
if (file_modification_time > 0 &&
|
||||
file_modification_time < allowed_time_limit) {
|
||||
file_modification_time < adjusted_allowed_time_limit) {
|
||||
files_marked_for_periodic_compaction_.emplace_back(level, f);
|
||||
}
|
||||
}
|
||||
@@ -5000,15 +5012,15 @@ struct VersionSet::ManifestWriter {
|
||||
|
||||
Status AtomicGroupReadBuffer::AddEdit(VersionEdit* edit) {
|
||||
assert(edit);
|
||||
if (edit->is_in_atomic_group_) {
|
||||
if (edit->IsInAtomicGroup()) {
|
||||
TEST_SYNC_POINT("AtomicGroupReadBuffer::AddEdit:AtomicGroup");
|
||||
if (replay_buffer_.empty()) {
|
||||
replay_buffer_.resize(edit->remaining_entries_ + 1);
|
||||
replay_buffer_.resize(edit->GetRemainingEntries() + 1);
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"AtomicGroupReadBuffer::AddEdit:FirstInAtomicGroup", edit);
|
||||
}
|
||||
read_edits_in_atomic_group_++;
|
||||
if (read_edits_in_atomic_group_ + edit->remaining_entries_ !=
|
||||
if (read_edits_in_atomic_group_ + edit->GetRemainingEntries() !=
|
||||
static_cast<uint32_t>(replay_buffer_.size())) {
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"AtomicGroupReadBuffer::AddEdit:IncorrectAtomicGroupSize", edit);
|
||||
@@ -5043,15 +5055,14 @@ void AtomicGroupReadBuffer::Clear() {
|
||||
replay_buffer_.clear();
|
||||
}
|
||||
|
||||
VersionSet::VersionSet(const std::string& dbname,
|
||||
const ImmutableDBOptions* _db_options,
|
||||
const FileOptions& storage_options, Cache* table_cache,
|
||||
WriteBufferManager* write_buffer_manager,
|
||||
WriteController* write_controller,
|
||||
BlockCacheTracer* const block_cache_tracer,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::string& db_id,
|
||||
const std::string& db_session_id)
|
||||
VersionSet::VersionSet(
|
||||
const std::string& dbname, const ImmutableDBOptions* _db_options,
|
||||
const FileOptions& storage_options, Cache* table_cache,
|
||||
WriteBufferManager* write_buffer_manager, WriteController* write_controller,
|
||||
BlockCacheTracer* const block_cache_tracer,
|
||||
const std::shared_ptr<IOTracer>& io_tracer, const std::string& db_id,
|
||||
const std::string& db_session_id, const std::string& daily_offpeak_time_utc,
|
||||
ErrorHandler* const error_handler)
|
||||
: column_family_set_(new ColumnFamilySet(
|
||||
dbname, _db_options, storage_options, table_cache,
|
||||
write_buffer_manager, write_controller, block_cache_tracer, io_tracer,
|
||||
@@ -5076,7 +5087,9 @@ VersionSet::VersionSet(const std::string& dbname,
|
||||
file_options_(storage_options),
|
||||
block_cache_tracer_(block_cache_tracer),
|
||||
io_tracer_(io_tracer),
|
||||
db_session_id_(db_session_id) {}
|
||||
db_session_id_(db_session_id),
|
||||
offpeak_time_option_(OffpeakTimeOption(daily_offpeak_time_utc)),
|
||||
error_handler_(error_handler) {}
|
||||
|
||||
VersionSet::~VersionSet() {
|
||||
// we need to delete column_family_set_ because its destructor depends on
|
||||
@@ -5175,6 +5188,8 @@ Status VersionSet::ProcessManifestWrites(
|
||||
autovector<Version*> versions;
|
||||
autovector<const MutableCFOptions*> mutable_cf_options_ptrs;
|
||||
std::vector<std::unique_ptr<BaseReferencedVersionBuilder>> builder_guards;
|
||||
autovector<const autovector<uint64_t>*> files_to_quarantine_if_commit_fail;
|
||||
autovector<uint64_t> limbo_descriptor_log_file_number;
|
||||
|
||||
// Tracking `max_last_sequence` is needed to ensure we write
|
||||
// `VersionEdit::last_sequence_`s in non-decreasing order according to the
|
||||
@@ -5208,15 +5223,15 @@ Status VersionSet::ProcessManifestWrites(
|
||||
// don't update, then Recover can report corrupted atomic group because
|
||||
// the `remaining_entries_` do not match.
|
||||
if (!batch_edits.empty()) {
|
||||
if (batch_edits.back()->is_in_atomic_group_ &&
|
||||
batch_edits.back()->remaining_entries_ > 0) {
|
||||
if (batch_edits.back()->IsInAtomicGroup() &&
|
||||
batch_edits.back()->GetRemainingEntries() > 0) {
|
||||
assert(group_start < batch_edits.size());
|
||||
const auto& edit_list = last_writer->edit_list;
|
||||
size_t k = 0;
|
||||
while (k < edit_list.size()) {
|
||||
if (!edit_list[k]->is_in_atomic_group_) {
|
||||
if (!edit_list[k]->IsInAtomicGroup()) {
|
||||
break;
|
||||
} else if (edit_list[k]->remaining_entries_ == 0) {
|
||||
} else if (edit_list[k]->GetRemainingEntries() == 0) {
|
||||
++k;
|
||||
break;
|
||||
}
|
||||
@@ -5224,8 +5239,10 @@ Status VersionSet::ProcessManifestWrites(
|
||||
}
|
||||
for (auto i = group_start; i < batch_edits.size(); ++i) {
|
||||
assert(static_cast<uint32_t>(k) <=
|
||||
batch_edits.back()->remaining_entries_);
|
||||
batch_edits[i]->remaining_entries_ -= static_cast<uint32_t>(k);
|
||||
batch_edits.back()->GetRemainingEntries());
|
||||
batch_edits[i]->SetRemainingEntries(
|
||||
batch_edits[i]->GetRemainingEntries() -
|
||||
static_cast<uint32_t>(k));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5268,10 +5285,10 @@ Status VersionSet::ProcessManifestWrites(
|
||||
assert(ucmp);
|
||||
std::optional<size_t> edit_ts_sz = ucmp->timestamp_size();
|
||||
for (const auto& e : last_writer->edit_list) {
|
||||
if (e->is_in_atomic_group_) {
|
||||
if (batch_edits.empty() || !batch_edits.back()->is_in_atomic_group_ ||
|
||||
(batch_edits.back()->is_in_atomic_group_ &&
|
||||
batch_edits.back()->remaining_entries_ == 0)) {
|
||||
if (e->IsInAtomicGroup()) {
|
||||
if (batch_edits.empty() || !batch_edits.back()->IsInAtomicGroup() ||
|
||||
(batch_edits.back()->IsInAtomicGroup() &&
|
||||
batch_edits.back()->GetRemainingEntries() == 0)) {
|
||||
group_start = batch_edits.size();
|
||||
}
|
||||
} else if (group_start != std::numeric_limits<size_t>::max()) {
|
||||
@@ -5310,7 +5327,7 @@ Status VersionSet::ProcessManifestWrites(
|
||||
// remaining_entries_.
|
||||
size_t k = 0;
|
||||
while (k < batch_edits.size()) {
|
||||
while (k < batch_edits.size() && !batch_edits[k]->is_in_atomic_group_) {
|
||||
while (k < batch_edits.size() && !batch_edits[k]->IsInAtomicGroup()) {
|
||||
++k;
|
||||
}
|
||||
if (k == batch_edits.size()) {
|
||||
@@ -5318,19 +5335,19 @@ Status VersionSet::ProcessManifestWrites(
|
||||
}
|
||||
size_t i = k;
|
||||
while (i < batch_edits.size()) {
|
||||
if (!batch_edits[i]->is_in_atomic_group_) {
|
||||
if (!batch_edits[i]->IsInAtomicGroup()) {
|
||||
break;
|
||||
}
|
||||
assert(i - k + batch_edits[i]->remaining_entries_ ==
|
||||
batch_edits[k]->remaining_entries_);
|
||||
if (batch_edits[i]->remaining_entries_ == 0) {
|
||||
assert(i - k + batch_edits[i]->GetRemainingEntries() ==
|
||||
batch_edits[k]->GetRemainingEntries());
|
||||
if (batch_edits[i]->GetRemainingEntries() == 0) {
|
||||
++i;
|
||||
break;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
assert(batch_edits[i - 1]->is_in_atomic_group_);
|
||||
assert(0 == batch_edits[i - 1]->remaining_entries_);
|
||||
assert(batch_edits[i - 1]->IsInAtomicGroup());
|
||||
assert(0 == batch_edits[i - 1]->GetRemainingEntries());
|
||||
std::vector<VersionEdit*> tmp;
|
||||
for (size_t j = k; j != i; ++j) {
|
||||
tmp.emplace_back(batch_edits[j]);
|
||||
@@ -5456,6 +5473,8 @@ Status VersionSet::ProcessManifestWrites(
|
||||
assert(batch_edits.size() == batch_edits_ts_sz.size());
|
||||
for (size_t bidx = 0; bidx < batch_edits.size(); bidx++) {
|
||||
auto& e = batch_edits[bidx];
|
||||
files_to_quarantine_if_commit_fail.push_back(
|
||||
e->GetFilesToQuarantineIfCommitFail());
|
||||
std::string record;
|
||||
if (!e->EncodeTo(&record, batch_edits_ts_sz[bidx])) {
|
||||
s = Status::Corruption("Unable to encode VersionEdit:" +
|
||||
@@ -5505,6 +5524,11 @@ Status VersionSet::ProcessManifestWrites(
|
||||
dir_contains_current_file);
|
||||
if (!io_s.ok()) {
|
||||
s = io_s;
|
||||
// Quarantine old manifest file in case new manifest file's CURRENT file
|
||||
// wasn't created successfully and the old manifest is needed.
|
||||
limbo_descriptor_log_file_number.push_back(manifest_file_number_);
|
||||
files_to_quarantine_if_commit_fail.push_back(
|
||||
&limbo_descriptor_log_file_number);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5513,7 +5537,7 @@ Status VersionSet::ProcessManifestWrites(
|
||||
new_manifest_file_size = descriptor_log_->file()->GetFileSize();
|
||||
}
|
||||
|
||||
if (first_writer.edit_list.front()->is_column_family_drop_) {
|
||||
if (first_writer.edit_list.front()->IsColumnFamilyDrop()) {
|
||||
TEST_SYNC_POINT("VersionSet::LogAndApply::ColumnFamilyDrop:0");
|
||||
TEST_SYNC_POINT("VersionSet::LogAndApply::ColumnFamilyDrop:1");
|
||||
TEST_SYNC_POINT("VersionSet::LogAndApply::ColumnFamilyDrop:2");
|
||||
@@ -5541,9 +5565,16 @@ Status VersionSet::ProcessManifestWrites(
|
||||
if (!io_s.ok()) {
|
||||
if (io_status_.ok()) {
|
||||
io_status_ = io_s;
|
||||
if (error_handler_) {
|
||||
error_handler_->AddFilesToQuarantine(
|
||||
files_to_quarantine_if_commit_fail);
|
||||
}
|
||||
}
|
||||
} else if (!io_status_.ok()) {
|
||||
io_status_ = io_s;
|
||||
if (error_handler_) {
|
||||
error_handler_->ClearFilesToQuarantine();
|
||||
}
|
||||
}
|
||||
|
||||
// Append the old manifest file to the obsolete_manifest_ list to be deleted
|
||||
@@ -5555,13 +5586,13 @@ Status VersionSet::ProcessManifestWrites(
|
||||
|
||||
// Install the new versions
|
||||
if (s.ok()) {
|
||||
if (first_writer.edit_list.front()->is_column_family_add_) {
|
||||
if (first_writer.edit_list.front()->IsColumnFamilyAdd()) {
|
||||
assert(batch_edits.size() == 1);
|
||||
assert(new_cf_options != nullptr);
|
||||
assert(max_last_sequence == descriptor_last_sequence_);
|
||||
CreateColumnFamily(*new_cf_options, read_options,
|
||||
first_writer.edit_list.front());
|
||||
} else if (first_writer.edit_list.front()->is_column_family_drop_) {
|
||||
} else if (first_writer.edit_list.front()->IsColumnFamilyDrop()) {
|
||||
assert(batch_edits.size() == 1);
|
||||
assert(max_last_sequence == descriptor_last_sequence_);
|
||||
first_writer.cfd->SetDropped();
|
||||
@@ -5574,22 +5605,22 @@ Status VersionSet::ProcessManifestWrites(
|
||||
for (const auto& e : batch_edits) {
|
||||
ColumnFamilyData* cfd = nullptr;
|
||||
if (!e->IsColumnFamilyManipulation()) {
|
||||
cfd = column_family_set_->GetColumnFamily(e->column_family_);
|
||||
cfd = column_family_set_->GetColumnFamily(e->GetColumnFamily());
|
||||
// e would not have been added to batch_edits if its corresponding
|
||||
// column family is dropped.
|
||||
assert(cfd);
|
||||
}
|
||||
if (cfd) {
|
||||
if (e->has_log_number_ && e->log_number_ > cfd->GetLogNumber()) {
|
||||
cfd->SetLogNumber(e->log_number_);
|
||||
if (e->HasLogNumber() && e->GetLogNumber() > cfd->GetLogNumber()) {
|
||||
cfd->SetLogNumber(e->GetLogNumber());
|
||||
}
|
||||
if (e->HasFullHistoryTsLow()) {
|
||||
cfd->SetFullHistoryTsLow(e->GetFullHistoryTsLow());
|
||||
}
|
||||
}
|
||||
if (e->has_min_log_number_to_keep_) {
|
||||
if (e->HasMinLogNumberToKeep()) {
|
||||
last_min_log_number_to_keep =
|
||||
std::max(last_min_log_number_to_keep, e->min_log_number_to_keep_);
|
||||
std::max(last_min_log_number_to_keep, e->GetMinLogNumberToKeep());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5606,7 +5637,7 @@ Status VersionSet::ProcessManifestWrites(
|
||||
descriptor_last_sequence_ = max_last_sequence;
|
||||
manifest_file_number_ = pending_manifest_file_number_;
|
||||
manifest_file_size_ = new_manifest_file_size;
|
||||
prev_log_number_ = first_writer.edit_list.front()->prev_log_number_;
|
||||
prev_log_number_ = first_writer.edit_list.front()->GetPrevLogNumber();
|
||||
} else {
|
||||
std::string version_edits;
|
||||
for (auto& e : batch_edits) {
|
||||
@@ -5754,7 +5785,7 @@ Status VersionSet::LogAndApply(
|
||||
int num_cfds = static_cast<int>(column_family_datas.size());
|
||||
if (num_cfds == 1 && column_family_datas[0] == nullptr) {
|
||||
assert(edit_lists.size() == 1 && edit_lists[0].size() == 1);
|
||||
assert(edit_lists[0][0]->is_column_family_add_);
|
||||
assert(edit_lists[0][0]->IsColumnFamilyAdd());
|
||||
assert(new_cf_options != nullptr);
|
||||
}
|
||||
std::deque<ManifestWriter> writers;
|
||||
@@ -5818,7 +5849,7 @@ void VersionSet::LogAndApplyCFHelper(VersionEdit* edit,
|
||||
edit->SetNextFile(next_file_number_.load());
|
||||
assert(!edit->HasLastSequence());
|
||||
edit->SetLastSequence(*max_last_sequence);
|
||||
if (edit->is_column_family_drop_) {
|
||||
if (edit->IsColumnFamilyDrop()) {
|
||||
// if we drop column family, we have to make sure to save max column family,
|
||||
// so that we don't reuse existing ID
|
||||
edit->SetMaxColumnFamily(column_family_set_->GetMaxColumnFamily());
|
||||
@@ -5836,12 +5867,12 @@ Status VersionSet::LogAndApplyHelper(ColumnFamilyData* cfd,
|
||||
assert(!edit->IsColumnFamilyManipulation());
|
||||
assert(max_last_sequence != nullptr);
|
||||
|
||||
if (edit->has_log_number_) {
|
||||
assert(edit->log_number_ >= cfd->GetLogNumber());
|
||||
assert(edit->log_number_ < next_file_number_.load());
|
||||
if (edit->HasLogNumber()) {
|
||||
assert(edit->GetLogNumber() >= cfd->GetLogNumber());
|
||||
assert(edit->GetLogNumber() < next_file_number_.load());
|
||||
}
|
||||
|
||||
if (!edit->has_prev_log_number_) {
|
||||
if (!edit->HasPrevLogNumber()) {
|
||||
edit->SetPrevLogNumber(prev_log_number_);
|
||||
}
|
||||
edit->SetNextFile(next_file_number_.load());
|
||||
@@ -5933,7 +5964,7 @@ Status VersionSet::Recover(
|
||||
handler.Iterate(reader, &log_read_status);
|
||||
s = handler.status();
|
||||
if (s.ok()) {
|
||||
log_number = handler.GetVersionEditParams().log_number_;
|
||||
log_number = handler.GetVersionEditParams().GetLogNumber();
|
||||
current_manifest_file_size = reader.GetReadOffset();
|
||||
assert(current_manifest_file_size != 0);
|
||||
handler.GetDbId(db_id);
|
||||
@@ -6201,7 +6232,8 @@ Status VersionSet::ReduceNumberOfLevels(const std::string& dbname,
|
||||
VersionSet versions(dbname, &db_options, file_options, tc.get(), &wb, &wc,
|
||||
nullptr /*BlockCacheTracer*/, nullptr /*IOTracer*/,
|
||||
/*db_id*/ "",
|
||||
/*db_session_id*/ "");
|
||||
/*db_session_id*/ "", options->daily_offpeak_time_utc,
|
||||
/*error_handler_*/ nullptr);
|
||||
Status status;
|
||||
|
||||
std::vector<ColumnFamilyDescriptor> dummy;
|
||||
@@ -7104,7 +7136,7 @@ uint64_t VersionSet::GetObsoleteSstFilesSize() const {
|
||||
ColumnFamilyData* VersionSet::CreateColumnFamily(
|
||||
const ColumnFamilyOptions& cf_options, const ReadOptions& read_options,
|
||||
const VersionEdit* edit) {
|
||||
assert(edit->is_column_family_add_);
|
||||
assert(edit->IsColumnFamilyAdd());
|
||||
|
||||
MutableCFOptions dummy_cf_options;
|
||||
Version* dummy_versions =
|
||||
@@ -7113,7 +7145,7 @@ ColumnFamilyData* VersionSet::CreateColumnFamily(
|
||||
// by avoiding calling "delete" explicitly (~Version is private)
|
||||
dummy_versions->Ref();
|
||||
auto new_cfd = column_family_set_->CreateColumnFamily(
|
||||
edit->column_family_name_, edit->column_family_, dummy_versions,
|
||||
edit->GetColumnFamilyName(), edit->GetColumnFamily(), dummy_versions,
|
||||
cf_options);
|
||||
|
||||
Version* v = new Version(new_cfd, this, file_options_,
|
||||
@@ -7130,7 +7162,7 @@ ColumnFamilyData* VersionSet::CreateColumnFamily(
|
||||
// cfd is not available to client
|
||||
new_cfd->CreateNewMemtable(*new_cfd->GetLatestMutableCFOptions(),
|
||||
LastSequence());
|
||||
new_cfd->SetLogNumber(edit->log_number_);
|
||||
new_cfd->SetLogNumber(edit->GetLogNumber());
|
||||
return new_cfd;
|
||||
}
|
||||
|
||||
@@ -7242,7 +7274,8 @@ ReactiveVersionSet::ReactiveVersionSet(
|
||||
: VersionSet(dbname, _db_options, _file_options, table_cache,
|
||||
write_buffer_manager, write_controller,
|
||||
/*block_cache_tracer=*/nullptr, io_tracer, /*db_id*/ "",
|
||||
/*db_session_id*/ "") {}
|
||||
/*db_session_id*/ "", /*daily_offpeak_time_utc*/ "",
|
||||
/*error_handler=*/nullptr) {}
|
||||
|
||||
ReactiveVersionSet::~ReactiveVersionSet() {}
|
||||
|
||||
|
||||
+25
-3
@@ -38,6 +38,7 @@
|
||||
#include "db/compaction/compaction.h"
|
||||
#include "db/compaction/compaction_picker.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/error_handler.h"
|
||||
#include "db/file_indexer.h"
|
||||
#include "db/log_reader.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
@@ -53,6 +54,7 @@
|
||||
#endif
|
||||
#include "monitoring/instrumented_mutex.h"
|
||||
#include "options/db_options.h"
|
||||
#include "options/offpeak_time_info.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/file_checksum.h"
|
||||
@@ -134,7 +136,8 @@ class VersionStorageInfo {
|
||||
bool _force_consistency_checks,
|
||||
EpochNumberRequirement epoch_number_requirement,
|
||||
SystemClock* clock,
|
||||
uint32_t bottommost_file_compaction_delay);
|
||||
uint32_t bottommost_file_compaction_delay,
|
||||
OffpeakTimeOption offpeak_time_option);
|
||||
// No copying allowed
|
||||
VersionStorageInfo(const VersionStorageInfo&) = delete;
|
||||
void operator=(const VersionStorageInfo&) = delete;
|
||||
@@ -751,7 +754,8 @@ class VersionStorageInfo {
|
||||
// target sizes.
|
||||
uint64_t estimated_compaction_needed_bytes_;
|
||||
|
||||
// Used for computing bottommost files marked for compaction.
|
||||
// Used for computing bottommost files marked for compaction and checking for
|
||||
// offpeak time.
|
||||
SystemClock* clock_;
|
||||
uint32_t bottommost_file_compaction_delay_;
|
||||
|
||||
@@ -763,6 +767,8 @@ class VersionStorageInfo {
|
||||
|
||||
EpochNumberRequirement epoch_number_requirement_;
|
||||
|
||||
OffpeakTimeOption offpeak_time_option_;
|
||||
|
||||
friend class Version;
|
||||
friend class VersionSet;
|
||||
};
|
||||
@@ -1146,7 +1152,9 @@ class VersionSet {
|
||||
WriteController* write_controller,
|
||||
BlockCacheTracer* const block_cache_tracer,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::string& db_id, const std::string& db_session_id);
|
||||
const std::string& db_id, const std::string& db_session_id,
|
||||
const std::string& daily_offpeak_time_utc,
|
||||
ErrorHandler* const error_handler);
|
||||
// No copying allowed
|
||||
VersionSet(const VersionSet&) = delete;
|
||||
void operator=(const VersionSet&) = delete;
|
||||
@@ -1501,6 +1509,14 @@ class VersionSet {
|
||||
new_options.writable_file_max_buffer_size;
|
||||
}
|
||||
|
||||
// TODO - Consider updating together when file options change in SetDBOptions
|
||||
const OffpeakTimeOption& offpeak_time_option() {
|
||||
return offpeak_time_option_;
|
||||
}
|
||||
void ChangeOffpeakTimeOption(const std::string& daily_offpeak_time_utc) {
|
||||
offpeak_time_option_.SetFromOffpeakTimeString(daily_offpeak_time_utc);
|
||||
}
|
||||
|
||||
const ImmutableDBOptions* db_options() const { return db_options_; }
|
||||
|
||||
static uint64_t GetNumLiveVersions(Version* dummy_versions);
|
||||
@@ -1651,6 +1667,12 @@ class VersionSet {
|
||||
|
||||
std::string db_session_id_;
|
||||
|
||||
// Off-peak time option used for compaction scoring
|
||||
OffpeakTimeOption offpeak_time_option_;
|
||||
|
||||
// Pointer to the DB's ErrorHandler.
|
||||
ErrorHandler* const error_handler_;
|
||||
|
||||
private:
|
||||
// REQUIRES db mutex at beginning. may release and re-acquire db mutex
|
||||
Status ProcessManifestWrites(std::deque<ManifestWriter>& writers,
|
||||
|
||||
+156
-51
@@ -21,6 +21,7 @@
|
||||
#include "table/block_based/block_based_table_factory.h"
|
||||
#include "table/mock_table.h"
|
||||
#include "table/unique_id_impl.h"
|
||||
#include "test_util/mock_time_env.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/string_util.h"
|
||||
@@ -132,7 +133,8 @@ class VersionStorageInfoTestBase : public testing::Test {
|
||||
/*src_vstorage=*/nullptr,
|
||||
/*_force_consistency_checks=*/false,
|
||||
EpochNumberRequirement::kMustPresent, ioptions_.clock,
|
||||
mutable_cf_options_.bottommost_file_compaction_delay) {}
|
||||
mutable_cf_options_.bottommost_file_compaction_delay,
|
||||
OffpeakTimeOption()) {}
|
||||
|
||||
~VersionStorageInfoTestBase() override {
|
||||
for (int i = 0; i < vstorage_.num_levels(); ++i) {
|
||||
@@ -1199,11 +1201,12 @@ class VersionSetTestBase {
|
||||
immutable_options_.fs = fs_;
|
||||
immutable_options_.clock = env_->GetSystemClock().get();
|
||||
|
||||
versions_.reset(
|
||||
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id*/ "", /*db_session_id*/ ""));
|
||||
versions_.reset(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr));
|
||||
reactive_versions_ = std::make_shared<ReactiveVersionSet>(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_, nullptr);
|
||||
@@ -1303,11 +1306,12 @@ class VersionSetTestBase {
|
||||
}
|
||||
|
||||
void ReopenDB() {
|
||||
versions_.reset(
|
||||
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id*/ "", /*db_session_id*/ ""));
|
||||
versions_.reset(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr));
|
||||
EXPECT_OK(versions_->Recover(column_families_, false));
|
||||
}
|
||||
|
||||
@@ -1815,11 +1819,12 @@ TEST_F(VersionSetTest, WalAddition) {
|
||||
|
||||
// Recover a new VersionSet.
|
||||
{
|
||||
std::unique_ptr<VersionSet> new_versions(
|
||||
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id*/ "", /*db_session_id*/ ""));
|
||||
std::unique_ptr<VersionSet> new_versions(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr));
|
||||
ASSERT_OK(new_versions->Recover(column_families_, /*read_only=*/false));
|
||||
const auto& wals = new_versions->GetWalSet().GetWals();
|
||||
ASSERT_EQ(wals.size(), 1);
|
||||
@@ -1882,11 +1887,12 @@ TEST_F(VersionSetTest, WalCloseWithoutSync) {
|
||||
|
||||
// Recover a new VersionSet.
|
||||
{
|
||||
std::unique_ptr<VersionSet> new_versions(
|
||||
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id*/ "", /*db_session_id*/ ""));
|
||||
std::unique_ptr<VersionSet> new_versions(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr));
|
||||
ASSERT_OK(new_versions->Recover(column_families_, false));
|
||||
const auto& wals = new_versions->GetWalSet().GetWals();
|
||||
ASSERT_EQ(wals.size(), 2);
|
||||
@@ -1935,11 +1941,12 @@ TEST_F(VersionSetTest, WalDeletion) {
|
||||
|
||||
// Recover a new VersionSet, only the non-closed WAL should show up.
|
||||
{
|
||||
std::unique_ptr<VersionSet> new_versions(
|
||||
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id*/ "", /*db_session_id*/ ""));
|
||||
std::unique_ptr<VersionSet> new_versions(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr));
|
||||
ASSERT_OK(new_versions->Recover(column_families_, false));
|
||||
const auto& wals = new_versions->GetWalSet().GetWals();
|
||||
ASSERT_EQ(wals.size(), 1);
|
||||
@@ -1973,11 +1980,12 @@ TEST_F(VersionSetTest, WalDeletion) {
|
||||
|
||||
// Recover from the new MANIFEST, only the non-closed WAL should show up.
|
||||
{
|
||||
std::unique_ptr<VersionSet> new_versions(
|
||||
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id*/ "", /*db_session_id*/ ""));
|
||||
std::unique_ptr<VersionSet> new_versions(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr));
|
||||
ASSERT_OK(new_versions->Recover(column_families_, false));
|
||||
const auto& wals = new_versions->GetWalSet().GetWals();
|
||||
ASSERT_EQ(wals.size(), 1);
|
||||
@@ -2093,11 +2101,12 @@ TEST_F(VersionSetTest, DeleteWalsBeforeNonExistingWalNumber) {
|
||||
|
||||
// Recover a new VersionSet, WAL0 is deleted, WAL1 is not.
|
||||
{
|
||||
std::unique_ptr<VersionSet> new_versions(
|
||||
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id*/ "", /*db_session_id*/ ""));
|
||||
std::unique_ptr<VersionSet> new_versions(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr));
|
||||
ASSERT_OK(new_versions->Recover(column_families_, false));
|
||||
const auto& wals = new_versions->GetWalSet().GetWals();
|
||||
ASSERT_EQ(wals.size(), 1);
|
||||
@@ -2129,11 +2138,12 @@ TEST_F(VersionSetTest, DeleteAllWals) {
|
||||
|
||||
// Recover a new VersionSet, all WALs are deleted.
|
||||
{
|
||||
std::unique_ptr<VersionSet> new_versions(
|
||||
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id*/ "", /*db_session_id*/ ""));
|
||||
std::unique_ptr<VersionSet> new_versions(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr));
|
||||
ASSERT_OK(new_versions->Recover(column_families_, false));
|
||||
const auto& wals = new_versions->GetWalSet().GetWals();
|
||||
ASSERT_EQ(wals.size(), 0);
|
||||
@@ -2171,11 +2181,12 @@ TEST_F(VersionSetTest, AtomicGroupWithWalEdits) {
|
||||
// Recover a new VersionSet, the min log number and the last WAL should be
|
||||
// kept.
|
||||
{
|
||||
std::unique_ptr<VersionSet> new_versions(
|
||||
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id*/ "", /*db_session_id*/ ""));
|
||||
std::unique_ptr<VersionSet> new_versions(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr));
|
||||
std::string db_id;
|
||||
ASSERT_OK(
|
||||
new_versions->Recover(column_families_, /*read_only=*/false, &db_id));
|
||||
@@ -2190,6 +2201,99 @@ TEST_F(VersionSetTest, AtomicGroupWithWalEdits) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(VersionSetTest, OffpeakTimeInfoTest) {
|
||||
Random rnd(test::RandomSeed());
|
||||
|
||||
// Sets off-peak time from 11:30PM to 4:30AM next day.
|
||||
// Starting at 1:30PM, use mock sleep to make time pass
|
||||
// and see if IsNowOffpeak() returns correctly per time changes
|
||||
int now_hour = 13;
|
||||
int now_minute = 30;
|
||||
versions_->ChangeOffpeakTimeOption("23:30-04:30");
|
||||
|
||||
auto mock_clock = std::make_shared<MockSystemClock>(env_->GetSystemClock());
|
||||
// Add some extra random days to current time
|
||||
int days = rnd.Uniform(100);
|
||||
mock_clock->SetCurrentTime(days * 86400 + now_hour * 3600 + now_minute * 60);
|
||||
int64_t now;
|
||||
ASSERT_OK(mock_clock.get()->GetCurrentTime(&now));
|
||||
|
||||
// Starting at 1:30PM. It's not off-peak
|
||||
ASSERT_FALSE(
|
||||
versions_->offpeak_time_option().GetOffpeakTimeInfo(now).is_now_offpeak);
|
||||
|
||||
// Now it's at 4:30PM. Still not off-peak
|
||||
mock_clock->MockSleepForSeconds(3 * 3600);
|
||||
ASSERT_OK(mock_clock.get()->GetCurrentTime(&now));
|
||||
ASSERT_FALSE(
|
||||
versions_->offpeak_time_option().GetOffpeakTimeInfo(now).is_now_offpeak);
|
||||
|
||||
// Now it's at 11:30PM. It's off-peak
|
||||
mock_clock->MockSleepForSeconds(7 * 3600);
|
||||
ASSERT_OK(mock_clock.get()->GetCurrentTime(&now));
|
||||
ASSERT_TRUE(
|
||||
versions_->offpeak_time_option().GetOffpeakTimeInfo(now).is_now_offpeak);
|
||||
|
||||
// Now it's at 2:30AM next day. It's still off-peak
|
||||
mock_clock->MockSleepForSeconds(3 * 3600);
|
||||
ASSERT_OK(mock_clock.get()->GetCurrentTime(&now));
|
||||
ASSERT_TRUE(
|
||||
versions_->offpeak_time_option().GetOffpeakTimeInfo(now).is_now_offpeak);
|
||||
|
||||
// Now it's at 4:30AM. It's still off-peak
|
||||
mock_clock->MockSleepForSeconds(2 * 3600);
|
||||
ASSERT_OK(mock_clock.get()->GetCurrentTime(&now));
|
||||
ASSERT_TRUE(
|
||||
versions_->offpeak_time_option().GetOffpeakTimeInfo(now).is_now_offpeak);
|
||||
|
||||
// Sleep for one more minute. It's at 4:31AM It's no longer off-peak
|
||||
mock_clock->MockSleepForSeconds(60);
|
||||
ASSERT_OK(mock_clock.get()->GetCurrentTime(&now));
|
||||
ASSERT_FALSE(
|
||||
versions_->offpeak_time_option().GetOffpeakTimeInfo(now).is_now_offpeak);
|
||||
|
||||
// Entire day offpeak
|
||||
versions_->ChangeOffpeakTimeOption("00:00-23:59");
|
||||
// It doesn't matter what time it is. It should be just offpeak.
|
||||
ASSERT_TRUE(
|
||||
versions_->offpeak_time_option().GetOffpeakTimeInfo(now).is_now_offpeak);
|
||||
|
||||
// Mock Sleep for 3 hours. It's still off-peak
|
||||
mock_clock->MockSleepForSeconds(3 * 3600);
|
||||
ASSERT_OK(mock_clock.get()->GetCurrentTime(&now));
|
||||
ASSERT_TRUE(
|
||||
versions_->offpeak_time_option().GetOffpeakTimeInfo(now).is_now_offpeak);
|
||||
|
||||
// Mock Sleep for 20 hours. It's still off-peak
|
||||
mock_clock->MockSleepForSeconds(20 * 3600);
|
||||
ASSERT_OK(mock_clock.get()->GetCurrentTime(&now));
|
||||
ASSERT_TRUE(
|
||||
versions_->offpeak_time_option().GetOffpeakTimeInfo(now).is_now_offpeak);
|
||||
|
||||
// Mock Sleep for 59 minutes. It's still off-peak
|
||||
mock_clock->MockSleepForSeconds(59 * 60);
|
||||
ASSERT_OK(mock_clock.get()->GetCurrentTime(&now));
|
||||
ASSERT_TRUE(
|
||||
versions_->offpeak_time_option().GetOffpeakTimeInfo(now).is_now_offpeak);
|
||||
|
||||
// Mock Sleep for 59 seconds. It's still off-peak
|
||||
mock_clock->MockSleepForSeconds(59);
|
||||
ASSERT_OK(mock_clock.get()->GetCurrentTime(&now));
|
||||
ASSERT_TRUE(
|
||||
versions_->offpeak_time_option().GetOffpeakTimeInfo(now).is_now_offpeak);
|
||||
|
||||
// Mock Sleep for 1 second (exactly 24h passed). It's still off-peak
|
||||
mock_clock->MockSleepForSeconds(1);
|
||||
ASSERT_OK(mock_clock.get()->GetCurrentTime(&now));
|
||||
ASSERT_TRUE(
|
||||
versions_->offpeak_time_option().GetOffpeakTimeInfo(now).is_now_offpeak);
|
||||
// Another second for sanity check
|
||||
mock_clock->MockSleepForSeconds(1);
|
||||
ASSERT_OK(mock_clock.get()->GetCurrentTime(&now));
|
||||
ASSERT_TRUE(
|
||||
versions_->offpeak_time_option().GetOffpeakTimeInfo(now).is_now_offpeak);
|
||||
}
|
||||
|
||||
TEST_F(VersionStorageInfoTest, AddRangeDeletionCompensatedFileSize) {
|
||||
// Tests that compensated range deletion size is added to compensated file
|
||||
// size.
|
||||
@@ -2236,11 +2340,12 @@ class VersionSetWithTimestampTest : public VersionSetTest {
|
||||
}
|
||||
|
||||
void VerifyFullHistoryTsLow(uint64_t expected_ts_low) {
|
||||
std::unique_ptr<VersionSet> vset(
|
||||
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id*/ "", /*db_session_id*/ ""));
|
||||
std::unique_ptr<VersionSet> vset(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr));
|
||||
ASSERT_OK(vset->Recover(column_families_, /*read_only=*/false,
|
||||
/*db_id=*/nullptr));
|
||||
for (auto* cfd : *(vset->GetColumnFamilySet())) {
|
||||
|
||||
+3
-1
@@ -25,7 +25,9 @@ class OfflineManifestWriter {
|
||||
options.table_cache_numshardbits)),
|
||||
versions_(db_path, &immutable_db_options_, sopt_, tc_.get(), &wb_, &wc_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id*/ "", /*db_session_id*/ "") {}
|
||||
/*db_id=*/"", /*db_session_id=*/"",
|
||||
options.daily_offpeak_time_utc,
|
||||
/*error_handler=*/nullptr) {}
|
||||
|
||||
Status Recover(const std::vector<ColumnFamilyDescriptor>& column_families) {
|
||||
return versions_.Recover(column_families, /*read_only*/ false,
|
||||
|
||||
+5
-4
@@ -153,10 +153,11 @@ void WalManager::PurgeObsoleteWALFiles() {
|
||||
return;
|
||||
}
|
||||
uint64_t const now_seconds = static_cast<uint64_t>(current_time);
|
||||
uint64_t const time_to_check = (ttl_enabled && !size_limit_enabled)
|
||||
? db_options_.WAL_ttl_seconds / 2
|
||||
: kDefaultIntervalToDeleteObsoleteWAL;
|
||||
|
||||
uint64_t const time_to_check =
|
||||
ttl_enabled
|
||||
? std::min(kDefaultIntervalToDeleteObsoleteWAL,
|
||||
std::max(uint64_t{1}, db_options_.WAL_ttl_seconds / 2))
|
||||
: kDefaultIntervalToDeleteObsoleteWAL;
|
||||
if (purge_wal_files_last_run_ + time_to_check > now_seconds) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -50,11 +50,12 @@ class WalManagerTest : public testing::Test {
|
||||
db_options_.fs = env_->GetFileSystem();
|
||||
db_options_.clock = env_->GetSystemClock().get();
|
||||
|
||||
versions_.reset(
|
||||
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id*/ "", /*db_session_id*/ ""));
|
||||
versions_.reset(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr));
|
||||
|
||||
wal_manager_.reset(
|
||||
new WalManager(db_options_, env_options_, nullptr /*IOTracer*/));
|
||||
@@ -333,4 +334,3 @@ int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
+183
-17
@@ -236,6 +236,144 @@ TEST_F(DBWideBasicTest, PutEntityColumnFamily) {
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &batch));
|
||||
}
|
||||
|
||||
TEST_F(DBWideBasicTest, GetEntityAsPinnableAttributeGroups) {
|
||||
Options options = GetDefaultOptions();
|
||||
CreateAndReopenWithCF({"hot_cf", "cold_cf"}, options);
|
||||
|
||||
constexpr int kDefaultCfHandleIndex = 0;
|
||||
constexpr int kHotCfHandleIndex = 1;
|
||||
constexpr int kColdCfHandleIndex = 2;
|
||||
|
||||
constexpr char first_key[] = "first";
|
||||
WideColumns first_default_columns{
|
||||
{"default_cf_col_1_name", "first_key_default_cf_col_1_value"},
|
||||
{"default_cf_col_2_name", "first_key_default_cf_col_2_value"}};
|
||||
WideColumns first_hot_columns{
|
||||
{"hot_cf_col_1_name", "first_key_hot_cf_col_1_value"},
|
||||
{"hot_cf_col_2_name", "first_key_hot_cf_col_2_value"}};
|
||||
WideColumns first_cold_columns{
|
||||
{"cold_cf_col_1_name", "first_key_cold_cf_col_1_value"}};
|
||||
|
||||
constexpr char second_key[] = "second";
|
||||
WideColumns second_hot_columns{
|
||||
{"hot_cf_col_1_name", "second_key_hot_cf_col_1_value"}};
|
||||
WideColumns second_cold_columns{
|
||||
{"cold_cf_col_1_name", "second_key_cold_cf_col_1_value"}};
|
||||
|
||||
AttributeGroups first_key_attribute_groups{
|
||||
AttributeGroup(handles_[kDefaultCfHandleIndex], first_default_columns),
|
||||
AttributeGroup(handles_[kHotCfHandleIndex], first_hot_columns),
|
||||
AttributeGroup(handles_[kColdCfHandleIndex], first_cold_columns)};
|
||||
AttributeGroups second_key_attribute_groups{
|
||||
AttributeGroup(handles_[kHotCfHandleIndex], second_hot_columns),
|
||||
AttributeGroup(handles_[kColdCfHandleIndex], second_cold_columns)};
|
||||
ASSERT_OK(
|
||||
db_->PutEntity(WriteOptions(), first_key, first_key_attribute_groups));
|
||||
ASSERT_OK(
|
||||
db_->PutEntity(WriteOptions(), second_key, second_key_attribute_groups));
|
||||
|
||||
std::vector<ColumnFamilyHandle*> all_cfs = handles_;
|
||||
std::vector<ColumnFamilyHandle*> default_and_hot_cfs{
|
||||
{handles_[kDefaultCfHandleIndex], handles_[kHotCfHandleIndex]}};
|
||||
std::vector<ColumnFamilyHandle*> hot_and_cold_cfs{
|
||||
{handles_[kHotCfHandleIndex], handles_[kColdCfHandleIndex]}};
|
||||
std::vector<ColumnFamilyHandle*> default_null_and_hot_cfs{
|
||||
handles_[kDefaultCfHandleIndex], nullptr, handles_[kHotCfHandleIndex],
|
||||
nullptr};
|
||||
auto create_result =
|
||||
[](const std::vector<ColumnFamilyHandle*>& column_families)
|
||||
-> PinnableAttributeGroups {
|
||||
PinnableAttributeGroups result;
|
||||
for (size_t i = 0; i < column_families.size(); ++i) {
|
||||
result.emplace_back(column_families[i]);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
{
|
||||
// Case 1. Invalid Argument (passing in null CF)
|
||||
AttributeGroups ag{
|
||||
AttributeGroup(nullptr, first_default_columns),
|
||||
AttributeGroup(handles_[kHotCfHandleIndex], first_hot_columns)};
|
||||
ASSERT_NOK(db_->PutEntity(WriteOptions(), first_key, ag));
|
||||
|
||||
PinnableAttributeGroups result = create_result(default_null_and_hot_cfs);
|
||||
Status s = db_->GetEntity(ReadOptions(), first_key, &result);
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_TRUE(s.IsInvalidArgument());
|
||||
// Valid CF, but failed with Incomplete status due to other attribute groups
|
||||
ASSERT_TRUE(result[0].status().IsIncomplete());
|
||||
// Null CF
|
||||
ASSERT_TRUE(result[1].status().IsInvalidArgument());
|
||||
// Valid CF, but failed with Incomplete status due to other attribute groups
|
||||
ASSERT_TRUE(result[2].status().IsIncomplete());
|
||||
// Null CF, but failed with Incomplete status because the nullcheck break
|
||||
// out early in the loop
|
||||
ASSERT_TRUE(result[3].status().IsIncomplete());
|
||||
}
|
||||
{
|
||||
// Case 2. Get first key from default cf and hot_cf and second key from
|
||||
// hot_cf and cold_cf
|
||||
constexpr size_t num_column_families = 2;
|
||||
PinnableAttributeGroups first_key_result =
|
||||
create_result(default_and_hot_cfs);
|
||||
PinnableAttributeGroups second_key_result = create_result(hot_and_cold_cfs);
|
||||
|
||||
// GetEntity for first_key
|
||||
ASSERT_OK(db_->GetEntity(ReadOptions(), first_key, &first_key_result));
|
||||
ASSERT_EQ(num_column_families, first_key_result.size());
|
||||
// We expect to get values for all keys and CFs
|
||||
for (size_t i = 0; i < num_column_families; ++i) {
|
||||
ASSERT_OK(first_key_result[i].status());
|
||||
}
|
||||
// verify values for first key (default cf and hot cf)
|
||||
ASSERT_EQ(first_default_columns, first_key_result[0].columns());
|
||||
ASSERT_EQ(first_hot_columns, first_key_result[1].columns());
|
||||
|
||||
// GetEntity for second_key
|
||||
ASSERT_OK(db_->GetEntity(ReadOptions(), second_key, &second_key_result));
|
||||
ASSERT_EQ(num_column_families, second_key_result.size());
|
||||
// We expect to get values for all keys and CFs
|
||||
for (size_t i = 0; i < num_column_families; ++i) {
|
||||
ASSERT_OK(second_key_result[i].status());
|
||||
}
|
||||
// verify values for second key (hot cf and cold cf)
|
||||
ASSERT_EQ(second_hot_columns, second_key_result[0].columns());
|
||||
ASSERT_EQ(second_cold_columns, second_key_result[1].columns());
|
||||
}
|
||||
{
|
||||
// Case 3. Get first key and second key from all cfs. For the second key, we
|
||||
// don't expect to get columns from default cf.
|
||||
constexpr size_t num_column_families = 3;
|
||||
PinnableAttributeGroups first_key_result = create_result(all_cfs);
|
||||
PinnableAttributeGroups second_key_result = create_result(all_cfs);
|
||||
|
||||
// GetEntity for first_key
|
||||
ASSERT_OK(db_->GetEntity(ReadOptions(), first_key, &first_key_result));
|
||||
ASSERT_EQ(num_column_families, first_key_result.size());
|
||||
// We expect to get values for all keys and CFs
|
||||
for (size_t i = 0; i < num_column_families; ++i) {
|
||||
ASSERT_OK(first_key_result[i].status());
|
||||
}
|
||||
// verify values for first key
|
||||
ASSERT_EQ(first_default_columns, first_key_result[0].columns());
|
||||
ASSERT_EQ(first_hot_columns, first_key_result[1].columns());
|
||||
ASSERT_EQ(first_cold_columns, first_key_result[2].columns());
|
||||
|
||||
// GetEntity for second_key
|
||||
ASSERT_OK(db_->GetEntity(ReadOptions(), second_key, &second_key_result));
|
||||
ASSERT_EQ(num_column_families, second_key_result.size());
|
||||
// key does not exist in default cf
|
||||
ASSERT_NOK(second_key_result[0].status());
|
||||
ASSERT_TRUE(second_key_result[0].status().IsNotFound());
|
||||
|
||||
// verify values for second key (hot cf and cold cf)
|
||||
ASSERT_OK(second_key_result[1].status());
|
||||
ASSERT_OK(second_key_result[2].status());
|
||||
ASSERT_EQ(second_hot_columns, second_key_result[1].columns());
|
||||
ASSERT_EQ(second_cold_columns, second_key_result[2].columns());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBWideBasicTest, MultiCFMultiGetEntity) {
|
||||
Options options = GetDefaultOptions();
|
||||
CreateAndReopenWithCF({"corinthian"}, options);
|
||||
@@ -274,9 +412,9 @@ TEST_F(DBWideBasicTest, MultiCFMultiGetEntityAsPinnableAttributeGroups) {
|
||||
Options options = GetDefaultOptions();
|
||||
CreateAndReopenWithCF({"hot_cf", "cold_cf"}, options);
|
||||
|
||||
constexpr int DEFAULT_CF_HANDLE_INDEX = 0;
|
||||
constexpr int HOT_CF_HANDLE_INDEX = 1;
|
||||
constexpr int COLD_CF_HANDLE_INDEX = 2;
|
||||
constexpr int kDefaultCfHandleIndex = 0;
|
||||
constexpr int kHotCfHandleIndex = 1;
|
||||
constexpr int kColdCfHandleIndex = 2;
|
||||
|
||||
constexpr char first_key[] = "first";
|
||||
WideColumns first_default_columns{
|
||||
@@ -293,25 +431,28 @@ TEST_F(DBWideBasicTest, MultiCFMultiGetEntityAsPinnableAttributeGroups) {
|
||||
WideColumns second_cold_columns{
|
||||
{"cold_cf_col_1_name", "second_key_cold_cf_col_1_value"}};
|
||||
|
||||
// TODO - update this to use the multi-attribute-group PutEntity when ready
|
||||
ASSERT_OK(db_->PutEntity(WriteOptions(), handles_[DEFAULT_CF_HANDLE_INDEX],
|
||||
first_key, first_default_columns));
|
||||
ASSERT_OK(db_->PutEntity(WriteOptions(), handles_[HOT_CF_HANDLE_INDEX],
|
||||
first_key, first_hot_columns));
|
||||
ASSERT_OK(db_->PutEntity(WriteOptions(), handles_[COLD_CF_HANDLE_INDEX],
|
||||
first_key, first_cold_columns));
|
||||
ASSERT_OK(db_->PutEntity(WriteOptions(), handles_[HOT_CF_HANDLE_INDEX],
|
||||
second_key, second_hot_columns));
|
||||
ASSERT_OK(db_->PutEntity(WriteOptions(), handles_[COLD_CF_HANDLE_INDEX],
|
||||
second_key, second_cold_columns));
|
||||
AttributeGroups first_key_attribute_groups{
|
||||
AttributeGroup(handles_[kDefaultCfHandleIndex], first_default_columns),
|
||||
AttributeGroup(handles_[kHotCfHandleIndex], first_hot_columns),
|
||||
AttributeGroup(handles_[kColdCfHandleIndex], first_cold_columns)};
|
||||
AttributeGroups second_key_attribute_groups{
|
||||
AttributeGroup(handles_[kHotCfHandleIndex], second_hot_columns),
|
||||
AttributeGroup(handles_[kColdCfHandleIndex], second_cold_columns)};
|
||||
|
||||
ASSERT_OK(
|
||||
db_->PutEntity(WriteOptions(), first_key, first_key_attribute_groups));
|
||||
ASSERT_OK(
|
||||
db_->PutEntity(WriteOptions(), second_key, second_key_attribute_groups));
|
||||
|
||||
constexpr size_t num_keys = 2;
|
||||
std::array<Slice, num_keys> keys = {first_key, second_key};
|
||||
std::vector<ColumnFamilyHandle*> all_cfs = handles_;
|
||||
std::vector<ColumnFamilyHandle*> default_and_hot_cfs{
|
||||
{handles_[DEFAULT_CF_HANDLE_INDEX], handles_[HOT_CF_HANDLE_INDEX]}};
|
||||
{handles_[kDefaultCfHandleIndex], handles_[kHotCfHandleIndex]}};
|
||||
std::vector<ColumnFamilyHandle*> hot_and_cold_cfs{
|
||||
{handles_[HOT_CF_HANDLE_INDEX], handles_[COLD_CF_HANDLE_INDEX]}};
|
||||
{handles_[kHotCfHandleIndex], handles_[kColdCfHandleIndex]}};
|
||||
std::vector<ColumnFamilyHandle*> null_and_hot_cfs{
|
||||
nullptr, handles_[kHotCfHandleIndex], nullptr};
|
||||
auto create_result =
|
||||
[](const std::vector<ColumnFamilyHandle*>& column_families)
|
||||
-> PinnableAttributeGroups {
|
||||
@@ -322,7 +463,7 @@ TEST_F(DBWideBasicTest, MultiCFMultiGetEntityAsPinnableAttributeGroups) {
|
||||
return result;
|
||||
};
|
||||
{
|
||||
// Check for invalid argument
|
||||
// Check for invalid read option argument
|
||||
ReadOptions read_options;
|
||||
read_options.io_activity = Env::IOActivity::kGetEntity;
|
||||
std::vector<PinnableAttributeGroups> results;
|
||||
@@ -336,6 +477,31 @@ TEST_F(DBWideBasicTest, MultiCFMultiGetEntityAsPinnableAttributeGroups) {
|
||||
ASSERT_TRUE(results[i][j].status().IsInvalidArgument());
|
||||
}
|
||||
}
|
||||
// Check for invalid column family in Attribute Group result
|
||||
results.clear();
|
||||
results.emplace_back(create_result(null_and_hot_cfs));
|
||||
results.emplace_back(create_result(all_cfs));
|
||||
db_->MultiGetEntity(ReadOptions(), num_keys, keys.data(), results.data());
|
||||
|
||||
// First one failed due to null CFs in the AttributeGroup
|
||||
// Null CF
|
||||
ASSERT_NOK(results[0][0].status());
|
||||
ASSERT_TRUE(results[0][0].status().IsInvalidArgument());
|
||||
// Valid CF, but failed with incomplete status because of other attribute
|
||||
// groups
|
||||
ASSERT_NOK(results[0][1].status());
|
||||
ASSERT_TRUE(results[0][1].status().IsIncomplete());
|
||||
// Null CF
|
||||
ASSERT_NOK(results[0][2].status());
|
||||
ASSERT_TRUE(results[0][2].status().IsInvalidArgument());
|
||||
|
||||
// Second one failed with Incomplete because first one failed
|
||||
ASSERT_NOK(results[1][0].status());
|
||||
ASSERT_TRUE(results[1][0].status().IsIncomplete());
|
||||
ASSERT_NOK(results[1][1].status());
|
||||
ASSERT_TRUE(results[1][1].status().IsIncomplete());
|
||||
ASSERT_NOK(results[1][2].status());
|
||||
ASSERT_TRUE(results[1][2].status().IsIncomplete());
|
||||
}
|
||||
{
|
||||
// Case 1. Get first key from default cf and hot_cf and second key from
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
#include "rocksdb/write_batch.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <stack>
|
||||
@@ -1016,6 +1017,22 @@ Status WriteBatch::PutEntity(ColumnFamilyHandle* column_family,
|
||||
return WriteBatchInternal::PutEntity(this, cf_id, key, columns);
|
||||
}
|
||||
|
||||
Status WriteBatch::PutEntity(const Slice& key,
|
||||
const AttributeGroups& attribute_groups) {
|
||||
if (attribute_groups.empty()) {
|
||||
return Status::InvalidArgument(
|
||||
"Cannot call this method with empty attribute groups");
|
||||
}
|
||||
Status s;
|
||||
for (const AttributeGroup& ag : attribute_groups) {
|
||||
s = PutEntity(ag.column_family(), key, ag.columns());
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status WriteBatchInternal::InsertNoop(WriteBatch* b) {
|
||||
b->rep_.push_back(static_cast<char>(kTypeNoop));
|
||||
return Status::OK();
|
||||
|
||||
@@ -12,7 +12,9 @@
|
||||
#include "db/column_family.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "db/memtable.h"
|
||||
#include "db/wide/wide_columns_helper.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "dbformat.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
@@ -276,6 +278,21 @@ struct TestHandler : public WriteBatch::Handler {
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
Status PutEntityCF(uint32_t column_family_id, const Slice& key,
|
||||
const Slice& entity) override {
|
||||
std::ostringstream oss;
|
||||
Status s = WideColumnsHelper::DumpSliceAsWideColumns(entity, oss, false);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
if (column_family_id == 0) {
|
||||
seen += "PutEntity(" + key.ToString() + ", " + oss.str() + ")";
|
||||
} else {
|
||||
seen += "PutEntityCF(" + std::to_string(column_family_id) + ", " +
|
||||
key.ToString() + ", " + oss.str() + ")";
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
Status DeleteCF(uint32_t column_family_id, const Slice& key) override {
|
||||
if (column_family_id == 0) {
|
||||
seen += "Delete(" + key.ToString() + ")";
|
||||
@@ -665,6 +682,82 @@ class ColumnFamilyHandleImplDummy : public ColumnFamilyHandleImpl {
|
||||
};
|
||||
} // anonymous namespace
|
||||
|
||||
TEST_F(WriteBatchTest, AttributeGroupTest) {
|
||||
WriteBatch batch;
|
||||
ColumnFamilyHandleImplDummy zero(0), two(2);
|
||||
AttributeGroups foo_ags;
|
||||
WideColumn zero_col_1{"0_c_1_n", "0_c_1_v"};
|
||||
WideColumn zero_col_2{"0_c_2_n", "0_c_2_v"};
|
||||
WideColumns zero_col_1_col_2{zero_col_1, zero_col_2};
|
||||
|
||||
WideColumn two_col_1{"2_c_1_n", "2_c_1_v"};
|
||||
WideColumn two_col_2{"2_c_2_n", "2_c_2_v"};
|
||||
WideColumns two_col_1_col_2{two_col_1, two_col_2};
|
||||
|
||||
foo_ags.emplace_back(&zero, zero_col_1_col_2);
|
||||
foo_ags.emplace_back(&two, two_col_1_col_2);
|
||||
|
||||
ASSERT_OK(batch.PutEntity("foo", foo_ags));
|
||||
|
||||
TestHandler handler;
|
||||
ASSERT_OK(batch.Iterate(&handler));
|
||||
ASSERT_EQ(
|
||||
"PutEntity(foo, 0_c_1_n:0_c_1_v "
|
||||
"0_c_2_n:0_c_2_v)"
|
||||
"PutEntityCF(2, foo, 2_c_1_n:2_c_1_v "
|
||||
"2_c_2_n:2_c_2_v)",
|
||||
handler.seen);
|
||||
}
|
||||
|
||||
TEST_F(WriteBatchTest, AttributeGroupSavePointTest) {
|
||||
WriteBatch batch;
|
||||
batch.SetSavePoint();
|
||||
|
||||
ColumnFamilyHandleImplDummy zero(0), two(2), three(3);
|
||||
AttributeGroups foo_ags;
|
||||
WideColumn zero_col_1{"0_c_1_n", "0_c_1_v"};
|
||||
WideColumn zero_col_2{"0_c_2_n", "0_c_2_v"};
|
||||
WideColumns zero_col_1_col_2{zero_col_1, zero_col_2};
|
||||
|
||||
WideColumn two_col_1{"2_c_1_n", "2_c_1_v"};
|
||||
WideColumn two_col_2{"2_c_2_n", "2_c_2_v"};
|
||||
WideColumns two_col_1_col_2{two_col_1, two_col_2};
|
||||
|
||||
foo_ags.emplace_back(&zero, zero_col_1_col_2);
|
||||
foo_ags.emplace_back(&two, two_col_1_col_2);
|
||||
|
||||
AttributeGroups bar_ags;
|
||||
WideColumn three_col_1{"3_c_1_n", "3_c_1_v"};
|
||||
WideColumn three_col_2{"3_c_2_n", "3_c_2_v"};
|
||||
WideColumns three_col_1_col_2{three_col_1, three_col_2};
|
||||
|
||||
bar_ags.emplace_back(&zero, zero_col_1_col_2);
|
||||
bar_ags.emplace_back(&three, three_col_1_col_2);
|
||||
|
||||
ASSERT_OK(batch.PutEntity("foo", foo_ags));
|
||||
batch.SetSavePoint();
|
||||
|
||||
ASSERT_OK(batch.PutEntity("bar", bar_ags));
|
||||
|
||||
TestHandler handler;
|
||||
ASSERT_OK(batch.Iterate(&handler));
|
||||
ASSERT_EQ(
|
||||
"PutEntity(foo, 0_c_1_n:0_c_1_v 0_c_2_n:0_c_2_v)"
|
||||
"PutEntityCF(2, foo, 2_c_1_n:2_c_1_v 2_c_2_n:2_c_2_v)"
|
||||
"PutEntity(bar, 0_c_1_n:0_c_1_v 0_c_2_n:0_c_2_v)"
|
||||
"PutEntityCF(3, bar, 3_c_1_n:3_c_1_v 3_c_2_n:3_c_2_v)",
|
||||
handler.seen);
|
||||
|
||||
ASSERT_OK(batch.RollbackToSavePoint());
|
||||
|
||||
handler.seen.clear();
|
||||
ASSERT_OK(batch.Iterate(&handler));
|
||||
ASSERT_EQ(
|
||||
"PutEntity(foo, 0_c_1_n:0_c_1_v 0_c_2_n:0_c_2_v)"
|
||||
"PutEntityCF(2, foo, 2_c_1_n:2_c_1_v 2_c_2_n:2_c_2_v)",
|
||||
handler.seen);
|
||||
}
|
||||
|
||||
TEST_F(WriteBatchTest, ColumnFamiliesBatchTest) {
|
||||
WriteBatch batch;
|
||||
ColumnFamilyHandleImplDummy zero(0), two(2), three(3), eight(8);
|
||||
@@ -677,6 +770,9 @@ TEST_F(WriteBatchTest, ColumnFamiliesBatchTest) {
|
||||
ASSERT_OK(batch.Merge(&three, Slice("threethree"), Slice("3three")));
|
||||
ASSERT_OK(batch.Put(&zero, Slice("foo"), Slice("bar")));
|
||||
ASSERT_OK(batch.Merge(Slice("omom"), Slice("nom")));
|
||||
// TODO(yuzhangyu): implement this.
|
||||
ASSERT_TRUE(
|
||||
batch.TimedPut(&zero, Slice("foo"), Slice("bar"), 0u).IsNotSupported());
|
||||
|
||||
TestHandler handler;
|
||||
ASSERT_OK(batch.Iterate(&handler));
|
||||
@@ -704,6 +800,8 @@ TEST_F(WriteBatchTest, ColumnFamiliesBatchWithIndexTest) {
|
||||
ASSERT_OK(batch.Merge(&three, Slice("threethree"), Slice("3three")));
|
||||
ASSERT_OK(batch.Put(&zero, Slice("foo"), Slice("bar")));
|
||||
ASSERT_OK(batch.Merge(Slice("omom"), Slice("nom")));
|
||||
ASSERT_TRUE(
|
||||
batch.TimedPut(&zero, Slice("foo"), Slice("bar"), 0u).IsNotSupported());
|
||||
|
||||
std::unique_ptr<WBWIIterator> iter;
|
||||
|
||||
|
||||
@@ -189,7 +189,7 @@ void CompressedCacheSetCapacityThread(void* v) {
|
||||
s.ToString().c_str());
|
||||
}
|
||||
} else if (FLAGS_compressed_secondary_cache_ratio > 0.0) {
|
||||
if (thread->rand.OneIn(2)) {
|
||||
if (thread->rand.OneIn(2)) { // if (thread->rand.OneIn(2)) {
|
||||
size_t capacity = block_cache->GetCapacity();
|
||||
size_t adjustment;
|
||||
if (FLAGS_use_write_buffer_manager && FLAGS_db_write_buffer_size > 0) {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <ios>
|
||||
#include <thread>
|
||||
|
||||
#include "rocksdb/options.h"
|
||||
#include "util/compression.h"
|
||||
#ifdef GFLAGS
|
||||
#include "db_stress_tool/db_stress_common.h"
|
||||
@@ -18,6 +19,7 @@
|
||||
#include "db_stress_tool/db_stress_driver.h"
|
||||
#include "db_stress_tool/db_stress_table_properties_collector.h"
|
||||
#include "db_stress_tool/db_stress_wide_merge_operator.h"
|
||||
#include "options/options_parser.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/filter_policy.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
@@ -1483,11 +1485,11 @@ void StressTest::VerifyIterator(ThreadState* thread,
|
||||
}
|
||||
|
||||
if (op == kLastOpSeekToFirst && ro.iterate_lower_bound != nullptr) {
|
||||
// SeekToFirst() with lower bound is not well defined.
|
||||
// SeekToFirst() with lower bound is not well-defined.
|
||||
*diverged = true;
|
||||
return;
|
||||
} else if (op == kLastOpSeekToLast && ro.iterate_upper_bound != nullptr) {
|
||||
// SeekToLast() with higher bound is not well defined.
|
||||
// SeekToLast() with higher bound is not well-defined.
|
||||
*diverged = true;
|
||||
return;
|
||||
} else if (op == kLastOpSeek && ro.iterate_lower_bound != nullptr &&
|
||||
@@ -1498,7 +1500,7 @@ void StressTest::VerifyIterator(ThreadState* thread,
|
||||
options_.comparator->CompareWithoutTimestamp(
|
||||
*ro.iterate_lower_bound, /*a_has_ts=*/false,
|
||||
*ro.iterate_upper_bound, /*b_has_ts*/ false) >= 0))) {
|
||||
// Lower bound behavior is not well defined if it is larger than
|
||||
// Lower bound behavior is not well-defined if it is larger than
|
||||
// seek key or upper bound. Disable the check for now.
|
||||
*diverged = true;
|
||||
return;
|
||||
@@ -1510,7 +1512,7 @@ void StressTest::VerifyIterator(ThreadState* thread,
|
||||
options_.comparator->CompareWithoutTimestamp(
|
||||
*ro.iterate_lower_bound, /*a_has_ts=*/false,
|
||||
*ro.iterate_upper_bound, /*b_has_ts=*/false) >= 0))) {
|
||||
// Uppder bound behavior is not well defined if it is smaller than
|
||||
// Upper bound behavior is not well-defined if it is smaller than
|
||||
// seek key or lower bound. Disable the check for now.
|
||||
*diverged = true;
|
||||
return;
|
||||
@@ -1538,13 +1540,13 @@ void StressTest::VerifyIterator(ThreadState* thread,
|
||||
}
|
||||
}
|
||||
fprintf(stderr,
|
||||
"Control interator is invalid but iterator has key %s "
|
||||
"Control iterator is invalid but iterator has key %s "
|
||||
"%s\n",
|
||||
iter->key().ToString(true).c_str(), op_logs.c_str());
|
||||
|
||||
*diverged = true;
|
||||
} else if (cmp_iter->Valid()) {
|
||||
// Iterator is not valid. It can be legimate if it has already been
|
||||
// Iterator is not valid. It can be legitimate if it has already been
|
||||
// out of upper or lower bound, or filtered out by prefix iterator.
|
||||
const Slice& total_order_key = cmp_iter->key();
|
||||
|
||||
@@ -1567,7 +1569,7 @@ void StressTest::VerifyIterator(ThreadState* thread,
|
||||
return;
|
||||
}
|
||||
fprintf(stderr,
|
||||
"Iterator stays in prefix but contol doesn't"
|
||||
"Iterator stays in prefix but control doesn't"
|
||||
" iterator key %s control iterator key %s %s\n",
|
||||
iter->key().ToString(true).c_str(),
|
||||
cmp_iter->key().ToString(true).c_str(), op_logs.c_str());
|
||||
@@ -1614,7 +1616,8 @@ void StressTest::VerifyIterator(ThreadState* thread,
|
||||
}
|
||||
|
||||
if (*diverged) {
|
||||
fprintf(stderr, "Control CF %s\n", cmp_cfh->GetName().c_str());
|
||||
fprintf(stderr, "VerifyIterator failed. Control CF %s\n",
|
||||
cmp_cfh->GetName().c_str());
|
||||
thread->stats.AddErrors(1);
|
||||
// Fail fast to preserve the DB state.
|
||||
thread->shared->SetVerificationFailure();
|
||||
@@ -1765,13 +1768,16 @@ Status StressTest::TestBackupRestore(
|
||||
}
|
||||
DB* restored_db = nullptr;
|
||||
std::vector<ColumnFamilyHandle*> restored_cf_handles;
|
||||
|
||||
// Not yet implemented: opening restored BlobDB or TransactionDB
|
||||
Options restore_options;
|
||||
if (s.ok() && !FLAGS_use_txn && !FLAGS_use_blob_db) {
|
||||
s = PrepareOptionsForRestoredDB(&restore_options);
|
||||
if (!s.ok()) {
|
||||
from = "PrepareRestoredDBOptions in backup/restore";
|
||||
}
|
||||
}
|
||||
if (s.ok() && !FLAGS_use_txn && !FLAGS_use_blob_db) {
|
||||
Options restore_options(options_);
|
||||
restore_options.best_efforts_recovery = false;
|
||||
restore_options.listeners.clear();
|
||||
// Avoid dangling/shared file descriptors, for reliable destroy
|
||||
restore_options.sst_file_manager = nullptr;
|
||||
std::vector<ColumnFamilyDescriptor> cf_descriptors;
|
||||
// TODO(ajkr): `column_family_names_` is not safe to access here when
|
||||
// `clear_column_family_one_in != 0`. But we can't easily switch to
|
||||
@@ -1889,6 +1895,68 @@ Status StressTest::TestBackupRestore(
|
||||
return s;
|
||||
}
|
||||
|
||||
void InitializeMergeOperator(Options& options) {
|
||||
if (FLAGS_use_full_merge_v1) {
|
||||
options.merge_operator = MergeOperators::CreateDeprecatedPutOperator();
|
||||
} else {
|
||||
if (FLAGS_use_put_entity_one_in > 0) {
|
||||
options.merge_operator = std::make_shared<DBStressWideMergeOperator>();
|
||||
} else {
|
||||
options.merge_operator = MergeOperators::CreatePutOperator();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Status StressTest::PrepareOptionsForRestoredDB(Options* options) {
|
||||
assert(options);
|
||||
// To avoid race with other threads' operations (e.g, SetOptions())
|
||||
// on the same pointer sub-option (e.g, `std::shared_ptr<const FilterPolicy>
|
||||
// filter_policy`) while having the same settings as `options_`, we create a
|
||||
// new Options object from `options_`'s string to deep copy these pointer
|
||||
// sub-options
|
||||
Status s;
|
||||
ConfigOptions config_opts;
|
||||
|
||||
std::string db_options_str;
|
||||
s = GetStringFromDBOptions(config_opts, options_, &db_options_str);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
DBOptions db_options;
|
||||
s = GetDBOptionsFromString(config_opts, Options(), db_options_str,
|
||||
&db_options);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
std::string cf_options_str;
|
||||
s = GetStringFromColumnFamilyOptions(config_opts, options_, &cf_options_str);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
ColumnFamilyOptions cf_options;
|
||||
s = GetColumnFamilyOptionsFromString(config_opts, Options(), cf_options_str,
|
||||
&cf_options);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
*options = Options(db_options, cf_options);
|
||||
options->best_efforts_recovery = false;
|
||||
options->listeners.clear();
|
||||
// Avoid dangling/shared file descriptors, for reliable destroy
|
||||
options->sst_file_manager = nullptr;
|
||||
// GetColumnFamilyOptionsFromString does not create customized merge operator.
|
||||
InitializeMergeOperator(*options);
|
||||
if (FLAGS_user_timestamp_size > 0) {
|
||||
// Check OPTIONS string loading can bootstrap the correct user comparator
|
||||
// from object registry.
|
||||
assert(options->comparator);
|
||||
assert(options->comparator == test::BytewiseComparatorWithU64TsWrapper());
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
Status StressTest::TestApproximateSize(
|
||||
ThreadState* thread, uint64_t iteration,
|
||||
const std::vector<int>& rand_column_families,
|
||||
@@ -3371,15 +3439,8 @@ void InitializeOptionsFromFlags(
|
||||
options.memtable_factory.reset(new VectorRepFactory());
|
||||
break;
|
||||
}
|
||||
if (FLAGS_use_full_merge_v1) {
|
||||
options.merge_operator = MergeOperators::CreateDeprecatedPutOperator();
|
||||
} else {
|
||||
if (FLAGS_use_put_entity_one_in > 0) {
|
||||
options.merge_operator = std::make_shared<DBStressWideMergeOperator>();
|
||||
} else {
|
||||
options.merge_operator = MergeOperators::CreatePutOperator();
|
||||
}
|
||||
}
|
||||
|
||||
InitializeMergeOperator(options);
|
||||
|
||||
if (FLAGS_enable_compaction_filter) {
|
||||
options.compaction_filter_factory =
|
||||
|
||||
@@ -194,6 +194,8 @@ class StressTest {
|
||||
const std::vector<int>& rand_column_families,
|
||||
const std::vector<int64_t>& rand_keys);
|
||||
|
||||
virtual Status PrepareOptionsForRestoredDB(Options* options);
|
||||
|
||||
virtual Status TestCheckpoint(ThreadState* thread,
|
||||
const std::vector<int>& rand_column_families,
|
||||
const std::vector<int64_t>& rand_keys);
|
||||
|
||||
@@ -612,7 +612,7 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
}
|
||||
Status s = NewTxn(wo, &txn);
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "NewTxn: %s\n", s.ToString().c_str());
|
||||
fprintf(stderr, "NewTxn error: %s\n", s.ToString().c_str());
|
||||
thread->shared->SafeTerminate();
|
||||
}
|
||||
}
|
||||
@@ -662,7 +662,8 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
assert(false);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "Transaction put: %s\n", s.ToString().c_str());
|
||||
fprintf(stderr, "Transaction put error: %s\n",
|
||||
s.ToString().c_str());
|
||||
thread->shared->SafeTerminate();
|
||||
}
|
||||
} else {
|
||||
@@ -1696,11 +1697,14 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
thread->shared->SetVerificationFailure();
|
||||
if (iter->Valid()) {
|
||||
fprintf(stderr,
|
||||
"Expected state has key %s, iterator is at key %s\n",
|
||||
"Verification failed. Expected state has key %s, iterator "
|
||||
"is at key %s\n",
|
||||
Slice(Key(j)).ToString(true).c_str(),
|
||||
iter->key().ToString(true).c_str());
|
||||
} else {
|
||||
fprintf(stderr, "Expected state has key %s, iterator is invalid\n",
|
||||
fprintf(stderr,
|
||||
"Verification failed. Expected state has key %s, iterator "
|
||||
"is invalid\n",
|
||||
Slice(Key(j)).ToString(true).c_str());
|
||||
}
|
||||
fprintf(stderr, "Column family: %s, op_logs: %s\n",
|
||||
@@ -1750,7 +1754,8 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
if (static_cast<int64_t>(curr) <= last_key) {
|
||||
thread->shared->SetVerificationFailure();
|
||||
fprintf(stderr,
|
||||
"TestIterateAgainstExpected found unexpectedly small key\n");
|
||||
"TestIterateAgainstExpected failed: found unexpectedly small "
|
||||
"key\n");
|
||||
fprintf(stderr, "Column family: %s, op_logs: %s\n",
|
||||
cfh->GetName().c_str(), op_logs.c_str());
|
||||
fprintf(stderr, "Last op found key: %s, expected at least: %s\n",
|
||||
@@ -1807,7 +1812,8 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
if (last_key <= static_cast<int64_t>(curr)) {
|
||||
thread->shared->SetVerificationFailure();
|
||||
fprintf(stderr,
|
||||
"TestIterateAgainstExpected found unexpectedly large key\n");
|
||||
"TestIterateAgainstExpected failed: found unexpectedly large "
|
||||
"key\n");
|
||||
fprintf(stderr, "Column family: %s, op_logs: %s\n",
|
||||
cfh->GetName().c_str(), op_logs.c_str());
|
||||
fprintf(stderr, "Last op found key: %s, expected at most: %s\n",
|
||||
@@ -1830,7 +1836,9 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
op_logs += "P";
|
||||
}
|
||||
|
||||
if (thread->rand.OneIn(2)) {
|
||||
// Write-prepared and Write-unprepared do not support Refresh() yet.
|
||||
if (!(FLAGS_use_txn && FLAGS_txn_write_policy != 0) &&
|
||||
thread->rand.OneIn(2)) {
|
||||
pre_read_expected_values.clear();
|
||||
post_read_expected_values.clear();
|
||||
// Refresh after forward/backward scan to allow higher chance of SV
|
||||
@@ -1839,7 +1847,9 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
pre_read_expected_values.push_back(
|
||||
shared->Get(rand_column_family, i + lb));
|
||||
}
|
||||
iter->Refresh();
|
||||
Status rs = iter->Refresh();
|
||||
assert(rs.ok());
|
||||
op_logs += "Refresh ";
|
||||
for (int64_t i = 0; i < static_cast<int64_t>(expected_values_size); ++i) {
|
||||
post_read_expected_values.push_back(
|
||||
shared->Get(rand_column_family, i + lb));
|
||||
@@ -1869,7 +1879,8 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
if (static_cast<int64_t>(curr) < mid) {
|
||||
thread->shared->SetVerificationFailure();
|
||||
fprintf(stderr,
|
||||
"TestIterateAgainstExpected found unexpectedly small key\n");
|
||||
"TestIterateAgainstExpected failed: found unexpectedly small "
|
||||
"key\n");
|
||||
fprintf(stderr, "Column family: %s, op_logs: %s\n",
|
||||
cfh->GetName().c_str(), op_logs.c_str());
|
||||
fprintf(stderr, "Last op found key: %s, expected at least: %s\n",
|
||||
@@ -1892,7 +1903,8 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
if (mid < static_cast<int64_t>(curr)) {
|
||||
thread->shared->SetVerificationFailure();
|
||||
fprintf(stderr,
|
||||
"TestIterateAgainstExpected found unexpectedly large key\n");
|
||||
"TestIterateAgainstExpected failed: found unexpectedly large "
|
||||
"key\n");
|
||||
fprintf(stderr, "Column family: %s, op_logs: %s\n",
|
||||
cfh->GetName().c_str(), op_logs.c_str());
|
||||
fprintf(stderr, "Last op found key: %s, expected at most: %s\n",
|
||||
@@ -1932,7 +1944,9 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
post_read_expected_value)) {
|
||||
// Fail fast to preserve the DB state.
|
||||
thread->shared->SetVerificationFailure();
|
||||
fprintf(stderr, "Iterator has key %s, but expected state does not.\n",
|
||||
fprintf(stderr,
|
||||
"Verification failed: iterator has key %s, but expected "
|
||||
"state does not.\n",
|
||||
iter->key().ToString(true).c_str());
|
||||
fprintf(stderr, "Column family: %s, op_logs: %s\n",
|
||||
cfh->GetName().c_str(), op_logs.c_str());
|
||||
@@ -1950,9 +1964,9 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
GetIntVal(iter->key().ToString(), &next);
|
||||
if (next <= curr) {
|
||||
thread->shared->SetVerificationFailure();
|
||||
fprintf(
|
||||
stderr,
|
||||
"TestIterateAgainstExpected found unexpectedly small key\n");
|
||||
fprintf(stderr,
|
||||
"TestIterateAgainstExpected failed: found unexpectedly "
|
||||
"small key\n");
|
||||
fprintf(stderr, "Column family: %s, op_logs: %s\n",
|
||||
cfh->GetName().c_str(), op_logs.c_str());
|
||||
fprintf(stderr, "Last op found key: %s, expected at least: %s\n",
|
||||
@@ -1975,9 +1989,9 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
GetIntVal(iter->key().ToString(), &prev);
|
||||
if (curr <= prev) {
|
||||
thread->shared->SetVerificationFailure();
|
||||
fprintf(
|
||||
stderr,
|
||||
"TestIterateAgainstExpected found unexpectedly large key\n");
|
||||
fprintf(stderr,
|
||||
"TestIterateAgainstExpected failed: found unexpectedly "
|
||||
"large key\n");
|
||||
fprintf(stderr, "Column family: %s, op_logs: %s\n",
|
||||
cfh->GetName().c_str(), op_logs.c_str());
|
||||
fprintf(stderr, "Last op found key: %s, expected at most: %s\n",
|
||||
|
||||
@@ -0,0 +1,287 @@
|
||||
---
|
||||
title: Java API Performance Improvements
|
||||
layout: post
|
||||
author: alanpaxton
|
||||
category: blog
|
||||
---
|
||||
# RocksDB Java API Performance Improvements
|
||||
|
||||
Evolved Binary has been working on several aspects of how the Java API to RocksDB can be improved. Two aspects of this which are of particular importance are performance and the developer experience.
|
||||
|
||||
* We have built some synthetic benchmark code to determine which are the most efficient methods of transferring data between Java and C++.
|
||||
* We have used the results of the synthetic benchmarking to guide plans for rationalising the API interfaces.
|
||||
* We have made some opportunistic performance optimizations/fixes within the Java API which have already yielded noticable improvements.
|
||||
|
||||
## Synthetic JNI API Performance Benchmarks
|
||||
The synthetic benchmark repository contains tests designed to isolate the Java to/from C++ interaction of a canonical data intensive Key/Value Store implemented in C++ with a Java (JNI) API layered on top.
|
||||
|
||||
JNI provides several mechanisms for allowing transfer of data between Java buffers and C++ buffers. These mechanisms are not trivial, because they require the JNI system to ensure that Java memory under the control of the JVM is not moved or garbage collected whilst it is being accessed outside the direct control of the JVM.
|
||||
|
||||
We set out to determine which of multiple options for transfer of data from `C++` to `Java` and vice-versa were the most efficient. We used the [Java Microbenchmark Harness](https://github.com/openjdk/jmh) to set up repeatable benchmarks to measure all the options.
|
||||
|
||||
We explore these and some other potential mechanisms in the detailed results (in our [Synthetic JNI performance repository](https://github.com/evolvedbinary/jni-benchmarks/blob/main/DataBenchmarks.md))
|
||||
|
||||
We summarise this work here:
|
||||
|
||||
### The Model
|
||||
|
||||
* In `C++` we represent the on-disk data as an in-memory map of `(key, value)`
|
||||
pairs.
|
||||
* For a fetch query, we expect the result to be a Java object with access to the
|
||||
contents of the _value_. This may be a standard Java object which does the job
|
||||
of data access (a `byte[]` or a `ByteBuffer`) or an object of our own devising
|
||||
which holds references to the value in some form (a `FastBuffer` pointing to
|
||||
`com.sun.unsafe.Unsafe` unsafe memory, for instance).
|
||||
|
||||
### Data Types
|
||||
|
||||
There are several potential data types for holding data for transfer, and they
|
||||
are unsurprisingly quite connected underneath.
|
||||
|
||||
#### Byte Array
|
||||
|
||||
The simplest data container is a _raw_ array of bytes (`byte[]`).
|
||||
|
||||
There are 3 different mechanisms for transferring data between a `byte[]` and
|
||||
C++
|
||||
|
||||
* At the C++ side, the method
|
||||
[`JNIEnv.GetArrayCritical()`](https://docs.oracle.com/en/java/javase/13/docs/specs/jni/functions.html#getprimitivearraycritical)
|
||||
allows access to a C++ pointer to the underlying array.
|
||||
* The `JNIEnv` methods `GetByteArrayElements()` and `ReleaseByteArrayElements()`
|
||||
fetch references/copies to and from the contents of a byte array, with less
|
||||
concern for critical sections than the _critical_ methods, though they are
|
||||
consequently more likely/certain to result in (extra) copies.
|
||||
* The `JNIEnv` methods `GetByteArrayRegion()` and `SetByteArrayRegion()`
|
||||
transfer raw C++ buffer data to and from the contents of a byte array. These
|
||||
must ultimately do some data pinning for the duration of copies; the
|
||||
mechanisms may be similar or different to the _critical_ operations, and
|
||||
therefore performance may differ.
|
||||
|
||||
#### Byte Buffer
|
||||
|
||||
A `ByteBuffer` abstracts the contents of a collection of bytes, and was in fact
|
||||
introduced to support a range of higher-performance I/O operations in some
|
||||
circumstances.
|
||||
|
||||
There are 2 types of byte buffers in Java, _indirect_ and _direct_. Indirect
|
||||
byte buffers are the standard, and the memory they use is on-heap as with all
|
||||
usual Java objects. In contrast, direct byte buffers are used to wrap off-heap
|
||||
memory which is accessible to direct network I/O. Either type of `ByteBuffer`
|
||||
can be allocated at the Java side, using the `allocate()` and `allocateDirect()`
|
||||
methods respectively.
|
||||
|
||||
Direct byte buffers can be created in C++ using the JNI method
|
||||
[`JNIEnv.NewDirectByteBuffer()`](https://docs.oracle.com/en/java/javase/13/docs/specs/jni/functions.html#newdirectbytebuffer)
|
||||
to wrap some native (C++) memory.
|
||||
|
||||
Direct byte buffers can be accessed in C++ using the
|
||||
[`JNIEnv.GetDirectBufferAddress()`](https://docs.oracle.com/en/java/javase/13/docs/specs/jni/functions.html#GetDirectBufferAddress)
|
||||
and measured using
|
||||
[`JNIEnv.GetDirectBufferCapacity()`](https://docs.oracle.com/en/java/javase/13/docs/specs/jni/functions.html#GetDirectBufferCapacity)
|
||||
|
||||
#### Unsafe Memory
|
||||
|
||||
The call `com.sun.unsafe.Unsafe.allocateMemory()` returns a handle which is (of course) just a pointer to raw memory, and
|
||||
can be used as such on the C++ side. We could turn it into a byte buffer on the
|
||||
C++ side by calling `JNIEnv.NewDirectByteBuffer()`, or simply use it as a native
|
||||
C++ buffer at the expected address, assuming we record or remember how much
|
||||
space was allocated.
|
||||
|
||||
A custom `FastBuffer` class provides access to unsafe memory from the Java side.
|
||||
|
||||
|
||||
#### Allocation
|
||||
|
||||
For these benchmarks, allocation has been excluded from the benchmark costs by
|
||||
pre-allocating a quantity of buffers of the appropriate kind as part of the test
|
||||
setup. Each run of the benchmark acquires an existing buffer from a pre-allocated
|
||||
FIFO list, and returns it afterwards. A small test has
|
||||
confirmed that the request and return cycle is of insignificant cost compared to
|
||||
the benchmark API call.
|
||||
|
||||
### GetJNIBenchmark Performance
|
||||
|
||||
Benchmarks ran for a duration of order 6 hours on an otherwise unloaded VM,
|
||||
the error bars are small and we can have strong confidence in the values
|
||||
derived and plotted.
|
||||
|
||||

|
||||
|
||||
Comparing all the benchmarks as the data size tends large, the conclusions we
|
||||
can draw are:
|
||||
|
||||
- Indirect byte buffers add cost; they are effectively an overhead on plain
|
||||
`byte[]` and the JNI-side only allows them to be accessed via their
|
||||
encapsulated `byte[]`.
|
||||
- `SetRegion` and `GetCritical` mechanisms for copying data into a `byte[]` are
|
||||
of very comparable performance; presumably the behaviour behind the scenes of
|
||||
`SetRegion` is very similar to that of declaring a critical region, doing a
|
||||
`memcpy()` and releasing the critical region.
|
||||
- `GetElements` methods for transferring data from C++ to Java are consistently
|
||||
less efficient than `SetRegion` and `GetCritical`.
|
||||
- Getting into a raw memory buffer, passed as an address (the `handle` of an
|
||||
`Unsafe` or of a netty `ByteBuf`) is of similar cost to the more efficient
|
||||
`byte[]` operations.
|
||||
- Getting into a direct `nio.ByteBuffer` is of similar cost again; while the
|
||||
ByteBuffer is passed over JNI as an ordinary Java object, JNI has a specific
|
||||
method for getting hold of the address of the direct buffer, and using this, the
|
||||
`get()` cost with a ByteBuffer is just that of the underlying C++ `memcpy()`.
|
||||
|
||||
At small(er) data sizes, we can see whether other factors are important.
|
||||
|
||||

|
||||
|
||||
- Indirect byte buffers are the most significant overhead here. Again, we can
|
||||
conclude that this is due to pure overhead compared to `byte[]` operations.
|
||||
- At the lowest data sizes, netty `ByteBuf`s and unsafe memory are marginally
|
||||
more efficient than `byte[]`s or (slightly less efficient) direct
|
||||
`nio.Bytebuffer`s. This may be explained by even the small cost of
|
||||
calling the JNI model on the C++ side simply to acquire a
|
||||
direct buffer address. The margins (nanoseconds) here are extremely small.
|
||||
|
||||
#### Post processing the results
|
||||
|
||||
Our benchmark model for post-processing is to transfer the results into a
|
||||
`byte[]`. Where the result is already a `byte[]` this may seem like an unfair
|
||||
extra cost, but the aim is to model the least cost processing step for any kind
|
||||
of result.
|
||||
|
||||
- Copying into a `byte[]` using the bulk methods supported by `byte[]`,
|
||||
`nio.ByteBuffer` have comparable performance.
|
||||
- Accessing the contents of an `Unsafe` buffer using the supplied unsafe methods
|
||||
is inefficient. The access is word by
|
||||
word, in Java.
|
||||
- Accessing the contents of a netty `ByteBuf` is similarly inefficient; again
|
||||
the access is presumably word by word, using normal
|
||||
Java mechanisms.
|
||||
|
||||

|
||||
|
||||
### PutJNIBenchmark
|
||||
|
||||
We benchmarked `Put` methods in a similar synthetic fashion in less depth, but enough to confirm that the performance profile is similar/symmetrical. As with `get()` using `GetElements` is the least performant way of implementing transfers to/from Java objects in C++/JNI, and other JNI mechanisms do not differ greatly one from another.
|
||||
|
||||
## Lessons from Synthetic API
|
||||
|
||||
Performance analysis shows that for `get()`, fetching into allocated `byte[]` is
|
||||
equally as efficient as any other mechanism, as long as JNI region methods are used
|
||||
for the internal data transfer. Copying out or otherwise using the
|
||||
result on the Java side is straightforward and efficient. Using `byte[]` avoids the manual memory
|
||||
management required with direct `nio.ByteBuffer`s, which extra work does not
|
||||
appear to provide any gain. A C++ implementation using the `GetRegion` JNI
|
||||
method is probably to be preferred to using `GetCritical` because while their
|
||||
performance is equal, `GetRegion` is a higher-level/simpler abstraction.
|
||||
|
||||
Vitally, whatever JNI transfer mechanism is chosen, the buffer allocation
|
||||
mechanism and pattern is crucial to achieving good performance. We experimented
|
||||
with making use of netty's pooled allocator part of the benchmark, and the
|
||||
difference of `getIntoPooledNettyByteBuf`, using the allocator, compared to
|
||||
`getIntoNettyByteBuf` using the same pre-allocate on setup as every other
|
||||
benchmark, is significant.
|
||||
|
||||
Equally importantly, transfer of data to or from buffers should where possible
|
||||
be done in bulk, using array copy or buffer copy mechanisms. Thought should
|
||||
perhaps be given to supporting common transformations in the underlying C++
|
||||
layer.
|
||||
|
||||
## API Recommendations
|
||||
|
||||
Of course there is some noise within the results. but we can agree:
|
||||
|
||||
* Don't make copies you don't need to make
|
||||
* Don't allocate/deallocate when you can avoid it
|
||||
|
||||
Translating this into designing an efficient API, we want to:
|
||||
|
||||
* Support API methods that return results in buffers supplied by the client.
|
||||
* Support `byte[]`-based APIs as the simplest way of getting data into a usable configuration for a broad range of Java use.
|
||||
* Support direct `ByteBuffer`s as these can reduce copies when used as part of a chain of `ByteBuffer`-based operations. This sort of sophisticated streaming model is most likely to be used by clients where performance is important, and so we decide to support it.
|
||||
* Support indirect `ByteBuffer`s for a combination of reasons:
|
||||
* API consistency between direct and indirect buffers
|
||||
* Simplicity of implementation, as we can wrap `byte[]`-oriented methods
|
||||
* Continue to support methods which allocate return buffers per-call, as these are the easiest to use on initial encounter with the RocksDB API.
|
||||
|
||||
High performance Java interaction with RocksDB ultimately requires architectural decisions by the client
|
||||
* Use more complex (client supplied buffer) API methods where performance matters
|
||||
* Don't allocate/deallocate where you don't need to
|
||||
* recycle your own buffers where this makes sense
|
||||
* or make sure that you are supplying the ultimate destination buffer (your cache, or a target network buffer) as input to RocksDB `get()` and `put()` calls
|
||||
|
||||
We are currently implementing a number of extra methods consistently across the Java fetch and store APIs to RocksDB in the PR [Java API consistency between RocksDB.put() , .merge() and Transaction.put() , .merge()](https://github.com/facebook/rocksdb/pull/11019) according to these principles.
|
||||
|
||||
## Optimizations
|
||||
|
||||
### Reduce Copies within API Implementation
|
||||
|
||||
Having analysed JNI performance as described, we reviewed the core of RocksJNI for opportunities to improve the performance. We noticed one thing in particular; some of the `get()` methods of the Java API had not been updated to take advantage of the new [`PinnableSlice`](http://rocksdb.org/blog/2017/08/24/pinnableslice.html) methods.
|
||||
|
||||
Fixing this turned out to be a straightforward change, which has now been incorporated in the codebase [Improve Java API `get()` performance by reducing copies](https://github.com/facebook/rocksdb/pull/10970)
|
||||
|
||||
#### Performance Results
|
||||
|
||||
Using the JMH performances tests we updated as part of the above PR, we can see a small but consistent improvement in performance for all of the different get method variants which we have enhanced in the PR.
|
||||
|
||||
```sh
|
||||
java -jar target/rocksdbjni-jmh-1.0-SNAPSHOT-benchmarks.jar -p keyCount=1000,50000 -p keySize=128 -p valueSize=1024,16384 -p columnFamilyTestType="1_column_family" GetBenchmarks.get GetBenchmarks.preallocatedByteBufferGet GetBenchmarks.preallocatedGet
|
||||
```
|
||||
The y-axis shows `ops/sec` in throughput, so higher is better.
|
||||
|
||||

|
||||
|
||||
### Analysis
|
||||
|
||||
Before the invention of the Pinnable Slice the simplest RocksDB (native) API `Get()` looked like this:
|
||||
|
||||
```cpp
|
||||
Status Get(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
std::string* value)
|
||||
```
|
||||
|
||||
After PinnableSlice the correct way for new code to implement a `get()` is like this
|
||||
|
||||
```cpp
|
||||
Status Get(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value)
|
||||
```
|
||||
|
||||
But of course RocksDB has to support legacy code, so there is an `inline` method in `db.h` which re-implements the former using the latter.
|
||||
And RocksJava API implementation seamlessly continues to use the `std::string`-based `get()`
|
||||
|
||||
Let's examine what happens when get() is called from Java
|
||||
|
||||
```cpp
|
||||
jint Java_org_rocksdb_RocksDB_get__JJ_3BII_3BIIJ(
|
||||
JNIEnv* env, jobject, jlong jdb_handle, jlong jropt_handle, jbyteArray jkey,
|
||||
jint jkey_off, jint jkey_len, jbyteArray jval, jint jval_off, jint jval_len,
|
||||
jlong jcf_handle)
|
||||
```
|
||||
|
||||
1. Create an empty `std::string value`
|
||||
2. Call `DB::Get()` using the `std::string` variant
|
||||
3. Copy the resultant `std::string` into Java, using the JNI `SetByteArrayRegion()` method
|
||||
|
||||
So stage (3) costs us a copy into Java. It's mostly unavoidable that there will be at least the one copy from a C++ buffer into a Java buffer.
|
||||
|
||||
But what does stage 2 do ?
|
||||
|
||||
* Create a `PinnableSlice(std::string&)` which uses the value as the slice's backing buffer.
|
||||
* Call `DB::Get()` using the PinnableSlice variant
|
||||
* Work out if the slice has pinned data, in which case copy the pinned data into value and release it.
|
||||
* ..or, if the slice has not pinned data, it is already in value (because we tried, but couldn't pin anything).
|
||||
|
||||
So stage (2) costs us a copy into a `std::string`. But! It's just a naive `std::string` that we have copied a large buffer into. And in RocksDB, the buffer is or can be large, so an extra copy something we need to worry about.
|
||||
|
||||
Luckily this is easy to fix. In the Java API (JNI) implementation:
|
||||
|
||||
1. Create a PinnableSlice() which uses its own default backing buffer.
|
||||
2. Call `DB::Get()` using the `PinnableSlice` variant of the RocksDB API
|
||||
3. Copy the data indicated by the `PinnableSlice` straight into the Java output buffer using the JNI `SetByteArrayRegion()` method, then release the slice.
|
||||
4. Work out if the slice has successfully pinned data, in which case copy the pinned data straight into the Java output buffer using the JNI `SetByteArrayRegion()` method, then release the pin.
|
||||
5. ..or, if the slice has not pinned data, it is in the pinnable slice's default backing buffer. All that is left, is to copy it straight into the Java output buffer using the JNI SetByteArrayRegion() method.
|
||||
|
||||
In the case where the `PinnableSlice` has succesfully pinned the data, this saves us the intermediate copy to the `std::string`. In the case where it hasn't, we still have the extra copy so the observed performance improvement depends on when the data can be pinned. Luckily, our benchmarking suggests that the pin is happening in a significant number of cases.
|
||||
|
||||
On discussion with the RocksDB core team we understand that the core `PinnableSlice` optimization is most likely to succeed when pages are loaded from the block cache, rather than when they are in `memtable`. And it might be possible to successfully pin in the `memtable` as well, with some extra coding effort. This would likely improve the results for these benchmarks.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 96 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 116 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 141 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
Vendored
+8
-8
@@ -57,10 +57,10 @@ static Env* GetTestEnv() {
|
||||
static std::shared_ptr<Env> env_guard;
|
||||
static Env* custom_env = nullptr;
|
||||
if (custom_env == nullptr) {
|
||||
const char* uri = getenv("TEST_ENV_URI");
|
||||
if (uri != nullptr) {
|
||||
EXPECT_OK(Env::CreateFromUri(ConfigOptions(), uri, "", &custom_env,
|
||||
&env_guard));
|
||||
const char* env_uri = getenv("TEST_ENV_URI");
|
||||
if (env_uri != nullptr) {
|
||||
EXPECT_OK(Env::CreateFromUri(ConfigOptions(), env_uri, /*fs_uri=*/"",
|
||||
&custom_env, &env_guard));
|
||||
}
|
||||
}
|
||||
EXPECT_NE(custom_env, nullptr);
|
||||
@@ -71,10 +71,10 @@ static Env* GetTestFS() {
|
||||
static std::shared_ptr<Env> fs_env_guard;
|
||||
static Env* fs_env = nullptr;
|
||||
if (fs_env == nullptr) {
|
||||
const char* uri = getenv("TEST_FS_URI");
|
||||
if (uri != nullptr) {
|
||||
EXPECT_OK(
|
||||
Env::CreateFromUri(ConfigOptions(), uri, "", &fs_env, &fs_env_guard));
|
||||
const char* fs_uri = getenv("TEST_FS_URI");
|
||||
if (fs_uri != nullptr) {
|
||||
EXPECT_OK(Env::CreateFromUri(ConfigOptions(), /*env_uri=*/"", fs_uri,
|
||||
&fs_env, &fs_env_guard));
|
||||
}
|
||||
}
|
||||
EXPECT_NE(fs_env, nullptr);
|
||||
|
||||
Vendored
+7
@@ -2906,6 +2906,13 @@ TEST_F(CreateEnvTest, CreateEncryptedFileSystem) {
|
||||
|
||||
std::string base_opts =
|
||||
std::string("provider=1://test; id=") + EncryptedFileSystem::kClassName();
|
||||
// Rewrite the default FileSystem URI if the "TEST_FS_URI" environment
|
||||
// variable is set. This is useful to test customer encryption plugins.
|
||||
const char* uri = getenv("TEST_FS_URI");
|
||||
if (uri != nullptr) {
|
||||
base_opts = uri;
|
||||
}
|
||||
|
||||
// The EncryptedFileSystem requires a "provider" option.
|
||||
ASSERT_NOK(FileSystem::CreateFromString(
|
||||
config_options_, EncryptedFileSystem::kClassName(), &fs));
|
||||
|
||||
Vendored
+2
-1
@@ -29,7 +29,8 @@
|
||||
|
||||
// For non linux platform, the following macros are used only as place
|
||||
// holder.
|
||||
#if !(defined OS_LINUX) && !(defined CYGWIN) && !(defined OS_AIX)
|
||||
#if !(defined OS_LINUX) && !(defined OS_FREEBSD) && !(defined CYGWIN) && \
|
||||
!(defined OS_AIX)
|
||||
#define POSIX_FADV_NORMAL 0 /* [MC1] no further special treatment */
|
||||
#define POSIX_FADV_RANDOM 1 /* [MC1] expect random page refs */
|
||||
#define POSIX_FADV_SEQUENTIAL 2 /* [MC1] expect sequential page refs */
|
||||
|
||||
Vendored
+1
@@ -21,6 +21,7 @@
|
||||
#ifdef __SSE4_2__
|
||||
#ifdef _WIN32
|
||||
#include <intrin.h>
|
||||
#define _rdtsc() __rdtsc()
|
||||
#else
|
||||
#include <x86intrin.h>
|
||||
#endif
|
||||
|
||||
@@ -144,6 +144,8 @@ int main() {
|
||||
options.create_if_missing = true;
|
||||
// Disable RocksDB background compaction.
|
||||
options.compaction_style = ROCKSDB_NAMESPACE::kCompactionStyleNone;
|
||||
// Small write buffer size for generating more sst files in level 0.
|
||||
options.write_buffer_size = 4 << 20;
|
||||
// Small slowdown and stop trigger for experimental purpose.
|
||||
options.level0_slowdown_writes_trigger = 3;
|
||||
options.level0_stop_writes_trigger = 5;
|
||||
|
||||
@@ -375,6 +375,14 @@ class Cache {
|
||||
// Returns the helper for the specified entry.
|
||||
virtual const CacheItemHelper* GetCacheItemHelper(Handle* handle) const = 0;
|
||||
|
||||
virtual Status GetSecondaryCacheCapacity(size_t& /*size*/) const {
|
||||
return Status::NotSupported();
|
||||
}
|
||||
|
||||
virtual Status GetSecondaryCachePinnedUsage(size_t& /*size*/) const {
|
||||
return Status::NotSupported();
|
||||
}
|
||||
|
||||
// Call this on shutdown if you want to speed it up. Cache will disown
|
||||
// any underlying data and will not free it on delete. This call will leak
|
||||
// memory - call this only if you're shutting down the process.
|
||||
@@ -595,6 +603,14 @@ class CacheWrapper : public Cache {
|
||||
return target_->HasStrictCapacityLimit();
|
||||
}
|
||||
|
||||
size_t GetOccupancyCount() const override {
|
||||
return target_->GetOccupancyCount();
|
||||
}
|
||||
|
||||
size_t GetTableAddressCount() const override {
|
||||
return target_->GetTableAddressCount();
|
||||
}
|
||||
|
||||
size_t GetCapacity() const override { return target_->GetCapacity(); }
|
||||
|
||||
size_t GetUsage() const override { return target_->GetUsage(); }
|
||||
@@ -630,6 +646,14 @@ class CacheWrapper : public Cache {
|
||||
target_->WaitAll(async_handles, count);
|
||||
}
|
||||
|
||||
uint32_t GetHashSeed() const override { return target_->GetHashSeed(); }
|
||||
|
||||
void ReportProblems(const std::shared_ptr<Logger>& info_log) const override {
|
||||
target_->ReportProblems(info_log);
|
||||
}
|
||||
|
||||
const std::shared_ptr<Cache>& GetTarget() { return target_; }
|
||||
|
||||
protected:
|
||||
std::shared_ptr<Cache> target_;
|
||||
};
|
||||
|
||||
@@ -714,7 +714,7 @@ struct AdvancedColumnFamilyOptions {
|
||||
//
|
||||
// Dynamically changeable through SetOptions() API
|
||||
std::vector<int> max_bytes_for_level_multiplier_additional =
|
||||
std::vector<int>(num_levels, 1);
|
||||
std::vector<int>(static_cast<size_t>(num_levels), 1);
|
||||
|
||||
// We try to limit number of bytes in one compaction to be lower than this
|
||||
// threshold. But it's not guaranteed.
|
||||
|
||||
@@ -78,6 +78,7 @@ typedef struct rocksdb_lru_cache_options_t rocksdb_lru_cache_options_t;
|
||||
typedef struct rocksdb_hyper_clock_cache_options_t
|
||||
rocksdb_hyper_clock_cache_options_t;
|
||||
typedef struct rocksdb_cache_t rocksdb_cache_t;
|
||||
typedef struct rocksdb_write_buffer_manager_t rocksdb_write_buffer_manager_t;
|
||||
typedef struct rocksdb_compactionfilter_t rocksdb_compactionfilter_t;
|
||||
typedef struct rocksdb_compactionfiltercontext_t
|
||||
rocksdb_compactionfiltercontext_t;
|
||||
@@ -1060,6 +1061,8 @@ rocksdb_block_based_options_set_pin_top_level_index_and_filter(
|
||||
rocksdb_block_based_table_options_t*, unsigned char);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_block_based_table_factory(
|
||||
rocksdb_options_t* opt, rocksdb_block_based_table_options_t* table_options);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_write_buffer_manager(
|
||||
rocksdb_options_t* opt, rocksdb_write_buffer_manager_t* wbm);
|
||||
|
||||
/* Cuckoo table options */
|
||||
|
||||
@@ -1143,6 +1146,8 @@ extern ROCKSDB_LIBRARY_API unsigned char rocksdb_options_get_paranoid_checks(
|
||||
rocksdb_options_t*);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_db_paths(
|
||||
rocksdb_options_t*, const rocksdb_dbpath_t** path_values, size_t num_paths);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_cf_paths(
|
||||
rocksdb_options_t*, const rocksdb_dbpath_t** path_values, size_t num_paths);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_env(rocksdb_options_t*,
|
||||
rocksdb_env_t*);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_info_log(rocksdb_options_t*,
|
||||
@@ -1256,6 +1261,10 @@ rocksdb_options_set_max_bytes_for_level_multiplier_additional(
|
||||
rocksdb_options_t*, int* level_values, size_t num_levels);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_enable_statistics(
|
||||
rocksdb_options_t*);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_periodic_compaction_seconds(
|
||||
rocksdb_options_t*, uint64_t);
|
||||
extern ROCKSDB_LIBRARY_API uint64_t
|
||||
rocksdb_options_get_periodic_compaction_seconds(rocksdb_options_t*);
|
||||
|
||||
enum {
|
||||
rocksdb_statistics_level_disable_all = 0,
|
||||
@@ -1668,6 +1677,10 @@ extern ROCKSDB_LIBRARY_API int rocksdb_options_get_wal_compression(
|
||||
/* RateLimiter */
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_ratelimiter_t* rocksdb_ratelimiter_create(
|
||||
int64_t rate_bytes_per_sec, int64_t refill_period_us, int32_t fairness);
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_ratelimiter_t*
|
||||
rocksdb_ratelimiter_create_auto_tuned(int64_t rate_bytes_per_sec,
|
||||
int64_t refill_period_us,
|
||||
int32_t fairness);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_ratelimiter_destroy(
|
||||
rocksdb_ratelimiter_t*);
|
||||
|
||||
@@ -2067,6 +2080,36 @@ rocksdb_cache_get_table_address_count(const rocksdb_cache_t* cache);
|
||||
extern ROCKSDB_LIBRARY_API size_t
|
||||
rocksdb_cache_get_occupancy_count(const rocksdb_cache_t* cache);
|
||||
|
||||
/* WriteBufferManager */
|
||||
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_write_buffer_manager_t*
|
||||
rocksdb_write_buffer_manager_create(size_t buffer_size, bool allow_stall);
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_write_buffer_manager_t*
|
||||
rocksdb_write_buffer_manager_create_with_cache(size_t buffer_size,
|
||||
const rocksdb_cache_t* cache,
|
||||
bool allow_stall);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_write_buffer_manager_destroy(
|
||||
rocksdb_write_buffer_manager_t* wbm);
|
||||
extern ROCKSDB_LIBRARY_API bool rocksdb_write_buffer_manager_enabled(
|
||||
rocksdb_write_buffer_manager_t* wbm);
|
||||
extern ROCKSDB_LIBRARY_API bool rocksdb_write_buffer_manager_cost_to_cache(
|
||||
rocksdb_write_buffer_manager_t* wbm);
|
||||
extern ROCKSDB_LIBRARY_API size_t
|
||||
rocksdb_write_buffer_manager_memory_usage(rocksdb_write_buffer_manager_t* wbm);
|
||||
extern ROCKSDB_LIBRARY_API size_t
|
||||
rocksdb_write_buffer_manager_mutable_memtable_memory_usage(
|
||||
rocksdb_write_buffer_manager_t* wbm);
|
||||
extern ROCKSDB_LIBRARY_API size_t
|
||||
rocksdb_write_buffer_manager_dummy_entries_in_cache_usage(
|
||||
rocksdb_write_buffer_manager_t* wbm);
|
||||
extern ROCKSDB_LIBRARY_API size_t
|
||||
rocksdb_write_buffer_manager_buffer_size(rocksdb_write_buffer_manager_t* wbm);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_write_buffer_manager_set_buffer_size(
|
||||
rocksdb_write_buffer_manager_t* wbm, size_t new_size);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_write_buffer_manager_set_allow_stall(
|
||||
rocksdb_write_buffer_manager_t* wbm, bool new_allow_stall);
|
||||
|
||||
/* HyperClockCache */
|
||||
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_hyper_clock_cache_options_t*
|
||||
@@ -2427,6 +2470,9 @@ extern ROCKSDB_LIBRARY_API char*
|
||||
rocksdb_sst_file_metadata_get_relative_filename(
|
||||
rocksdb_sst_file_metadata_t* file_meta);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API char* rocksdb_sst_file_metadata_get_directory(
|
||||
rocksdb_sst_file_metadata_t* file_meta);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API uint64_t
|
||||
rocksdb_sst_file_metadata_get_size(rocksdb_sst_file_metadata_t* file_meta);
|
||||
|
||||
|
||||
@@ -499,6 +499,10 @@ enum TieredAdmissionPolicy {
|
||||
// allocations costed to the block cache, will be distributed
|
||||
// proportionally across both the primary and secondary.
|
||||
struct TieredCacheOptions {
|
||||
// This should point to an instance of either LRUCacheOptions or
|
||||
// HyperClockCacheOptions, depending on the cache_type. In either
|
||||
// case, the capacity and secondary_cache fields in those options
|
||||
// should not be set. If set, they will be ignored by NewTieredCache.
|
||||
ShardedCacheOptions* cache_opts = nullptr;
|
||||
PrimaryCacheType cache_type = PrimaryCacheType::kCacheTypeLRU;
|
||||
TieredAdmissionPolicy adm_policy = TieredAdmissionPolicy::kAdmPolicyAuto;
|
||||
|
||||
+35
-2
@@ -435,6 +435,10 @@ class DB {
|
||||
virtual Status PutEntity(const WriteOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
const WideColumns& columns);
|
||||
// Split and store wide column entities in multiple column families (a.k.a.
|
||||
// AttributeGroups)
|
||||
virtual Status PutEntity(const WriteOptions& options, const Slice& key,
|
||||
const AttributeGroups& attribute_groups);
|
||||
|
||||
// Remove the database entry (if any) for "key". Returns OK on
|
||||
// success, and a non-OK status on error. It is not an error if "key"
|
||||
@@ -505,6 +509,15 @@ class DB {
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice& begin_key, const Slice& end_key,
|
||||
const Slice& ts);
|
||||
virtual Status DeleteRange(const WriteOptions& options,
|
||||
const Slice& begin_key, const Slice& end_key) {
|
||||
return DeleteRange(options, DefaultColumnFamily(), begin_key, end_key);
|
||||
}
|
||||
virtual Status DeleteRange(const WriteOptions& options,
|
||||
const Slice& begin_key, const Slice& end_key,
|
||||
const Slice& ts) {
|
||||
return DeleteRange(options, DefaultColumnFamily(), begin_key, end_key, ts);
|
||||
}
|
||||
|
||||
// Merge the database entry for "key" with "value". Returns OK on success,
|
||||
// and a non-OK status on error. The semantics of this operation is
|
||||
@@ -602,6 +615,16 @@ class DB {
|
||||
return Status::NotSupported("GetEntity not supported");
|
||||
}
|
||||
|
||||
// Returns logically grouped wide-column entities per column family (a.k.a.
|
||||
// attribute groups) for a single key. PinnableAttributeGroups is a vector of
|
||||
// PinnableAttributeGroup. Each PinnableAttributeGroup will have
|
||||
// ColumnFamilyHandle* as input, and Status and PinnableWideColumns as output.
|
||||
virtual Status GetEntity(const ReadOptions& /* options */,
|
||||
const Slice& /* key */,
|
||||
PinnableAttributeGroups* /* result */) {
|
||||
return Status::NotSupported("GetEntity not supported");
|
||||
}
|
||||
|
||||
// Populates the `merge_operands` array with all the merge operands in the DB
|
||||
// for `key`. The `merge_operands` array will be populated in the order of
|
||||
// insertion. The number of entries populated in `merge_operands` will be
|
||||
@@ -1636,7 +1659,17 @@ class DB {
|
||||
virtual Status GetFullHistoryTsLow(ColumnFamilyHandle* column_family,
|
||||
std::string* ts_low) = 0;
|
||||
|
||||
// Allow compactions to delete obsolete files.
|
||||
// Enable deleting obsolete files.
|
||||
// Usually users should only need to call this if they have previously called
|
||||
// `DisableFileDeletions`.
|
||||
// File deletions disabling and enabling is not controlled by a binary flag,
|
||||
// instead it's represented as a counter to allow different callers to
|
||||
// independently disable file deletion. Disabling file deletion can be
|
||||
// critical for operations like making a backup. So the counter implementation
|
||||
// makes the file deletion disabled as long as there is one caller requesting
|
||||
// so, and only when every caller agrees to re-enable file deletion, it will
|
||||
// be enabled. So be careful when calling this function with force = true as
|
||||
// explained below.
|
||||
// If force == true, the call to EnableFileDeletions() will guarantee that
|
||||
// file deletions are enabled after the call, even if DisableFileDeletions()
|
||||
// was called multiple times before.
|
||||
@@ -1645,7 +1678,7 @@ class DB {
|
||||
// enabling the two methods to be called by two threads concurrently without
|
||||
// synchronization -- i.e., file deletions will be enabled only after both
|
||||
// threads call EnableFileDeletions()
|
||||
virtual Status EnableFileDeletions(bool force = true) = 0;
|
||||
virtual Status EnableFileDeletions(bool force) = 0;
|
||||
|
||||
// Retrieves the creation time of the oldest file in the DB.
|
||||
// This API only works if max_open_files = -1, if it is not then
|
||||
|
||||
@@ -280,7 +280,7 @@ class Env : public Customizable {
|
||||
const EnvOptions& options);
|
||||
|
||||
// Open `fname` for random read and write, if file doesn't exist the file
|
||||
// will be created. On success, stores a pointer to the new file in
|
||||
// will not be created. On success, stores a pointer to the new file in
|
||||
// *result and returns OK. On failure returns non-OK.
|
||||
//
|
||||
// The returned file will only be accessed by one thread at a time.
|
||||
@@ -1001,7 +1001,7 @@ class WritableFile {
|
||||
/*
|
||||
* Get the size of valid data in the file.
|
||||
*/
|
||||
virtual uint64_t GetFileSize() { return 0; };
|
||||
virtual uint64_t GetFileSize() { return 0; }
|
||||
|
||||
/*
|
||||
* Get and set the default pre-allocation block size for writes to
|
||||
|
||||
Loaded 100 of 182 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user