Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e0ff19b1b | ||
|
|
414de699b6 | ||
|
|
300b2249dc | ||
|
|
a28f13c937 | ||
|
|
de97dddf61 | ||
|
|
05835f4071 | ||
|
|
51b540921d | ||
|
|
e8b841eb47 | ||
|
|
6ee0acdbb4 | ||
|
|
6fc2818ef8 | ||
|
|
5881f4ac50 | ||
|
|
28aa6d4e76 | ||
|
|
9d7de9605a | ||
|
|
f64f08e0bc | ||
|
|
19a89267ca | ||
|
|
82a70e1470 | ||
|
|
d96b0127a0 | ||
|
|
8ea0a2c1bd | ||
|
|
e817bc9628 | ||
|
|
5ba1b6e549 | ||
|
|
d53f7ff69a | ||
|
|
865a25101d | ||
|
|
575ea26ec9 | ||
|
|
298edae941 | ||
|
|
d81c2d1e3d | ||
|
|
ecccc63179 | ||
|
|
d5bd0039b9 | ||
|
|
25be1ed66a | ||
|
|
32a4d59477 | ||
|
|
b788e3f497 | ||
|
|
281ac9c89e | ||
|
|
99908db998 | ||
|
|
416dc7bed7 | ||
|
|
3981430f54 | ||
|
|
dcddc1065e | ||
|
|
9047fd7766 | ||
|
|
79407a8a61 | ||
|
|
d5a46c40e5 | ||
|
|
d60ae5b1c7 | ||
|
|
0d0aa578ff | ||
|
|
146263887f | ||
|
|
b3dbeadc34 | ||
|
|
58162835d1 | ||
|
|
a42a342a7a | ||
|
|
6ad0810393 | ||
|
|
3897ce3125 | ||
|
|
d61a449364 | ||
|
|
5a2b4ed671 | ||
|
|
f44e69c64a | ||
|
|
db325a5904 | ||
|
|
2f93a3b809 | ||
|
|
80a59a03a7 | ||
|
|
707f8d168a | ||
|
|
4d5b575563 | ||
|
|
9167ece586 | ||
|
|
799cf37cb1 | ||
|
|
2655477c67 | ||
|
|
eda83eaac0 | ||
|
|
ab718b415f | ||
|
|
d561af487c | ||
|
|
9e701b48e0 | ||
|
|
1c88f66ff8 | ||
|
|
cda7923169 | ||
|
|
b53e3d2adb | ||
|
|
c75ef03e58 | ||
|
|
dfa6b408fe | ||
|
|
886774eabf | ||
|
|
956ce9bde2 | ||
|
|
a607b88240 | ||
|
|
a4405fd981 |
No files matched your search
@@ -49,6 +49,9 @@ commands:
|
||||
echo "export GTEST_OUTPUT=\"xml:/tmp/test-results/\"" >> $BASH_ENV
|
||||
echo "export SKIP_FORMAT_BUCK_CHECKS=1" >> $BASH_ENV
|
||||
echo "export PRINT_PARALLEL_OUTPUTS=1" >> $BASH_ENV
|
||||
echo "export GTEST_COLOR=1" >> $BASH_ENV
|
||||
echo "export CTEST_OUTPUT_ON_FAILURE=1" >> $BASH_ENV
|
||||
echo "export CTEST_TEST_TIMEOUT=300" >> $BASH_ENV
|
||||
|
||||
pre-steps-macos:
|
||||
steps:
|
||||
@@ -88,6 +91,14 @@ commands:
|
||||
command: |
|
||||
sudo apt-get update -y && sudo apt-get install -y libgflags-dev
|
||||
|
||||
upgrade-cmake:
|
||||
steps:
|
||||
- run:
|
||||
name: Upgrade cmake
|
||||
command: |
|
||||
sudo apt remove --purge cmake
|
||||
sudo snap install cmake --classic
|
||||
|
||||
install-gflags-on-macos:
|
||||
steps:
|
||||
- run:
|
||||
@@ -140,12 +151,12 @@ jobs:
|
||||
- install-cmake-on-macos
|
||||
- install-gflags-on-macos
|
||||
- pre-steps-macos
|
||||
- run: ulimit -S -n 1048576 && (mkdir build && cd build && cmake -DWITH_GFLAGS=1 .. && make V=1 -j32) | .circleci/cat_ignore_eagain
|
||||
- run: ulimit -S -n 1048576 && (mkdir build && cd build && cmake -DWITH_GFLAGS=1 .. && make V=1 -j32 && ctest -j10) | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-linux:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -155,7 +166,7 @@ jobs:
|
||||
|
||||
build-linux-mem-env:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -165,7 +176,7 @@ jobs:
|
||||
|
||||
build-linux-encrypted-env:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -175,7 +186,7 @@ jobs:
|
||||
|
||||
build-linux-shared_lib-alt_namespace-status_checked:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -185,7 +196,7 @@ jobs:
|
||||
|
||||
build-linux-release:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: large
|
||||
steps:
|
||||
- checkout # check out the code in the project directory
|
||||
@@ -212,7 +223,7 @@ jobs:
|
||||
|
||||
build-linux-lite:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -222,7 +233,7 @@ jobs:
|
||||
|
||||
build-linux-lite-release:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: large
|
||||
steps:
|
||||
- checkout # check out the code in the project directory
|
||||
@@ -235,7 +246,7 @@ jobs:
|
||||
|
||||
build-linux-clang-no_test_run:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
- checkout # check out the code in the project directory
|
||||
@@ -245,7 +256,7 @@ jobs:
|
||||
|
||||
build-linux-clang10-asan:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -256,7 +267,7 @@ jobs:
|
||||
|
||||
build-linux-clang10-mini-tsan:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -267,7 +278,7 @@ jobs:
|
||||
|
||||
build-linux-clang10-ubsan:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -278,7 +289,7 @@ jobs:
|
||||
|
||||
build-linux-clang10-clang-analyze:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -288,13 +299,25 @@ jobs:
|
||||
- run: CC=clang-10 CXX=clang++-10 ROCKSDB_DISABLE_ALIGNED_NEW=1 CLANG_ANALYZER="/usr/bin/clang++-10" CLANG_SCAN_BUILD=scan-build-10 USE_CLANG=1 make V=1 -j32 analyze | .circleci/cat_ignore_eagain # aligned new doesn't work for reason we haven't figured out. For unknown, reason passing "clang++-10" as CLANG_ANALYZER doesn't work, and we need a full path.
|
||||
- post-steps
|
||||
|
||||
build-linux-cmake-no_test_run:
|
||||
build-linux-cmake:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
resource_class: large
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- checkout # check out the code in the project directory
|
||||
- run: (mkdir build && cd build && cmake -DWITH_GFLAGS=0 .. && make V=1 -j8) | .circleci/cat_ignore_eagain
|
||||
- install-gflags
|
||||
- upgrade-cmake
|
||||
- run: (mkdir build && cd build && cmake -DWITH_GFLAGS=1 .. && make V=1 -j20 && ctest -j20) | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-linux-cmake-ubuntu-20:
|
||||
machine:
|
||||
image: ubuntu-2004:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- checkout # check out the code in the project directory
|
||||
- install-gflags
|
||||
- run: (mkdir build && cd build && cmake -DWITH_GFLAGS=1 .. && make V=1 -j20 && ctest -j20) | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-linux-unity:
|
||||
@@ -309,7 +332,7 @@ jobs:
|
||||
|
||||
build-linux-gcc-4_8-no_test_run:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: large
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -417,7 +440,7 @@ jobs:
|
||||
|
||||
build-linux-java:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: large
|
||||
environment:
|
||||
JAVA_HOME: /usr/lib/jvm/java-1.8.0-openjdk-amd64
|
||||
@@ -441,7 +464,7 @@ jobs:
|
||||
|
||||
build-linux-java-static:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: large
|
||||
environment:
|
||||
JAVA_HOME: /usr/lib/jvm/java-1.8.0-openjdk-amd64
|
||||
@@ -512,7 +535,7 @@ jobs:
|
||||
|
||||
build-examples:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: large
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -525,7 +548,7 @@ jobs:
|
||||
|
||||
build-cmake-mingw:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -548,7 +571,7 @@ jobs:
|
||||
|
||||
build-linux-non-shm:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
parameters:
|
||||
start_test:
|
||||
@@ -604,6 +627,7 @@ jobs:
|
||||
JAVA_HOME: /usr/lib/jvm/java-8-openjdk-arm64
|
||||
steps:
|
||||
- pre-steps
|
||||
- install-gflags
|
||||
- run:
|
||||
name: "Set Java Environment"
|
||||
command: |
|
||||
@@ -616,7 +640,7 @@ jobs:
|
||||
command: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DWITH_TESTS=0 -DWITH_GFLAGS=0 -DWITH_BENCHMARK_TOOLS=0 -DWITH_TOOLS=0 -DWITH_CORE_TOOLS=1 ..
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DWITH_TESTS=0 -DWITH_GFLAGS=1 -DWITH_BENCHMARK_TOOLS=0 -DWITH_TOOLS=0 -DWITH_CORE_TOOLS=1 ..
|
||||
make -j4
|
||||
- run:
|
||||
name: "Build Java with cmake"
|
||||
@@ -624,13 +648,13 @@ jobs:
|
||||
rm -rf build
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DJNI=1 -DCMAKE_BUILD_TYPE=Release -DWITH_GFLAGS=0 ..
|
||||
cmake -DJNI=1 -DCMAKE_BUILD_TYPE=Release -DWITH_GFLAGS=1 ..
|
||||
make -j4 rocksdb rocksdbjni
|
||||
- post-steps
|
||||
|
||||
build-format-compatible:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -650,6 +674,10 @@ workflows:
|
||||
build-linux:
|
||||
jobs:
|
||||
- build-linux
|
||||
build-linux-cmake:
|
||||
jobs:
|
||||
- build-linux-cmake
|
||||
- build-linux-cmake-ubuntu-20
|
||||
build-linux-mem-env:
|
||||
jobs:
|
||||
- build-linux-mem-env
|
||||
@@ -733,7 +761,6 @@ workflows:
|
||||
build-linux-compilers-no_test_run:
|
||||
jobs:
|
||||
- build-linux-clang-no_test_run
|
||||
- build-linux-cmake-no_test_run
|
||||
- build-linux-gcc-4_8-no_test_run
|
||||
- build-linux-gcc-8-no_test_run
|
||||
- build-linux-gcc-9-no_test_run
|
||||
|
||||
@@ -32,10 +32,11 @@
|
||||
# 3. cmake ..
|
||||
# 4. make -j
|
||||
|
||||
cmake_minimum_required(VERSION 3.5.1)
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/modules/")
|
||||
include(ReadVersion)
|
||||
include(GoogleTest)
|
||||
get_rocksdb_version(rocksdb_VERSION)
|
||||
project(rocksdb
|
||||
VERSION ${rocksdb_VERSION}
|
||||
@@ -71,6 +72,12 @@ option(WITH_WINDOWS_UTF8_FILENAMES "use UTF8 as characterset for opening files,
|
||||
if (WITH_WINDOWS_UTF8_FILENAMES)
|
||||
add_definitions(-DROCKSDB_WINDOWS_UTF8_FILENAMES)
|
||||
endif()
|
||||
|
||||
if ($ENV{CIRCLECI})
|
||||
message(STATUS "Build for CircieCI env, a few tests may be disabled")
|
||||
add_definitions(-DCIRCLECI)
|
||||
endif()
|
||||
|
||||
# third-party/folly is only validated to work on Linux and Windows for now.
|
||||
# So only turn it on there by default.
|
||||
if(CMAKE_SYSTEM_NAME MATCHES "Linux|Windows")
|
||||
@@ -123,11 +130,11 @@ else()
|
||||
set(GFLAGS_LIB ${GFLAGS_TARGET})
|
||||
else()
|
||||
# Config with GFLAGS_LIBRARIES available since gflags 2.1.0
|
||||
set(GFLAGS_LIB ${GFLAGS_LIBRARIES})
|
||||
set(GFLAGS_LIB ${gflags_LIBRARIES})
|
||||
endif()
|
||||
else()
|
||||
find_package(gflags REQUIRED)
|
||||
set(GFLAGS_LIB gflags::gflags)
|
||||
set(GFLAGS_LIB gflags::gflags)
|
||||
endif()
|
||||
include_directories(${GFLAGS_INCLUDE_DIR})
|
||||
list(APPEND THIRDPARTY_LIBS ${GFLAGS_LIB})
|
||||
@@ -211,7 +218,7 @@ if(MSVC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi /nologo /EHsc /GS /Gd /GR /GF /fp:precise /Zc:wchar_t /Zc:forScope /errorReport:queue")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /FC /d2Zi+ /W4 /wd4127 /wd4800 /wd4996 /wd4351 /wd4100 /wd4204 /wd4324")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W -Wextra -Wall")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W -Wextra -Wall -pthread")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-compare -Wshadow -Wno-unused-parameter -Wno-unused-variable -Woverloaded-virtual -Wnon-virtual-dtor -Wno-missing-field-initializers -Wno-strict-aliasing")
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wstrict-prototypes")
|
||||
@@ -621,6 +628,7 @@ set(SOURCES
|
||||
cache/lru_cache.cc
|
||||
cache/sharded_cache.cc
|
||||
db/arena_wrapped_db_iter.cc
|
||||
db/blob/blob_fetcher.cc
|
||||
db/blob/blob_file_addition.cc
|
||||
db/blob/blob_file_builder.cc
|
||||
db/blob/blob_file_cache.cc
|
||||
@@ -1126,6 +1134,7 @@ if(WITH_TESTS)
|
||||
db/blob/db_blob_index_test.cc
|
||||
db/column_family_test.cc
|
||||
db/compact_files_test.cc
|
||||
db/compaction/clipping_iterator_test.cc
|
||||
db/compaction/compaction_job_stats_test.cc
|
||||
db/compaction/compaction_job_test.cc
|
||||
db/compaction/compaction_iterator_test.cc
|
||||
@@ -1321,21 +1330,21 @@ if(WITH_TESTS)
|
||||
|
||||
foreach(sourcefile ${TESTS})
|
||||
get_filename_component(exename ${sourcefile} NAME_WE)
|
||||
add_executable(${CMAKE_PROJECT_NAME}_${exename}${ARTIFACT_SUFFIX} ${sourcefile})
|
||||
set_target_properties(${CMAKE_PROJECT_NAME}_${exename}${ARTIFACT_SUFFIX}
|
||||
add_executable(${exename}${ARTIFACT_SUFFIX} ${sourcefile})
|
||||
set_target_properties(${exename}${ARTIFACT_SUFFIX}
|
||||
PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD_RELEASE 1
|
||||
EXCLUDE_FROM_DEFAULT_BUILD_MINRELEASE 1
|
||||
EXCLUDE_FROM_DEFAULT_BUILD_RELWITHDEBINFO 1
|
||||
OUTPUT_NAME ${exename}${ARTIFACT_SUFFIX}
|
||||
)
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME}_${exename}${ARTIFACT_SUFFIX} testutillib${ARTIFACT_SUFFIX} testharness gtest ${THIRDPARTY_LIBS} ${ROCKSDB_LIB})
|
||||
target_link_libraries(${exename}${ARTIFACT_SUFFIX} testutillib${ARTIFACT_SUFFIX} testharness gtest ${THIRDPARTY_LIBS} ${ROCKSDB_LIB})
|
||||
if(NOT "${exename}" MATCHES "db_sanity_test")
|
||||
add_test(NAME ${exename} COMMAND ${exename}${ARTIFACT_SUFFIX})
|
||||
add_dependencies(check ${CMAKE_PROJECT_NAME}_${exename}${ARTIFACT_SUFFIX})
|
||||
gtest_discover_tests(${exename} DISCOVERY_TIMEOUT 120)
|
||||
add_dependencies(check ${exename}${ARTIFACT_SUFFIX})
|
||||
endif()
|
||||
if("${exename}" MATCHES "env_librados_test")
|
||||
# env_librados_test.cc uses librados directly
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME}_${exename}${ARTIFACT_SUFFIX} rados)
|
||||
target_link_libraries(${exename}${ARTIFACT_SUFFIX} rados)
|
||||
endif()
|
||||
endforeach(sourcefile ${TESTS})
|
||||
|
||||
|
||||
@@ -1,16 +1,33 @@
|
||||
# Rocksdb Change Log
|
||||
## 6.21.3 (2021-07-19)
|
||||
## 6.22.3 (2021-07-19)
|
||||
### Bug Fixes
|
||||
* Fixed confusingly duplicated output in LOG for periodic stats ("DUMPING STATS"), including "Compaction Stats" and "File Read Latency Histogram By Level". (Included because the next bug fix built upon this one.)
|
||||
* Fixed block cache entry stat scans not to hold the DB mutex, which was a serious performance bug for tail latencies in TransactionDB and likely elsewhere.
|
||||
|
||||
## 6.21.2 (2021-06-14)
|
||||
## 6.22.2 (2021-07-14)
|
||||
### Bug Fixes
|
||||
* Fixed related but less common performance bugs in background gathering of block cache entry statistics.
|
||||
* Fix continuous logging of an existing background error on every user write
|
||||
|
||||
## 6.21.1 (2021-06-08)
|
||||
## 6.22.1 (2021-06-25)
|
||||
### Bug Fixes
|
||||
* Fixed a performance bug in background gathering of block cache entry statistics, that could consume a lot of CPU when there are many column families with a shared block cache.
|
||||
* `GetLiveFilesMetaData()` now populates the `temperature`, `oldest_ancester_time`, and `file_creation_time` fields of its `LiveFileMetaData` results when the information is available. Previously these fields always contained zero indicating unknown.
|
||||
|
||||
## 6.22.0 (2021-06-18)
|
||||
### Behavior Changes
|
||||
* Added two additional tickers, MEMTABLE_PAYLOAD_BYTES_AT_FLUSH and MEMTABLE_GARBAGE_BYTES_AT_FLUSH. These stats can be used to estimate the ratio of "garbage" (outdated) bytes in the memtable that are discarded at flush time.
|
||||
* Added API comments clarifying safe usage of Disable/EnableManualCompaction and EventListener callbacks for compaction.
|
||||
|
||||
### Bug Fixes
|
||||
* fs_posix.cc GetFreeSpace() always report disk space available to root even when running as non-root. Linux defaults often have disk mounts with 5 to 10 percent of total space reserved only for root. Out of space could result for non-root users.
|
||||
* Subcompactions are now disabled when user-defined timestamps are used, since the subcompaction boundary picking logic is currently not timestamp-aware, which could lead to incorrect results when different subcompactions process keys that only differ by timestamp.
|
||||
* Fix an issue that `DeleteFilesInRange()` may cause ongoing compaction reports corruption exception, or ASSERT for debug build. There's no actual data loss or corruption that we find.
|
||||
* Fixed confusingly duplicated output in LOG for periodic stats ("DUMPING STATS"), including "Compaction Stats" and "File Read Latency Histogram By Level".
|
||||
* Fixed performance bugs in background gathering of block cache entry statistics, that could consume a lot of CPU when there are many column families with a shared block cache.
|
||||
|
||||
### New Features
|
||||
* Marked the Ribbon filter and optimize_filters_for_memory features as production-ready, each enabling memory savings for Bloom-like filters. Use `NewRibbonFilterPolicy` in place of `NewBloomFilterPolicy` to use Ribbon filters instead of Bloom, or `ribbonfilter` in place of `bloomfilter` in configuration string.
|
||||
* Allow `DBWithTTL` to use `DeleteRange` api just like other DBs. `DeleteRangeCF()` which executes `WriteBatchInternal::DeleteRange()` has been added to the handler in `DBWithTTLImpl::Write()` to implement it.
|
||||
* Add BlockBasedTableOptions.prepopulate_block_cache. If enabled, it prepopulate warm/hot data blocks which are already in memory into block cache at the time of flush. On a flush, the data block that is in memory (in memtables) get flushed to the device. If using Direct IO, additional IO is incurred to read this data back into memory again, which is avoided by enabling this option and it also helps with Distributed FileSystem. More details in include/rocksdb/table.h.
|
||||
* Added a `cancel` field to `CompactRangeOptions`, allowing individual in-process manual range compactions to be cancelled.
|
||||
|
||||
## 6.21.0 (2021-05-21)
|
||||
### Bug Fixes
|
||||
|
||||
@@ -516,8 +516,10 @@ endif
|
||||
ifdef ASSERT_STATUS_CHECKED
|
||||
# TODO: finish fixing all tests to pass this check
|
||||
TESTS_FAILING_ASC = \
|
||||
c_test \
|
||||
db_test \
|
||||
db_test2 \
|
||||
env_test \
|
||||
range_locking_test \
|
||||
testutil_test \
|
||||
|
||||
@@ -1866,6 +1868,10 @@ db_blob_corruption_test: $(OBJ_DIR)/db/blob/db_blob_corruption_test.o $(TEST_LIB
|
||||
|
||||
db_write_buffer_manager_test: $(OBJ_DIR)/db/db_write_buffer_manager_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
clipping_iterator_test: $(OBJ_DIR)/db/compaction/clipping_iterator_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
#-------------------------------------------------
|
||||
# make install related stuff
|
||||
PREFIX ?= /usr/local
|
||||
|
||||
@@ -137,6 +137,7 @@ cpp_library(
|
||||
"cache/lru_cache.cc",
|
||||
"cache/sharded_cache.cc",
|
||||
"db/arena_wrapped_db_iter.cc",
|
||||
"db/blob/blob_fetcher.cc",
|
||||
"db/blob/blob_file_addition.cc",
|
||||
"db/blob/blob_file_builder.cc",
|
||||
"db/blob/blob_file_cache.cc",
|
||||
@@ -448,6 +449,7 @@ cpp_library(
|
||||
"cache/lru_cache.cc",
|
||||
"cache/sharded_cache.cc",
|
||||
"db/arena_wrapped_db_iter.cc",
|
||||
"db/blob/blob_fetcher.cc",
|
||||
"db/blob/blob_file_addition.cc",
|
||||
"db/blob/blob_file_builder.cc",
|
||||
"db/blob/blob_file_cache.cc",
|
||||
@@ -1051,6 +1053,13 @@ ROCKS_TESTS = [
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"clipping_iterator_test",
|
||||
"db/compaction/clipping_iterator_test.cc",
|
||||
"parallel",
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"coding_test",
|
||||
"util/coding_test.cc",
|
||||
|
||||
@@ -106,9 +106,7 @@ NON_SHM="TMPD=/tmp/rocksdb_test_tmp"
|
||||
GCC_481="ROCKSDB_FBCODE_BUILD_WITH_481=1"
|
||||
ASAN="COMPILE_WITH_ASAN=1"
|
||||
CLANG="USE_CLANG=1"
|
||||
# in gcc-5 there are known problems with TSAN like https://gcc.gnu.org/bugzilla/show_bug.cgi?id=71090.
|
||||
# using platform007 gives us gcc-8 or higher which has that bug fixed.
|
||||
TSAN="ROCKSDB_FBCODE_BUILD_WITH_PLATFORM007=1 COMPILE_WITH_TSAN=1"
|
||||
TSAN="COMPILE_WITH_TSAN=1"
|
||||
UBSAN="COMPILE_WITH_UBSAN=1"
|
||||
TSAN_CRASH='CRASH_TEST_EXT_ARGS="--compression_type=zstd --log2_keys_per_lock=22"'
|
||||
NON_TSAN_CRASH="CRASH_TEST_EXT_ARGS=--compression_type=zstd"
|
||||
|
||||
@@ -27,7 +27,7 @@ enum class CacheEntryRole {
|
||||
kIndexBlock,
|
||||
// Other kinds of block-based table block
|
||||
kOtherBlock,
|
||||
// WriteBufferManager resevations to account for memtable usage
|
||||
// WriteBufferManager reservations to account for memtable usage
|
||||
kWriteBuffer,
|
||||
// Default bucket, for miscellaneous cache entries. Do not use for
|
||||
// entries that could potentially add up to large usage.
|
||||
|
||||
@@ -309,7 +309,8 @@ void LRUCacheShard::SetStrictCapacityLimit(bool strict_capacity_limit) {
|
||||
strict_capacity_limit_ = strict_capacity_limit;
|
||||
}
|
||||
|
||||
Status LRUCacheShard::InsertItem(LRUHandle* e, Cache::Handle** handle) {
|
||||
Status LRUCacheShard::InsertItem(LRUHandle* e, Cache::Handle** handle,
|
||||
bool free_handle_on_fail) {
|
||||
Status s = Status::OK();
|
||||
autovector<LRUHandle*> last_reference_list;
|
||||
size_t total_charge = e->CalcTotalCharge(metadata_charge_policy_);
|
||||
@@ -323,14 +324,16 @@ Status LRUCacheShard::InsertItem(LRUHandle* e, Cache::Handle** handle) {
|
||||
|
||||
if ((usage_ + total_charge) > capacity_ &&
|
||||
(strict_capacity_limit_ || handle == nullptr)) {
|
||||
e->SetInCache(false);
|
||||
if (handle == nullptr) {
|
||||
// Don't insert the entry but still return ok, as if the entry inserted
|
||||
// into cache and get evicted immediately.
|
||||
e->SetInCache(false);
|
||||
last_reference_list.push_back(e);
|
||||
} else {
|
||||
delete[] reinterpret_cast<char*>(e);
|
||||
*handle = nullptr;
|
||||
if (free_handle_on_fail) {
|
||||
delete[] reinterpret_cast<char*>(e);
|
||||
*handle = nullptr;
|
||||
}
|
||||
s = Status::Incomplete("Insert failed due to LRU cache being full.");
|
||||
}
|
||||
} else {
|
||||
@@ -375,6 +378,43 @@ Status LRUCacheShard::InsertItem(LRUHandle* e, Cache::Handle** handle) {
|
||||
return s;
|
||||
}
|
||||
|
||||
void LRUCacheShard::Promote(LRUHandle* e) {
|
||||
SecondaryCacheResultHandle* secondary_handle = e->sec_handle;
|
||||
|
||||
assert(secondary_handle->IsReady());
|
||||
e->SetIncomplete(false);
|
||||
e->SetInCache(true);
|
||||
e->SetPromoted(true);
|
||||
e->value = secondary_handle->Value();
|
||||
e->charge = secondary_handle->Size();
|
||||
delete secondary_handle;
|
||||
|
||||
// This call could fail if the cache is over capacity and
|
||||
// strict_capacity_limit_ is true. In such a case, we don't want
|
||||
// InsertItem() to free the handle, since the item is already in memory
|
||||
// and the caller will most likely just read from disk if we erase it here.
|
||||
if (e->value) {
|
||||
Cache::Handle* handle = reinterpret_cast<Cache::Handle*>(e);
|
||||
Status s = InsertItem(e, &handle, /*free_handle_on_fail=*/false);
|
||||
if (s.ok()) {
|
||||
// InsertItem would have taken a reference on the item, so decrement it
|
||||
// here as we expect the caller to already hold a reference
|
||||
e->Unref();
|
||||
} else {
|
||||
// Item is in memory, but not accounted against the cache capacity.
|
||||
// When the handle is released, the item should get deleted
|
||||
assert(!e->InCache());
|
||||
}
|
||||
} else {
|
||||
// Since the secondary cache lookup failed, mark the item as not in cache
|
||||
// and charge the cache only for metadata usage, i.e handle, key etc
|
||||
MutexLock l(&mutex_);
|
||||
e->charge = 0;
|
||||
e->SetInCache(false);
|
||||
usage_ += e->CalcTotalCharge(metadata_charge_policy_);
|
||||
}
|
||||
}
|
||||
|
||||
Cache::Handle* LRUCacheShard::Lookup(
|
||||
const Slice& key, uint32_t hash,
|
||||
const ShardedCache::CacheItemHelper* helper,
|
||||
@@ -399,47 +439,44 @@ Cache::Handle* LRUCacheShard::Lookup(
|
||||
// mutex if we're going to lookup in the secondary cache
|
||||
// Only support synchronous for now
|
||||
// TODO: Support asynchronous lookup in secondary cache
|
||||
if (!e && secondary_cache_ && helper && helper->saveto_cb && wait) {
|
||||
if (!e && secondary_cache_ && helper && helper->saveto_cb) {
|
||||
// For objects from the secondary cache, we expect the caller to provide
|
||||
// a way to create/delete the primary cache object. The only case where
|
||||
// a deleter would not be required is for dummy entries inserted for
|
||||
// accounting purposes, which we won't demote to the secondary cache
|
||||
// anyway.
|
||||
assert(create_cb && helper->del_cb);
|
||||
std::unique_ptr<SecondaryCacheHandle> secondary_handle =
|
||||
std::unique_ptr<SecondaryCacheResultHandle> secondary_handle =
|
||||
secondary_cache_->Lookup(key, create_cb, wait);
|
||||
if (secondary_handle != nullptr) {
|
||||
void* value = nullptr;
|
||||
e = reinterpret_cast<LRUHandle*>(
|
||||
new char[sizeof(LRUHandle) - 1 + key.size()]);
|
||||
|
||||
e->flags = 0;
|
||||
e->SetPromoted(true);
|
||||
e->SetSecondaryCacheCompatible(true);
|
||||
e->info_.helper = helper;
|
||||
e->key_length = key.size();
|
||||
e->hash = hash;
|
||||
e->refs = 0;
|
||||
e->next = e->prev = nullptr;
|
||||
e->SetInCache(true);
|
||||
e->SetPriority(priority);
|
||||
memcpy(e->key_data, key.data(), key.size());
|
||||
e->value = nullptr;
|
||||
e->sec_handle = secondary_handle.release();
|
||||
e->Ref();
|
||||
|
||||
value = secondary_handle->Value();
|
||||
e->value = value;
|
||||
e->charge = secondary_handle->Size();
|
||||
|
||||
// This call could nullify e if the cache is over capacity and
|
||||
// strict_capacity_limit_ is true. In such a case, the caller will try
|
||||
// to insert later, which might again fail, but its ok as this should
|
||||
// not be common
|
||||
// Being conservative here since there could be lookups that are
|
||||
// actually ok to fail rather than succeed and bloat up the memory
|
||||
// usage (preloading partitioned index blocks, for example).
|
||||
Status s = InsertItem(e, reinterpret_cast<Cache::Handle**>(&e));
|
||||
if (!s.ok()) {
|
||||
assert(e == nullptr);
|
||||
(*helper->del_cb)(key, value);
|
||||
if (wait) {
|
||||
Promote(e);
|
||||
if (!e->value) {
|
||||
// The secondary cache returned a handle, but the lookup failed
|
||||
e->Unref();
|
||||
e->Free();
|
||||
e = nullptr;
|
||||
}
|
||||
} else {
|
||||
// If wait is false, we always return a handle and let the caller
|
||||
// release the handle after checking for success or failure
|
||||
e->SetIncomplete(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -530,7 +567,7 @@ Status LRUCacheShard::Insert(const Slice& key, uint32_t hash, void* value,
|
||||
e->SetPriority(priority);
|
||||
memcpy(e->key_data, key.data(), key.size());
|
||||
|
||||
return InsertItem(e, handle);
|
||||
return InsertItem(e, handle, /* free_handle_on_fail */ true);
|
||||
}
|
||||
|
||||
void LRUCacheShard::Erase(const Slice& key, uint32_t hash) {
|
||||
@@ -560,6 +597,18 @@ void LRUCacheShard::Erase(const Slice& key, uint32_t hash) {
|
||||
}
|
||||
}
|
||||
|
||||
bool LRUCacheShard::IsReady(Cache::Handle* handle) {
|
||||
LRUHandle* e = reinterpret_cast<LRUHandle*>(handle);
|
||||
MutexLock l(&mutex_);
|
||||
bool ready = true;
|
||||
if (e->IsPending()) {
|
||||
assert(secondary_cache_);
|
||||
assert(e->sec_handle);
|
||||
ready = e->sec_handle->IsReady();
|
||||
}
|
||||
return ready;
|
||||
}
|
||||
|
||||
size_t LRUCacheShard::GetUsage() const {
|
||||
MutexLock l(&mutex_);
|
||||
return usage_;
|
||||
@@ -600,6 +649,7 @@ LRUCache::LRUCache(size_t capacity, int num_shard_bits,
|
||||
use_adaptive_mutex, metadata_charge_policy,
|
||||
/* max_upper_hash_bits */ 32 - num_shard_bits, secondary_cache);
|
||||
}
|
||||
secondary_cache_ = secondary_cache;
|
||||
}
|
||||
|
||||
LRUCache::~LRUCache() {
|
||||
@@ -665,6 +715,36 @@ double LRUCache::GetHighPriPoolRatio() {
|
||||
return result;
|
||||
}
|
||||
|
||||
void LRUCache::WaitAll(std::vector<Handle*>& handles) {
|
||||
if (secondary_cache_) {
|
||||
std::vector<SecondaryCacheResultHandle*> sec_handles;
|
||||
sec_handles.reserve(handles.size());
|
||||
for (Handle* handle : handles) {
|
||||
if (!handle) {
|
||||
continue;
|
||||
}
|
||||
LRUHandle* lru_handle = reinterpret_cast<LRUHandle*>(handle);
|
||||
if (!lru_handle->IsPending()) {
|
||||
continue;
|
||||
}
|
||||
sec_handles.emplace_back(lru_handle->sec_handle);
|
||||
}
|
||||
secondary_cache_->WaitAll(sec_handles);
|
||||
for (Handle* handle : handles) {
|
||||
if (!handle) {
|
||||
continue;
|
||||
}
|
||||
LRUHandle* lru_handle = reinterpret_cast<LRUHandle*>(handle);
|
||||
if (!lru_handle->IsPending()) {
|
||||
continue;
|
||||
}
|
||||
uint32_t hash = GetHash(handle);
|
||||
LRUCacheShard* shard = static_cast<LRUCacheShard*>(GetShard(Shard(hash)));
|
||||
shard->Promote(lru_handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Cache> NewLRUCache(
|
||||
size_t capacity, int num_shard_bits, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio,
|
||||
|
||||
@@ -57,7 +57,12 @@ struct LRUHandle {
|
||||
Cache::DeleterFn deleter;
|
||||
const ShardedCache::CacheItemHelper* helper;
|
||||
} info_;
|
||||
LRUHandle* next_hash;
|
||||
// An entry is not added to the LRUHandleTable until the secondary cache
|
||||
// lookup is complete, so its safe to have this union.
|
||||
union {
|
||||
LRUHandle* next_hash;
|
||||
SecondaryCacheResultHandle* sec_handle;
|
||||
};
|
||||
LRUHandle* next;
|
||||
LRUHandle* prev;
|
||||
size_t charge; // TODO(opt): Only allow uint32_t?
|
||||
@@ -189,7 +194,16 @@ struct LRUHandle {
|
||||
if (!IsSecondaryCacheCompatible() && info_.deleter) {
|
||||
(*info_.deleter)(key(), value);
|
||||
} else if (IsSecondaryCacheCompatible()) {
|
||||
(*info_.helper->del_cb)(key(), value);
|
||||
if (IsPending()) {
|
||||
assert(sec_handle != nullptr);
|
||||
SecondaryCacheResultHandle* tmp_sec_handle = sec_handle;
|
||||
tmp_sec_handle->Wait();
|
||||
value = tmp_sec_handle->Value();
|
||||
delete tmp_sec_handle;
|
||||
}
|
||||
if (value) {
|
||||
(*info_.helper->del_cb)(key(), value);
|
||||
}
|
||||
}
|
||||
delete[] reinterpret_cast<char*>(this);
|
||||
}
|
||||
@@ -314,7 +328,7 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
bool force_erase) override {
|
||||
return Release(handle, force_erase);
|
||||
}
|
||||
virtual bool IsReady(Cache::Handle* /*handle*/) override { return true; }
|
||||
virtual bool IsReady(Cache::Handle* /*handle*/) override;
|
||||
virtual void Wait(Cache::Handle* /*handle*/) override {}
|
||||
virtual bool Ref(Cache::Handle* handle) override;
|
||||
virtual bool Release(Cache::Handle* handle,
|
||||
@@ -347,10 +361,23 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
double GetHighPriPoolRatio();
|
||||
|
||||
private:
|
||||
Status InsertItem(LRUHandle* item, Cache::Handle** handle);
|
||||
friend class LRUCache;
|
||||
// Insert an item into the hash table and, if handle is null, insert into
|
||||
// the LRU list. Older items are evicted as necessary. If the cache is full
|
||||
// and free_handle_on_fail is true, the item is deleted and handle is set to.
|
||||
Status InsertItem(LRUHandle* item, Cache::Handle** handle,
|
||||
bool free_handle_on_fail);
|
||||
Status Insert(const Slice& key, uint32_t hash, void* value, size_t charge,
|
||||
DeleterFn deleter, const Cache::CacheItemHelper* helper,
|
||||
Cache::Handle** handle, Cache::Priority priority);
|
||||
// Promote an item looked up from the secondary cache to the LRU cache. The
|
||||
// item is only inserted into the hash table and not the LRU list, and only
|
||||
// if the cache is not at full capacity, as is the case during Insert. The
|
||||
// caller should hold a reference on the LRUHandle. When the caller releases
|
||||
// the last reference, the item is added to the LRU list.
|
||||
// The item is promoted to the high pri or low pri pool as specified by the
|
||||
// caller in Lookup.
|
||||
void Promote(LRUHandle* e);
|
||||
void LRU_Remove(LRUHandle* e);
|
||||
void LRU_Insert(LRUHandle* e);
|
||||
|
||||
@@ -437,7 +464,7 @@ class LRUCache
|
||||
virtual uint32_t GetHash(Handle* handle) const override;
|
||||
virtual DeleterFn GetDeleter(Handle* handle) const override;
|
||||
virtual void DisownData() override;
|
||||
virtual void WaitAll(std::vector<Handle*>& /*handles*/) override {}
|
||||
virtual void WaitAll(std::vector<Handle*>& handles) override;
|
||||
|
||||
// Retrieves number of elements in LRU, for unit test purpose only
|
||||
size_t TEST_GetLRUSize();
|
||||
@@ -447,6 +474,7 @@ class LRUCache
|
||||
private:
|
||||
LRUCacheShard* shards_ = nullptr;
|
||||
int num_shards_ = 0;
|
||||
std::shared_ptr<SecondaryCache> secondary_cache_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "test_util/testharness.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/random.h"
|
||||
#include "utilities/fault_injection_fs.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -203,6 +204,19 @@ TEST_F(LRUCacheTest, EntriesWithPriority) {
|
||||
|
||||
class TestSecondaryCache : public SecondaryCache {
|
||||
public:
|
||||
// Specifies what action to take on a lookup for a particular key
|
||||
enum ResultType {
|
||||
SUCCESS,
|
||||
// Fail lookup immediately
|
||||
FAIL,
|
||||
// Defer the result. It will returned after Wait/WaitAll is called
|
||||
DEFER,
|
||||
// Defer the result and eventually return failure
|
||||
DEFER_AND_FAIL
|
||||
};
|
||||
|
||||
using ResultMap = std::unordered_map<std::string, ResultType>;
|
||||
|
||||
explicit TestSecondaryCache(size_t capacity)
|
||||
: num_inserts_(0), num_lookups_(0), inject_failure_(false) {
|
||||
cache_ = NewLRUCache(capacity, 0, false, 0.5, nullptr,
|
||||
@@ -216,11 +230,16 @@ class TestSecondaryCache : public SecondaryCache {
|
||||
|
||||
void ResetInjectFailure() { inject_failure_ = false; }
|
||||
|
||||
void SetDbSessionId(const std::string& db_session_id) {
|
||||
db_session_id_ = db_session_id;
|
||||
}
|
||||
|
||||
Status Insert(const Slice& key, void* value,
|
||||
const Cache::CacheItemHelper* helper) override {
|
||||
if (inject_failure_) {
|
||||
return Status::Corruption("Insertion Data Corrupted");
|
||||
}
|
||||
assert(IsDbSessionIdAsKeyPrefix(key) == true);
|
||||
size_t size;
|
||||
char* buf;
|
||||
Status s;
|
||||
@@ -240,22 +259,37 @@ class TestSecondaryCache : public SecondaryCache {
|
||||
});
|
||||
}
|
||||
|
||||
std::unique_ptr<SecondaryCacheHandle> Lookup(
|
||||
std::unique_ptr<SecondaryCacheResultHandle> Lookup(
|
||||
const Slice& key, const Cache::CreateCallback& create_cb,
|
||||
bool /*wait*/) override {
|
||||
std::unique_ptr<SecondaryCacheHandle> secondary_handle;
|
||||
std::string key_str = key.ToString();
|
||||
TEST_SYNC_POINT_CALLBACK("TestSecondaryCache::Lookup", &key_str);
|
||||
|
||||
std::unique_ptr<SecondaryCacheResultHandle> secondary_handle;
|
||||
ResultType type = ResultType::SUCCESS;
|
||||
auto iter = result_map_.find(key.ToString());
|
||||
if (iter != result_map_.end()) {
|
||||
type = iter->second;
|
||||
}
|
||||
if (type == ResultType::FAIL) {
|
||||
return secondary_handle;
|
||||
}
|
||||
|
||||
Cache::Handle* handle = cache_->Lookup(key);
|
||||
num_lookups_++;
|
||||
if (handle) {
|
||||
void* value;
|
||||
size_t charge;
|
||||
char* ptr = (char*)cache_->Value(handle);
|
||||
size_t size = DecodeFixed64(ptr);
|
||||
ptr += sizeof(uint64_t);
|
||||
Status s = create_cb(ptr, size, &value, &charge);
|
||||
void* value = nullptr;
|
||||
size_t charge = 0;
|
||||
Status s;
|
||||
if (type != ResultType::DEFER_AND_FAIL) {
|
||||
char* ptr = (char*)cache_->Value(handle);
|
||||
size_t size = DecodeFixed64(ptr);
|
||||
ptr += sizeof(uint64_t);
|
||||
s = create_cb(ptr, size, &value, &charge);
|
||||
}
|
||||
if (s.ok()) {
|
||||
secondary_handle.reset(
|
||||
new TestSecondaryCacheHandle(cache_.get(), handle, value, charge));
|
||||
secondary_handle.reset(new TestSecondaryCacheResultHandle(
|
||||
cache_.get(), handle, value, charge, type));
|
||||
} else {
|
||||
cache_->Release(handle);
|
||||
}
|
||||
@@ -265,47 +299,92 @@ class TestSecondaryCache : public SecondaryCache {
|
||||
|
||||
void Erase(const Slice& /*key*/) override {}
|
||||
|
||||
void WaitAll(std::vector<SecondaryCacheHandle*> /*handles*/) override {}
|
||||
void WaitAll(std::vector<SecondaryCacheResultHandle*> handles) override {
|
||||
for (SecondaryCacheResultHandle* handle : handles) {
|
||||
TestSecondaryCacheResultHandle* sec_handle =
|
||||
static_cast<TestSecondaryCacheResultHandle*>(handle);
|
||||
sec_handle->SetReady();
|
||||
}
|
||||
}
|
||||
|
||||
std::string GetPrintableOptions() const override { return ""; }
|
||||
|
||||
void SetResultMap(ResultMap&& map) { result_map_ = std::move(map); }
|
||||
|
||||
uint32_t num_inserts() { return num_inserts_; }
|
||||
|
||||
uint32_t num_lookups() { return num_lookups_; }
|
||||
|
||||
private:
|
||||
class TestSecondaryCacheHandle : public SecondaryCacheHandle {
|
||||
public:
|
||||
TestSecondaryCacheHandle(Cache* cache, Cache::Handle* handle, void* value,
|
||||
size_t size)
|
||||
: cache_(cache), handle_(handle), value_(value), size_(size) {}
|
||||
~TestSecondaryCacheHandle() override { cache_->Release(handle_); }
|
||||
bool IsDbSessionIdAsKeyPrefix(const Slice& key) {
|
||||
if (db_session_id_.size() == 0) {
|
||||
return true;
|
||||
}
|
||||
if (key.size() < 20) {
|
||||
return false;
|
||||
}
|
||||
std::string s_key = key.ToString();
|
||||
if (s_key.substr(0, 20) != db_session_id_) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsReady() override { return true; }
|
||||
private:
|
||||
class TestSecondaryCacheResultHandle : public SecondaryCacheResultHandle {
|
||||
public:
|
||||
TestSecondaryCacheResultHandle(Cache* cache, Cache::Handle* handle,
|
||||
void* value, size_t size, ResultType type)
|
||||
: cache_(cache),
|
||||
handle_(handle),
|
||||
value_(value),
|
||||
size_(size),
|
||||
is_ready_(true) {
|
||||
if (type != ResultType::SUCCESS) {
|
||||
is_ready_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
~TestSecondaryCacheResultHandle() override { cache_->Release(handle_); }
|
||||
|
||||
bool IsReady() override { return is_ready_; }
|
||||
|
||||
void Wait() override {}
|
||||
|
||||
void* Value() override { return value_; }
|
||||
void* Value() override {
|
||||
assert(is_ready_);
|
||||
return value_;
|
||||
}
|
||||
|
||||
size_t Size() override { return size_; }
|
||||
size_t Size() override { return Value() ? size_ : 0; }
|
||||
|
||||
void SetReady() { is_ready_ = true; }
|
||||
|
||||
private:
|
||||
Cache* cache_;
|
||||
Cache::Handle* handle_;
|
||||
void* value_;
|
||||
size_t size_;
|
||||
bool is_ready_;
|
||||
};
|
||||
|
||||
std::shared_ptr<Cache> cache_;
|
||||
uint32_t num_inserts_;
|
||||
uint32_t num_lookups_;
|
||||
bool inject_failure_;
|
||||
std::string db_session_id_;
|
||||
ResultMap result_map_;
|
||||
};
|
||||
|
||||
class DBSecondaryCacheTest : public DBTestBase {
|
||||
public:
|
||||
DBSecondaryCacheTest()
|
||||
: DBTestBase("/db_secondary_cache_test", /*env_do_fsync=*/true) {}
|
||||
: DBTestBase("/db_secondary_cache_test", /*env_do_fsync=*/true) {
|
||||
fault_fs_.reset(new FaultInjectionTestFS(env_->GetFileSystem()));
|
||||
fault_env_.reset(new CompositeEnvWrapper(env_, fault_fs_));
|
||||
}
|
||||
|
||||
std::shared_ptr<FaultInjectionTestFS> fault_fs_;
|
||||
std::unique_ptr<Env> fault_env_;
|
||||
};
|
||||
|
||||
class LRUSecondaryCacheTest : public LRUCacheTest {
|
||||
@@ -323,6 +402,7 @@ class LRUSecondaryCacheTest : public LRUCacheTest {
|
||||
|
||||
char* Buf() { return buf_.get(); }
|
||||
size_t Size() { return size_; }
|
||||
std::string ToString() { return std::string(Buf(), Size()); }
|
||||
|
||||
private:
|
||||
std::unique_ptr<char[]> buf_;
|
||||
@@ -548,14 +628,15 @@ TEST_F(LRUSecondaryCacheTest, FullCapacityTest) {
|
||||
handle = cache->Lookup("k2", &LRUSecondaryCacheTest::helper_,
|
||||
test_item_creator, Cache::Priority::LOW, true);
|
||||
ASSERT_NE(handle, nullptr);
|
||||
// This lookup should fail, since k1 promotion would have failed due to
|
||||
// the block cache being at capacity
|
||||
// k1 promotion should fail due to the block cache being at capacity,
|
||||
// but the lookup should still succeed
|
||||
Cache::Handle* handle2;
|
||||
handle2 = cache->Lookup("k1", &LRUSecondaryCacheTest::helper_,
|
||||
test_item_creator, Cache::Priority::LOW, true);
|
||||
ASSERT_EQ(handle2, nullptr);
|
||||
// Since k1 didn't get promoted, k2 should still be in cache
|
||||
ASSERT_NE(handle2, nullptr);
|
||||
// Since k1 didn't get inserted, k2 should still be in cache
|
||||
cache->Release(handle);
|
||||
cache->Release(handle2);
|
||||
handle = cache->Lookup("k2", &LRUSecondaryCacheTest::helper_,
|
||||
test_item_creator, Cache::Priority::LOW, true);
|
||||
ASSERT_NE(handle, nullptr);
|
||||
@@ -586,11 +667,16 @@ TEST_F(DBSecondaryCacheTest, TestSecondaryCacheCorrectness1) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.env = fault_env_.get();
|
||||
fault_fs_->SetFailGetUniqueId(true);
|
||||
|
||||
// Set the file paranoid check, so after flush, the file will be read
|
||||
// all the blocks will be accessed.
|
||||
options.paranoid_file_checks = true;
|
||||
DestroyAndReopen(options);
|
||||
std::string session_id;
|
||||
ASSERT_OK(db_->GetDbSessionId(session_id));
|
||||
secondary_cache->SetDbSessionId(session_id);
|
||||
Random rnd(301);
|
||||
const int N = 6;
|
||||
for (int i = 0; i < N; i++) {
|
||||
@@ -679,7 +765,12 @@ TEST_F(DBSecondaryCacheTest, TestSecondaryCacheCorrectness2) {
|
||||
options.create_if_missing = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.paranoid_file_checks = true;
|
||||
options.env = fault_env_.get();
|
||||
fault_fs_->SetFailGetUniqueId(true);
|
||||
DestroyAndReopen(options);
|
||||
std::string session_id;
|
||||
ASSERT_OK(db_->GetDbSessionId(session_id));
|
||||
secondary_cache->SetDbSessionId(session_id);
|
||||
Random rnd(301);
|
||||
const int N = 6;
|
||||
for (int i = 0; i < N; i++) {
|
||||
@@ -767,7 +858,13 @@ TEST_F(DBSecondaryCacheTest, NoSecondaryCacheInsertion) {
|
||||
options.create_if_missing = true;
|
||||
options.paranoid_file_checks = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.env = fault_env_.get();
|
||||
fault_fs_->SetFailGetUniqueId(true);
|
||||
|
||||
DestroyAndReopen(options);
|
||||
std::string session_id;
|
||||
ASSERT_OK(db_->GetDbSessionId(session_id));
|
||||
secondary_cache->SetDbSessionId(session_id);
|
||||
Random rnd(301);
|
||||
const int N = 6;
|
||||
for (int i = 0; i < N; i++) {
|
||||
@@ -814,7 +911,12 @@ TEST_F(DBSecondaryCacheTest, SecondaryCacheIntensiveTesting) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.env = fault_env_.get();
|
||||
fault_fs_->SetFailGetUniqueId(true);
|
||||
DestroyAndReopen(options);
|
||||
std::string session_id;
|
||||
ASSERT_OK(db_->GetDbSessionId(session_id));
|
||||
secondary_cache->SetDbSessionId(session_id);
|
||||
Random rnd(301);
|
||||
const int N = 256;
|
||||
for (int i = 0; i < N; i++) {
|
||||
@@ -859,7 +961,12 @@ TEST_F(DBSecondaryCacheTest, SecondaryCacheFailureTest) {
|
||||
options.create_if_missing = true;
|
||||
options.paranoid_file_checks = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.env = fault_env_.get();
|
||||
fault_fs_->SetFailGetUniqueId(true);
|
||||
DestroyAndReopen(options);
|
||||
std::string session_id;
|
||||
ASSERT_OK(db_->GetDbSessionId(session_id));
|
||||
secondary_cache->SetDbSessionId(session_id);
|
||||
Random rnd(301);
|
||||
const int N = 6;
|
||||
for (int i = 0; i < N; i++) {
|
||||
@@ -932,6 +1039,141 @@ TEST_F(DBSecondaryCacheTest, SecondaryCacheFailureTest) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(LRUSecondaryCacheTest, BasicWaitAllTest) {
|
||||
LRUCacheOptions opts(1024, 2, false, 0.5, nullptr, kDefaultToAdaptiveMutex,
|
||||
kDontChargeCacheMetadata);
|
||||
std::shared_ptr<TestSecondaryCache> secondary_cache =
|
||||
std::make_shared<TestSecondaryCache>(32 * 1024);
|
||||
opts.secondary_cache = secondary_cache;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(opts);
|
||||
const int num_keys = 32;
|
||||
|
||||
Random rnd(301);
|
||||
std::vector<std::string> values;
|
||||
for (int i = 0; i < num_keys; ++i) {
|
||||
std::string str = rnd.RandomString(1020);
|
||||
values.emplace_back(str);
|
||||
TestItem* item = new TestItem(str.data(), str.length());
|
||||
ASSERT_OK(cache->Insert("k" + std::to_string(i), item,
|
||||
&LRUSecondaryCacheTest::helper_, str.length()));
|
||||
}
|
||||
// Force all entries to be evicted to the secondary cache
|
||||
cache->SetCapacity(0);
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 32u);
|
||||
cache->SetCapacity(32 * 1024);
|
||||
|
||||
secondary_cache->SetResultMap(
|
||||
{{"k3", TestSecondaryCache::ResultType::DEFER},
|
||||
{"k4", TestSecondaryCache::ResultType::DEFER_AND_FAIL},
|
||||
{"k5", TestSecondaryCache::ResultType::FAIL}});
|
||||
std::vector<Cache::Handle*> results;
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
results.emplace_back(
|
||||
cache->Lookup("k" + std::to_string(i), &LRUSecondaryCacheTest::helper_,
|
||||
test_item_creator, Cache::Priority::LOW, false));
|
||||
}
|
||||
cache->WaitAll(results);
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
if (i == 4) {
|
||||
ASSERT_EQ(cache->Value(results[i]), nullptr);
|
||||
} else if (i == 5) {
|
||||
ASSERT_EQ(results[i], nullptr);
|
||||
continue;
|
||||
} else {
|
||||
TestItem* item = static_cast<TestItem*>(cache->Value(results[i]));
|
||||
ASSERT_EQ(item->ToString(), values[i]);
|
||||
}
|
||||
cache->Release(results[i]);
|
||||
}
|
||||
|
||||
cache.reset();
|
||||
secondary_cache.reset();
|
||||
}
|
||||
|
||||
// In this test, we have one KV pair per data block. We indirectly determine
|
||||
// the cache key associated with each data block (and thus each KV) by using
|
||||
// a sync point callback in TestSecondaryCache::Lookup. We then control the
|
||||
// lookup result by setting the ResultMap.
|
||||
TEST_F(DBSecondaryCacheTest, TestSecondaryCacheMultiGet) {
|
||||
LRUCacheOptions opts(1 << 20, 0, false, 0.5, nullptr, kDefaultToAdaptiveMutex,
|
||||
kDontChargeCacheMetadata);
|
||||
std::shared_ptr<TestSecondaryCache> secondary_cache(
|
||||
new TestSecondaryCache(2048 * 1024));
|
||||
opts.secondary_cache = secondary_cache;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(opts);
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_cache = cache;
|
||||
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.paranoid_file_checks = true;
|
||||
DestroyAndReopen(options);
|
||||
Random rnd(301);
|
||||
const int N = 8;
|
||||
std::vector<std::string> keys;
|
||||
for (int i = 0; i < N; i++) {
|
||||
std::string p_v = rnd.RandomString(4000);
|
||||
keys.emplace_back(p_v);
|
||||
ASSERT_OK(Put(Key(i), p_v));
|
||||
}
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
// After Flush is successful, RocksDB does the paranoid check for the new
|
||||
// SST file. This will try to lookup all data blocks in the secondary
|
||||
// cache.
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
|
||||
ASSERT_EQ(secondary_cache->num_lookups(), 8u);
|
||||
|
||||
cache->SetCapacity(0);
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 8u);
|
||||
cache->SetCapacity(1 << 20);
|
||||
|
||||
std::vector<std::string> cache_keys;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"TestSecondaryCache::Lookup", [&cache_keys](void* key) -> void {
|
||||
cache_keys.emplace_back(*(static_cast<std::string*>(key)));
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
for (int i = 0; i < N; ++i) {
|
||||
std::string v = Get(Key(i));
|
||||
ASSERT_EQ(4000, v.size());
|
||||
ASSERT_EQ(v, keys[i]);
|
||||
}
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ASSERT_EQ(secondary_cache->num_lookups(), 16u);
|
||||
cache->SetCapacity(0);
|
||||
cache->SetCapacity(1 << 20);
|
||||
|
||||
ASSERT_EQ(Get(Key(2)), keys[2]);
|
||||
ASSERT_EQ(Get(Key(7)), keys[7]);
|
||||
secondary_cache->SetResultMap(
|
||||
{{cache_keys[3], TestSecondaryCache::ResultType::DEFER},
|
||||
{cache_keys[4], TestSecondaryCache::ResultType::DEFER_AND_FAIL},
|
||||
{cache_keys[5], TestSecondaryCache::ResultType::FAIL}});
|
||||
|
||||
std::vector<std::string> mget_keys(
|
||||
{Key(0), Key(1), Key(2), Key(3), Key(4), Key(5), Key(6), Key(7)});
|
||||
std::vector<PinnableSlice> values(mget_keys.size());
|
||||
std::vector<Status> s(keys.size());
|
||||
std::vector<Slice> key_slices;
|
||||
for (const std::string& key : mget_keys) {
|
||||
key_slices.emplace_back(key);
|
||||
}
|
||||
uint32_t num_lookups = secondary_cache->num_lookups();
|
||||
dbfull()->MultiGet(ReadOptions(), dbfull()->DefaultColumnFamily(),
|
||||
key_slices.size(), key_slices.data(), values.data(),
|
||||
s.data(), false);
|
||||
ASSERT_EQ(secondary_cache->num_lookups(), num_lookups + 5);
|
||||
for (int i = 0; i < N; ++i) {
|
||||
ASSERT_OK(s[i]);
|
||||
ASSERT_EQ(values[i].ToString(), keys[i]);
|
||||
values[i].Reset();
|
||||
}
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -115,9 +115,10 @@ class ShardedCache : public Cache {
|
||||
int GetNumShardBits() const;
|
||||
uint32_t GetNumShards() const;
|
||||
|
||||
private:
|
||||
protected:
|
||||
inline uint32_t Shard(uint32_t hash) { return hash & shard_mask_; }
|
||||
|
||||
private:
|
||||
const uint32_t shard_mask_;
|
||||
mutable port::Mutex capacity_mutex_;
|
||||
size_t capacity_;
|
||||
|
||||
@@ -31,16 +31,15 @@ Status ArenaWrappedDBIter::GetProperty(std::string prop_name,
|
||||
}
|
||||
|
||||
void ArenaWrappedDBIter::Init(
|
||||
Env* env, const ReadOptions& read_options,
|
||||
const ImmutableOptions& cf_options,
|
||||
Env* env, const ReadOptions& read_options, const ImmutableOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options, const Version* version,
|
||||
const SequenceNumber& sequence, uint64_t max_sequential_skip_in_iteration,
|
||||
uint64_t version_number, ReadCallback* read_callback, DBImpl* db_impl,
|
||||
ColumnFamilyData* cfd, bool expose_blob_index, bool allow_refresh) {
|
||||
auto mem = arena_.AllocateAligned(sizeof(DBIter));
|
||||
db_iter_ =
|
||||
new (mem) DBIter(env, read_options, cf_options, mutable_cf_options,
|
||||
cf_options.user_comparator, /* iter */ nullptr, version,
|
||||
new (mem) DBIter(env, read_options, ioptions, mutable_cf_options,
|
||||
ioptions.user_comparator, /* iter */ nullptr, version,
|
||||
sequence, true, max_sequential_skip_in_iteration,
|
||||
read_callback, db_impl, cfd, expose_blob_index);
|
||||
sv_number_ = version_number;
|
||||
@@ -88,16 +87,15 @@ Status ArenaWrappedDBIter::Refresh() {
|
||||
}
|
||||
|
||||
ArenaWrappedDBIter* NewArenaWrappedDbIterator(
|
||||
Env* env, const ReadOptions& read_options,
|
||||
const ImmutableOptions& cf_options,
|
||||
Env* env, const ReadOptions& read_options, const ImmutableOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options, const Version* version,
|
||||
const SequenceNumber& sequence, uint64_t max_sequential_skip_in_iterations,
|
||||
uint64_t version_number, ReadCallback* read_callback, DBImpl* db_impl,
|
||||
ColumnFamilyData* cfd, bool expose_blob_index, bool allow_refresh) {
|
||||
ArenaWrappedDBIter* iter = new ArenaWrappedDBIter();
|
||||
iter->Init(env, read_options, cf_options, mutable_cf_options, version,
|
||||
sequence, max_sequential_skip_in_iterations, version_number,
|
||||
read_callback, db_impl, cfd, expose_blob_index, allow_refresh);
|
||||
iter->Init(env, read_options, ioptions, mutable_cf_options, version, sequence,
|
||||
max_sequential_skip_in_iterations, version_number, read_callback,
|
||||
db_impl, cfd, expose_blob_index, allow_refresh);
|
||||
if (db_impl != nullptr && cfd != nullptr && allow_refresh) {
|
||||
iter->StoreRefreshInfo(db_impl, cfd, read_callback, expose_blob_index);
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
Status Refresh() override;
|
||||
|
||||
void Init(Env* env, const ReadOptions& read_options,
|
||||
const ImmutableOptions& cf_options,
|
||||
const ImmutableOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options, const Version* version,
|
||||
const SequenceNumber& sequence,
|
||||
uint64_t max_sequential_skip_in_iterations, uint64_t version_number,
|
||||
@@ -105,8 +105,7 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
// `db_impl` and `cfd` are used for reneweal. If left null, renewal will not
|
||||
// be supported.
|
||||
extern ArenaWrappedDBIter* NewArenaWrappedDbIterator(
|
||||
Env* env, const ReadOptions& read_options,
|
||||
const ImmutableOptions& cf_options,
|
||||
Env* env, const ReadOptions& read_options, const ImmutableOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options, const Version* version,
|
||||
const SequenceNumber& sequence, uint64_t max_sequential_skip_in_iterations,
|
||||
uint64_t version_number, ReadCallback* read_callback,
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/blob/blob_fetcher.h"
|
||||
|
||||
#include "db/version_set.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
Status BlobFetcher::FetchBlob(const Slice& user_key, const Slice& blob_index,
|
||||
PinnableSlice* blob_value) {
|
||||
Status s;
|
||||
assert(version_);
|
||||
constexpr uint64_t* bytes_read = nullptr;
|
||||
s = version_->GetBlob(read_options_, user_key, blob_index, blob_value,
|
||||
bytes_read);
|
||||
return s;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
class Version;
|
||||
|
||||
class BlobFetcher {
|
||||
public:
|
||||
BlobFetcher(Version* version, const ReadOptions& read_options)
|
||||
: version_(version), read_options_(read_options) {}
|
||||
|
||||
Status FetchBlob(const Slice& user_key, const Slice& blob_index,
|
||||
PinnableSlice* blob_value);
|
||||
|
||||
private:
|
||||
Version* version_;
|
||||
ReadOptions read_options_;
|
||||
};
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -29,7 +29,7 @@ namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
BlobFileBuilder::BlobFileBuilder(
|
||||
VersionSet* versions, FileSystem* fs,
|
||||
const ImmutableOptions* immutable_cf_options,
|
||||
const ImmutableOptions* immutable_options,
|
||||
const MutableCFOptions* mutable_cf_options, const FileOptions* file_options,
|
||||
int job_id, uint32_t column_family_id,
|
||||
const std::string& column_family_name, Env::IOPriority io_priority,
|
||||
@@ -39,14 +39,14 @@ BlobFileBuilder::BlobFileBuilder(
|
||||
std::vector<std::string>* blob_file_paths,
|
||||
std::vector<BlobFileAddition>* blob_file_additions)
|
||||
: BlobFileBuilder([versions]() { return versions->NewFileNumber(); }, fs,
|
||||
immutable_cf_options, mutable_cf_options, file_options,
|
||||
immutable_options, mutable_cf_options, file_options,
|
||||
job_id, column_family_id, column_family_name, io_priority,
|
||||
write_hint, io_tracer, blob_callback, blob_file_paths,
|
||||
blob_file_additions) {}
|
||||
|
||||
BlobFileBuilder::BlobFileBuilder(
|
||||
std::function<uint64_t()> file_number_generator, FileSystem* fs,
|
||||
const ImmutableOptions* immutable_cf_options,
|
||||
const ImmutableOptions* immutable_options,
|
||||
const MutableCFOptions* mutable_cf_options, const FileOptions* file_options,
|
||||
int job_id, uint32_t column_family_id,
|
||||
const std::string& column_family_name, Env::IOPriority io_priority,
|
||||
@@ -57,7 +57,7 @@ BlobFileBuilder::BlobFileBuilder(
|
||||
std::vector<BlobFileAddition>* blob_file_additions)
|
||||
: file_number_generator_(std::move(file_number_generator)),
|
||||
fs_(fs),
|
||||
immutable_cf_options_(immutable_cf_options),
|
||||
immutable_options_(immutable_options),
|
||||
min_blob_size_(mutable_cf_options->min_blob_size),
|
||||
blob_file_size_(mutable_cf_options->blob_file_size),
|
||||
blob_compression_type_(mutable_cf_options->blob_compression_type),
|
||||
@@ -75,7 +75,7 @@ BlobFileBuilder::BlobFileBuilder(
|
||||
blob_bytes_(0) {
|
||||
assert(file_number_generator_);
|
||||
assert(fs_);
|
||||
assert(immutable_cf_options_);
|
||||
assert(immutable_options_);
|
||||
assert(file_options_);
|
||||
assert(blob_file_paths_);
|
||||
assert(blob_file_paths_->empty());
|
||||
@@ -156,10 +156,10 @@ Status BlobFileBuilder::OpenBlobFileIfNeeded() {
|
||||
assert(file_number_generator_);
|
||||
const uint64_t blob_file_number = file_number_generator_();
|
||||
|
||||
assert(immutable_cf_options_);
|
||||
assert(!immutable_cf_options_->cf_paths.empty());
|
||||
std::string blob_file_path = BlobFileName(
|
||||
immutable_cf_options_->cf_paths.front().path, blob_file_number);
|
||||
assert(immutable_options_);
|
||||
assert(!immutable_options_->cf_paths.empty());
|
||||
std::string blob_file_path =
|
||||
BlobFileName(immutable_options_->cf_paths.front().path, blob_file_number);
|
||||
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
|
||||
@@ -184,20 +184,20 @@ Status BlobFileBuilder::OpenBlobFileIfNeeded() {
|
||||
assert(file);
|
||||
file->SetIOPriority(io_priority_);
|
||||
file->SetWriteLifeTimeHint(write_hint_);
|
||||
FileTypeSet tmp_set = immutable_cf_options_->checksum_handoff_file_types;
|
||||
Statistics* const statistics = immutable_cf_options_->stats;
|
||||
FileTypeSet tmp_set = immutable_options_->checksum_handoff_file_types;
|
||||
Statistics* const statistics = immutable_options_->stats;
|
||||
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
|
||||
std::move(file), blob_file_paths_->back(), *file_options_,
|
||||
immutable_cf_options_->clock, io_tracer_, statistics,
|
||||
immutable_cf_options_->listeners,
|
||||
immutable_cf_options_->file_checksum_gen_factory.get(),
|
||||
immutable_options_->clock, io_tracer_, statistics,
|
||||
immutable_options_->listeners,
|
||||
immutable_options_->file_checksum_gen_factory.get(),
|
||||
tmp_set.Contains(FileType::kBlobFile)));
|
||||
|
||||
constexpr bool do_flush = false;
|
||||
|
||||
std::unique_ptr<BlobLogWriter> blob_log_writer(new BlobLogWriter(
|
||||
std::move(file_writer), immutable_cf_options_->clock, statistics,
|
||||
blob_file_number, immutable_cf_options_->use_fsync, do_flush));
|
||||
std::move(file_writer), immutable_options_->clock, statistics,
|
||||
blob_file_number, immutable_options_->use_fsync, do_flush));
|
||||
|
||||
constexpr bool has_ttl = false;
|
||||
constexpr ExpirationRange expiration_range;
|
||||
@@ -300,8 +300,8 @@ Status BlobFileBuilder::CloseBlobFile() {
|
||||
std::move(checksum_method),
|
||||
std::move(checksum_value));
|
||||
|
||||
assert(immutable_cf_options_);
|
||||
ROCKS_LOG_INFO(immutable_cf_options_->logger,
|
||||
assert(immutable_options_);
|
||||
ROCKS_LOG_INFO(immutable_options_->logger,
|
||||
"[%s] [JOB %d] Generated blob file #%" PRIu64 ": %" PRIu64
|
||||
" total blobs, %" PRIu64 " total bytes",
|
||||
column_family_name_.c_str(), job_id_, blob_file_number,
|
||||
|
||||
@@ -77,7 +77,7 @@ class BlobFileBuilder {
|
||||
|
||||
std::function<uint64_t()> file_number_generator_;
|
||||
FileSystem* fs_;
|
||||
const ImmutableOptions* immutable_cf_options_;
|
||||
const ImmutableOptions* immutable_options_;
|
||||
uint64_t min_blob_size_;
|
||||
uint64_t blob_file_size_;
|
||||
CompressionType blob_compression_type_;
|
||||
|
||||
@@ -129,7 +129,7 @@ TEST_F(BlobFileBuilderTest, BuildAndCheckOneFile) {
|
||||
options.enable_blob_files = true;
|
||||
options.env = &mock_env_;
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
|
||||
constexpr int job_id = 1;
|
||||
@@ -141,12 +141,11 @@ TEST_F(BlobFileBuilderTest, BuildAndCheckOneFile) {
|
||||
std::vector<std::string> blob_file_paths;
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
|
||||
BlobFileBuilder builder(TestFileNumberGenerator(), fs_, &immutable_cf_options,
|
||||
&mutable_cf_options, &file_options_, job_id,
|
||||
column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/,
|
||||
nullptr /*BlobFileCompletionCallback*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
BlobFileBuilder builder(
|
||||
TestFileNumberGenerator(), fs_, &immutable_options, &mutable_cf_options,
|
||||
&file_options_, job_id, column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> expected_key_value_pairs(
|
||||
number_of_blobs);
|
||||
@@ -178,9 +177,9 @@ TEST_F(BlobFileBuilderTest, BuildAndCheckOneFile) {
|
||||
|
||||
const std::string& blob_file_path = blob_file_paths[0];
|
||||
|
||||
ASSERT_EQ(blob_file_path,
|
||||
BlobFileName(immutable_cf_options.cf_paths.front().path,
|
||||
blob_file_number));
|
||||
ASSERT_EQ(
|
||||
blob_file_path,
|
||||
BlobFileName(immutable_options.cf_paths.front().path, blob_file_number));
|
||||
|
||||
ASSERT_EQ(blob_file_additions.size(), 1);
|
||||
|
||||
@@ -214,7 +213,7 @@ TEST_F(BlobFileBuilderTest, BuildAndCheckMultipleFiles) {
|
||||
options.blob_file_size = value_size;
|
||||
options.env = &mock_env_;
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
|
||||
constexpr int job_id = 1;
|
||||
@@ -226,12 +225,11 @@ TEST_F(BlobFileBuilderTest, BuildAndCheckMultipleFiles) {
|
||||
std::vector<std::string> blob_file_paths;
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
|
||||
BlobFileBuilder builder(TestFileNumberGenerator(), fs_, &immutable_cf_options,
|
||||
&mutable_cf_options, &file_options_, job_id,
|
||||
column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/,
|
||||
nullptr /*BlobFileCompletionCallback*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
BlobFileBuilder builder(
|
||||
TestFileNumberGenerator(), fs_, &immutable_options, &mutable_cf_options,
|
||||
&file_options_, job_id, column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> expected_key_value_pairs(
|
||||
number_of_blobs);
|
||||
@@ -264,7 +262,7 @@ TEST_F(BlobFileBuilderTest, BuildAndCheckMultipleFiles) {
|
||||
const uint64_t blob_file_number = i + 2;
|
||||
|
||||
ASSERT_EQ(blob_file_paths[i],
|
||||
BlobFileName(immutable_cf_options.cf_paths.front().path,
|
||||
BlobFileName(immutable_options.cf_paths.front().path,
|
||||
blob_file_number));
|
||||
|
||||
const auto& blob_file_addition = blob_file_additions[i];
|
||||
@@ -301,7 +299,7 @@ TEST_F(BlobFileBuilderTest, InlinedValues) {
|
||||
options.min_blob_size = 1024;
|
||||
options.env = &mock_env_;
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
|
||||
constexpr int job_id = 1;
|
||||
@@ -313,12 +311,11 @@ TEST_F(BlobFileBuilderTest, InlinedValues) {
|
||||
std::vector<std::string> blob_file_paths;
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
|
||||
BlobFileBuilder builder(TestFileNumberGenerator(), fs_, &immutable_cf_options,
|
||||
&mutable_cf_options, &file_options_, job_id,
|
||||
column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/,
|
||||
nullptr /*BlobFileCompletionCallback*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
BlobFileBuilder builder(
|
||||
TestFileNumberGenerator(), fs_, &immutable_options, &mutable_cf_options,
|
||||
&file_options_, job_id, column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
|
||||
for (size_t i = 0; i < number_of_blobs; ++i) {
|
||||
const std::string key = std::to_string(i);
|
||||
@@ -355,7 +352,7 @@ TEST_F(BlobFileBuilderTest, Compression) {
|
||||
options.blob_compression_type = kSnappyCompression;
|
||||
options.env = &mock_env_;
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
|
||||
constexpr int job_id = 1;
|
||||
@@ -367,12 +364,11 @@ TEST_F(BlobFileBuilderTest, Compression) {
|
||||
std::vector<std::string> blob_file_paths;
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
|
||||
BlobFileBuilder builder(TestFileNumberGenerator(), fs_, &immutable_cf_options,
|
||||
&mutable_cf_options, &file_options_, job_id,
|
||||
column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/,
|
||||
nullptr /*BlobFileCompletionCallback*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
BlobFileBuilder builder(
|
||||
TestFileNumberGenerator(), fs_, &immutable_options, &mutable_cf_options,
|
||||
&file_options_, job_id, column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
|
||||
const std::string key("1");
|
||||
const std::string uncompressed_value(value_size, 'x');
|
||||
@@ -391,9 +387,9 @@ TEST_F(BlobFileBuilderTest, Compression) {
|
||||
|
||||
const std::string& blob_file_path = blob_file_paths[0];
|
||||
|
||||
ASSERT_EQ(blob_file_path,
|
||||
BlobFileName(immutable_cf_options.cf_paths.front().path,
|
||||
blob_file_number));
|
||||
ASSERT_EQ(
|
||||
blob_file_path,
|
||||
BlobFileName(immutable_options.cf_paths.front().path, blob_file_number));
|
||||
|
||||
ASSERT_EQ(blob_file_additions.size(), 1);
|
||||
|
||||
@@ -438,7 +434,7 @@ TEST_F(BlobFileBuilderTest, CompressionError) {
|
||||
options.enable_blob_files = true;
|
||||
options.blob_compression_type = kSnappyCompression;
|
||||
options.env = &mock_env_;
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
|
||||
constexpr int job_id = 1;
|
||||
@@ -450,12 +446,11 @@ TEST_F(BlobFileBuilderTest, CompressionError) {
|
||||
std::vector<std::string> blob_file_paths;
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
|
||||
BlobFileBuilder builder(TestFileNumberGenerator(), fs_, &immutable_cf_options,
|
||||
&mutable_cf_options, &file_options_, job_id,
|
||||
column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/,
|
||||
nullptr /*BlobFileCompletionCallback*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
BlobFileBuilder builder(
|
||||
TestFileNumberGenerator(), fs_, &immutable_options, &mutable_cf_options,
|
||||
&file_options_, job_id, column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack("CompressData:TamperWithReturnValue",
|
||||
[](void* arg) {
|
||||
@@ -477,9 +472,9 @@ TEST_F(BlobFileBuilderTest, CompressionError) {
|
||||
constexpr uint64_t blob_file_number = 2;
|
||||
|
||||
ASSERT_EQ(blob_file_paths.size(), 1);
|
||||
ASSERT_EQ(blob_file_paths[0],
|
||||
BlobFileName(immutable_cf_options.cf_paths.front().path,
|
||||
blob_file_number));
|
||||
ASSERT_EQ(
|
||||
blob_file_paths[0],
|
||||
BlobFileName(immutable_options.cf_paths.front().path, blob_file_number));
|
||||
|
||||
ASSERT_TRUE(blob_file_additions.empty());
|
||||
}
|
||||
@@ -517,7 +512,7 @@ TEST_F(BlobFileBuilderTest, Checksum) {
|
||||
std::make_shared<DummyFileChecksumGenFactory>();
|
||||
options.env = &mock_env_;
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
|
||||
constexpr int job_id = 1;
|
||||
@@ -529,12 +524,11 @@ TEST_F(BlobFileBuilderTest, Checksum) {
|
||||
std::vector<std::string> blob_file_paths;
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
|
||||
BlobFileBuilder builder(TestFileNumberGenerator(), fs_, &immutable_cf_options,
|
||||
&mutable_cf_options, &file_options_, job_id,
|
||||
column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/,
|
||||
nullptr /*BlobFileCompletionCallback*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
BlobFileBuilder builder(
|
||||
TestFileNumberGenerator(), fs_, &immutable_options, &mutable_cf_options,
|
||||
&file_options_, job_id, column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
|
||||
const std::string key("1");
|
||||
const std::string value("deadbeef");
|
||||
@@ -553,9 +547,9 @@ TEST_F(BlobFileBuilderTest, Checksum) {
|
||||
|
||||
const std::string& blob_file_path = blob_file_paths[0];
|
||||
|
||||
ASSERT_EQ(blob_file_path,
|
||||
BlobFileName(immutable_cf_options.cf_paths.front().path,
|
||||
blob_file_number));
|
||||
ASSERT_EQ(
|
||||
blob_file_path,
|
||||
BlobFileName(immutable_options.cf_paths.front().path, blob_file_number));
|
||||
|
||||
ASSERT_EQ(blob_file_additions.size(), 1);
|
||||
|
||||
@@ -614,7 +608,7 @@ TEST_P(BlobFileBuilderIOErrorTest, IOError) {
|
||||
options.blob_file_size = value_size;
|
||||
options.env = &mock_env_;
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
|
||||
constexpr int job_id = 1;
|
||||
@@ -626,12 +620,11 @@ TEST_P(BlobFileBuilderIOErrorTest, IOError) {
|
||||
std::vector<std::string> blob_file_paths;
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
|
||||
BlobFileBuilder builder(TestFileNumberGenerator(), fs_, &immutable_cf_options,
|
||||
&mutable_cf_options, &file_options_, job_id,
|
||||
column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/,
|
||||
nullptr /*BlobFileCompletionCallback*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
BlobFileBuilder builder(
|
||||
TestFileNumberGenerator(), fs_, &immutable_options, &mutable_cf_options,
|
||||
&file_options_, job_id, column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(sync_point_, [this](void* arg) {
|
||||
Status* const s = static_cast<Status*>(arg);
|
||||
@@ -658,7 +651,7 @@ TEST_P(BlobFileBuilderIOErrorTest, IOError) {
|
||||
|
||||
ASSERT_EQ(blob_file_paths.size(), 1);
|
||||
ASSERT_EQ(blob_file_paths[0],
|
||||
BlobFileName(immutable_cf_options.cf_paths.front().path,
|
||||
BlobFileName(immutable_options.cf_paths.front().path,
|
||||
blob_file_number));
|
||||
}
|
||||
|
||||
|
||||
@@ -26,13 +26,13 @@ BlobFileCache::BlobFileCache(Cache* cache,
|
||||
const std::shared_ptr<IOTracer>& io_tracer)
|
||||
: cache_(cache),
|
||||
mutex_(kNumberOfMutexStripes, kGetSliceNPHash64UnseededFnPtr),
|
||||
immutable_cf_options_(immutable_options),
|
||||
immutable_options_(immutable_options),
|
||||
file_options_(file_options),
|
||||
column_family_id_(column_family_id),
|
||||
blob_file_read_hist_(blob_file_read_hist),
|
||||
io_tracer_(io_tracer) {
|
||||
assert(cache_);
|
||||
assert(immutable_cf_options_);
|
||||
assert(immutable_options_);
|
||||
assert(file_options_);
|
||||
}
|
||||
|
||||
@@ -63,8 +63,8 @@ Status BlobFileCache::GetBlobFileReader(
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
assert(immutable_cf_options_);
|
||||
Statistics* const statistics = immutable_cf_options_->stats;
|
||||
assert(immutable_options_);
|
||||
Statistics* const statistics = immutable_options_->stats;
|
||||
|
||||
RecordTick(statistics, NO_FILE_OPENS);
|
||||
|
||||
@@ -73,7 +73,7 @@ Status BlobFileCache::GetBlobFileReader(
|
||||
{
|
||||
assert(file_options_);
|
||||
const Status s = BlobFileReader::Create(
|
||||
*immutable_cf_options_, *file_options_, column_family_id_,
|
||||
*immutable_options_, *file_options_, column_family_id_,
|
||||
blob_file_read_hist_, blob_file_number, io_tracer_, &reader);
|
||||
if (!s.ok()) {
|
||||
RecordTick(statistics, NO_FILE_ERRORS);
|
||||
|
||||
@@ -40,7 +40,7 @@ class BlobFileCache {
|
||||
// Note: mutex_ below is used to guard against multiple threads racing to open
|
||||
// the same file.
|
||||
Striped<port::Mutex, Slice> mutex_;
|
||||
const ImmutableOptions* immutable_cf_options_;
|
||||
const ImmutableOptions* immutable_options_;
|
||||
const FileOptions* file_options_;
|
||||
uint32_t column_family_id_;
|
||||
HistogramImpl* blob_file_read_hist_;
|
||||
|
||||
@@ -29,28 +29,27 @@ namespace {
|
||||
|
||||
// Creates a test blob file with a single blob in it.
|
||||
void WriteBlobFile(uint32_t column_family_id,
|
||||
const ImmutableOptions& immutable_cf_options,
|
||||
const ImmutableOptions& immutable_options,
|
||||
uint64_t blob_file_number) {
|
||||
assert(!immutable_cf_options.cf_paths.empty());
|
||||
assert(!immutable_options.cf_paths.empty());
|
||||
|
||||
const std::string blob_file_path = BlobFileName(
|
||||
immutable_cf_options.cf_paths.front().path, blob_file_number);
|
||||
const std::string blob_file_path =
|
||||
BlobFileName(immutable_options.cf_paths.front().path, blob_file_number);
|
||||
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
ASSERT_OK(NewWritableFile(immutable_cf_options.fs.get(), blob_file_path,
|
||||
&file, FileOptions()));
|
||||
ASSERT_OK(NewWritableFile(immutable_options.fs.get(), blob_file_path, &file,
|
||||
FileOptions()));
|
||||
|
||||
std::unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(file), blob_file_path, FileOptions(),
|
||||
immutable_cf_options.clock));
|
||||
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
|
||||
std::move(file), blob_file_path, FileOptions(), immutable_options.clock));
|
||||
|
||||
constexpr Statistics* statistics = nullptr;
|
||||
constexpr bool use_fsync = false;
|
||||
constexpr bool do_flush = false;
|
||||
|
||||
BlobLogWriter blob_log_writer(std::move(file_writer),
|
||||
immutable_cf_options.clock, statistics,
|
||||
blob_file_number, use_fsync, do_flush);
|
||||
BlobLogWriter blob_log_writer(std::move(file_writer), immutable_options.clock,
|
||||
statistics, blob_file_number, use_fsync,
|
||||
do_flush);
|
||||
|
||||
constexpr bool has_ttl = false;
|
||||
constexpr ExpirationRange expiration_range;
|
||||
@@ -100,10 +99,10 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader) {
|
||||
options.enable_blob_files = true;
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
constexpr uint64_t blob_file_number = 123;
|
||||
|
||||
WriteBlobFile(column_family_id, immutable_cf_options, blob_file_number);
|
||||
WriteBlobFile(column_family_id, immutable_options, blob_file_number);
|
||||
|
||||
constexpr size_t capacity = 10;
|
||||
std::shared_ptr<Cache> backing_cache = NewLRUCache(capacity);
|
||||
@@ -111,7 +110,7 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader) {
|
||||
FileOptions file_options;
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_cf_options,
|
||||
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_options,
|
||||
&file_options, column_family_id,
|
||||
blob_file_read_hist, nullptr /*IOTracer*/);
|
||||
|
||||
@@ -145,10 +144,10 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_Race) {
|
||||
options.enable_blob_files = true;
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
constexpr uint64_t blob_file_number = 123;
|
||||
|
||||
WriteBlobFile(column_family_id, immutable_cf_options, blob_file_number);
|
||||
WriteBlobFile(column_family_id, immutable_options, blob_file_number);
|
||||
|
||||
constexpr size_t capacity = 10;
|
||||
std::shared_ptr<Cache> backing_cache = NewLRUCache(capacity);
|
||||
@@ -156,7 +155,7 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_Race) {
|
||||
FileOptions file_options;
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_cf_options,
|
||||
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_options,
|
||||
&file_options, column_family_id,
|
||||
blob_file_read_hist, nullptr /*IOTracer*/);
|
||||
|
||||
@@ -199,12 +198,12 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_IOError) {
|
||||
constexpr size_t capacity = 10;
|
||||
std::shared_ptr<Cache> backing_cache = NewLRUCache(capacity);
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
FileOptions file_options;
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_cf_options,
|
||||
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_options,
|
||||
&file_options, column_family_id,
|
||||
blob_file_read_hist, nullptr /*IOTracer*/);
|
||||
|
||||
@@ -231,10 +230,10 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_CacheFull) {
|
||||
options.enable_blob_files = true;
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
constexpr uint64_t blob_file_number = 123;
|
||||
|
||||
WriteBlobFile(column_family_id, immutable_cf_options, blob_file_number);
|
||||
WriteBlobFile(column_family_id, immutable_options, blob_file_number);
|
||||
|
||||
constexpr size_t capacity = 0;
|
||||
constexpr int num_shard_bits = -1; // determined automatically
|
||||
@@ -245,7 +244,7 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_CacheFull) {
|
||||
FileOptions file_options;
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_cf_options,
|
||||
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_options,
|
||||
&file_options, column_family_id,
|
||||
blob_file_read_hist, nullptr /*IOTracer*/);
|
||||
|
||||
|
||||
@@ -21,10 +21,9 @@
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
Status BlobFileReader::Create(
|
||||
const ImmutableOptions& immutable_cf_options,
|
||||
const FileOptions& file_options, uint32_t column_family_id,
|
||||
HistogramImpl* blob_file_read_hist, uint64_t blob_file_number,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const ImmutableOptions& immutable_options, const FileOptions& file_options,
|
||||
uint32_t column_family_id, HistogramImpl* blob_file_read_hist,
|
||||
uint64_t blob_file_number, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
std::unique_ptr<BlobFileReader>* blob_file_reader) {
|
||||
assert(blob_file_reader);
|
||||
assert(!*blob_file_reader);
|
||||
@@ -34,7 +33,7 @@ Status BlobFileReader::Create(
|
||||
|
||||
{
|
||||
const Status s =
|
||||
OpenFile(immutable_cf_options, file_options, blob_file_read_hist,
|
||||
OpenFile(immutable_options, file_options, blob_file_read_hist,
|
||||
blob_file_number, io_tracer, &file_size, &file_reader);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -67,20 +66,20 @@ Status BlobFileReader::Create(
|
||||
}
|
||||
|
||||
Status BlobFileReader::OpenFile(
|
||||
const ImmutableOptions& immutable_cf_options, const FileOptions& file_opts,
|
||||
const ImmutableOptions& immutable_options, const FileOptions& file_opts,
|
||||
HistogramImpl* blob_file_read_hist, uint64_t blob_file_number,
|
||||
const std::shared_ptr<IOTracer>& io_tracer, uint64_t* file_size,
|
||||
std::unique_ptr<RandomAccessFileReader>* file_reader) {
|
||||
assert(file_size);
|
||||
assert(file_reader);
|
||||
|
||||
const auto& cf_paths = immutable_cf_options.cf_paths;
|
||||
const auto& cf_paths = immutable_options.cf_paths;
|
||||
assert(!cf_paths.empty());
|
||||
|
||||
const std::string blob_file_path =
|
||||
BlobFileName(cf_paths.front().path, blob_file_number);
|
||||
|
||||
FileSystem* const fs = immutable_cf_options.fs.get();
|
||||
FileSystem* const fs = immutable_options.fs.get();
|
||||
assert(fs);
|
||||
|
||||
constexpr IODebugContext* dbg = nullptr;
|
||||
@@ -113,15 +112,15 @@ Status BlobFileReader::OpenFile(
|
||||
|
||||
assert(file);
|
||||
|
||||
if (immutable_cf_options.advise_random_on_open) {
|
||||
if (immutable_options.advise_random_on_open) {
|
||||
file->Hint(FSRandomAccessFile::kRandom);
|
||||
}
|
||||
|
||||
file_reader->reset(new RandomAccessFileReader(
|
||||
std::move(file), blob_file_path, immutable_cf_options.clock, io_tracer,
|
||||
immutable_cf_options.stats, BLOB_DB_BLOB_FILE_READ_MICROS,
|
||||
blob_file_read_hist, immutable_cf_options.rate_limiter.get(),
|
||||
immutable_cf_options.listeners));
|
||||
std::move(file), blob_file_path, immutable_options.clock, io_tracer,
|
||||
immutable_options.stats, BLOB_DB_BLOB_FILE_READ_MICROS,
|
||||
blob_file_read_hist, immutable_options.rate_limiter.get(),
|
||||
immutable_options.listeners));
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -30,35 +30,34 @@ namespace {
|
||||
// Creates a test blob file with a single blob in it. Note: this method
|
||||
// makes it possible to test various corner cases by allowing the caller
|
||||
// to specify the contents of various blob file header/footer fields.
|
||||
void WriteBlobFile(const ImmutableOptions& immutable_cf_options,
|
||||
void WriteBlobFile(const ImmutableOptions& immutable_options,
|
||||
uint32_t column_family_id, bool has_ttl,
|
||||
const ExpirationRange& expiration_range_header,
|
||||
const ExpirationRange& expiration_range_footer,
|
||||
uint64_t blob_file_number, const Slice& key,
|
||||
const Slice& blob, CompressionType compression_type,
|
||||
uint64_t* blob_offset, uint64_t* blob_size) {
|
||||
assert(!immutable_cf_options.cf_paths.empty());
|
||||
assert(!immutable_options.cf_paths.empty());
|
||||
assert(blob_offset);
|
||||
assert(blob_size);
|
||||
|
||||
const std::string blob_file_path = BlobFileName(
|
||||
immutable_cf_options.cf_paths.front().path, blob_file_number);
|
||||
const std::string blob_file_path =
|
||||
BlobFileName(immutable_options.cf_paths.front().path, blob_file_number);
|
||||
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
ASSERT_OK(NewWritableFile(immutable_cf_options.fs.get(), blob_file_path,
|
||||
&file, FileOptions()));
|
||||
ASSERT_OK(NewWritableFile(immutable_options.fs.get(), blob_file_path, &file,
|
||||
FileOptions()));
|
||||
|
||||
std::unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(file), blob_file_path, FileOptions(),
|
||||
immutable_cf_options.clock));
|
||||
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
|
||||
std::move(file), blob_file_path, FileOptions(), immutable_options.clock));
|
||||
|
||||
constexpr Statistics* statistics = nullptr;
|
||||
constexpr bool use_fsync = false;
|
||||
constexpr bool do_flush = false;
|
||||
|
||||
BlobLogWriter blob_log_writer(std::move(file_writer),
|
||||
immutable_cf_options.clock, statistics,
|
||||
blob_file_number, use_fsync, do_flush);
|
||||
BlobLogWriter blob_log_writer(std::move(file_writer), immutable_options.clock,
|
||||
statistics, blob_file_number, use_fsync,
|
||||
do_flush);
|
||||
|
||||
BlobLogHeader header(column_family_id, compression_type, has_ttl,
|
||||
expiration_range_header);
|
||||
@@ -122,7 +121,7 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -134,17 +133,17 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
uint64_t blob_offset = 0;
|
||||
uint64_t blob_size = 0;
|
||||
|
||||
WriteBlobFile(immutable_cf_options, column_family_id, has_ttl,
|
||||
expiration_range, expiration_range, blob_file_number, key, blob,
|
||||
kNoCompression, &blob_offset, &blob_size);
|
||||
WriteBlobFile(immutable_options, column_family_id, has_ttl, expiration_range,
|
||||
expiration_range, blob_file_number, key, blob, kNoCompression,
|
||||
&blob_offset, &blob_size);
|
||||
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
ASSERT_OK(BlobFileReader::Create(
|
||||
immutable_cf_options, FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
immutable_options, FileOptions(), column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
|
||||
// Make sure the blob can be retrieved with and without checksum verification
|
||||
ReadOptions read_options;
|
||||
@@ -262,7 +261,7 @@ TEST_F(BlobFileReaderTest, Malformed) {
|
||||
test::PerThreadDBPath(&mock_env_, "BlobFileReaderTest_Malformed"), 0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr uint64_t blob_file_number = 1;
|
||||
@@ -271,23 +270,23 @@ TEST_F(BlobFileReaderTest, Malformed) {
|
||||
constexpr bool has_ttl = false;
|
||||
constexpr ExpirationRange expiration_range;
|
||||
|
||||
const std::string blob_file_path = BlobFileName(
|
||||
immutable_cf_options.cf_paths.front().path, blob_file_number);
|
||||
const std::string blob_file_path =
|
||||
BlobFileName(immutable_options.cf_paths.front().path, blob_file_number);
|
||||
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
ASSERT_OK(NewWritableFile(immutable_cf_options.fs.get(), blob_file_path,
|
||||
&file, FileOptions()));
|
||||
ASSERT_OK(NewWritableFile(immutable_options.fs.get(), blob_file_path, &file,
|
||||
FileOptions()));
|
||||
|
||||
std::unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(file), blob_file_path, FileOptions(),
|
||||
immutable_cf_options.clock));
|
||||
immutable_options.clock));
|
||||
|
||||
constexpr Statistics* statistics = nullptr;
|
||||
constexpr bool use_fsync = false;
|
||||
constexpr bool do_flush = false;
|
||||
|
||||
BlobLogWriter blob_log_writer(std::move(file_writer),
|
||||
immutable_cf_options.clock, statistics,
|
||||
immutable_options.clock, statistics,
|
||||
blob_file_number, use_fsync, do_flush);
|
||||
|
||||
BlobLogHeader header(column_family_id, kNoCompression, has_ttl,
|
||||
@@ -300,7 +299,7 @@ TEST_F(BlobFileReaderTest, Malformed) {
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_cf_options, FileOptions(),
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_options, FileOptions(),
|
||||
column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/,
|
||||
&reader)
|
||||
@@ -314,7 +313,7 @@ TEST_F(BlobFileReaderTest, TTL) {
|
||||
test::PerThreadDBPath(&mock_env_, "BlobFileReaderTest_TTL"), 0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = true;
|
||||
@@ -326,15 +325,15 @@ TEST_F(BlobFileReaderTest, TTL) {
|
||||
uint64_t blob_offset = 0;
|
||||
uint64_t blob_size = 0;
|
||||
|
||||
WriteBlobFile(immutable_cf_options, column_family_id, has_ttl,
|
||||
expiration_range, expiration_range, blob_file_number, key, blob,
|
||||
kNoCompression, &blob_offset, &blob_size);
|
||||
WriteBlobFile(immutable_options, column_family_id, has_ttl, expiration_range,
|
||||
expiration_range, blob_file_number, key, blob, kNoCompression,
|
||||
&blob_offset, &blob_size);
|
||||
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_cf_options, FileOptions(),
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_options, FileOptions(),
|
||||
column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/,
|
||||
&reader)
|
||||
@@ -350,7 +349,7 @@ TEST_F(BlobFileReaderTest, ExpirationRangeInHeader) {
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -364,7 +363,7 @@ TEST_F(BlobFileReaderTest, ExpirationRangeInHeader) {
|
||||
uint64_t blob_offset = 0;
|
||||
uint64_t blob_size = 0;
|
||||
|
||||
WriteBlobFile(immutable_cf_options, column_family_id, has_ttl,
|
||||
WriteBlobFile(immutable_options, column_family_id, has_ttl,
|
||||
expiration_range_header, expiration_range_footer,
|
||||
blob_file_number, key, blob, kNoCompression, &blob_offset,
|
||||
&blob_size);
|
||||
@@ -373,7 +372,7 @@ TEST_F(BlobFileReaderTest, ExpirationRangeInHeader) {
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_cf_options, FileOptions(),
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_options, FileOptions(),
|
||||
column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/,
|
||||
&reader)
|
||||
@@ -389,7 +388,7 @@ TEST_F(BlobFileReaderTest, ExpirationRangeInFooter) {
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -403,7 +402,7 @@ TEST_F(BlobFileReaderTest, ExpirationRangeInFooter) {
|
||||
uint64_t blob_offset = 0;
|
||||
uint64_t blob_size = 0;
|
||||
|
||||
WriteBlobFile(immutable_cf_options, column_family_id, has_ttl,
|
||||
WriteBlobFile(immutable_options, column_family_id, has_ttl,
|
||||
expiration_range_header, expiration_range_footer,
|
||||
blob_file_number, key, blob, kNoCompression, &blob_offset,
|
||||
&blob_size);
|
||||
@@ -412,7 +411,7 @@ TEST_F(BlobFileReaderTest, ExpirationRangeInFooter) {
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_cf_options, FileOptions(),
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_options, FileOptions(),
|
||||
column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/,
|
||||
&reader)
|
||||
@@ -428,7 +427,7 @@ TEST_F(BlobFileReaderTest, IncorrectColumnFamily) {
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -440,9 +439,9 @@ TEST_F(BlobFileReaderTest, IncorrectColumnFamily) {
|
||||
uint64_t blob_offset = 0;
|
||||
uint64_t blob_size = 0;
|
||||
|
||||
WriteBlobFile(immutable_cf_options, column_family_id, has_ttl,
|
||||
expiration_range, expiration_range, blob_file_number, key, blob,
|
||||
kNoCompression, &blob_offset, &blob_size);
|
||||
WriteBlobFile(immutable_options, column_family_id, has_ttl, expiration_range,
|
||||
expiration_range, blob_file_number, key, blob, kNoCompression,
|
||||
&blob_offset, &blob_size);
|
||||
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
@@ -450,7 +449,7 @@ TEST_F(BlobFileReaderTest, IncorrectColumnFamily) {
|
||||
|
||||
constexpr uint32_t incorrect_column_family_id = 2;
|
||||
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_cf_options, FileOptions(),
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_options, FileOptions(),
|
||||
incorrect_column_family_id,
|
||||
blob_file_read_hist, blob_file_number,
|
||||
nullptr /*IOTracer*/, &reader)
|
||||
@@ -464,7 +463,7 @@ TEST_F(BlobFileReaderTest, BlobCRCError) {
|
||||
test::PerThreadDBPath(&mock_env_, "BlobFileReaderTest_BlobCRCError"), 0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -476,17 +475,17 @@ TEST_F(BlobFileReaderTest, BlobCRCError) {
|
||||
uint64_t blob_offset = 0;
|
||||
uint64_t blob_size = 0;
|
||||
|
||||
WriteBlobFile(immutable_cf_options, column_family_id, has_ttl,
|
||||
expiration_range, expiration_range, blob_file_number, key, blob,
|
||||
kNoCompression, &blob_offset, &blob_size);
|
||||
WriteBlobFile(immutable_options, column_family_id, has_ttl, expiration_range,
|
||||
expiration_range, blob_file_number, key, blob, kNoCompression,
|
||||
&blob_offset, &blob_size);
|
||||
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
ASSERT_OK(BlobFileReader::Create(
|
||||
immutable_cf_options, FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
immutable_options, FileOptions(), column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlobFileReader::VerifyBlob:CheckBlobCRC", [](void* arg) {
|
||||
@@ -522,7 +521,7 @@ TEST_F(BlobFileReaderTest, Compression) {
|
||||
test::PerThreadDBPath(&mock_env_, "BlobFileReaderTest_Compression"), 0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -534,8 +533,8 @@ TEST_F(BlobFileReaderTest, Compression) {
|
||||
uint64_t blob_offset = 0;
|
||||
uint64_t blob_size = 0;
|
||||
|
||||
WriteBlobFile(immutable_cf_options, column_family_id, has_ttl,
|
||||
expiration_range, expiration_range, blob_file_number, key, blob,
|
||||
WriteBlobFile(immutable_options, column_family_id, has_ttl, expiration_range,
|
||||
expiration_range, blob_file_number, key, blob,
|
||||
kSnappyCompression, &blob_offset, &blob_size);
|
||||
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
@@ -543,8 +542,8 @@ TEST_F(BlobFileReaderTest, Compression) {
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
ASSERT_OK(BlobFileReader::Create(
|
||||
immutable_cf_options, FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
immutable_options, FileOptions(), column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
|
||||
// Make sure the blob can be retrieved with and without checksum verification
|
||||
ReadOptions read_options;
|
||||
@@ -590,7 +589,7 @@ TEST_F(BlobFileReaderTest, UncompressionError) {
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -602,8 +601,8 @@ TEST_F(BlobFileReaderTest, UncompressionError) {
|
||||
uint64_t blob_offset = 0;
|
||||
uint64_t blob_size = 0;
|
||||
|
||||
WriteBlobFile(immutable_cf_options, column_family_id, has_ttl,
|
||||
expiration_range, expiration_range, blob_file_number, key, blob,
|
||||
WriteBlobFile(immutable_options, column_family_id, has_ttl, expiration_range,
|
||||
expiration_range, blob_file_number, key, blob,
|
||||
kSnappyCompression, &blob_offset, &blob_size);
|
||||
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
@@ -611,8 +610,8 @@ TEST_F(BlobFileReaderTest, UncompressionError) {
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
ASSERT_OK(BlobFileReader::Create(
|
||||
immutable_cf_options, FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
immutable_options, FileOptions(), column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlobFileReader::UncompressBlobIfNeeded:TamperWithResult", [](void* arg) {
|
||||
@@ -671,7 +670,7 @@ TEST_P(BlobFileReaderIOErrorTest, IOError) {
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -683,9 +682,9 @@ TEST_P(BlobFileReaderIOErrorTest, IOError) {
|
||||
uint64_t blob_offset = 0;
|
||||
uint64_t blob_size = 0;
|
||||
|
||||
WriteBlobFile(immutable_cf_options, column_family_id, has_ttl,
|
||||
expiration_range, expiration_range, blob_file_number, key, blob,
|
||||
kNoCompression, &blob_offset, &blob_size);
|
||||
WriteBlobFile(immutable_options, column_family_id, has_ttl, expiration_range,
|
||||
expiration_range, blob_file_number, key, blob, kNoCompression,
|
||||
&blob_offset, &blob_size);
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(sync_point_, [this](void* /* arg */) {
|
||||
fault_injection_env_.SetFilesystemActive(false,
|
||||
@@ -698,8 +697,8 @@ TEST_P(BlobFileReaderIOErrorTest, IOError) {
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
const Status s = BlobFileReader::Create(
|
||||
immutable_cf_options, FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number, nullptr /*IOTracer*/, &reader);
|
||||
immutable_options, FileOptions(), column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/, &reader);
|
||||
|
||||
const bool fail_during_create =
|
||||
(sync_point_ != "BlobFileReader::GetBlob:ReadFromFile");
|
||||
@@ -749,7 +748,7 @@ TEST_P(BlobFileReaderDecodingErrorTest, DecodingError) {
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -761,9 +760,9 @@ TEST_P(BlobFileReaderDecodingErrorTest, DecodingError) {
|
||||
uint64_t blob_offset = 0;
|
||||
uint64_t blob_size = 0;
|
||||
|
||||
WriteBlobFile(immutable_cf_options, column_family_id, has_ttl,
|
||||
expiration_range, expiration_range, blob_file_number, key, blob,
|
||||
kNoCompression, &blob_offset, &blob_size);
|
||||
WriteBlobFile(immutable_options, column_family_id, has_ttl, expiration_range,
|
||||
expiration_range, blob_file_number, key, blob, kNoCompression,
|
||||
&blob_offset, &blob_size);
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(sync_point_, [](void* arg) {
|
||||
Slice* const slice = static_cast<Slice*>(arg);
|
||||
@@ -780,8 +779,8 @@ TEST_P(BlobFileReaderDecodingErrorTest, DecodingError) {
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
const Status s = BlobFileReader::Create(
|
||||
immutable_cf_options, FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number, nullptr /*IOTracer*/, &reader);
|
||||
immutable_options, FileOptions(), column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/, &reader);
|
||||
|
||||
const bool fail_during_create =
|
||||
sync_point_ != "BlobFileReader::GetBlob:TamperWithResult";
|
||||
|
||||
@@ -310,6 +310,63 @@ TEST_F(DBBlobBasicTest, BestEffortsRecovery_MissingNewestBlobFile) {
|
||||
ASSERT_EQ("value" + std::to_string(kNumTableFiles - 2), value);
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, GetMergeBlobWithPut) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(Put("Key1", "v1"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Merge("Key1", "v2"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Merge("Key1", "v3"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
std::string value;
|
||||
ASSERT_OK(db_->Get(ReadOptions(), "Key1", &value));
|
||||
ASSERT_EQ(Get("Key1"), "v1,v2,v3");
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, MultiGetMergeBlobWithPut) {
|
||||
constexpr size_t num_keys = 3;
|
||||
|
||||
Options options = GetDefaultOptions();
|
||||
options.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(Put("Key0", "v0_0"));
|
||||
ASSERT_OK(Put("Key1", "v1_0"));
|
||||
ASSERT_OK(Put("Key2", "v2_0"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Merge("Key0", "v0_1"));
|
||||
ASSERT_OK(Merge("Key1", "v1_1"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Merge("Key0", "v0_2"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
std::array<Slice, num_keys> keys{{"Key0", "Key1", "Key2"}};
|
||||
std::array<PinnableSlice, num_keys> values;
|
||||
std::array<Status, num_keys> statuses;
|
||||
|
||||
db_->MultiGet(ReadOptions(), db_->DefaultColumnFamily(), num_keys, &keys[0],
|
||||
&values[0], &statuses[0]);
|
||||
|
||||
ASSERT_OK(statuses[0]);
|
||||
ASSERT_EQ(values[0], "v0_0,v0_1,v0_2");
|
||||
|
||||
ASSERT_OK(statuses[1]);
|
||||
ASSERT_EQ(values[1], "v1_0,v1_1");
|
||||
|
||||
ASSERT_OK(statuses[2]);
|
||||
ASSERT_EQ(values[2], "v2_0");
|
||||
}
|
||||
|
||||
class DBBlobBasicIOErrorTest : public DBBlobBasicTest,
|
||||
public testing::WithParamInterface<std::string> {
|
||||
protected:
|
||||
|
||||
@@ -399,7 +399,7 @@ TEST_F(DBBlobIndexTest, Iterate) {
|
||||
create_normal_iterator);
|
||||
verify(9, Status::kOk, get_value(10, 0), get_value(8, 0),
|
||||
create_normal_iterator);
|
||||
verify(11, Status::kNotSupported, "", "", create_normal_iterator);
|
||||
verify(11, Status::kCorruption, "", "", create_normal_iterator);
|
||||
verify(13, Status::kOk,
|
||||
get_value(13, 2) + "," + get_value(13, 1) + "," + get_value(13, 0),
|
||||
get_value(13, 2) + "," + get_value(13, 1) + "," + get_value(13, 0),
|
||||
@@ -418,7 +418,11 @@ TEST_F(DBBlobIndexTest, Iterate) {
|
||||
create_blob_iterator, check_is_blob(false));
|
||||
verify(9, Status::kOk, get_value(10, 0), get_value(8, 0),
|
||||
create_blob_iterator, check_is_blob(false));
|
||||
verify(11, Status::kNotSupported, "", "", create_blob_iterator);
|
||||
if (tier <= kImmutableMemtables) {
|
||||
verify(11, Status::kNotSupported, "", "", create_blob_iterator);
|
||||
} else {
|
||||
verify(11, Status::kCorruption, "", "", create_blob_iterator);
|
||||
}
|
||||
verify(13, Status::kOk,
|
||||
get_value(13, 2) + "," + get_value(13, 1) + "," + get_value(13, 0),
|
||||
get_value(13, 2) + "," + get_value(13, 1) + "," + get_value(13, 0),
|
||||
@@ -440,7 +444,11 @@ TEST_F(DBBlobIndexTest, Iterate) {
|
||||
create_blob_iterator, check_is_blob(false));
|
||||
verify(9, Status::kOk, get_value(10, 0), get_value(8, 0),
|
||||
create_blob_iterator, check_is_blob(false));
|
||||
verify(11, Status::kNotSupported, "", "", create_blob_iterator);
|
||||
if (tier <= kImmutableMemtables) {
|
||||
verify(11, Status::kNotSupported, "", "", create_blob_iterator);
|
||||
} else {
|
||||
verify(11, Status::kCorruption, "", "", create_blob_iterator);
|
||||
}
|
||||
verify(13, Status::kOk,
|
||||
get_value(13, 2) + "," + get_value(13, 1) + "," + get_value(13, 0),
|
||||
get_value(13, 2) + "," + get_value(13, 1) + "," + get_value(13, 0),
|
||||
@@ -455,6 +463,106 @@ TEST_F(DBBlobIndexTest, Iterate) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBlobIndexTest, IntegratedBlobIterate) {
|
||||
const std::vector<std::vector<std::string>> data = {
|
||||
/*00*/ {"Put"},
|
||||
/*01*/ {"Put", "Merge", "Merge", "Merge"},
|
||||
/*02*/ {"Put"}};
|
||||
|
||||
auto get_key = [](size_t index) { return ("key" + std::to_string(index)); };
|
||||
|
||||
auto get_value = [&](size_t index, size_t version) {
|
||||
return get_key(index) + "_value" + ToString(version);
|
||||
};
|
||||
|
||||
auto check_iterator = [&](Iterator* iterator, Status expected_status,
|
||||
const Slice& expected_value) {
|
||||
ASSERT_EQ(expected_status, iterator->status());
|
||||
if (expected_status.ok()) {
|
||||
ASSERT_TRUE(iterator->Valid());
|
||||
ASSERT_EQ(expected_value, iterator->value());
|
||||
} else {
|
||||
ASSERT_FALSE(iterator->Valid());
|
||||
}
|
||||
};
|
||||
|
||||
auto verify = [&](size_t index, Status expected_status,
|
||||
const Slice& expected_value) {
|
||||
// Seek
|
||||
{
|
||||
Iterator* iterator = db_->NewIterator(ReadOptions());
|
||||
std::unique_ptr<Iterator> iterator_guard(iterator);
|
||||
ASSERT_OK(iterator->status());
|
||||
ASSERT_OK(iterator->Refresh());
|
||||
iterator->Seek(get_key(index));
|
||||
check_iterator(iterator, expected_status, expected_value);
|
||||
}
|
||||
// Next
|
||||
{
|
||||
Iterator* iterator = db_->NewIterator(ReadOptions());
|
||||
std::unique_ptr<Iterator> iterator_guard(iterator);
|
||||
ASSERT_OK(iterator->Refresh());
|
||||
iterator->Seek(get_key(index - 1));
|
||||
ASSERT_TRUE(iterator->Valid());
|
||||
ASSERT_OK(iterator->status());
|
||||
iterator->Next();
|
||||
check_iterator(iterator, expected_status, expected_value);
|
||||
}
|
||||
// SeekForPrev
|
||||
{
|
||||
Iterator* iterator = db_->NewIterator(ReadOptions());
|
||||
std::unique_ptr<Iterator> iterator_guard(iterator);
|
||||
ASSERT_OK(iterator->status());
|
||||
ASSERT_OK(iterator->Refresh());
|
||||
iterator->SeekForPrev(get_key(index));
|
||||
check_iterator(iterator, expected_status, expected_value);
|
||||
}
|
||||
// Prev
|
||||
{
|
||||
Iterator* iterator = db_->NewIterator(ReadOptions());
|
||||
std::unique_ptr<Iterator> iterator_guard(iterator);
|
||||
iterator->Seek(get_key(index + 1));
|
||||
ASSERT_TRUE(iterator->Valid());
|
||||
ASSERT_OK(iterator->status());
|
||||
iterator->Prev();
|
||||
check_iterator(iterator, expected_status, expected_value);
|
||||
}
|
||||
};
|
||||
|
||||
Options options = GetTestOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// fill data
|
||||
for (size_t i = 0; i < data.size(); i++) {
|
||||
for (size_t j = 0; j < data[i].size(); j++) {
|
||||
std::string key = get_key(i);
|
||||
std::string value = get_value(i, j);
|
||||
if (data[i][j] == "Put") {
|
||||
ASSERT_OK(Put(key, value));
|
||||
ASSERT_OK(Flush());
|
||||
} else if (data[i][j] == "Merge") {
|
||||
ASSERT_OK(Merge(key, value));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string expected_value = get_value(1, 0) + "," + get_value(1, 1) + "," +
|
||||
get_value(1, 2) + "," + get_value(1, 3);
|
||||
Status expected_status;
|
||||
verify(1, expected_status, expected_value);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Test DBIter::FindValueForCurrentKeyUsingSeek flow.
|
||||
ASSERT_OK(dbfull()->SetOptions(cfh(),
|
||||
{{"max_sequential_skip_in_iterations", "0"}}));
|
||||
verify(1, expected_status, expected_value);
|
||||
#endif // !ROCKSDB_LITE
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -69,7 +69,8 @@ Status BuildTable(
|
||||
int job_id, const Env::IOPriority io_priority,
|
||||
TableProperties* table_properties, Env::WriteLifeTimeHint write_hint,
|
||||
const std::string* full_history_ts_low,
|
||||
BlobFileCompletionCallback* blob_callback, uint64_t* num_input_entries) {
|
||||
BlobFileCompletionCallback* blob_callback, uint64_t* num_input_entries,
|
||||
uint64_t* memtable_payload_bytes, uint64_t* memtable_garbage_bytes) {
|
||||
assert((tboptions.column_family_id ==
|
||||
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily) ==
|
||||
tboptions.column_family_name.empty());
|
||||
@@ -89,9 +90,12 @@ Status BuildTable(
|
||||
new CompactionRangeDelAggregator(&tboptions.internal_comparator,
|
||||
snapshots));
|
||||
uint64_t num_unfragmented_tombstones = 0;
|
||||
uint64_t total_tombstone_payload_bytes = 0;
|
||||
for (auto& range_del_iter : range_del_iters) {
|
||||
num_unfragmented_tombstones +=
|
||||
range_del_iter->num_unfragmented_tombstones();
|
||||
total_tombstone_payload_bytes +=
|
||||
range_del_iter->total_tombstone_payload_bytes();
|
||||
range_del_agg->AddTombstones(std::move(range_del_iter));
|
||||
}
|
||||
|
||||
@@ -191,7 +195,8 @@ Status BuildTable(
|
||||
/*compaction=*/nullptr, compaction_filter.get(),
|
||||
/*shutting_down=*/nullptr,
|
||||
/*preserve_deletes_seqnum=*/0, /*manual_compaction_paused=*/nullptr,
|
||||
db_options.info_log, full_history_ts_low);
|
||||
/*manual_compaction_canceled=*/nullptr, db_options.info_log,
|
||||
full_history_ts_low);
|
||||
|
||||
c_iter.SeekToFirst();
|
||||
for (; c_iter.Valid(); c_iter.Next()) {
|
||||
@@ -253,6 +258,25 @@ Status BuildTable(
|
||||
meta->marked_for_compaction = builder->NeedCompact();
|
||||
assert(meta->fd.GetFileSize() > 0);
|
||||
tp = builder->GetTableProperties(); // refresh now that builder is finished
|
||||
if (memtable_payload_bytes != nullptr &&
|
||||
memtable_garbage_bytes != nullptr) {
|
||||
const CompactionIterationStats& ci_stats = c_iter.iter_stats();
|
||||
uint64_t total_payload_bytes = ci_stats.total_input_raw_key_bytes +
|
||||
ci_stats.total_input_raw_value_bytes +
|
||||
total_tombstone_payload_bytes;
|
||||
uint64_t total_payload_bytes_written =
|
||||
(tp.raw_key_size + tp.raw_value_size);
|
||||
// Prevent underflow, which may still happen at this point
|
||||
// since we only support inserts, deletes, and deleteRanges.
|
||||
if (total_payload_bytes_written <= total_payload_bytes) {
|
||||
*memtable_payload_bytes = total_payload_bytes;
|
||||
*memtable_garbage_bytes =
|
||||
total_payload_bytes - total_payload_bytes_written;
|
||||
} else {
|
||||
*memtable_payload_bytes = 0;
|
||||
*memtable_garbage_bytes = 0;
|
||||
}
|
||||
}
|
||||
if (table_properties) {
|
||||
*table_properties = tp;
|
||||
}
|
||||
|
||||
@@ -66,6 +66,8 @@ extern Status BuildTable(
|
||||
Env::WriteLifeTimeHint write_hint = Env::WLTH_NOT_SET,
|
||||
const std::string* full_history_ts_low = nullptr,
|
||||
BlobFileCompletionCallback* blob_callback = nullptr,
|
||||
uint64_t* num_input_entries = nullptr);
|
||||
uint64_t* num_input_entries = nullptr,
|
||||
uint64_t* memtable_payload_bytes = nullptr,
|
||||
uint64_t* memtable_garbage_bytes = nullptr);
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -504,7 +504,8 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
const ColumnFamilyOptions& cf_options, const ImmutableDBOptions& db_options,
|
||||
const FileOptions& file_options, ColumnFamilySet* column_family_set,
|
||||
BlockCacheTracer* const block_cache_tracer,
|
||||
const std::shared_ptr<IOTracer>& io_tracer)
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::string& db_session_id)
|
||||
: id_(id),
|
||||
name_(name),
|
||||
dummy_versions_(_dummy_versions),
|
||||
@@ -562,7 +563,8 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
internal_stats_.reset(
|
||||
new InternalStats(ioptions_.num_levels, ioptions_.clock, this));
|
||||
table_cache_.reset(new TableCache(ioptions_, file_options, _table_cache,
|
||||
block_cache_tracer, io_tracer));
|
||||
block_cache_tracer, io_tracer,
|
||||
db_session_id));
|
||||
blob_file_cache_.reset(
|
||||
new BlobFileCache(_table_cache, ioptions(), soptions(), id_,
|
||||
internal_stats_->GetBlobFileReadHist(), io_tracer));
|
||||
@@ -1451,12 +1453,13 @@ ColumnFamilySet::ColumnFamilySet(const std::string& dbname,
|
||||
WriteBufferManager* _write_buffer_manager,
|
||||
WriteController* _write_controller,
|
||||
BlockCacheTracer* const block_cache_tracer,
|
||||
const std::shared_ptr<IOTracer>& io_tracer)
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::string& db_session_id)
|
||||
: max_column_family_(0),
|
||||
dummy_cfd_(new ColumnFamilyData(
|
||||
ColumnFamilyData::kDummyColumnFamilyDataId, "", nullptr, nullptr,
|
||||
nullptr, ColumnFamilyOptions(), *db_options, file_options, nullptr,
|
||||
block_cache_tracer, io_tracer)),
|
||||
block_cache_tracer, io_tracer, db_session_id)),
|
||||
default_cfd_cache_(nullptr),
|
||||
db_name_(dbname),
|
||||
db_options_(db_options),
|
||||
@@ -1465,7 +1468,8 @@ ColumnFamilySet::ColumnFamilySet(const std::string& dbname,
|
||||
write_buffer_manager_(_write_buffer_manager),
|
||||
write_controller_(_write_controller),
|
||||
block_cache_tracer_(block_cache_tracer),
|
||||
io_tracer_(io_tracer) {
|
||||
io_tracer_(io_tracer),
|
||||
db_session_id_(db_session_id) {
|
||||
// initialize linked list
|
||||
dummy_cfd_->prev_ = dummy_cfd_;
|
||||
dummy_cfd_->next_ = dummy_cfd_;
|
||||
@@ -1531,7 +1535,8 @@ ColumnFamilyData* ColumnFamilySet::CreateColumnFamily(
|
||||
assert(column_families_.find(name) == column_families_.end());
|
||||
ColumnFamilyData* new_cfd = new ColumnFamilyData(
|
||||
id, name, dummy_versions, table_cache_, write_buffer_manager_, options,
|
||||
*db_options_, file_options_, this, block_cache_tracer_, io_tracer_);
|
||||
*db_options_, file_options_, this, block_cache_tracer_, io_tracer_,
|
||||
db_session_id_);
|
||||
column_families_.insert({name, id});
|
||||
column_family_data_.insert({id, new_cfd});
|
||||
max_column_family_ = std::max(max_column_family_, id);
|
||||
|
||||
@@ -532,7 +532,8 @@ class ColumnFamilyData {
|
||||
const FileOptions& file_options,
|
||||
ColumnFamilySet* column_family_set,
|
||||
BlockCacheTracer* const block_cache_tracer,
|
||||
const std::shared_ptr<IOTracer>& io_tracer);
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::string& db_session_id);
|
||||
|
||||
std::vector<std::string> GetDbPaths() const;
|
||||
|
||||
@@ -668,7 +669,8 @@ class ColumnFamilySet {
|
||||
WriteBufferManager* _write_buffer_manager,
|
||||
WriteController* _write_controller,
|
||||
BlockCacheTracer* const block_cache_tracer,
|
||||
const std::shared_ptr<IOTracer>& io_tracer);
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::string& db_session_id);
|
||||
~ColumnFamilySet();
|
||||
|
||||
ColumnFamilyData* GetDefault() const;
|
||||
@@ -733,6 +735,7 @@ class ColumnFamilySet {
|
||||
WriteController* write_controller_;
|
||||
BlockCacheTracer* const block_cache_tracer_;
|
||||
std::shared_ptr<IOTracer> io_tracer_;
|
||||
std::string db_session_id_;
|
||||
};
|
||||
|
||||
// We use ColumnFamilyMemTablesImpl to provide WriteBatch a way to access
|
||||
|
||||
@@ -56,16 +56,8 @@ class ColumnFamilyTestBase : public testing::Test {
|
||||
public:
|
||||
explicit ColumnFamilyTestBase(uint32_t format) : rnd_(139), format_(format) {
|
||||
Env* base_env = Env::Default();
|
||||
#ifndef ROCKSDB_LITE
|
||||
const char* test_env_uri = getenv("TEST_ENV_URI");
|
||||
if (test_env_uri) {
|
||||
Env* test_env = nullptr;
|
||||
Status s = Env::LoadEnv(test_env_uri, &test_env, &env_guard_);
|
||||
base_env = test_env;
|
||||
EXPECT_OK(s);
|
||||
EXPECT_NE(Env::Default(), base_env);
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
EXPECT_OK(
|
||||
test::CreateEnvFromSystem(ConfigOptions(), &base_env, &env_guard_));
|
||||
EXPECT_NE(nullptr, base_env);
|
||||
env_ = new EnvCounter(base_env);
|
||||
env_->skip_fsync_ = true;
|
||||
|
||||
@@ -0,0 +1,275 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "table/internal_iterator.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// An internal iterator that wraps another one and ensures that any keys
|
||||
// returned are strictly within a range [start, end). If the underlying
|
||||
// iterator has already performed the bounds checking, it relies on that result;
|
||||
// otherwise, it performs the necessary key comparisons itself. Both bounds
|
||||
// are optional.
|
||||
class ClippingIterator : public InternalIterator {
|
||||
public:
|
||||
ClippingIterator(InternalIterator* iter, const Slice* start, const Slice* end,
|
||||
const Comparator* cmp)
|
||||
: iter_(iter), start_(start), end_(end), cmp_(cmp), valid_(false) {
|
||||
assert(iter_);
|
||||
assert(cmp_);
|
||||
assert(!start_ || !end_ || cmp_->Compare(*start_, *end_) <= 0);
|
||||
|
||||
UpdateAndEnforceBounds();
|
||||
}
|
||||
|
||||
bool Valid() const override { return valid_; }
|
||||
|
||||
void SeekToFirst() override {
|
||||
if (start_) {
|
||||
iter_->Seek(*start_);
|
||||
} else {
|
||||
iter_->SeekToFirst();
|
||||
}
|
||||
|
||||
UpdateAndEnforceUpperBound();
|
||||
}
|
||||
|
||||
void SeekToLast() override {
|
||||
if (end_) {
|
||||
iter_->SeekForPrev(*end_);
|
||||
|
||||
// Upper bound is exclusive, so we need a key which is strictly smaller
|
||||
if (iter_->Valid() && cmp_->Compare(iter_->key(), *end_) == 0) {
|
||||
iter_->Prev();
|
||||
}
|
||||
} else {
|
||||
iter_->SeekToLast();
|
||||
}
|
||||
|
||||
UpdateAndEnforceLowerBound();
|
||||
}
|
||||
|
||||
void Seek(const Slice& target) override {
|
||||
if (start_ && cmp_->Compare(target, *start_) < 0) {
|
||||
iter_->Seek(*start_);
|
||||
UpdateAndEnforceUpperBound();
|
||||
return;
|
||||
}
|
||||
|
||||
if (end_ && cmp_->Compare(target, *end_) >= 0) {
|
||||
valid_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
iter_->Seek(target);
|
||||
UpdateAndEnforceUpperBound();
|
||||
}
|
||||
|
||||
void SeekForPrev(const Slice& target) override {
|
||||
if (start_ && cmp_->Compare(target, *start_) < 0) {
|
||||
valid_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (end_ && cmp_->Compare(target, *end_) >= 0) {
|
||||
iter_->SeekForPrev(*end_);
|
||||
|
||||
// Upper bound is exclusive, so we need a key which is strictly smaller
|
||||
if (iter_->Valid() && cmp_->Compare(iter_->key(), *end_) == 0) {
|
||||
iter_->Prev();
|
||||
}
|
||||
|
||||
UpdateAndEnforceLowerBound();
|
||||
return;
|
||||
}
|
||||
|
||||
iter_->SeekForPrev(target);
|
||||
UpdateAndEnforceLowerBound();
|
||||
}
|
||||
|
||||
void Next() override {
|
||||
assert(valid_);
|
||||
iter_->Next();
|
||||
UpdateAndEnforceUpperBound();
|
||||
}
|
||||
|
||||
bool NextAndGetResult(IterateResult* result) override {
|
||||
assert(valid_);
|
||||
assert(result);
|
||||
|
||||
IterateResult res;
|
||||
valid_ = iter_->NextAndGetResult(&res);
|
||||
|
||||
if (!valid_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (end_) {
|
||||
EnforceUpperBoundImpl(res.bound_check_result);
|
||||
|
||||
if (!valid_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
res.bound_check_result = IterBoundCheck::kInbound;
|
||||
*result = res;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Prev() override {
|
||||
assert(valid_);
|
||||
iter_->Prev();
|
||||
UpdateAndEnforceLowerBound();
|
||||
}
|
||||
|
||||
Slice key() const override {
|
||||
assert(valid_);
|
||||
return iter_->key();
|
||||
}
|
||||
|
||||
Slice user_key() const override {
|
||||
assert(valid_);
|
||||
return iter_->user_key();
|
||||
}
|
||||
|
||||
Slice value() const override {
|
||||
assert(valid_);
|
||||
return iter_->value();
|
||||
}
|
||||
|
||||
Status status() const override { return iter_->status(); }
|
||||
|
||||
bool PrepareValue() override {
|
||||
assert(valid_);
|
||||
|
||||
if (iter_->PrepareValue()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
assert(!iter_->Valid());
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MayBeOutOfLowerBound() override {
|
||||
assert(valid_);
|
||||
return false;
|
||||
}
|
||||
|
||||
IterBoundCheck UpperBoundCheckResult() override {
|
||||
assert(valid_);
|
||||
return IterBoundCheck::kInbound;
|
||||
}
|
||||
|
||||
void SetPinnedItersMgr(PinnedIteratorsManager* pinned_iters_mgr) override {
|
||||
iter_->SetPinnedItersMgr(pinned_iters_mgr);
|
||||
}
|
||||
|
||||
bool IsKeyPinned() const override {
|
||||
assert(valid_);
|
||||
return iter_->IsKeyPinned();
|
||||
}
|
||||
|
||||
bool IsValuePinned() const override {
|
||||
assert(valid_);
|
||||
return iter_->IsValuePinned();
|
||||
}
|
||||
|
||||
Status GetProperty(std::string prop_name, std::string* prop) override {
|
||||
return iter_->GetProperty(prop_name, prop);
|
||||
}
|
||||
|
||||
private:
|
||||
void UpdateValid() {
|
||||
assert(!iter_->Valid() || iter_->status().ok());
|
||||
|
||||
valid_ = iter_->Valid();
|
||||
}
|
||||
|
||||
void EnforceUpperBoundImpl(IterBoundCheck bound_check_result) {
|
||||
if (bound_check_result == IterBoundCheck::kInbound) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (bound_check_result == IterBoundCheck::kOutOfBound) {
|
||||
valid_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
assert(bound_check_result == IterBoundCheck::kUnknown);
|
||||
|
||||
if (cmp_->Compare(key(), *end_) >= 0) {
|
||||
valid_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void EnforceUpperBound() {
|
||||
if (!valid_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!end_) {
|
||||
return;
|
||||
}
|
||||
|
||||
EnforceUpperBoundImpl(iter_->UpperBoundCheckResult());
|
||||
}
|
||||
|
||||
void EnforceLowerBound() {
|
||||
if (!valid_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!start_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!iter_->MayBeOutOfLowerBound()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmp_->Compare(key(), *start_) < 0) {
|
||||
valid_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void AssertBounds() {
|
||||
assert(!valid_ || !start_ || cmp_->Compare(key(), *start_) >= 0);
|
||||
assert(!valid_ || !end_ || cmp_->Compare(key(), *end_) < 0);
|
||||
}
|
||||
|
||||
void UpdateAndEnforceBounds() {
|
||||
UpdateValid();
|
||||
EnforceUpperBound();
|
||||
EnforceLowerBound();
|
||||
AssertBounds();
|
||||
}
|
||||
|
||||
void UpdateAndEnforceUpperBound() {
|
||||
UpdateValid();
|
||||
EnforceUpperBound();
|
||||
AssertBounds();
|
||||
}
|
||||
|
||||
void UpdateAndEnforceLowerBound() {
|
||||
UpdateValid();
|
||||
EnforceLowerBound();
|
||||
AssertBounds();
|
||||
}
|
||||
|
||||
InternalIterator* iter_;
|
||||
const Slice* start_;
|
||||
const Slice* end_;
|
||||
const Comparator* cmp_;
|
||||
bool valid_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -0,0 +1,256 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/compaction/clipping_iterator.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// A vector iterator which does its own bounds checking. This is for testing the
|
||||
// optimizations in the clipping iterator where we bypass the bounds checking if
|
||||
// the input iterator has already performed it.
|
||||
class BoundsCheckingVectorIterator : public test::VectorIterator {
|
||||
public:
|
||||
BoundsCheckingVectorIterator(const std::vector<std::string>& keys,
|
||||
const std::vector<std::string>& values,
|
||||
const Slice* start, const Slice* end,
|
||||
const Comparator* cmp)
|
||||
: VectorIterator(keys, values), start_(start), end_(end), cmp_(cmp) {
|
||||
assert(cmp_);
|
||||
}
|
||||
|
||||
bool NextAndGetResult(IterateResult* result) override {
|
||||
assert(Valid());
|
||||
assert(result);
|
||||
|
||||
Next();
|
||||
|
||||
if (!Valid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
result->key = key();
|
||||
result->bound_check_result = UpperBoundCheckResult();
|
||||
result->value_prepared = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MayBeOutOfLowerBound() override {
|
||||
assert(Valid());
|
||||
|
||||
if (!start_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return cmp_->Compare(key(), *start_) < 0;
|
||||
}
|
||||
|
||||
IterBoundCheck UpperBoundCheckResult() override {
|
||||
assert(Valid());
|
||||
|
||||
if (!end_) {
|
||||
return IterBoundCheck::kInbound;
|
||||
}
|
||||
|
||||
return cmp_->Compare(key(), *end_) >= 0 ? IterBoundCheck::kOutOfBound
|
||||
: IterBoundCheck::kInbound;
|
||||
}
|
||||
|
||||
private:
|
||||
const Slice* start_;
|
||||
const Slice* end_;
|
||||
const Comparator* cmp_;
|
||||
};
|
||||
|
||||
class ClippingIteratorTest
|
||||
: public ::testing::Test,
|
||||
public ::testing::WithParamInterface<std::tuple<bool, size_t, size_t>> {};
|
||||
|
||||
TEST_P(ClippingIteratorTest, Clip) {
|
||||
const std::vector<std::string> keys{"key0", "key1", "key2", "key3", "key4",
|
||||
"key5", "key6", "key7", "key8", "key9"};
|
||||
const std::vector<std::string> values{
|
||||
"unused0", "value1", "value2", "value3", "unused4",
|
||||
"unused5", "unused6", "unused7", "unused8", "unused9"};
|
||||
|
||||
assert(keys.size() == values.size());
|
||||
|
||||
// Note: the input always contains key1, key2, and key3; however, the clipping
|
||||
// window is based on the test parameters: its left edge is a value in the
|
||||
// range [0, 4], and its size is a value in the range [0, 5]
|
||||
const std::vector<std::string> input_keys{keys[1], keys[2], keys[3]};
|
||||
const std::vector<std::string> input_values{values[1], values[2], values[3]};
|
||||
|
||||
const bool use_bounds_checking_vec_it = std::get<0>(GetParam());
|
||||
|
||||
const size_t clip_start_idx = std::get<1>(GetParam());
|
||||
const size_t clip_window_size = std::get<2>(GetParam());
|
||||
const size_t clip_end_idx = clip_start_idx + clip_window_size;
|
||||
|
||||
const Slice start(keys[clip_start_idx]);
|
||||
const Slice end(keys[clip_end_idx]);
|
||||
|
||||
std::unique_ptr<InternalIterator> input(
|
||||
use_bounds_checking_vec_it
|
||||
? new BoundsCheckingVectorIterator(input_keys, input_values, &start,
|
||||
&end, BytewiseComparator())
|
||||
: new test::VectorIterator(input_keys, input_values));
|
||||
|
||||
ClippingIterator clip(input.get(), &start, &end, BytewiseComparator());
|
||||
|
||||
// The range the clipping iterator should return values from. This is
|
||||
// essentially the intersection of the input range [1, 4) and the clipping
|
||||
// window [clip_start_idx, clip_end_idx)
|
||||
const size_t data_start_idx =
|
||||
std::max(clip_start_idx, static_cast<size_t>(1));
|
||||
const size_t data_end_idx = std::min(clip_end_idx, static_cast<size_t>(4));
|
||||
|
||||
// Range is empty; all Seeks should fail
|
||||
if (data_start_idx >= data_end_idx) {
|
||||
clip.SeekToFirst();
|
||||
ASSERT_FALSE(clip.Valid());
|
||||
|
||||
clip.SeekToLast();
|
||||
ASSERT_FALSE(clip.Valid());
|
||||
|
||||
for (size_t i = 0; i < keys.size(); ++i) {
|
||||
clip.Seek(keys[i]);
|
||||
ASSERT_FALSE(clip.Valid());
|
||||
|
||||
clip.SeekForPrev(keys[i]);
|
||||
ASSERT_FALSE(clip.Valid());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Range is non-empty; call SeekToFirst and iterate forward
|
||||
clip.SeekToFirst();
|
||||
ASSERT_TRUE(clip.Valid());
|
||||
ASSERT_EQ(clip.key(), keys[data_start_idx]);
|
||||
ASSERT_EQ(clip.value(), values[data_start_idx]);
|
||||
ASSERT_FALSE(clip.MayBeOutOfLowerBound());
|
||||
ASSERT_EQ(clip.UpperBoundCheckResult(), IterBoundCheck::kInbound);
|
||||
|
||||
for (size_t i = data_start_idx + 1; i < data_end_idx; ++i) {
|
||||
clip.Next();
|
||||
ASSERT_TRUE(clip.Valid());
|
||||
ASSERT_EQ(clip.key(), keys[i]);
|
||||
ASSERT_EQ(clip.value(), values[i]);
|
||||
ASSERT_FALSE(clip.MayBeOutOfLowerBound());
|
||||
ASSERT_EQ(clip.UpperBoundCheckResult(), IterBoundCheck::kInbound);
|
||||
}
|
||||
|
||||
clip.Next();
|
||||
ASSERT_FALSE(clip.Valid());
|
||||
|
||||
// Do it again using NextAndGetResult
|
||||
clip.SeekToFirst();
|
||||
ASSERT_TRUE(clip.Valid());
|
||||
ASSERT_EQ(clip.key(), keys[data_start_idx]);
|
||||
ASSERT_EQ(clip.value(), values[data_start_idx]);
|
||||
ASSERT_FALSE(clip.MayBeOutOfLowerBound());
|
||||
ASSERT_EQ(clip.UpperBoundCheckResult(), IterBoundCheck::kInbound);
|
||||
|
||||
for (size_t i = data_start_idx + 1; i < data_end_idx; ++i) {
|
||||
IterateResult result;
|
||||
ASSERT_TRUE(clip.NextAndGetResult(&result));
|
||||
ASSERT_EQ(result.key, keys[i]);
|
||||
ASSERT_EQ(result.bound_check_result, IterBoundCheck::kInbound);
|
||||
ASSERT_TRUE(clip.Valid());
|
||||
ASSERT_EQ(clip.key(), keys[i]);
|
||||
ASSERT_EQ(clip.value(), values[i]);
|
||||
ASSERT_FALSE(clip.MayBeOutOfLowerBound());
|
||||
ASSERT_EQ(clip.UpperBoundCheckResult(), IterBoundCheck::kInbound);
|
||||
}
|
||||
|
||||
IterateResult result;
|
||||
ASSERT_FALSE(clip.NextAndGetResult(&result));
|
||||
ASSERT_FALSE(clip.Valid());
|
||||
|
||||
// Call SeekToLast and iterate backward
|
||||
clip.SeekToLast();
|
||||
ASSERT_TRUE(clip.Valid());
|
||||
ASSERT_EQ(clip.key(), keys[data_end_idx - 1]);
|
||||
ASSERT_EQ(clip.value(), values[data_end_idx - 1]);
|
||||
ASSERT_FALSE(clip.MayBeOutOfLowerBound());
|
||||
ASSERT_EQ(clip.UpperBoundCheckResult(), IterBoundCheck::kInbound);
|
||||
|
||||
for (size_t i = data_end_idx - 2; i >= data_start_idx; --i) {
|
||||
clip.Prev();
|
||||
ASSERT_TRUE(clip.Valid());
|
||||
ASSERT_EQ(clip.key(), keys[i]);
|
||||
ASSERT_EQ(clip.value(), values[i]);
|
||||
ASSERT_FALSE(clip.MayBeOutOfLowerBound());
|
||||
ASSERT_EQ(clip.UpperBoundCheckResult(), IterBoundCheck::kInbound);
|
||||
}
|
||||
|
||||
clip.Prev();
|
||||
ASSERT_FALSE(clip.Valid());
|
||||
|
||||
// Call Seek/SeekForPrev for all keys; Seek should return the smallest key
|
||||
// which is >= the target; SeekForPrev should return the largest key which is
|
||||
// <= the target
|
||||
for (size_t i = 0; i < keys.size(); ++i) {
|
||||
clip.Seek(keys[i]);
|
||||
|
||||
if (i < data_start_idx) {
|
||||
ASSERT_TRUE(clip.Valid());
|
||||
ASSERT_EQ(clip.key(), keys[data_start_idx]);
|
||||
ASSERT_EQ(clip.value(), values[data_start_idx]);
|
||||
ASSERT_FALSE(clip.MayBeOutOfLowerBound());
|
||||
ASSERT_EQ(clip.UpperBoundCheckResult(), IterBoundCheck::kInbound);
|
||||
} else if (i < data_end_idx) {
|
||||
ASSERT_TRUE(clip.Valid());
|
||||
ASSERT_EQ(clip.key(), keys[i]);
|
||||
ASSERT_EQ(clip.value(), values[i]);
|
||||
ASSERT_FALSE(clip.MayBeOutOfLowerBound());
|
||||
ASSERT_EQ(clip.UpperBoundCheckResult(), IterBoundCheck::kInbound);
|
||||
} else {
|
||||
ASSERT_FALSE(clip.Valid());
|
||||
}
|
||||
|
||||
clip.SeekForPrev(keys[i]);
|
||||
|
||||
if (i < data_start_idx) {
|
||||
ASSERT_FALSE(clip.Valid());
|
||||
} else if (i < data_end_idx) {
|
||||
ASSERT_TRUE(clip.Valid());
|
||||
ASSERT_EQ(clip.key(), keys[i]);
|
||||
ASSERT_EQ(clip.value(), values[i]);
|
||||
ASSERT_FALSE(clip.MayBeOutOfLowerBound());
|
||||
ASSERT_EQ(clip.UpperBoundCheckResult(), IterBoundCheck::kInbound);
|
||||
} else {
|
||||
ASSERT_TRUE(clip.Valid());
|
||||
ASSERT_EQ(clip.key(), keys[data_end_idx - 1]);
|
||||
ASSERT_EQ(clip.value(), values[data_end_idx - 1]);
|
||||
ASSERT_FALSE(clip.MayBeOutOfLowerBound());
|
||||
ASSERT_EQ(clip.UpperBoundCheckResult(), IterBoundCheck::kInbound);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
ClippingIteratorTest, ClippingIteratorTest,
|
||||
::testing::Combine(
|
||||
::testing::Bool(),
|
||||
::testing::Range(static_cast<size_t>(0), static_cast<size_t>(5)),
|
||||
::testing::Range(static_cast<size_t>(0), static_cast<size_t>(6))));
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -563,6 +563,14 @@ bool Compaction::ShouldFormSubcompactions() const {
|
||||
if (max_subcompactions_ <= 1 || cfd_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Note: the subcompaction boundary picking logic does not currently guarantee
|
||||
// that all user keys that differ only by timestamp get processed by the same
|
||||
// subcompaction.
|
||||
if (cfd_->user_comparator()->timestamp_size() > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cfd_->ioptions()->compaction_style == kCompactionStyleLevel) {
|
||||
return (start_level_ == 0 || is_manual_compaction_) && output_level_ > 0 &&
|
||||
!IsOutputLevelEmpty();
|
||||
|
||||
@@ -223,9 +223,9 @@ class Compaction {
|
||||
// How many total levels are there?
|
||||
int number_levels() const { return number_levels_; }
|
||||
|
||||
// Return the ImmutableCFOptions that should be used throughout the compaction
|
||||
// Return the ImmutableOptions that should be used throughout the compaction
|
||||
// procedure
|
||||
const ImmutableOptions* immutable_cf_options() const {
|
||||
const ImmutableOptions* immutable_options() const {
|
||||
return &immutable_options_;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ CompactionIterator::CompactionIterator(
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const SequenceNumber preserve_deletes_seqnum,
|
||||
const std::atomic<int>* manual_compaction_paused,
|
||||
const std::atomic<bool>* manual_compaction_canceled,
|
||||
const std::shared_ptr<Logger> info_log,
|
||||
const std::string* full_history_ts_low)
|
||||
: CompactionIterator(
|
||||
@@ -55,7 +56,8 @@ CompactionIterator::CompactionIterator(
|
||||
std::unique_ptr<CompactionProxy>(
|
||||
compaction ? new RealCompaction(compaction) : nullptr),
|
||||
compaction_filter, shutting_down, preserve_deletes_seqnum,
|
||||
manual_compaction_paused, info_log, full_history_ts_low) {}
|
||||
manual_compaction_paused, manual_compaction_canceled, info_log,
|
||||
full_history_ts_low) {}
|
||||
|
||||
CompactionIterator::CompactionIterator(
|
||||
InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
|
||||
@@ -70,6 +72,7 @@ CompactionIterator::CompactionIterator(
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const SequenceNumber preserve_deletes_seqnum,
|
||||
const std::atomic<int>* manual_compaction_paused,
|
||||
const std::atomic<bool>* manual_compaction_canceled,
|
||||
const std::shared_ptr<Logger> info_log,
|
||||
const std::string* full_history_ts_low)
|
||||
: input_(
|
||||
@@ -91,6 +94,7 @@ CompactionIterator::CompactionIterator(
|
||||
compaction_filter_(compaction_filter),
|
||||
shutting_down_(shutting_down),
|
||||
manual_compaction_paused_(manual_compaction_paused),
|
||||
manual_compaction_canceled_(manual_compaction_canceled),
|
||||
preserve_deletes_seqnum_(preserve_deletes_seqnum),
|
||||
info_log_(info_log),
|
||||
allow_data_in_errors_(allow_data_in_errors),
|
||||
|
||||
@@ -104,7 +104,7 @@ class CompactionIterator {
|
||||
explicit RealCompaction(const Compaction* compaction)
|
||||
: compaction_(compaction) {
|
||||
assert(compaction_);
|
||||
assert(compaction_->immutable_cf_options());
|
||||
assert(compaction_->immutable_options());
|
||||
assert(compaction_->mutable_cf_options());
|
||||
}
|
||||
|
||||
@@ -126,11 +126,11 @@ class CompactionIterator {
|
||||
}
|
||||
|
||||
bool allow_ingest_behind() const override {
|
||||
return compaction_->immutable_cf_options()->allow_ingest_behind;
|
||||
return compaction_->immutable_options()->allow_ingest_behind;
|
||||
}
|
||||
|
||||
bool preserve_deletes() const override {
|
||||
return compaction_->immutable_cf_options()->preserve_deletes;
|
||||
return compaction_->immutable_options()->preserve_deletes;
|
||||
}
|
||||
|
||||
bool enable_blob_garbage_collection() const override {
|
||||
@@ -150,40 +150,40 @@ class CompactionIterator {
|
||||
const Compaction* compaction_;
|
||||
};
|
||||
|
||||
CompactionIterator(InternalIterator* input, const Comparator* cmp,
|
||||
MergeHelper* merge_helper, SequenceNumber last_sequence,
|
||||
std::vector<SequenceNumber>* snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const SnapshotChecker* snapshot_checker, Env* env,
|
||||
bool report_detailed_time, bool expect_valid_internal_key,
|
||||
CompactionRangeDelAggregator* range_del_agg,
|
||||
BlobFileBuilder* blob_file_builder,
|
||||
bool allow_data_in_errors,
|
||||
const Compaction* compaction = nullptr,
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
const std::atomic<bool>* shutting_down = nullptr,
|
||||
const SequenceNumber preserve_deletes_seqnum = 0,
|
||||
const std::atomic<int>* manual_compaction_paused = nullptr,
|
||||
const std::shared_ptr<Logger> info_log = nullptr,
|
||||
const std::string* full_history_ts_low = nullptr);
|
||||
CompactionIterator(
|
||||
InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
|
||||
SequenceNumber last_sequence, std::vector<SequenceNumber>* snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const SnapshotChecker* snapshot_checker, Env* env,
|
||||
bool report_detailed_time, bool expect_valid_internal_key,
|
||||
CompactionRangeDelAggregator* range_del_agg,
|
||||
BlobFileBuilder* blob_file_builder, bool allow_data_in_errors,
|
||||
const Compaction* compaction = nullptr,
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
const std::atomic<bool>* shutting_down = nullptr,
|
||||
const SequenceNumber preserve_deletes_seqnum = 0,
|
||||
const std::atomic<int>* manual_compaction_paused = nullptr,
|
||||
const std::atomic<bool>* manual_compaction_canceled = nullptr,
|
||||
const std::shared_ptr<Logger> info_log = nullptr,
|
||||
const std::string* full_history_ts_low = nullptr);
|
||||
|
||||
// Constructor with custom CompactionProxy, used for tests.
|
||||
CompactionIterator(InternalIterator* input, const Comparator* cmp,
|
||||
MergeHelper* merge_helper, SequenceNumber last_sequence,
|
||||
std::vector<SequenceNumber>* snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const SnapshotChecker* snapshot_checker, Env* env,
|
||||
bool report_detailed_time, bool expect_valid_internal_key,
|
||||
CompactionRangeDelAggregator* range_del_agg,
|
||||
BlobFileBuilder* blob_file_builder,
|
||||
bool allow_data_in_errors,
|
||||
std::unique_ptr<CompactionProxy> compaction,
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
const std::atomic<bool>* shutting_down = nullptr,
|
||||
const SequenceNumber preserve_deletes_seqnum = 0,
|
||||
const std::atomic<int>* manual_compaction_paused = nullptr,
|
||||
const std::shared_ptr<Logger> info_log = nullptr,
|
||||
const std::string* full_history_ts_low = nullptr);
|
||||
CompactionIterator(
|
||||
InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
|
||||
SequenceNumber last_sequence, std::vector<SequenceNumber>* snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const SnapshotChecker* snapshot_checker, Env* env,
|
||||
bool report_detailed_time, bool expect_valid_internal_key,
|
||||
CompactionRangeDelAggregator* range_del_agg,
|
||||
BlobFileBuilder* blob_file_builder, bool allow_data_in_errors,
|
||||
std::unique_ptr<CompactionProxy> compaction,
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
const std::atomic<bool>* shutting_down = nullptr,
|
||||
const SequenceNumber preserve_deletes_seqnum = 0,
|
||||
const std::atomic<int>* manual_compaction_paused = nullptr,
|
||||
const std::atomic<bool>* manual_compaction_canceled = nullptr,
|
||||
const std::shared_ptr<Logger> info_log = nullptr,
|
||||
const std::string* full_history_ts_low = nullptr);
|
||||
|
||||
~CompactionIterator();
|
||||
|
||||
@@ -303,6 +303,7 @@ class CompactionIterator {
|
||||
const CompactionFilter* compaction_filter_;
|
||||
const std::atomic<bool>* shutting_down_;
|
||||
const std::atomic<int>* manual_compaction_paused_;
|
||||
const std::atomic<bool>* manual_compaction_canceled_;
|
||||
const SequenceNumber preserve_deletes_seqnum_;
|
||||
bool bottommost_level_;
|
||||
bool valid_ = false;
|
||||
@@ -399,8 +400,10 @@ class CompactionIterator {
|
||||
|
||||
bool IsPausingManualCompaction() {
|
||||
// This is a best-effort facility, so memory_order_relaxed is sufficient.
|
||||
return manual_compaction_paused_ &&
|
||||
manual_compaction_paused_->load(std::memory_order_relaxed) > 0;
|
||||
return (manual_compaction_paused_ &&
|
||||
manual_compaction_paused_->load(std::memory_order_relaxed) > 0) ||
|
||||
(manual_compaction_canceled_ &&
|
||||
manual_compaction_canceled_->load(std::memory_order_relaxed));
|
||||
}
|
||||
};
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -282,7 +282,8 @@ class CompactionIteratorTest : public testing::TestWithParam<bool> {
|
||||
range_del_agg_.get(), nullptr /* blob_file_builder */,
|
||||
true /*allow_data_in_errors*/, std::move(compaction), filter,
|
||||
&shutting_down_, /*preserve_deletes_seqnum=*/0,
|
||||
/*manual_compaction_paused=*/nullptr, /*info_log=*/nullptr,
|
||||
/*manual_compaction_paused=*/nullptr,
|
||||
/*manual_compaction_canceled=*/nullptr, /*info_log=*/nullptr,
|
||||
full_history_ts_low));
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "db/blob/blob_file_addition.h"
|
||||
#include "db/blob/blob_file_builder.h"
|
||||
#include "db/builder.h"
|
||||
#include "db/compaction/clipping_iterator.h"
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/db_iter.h"
|
||||
#include "db/dbformat.h"
|
||||
@@ -313,9 +314,10 @@ CompactionJob::CompactionJob(
|
||||
EventLogger* event_logger, bool paranoid_file_checks, bool measure_io_stats,
|
||||
const std::string& dbname, CompactionJobStats* compaction_job_stats,
|
||||
Env::Priority thread_pri, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::atomic<int>* manual_compaction_paused, const std::string& db_id,
|
||||
const std::string& db_session_id, std::string full_history_ts_low,
|
||||
BlobFileCompletionCallback* blob_callback)
|
||||
const std::atomic<int>* manual_compaction_paused,
|
||||
const std::atomic<bool>* manual_compaction_canceled,
|
||||
const std::string& db_id, const std::string& db_session_id,
|
||||
std::string full_history_ts_low, BlobFileCompletionCallback* blob_callback)
|
||||
: compact_(new CompactionState(compaction)),
|
||||
compaction_stats_(compaction->compaction_reason(), 1),
|
||||
db_options_(db_options),
|
||||
@@ -339,6 +341,7 @@ CompactionJob::CompactionJob(
|
||||
versions_(versions),
|
||||
shutting_down_(shutting_down),
|
||||
manual_compaction_paused_(manual_compaction_paused),
|
||||
manual_compaction_canceled_(manual_compaction_canceled),
|
||||
preserve_deletes_seqnum_(preserve_deletes_seqnum),
|
||||
db_directory_(db_directory),
|
||||
blob_output_directory_(blob_output_directory),
|
||||
@@ -550,9 +553,10 @@ void CompactionJob::GenSubcompactionBoundaries() {
|
||||
int base_level = v->storage_info()->base_level();
|
||||
uint64_t max_output_files = static_cast<uint64_t>(std::ceil(
|
||||
sum / min_file_fill_percent /
|
||||
MaxFileSizeForLevel(*(c->mutable_cf_options()), out_lvl,
|
||||
c->immutable_cf_options()->compaction_style, base_level,
|
||||
c->immutable_cf_options()->level_compaction_dynamic_level_bytes)));
|
||||
MaxFileSizeForLevel(
|
||||
*(c->mutable_cf_options()), out_lvl,
|
||||
c->immutable_options()->compaction_style, base_level,
|
||||
c->immutable_options()->level_compaction_dynamic_level_bytes)));
|
||||
uint64_t subcompactions =
|
||||
std::min({static_cast<uint64_t>(ranges.size()),
|
||||
static_cast<uint64_t>(c->max_subcompactions()),
|
||||
@@ -751,7 +755,7 @@ Status CompactionJob::Run() {
|
||||
for (const auto& state : compact_->sub_compact_states) {
|
||||
for (const auto& output : state.outputs) {
|
||||
auto fn =
|
||||
TableFileName(state.compaction->immutable_cf_options()->cf_paths,
|
||||
TableFileName(state.compaction->immutable_options()->cf_paths,
|
||||
output.meta.fd.GetNumber(), output.meta.fd.GetPathId());
|
||||
tp[fn] = output.table_properties;
|
||||
}
|
||||
@@ -1014,7 +1018,7 @@ void CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
for (const auto& file : compaction_result.output_files) {
|
||||
uint64_t file_num = versions_->NewFileNumber();
|
||||
auto src_file = compaction_result.output_path + "/" + file.file_name;
|
||||
auto tgt_file = TableFileName(compaction->immutable_cf_options()->cf_paths,
|
||||
auto tgt_file = TableFileName(compaction->immutable_options()->cf_paths,
|
||||
file_num, compaction->output_path_id());
|
||||
s = fs_->RenameFile(src_file, tgt_file, IOOptions(), nullptr);
|
||||
if (!s.ok()) {
|
||||
@@ -1084,6 +1088,10 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
|
||||
CompactionRangeDelAggregator range_del_agg(&cfd->internal_comparator(),
|
||||
existing_snapshots_);
|
||||
|
||||
const Slice* const start = sub_compact->start;
|
||||
const Slice* const end = sub_compact->end;
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.verify_checksums = true;
|
||||
read_options.fill_cache = false;
|
||||
@@ -1093,11 +1101,42 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
// (b) CompactionFilter::Decision::kRemoveAndSkipUntil.
|
||||
read_options.total_order_seek = true;
|
||||
|
||||
// Note: if we're going to support subcompactions for user-defined timestamps,
|
||||
// the timestamp part will have to be stripped from the bounds here.
|
||||
assert((!start && !end) || cfd->user_comparator()->timestamp_size() == 0);
|
||||
read_options.iterate_lower_bound = start;
|
||||
read_options.iterate_upper_bound = end;
|
||||
|
||||
// Although the v2 aggregator is what the level iterator(s) know about,
|
||||
// the AddTombstones calls will be propagated down to the v1 aggregator.
|
||||
std::unique_ptr<InternalIterator> input(
|
||||
std::unique_ptr<InternalIterator> raw_input(
|
||||
versions_->MakeInputIterator(read_options, sub_compact->compaction,
|
||||
&range_del_agg, file_options_for_read_));
|
||||
InternalIterator* input = raw_input.get();
|
||||
|
||||
IterKey start_ikey;
|
||||
IterKey end_ikey;
|
||||
Slice start_slice;
|
||||
Slice end_slice;
|
||||
|
||||
if (start) {
|
||||
start_ikey.SetInternalKey(*start, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
start_slice = start_ikey.GetInternalKey();
|
||||
}
|
||||
if (end) {
|
||||
end_ikey.SetInternalKey(*end, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
end_slice = end_ikey.GetInternalKey();
|
||||
}
|
||||
|
||||
std::unique_ptr<InternalIterator> clip;
|
||||
if (start || end) {
|
||||
clip.reset(new ClippingIterator(
|
||||
raw_input.get(), start ? &start_slice : nullptr,
|
||||
end ? &end_slice : nullptr, &cfd->internal_comparator()));
|
||||
input = clip.get();
|
||||
}
|
||||
|
||||
input->SeekToFirst();
|
||||
|
||||
AutoThreadOperationStageUpdater stage_updater(
|
||||
ThreadStatus::STAGE_COMPACTION_PROCESS_KV);
|
||||
@@ -1138,7 +1177,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
std::unique_ptr<BlobFileBuilder> blob_file_builder(
|
||||
mutable_cf_options->enable_blob_files
|
||||
? new BlobFileBuilder(versions_, fs_.get(),
|
||||
sub_compact->compaction->immutable_cf_options(),
|
||||
sub_compact->compaction->immutable_options(),
|
||||
mutable_cf_options, &file_options_, job_id_,
|
||||
cfd->GetID(), cfd->GetName(),
|
||||
Env::IOPriority::IO_LOW, write_hint_,
|
||||
@@ -1152,28 +1191,18 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
reinterpret_cast<void*>(
|
||||
const_cast<std::atomic<int>*>(manual_compaction_paused_)));
|
||||
|
||||
Slice* start = sub_compact->start;
|
||||
Slice* end = sub_compact->end;
|
||||
if (start != nullptr) {
|
||||
IterKey start_iter;
|
||||
start_iter.SetInternalKey(*start, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
input->Seek(start_iter.GetInternalKey());
|
||||
} else {
|
||||
input->SeekToFirst();
|
||||
}
|
||||
|
||||
Status status;
|
||||
const std::string* const full_history_ts_low =
|
||||
full_history_ts_low_.empty() ? nullptr : &full_history_ts_low_;
|
||||
sub_compact->c_iter.reset(new CompactionIterator(
|
||||
input.get(), cfd->user_comparator(), &merge, versions_->LastSequence(),
|
||||
input, cfd->user_comparator(), &merge, versions_->LastSequence(),
|
||||
&existing_snapshots_, earliest_write_conflict_snapshot_,
|
||||
snapshot_checker_, env_, ShouldReportDetailedTime(env_, stats_),
|
||||
/*expect_valid_internal_key=*/true, &range_del_agg,
|
||||
blob_file_builder.get(), db_options_.allow_data_in_errors,
|
||||
sub_compact->compaction, compaction_filter, shutting_down_,
|
||||
preserve_deletes_seqnum_, manual_compaction_paused_, db_options_.info_log,
|
||||
full_history_ts_low));
|
||||
preserve_deletes_seqnum_, manual_compaction_paused_,
|
||||
manual_compaction_canceled_, db_options_.info_log, full_history_ts_low));
|
||||
auto c_iter = sub_compact->c_iter.get();
|
||||
c_iter->SeekToFirst();
|
||||
if (c_iter->Valid() && sub_compact->compaction->output_level() != 0) {
|
||||
@@ -1197,12 +1226,9 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
const Slice& key = c_iter->key();
|
||||
const Slice& value = c_iter->value();
|
||||
|
||||
// If an end key (exclusive) is specified, check if the current key is
|
||||
// >= than it and exit if it is because the iterator is out of its range
|
||||
if (end != nullptr &&
|
||||
cfd->user_comparator()->Compare(c_iter->user_key(), *end) >= 0) {
|
||||
break;
|
||||
}
|
||||
assert(!end ||
|
||||
cfd->user_comparator()->Compare(c_iter->user_key(), *end) < 0);
|
||||
|
||||
if (c_iter_stats.num_input_records % kRecordStatsEvery ==
|
||||
kRecordStatsEvery - 1) {
|
||||
RecordDroppedKeys(c_iter_stats, &sub_compact->compaction_job_stats);
|
||||
@@ -1317,8 +1343,10 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
status = Status::ShutdownInProgress("Database shutdown");
|
||||
}
|
||||
if ((status.ok() || status.IsColumnFamilyDropped()) &&
|
||||
(manual_compaction_paused_ &&
|
||||
manual_compaction_paused_->load(std::memory_order_relaxed) > 0)) {
|
||||
((manual_compaction_paused_ &&
|
||||
manual_compaction_paused_->load(std::memory_order_relaxed) > 0) ||
|
||||
(manual_compaction_canceled_ &&
|
||||
manual_compaction_canceled_->load(std::memory_order_relaxed)))) {
|
||||
status = Status::Incomplete(Status::SubCode::kManualCompactionPaused);
|
||||
}
|
||||
if (status.ok()) {
|
||||
@@ -1387,7 +1415,8 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
#endif // ROCKSDB_ASSERT_STATUS_CHECKED
|
||||
|
||||
sub_compact->c_iter.reset();
|
||||
input.reset();
|
||||
clip.reset();
|
||||
raw_input.reset();
|
||||
sub_compact->status = status;
|
||||
}
|
||||
|
||||
@@ -1670,7 +1699,7 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
// This happens when the output level is bottom level, at the same time
|
||||
// the sub_compact output nothing.
|
||||
std::string fname =
|
||||
TableFileName(sub_compact->compaction->immutable_cf_options()->cf_paths,
|
||||
TableFileName(sub_compact->compaction->immutable_options()->cf_paths,
|
||||
meta->fd.GetNumber(), meta->fd.GetPathId());
|
||||
|
||||
// TODO(AR) it is not clear if there are any larger implications if
|
||||
@@ -1914,7 +1943,7 @@ Status CompactionJob::OpenCompactionOutputFile(
|
||||
writable_file->SetPreallocationBlockSize(static_cast<size_t>(
|
||||
sub_compact->compaction->OutputFilePreallocationSize()));
|
||||
const auto& listeners =
|
||||
sub_compact->compaction->immutable_cf_options()->listeners;
|
||||
sub_compact->compaction->immutable_options()->listeners;
|
||||
sub_compact->outfile.reset(new WritableFileWriter(
|
||||
std::move(writable_file), fname, file_options_, db_options_.clock,
|
||||
io_tracer_, db_options_.stats, listeners,
|
||||
@@ -1929,7 +1958,8 @@ Status CompactionJob::OpenCompactionOutputFile(
|
||||
cfd->GetName(), sub_compact->compaction->output_level(),
|
||||
bottommost_level_, TableFileCreationReason::kCompaction,
|
||||
oldest_ancester_time, 0 /* oldest_key_time */, current_time, db_id_,
|
||||
db_session_id_, sub_compact->compaction->max_output_file_size());
|
||||
db_session_id_, sub_compact->compaction->max_output_file_size(),
|
||||
file_number);
|
||||
sub_compact->builder.reset(
|
||||
NewTableBuilder(tboptions, sub_compact->outfile.get()));
|
||||
LogFlush(db_options_.info_log);
|
||||
@@ -2095,7 +2125,7 @@ void CompactionJob::LogCompaction() {
|
||||
}
|
||||
|
||||
std::string CompactionJob::GetTableFileName(uint64_t file_number) {
|
||||
return TableFileName(compact_->compaction->immutable_cf_options()->cf_paths,
|
||||
return TableFileName(compact_->compaction->immutable_options()->cf_paths,
|
||||
file_number, compact_->compaction->output_path_id());
|
||||
}
|
||||
|
||||
@@ -2126,7 +2156,7 @@ CompactionServiceCompactionJob::CompactionServiceCompactionJob(
|
||||
compaction->mutable_cf_options()->paranoid_file_checks,
|
||||
compaction->mutable_cf_options()->report_bg_io_stats, dbname,
|
||||
&(compaction_service_result->stats), Env::Priority::USER, io_tracer,
|
||||
nullptr, db_id, db_session_id,
|
||||
nullptr, nullptr, db_id, db_session_id,
|
||||
compaction->column_family_data()->GetFullHistoryTsLow()),
|
||||
output_path_(output_path),
|
||||
compaction_input_(compaction_service_input),
|
||||
|
||||
@@ -81,6 +81,7 @@ class CompactionJob {
|
||||
const std::string& dbname, CompactionJobStats* compaction_job_stats,
|
||||
Env::Priority thread_pri, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::atomic<int>* manual_compaction_paused = nullptr,
|
||||
const std::atomic<bool>* manual_compaction_canceled = nullptr,
|
||||
const std::string& db_id = "", const std::string& db_session_id = "",
|
||||
std::string full_history_ts_low = "",
|
||||
BlobFileCompletionCallback* blob_callback = nullptr);
|
||||
@@ -185,6 +186,7 @@ class CompactionJob {
|
||||
VersionSet* versions_;
|
||||
const std::atomic<bool>* shutting_down_;
|
||||
const std::atomic<int>* manual_compaction_paused_;
|
||||
const std::atomic<bool>* manual_compaction_canceled_;
|
||||
const SequenceNumber preserve_deletes_seqnum_;
|
||||
FSDirectory* db_directory_;
|
||||
FSDirectory* blob_output_directory_;
|
||||
|
||||
@@ -82,10 +82,11 @@ class CompactionJobTestBase : public testing::Test {
|
||||
mutable_db_options_(),
|
||||
table_cache_(NewLRUCache(50000, 16)),
|
||||
write_buffer_manager_(db_options_.db_write_buffer_size),
|
||||
versions_(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr)),
|
||||
versions_(new VersionSet(dbname_, &db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_,
|
||||
&write_controller_,
|
||||
/*block_cache_tracer=*/nullptr,
|
||||
/*io_tracer=*/nullptr, /*db_session_id*/ "")),
|
||||
shutting_down_(false),
|
||||
preserve_deletes_seqnum_(0),
|
||||
mock_table_factory_(new mock::MockTableFactory()),
|
||||
@@ -269,7 +270,8 @@ class CompactionJobTestBase : public testing::Test {
|
||||
versions_.reset(
|
||||
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr));
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_session_id*/ ""));
|
||||
compaction_job_stats_.Reset();
|
||||
ASSERT_OK(SetIdentityFile(env_, dbname_));
|
||||
|
||||
@@ -350,7 +352,8 @@ class CompactionJobTestBase : public testing::Test {
|
||||
earliest_write_conflict_snapshot, snapshot_checker, table_cache_,
|
||||
&event_logger, false, false, dbname_, &compaction_job_stats_,
|
||||
Env::Priority::USER, nullptr /* IOTracer */,
|
||||
/*manual_compaction_paused=*/nullptr, /*db_id=*/"",
|
||||
/*manual_compaction_paused=*/nullptr,
|
||||
/*manual_compaction_canceled=*/nullptr, /*db_id=*/"",
|
||||
/*db_session_id=*/"", full_history_ts_low_);
|
||||
VerifyInitializationOfCompactionJobStats(compaction_job_stats_);
|
||||
|
||||
|
||||
@@ -16,6 +16,10 @@ class MyTestCompactionService : public CompactionService {
|
||||
std::shared_ptr<FileSystem> fs, Options& options)
|
||||
: db_path_(db_path), fs_(fs), options_(options) {}
|
||||
|
||||
static const char* kClassName() { return "MyTestCompactionService"; }
|
||||
|
||||
const char* Name() const override { return kClassName(); }
|
||||
|
||||
CompactionServiceJobStatus Start(const std::string& compaction_service_input,
|
||||
int job_id) override {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
@@ -51,9 +55,9 @@ class MyTestCompactionService : public CompactionService {
|
||||
options_override.table_factory = options_.table_factory;
|
||||
options_override.sst_partitioner_factory = options_.sst_partitioner_factory;
|
||||
|
||||
Status s = DB::OpenAndCompact(db_path_, db_path_ + "/" + ToString(job_id),
|
||||
compaction_input, compaction_service_result,
|
||||
options_override);
|
||||
Status s = DB::OpenAndCompact(
|
||||
db_path_, db_path_ + "/" + ROCKSDB_NAMESPACE::ToString(job_id),
|
||||
compaction_input, compaction_service_result, options_override);
|
||||
TEST_SYNC_POINT_CALLBACK("MyTestCompactionService::WaitForComplete::End",
|
||||
compaction_service_result);
|
||||
compaction_num_.fetch_add(1);
|
||||
|
||||
@@ -55,14 +55,9 @@ class CorruptionTest : public testing::Test {
|
||||
// bug in recovery code. Keep it 4 for now to make the test passes.
|
||||
tiny_cache_ = NewLRUCache(100, 4);
|
||||
Env* base_env = Env::Default();
|
||||
#ifndef ROCKSDB_LITE
|
||||
const char* test_env_uri = getenv("TEST_ENV_URI");
|
||||
if (test_env_uri) {
|
||||
Status s = Env::LoadEnv(test_env_uri, &base_env, &env_guard_);
|
||||
EXPECT_OK(s);
|
||||
EXPECT_NE(Env::Default(), base_env);
|
||||
}
|
||||
#endif //! ROCKSDB_LITE
|
||||
EXPECT_OK(
|
||||
test::CreateEnvFromSystem(ConfigOptions(), &base_env, &env_guard_));
|
||||
EXPECT_NE(base_env, nullptr);
|
||||
env_ = new test::ErrorEnv(base_env);
|
||||
options_.wal_recovery_mode = WALRecoveryMode::kTolerateCorruptedTailRecords;
|
||||
options_.env = env_;
|
||||
|
||||
@@ -257,12 +257,28 @@ TEST_F(DBBlockCacheTest, TestWithoutCompressedBlockCache) {
|
||||
|
||||
#ifdef SNAPPY
|
||||
TEST_F(DBBlockCacheTest, TestWithCompressedBlockCache) {
|
||||
ReadOptions read_options;
|
||||
auto table_options = GetTableOptions();
|
||||
auto options = GetOptions(table_options);
|
||||
options.compression = CompressionType::kSnappyCompression;
|
||||
InitTable(options);
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.no_block_cache = true;
|
||||
table_options.block_cache_compressed = nullptr;
|
||||
table_options.block_size = 1;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(20));
|
||||
table_options.cache_index_and_filter_blocks = false;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.compression = CompressionType::kSnappyCompression;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
std::string value(kValueSize, 'a');
|
||||
for (size_t i = 0; i < kNumBlocks; i++) {
|
||||
ASSERT_OK(Put(ToString(i), value));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
ReadOptions read_options;
|
||||
std::shared_ptr<Cache> compressed_cache = NewLRUCache(1 << 25, 0, false);
|
||||
LRUCacheOptions co;
|
||||
co.capacity = 0;
|
||||
@@ -272,25 +288,23 @@ TEST_F(DBBlockCacheTest, TestWithCompressedBlockCache) {
|
||||
co.metadata_charge_policy = kDontChargeCacheMetadata;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(co);
|
||||
table_options.block_cache = cache;
|
||||
table_options.no_block_cache = false;
|
||||
table_options.block_cache_compressed = compressed_cache;
|
||||
table_options.max_auto_readahead_size = 0;
|
||||
table_options.cache_index_and_filter_blocks = false;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
Reopen(options);
|
||||
RecordCacheCounters(options);
|
||||
|
||||
std::vector<std::unique_ptr<Iterator>> iterators(kNumBlocks - 1);
|
||||
Iterator* iter = nullptr;
|
||||
|
||||
// Load blocks into cache.
|
||||
for (size_t i = 0; i + 1 < kNumBlocks; i++) {
|
||||
iter = db_->NewIterator(read_options);
|
||||
iter->Seek(ToString(i));
|
||||
ASSERT_OK(iter->status());
|
||||
for (size_t i = 0; i < kNumBlocks - 1; i++) {
|
||||
ASSERT_EQ(value, Get(ToString(i)));
|
||||
CheckCacheCounters(options, 1, 0, 1, 0);
|
||||
CheckCompressedCacheCounters(options, 1, 0, 1, 0);
|
||||
iterators[i].reset(iter);
|
||||
}
|
||||
|
||||
size_t usage = cache->GetUsage();
|
||||
ASSERT_LT(0, usage);
|
||||
ASSERT_EQ(0, usage);
|
||||
ASSERT_EQ(usage, cache->GetPinnedUsage());
|
||||
size_t compressed_usage = compressed_cache->GetUsage();
|
||||
ASSERT_LT(0, compressed_usage);
|
||||
@@ -302,24 +316,21 @@ TEST_F(DBBlockCacheTest, TestWithCompressedBlockCache) {
|
||||
cache->SetCapacity(usage);
|
||||
cache->SetStrictCapacityLimit(true);
|
||||
ASSERT_EQ(usage, cache->GetPinnedUsage());
|
||||
iter = db_->NewIterator(read_options);
|
||||
iter->Seek(ToString(kNumBlocks - 1));
|
||||
ASSERT_TRUE(iter->status().IsIncomplete());
|
||||
CheckCacheCounters(options, 1, 0, 0, 1);
|
||||
|
||||
// Load last key block.
|
||||
ASSERT_EQ("Result incomplete: Insert failed due to LRU cache being full.",
|
||||
Get(ToString(kNumBlocks - 1)));
|
||||
// Failure won't record the miss counter.
|
||||
CheckCacheCounters(options, 0, 0, 0, 1);
|
||||
CheckCompressedCacheCounters(options, 1, 0, 1, 0);
|
||||
delete iter;
|
||||
iter = nullptr;
|
||||
|
||||
// Clear strict capacity limit flag. This time we shall hit compressed block
|
||||
// cache.
|
||||
// cache and load into block cache.
|
||||
cache->SetStrictCapacityLimit(false);
|
||||
iter = db_->NewIterator(read_options);
|
||||
iter->Seek(ToString(kNumBlocks - 1));
|
||||
ASSERT_OK(iter->status());
|
||||
// Load last key block.
|
||||
ASSERT_EQ(value, Get(ToString(kNumBlocks - 1)));
|
||||
CheckCacheCounters(options, 1, 0, 1, 0);
|
||||
CheckCompressedCacheCounters(options, 0, 1, 0, 0);
|
||||
delete iter;
|
||||
iter = nullptr;
|
||||
}
|
||||
#endif // SNAPPY
|
||||
|
||||
@@ -467,6 +478,33 @@ TEST_F(DBBlockCacheTest, IndexAndFilterBlocksStats) {
|
||||
// filter_bytes_insert);
|
||||
}
|
||||
|
||||
#if (defined OS_LINUX || defined OS_WIN)
|
||||
TEST_F(DBBlockCacheTest, WarmCacheWithDataBlocksDuringFlush) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_cache = NewLRUCache(1 << 25, 0, false);
|
||||
table_options.cache_index_and_filter_blocks = false;
|
||||
table_options.prepopulate_block_cache =
|
||||
BlockBasedTableOptions::PrepopulateBlockCache::kFlushOnly;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
std::string value(kValueSize, 'a');
|
||||
for (size_t i = 1; i <= kNumBlocks; i++) {
|
||||
ASSERT_OK(Put(ToString(i), value));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(i, options.statistics->getTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
|
||||
ASSERT_EQ(value, Get(ToString(i)));
|
||||
ASSERT_EQ(0, options.statistics->getTickerCount(BLOCK_CACHE_DATA_MISS));
|
||||
ASSERT_EQ(i, options.statistics->getTickerCount(BLOCK_CACHE_DATA_HIT));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
// A mock cache wraps LRUCache, and record how many entries have been
|
||||
|
||||
@@ -3600,6 +3600,41 @@ TEST_F(DBCompactionTest, CompactFilesOverlapInL0Bug) {
|
||||
ASSERT_EQ("new_val", Get(Key(0)));
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, DeleteFilesInRangeConflictWithCompaction) {
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
const Snapshot* snapshot = nullptr;
|
||||
const int kMaxKey = 10;
|
||||
|
||||
for (int i = 0; i < kMaxKey; i++) {
|
||||
ASSERT_OK(Put(Key(i), Key(i)));
|
||||
ASSERT_OK(Delete(Key(i)));
|
||||
if (!snapshot) {
|
||||
snapshot = db_->GetSnapshot();
|
||||
}
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(1);
|
||||
ASSERT_OK(Put(Key(kMaxKey), Key(kMaxKey)));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
// test DeleteFilesInRange() deletes the files already picked for compaction
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"VersionSet::LogAndApply:WriteManifestStart",
|
||||
"BackgroundCallCompaction:0"},
|
||||
{"DBImpl::BackgroundCompaction:Finish",
|
||||
"VersionSet::LogAndApply:WriteManifestDone"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// release snapshot which mark bottommost file for compaction
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
std::string begin_string = Key(0);
|
||||
std::string end_string = Key(kMaxKey + 1);
|
||||
Slice begin(begin_string);
|
||||
Slice end(end_string);
|
||||
ASSERT_OK(DeleteFilesInRange(db_, db_->DefaultColumnFamily(), &begin, &end));
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, CompactBottomLevelFilesWithDeletions) {
|
||||
// bottom-level files may contain deletions due to snapshots protecting the
|
||||
// deleted keys. Once the snapshot is released, we should see files with many
|
||||
@@ -5208,11 +5243,12 @@ TEST_F(DBCompactionTest, ManualCompactionMax) {
|
||||
ASSERT_TRUE(num_compactions.load() == 1);
|
||||
|
||||
// split the compaction to 5
|
||||
uint64_t total = (l1_avg_size * 10) + (l2_avg_size * 100);
|
||||
int num_split = 5;
|
||||
opts.max_compaction_bytes = total / num_split;
|
||||
DestroyAndReopen(opts);
|
||||
generate_sst_func();
|
||||
uint64_t total_size = (l1_avg_size * 10) + (l2_avg_size * 100);
|
||||
opts.max_compaction_bytes = total_size / num_split;
|
||||
Reopen(opts);
|
||||
num_compactions.store(0);
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
ASSERT_TRUE(num_compactions.load() == num_split);
|
||||
@@ -5230,8 +5266,9 @@ TEST_F(DBCompactionTest, ManualCompactionMax) {
|
||||
opts.max_compaction_bytes = 0;
|
||||
DestroyAndReopen(opts);
|
||||
generate_sst_func();
|
||||
total_size = (l1_avg_size * 10) + (l2_avg_size * 100);
|
||||
Status s = db_->SetOptions(
|
||||
{{"max_compaction_bytes", std::to_string(total / num_split)}});
|
||||
{{"max_compaction_bytes", std::to_string(total_size / num_split)}});
|
||||
ASSERT_OK(s);
|
||||
|
||||
num_compactions.store(0);
|
||||
|
||||
@@ -285,6 +285,379 @@ TEST_F(DBFlushTest, ScheduleOnlyOneBgThread) {
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
// The following 3 tests are designed for testing garbage statistics at flush
|
||||
// time.
|
||||
//
|
||||
// ======= General Information ======= (from GitHub Wiki).
|
||||
// There are three scenarios where memtable flush can be triggered:
|
||||
//
|
||||
// 1 - Memtable size exceeds ColumnFamilyOptions::write_buffer_size
|
||||
// after a write.
|
||||
// 2 - Total memtable size across all column families exceeds
|
||||
// DBOptions::db_write_buffer_size,
|
||||
// or DBOptions::write_buffer_manager signals a flush. In this scenario
|
||||
// the largest memtable will be flushed.
|
||||
// 3 - Total WAL file size exceeds DBOptions::max_total_wal_size.
|
||||
// In this scenario the memtable with the oldest data will be flushed,
|
||||
// in order to allow the WAL file with data from this memtable to be
|
||||
// purged.
|
||||
//
|
||||
// As a result, a memtable can be flushed before it is full. This is one
|
||||
// reason the generated SST file can be smaller than the corresponding
|
||||
// memtable. Compression is another factor to make SST file smaller than
|
||||
// corresponding memtable, since data in memtable is uncompressed.
|
||||
|
||||
TEST_F(DBFlushTest, StatisticsGarbageBasic) {
|
||||
Options options = CurrentOptions();
|
||||
|
||||
// The following options are used to enforce several values that
|
||||
// may already exist as default values to make this test resilient
|
||||
// to default value updates in the future.
|
||||
options.statistics = CreateDBStatistics();
|
||||
|
||||
// Record all statistics.
|
||||
options.statistics->set_stats_level(StatsLevel::kAll);
|
||||
|
||||
// create the DB if it's not already present
|
||||
options.create_if_missing = true;
|
||||
|
||||
// Useful for now as we are trying to compare uncompressed data savings on
|
||||
// flush().
|
||||
options.compression = kNoCompression;
|
||||
|
||||
// Prevent memtable in place updates. Should already be disabled
|
||||
// (from Wiki:
|
||||
// In place updates can be enabled by toggling on the bool
|
||||
// inplace_update_support flag. However, this flag is by default set to
|
||||
// false
|
||||
// because this thread-safe in-place update support is not compatible
|
||||
// with concurrent memtable writes. Note that the bool
|
||||
// allow_concurrent_memtable_write is set to true by default )
|
||||
options.inplace_update_support = false;
|
||||
options.allow_concurrent_memtable_write = true;
|
||||
|
||||
// Enforce size of a single MemTable to 64MB (64MB = 67108864 bytes).
|
||||
options.write_buffer_size = 64 << 20;
|
||||
|
||||
ASSERT_OK(TryReopen(options));
|
||||
|
||||
// Put multiple times the same key-values.
|
||||
// The encoded length of a db entry in the memtable is
|
||||
// defined in db/memtable.cc (MemTable::Add) as the variable:
|
||||
// encoded_len= VarintLength(internal_key_size) --> =
|
||||
// log_256(internal_key).
|
||||
// Min # of bytes
|
||||
// necessary to
|
||||
// store
|
||||
// internal_key_size.
|
||||
// + internal_key_size --> = actual key string,
|
||||
// (size key_size: w/o term null char)
|
||||
// + 8 bytes for
|
||||
// fixed uint64 "seq
|
||||
// number
|
||||
// +
|
||||
// insertion type"
|
||||
// + VarintLength(val_size) --> = min # of bytes to
|
||||
// store val_size
|
||||
// + val_size --> = actual value
|
||||
// string
|
||||
// For example, in our situation, "key1" : size 4, "value1" : size 6
|
||||
// (the terminating null characters are not copied over to the memtable).
|
||||
// And therefore encoded_len = 1 + (4+8) + 1 + 6 = 20 bytes per entry.
|
||||
// However in terms of raw data contained in the memtable, and written
|
||||
// over to the SSTable, we only count internal_key_size and val_size,
|
||||
// because this is the only raw chunk of bytes that contains everything
|
||||
// necessary to reconstruct a user entry: sequence number, insertion type,
|
||||
// key, and value.
|
||||
|
||||
// To test the relevance of our Memtable garbage statistics,
|
||||
// namely MEMTABLE_PAYLOAD_BYTES_AT_FLUSH and MEMTABLE_GARBAGE_BYTES_AT_FLUSH,
|
||||
// we insert K-V pairs with 3 distinct keys (of length 4),
|
||||
// and random values of arbitrary length RAND_VALUES_LENGTH,
|
||||
// and we repeat this step NUM_REPEAT times total.
|
||||
// At the end, we insert 3 final K-V pairs with the same 3 keys
|
||||
// and known values (these will be the final values, of length 6).
|
||||
// I chose NUM_REPEAT=2,000 such that no automatic flush is
|
||||
// triggered (the number of bytes in the memtable is therefore
|
||||
// well below any meaningful heuristic for a memtable of size 64MB).
|
||||
// As a result, since each K-V pair is inserted as a payload
|
||||
// of N meaningful bytes (sequence number, insertion type,
|
||||
// key, and value = 8 + 4 + RAND_VALUE_LENGTH),
|
||||
// MEMTABLE_GARBAGE_BYTES_AT_FLUSH should be equal to 2,000 * N bytes
|
||||
// and MEMTABLE_PAYLAOD_BYTES_AT_FLUSH = MEMTABLE_GARBAGE_BYTES_AT_FLUSH +
|
||||
// (3*(8 + 4 + 6)) bytes. For RAND_VALUE_LENGTH = 172 (arbitrary value), we
|
||||
// expect:
|
||||
// N = 8 + 4 + 172 = 184 bytes
|
||||
// MEMTABLE_GARBAGE_BYTES_AT_FLUSH = 2,000 * 184 = 368,000 bytes.
|
||||
// MEMTABLE_PAYLOAD_BYTES_AT_FLUSH = 368,000 + 3*18 = 368,054 bytes.
|
||||
|
||||
const size_t NUM_REPEAT = 2000;
|
||||
const size_t RAND_VALUES_LENGTH = 172;
|
||||
const std::string KEY1 = "key1";
|
||||
const std::string KEY2 = "key2";
|
||||
const std::string KEY3 = "key3";
|
||||
const std::string VALUE1 = "value1";
|
||||
const std::string VALUE2 = "value2";
|
||||
const std::string VALUE3 = "value3";
|
||||
uint64_t EXPECTED_MEMTABLE_PAYLOAD_BYTES_AT_FLUSH = 0;
|
||||
uint64_t EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH = 0;
|
||||
|
||||
Random rnd(301);
|
||||
// Insertion of of K-V pairs, multiple times.
|
||||
for (size_t i = 0; i < NUM_REPEAT; i++) {
|
||||
// Create value strings of arbitrary length RAND_VALUES_LENGTH bytes.
|
||||
std::string p_v1 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
std::string p_v2 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
std::string p_v3 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
ASSERT_OK(Put(KEY1, p_v1));
|
||||
ASSERT_OK(Put(KEY2, p_v2));
|
||||
ASSERT_OK(Put(KEY3, p_v3));
|
||||
EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH +=
|
||||
KEY1.size() + p_v1.size() + sizeof(uint64_t);
|
||||
EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH +=
|
||||
KEY2.size() + p_v2.size() + sizeof(uint64_t);
|
||||
EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH +=
|
||||
KEY3.size() + p_v3.size() + sizeof(uint64_t);
|
||||
}
|
||||
|
||||
// The memtable data bytes includes the "garbage"
|
||||
// bytes along with the useful payload.
|
||||
EXPECTED_MEMTABLE_PAYLOAD_BYTES_AT_FLUSH =
|
||||
EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH;
|
||||
|
||||
ASSERT_OK(Put(KEY1, VALUE1));
|
||||
ASSERT_OK(Put(KEY2, VALUE2));
|
||||
ASSERT_OK(Put(KEY3, VALUE3));
|
||||
|
||||
// Add useful payload to the memtable data bytes:
|
||||
EXPECTED_MEMTABLE_PAYLOAD_BYTES_AT_FLUSH +=
|
||||
KEY1.size() + VALUE1.size() + KEY2.size() + VALUE2.size() + KEY3.size() +
|
||||
VALUE3.size() + 3 * sizeof(uint64_t);
|
||||
|
||||
// We assert that the last K-V pairs have been successfully inserted,
|
||||
// and that the valid values are VALUE1, VALUE2, VALUE3.
|
||||
PinnableSlice value;
|
||||
ASSERT_OK(Get(KEY1, &value));
|
||||
ASSERT_EQ(value.ToString(), VALUE1);
|
||||
ASSERT_OK(Get(KEY2, &value));
|
||||
ASSERT_EQ(value.ToString(), VALUE2);
|
||||
ASSERT_OK(Get(KEY3, &value));
|
||||
ASSERT_EQ(value.ToString(), VALUE3);
|
||||
|
||||
// Force flush to SST. Increments the statistics counter.
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Collect statistics.
|
||||
uint64_t mem_data_bytes =
|
||||
TestGetTickerCount(options, MEMTABLE_PAYLOAD_BYTES_AT_FLUSH);
|
||||
uint64_t mem_garbage_bytes =
|
||||
TestGetTickerCount(options, MEMTABLE_GARBAGE_BYTES_AT_FLUSH);
|
||||
|
||||
EXPECT_EQ(mem_data_bytes, EXPECTED_MEMTABLE_PAYLOAD_BYTES_AT_FLUSH);
|
||||
EXPECT_EQ(mem_garbage_bytes, EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH);
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBFlushTest, StatisticsGarbageInsertAndDeletes) {
|
||||
Options options = CurrentOptions();
|
||||
options.statistics = CreateDBStatistics();
|
||||
options.statistics->set_stats_level(StatsLevel::kAll);
|
||||
options.create_if_missing = true;
|
||||
options.compression = kNoCompression;
|
||||
options.inplace_update_support = false;
|
||||
options.allow_concurrent_memtable_write = true;
|
||||
options.write_buffer_size = 67108864;
|
||||
|
||||
ASSERT_OK(TryReopen(options));
|
||||
|
||||
const size_t NUM_REPEAT = 2000;
|
||||
const size_t RAND_VALUES_LENGTH = 37;
|
||||
const std::string KEY1 = "key1";
|
||||
const std::string KEY2 = "key2";
|
||||
const std::string KEY3 = "key3";
|
||||
const std::string KEY4 = "key4";
|
||||
const std::string KEY5 = "key5";
|
||||
const std::string KEY6 = "key6";
|
||||
|
||||
uint64_t EXPECTED_MEMTABLE_PAYLOAD_BYTES_AT_FLUSH = 0;
|
||||
uint64_t EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH = 0;
|
||||
|
||||
WriteBatch batch;
|
||||
|
||||
Random rnd(301);
|
||||
// Insertion of of K-V pairs, multiple times.
|
||||
for (size_t i = 0; i < NUM_REPEAT; i++) {
|
||||
// Create value strings of arbitrary length RAND_VALUES_LENGTH bytes.
|
||||
std::string p_v1 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
std::string p_v2 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
std::string p_v3 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
ASSERT_OK(Put(KEY1, p_v1));
|
||||
ASSERT_OK(Put(KEY2, p_v2));
|
||||
ASSERT_OK(Put(KEY3, p_v3));
|
||||
EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH +=
|
||||
KEY1.size() + p_v1.size() + sizeof(uint64_t);
|
||||
EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH +=
|
||||
KEY2.size() + p_v2.size() + sizeof(uint64_t);
|
||||
EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH +=
|
||||
KEY3.size() + p_v3.size() + sizeof(uint64_t);
|
||||
ASSERT_OK(Delete(KEY1));
|
||||
ASSERT_OK(Delete(KEY2));
|
||||
ASSERT_OK(Delete(KEY3));
|
||||
EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH +=
|
||||
KEY1.size() + KEY2.size() + KEY3.size() + 3 * sizeof(uint64_t);
|
||||
}
|
||||
|
||||
// The memtable data bytes includes the "garbage"
|
||||
// bytes along with the useful payload.
|
||||
EXPECTED_MEMTABLE_PAYLOAD_BYTES_AT_FLUSH =
|
||||
EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH;
|
||||
|
||||
// Note : one set of delete for KEY1, KEY2, KEY3 is written to
|
||||
// SSTable to propagate the delete operations to K-V pairs
|
||||
// that could have been inserted into the database during past Flush
|
||||
// opeartions.
|
||||
EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH -=
|
||||
KEY1.size() + KEY2.size() + KEY3.size() + 3 * sizeof(uint64_t);
|
||||
|
||||
// Additional useful paylaod.
|
||||
ASSERT_OK(Delete(KEY4));
|
||||
ASSERT_OK(Delete(KEY5));
|
||||
ASSERT_OK(Delete(KEY6));
|
||||
|
||||
// // Add useful payload to the memtable data bytes:
|
||||
EXPECTED_MEMTABLE_PAYLOAD_BYTES_AT_FLUSH +=
|
||||
KEY4.size() + KEY5.size() + KEY6.size() + 3 * sizeof(uint64_t);
|
||||
|
||||
// We assert that the K-V pairs have been successfully deleted.
|
||||
PinnableSlice value;
|
||||
ASSERT_NOK(Get(KEY1, &value));
|
||||
ASSERT_NOK(Get(KEY2, &value));
|
||||
ASSERT_NOK(Get(KEY3, &value));
|
||||
|
||||
// Force flush to SST. Increments the statistics counter.
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Collect statistics.
|
||||
uint64_t mem_data_bytes =
|
||||
TestGetTickerCount(options, MEMTABLE_PAYLOAD_BYTES_AT_FLUSH);
|
||||
uint64_t mem_garbage_bytes =
|
||||
TestGetTickerCount(options, MEMTABLE_GARBAGE_BYTES_AT_FLUSH);
|
||||
|
||||
EXPECT_EQ(mem_data_bytes, EXPECTED_MEMTABLE_PAYLOAD_BYTES_AT_FLUSH);
|
||||
EXPECT_EQ(mem_garbage_bytes, EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH);
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBFlushTest, StatisticsGarbageRangeDeletes) {
|
||||
Options options = CurrentOptions();
|
||||
options.statistics = CreateDBStatistics();
|
||||
options.statistics->set_stats_level(StatsLevel::kAll);
|
||||
options.create_if_missing = true;
|
||||
options.compression = kNoCompression;
|
||||
options.inplace_update_support = false;
|
||||
options.allow_concurrent_memtable_write = true;
|
||||
options.write_buffer_size = 67108864;
|
||||
|
||||
ASSERT_OK(TryReopen(options));
|
||||
|
||||
const size_t NUM_REPEAT = 1000;
|
||||
const size_t RAND_VALUES_LENGTH = 42;
|
||||
const std::string KEY1 = "key1";
|
||||
const std::string KEY2 = "key2";
|
||||
const std::string KEY3 = "key3";
|
||||
const std::string KEY4 = "key4";
|
||||
const std::string KEY5 = "key5";
|
||||
const std::string KEY6 = "key6";
|
||||
const std::string VALUE3 = "value3";
|
||||
|
||||
uint64_t EXPECTED_MEMTABLE_PAYLOAD_BYTES_AT_FLUSH = 0;
|
||||
uint64_t EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH = 0;
|
||||
|
||||
Random rnd(301);
|
||||
// Insertion of of K-V pairs, multiple times.
|
||||
// Also insert DeleteRange
|
||||
for (size_t i = 0; i < NUM_REPEAT; i++) {
|
||||
// Create value strings of arbitrary length RAND_VALUES_LENGTH bytes.
|
||||
std::string p_v1 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
std::string p_v2 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
std::string p_v3 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
ASSERT_OK(Put(KEY1, p_v1));
|
||||
ASSERT_OK(Put(KEY2, p_v2));
|
||||
ASSERT_OK(Put(KEY3, p_v3));
|
||||
EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH +=
|
||||
KEY1.size() + p_v1.size() + sizeof(uint64_t);
|
||||
EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH +=
|
||||
KEY2.size() + p_v2.size() + sizeof(uint64_t);
|
||||
EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH +=
|
||||
KEY3.size() + p_v3.size() + sizeof(uint64_t);
|
||||
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), KEY1,
|
||||
KEY2));
|
||||
// Note: DeleteRange have an exclusive upper bound, e.g. here: [KEY2,KEY3)
|
||||
// is deleted.
|
||||
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), KEY2,
|
||||
KEY3));
|
||||
// Delete ranges are stored as a regular K-V pair, with key=STARTKEY,
|
||||
// value=ENDKEY.
|
||||
EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH +=
|
||||
(KEY1.size() + KEY2.size() + sizeof(uint64_t)) +
|
||||
(KEY2.size() + KEY3.size() + sizeof(uint64_t));
|
||||
}
|
||||
|
||||
// The memtable data bytes includes the "garbage"
|
||||
// bytes along with the useful payload.
|
||||
EXPECTED_MEMTABLE_PAYLOAD_BYTES_AT_FLUSH =
|
||||
EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH;
|
||||
|
||||
// Note : one set of deleteRange for (KEY1, KEY2) and (KEY2, KEY3) is written
|
||||
// to SSTable to propagate the deleteRange operations to K-V pairs that could
|
||||
// have been inserted into the database during past Flush opeartions.
|
||||
EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH -=
|
||||
(KEY1.size() + KEY2.size() + sizeof(uint64_t)) +
|
||||
(KEY2.size() + KEY3.size() + sizeof(uint64_t));
|
||||
|
||||
// Overwrite KEY3 with known value (VALUE3)
|
||||
// Note that during the whole time KEY3 has never been deleted
|
||||
// by the RangeDeletes.
|
||||
ASSERT_OK(Put(KEY3, VALUE3));
|
||||
EXPECTED_MEMTABLE_PAYLOAD_BYTES_AT_FLUSH +=
|
||||
KEY3.size() + VALUE3.size() + sizeof(uint64_t);
|
||||
|
||||
// Additional useful paylaod.
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), KEY4, KEY5));
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), KEY5, KEY6));
|
||||
|
||||
// Add useful payload to the memtable data bytes:
|
||||
EXPECTED_MEMTABLE_PAYLOAD_BYTES_AT_FLUSH +=
|
||||
(KEY4.size() + KEY5.size() + sizeof(uint64_t)) +
|
||||
(KEY5.size() + KEY6.size() + sizeof(uint64_t));
|
||||
|
||||
// We assert that the K-V pairs have been successfully deleted.
|
||||
PinnableSlice value;
|
||||
ASSERT_NOK(Get(KEY1, &value));
|
||||
ASSERT_NOK(Get(KEY2, &value));
|
||||
// And that KEY3's value is correct.
|
||||
ASSERT_OK(Get(KEY3, &value));
|
||||
ASSERT_EQ(value, VALUE3);
|
||||
|
||||
// Force flush to SST. Increments the statistics counter.
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Collect statistics.
|
||||
uint64_t mem_data_bytes =
|
||||
TestGetTickerCount(options, MEMTABLE_PAYLOAD_BYTES_AT_FLUSH);
|
||||
uint64_t mem_garbage_bytes =
|
||||
TestGetTickerCount(options, MEMTABLE_GARBAGE_BYTES_AT_FLUSH);
|
||||
|
||||
EXPECT_EQ(mem_data_bytes, EXPECTED_MEMTABLE_PAYLOAD_BYTES_AT_FLUSH);
|
||||
EXPECT_EQ(mem_garbage_bytes, EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH);
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_P(DBFlushDirectIOTest, DirectIO) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
|
||||
@@ -252,16 +252,17 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
co.num_shard_bits = immutable_db_options_.table_cache_numshardbits;
|
||||
co.metadata_charge_policy = kDontChargeCacheMetadata;
|
||||
table_cache_ = NewLRUCache(co);
|
||||
SetDbSessionId();
|
||||
assert(!db_session_id_.empty());
|
||||
|
||||
versions_.reset(new VersionSet(dbname_, &immutable_db_options_, file_options_,
|
||||
table_cache_.get(), write_buffer_manager_,
|
||||
&write_controller_, &block_cache_tracer_,
|
||||
io_tracer_));
|
||||
io_tracer_, db_session_id_));
|
||||
column_family_memtables_.reset(
|
||||
new ColumnFamilyMemTablesImpl(versions_->GetColumnFamilySet()));
|
||||
|
||||
DumpRocksDBBuildVersion(immutable_db_options_.info_log.get());
|
||||
SetDbSessionId();
|
||||
DumpDBFileSummary(immutable_db_options_, dbname_, db_session_id_);
|
||||
immutable_db_options_.Dump(immutable_db_options_.info_log.get());
|
||||
mutable_db_options_.Dump(immutable_db_options_.info_log.get());
|
||||
@@ -1894,6 +1895,16 @@ std::vector<Status> DBImpl::MultiGet(
|
||||
}
|
||||
#endif // NDEBUG
|
||||
|
||||
if (tracer_) {
|
||||
// TODO: This mutex should be removed later, to improve performance when
|
||||
// tracing is enabled.
|
||||
InstrumentedMutexLock lock(&trace_mutex_);
|
||||
if (tracer_) {
|
||||
// TODO: maybe handle the tracing status?
|
||||
tracer_->MultiGet(column_family, keys).PermitUncheckedError();
|
||||
}
|
||||
}
|
||||
|
||||
SequenceNumber consistent_seqnum;
|
||||
|
||||
std::unordered_map<uint32_t, MultiGetColumnFamilyData> multiget_cf_data(
|
||||
@@ -2205,6 +2216,16 @@ void DBImpl::MultiGet(const ReadOptions& read_options, const size_t num_keys,
|
||||
}
|
||||
#endif // NDEBUG
|
||||
|
||||
if (tracer_) {
|
||||
// TODO: This mutex should be removed later, to improve performance when
|
||||
// tracing is enabled.
|
||||
InstrumentedMutexLock lock(&trace_mutex_);
|
||||
if (tracer_) {
|
||||
// TODO: maybe handle the tracing status?
|
||||
tracer_->MultiGet(num_keys, column_families, keys).PermitUncheckedError();
|
||||
}
|
||||
}
|
||||
|
||||
autovector<KeyContext, MultiGetContext::MAX_BATCH_SIZE> key_context;
|
||||
autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE> sorted_keys;
|
||||
sorted_keys.resize(num_keys);
|
||||
@@ -2367,6 +2388,15 @@ void DBImpl::MultiGet(const ReadOptions& read_options,
|
||||
const Slice* keys, PinnableSlice* values,
|
||||
std::string* timestamps, Status* statuses,
|
||||
const bool sorted_input) {
|
||||
if (tracer_) {
|
||||
// TODO: This mutex should be removed later, to improve performance when
|
||||
// tracing is enabled.
|
||||
InstrumentedMutexLock lock(&trace_mutex_);
|
||||
if (tracer_) {
|
||||
// TODO: maybe handle the tracing status?
|
||||
tracer_->MultiGet(num_keys, column_family, keys).PermitUncheckedError();
|
||||
}
|
||||
}
|
||||
autovector<KeyContext, MultiGetContext::MAX_BATCH_SIZE> key_context;
|
||||
autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE> sorted_keys;
|
||||
sorted_keys.resize(num_keys);
|
||||
@@ -3704,6 +3734,8 @@ Status DBImpl::DeleteFilesInRanges(ColumnFamilyHandle* column_family,
|
||||
deleted_files.insert(level_file);
|
||||
level_file->being_compacted = true;
|
||||
}
|
||||
vstorage->ComputeCompactionScore(*cfd->ioptions(),
|
||||
*cfd->GetLatestMutableCFOptions());
|
||||
}
|
||||
}
|
||||
if (edit.GetDeletedFiles().empty()) {
|
||||
|
||||
@@ -1454,15 +1454,16 @@ class DBImpl : public DB {
|
||||
uint32_t output_path_id;
|
||||
Status status;
|
||||
bool done;
|
||||
bool in_progress; // compaction request being processed?
|
||||
bool incomplete; // only part of requested range compacted
|
||||
bool exclusive; // current behavior of only one manual
|
||||
bool disallow_trivial_move; // Force actual compaction to run
|
||||
const InternalKey* begin; // nullptr means beginning of key range
|
||||
const InternalKey* end; // nullptr means end of key range
|
||||
InternalKey* manual_end; // how far we are compacting
|
||||
InternalKey tmp_storage; // Used to keep track of compaction progress
|
||||
InternalKey tmp_storage1; // Used to keep track of compaction progress
|
||||
bool in_progress; // compaction request being processed?
|
||||
bool incomplete; // only part of requested range compacted
|
||||
bool exclusive; // current behavior of only one manual
|
||||
bool disallow_trivial_move; // Force actual compaction to run
|
||||
const InternalKey* begin; // nullptr means beginning of key range
|
||||
const InternalKey* end; // nullptr means end of key range
|
||||
InternalKey* manual_end; // how far we are compacting
|
||||
InternalKey tmp_storage; // Used to keep track of compaction progress
|
||||
InternalKey tmp_storage1; // Used to keep track of compaction progress
|
||||
std::atomic<bool>* canceled; // Compaction canceled by the user?
|
||||
};
|
||||
struct PrepickedCompaction {
|
||||
// background compaction takes ownership of `compaction`.
|
||||
|
||||
@@ -807,6 +807,10 @@ Status DBImpl::CompactRange(const CompactRangeOptions& options,
|
||||
return Status::Incomplete(Status::SubCode::kManualCompactionPaused);
|
||||
}
|
||||
|
||||
if (options.canceled && options.canceled->load(std::memory_order_acquire)) {
|
||||
return Status::Incomplete(Status::SubCode::kManualCompactionPaused);
|
||||
}
|
||||
|
||||
const Comparator* const ucmp = column_family->GetComparator();
|
||||
assert(ucmp);
|
||||
size_t ts_sz = ucmp->timestamp_size();
|
||||
@@ -1253,7 +1257,7 @@ Status DBImpl::CompactFilesImpl(
|
||||
c->mutable_cf_options()->paranoid_file_checks,
|
||||
c->mutable_cf_options()->report_bg_io_stats, dbname_,
|
||||
&compaction_job_stats, Env::Priority::USER, io_tracer_,
|
||||
&manual_compaction_paused_, db_id_, db_session_id_,
|
||||
&manual_compaction_paused_, nullptr, db_id_, db_session_id_,
|
||||
c->column_family_data()->GetFullHistoryTsLow());
|
||||
|
||||
// Creating a compaction influences the compaction score because the score
|
||||
@@ -1328,7 +1332,7 @@ Status DBImpl::CompactFilesImpl(
|
||||
if (output_file_names != nullptr) {
|
||||
for (const auto& newf : c->edit()->GetNewFiles()) {
|
||||
(*output_file_names)
|
||||
.push_back(TableFileName(c->immutable_cf_options()->cf_paths,
|
||||
.push_back(TableFileName(c->immutable_options()->cf_paths,
|
||||
newf.second.fd.GetNumber(),
|
||||
newf.second.fd.GetPathId()));
|
||||
}
|
||||
@@ -1426,10 +1430,13 @@ void DBImpl::NotifyOnCompactionCompleted(
|
||||
if (shutting_down_.load(std::memory_order_acquire)) {
|
||||
return;
|
||||
}
|
||||
// TODO: Should disabling manual compaction squash compaction completed
|
||||
// notifications that aren't the result of a shutdown?
|
||||
if (c->is_manual_compaction() &&
|
||||
manual_compaction_paused_.load(std::memory_order_acquire) > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Version* current = cfd->current();
|
||||
current->Ref();
|
||||
// release lock while notifying events
|
||||
@@ -1654,6 +1661,7 @@ Status DBImpl::RunManualCompaction(
|
||||
manual.incomplete = false;
|
||||
manual.exclusive = exclusive;
|
||||
manual.disallow_trivial_move = disallow_trivial_move;
|
||||
manual.canceled = compact_range_options.canceled;
|
||||
// For universal compaction, we enforce every manual compaction to compact
|
||||
// all files.
|
||||
if (begin == nullptr ||
|
||||
@@ -2819,6 +2827,9 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
} else if (is_manual &&
|
||||
manual_compaction_paused_.load(std::memory_order_acquire) > 0) {
|
||||
status = Status::Incomplete(Status::SubCode::kManualCompactionPaused);
|
||||
} else if (is_manual && manual_compaction->canceled &&
|
||||
manual_compaction->canceled->load(std::memory_order_acquire)) {
|
||||
status = Status::Incomplete(Status::SubCode::kManualCompactionPaused);
|
||||
}
|
||||
} else {
|
||||
status = error_handler_.GetBGError();
|
||||
@@ -2946,7 +2957,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
c->column_family_data()
|
||||
->current()
|
||||
->storage_info()
|
||||
->ComputeCompactionScore(*(c->immutable_cf_options()),
|
||||
->ComputeCompactionScore(*(c->immutable_options()),
|
||||
*(c->mutable_cf_options()));
|
||||
AddToCompactionQueue(cfd);
|
||||
++unscheduled_compactions_;
|
||||
@@ -3140,7 +3151,8 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
c->mutable_cf_options()->paranoid_file_checks,
|
||||
c->mutable_cf_options()->report_bg_io_stats, dbname_,
|
||||
&compaction_job_stats, thread_pri, io_tracer_,
|
||||
is_manual ? &manual_compaction_paused_ : nullptr, db_id_,
|
||||
is_manual ? &manual_compaction_paused_ : nullptr,
|
||||
is_manual ? manual_compaction->canceled : nullptr, db_id_,
|
||||
db_session_id_, c->column_family_data()->GetFullHistoryTsLow());
|
||||
compaction_job.Prepare();
|
||||
|
||||
@@ -3220,7 +3232,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
c->column_family_data()
|
||||
->current()
|
||||
->storage_info()
|
||||
->ComputeCompactionScore(*(c->immutable_cf_options()),
|
||||
->ComputeCompactionScore(*(c->immutable_options()),
|
||||
*(c->mutable_cf_options()));
|
||||
if (!cfd->queued_for_compaction()) {
|
||||
AddToCompactionQueue(cfd);
|
||||
@@ -3385,7 +3397,7 @@ void DBImpl::BuildCompactionJobInfo(
|
||||
for (const auto fmd : *c->inputs(i)) {
|
||||
const FileDescriptor& desc = fmd->fd;
|
||||
const uint64_t file_number = desc.GetNumber();
|
||||
auto fn = TableFileName(c->immutable_cf_options()->cf_paths, file_number,
|
||||
auto fn = TableFileName(c->immutable_options()->cf_paths, file_number,
|
||||
desc.GetPathId());
|
||||
compaction_job_info->input_files.push_back(fn);
|
||||
compaction_job_info->input_file_infos.push_back(CompactionFileInfo{
|
||||
@@ -3404,7 +3416,7 @@ void DBImpl::BuildCompactionJobInfo(
|
||||
const FileDescriptor& desc = meta.fd;
|
||||
const uint64_t file_number = desc.GetNumber();
|
||||
compaction_job_info->output_files.push_back(TableFileName(
|
||||
c->immutable_cf_options()->cf_paths, file_number, desc.GetPathId()));
|
||||
c->immutable_options()->cf_paths, file_number, desc.GetPathId()));
|
||||
compaction_job_info->output_file_infos.push_back(CompactionFileInfo{
|
||||
newf.first, file_number, meta.oldest_blob_file_number});
|
||||
}
|
||||
|
||||
@@ -1259,7 +1259,8 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& wal_numbers,
|
||||
// If there's no data in the WAL, or we flushed all the data, still
|
||||
// truncate the log file. If the process goes into a crash loop before
|
||||
// the file is deleted, the preallocated space will never get freed.
|
||||
GetLogSizeAndMaybeTruncate(wal_numbers.back(), true, nullptr)
|
||||
const bool truncate = !read_only;
|
||||
GetLogSizeAndMaybeTruncate(wal_numbers.back(), truncate, nullptr)
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
}
|
||||
@@ -1292,7 +1293,7 @@ Status DBImpl::GetLogSizeAndMaybeTruncate(uint64_t wal_number, bool truncate,
|
||||
truncate_status = last_log->Close(IOOptions(), nullptr);
|
||||
}
|
||||
// Not a critical error if fail to truncate.
|
||||
if (!truncate_status.ok()) {
|
||||
if (!truncate_status.ok() && !truncate_status.IsNotSupported()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"Failed to truncate log #%" PRIu64 ": %s", wal_number,
|
||||
truncate_status.ToString().c_str());
|
||||
@@ -1401,7 +1402,7 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
0 /* level */, false /* is_bottommost */,
|
||||
TableFileCreationReason::kRecovery, current_time,
|
||||
0 /* oldest_key_time */, 0 /* file_creation_time */, db_id_,
|
||||
db_session_id_, 0 /* target_file_size */);
|
||||
db_session_id_, 0 /* target_file_size */, meta.fd.GetNumber());
|
||||
s = BuildTable(
|
||||
dbname_, versions_.get(), immutable_db_options_, tboptions,
|
||||
file_options_for_compaction_, cfd->table_cache(), iter.get(),
|
||||
|
||||
@@ -251,6 +251,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
write_thread_.EnterAsBatchGroupLeader(&w, &write_group);
|
||||
|
||||
IOStatus io_s;
|
||||
Status pre_release_cb_status;
|
||||
if (status.ok()) {
|
||||
// Rules for when we can update the memtable concurrently
|
||||
// 1. supported by memtable
|
||||
@@ -361,7 +362,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
writer->sequence, disable_memtable, writer->log_used, index++,
|
||||
pre_release_callback_cnt);
|
||||
if (!ws.ok()) {
|
||||
status = ws;
|
||||
status = pre_release_cb_status = ws;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -414,10 +415,13 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
|
||||
if (!w.CallbackFailed()) {
|
||||
if (!io_s.ok()) {
|
||||
assert(pre_release_cb_status.ok());
|
||||
IOStatusCheck(io_s);
|
||||
} else {
|
||||
WriteStatusCheck(status);
|
||||
WriteStatusCheck(pre_release_cb_status);
|
||||
}
|
||||
} else {
|
||||
assert(io_s.ok() && pre_release_cb_status.ok());
|
||||
}
|
||||
|
||||
if (need_log_sync) {
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
DBIter::DBIter(Env* _env, const ReadOptions& read_options,
|
||||
const ImmutableOptions& cf_options,
|
||||
const ImmutableOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const Comparator* cmp, InternalIterator* iter,
|
||||
const Version* version, SequenceNumber s, bool arena_mode,
|
||||
@@ -45,15 +45,15 @@ DBIter::DBIter(Env* _env, const ReadOptions& read_options,
|
||||
ColumnFamilyData* cfd, bool expose_blob_index)
|
||||
: prefix_extractor_(mutable_cf_options.prefix_extractor.get()),
|
||||
env_(_env),
|
||||
clock_(cf_options.clock),
|
||||
logger_(cf_options.logger),
|
||||
clock_(ioptions.clock),
|
||||
logger_(ioptions.logger),
|
||||
user_comparator_(cmp),
|
||||
merge_operator_(cf_options.merge_operator.get()),
|
||||
merge_operator_(ioptions.merge_operator.get()),
|
||||
iter_(iter),
|
||||
version_(version),
|
||||
read_callback_(read_callback),
|
||||
sequence_(s),
|
||||
statistics_(cf_options.stats),
|
||||
statistics_(ioptions.stats),
|
||||
max_skip_(max_sequential_skip_in_iterations),
|
||||
max_skippable_internal_keys_(read_options.max_skippable_internal_keys),
|
||||
num_internal_keys_skipped_(0),
|
||||
@@ -75,7 +75,7 @@ DBIter::DBIter(Env* _env, const ReadOptions& read_options,
|
||||
expose_blob_index_(expose_blob_index),
|
||||
is_blob_(false),
|
||||
arena_mode_(arena_mode),
|
||||
range_del_agg_(&cf_options.internal_comparator, s),
|
||||
range_del_agg_(&ioptions.internal_comparator, s),
|
||||
db_impl_(db_impl),
|
||||
cfd_(cfd),
|
||||
start_seqnum_(read_options.iter_start_seqnum),
|
||||
@@ -576,12 +576,8 @@ bool DBIter::MergeValuesNewToOld() {
|
||||
// hit a put, merge the put value with operands and store the
|
||||
// final result in saved_value_. We are done!
|
||||
const Slice val = iter_.value();
|
||||
Status s = MergeHelper::TimedFullMerge(
|
||||
merge_operator_, ikey.user_key, &val, merge_context_.GetOperands(),
|
||||
&saved_value_, logger_, statistics_, clock_, &pinned_value_, true);
|
||||
Status s = Merge(&val, ikey.user_key);
|
||||
if (!s.ok()) {
|
||||
valid_ = false;
|
||||
status_ = s;
|
||||
return false;
|
||||
}
|
||||
// iter_ is positioned after put
|
||||
@@ -598,9 +594,31 @@ bool DBIter::MergeValuesNewToOld() {
|
||||
iter_.value(), iter_.iter()->IsValuePinned() /* operand_pinned */);
|
||||
PERF_COUNTER_ADD(internal_merge_count, 1);
|
||||
} else if (kTypeBlobIndex == ikey.type) {
|
||||
status_ = Status::NotSupported("BlobDB does not support merge operator.");
|
||||
valid_ = false;
|
||||
return false;
|
||||
if (expose_blob_index_) {
|
||||
status_ =
|
||||
Status::NotSupported("BlobDB does not support merge operator.");
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
// hit a put, merge the put value with operands and store the
|
||||
// final result in saved_value_. We are done!
|
||||
if (!SetBlobValueIfNeeded(ikey.user_key, iter_.value())) {
|
||||
return false;
|
||||
}
|
||||
valid_ = true;
|
||||
const Slice blob_value = value();
|
||||
Status s = Merge(&blob_value, ikey.user_key);
|
||||
if (!s.ok()) {
|
||||
return false;
|
||||
}
|
||||
is_blob_ = false;
|
||||
// iter_ is positioned after put
|
||||
iter_.Next();
|
||||
if (!iter_.status().ok()) {
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
valid_ = false;
|
||||
status_ = Status::Corruption(
|
||||
@@ -619,16 +637,10 @@ bool DBIter::MergeValuesNewToOld() {
|
||||
// a deletion marker.
|
||||
// feed null as the existing value to the merge operator, such that
|
||||
// client can differentiate this scenario and do things accordingly.
|
||||
Status s = MergeHelper::TimedFullMerge(
|
||||
merge_operator_, saved_key_.GetUserKey(), nullptr,
|
||||
merge_context_.GetOperands(), &saved_value_, logger_, statistics_, clock_,
|
||||
&pinned_value_, true);
|
||||
Status s = Merge(nullptr, saved_key_.GetUserKey());
|
||||
if (!s.ok()) {
|
||||
valid_ = false;
|
||||
status_ = s;
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(status_.ok());
|
||||
return true;
|
||||
}
|
||||
@@ -931,21 +943,36 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
if (last_not_merge_type == kTypeDeletion ||
|
||||
last_not_merge_type == kTypeSingleDeletion ||
|
||||
last_not_merge_type == kTypeRangeDeletion) {
|
||||
s = MergeHelper::TimedFullMerge(
|
||||
merge_operator_, saved_key_.GetUserKey(), nullptr,
|
||||
merge_context_.GetOperands(), &saved_value_, logger_, statistics_,
|
||||
clock_, &pinned_value_, true);
|
||||
s = Merge(nullptr, saved_key_.GetUserKey());
|
||||
if (!s.ok()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else if (last_not_merge_type == kTypeBlobIndex) {
|
||||
status_ =
|
||||
Status::NotSupported("BlobDB does not support merge operator.");
|
||||
valid_ = false;
|
||||
return false;
|
||||
if (expose_blob_index_) {
|
||||
status_ =
|
||||
Status::NotSupported("BlobDB does not support merge operator.");
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
if (!SetBlobValueIfNeeded(saved_key_.GetUserKey(), pinned_value_)) {
|
||||
return false;
|
||||
}
|
||||
valid_ = true;
|
||||
const Slice blob_value = value();
|
||||
s = Merge(&blob_value, saved_key_.GetUserKey());
|
||||
if (!s.ok()) {
|
||||
return false;
|
||||
}
|
||||
is_blob_ = false;
|
||||
return true;
|
||||
} else {
|
||||
assert(last_not_merge_type == kTypeValue);
|
||||
s = MergeHelper::TimedFullMerge(
|
||||
merge_operator_, saved_key_.GetUserKey(), &pinned_value_,
|
||||
merge_context_.GetOperands(), &saved_value_, logger_, statistics_,
|
||||
clock_, &pinned_value_, true);
|
||||
s = Merge(&pinned_value_, saved_key_.GetUserKey());
|
||||
if (!s.ok()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case kTypeValue:
|
||||
@@ -955,7 +982,6 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
if (!SetBlobValueIfNeeded(saved_key_.GetUserKey(), pinned_value_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
valid_ = false;
|
||||
@@ -1095,25 +1121,33 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
|
||||
if (ikey.type == kTypeValue) {
|
||||
const Slice val = iter_.value();
|
||||
Status s = MergeHelper::TimedFullMerge(
|
||||
merge_operator_, saved_key_.GetUserKey(), &val,
|
||||
merge_context_.GetOperands(), &saved_value_, logger_, statistics_,
|
||||
clock_, &pinned_value_, true);
|
||||
Status s = Merge(&val, saved_key_.GetUserKey());
|
||||
if (!s.ok()) {
|
||||
valid_ = false;
|
||||
status_ = s;
|
||||
return false;
|
||||
}
|
||||
valid_ = true;
|
||||
return true;
|
||||
} else if (ikey.type == kTypeMerge) {
|
||||
merge_context_.PushOperand(
|
||||
iter_.value(), iter_.iter()->IsValuePinned() /* operand_pinned */);
|
||||
PERF_COUNTER_ADD(internal_merge_count, 1);
|
||||
} else if (ikey.type == kTypeBlobIndex) {
|
||||
status_ = Status::NotSupported("BlobDB does not support merge operator.");
|
||||
valid_ = false;
|
||||
return false;
|
||||
if (expose_blob_index_) {
|
||||
status_ =
|
||||
Status::NotSupported("BlobDB does not support merge operator.");
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
if (!SetBlobValueIfNeeded(ikey.user_key, iter_.value())) {
|
||||
return false;
|
||||
}
|
||||
valid_ = true;
|
||||
const Slice blob_value = value();
|
||||
Status s = Merge(&blob_value, saved_key_.GetUserKey());
|
||||
if (!s.ok()) {
|
||||
return false;
|
||||
}
|
||||
is_blob_ = false;
|
||||
return true;
|
||||
} else {
|
||||
valid_ = false;
|
||||
status_ = Status::Corruption(
|
||||
@@ -1123,13 +1157,8 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
}
|
||||
}
|
||||
|
||||
Status s = MergeHelper::TimedFullMerge(
|
||||
merge_operator_, saved_key_.GetUserKey(), nullptr,
|
||||
merge_context_.GetOperands(), &saved_value_, logger_, statistics_, clock_,
|
||||
&pinned_value_, true);
|
||||
Status s = Merge(nullptr, saved_key_.GetUserKey());
|
||||
if (!s.ok()) {
|
||||
valid_ = false;
|
||||
status_ = s;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1152,6 +1181,19 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
return true;
|
||||
}
|
||||
|
||||
Status DBIter::Merge(const Slice* val, const Slice& user_key) {
|
||||
Status s = MergeHelper::TimedFullMerge(
|
||||
merge_operator_, user_key, val, merge_context_.GetOperands(),
|
||||
&saved_value_, logger_, statistics_, clock_, &pinned_value_, true);
|
||||
if (!s.ok()) {
|
||||
valid_ = false;
|
||||
status_ = s;
|
||||
return s;
|
||||
}
|
||||
valid_ = true;
|
||||
return s;
|
||||
}
|
||||
|
||||
// Move backwards until the key smaller than saved_key_.
|
||||
// Changes valid_ only if return value is false.
|
||||
bool DBIter::FindUserKeyBeforeSavedKey() {
|
||||
@@ -1536,7 +1578,7 @@ void DBIter::SeekToLast() {
|
||||
}
|
||||
|
||||
Iterator* NewDBIterator(Env* env, const ReadOptions& read_options,
|
||||
const ImmutableOptions& cf_options,
|
||||
const ImmutableOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const Comparator* user_key_comparator,
|
||||
InternalIterator* internal_iter, const Version* version,
|
||||
@@ -1545,7 +1587,7 @@ Iterator* NewDBIterator(Env* env, const ReadOptions& read_options,
|
||||
ReadCallback* read_callback, DBImpl* db_impl,
|
||||
ColumnFamilyData* cfd, bool expose_blob_index) {
|
||||
DBIter* db_iter =
|
||||
new DBIter(env, read_options, cf_options, mutable_cf_options,
|
||||
new DBIter(env, read_options, ioptions, mutable_cf_options,
|
||||
user_key_comparator, internal_iter, version, sequence, false,
|
||||
max_sequential_skip_in_iterations, read_callback, db_impl, cfd,
|
||||
expose_blob_index);
|
||||
|
||||
@@ -114,7 +114,7 @@ class DBIter final : public Iterator {
|
||||
};
|
||||
|
||||
DBIter(Env* _env, const ReadOptions& read_options,
|
||||
const ImmutableOptions& cf_options,
|
||||
const ImmutableOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options, const Comparator* cmp,
|
||||
InternalIterator* iter, const Version* version, SequenceNumber s,
|
||||
bool arena_mode, uint64_t max_sequential_skip_in_iterations,
|
||||
@@ -299,6 +299,8 @@ class DBIter final : public Iterator {
|
||||
// index when using the integrated BlobDB implementation.
|
||||
bool SetBlobValueIfNeeded(const Slice& user_key, const Slice& blob_index);
|
||||
|
||||
Status Merge(const Slice* val, const Slice& user_key);
|
||||
|
||||
const SliceTransform* prefix_extractor_;
|
||||
Env* const env_;
|
||||
SystemClock* clock_;
|
||||
@@ -383,8 +385,7 @@ class DBIter final : public Iterator {
|
||||
// "*internal_iter") that were live at the specified `sequence` number
|
||||
// into appropriate user keys.
|
||||
extern Iterator* NewDBIterator(
|
||||
Env* env, const ReadOptions& read_options,
|
||||
const ImmutableOptions& cf_options,
|
||||
Env* env, const ReadOptions& read_options, const ImmutableOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const Comparator* user_key_comparator, InternalIterator* internal_iter,
|
||||
const Version* version, const SequenceNumber& sequence,
|
||||
|
||||
@@ -237,7 +237,7 @@ class DBIteratorTest : public testing::Test {
|
||||
|
||||
TEST_F(DBIteratorTest, DBIteratorPrevNext) {
|
||||
Options options;
|
||||
ImmutableOptions cf_options = ImmutableOptions(options);
|
||||
ImmutableOptions ioptions = ImmutableOptions(options);
|
||||
MutableCFOptions mutable_cf_options = MutableCFOptions(options);
|
||||
{
|
||||
TestIterator* internal_iter = new TestIterator(BytewiseComparator());
|
||||
@@ -252,7 +252,7 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
|
||||
|
||||
ReadOptions ro;
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -286,7 +286,7 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
|
||||
|
||||
ReadOptions ro;
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -314,7 +314,7 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
|
||||
ro.iterate_upper_bound = &prefix;
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -348,7 +348,7 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
|
||||
ro.iterate_upper_bound = &prefix;
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -385,7 +385,7 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
|
||||
ro.iterate_upper_bound = &prefix;
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -417,7 +417,7 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
|
||||
ro.iterate_upper_bound = &prefix;
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 7 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -456,7 +456,7 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
|
||||
ro.iterate_upper_bound = &prefix;
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 4 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -483,7 +483,7 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
|
||||
ro.iterate_upper_bound = &prefix;
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -508,7 +508,7 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
|
||||
ro.iterate_upper_bound = &prefix;
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -545,7 +545,7 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
|
||||
ro.iterate_upper_bound = &prefix;
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 7 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -576,7 +576,7 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
|
||||
|
||||
ReadOptions ro;
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -621,7 +621,7 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
|
||||
|
||||
ReadOptions ro;
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 2 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -654,7 +654,7 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
|
||||
|
||||
ReadOptions ro;
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -677,7 +677,7 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
|
||||
|
||||
TEST_F(DBIteratorTest, DBIteratorEmpty) {
|
||||
Options options;
|
||||
ImmutableOptions cf_options = ImmutableOptions(options);
|
||||
ImmutableOptions ioptions = ImmutableOptions(options);
|
||||
MutableCFOptions mutable_cf_options = MutableCFOptions(options);
|
||||
ReadOptions ro;
|
||||
|
||||
@@ -686,7 +686,7 @@ TEST_F(DBIteratorTest, DBIteratorEmpty) {
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 0 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -700,7 +700,7 @@ TEST_F(DBIteratorTest, DBIteratorEmpty) {
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 0 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -757,7 +757,7 @@ TEST_F(DBIteratorTest, DBIteratorUseSkip) {
|
||||
ReadOptions ro;
|
||||
Options options;
|
||||
options.merge_operator = MergeOperators::CreateFromStringId("stringappend");
|
||||
ImmutableOptions cf_options = ImmutableOptions(options);
|
||||
ImmutableOptions ioptions = ImmutableOptions(options);
|
||||
MutableCFOptions mutable_cf_options = MutableCFOptions(options);
|
||||
|
||||
{
|
||||
@@ -772,7 +772,7 @@ TEST_F(DBIteratorTest, DBIteratorUseSkip) {
|
||||
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, i + 2 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -810,7 +810,7 @@ TEST_F(DBIteratorTest, DBIteratorUseSkip) {
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, i + 2 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -841,7 +841,7 @@ TEST_F(DBIteratorTest, DBIteratorUseSkip) {
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 202 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -876,7 +876,7 @@ TEST_F(DBIteratorTest, DBIteratorUseSkip) {
|
||||
internal_iter->AddPut("c", "200");
|
||||
internal_iter->Finish();
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, i /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -896,7 +896,7 @@ TEST_F(DBIteratorTest, DBIteratorUseSkip) {
|
||||
internal_iter->AddPut("c", "200");
|
||||
internal_iter->Finish();
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 200 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -934,7 +934,7 @@ TEST_F(DBIteratorTest, DBIteratorUseSkip) {
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, i + 2 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -971,7 +971,7 @@ TEST_F(DBIteratorTest, DBIteratorUseSkip) {
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, i + 2 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -1004,7 +1004,7 @@ TEST_F(DBIteratorTest, DBIteratorUseSkip) {
|
||||
|
||||
TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) {
|
||||
Options options;
|
||||
ImmutableOptions cf_options = ImmutableOptions(options);
|
||||
ImmutableOptions ioptions = ImmutableOptions(options);
|
||||
MutableCFOptions mutable_cf_options = MutableCFOptions(options);
|
||||
ReadOptions ro;
|
||||
|
||||
@@ -1023,7 +1023,7 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) {
|
||||
|
||||
ro.max_skippable_internal_keys = 0;
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -1071,7 +1071,7 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) {
|
||||
|
||||
ro.max_skippable_internal_keys = 2;
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -1117,7 +1117,7 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) {
|
||||
|
||||
ro.max_skippable_internal_keys = 2;
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -1157,7 +1157,7 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) {
|
||||
|
||||
ro.max_skippable_internal_keys = 2;
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -1194,7 +1194,7 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) {
|
||||
|
||||
ro.max_skippable_internal_keys = 2;
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -1226,7 +1226,7 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) {
|
||||
|
||||
ro.max_skippable_internal_keys = 2;
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -1265,7 +1265,7 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) {
|
||||
|
||||
ro.max_skippable_internal_keys = 2;
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -1304,7 +1304,7 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) {
|
||||
|
||||
ro.max_skippable_internal_keys = i;
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 2 * i + 1 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -1359,7 +1359,7 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) {
|
||||
options.max_sequential_skip_in_iterations = 1000;
|
||||
ro.max_skippable_internal_keys = i;
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 2 * i + 1 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -1500,7 +1500,7 @@ TEST_F(DBIteratorTest, DBIterator5) {
|
||||
ReadOptions ro;
|
||||
Options options;
|
||||
options.merge_operator = MergeOperators::CreateFromStringId("stringappend");
|
||||
ImmutableOptions cf_options = ImmutableOptions(options);
|
||||
ImmutableOptions ioptions = ImmutableOptions(options);
|
||||
MutableCFOptions mutable_cf_options = MutableCFOptions(options);
|
||||
|
||||
{
|
||||
@@ -1515,7 +1515,7 @@ TEST_F(DBIteratorTest, DBIterator5) {
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 0 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -1539,7 +1539,7 @@ TEST_F(DBIteratorTest, DBIterator5) {
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 1 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -1563,7 +1563,7 @@ TEST_F(DBIteratorTest, DBIterator5) {
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 2 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -1587,7 +1587,7 @@ TEST_F(DBIteratorTest, DBIterator5) {
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 3 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -1611,7 +1611,7 @@ TEST_F(DBIteratorTest, DBIterator5) {
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 4 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -1635,7 +1635,7 @@ TEST_F(DBIteratorTest, DBIterator5) {
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 5 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -1659,7 +1659,7 @@ TEST_F(DBIteratorTest, DBIterator5) {
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 6 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -1681,7 +1681,7 @@ TEST_F(DBIteratorTest, DBIterator5) {
|
||||
internal_iter->AddPut("b", "val_b");
|
||||
internal_iter->Finish();
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -1698,7 +1698,7 @@ TEST_F(DBIteratorTest, DBIterator6) {
|
||||
ReadOptions ro;
|
||||
Options options;
|
||||
options.merge_operator = MergeOperators::CreateFromStringId("stringappend");
|
||||
ImmutableOptions cf_options = ImmutableOptions(options);
|
||||
ImmutableOptions ioptions = ImmutableOptions(options);
|
||||
MutableCFOptions mutable_cf_options = MutableCFOptions(options);
|
||||
|
||||
{
|
||||
@@ -1713,7 +1713,7 @@ TEST_F(DBIteratorTest, DBIterator6) {
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 0 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -1737,7 +1737,7 @@ TEST_F(DBIteratorTest, DBIterator6) {
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 1 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -1761,7 +1761,7 @@ TEST_F(DBIteratorTest, DBIterator6) {
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 2 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -1785,7 +1785,7 @@ TEST_F(DBIteratorTest, DBIterator6) {
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 3 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -1805,7 +1805,7 @@ TEST_F(DBIteratorTest, DBIterator6) {
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 4 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -1829,7 +1829,7 @@ TEST_F(DBIteratorTest, DBIterator6) {
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 5 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -1853,7 +1853,7 @@ TEST_F(DBIteratorTest, DBIterator6) {
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 6 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -1870,7 +1870,7 @@ TEST_F(DBIteratorTest, DBIterator7) {
|
||||
ReadOptions ro;
|
||||
Options options;
|
||||
options.merge_operator = MergeOperators::CreateFromStringId("stringappend");
|
||||
ImmutableOptions cf_options = ImmutableOptions(options);
|
||||
ImmutableOptions ioptions = ImmutableOptions(options);
|
||||
MutableCFOptions mutable_cf_options = MutableCFOptions(options);
|
||||
|
||||
{
|
||||
@@ -1897,7 +1897,7 @@ TEST_F(DBIteratorTest, DBIterator7) {
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 0 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -1933,7 +1933,7 @@ TEST_F(DBIteratorTest, DBIterator7) {
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 2 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -1975,7 +1975,7 @@ TEST_F(DBIteratorTest, DBIterator7) {
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 4 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -2017,7 +2017,7 @@ TEST_F(DBIteratorTest, DBIterator7) {
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 5 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -2064,7 +2064,7 @@ TEST_F(DBIteratorTest, DBIterator7) {
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 6 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -2112,7 +2112,7 @@ TEST_F(DBIteratorTest, DBIterator7) {
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 7 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -2154,7 +2154,7 @@ TEST_F(DBIteratorTest, DBIterator7) {
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 9 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -2202,7 +2202,7 @@ TEST_F(DBIteratorTest, DBIterator7) {
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 13 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
@@ -2251,7 +2251,7 @@ TEST_F(DBIteratorTest, DBIterator7) {
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, cf_options, mutable_cf_options, BytewiseComparator(),
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 14 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
|
||||
@@ -19,6 +19,28 @@
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
namespace {
|
||||
class LimitedStringAppendMergeOp : public StringAppendTESTOperator {
|
||||
public:
|
||||
LimitedStringAppendMergeOp(int limit, char delim)
|
||||
: StringAppendTESTOperator(delim), limit_(limit) {}
|
||||
|
||||
const char* Name() const override {
|
||||
return "DBMergeOperatorTest::LimitedStringAppendMergeOp";
|
||||
}
|
||||
|
||||
bool ShouldMerge(const std::vector<Slice>& operands) const override {
|
||||
if (operands.size() > 0 && limit_ > 0 && operands.size() >= limit_) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t limit_ = 0;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
class DBMergeOperandTest : public DBTestBase {
|
||||
public:
|
||||
DBMergeOperandTest()
|
||||
@@ -26,26 +48,6 @@ class DBMergeOperandTest : public DBTestBase {
|
||||
};
|
||||
|
||||
TEST_F(DBMergeOperandTest, GetMergeOperandsBasic) {
|
||||
class LimitedStringAppendMergeOp : public StringAppendTESTOperator {
|
||||
public:
|
||||
LimitedStringAppendMergeOp(int limit, char delim)
|
||||
: StringAppendTESTOperator(delim), limit_(limit) {}
|
||||
|
||||
const char* Name() const override {
|
||||
return "DBMergeOperatorTest::LimitedStringAppendMergeOp";
|
||||
}
|
||||
|
||||
bool ShouldMerge(const std::vector<Slice>& operands) const override {
|
||||
if (operands.size() > 0 && limit_ > 0 && operands.size() >= limit_) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t limit_ = 0;
|
||||
};
|
||||
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
// Use only the latest two merge operands.
|
||||
@@ -214,7 +216,8 @@ TEST_F(DBMergeOperandTest, GetMergeOperandsBasic) {
|
||||
ASSERT_EQ(values[2], "dc");
|
||||
ASSERT_EQ(values[3], "ed");
|
||||
|
||||
// First 3 k5 values are in SST and next 4 k5 values are in Immutable Memtable
|
||||
// First 3 k5 values are in SST and next 4 k5 values are in Immutable
|
||||
// Memtable
|
||||
ASSERT_OK(Merge("k5", "who"));
|
||||
ASSERT_OK(Merge("k5", "am"));
|
||||
ASSERT_OK(Merge("k5", "i"));
|
||||
@@ -232,6 +235,93 @@ TEST_F(DBMergeOperandTest, GetMergeOperandsBasic) {
|
||||
ASSERT_EQ(values[2], "am");
|
||||
}
|
||||
|
||||
TEST_F(DBMergeOperandTest, BlobDBGetMergeOperandsBasic) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
// Use only the latest two merge operands.
|
||||
options.merge_operator = std::make_shared<LimitedStringAppendMergeOp>(2, ',');
|
||||
options.env = env_;
|
||||
Reopen(options);
|
||||
int num_records = 4;
|
||||
int number_of_operands = 0;
|
||||
std::vector<PinnableSlice> values(num_records);
|
||||
GetMergeOperandsOptions merge_operands_info;
|
||||
merge_operands_info.expected_max_number_of_operands = num_records;
|
||||
|
||||
// All k1 values are in memtable.
|
||||
ASSERT_OK(Put("k1", "x"));
|
||||
ASSERT_OK(Merge("k1", "b"));
|
||||
ASSERT_OK(Merge("k1", "c"));
|
||||
ASSERT_OK(Merge("k1", "d"));
|
||||
ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(),
|
||||
"k1", values.data(), &merge_operands_info,
|
||||
&number_of_operands));
|
||||
ASSERT_EQ(values[0], "x");
|
||||
ASSERT_EQ(values[1], "b");
|
||||
ASSERT_EQ(values[2], "c");
|
||||
ASSERT_EQ(values[3], "d");
|
||||
|
||||
// expected_max_number_of_operands is less than number of merge operands so
|
||||
// status should be Incomplete.
|
||||
merge_operands_info.expected_max_number_of_operands = num_records - 1;
|
||||
Status status = db_->GetMergeOperands(
|
||||
ReadOptions(), db_->DefaultColumnFamily(), "k1", values.data(),
|
||||
&merge_operands_info, &number_of_operands);
|
||||
ASSERT_EQ(status.IsIncomplete(), true);
|
||||
merge_operands_info.expected_max_number_of_operands = num_records;
|
||||
|
||||
// All k2 values are flushed to L0 into a single file.
|
||||
ASSERT_OK(Put("k2", "q"));
|
||||
ASSERT_OK(Merge("k2", "w"));
|
||||
ASSERT_OK(Merge("k2", "e"));
|
||||
ASSERT_OK(Merge("k2", "r"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(),
|
||||
"k2", values.data(), &merge_operands_info,
|
||||
&number_of_operands));
|
||||
ASSERT_EQ(values[0], "q,w,e,r");
|
||||
|
||||
// Do some compaction that will make the following tests more predictable
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
|
||||
// All k3 values are flushed and are in different files.
|
||||
ASSERT_OK(Put("k3", "ab"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Merge("k3", "bc"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Merge("k3", "cd"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Merge("k3", "de"));
|
||||
ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(),
|
||||
"k3", values.data(), &merge_operands_info,
|
||||
&number_of_operands));
|
||||
ASSERT_EQ(values[0], "ab");
|
||||
ASSERT_EQ(values[1], "bc");
|
||||
ASSERT_EQ(values[2], "cd");
|
||||
ASSERT_EQ(values[3], "de");
|
||||
|
||||
// All K4 values are in different levels
|
||||
ASSERT_OK(Put("k4", "ba"));
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(4);
|
||||
ASSERT_OK(Merge("k4", "cb"));
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(3);
|
||||
ASSERT_OK(Merge("k4", "dc"));
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(1);
|
||||
ASSERT_OK(Merge("k4", "ed"));
|
||||
ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(),
|
||||
"k4", values.data(), &merge_operands_info,
|
||||
&number_of_operands));
|
||||
ASSERT_EQ(values[0], "ba");
|
||||
ASSERT_EQ(values[1], "cb");
|
||||
ASSERT_EQ(values[2], "dc");
|
||||
ASSERT_EQ(values[3], "ed");
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <atomic>
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "db/read_callback.h"
|
||||
@@ -3201,6 +3202,180 @@ TEST_F(DBTest2, PausingManualCompaction4) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, CancelManualCompaction1) {
|
||||
CompactRangeOptions compact_options;
|
||||
auto canceledPtr =
|
||||
std::unique_ptr<std::atomic<bool>>(new std::atomic<bool>{true});
|
||||
compact_options.canceled = canceledPtr.get();
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.num_levels = 7;
|
||||
|
||||
Random rnd(301);
|
||||
auto generate_files = [&]() {
|
||||
for (int i = 0; i < options.num_levels; i++) {
|
||||
for (int j = 0; j < options.num_levels - i + 1; j++) {
|
||||
for (int k = 0; k < 1000; k++) {
|
||||
ASSERT_OK(Put(Key(k + j * 1000), rnd.RandomString(50)));
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
|
||||
for (int l = 1; l < options.num_levels - i; l++) {
|
||||
MoveFilesToLevel(l);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
DestroyAndReopen(options);
|
||||
generate_files();
|
||||
#ifndef ROCKSDB_LITE
|
||||
ASSERT_EQ("2,3,4,5,6,7,8", FilesPerLevel());
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
int run_manual_compactions = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionJob::Run():PausingManualCompaction:1",
|
||||
[&](void* /*arg*/) { run_manual_compactions++; });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Setup a callback to disable compactions after a couple of levels are
|
||||
// compacted
|
||||
int compactions_run = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::RunManualCompaction()::1",
|
||||
[&](void* /*arg*/) { ++compactions_run; });
|
||||
|
||||
dbfull()->CompactRange(compact_options, nullptr, nullptr);
|
||||
dbfull()->TEST_WaitForCompact(true);
|
||||
|
||||
// Since compactions are disabled, we shouldn't start compacting.
|
||||
// E.g. we should call the compaction function exactly one time.
|
||||
ASSERT_EQ(compactions_run, 0);
|
||||
ASSERT_EQ(run_manual_compactions, 0);
|
||||
#ifndef ROCKSDB_LITE
|
||||
ASSERT_EQ("2,3,4,5,6,7,8", FilesPerLevel());
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
compactions_run = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearCallBack(
|
||||
"DBImpl::RunManualCompaction()::1");
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::RunManualCompaction()::1", [&](void* /*arg*/) {
|
||||
++compactions_run;
|
||||
// After 3 compactions disable
|
||||
if (compactions_run == 3) {
|
||||
compact_options.canceled->store(true, std::memory_order_release);
|
||||
}
|
||||
});
|
||||
|
||||
compact_options.canceled->store(false, std::memory_order_release);
|
||||
dbfull()->CompactRange(compact_options, nullptr, nullptr);
|
||||
dbfull()->TEST_WaitForCompact(true);
|
||||
|
||||
ASSERT_EQ(compactions_run, 3);
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearCallBack(
|
||||
"DBImpl::RunManualCompaction()::1");
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearCallBack(
|
||||
"CompactionJob::Run():PausingManualCompaction:1");
|
||||
|
||||
// Compactions should work again if we re-enable them..
|
||||
compact_options.canceled->store(false, std::memory_order_relaxed);
|
||||
dbfull()->CompactRange(compact_options, nullptr, nullptr);
|
||||
dbfull()->TEST_WaitForCompact(true);
|
||||
#ifndef ROCKSDB_LITE
|
||||
ASSERT_EQ("0,0,0,0,0,0,2", FilesPerLevel());
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, CancelManualCompaction2) {
|
||||
CompactRangeOptions compact_options;
|
||||
auto canceledPtr =
|
||||
std::unique_ptr<std::atomic<bool>>(new std::atomic<bool>{true});
|
||||
compact_options.canceled = canceledPtr.get();
|
||||
compact_options.max_subcompactions = 1;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.num_levels = 7;
|
||||
|
||||
Random rnd(301);
|
||||
auto generate_files = [&]() {
|
||||
for (int i = 0; i < options.num_levels; i++) {
|
||||
for (int j = 0; j < options.num_levels - i + 1; j++) {
|
||||
for (int k = 0; k < 1000; k++) {
|
||||
ASSERT_OK(Put(Key(k + j * 1000), rnd.RandomString(50)));
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
|
||||
for (int l = 1; l < options.num_levels - i; l++) {
|
||||
MoveFilesToLevel(l);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
DestroyAndReopen(options);
|
||||
generate_files();
|
||||
#ifndef ROCKSDB_LITE
|
||||
ASSERT_EQ("2,3,4,5,6,7,8", FilesPerLevel());
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
int compactions_run = 0;
|
||||
std::atomic<int> kv_compactions{0};
|
||||
int compactions_stopped_at = 0;
|
||||
int kv_compactions_stopped_at = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::RunManualCompaction()::1", [&](void* /*arg*/) {
|
||||
++compactions_run;
|
||||
// After 3 compactions disable
|
||||
});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionIterator:ProcessKV", [&](void* /*arg*/) {
|
||||
int kv_compactions_run =
|
||||
kv_compactions.fetch_add(1, std::memory_order_release);
|
||||
if (kv_compactions_run == 5) {
|
||||
compact_options.canceled->store(true, std::memory_order_release);
|
||||
kv_compactions_stopped_at = kv_compactions_run;
|
||||
compactions_stopped_at = compactions_run;
|
||||
}
|
||||
});
|
||||
|
||||
compact_options.canceled->store(false, std::memory_order_release);
|
||||
dbfull()->CompactRange(compact_options, nullptr, nullptr);
|
||||
dbfull()->TEST_WaitForCompact(true);
|
||||
|
||||
// NOTE: as we set compact_options.max_subcompacitons = 1, and store true to
|
||||
// the canceled variable from the single compacting thread (via callback),
|
||||
// this value is deterministically kv_compactions_stopped_at + 1.
|
||||
ASSERT_EQ(kv_compactions, kv_compactions_stopped_at + 1);
|
||||
ASSERT_EQ(compactions_run, compactions_stopped_at);
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearCallBack(
|
||||
"CompactionIterator::ProcessKV");
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearCallBack(
|
||||
"DBImpl::RunManualCompaction()::1");
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearCallBack(
|
||||
"CompactionJob::Run():PausingManualCompaction:1");
|
||||
|
||||
// Compactions should work again if we re-enable them..
|
||||
compact_options.canceled->store(false, std::memory_order_relaxed);
|
||||
dbfull()->CompactRange(compact_options, nullptr, nullptr);
|
||||
dbfull()->TEST_WaitForCompact(true);
|
||||
#ifndef ROCKSDB_LITE
|
||||
ASSERT_EQ("0,0,0,0,0,0,2", FilesPerLevel());
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, OptimizeForPointLookup) {
|
||||
Options options = CurrentOptions();
|
||||
Close();
|
||||
|
||||
@@ -59,16 +59,8 @@ SpecialEnv::SpecialEnv(Env* base, bool time_elapse_only_sleep)
|
||||
DBTestBase::DBTestBase(const std::string path, bool env_do_fsync)
|
||||
: mem_env_(nullptr), encrypted_env_(nullptr), option_config_(kDefault) {
|
||||
Env* base_env = Env::Default();
|
||||
#ifndef ROCKSDB_LITE
|
||||
const char* test_env_uri = getenv("TEST_ENV_URI");
|
||||
if (test_env_uri) {
|
||||
Env* test_env = nullptr;
|
||||
Status s = Env::LoadEnv(test_env_uri, &test_env, &env_guard_);
|
||||
base_env = test_env;
|
||||
EXPECT_OK(s);
|
||||
EXPECT_NE(Env::Default(), base_env);
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
ConfigOptions config_options;
|
||||
EXPECT_OK(test::CreateEnvFromSystem(config_options, &base_env, &env_guard_));
|
||||
EXPECT_NE(nullptr, base_env);
|
||||
if (getenv("MEM_ENV")) {
|
||||
mem_env_ = new MockEnv(base_env);
|
||||
@@ -77,7 +69,7 @@ DBTestBase::DBTestBase(const std::string path, bool env_do_fsync)
|
||||
if (getenv("ENCRYPTED_ENV")) {
|
||||
std::shared_ptr<EncryptionProvider> provider;
|
||||
Status s = EncryptionProvider::CreateFromString(
|
||||
ConfigOptions(), std::string("test://") + getenv("ENCRYPTED_ENV"),
|
||||
config_options, std::string("test://") + getenv("ENCRYPTED_ENV"),
|
||||
&provider);
|
||||
encrypted_env_ = NewEncryptedEnv(mem_env_ ? mem_env_ : base_env, provider);
|
||||
}
|
||||
|
||||
@@ -303,6 +303,9 @@ class SpecialEnv : public EnvWrapper {
|
||||
Status Allocate(uint64_t offset, uint64_t len) override {
|
||||
return base_->Allocate(offset, len);
|
||||
}
|
||||
size_t GetUniqueId(char* id, size_t max_size) const override {
|
||||
return base_->GetUniqueId(id, max_size);
|
||||
}
|
||||
};
|
||||
class ManifestFile : public WritableFile {
|
||||
public:
|
||||
|
||||
@@ -1262,10 +1262,11 @@ class RecoveryTestHelper {
|
||||
std::unique_ptr<WalManager> wal_manager;
|
||||
WriteController write_controller;
|
||||
|
||||
versions.reset(new VersionSet(
|
||||
test->dbname_, &db_options, file_options, table_cache.get(),
|
||||
&write_buffer_manager, &write_controller,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr));
|
||||
versions.reset(new VersionSet(test->dbname_, &db_options, file_options,
|
||||
table_cache.get(), &write_buffer_manager,
|
||||
&write_controller,
|
||||
/*block_cache_tracer=*/nullptr,
|
||||
/*io_tracer=*/nullptr, /*db_session_id*/ ""));
|
||||
|
||||
wal_manager.reset(
|
||||
new WalManager(db_options, file_options, /*io_tracer=*/nullptr));
|
||||
@@ -2001,6 +2002,61 @@ TEST_F(DBWALTest, TruncateLastLogAfterRecoverWALEmpty) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(DBWALTest, ReadOnlyRecoveryNoTruncate) {
|
||||
constexpr size_t kKB = 1024;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.avoid_flush_during_recovery = true;
|
||||
if (mem_env_) {
|
||||
ROCKSDB_GTEST_SKIP("Test requires non-mem environment");
|
||||
return;
|
||||
}
|
||||
if (!IsFallocateSupported()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// create DB and close with file truncate disabled
|
||||
std::atomic_bool enable_truncate{false};
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"PosixWritableFile::Close", [&](void* arg) {
|
||||
if (!enable_truncate) {
|
||||
*(reinterpret_cast<size_t*>(arg)) = 0;
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
DestroyAndReopen(options);
|
||||
size_t preallocated_size =
|
||||
dbfull()->TEST_GetWalPreallocateBlockSize(options.write_buffer_size);
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
VectorLogPtr log_files_before;
|
||||
ASSERT_OK(dbfull()->GetSortedWalFiles(log_files_before));
|
||||
ASSERT_EQ(1, log_files_before.size());
|
||||
auto& file_before = log_files_before[0];
|
||||
ASSERT_LT(file_before->SizeFileBytes(), 1 * kKB);
|
||||
// The log file has preallocated space.
|
||||
auto db_size = GetAllocatedFileSize(dbname_ + file_before->PathName());
|
||||
ASSERT_GE(db_size, preallocated_size);
|
||||
Close();
|
||||
|
||||
// enable truncate and open DB as readonly, the file should not be truncated
|
||||
// and DB size is not changed.
|
||||
enable_truncate = true;
|
||||
ASSERT_OK(ReadOnlyReopen(options));
|
||||
VectorLogPtr log_files_after;
|
||||
ASSERT_OK(dbfull()->GetSortedWalFiles(log_files_after));
|
||||
ASSERT_EQ(1, log_files_after.size());
|
||||
ASSERT_LT(log_files_after[0]->SizeFileBytes(), 1 * kKB);
|
||||
ASSERT_EQ(log_files_after[0]->PathName(), file_before->PathName());
|
||||
// The preallocated space should NOT be truncated.
|
||||
// the DB size is almost the same.
|
||||
ASSERT_NEAR(GetAllocatedFileSize(dbname_ + file_before->PathName()), db_size,
|
||||
db_size / 100);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
#endif // ROCKSDB_FALLOCATE_PRESENT
|
||||
#endif // ROCKSDB_PLATFORM_POSIX
|
||||
|
||||
|
||||
@@ -418,15 +418,18 @@ const Status& ErrorHandler::SetBGError(const IOStatus& bg_io_err,
|
||||
&bg_err, db_mutex_, &auto_recovery);
|
||||
recover_context_ = context;
|
||||
return bg_error_;
|
||||
} else if (bg_io_err.GetScope() ==
|
||||
IOStatus::IOErrorScope::kIOErrorScopeFile ||
|
||||
bg_io_err.GetRetryable()) {
|
||||
} else if (bg_io_err.subcode() != IOStatus::SubCode::kNoSpace &&
|
||||
(bg_io_err.GetScope() ==
|
||||
IOStatus::IOErrorScope::kIOErrorScopeFile ||
|
||||
bg_io_err.GetRetryable())) {
|
||||
// Second, check if the error is a retryable IO error (file scope IO error
|
||||
// is also treated as retryable IO error in RocksDB write path). if it is
|
||||
// retryable error and its severity is higher than bg_error_, overwrite the
|
||||
// bg_error_ with new error. In current stage, for retryable IO error of
|
||||
// compaction, treat it as soft error. In other cases, treat the retryable
|
||||
// IO error as hard error.
|
||||
// IO error as hard error. Note that, all the NoSpace error should be
|
||||
// handled by the SstFileManager::StartErrorRecovery(). Therefore, no matter
|
||||
// it is retryable or file scope, this logic will be bypassed.
|
||||
bool auto_recovery = false;
|
||||
EventHelpers::NotifyOnBackgroundError(db_options_.listeners, reason,
|
||||
&new_bg_io_err, db_mutex_,
|
||||
|
||||
@@ -193,6 +193,53 @@ TEST_F(DBErrorHandlingFSTest, FLushWriteError) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
// 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());
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
options.listeners.emplace_back(listener);
|
||||
options.max_bgerror_resume_count = 2;
|
||||
options.bgerror_resume_retry_interval = 100000; // 0.1 second
|
||||
options.statistics = CreateDBStatistics();
|
||||
Status s;
|
||||
|
||||
listener->EnableAutoRecovery(false);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
IOStatus error_msg = IOStatus::NoSpace("Retryable IO Error");
|
||||
error_msg.SetRetryable(true);
|
||||
|
||||
ASSERT_OK(Put(Key(1), "val1"));
|
||||
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::kHardError);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
s = dbfull()->Resume();
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(1, options.statistics->getAndResetTickerCount(
|
||||
ERROR_HANDLER_BG_ERROR_COUNT));
|
||||
ASSERT_EQ(1, options.statistics->getAndResetTickerCount(
|
||||
ERROR_HANDLER_BG_IO_ERROR_COUNT));
|
||||
ASSERT_EQ(0, options.statistics->getAndResetTickerCount(
|
||||
ERROR_HANDLER_BG_RETRYABLE_IO_ERROR_COUNT));
|
||||
ASSERT_EQ(0, options.statistics->getAndResetTickerCount(
|
||||
ERROR_HANDLER_AUTORESUME_COUNT));
|
||||
ASSERT_EQ(0, options.statistics->getAndResetTickerCount(
|
||||
ERROR_HANDLER_AUTORESUME_RETRY_TOTAL_COUNT));
|
||||
ASSERT_EQ(0, options.statistics->getAndResetTickerCount(
|
||||
ERROR_HANDLER_AUTORESUME_SUCCESS_COUNT));
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWriteRetryableError) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
|
||||
@@ -403,6 +403,8 @@ Status FlushJob::WriteLevel0Table() {
|
||||
: meta_.oldest_ancester_time;
|
||||
|
||||
uint64_t num_input_entries = 0;
|
||||
uint64_t memtable_payload_bytes = 0;
|
||||
uint64_t memtable_garbage_bytes = 0;
|
||||
IOStatus io_s;
|
||||
const std::string* const full_history_ts_low =
|
||||
(full_history_ts_low_.empty()) ? nullptr : &full_history_ts_low_;
|
||||
@@ -412,7 +414,8 @@ Status FlushJob::WriteLevel0Table() {
|
||||
mutable_cf_options_.compression_opts, cfd_->GetID(), cfd_->GetName(),
|
||||
0 /* level */, false /* is_bottommost */,
|
||||
TableFileCreationReason::kFlush, creation_time, oldest_key_time,
|
||||
current_time, db_id_, db_session_id_, 0 /* target_file_size */);
|
||||
current_time, db_id_, db_session_id_, 0 /* target_file_size */,
|
||||
meta_.fd.GetNumber());
|
||||
s = BuildTable(
|
||||
dbname_, versions_, db_options_, tboptions, file_options_,
|
||||
cfd_->table_cache(), iter.get(), std::move(range_del_iters), &meta_,
|
||||
@@ -421,7 +424,7 @@ Status FlushJob::WriteLevel0Table() {
|
||||
mutable_cf_options_.paranoid_file_checks, cfd_->internal_stats(),
|
||||
&io_s, io_tracer_, event_logger_, job_context_->job_id, Env::IO_HIGH,
|
||||
&table_properties_, write_hint, full_history_ts_low, blob_callback_,
|
||||
&num_input_entries);
|
||||
&num_input_entries, &memtable_payload_bytes, &memtable_garbage_bytes);
|
||||
if (!io_s.ok()) {
|
||||
io_status_ = io_s;
|
||||
}
|
||||
@@ -436,6 +439,12 @@ Status FlushJob::WriteLevel0Table() {
|
||||
s = Status::Corruption(msg);
|
||||
}
|
||||
}
|
||||
if (tboptions.reason == TableFileCreationReason::kFlush) {
|
||||
RecordTick(stats_, MEMTABLE_PAYLOAD_BYTES_AT_FLUSH,
|
||||
memtable_payload_bytes);
|
||||
RecordTick(stats_, MEMTABLE_GARBAGE_BYTES_AT_FLUSH,
|
||||
memtable_garbage_bytes);
|
||||
}
|
||||
LogFlush(db_options_.info_log);
|
||||
}
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
|
||||
@@ -127,7 +127,8 @@ class FlushJobTestBase : public testing::Test {
|
||||
versions_.reset(
|
||||
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr));
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_session_id*/ ""));
|
||||
EXPECT_OK(versions_->Recover(column_families, false));
|
||||
}
|
||||
|
||||
|
||||
@@ -1267,7 +1267,7 @@ void InternalStats::DumpDBStats(std::string* value) {
|
||||
snprintf(
|
||||
buf, sizeof(buf),
|
||||
"Interval WAL: %s writes, %s syncs, "
|
||||
"%.2f writes per sync, written: %.2f MB, %.2f MB/s\n",
|
||||
"%.2f writes per sync, written: %.2f GB, %.2f MB/s\n",
|
||||
NumberToHumanString(interval_write_with_wal).c_str(),
|
||||
NumberToHumanString(interval_wal_synced).c_str(),
|
||||
interval_write_with_wal / static_cast<double>(interval_wal_synced + 1),
|
||||
|
||||
@@ -23,7 +23,7 @@ struct SuperVersion;
|
||||
struct SuperVersionContext {
|
||||
struct WriteStallNotification {
|
||||
WriteStallInfo write_stall_info;
|
||||
const ImmutableOptions* immutable_cf_options;
|
||||
const ImmutableOptions* immutable_options;
|
||||
};
|
||||
|
||||
autovector<SuperVersion*> superversions_to_free;
|
||||
@@ -66,7 +66,7 @@ struct SuperVersionContext {
|
||||
notif.write_stall_info.cf_name = name;
|
||||
notif.write_stall_info.condition.prev = old_cond;
|
||||
notif.write_stall_info.condition.cur = new_cond;
|
||||
notif.immutable_cf_options = ioptions;
|
||||
notif.immutable_options = ioptions;
|
||||
write_stall_notifications.push_back(notif);
|
||||
#else
|
||||
(void)old_cond;
|
||||
@@ -80,7 +80,7 @@ struct SuperVersionContext {
|
||||
#if !defined(ROCKSDB_LITE) && !defined(ROCKSDB_DISABLE_STALL_NOTIFICATION)
|
||||
// notify listeners on changed write stall conditions
|
||||
for (auto& notif : write_stall_notifications) {
|
||||
for (auto& listener : notif.immutable_cf_options->listeners) {
|
||||
for (auto& listener : notif.immutable_options->listeners) {
|
||||
listener->OnStallConditionsChanged(notif.write_stall_info);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ 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);
|
||||
/*io_tracer=*/nullptr, /*db_session_id*/ "");
|
||||
std::vector<ColumnFamilyDescriptor> cf_descs;
|
||||
cf_descs.emplace_back(kDefaultColumnFamilyName, ColumnFamilyOptions());
|
||||
cf_descs.emplace_back("one", ColumnFamilyOptions());
|
||||
@@ -153,7 +153,7 @@ 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);
|
||||
/*io_tracer=*/nullptr, /*db_session_id*/ "");
|
||||
std::vector<ColumnFamilyDescriptor> cf_descs;
|
||||
cf_descs.emplace_back(kDefaultColumnFamilyName, ColumnFamilyOptions());
|
||||
cf_descs.emplace_back("one", ColumnFamilyOptions());
|
||||
|
||||
@@ -28,8 +28,11 @@ FragmentedRangeTombstoneList::FragmentedRangeTombstoneList(
|
||||
InternalKey pinned_last_start_key;
|
||||
Slice last_start_key;
|
||||
num_unfragmented_tombstones_ = 0;
|
||||
total_tombstone_payload_bytes_ = 0;
|
||||
for (unfragmented_tombstones->SeekToFirst(); unfragmented_tombstones->Valid();
|
||||
unfragmented_tombstones->Next(), num_unfragmented_tombstones_++) {
|
||||
total_tombstone_payload_bytes_ += unfragmented_tombstones->key().size() +
|
||||
unfragmented_tombstones->value().size();
|
||||
if (num_unfragmented_tombstones_ > 0 &&
|
||||
icmp.Compare(last_start_key, unfragmented_tombstones->key()) > 0) {
|
||||
is_sorted = false;
|
||||
@@ -52,8 +55,12 @@ FragmentedRangeTombstoneList::FragmentedRangeTombstoneList(
|
||||
std::vector<std::string> keys, values;
|
||||
keys.reserve(num_unfragmented_tombstones_);
|
||||
values.reserve(num_unfragmented_tombstones_);
|
||||
// Reset the counter to zero for the next iteration over keys.
|
||||
total_tombstone_payload_bytes_ = 0;
|
||||
for (unfragmented_tombstones->SeekToFirst(); unfragmented_tombstones->Valid();
|
||||
unfragmented_tombstones->Next()) {
|
||||
total_tombstone_payload_bytes_ += unfragmented_tombstones->key().size() +
|
||||
unfragmented_tombstones->value().size();
|
||||
keys.emplace_back(unfragmented_tombstones->key().data(),
|
||||
unfragmented_tombstones->key().size());
|
||||
values.emplace_back(unfragmented_tombstones->value().data(),
|
||||
|
||||
@@ -72,6 +72,10 @@ struct FragmentedRangeTombstoneList {
|
||||
return num_unfragmented_tombstones_;
|
||||
}
|
||||
|
||||
uint64_t total_tombstone_payload_bytes() const {
|
||||
return total_tombstone_payload_bytes_;
|
||||
}
|
||||
|
||||
private:
|
||||
// Given an ordered range tombstone iterator unfragmented_tombstones,
|
||||
// "fragment" the tombstones into non-overlapping pieces, and store them in
|
||||
@@ -87,6 +91,7 @@ struct FragmentedRangeTombstoneList {
|
||||
std::list<std::string> pinned_slices_;
|
||||
PinnedIteratorsManager pinned_iters_mgr_;
|
||||
uint64_t num_unfragmented_tombstones_;
|
||||
uint64_t total_tombstone_payload_bytes_;
|
||||
};
|
||||
|
||||
// FragmentedRangeTombstoneIterator converts an InternalIterator of a range-del
|
||||
@@ -188,6 +193,9 @@ class FragmentedRangeTombstoneIterator : public InternalIterator {
|
||||
uint64_t num_unfragmented_tombstones() const {
|
||||
return tombstones_->num_unfragmented_tombstones();
|
||||
}
|
||||
uint64_t total_tombstone_payload_bytes() const {
|
||||
return tombstones_->total_tombstone_payload_bytes();
|
||||
}
|
||||
|
||||
private:
|
||||
using RangeTombstoneStack = FragmentedRangeTombstoneList::RangeTombstoneStack;
|
||||
|
||||
@@ -109,14 +109,20 @@ class Repairer {
|
||||
// TableCache can be small since we expect each table to be opened
|
||||
// once.
|
||||
NewLRUCache(10, db_options_.table_cache_numshardbits)),
|
||||
table_cache_(new TableCache(
|
||||
default_iopts_, env_options_, raw_table_cache_.get(),
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr)),
|
||||
table_cache_(
|
||||
// TODO: db_session_id for TableCache should be initialized after
|
||||
// db_session_id_ is set.
|
||||
new TableCache(default_iopts_, env_options_, raw_table_cache_.get(),
|
||||
/*block_cache_tracer=*/nullptr,
|
||||
/*io_tracer=*/nullptr, /*db_session_id*/ "")),
|
||||
wb_(db_options_.db_write_buffer_size),
|
||||
wc_(db_options_.delayed_write_rate),
|
||||
// TODO: db_session_id for VersionSet should be initialized after
|
||||
// db_session_id_ is set and use it for initialization.
|
||||
vset_(dbname_, &immutable_db_options_, env_options_,
|
||||
raw_table_cache_.get(), &wb_, &wc_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr),
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_session_id*/ ""),
|
||||
next_file_number_(1),
|
||||
db_lock_(nullptr),
|
||||
closed_(false) {
|
||||
@@ -448,7 +454,8 @@ class Repairer {
|
||||
-1 /* level */, false /* is_bottommost */,
|
||||
TableFileCreationReason::kRecovery, current_time,
|
||||
0 /* oldest_key_time */, 0 /* file_creation_time */,
|
||||
"DB Repairer" /* db_id */, db_session_id_, 0 /*target_file_size*/);
|
||||
"DB Repairer" /* db_id */, db_session_id_, 0 /*target_file_size*/,
|
||||
meta.fd.GetNumber());
|
||||
status = BuildTable(
|
||||
dbname_, /* versions */ nullptr, immutable_db_options_, tboptions,
|
||||
env_options_, table_cache_.get(), iter.get(),
|
||||
|
||||
@@ -68,14 +68,16 @@ const int kLoadConcurency = 128;
|
||||
TableCache::TableCache(const ImmutableOptions& ioptions,
|
||||
const FileOptions& file_options, Cache* const cache,
|
||||
BlockCacheTracer* const block_cache_tracer,
|
||||
const std::shared_ptr<IOTracer>& io_tracer)
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::string& db_session_id)
|
||||
: ioptions_(ioptions),
|
||||
file_options_(file_options),
|
||||
cache_(cache),
|
||||
immortal_tables_(false),
|
||||
block_cache_tracer_(block_cache_tracer),
|
||||
loader_mutex_(kLoadConcurency, kGetSliceNPHash64UnseededFnPtr),
|
||||
io_tracer_(io_tracer) {
|
||||
io_tracer_(io_tracer),
|
||||
db_session_id_(db_session_id) {
|
||||
if (ioptions_.row_cache) {
|
||||
// If the same cache is shared by multiple instances, we need to
|
||||
// disambiguate its entries.
|
||||
@@ -133,11 +135,11 @@ Status TableCache::GetTableReader(
|
||||
file_read_hist, ioptions_.rate_limiter.get(), ioptions_.listeners));
|
||||
s = ioptions_.table_factory->NewTableReader(
|
||||
ro,
|
||||
TableReaderOptions(ioptions_, prefix_extractor, file_options,
|
||||
internal_comparator, skip_filters, immortal_tables_,
|
||||
false /* force_direct_prefetch */, level,
|
||||
fd.largest_seqno, block_cache_tracer_,
|
||||
max_file_size_for_l0_meta_pin),
|
||||
TableReaderOptions(
|
||||
ioptions_, prefix_extractor, file_options, internal_comparator,
|
||||
skip_filters, immortal_tables_, false /* force_direct_prefetch */,
|
||||
level, fd.largest_seqno, block_cache_tracer_,
|
||||
max_file_size_for_l0_meta_pin, db_session_id_, fd.GetNumber()),
|
||||
std::move(file_reader), fd.GetFileSize(), table_reader,
|
||||
prefetch_index_and_filter_in_cache);
|
||||
TEST_SYNC_POINT("TableCache::GetTableReader:0");
|
||||
|
||||
@@ -51,7 +51,8 @@ class TableCache {
|
||||
TableCache(const ImmutableOptions& ioptions,
|
||||
const FileOptions& storage_options, Cache* cache,
|
||||
BlockCacheTracer* const block_cache_tracer,
|
||||
const std::shared_ptr<IOTracer>& io_tracer);
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::string& db_session_id);
|
||||
~TableCache();
|
||||
|
||||
// Return an iterator for the specified file number (the corresponding
|
||||
@@ -228,6 +229,7 @@ class TableCache {
|
||||
BlockCacheTracer* const block_cache_tracer_;
|
||||
Striped<port::Mutex, Slice> loader_mutex_;
|
||||
std::shared_ptr<IOTracer> io_tracer_;
|
||||
std::string db_session_id_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -909,7 +909,10 @@ void DumpManifestHandler::CheckIterationResult(const log::Reader& reader,
|
||||
fprintf(stdout, "comparator: %s\n", cfd->user_comparator()->Name());
|
||||
}
|
||||
assert(cfd->current());
|
||||
fprintf(stdout, "%s \n", cfd->current()->DebugString(hex_).c_str());
|
||||
|
||||
// Print out DebugStrings. Can include non-terminating null characters.
|
||||
fwrite(cfd->current()->DebugString(hex_).data(), sizeof(char),
|
||||
cfd->current()->DebugString(hex_).size(), stdout);
|
||||
}
|
||||
fprintf(stdout,
|
||||
"next_file_number %" PRIu64 " last_sequence %" PRIu64
|
||||
|
||||
@@ -285,9 +285,13 @@ class DumpManifestHandler : public VersionEditHandler {
|
||||
Status ApplyVersionEdit(VersionEdit& edit, ColumnFamilyData** cfd) override {
|
||||
// Write out each individual edit
|
||||
if (verbose_ && !json_) {
|
||||
fprintf(stdout, "%s\n", edit.DebugString(hex_).c_str());
|
||||
// Print out DebugStrings. Can include non-terminating null characters.
|
||||
fwrite(edit.DebugString(hex_).data(), sizeof(char),
|
||||
edit.DebugString(hex_).size(), stdout);
|
||||
} else if (json_) {
|
||||
fprintf(stdout, "%s\n", edit.DebugJSON(count_, hex_).c_str());
|
||||
// Print out DebugStrings. Can include non-terminating null characters.
|
||||
fwrite(edit.DebugString(hex_).data(), sizeof(char),
|
||||
edit.DebugString(hex_).size(), stdout);
|
||||
}
|
||||
++count_;
|
||||
return VersionEditHandler::ApplyVersionEdit(edit, cfd);
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "compaction/compaction.h"
|
||||
#include "db/blob/blob_fetcher.h"
|
||||
#include "db/blob/blob_file_cache.h"
|
||||
#include "db/blob/blob_file_reader.h"
|
||||
#include "db/blob/blob_index.h"
|
||||
@@ -1875,6 +1876,7 @@ void Version::Get(const ReadOptions& read_options, const LookupKey& k,
|
||||
// need to provide it here.
|
||||
bool is_blob_index = false;
|
||||
bool* const is_blob_to_use = is_blob ? is_blob : &is_blob_index;
|
||||
BlobFetcher blob_fetcher(this, read_options);
|
||||
|
||||
GetContext get_context(
|
||||
user_comparator(), merge_operator_, info_log_, db_statistics_,
|
||||
@@ -1882,7 +1884,7 @@ void Version::Get(const ReadOptions& read_options, const LookupKey& k,
|
||||
do_merge ? value : nullptr, do_merge ? timestamp : nullptr, value_found,
|
||||
merge_context, do_merge, max_covering_tombstone_seq, clock_, seq,
|
||||
merge_operator_ ? &pinned_iters_mgr : nullptr, callback, is_blob_to_use,
|
||||
tracing_get_id);
|
||||
tracing_get_id, &blob_fetcher);
|
||||
|
||||
// Pin blocks that we read to hold merge operands
|
||||
if (merge_operator_) {
|
||||
@@ -2031,6 +2033,7 @@ void Version::MultiGet(const ReadOptions& read_options, MultiGetRange* range,
|
||||
// use autovector in order to avoid unnecessary construction of GetContext
|
||||
// objects, which is expensive
|
||||
autovector<GetContext, 16> get_ctx;
|
||||
BlobFetcher blob_fetcher(this, read_options);
|
||||
for (auto iter = range->begin(); iter != range->end(); ++iter) {
|
||||
assert(iter->s->ok() || iter->s->IsMergeInProgress());
|
||||
get_ctx.emplace_back(
|
||||
@@ -2039,7 +2042,7 @@ void Version::MultiGet(const ReadOptions& read_options, MultiGetRange* range,
|
||||
iter->ukey_with_ts, iter->value, iter->timestamp, nullptr,
|
||||
&(iter->merge_context), true, &iter->max_covering_tombstone_seq, clock_,
|
||||
nullptr, merge_operator_ ? &pinned_iters_mgr : nullptr, callback,
|
||||
&iter->is_blob_index, tracing_mget_id);
|
||||
&iter->is_blob_index, tracing_mget_id, &blob_fetcher);
|
||||
// MergeInProgress status, if set, has been transferred to the get_context
|
||||
// state, so we set status to ok here. From now on, the iter status will
|
||||
// be used for IO errors, and get_context state will be used for any
|
||||
@@ -2551,7 +2554,7 @@ uint32_t GetExpiredTtlFilesCount(const ImmutableOptions& ioptions,
|
||||
} // anonymous namespace
|
||||
|
||||
void VersionStorageInfo::ComputeCompactionScore(
|
||||
const ImmutableOptions& immutable_cf_options,
|
||||
const ImmutableOptions& immutable_options,
|
||||
const MutableCFOptions& mutable_cf_options) {
|
||||
for (int level = 0; level <= MaxInputLevel(); level++) {
|
||||
double score;
|
||||
@@ -2603,7 +2606,7 @@ void VersionStorageInfo::ComputeCompactionScore(
|
||||
if (mutable_cf_options.ttl > 0) {
|
||||
score = std::max(
|
||||
static_cast<double>(GetExpiredTtlFilesCount(
|
||||
immutable_cf_options, mutable_cf_options, files_[level])),
|
||||
immutable_options, mutable_cf_options, files_[level])),
|
||||
score);
|
||||
}
|
||||
|
||||
@@ -2615,7 +2618,7 @@ void VersionStorageInfo::ComputeCompactionScore(
|
||||
// L0 files. Take into account size as well to avoid later giant
|
||||
// compactions to the base level.
|
||||
uint64_t l0_target_size = mutable_cf_options.max_bytes_for_level_base;
|
||||
if (immutable_cf_options.level_compaction_dynamic_level_bytes &&
|
||||
if (immutable_options.level_compaction_dynamic_level_bytes &&
|
||||
level_multiplier_ != 0.0) {
|
||||
// Prevent L0 to Lbase fanout from growing larger than
|
||||
// `level_multiplier_`. This prevents us from getting stuck picking
|
||||
@@ -2663,11 +2666,11 @@ void VersionStorageInfo::ComputeCompactionScore(
|
||||
ComputeFilesMarkedForCompaction();
|
||||
ComputeBottommostFilesMarkedForCompaction();
|
||||
if (mutable_cf_options.ttl > 0) {
|
||||
ComputeExpiredTtlFiles(immutable_cf_options, mutable_cf_options.ttl);
|
||||
ComputeExpiredTtlFiles(immutable_options, mutable_cf_options.ttl);
|
||||
}
|
||||
if (mutable_cf_options.periodic_compaction_seconds > 0) {
|
||||
ComputeFilesMarkedForPeriodicCompaction(
|
||||
immutable_cf_options, mutable_cf_options.periodic_compaction_seconds);
|
||||
immutable_options, mutable_cf_options.periodic_compaction_seconds);
|
||||
}
|
||||
EstimateCompactionBytesNeeded(mutable_cf_options);
|
||||
}
|
||||
@@ -3780,11 +3783,12 @@ VersionSet::VersionSet(const std::string& dbname,
|
||||
WriteBufferManager* write_buffer_manager,
|
||||
WriteController* write_controller,
|
||||
BlockCacheTracer* const block_cache_tracer,
|
||||
const std::shared_ptr<IOTracer>& io_tracer)
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::string& db_session_id)
|
||||
: column_family_set_(
|
||||
new ColumnFamilySet(dbname, _db_options, storage_options, table_cache,
|
||||
write_buffer_manager, write_controller,
|
||||
block_cache_tracer, io_tracer)),
|
||||
block_cache_tracer, io_tracer, db_session_id)),
|
||||
table_cache_(table_cache),
|
||||
env_(_db_options->env),
|
||||
fs_(_db_options->fs, io_tracer),
|
||||
@@ -3803,7 +3807,8 @@ VersionSet::VersionSet(const std::string& dbname,
|
||||
manifest_file_size_(0),
|
||||
file_options_(storage_options),
|
||||
block_cache_tracer_(block_cache_tracer),
|
||||
io_tracer_(io_tracer) {}
|
||||
io_tracer_(io_tracer),
|
||||
db_session_id_(db_session_id) {}
|
||||
|
||||
VersionSet::~VersionSet() {
|
||||
// we need to delete column_family_set_ because its destructor depends on
|
||||
@@ -3824,9 +3829,9 @@ void VersionSet::Reset() {
|
||||
if (column_family_set_) {
|
||||
WriteBufferManager* wbm = column_family_set_->write_buffer_manager();
|
||||
WriteController* wc = column_family_set_->write_controller();
|
||||
column_family_set_.reset(
|
||||
new ColumnFamilySet(dbname_, db_options_, file_options_, table_cache_,
|
||||
wbm, wc, block_cache_tracer_, io_tracer_));
|
||||
column_family_set_.reset(new ColumnFamilySet(
|
||||
dbname_, db_options_, file_options_, table_cache_, wbm, wc,
|
||||
block_cache_tracer_, io_tracer_, db_session_id_));
|
||||
}
|
||||
db_id_.clear();
|
||||
next_file_number_.store(2);
|
||||
@@ -4087,7 +4092,7 @@ Status VersionSet::ProcessManifestWrites(
|
||||
{
|
||||
FileOptions opt_file_opts = fs_->OptimizeForManifestWrite(file_options_);
|
||||
mu->Unlock();
|
||||
|
||||
TEST_SYNC_POINT("VersionSet::LogAndApply:WriteManifestStart");
|
||||
TEST_SYNC_POINT_CALLBACK("VersionSet::LogAndApply:WriteManifest", nullptr);
|
||||
if (!first_writer.edit_list.front()->IsColumnFamilyManipulation()) {
|
||||
for (int i = 0; i < static_cast<int>(versions.size()); ++i) {
|
||||
@@ -4838,7 +4843,8 @@ Status VersionSet::ReduceNumberOfLevels(const std::string& dbname,
|
||||
WriteController wc(options->delayed_write_rate);
|
||||
WriteBufferManager wb(options->db_write_buffer_size);
|
||||
VersionSet versions(dbname, &db_options, file_options, tc.get(), &wb, &wc,
|
||||
nullptr /*BlockCacheTracer*/, nullptr /*IOTracer*/);
|
||||
nullptr /*BlockCacheTracer*/, nullptr /*IOTracer*/,
|
||||
/*db_session_id*/ "");
|
||||
Status status;
|
||||
|
||||
std::vector<ColumnFamilyDescriptor> dummy;
|
||||
@@ -5584,6 +5590,9 @@ void VersionSet::GetLiveFilesMetaData(std::vector<LiveFileMetaData>* metadata) {
|
||||
filemetadata.oldest_blob_file_number = file->oldest_blob_file_number;
|
||||
filemetadata.file_checksum = file->file_checksum;
|
||||
filemetadata.file_checksum_func_name = file->file_checksum_func_name;
|
||||
filemetadata.temperature = file->temperature;
|
||||
filemetadata.oldest_ancester_time = file->TryGetOldestAncesterTime();
|
||||
filemetadata.file_creation_time = file->TryGetFileCreationTime();
|
||||
metadata->push_back(filemetadata);
|
||||
}
|
||||
}
|
||||
@@ -5699,7 +5708,8 @@ ReactiveVersionSet::ReactiveVersionSet(
|
||||
const std::shared_ptr<IOTracer>& io_tracer)
|
||||
: VersionSet(dbname, _db_options, _file_options, table_cache,
|
||||
write_buffer_manager, write_controller,
|
||||
/*block_cache_tracer=*/nullptr, io_tracer) {}
|
||||
/*block_cache_tracer=*/nullptr, io_tracer,
|
||||
/*db_session_id*/ "") {}
|
||||
|
||||
ReactiveVersionSet::~ReactiveVersionSet() {}
|
||||
|
||||
|
||||
@@ -150,7 +150,7 @@ class VersionStorageInfo {
|
||||
// We use compaction scores to figure out which compaction to do next
|
||||
// REQUIRES: db_mutex held!!
|
||||
// TODO find a better way to pass compaction_options_fifo.
|
||||
void ComputeCompactionScore(const ImmutableOptions& immutable_cf_options,
|
||||
void ComputeCompactionScore(const ImmutableOptions& immutable_options,
|
||||
const MutableCFOptions& mutable_cf_options);
|
||||
|
||||
// Estimate est_comp_needed_bytes_
|
||||
@@ -935,7 +935,8 @@ class VersionSet {
|
||||
WriteBufferManager* write_buffer_manager,
|
||||
WriteController* write_controller,
|
||||
BlockCacheTracer* const block_cache_tracer,
|
||||
const std::shared_ptr<IOTracer>& io_tracer);
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::string& db_session_id);
|
||||
// No copying allowed
|
||||
VersionSet(const VersionSet&) = delete;
|
||||
void operator=(const VersionSet&) = delete;
|
||||
@@ -1396,6 +1397,8 @@ class VersionSet {
|
||||
|
||||
std::shared_ptr<IOTracer> io_tracer_;
|
||||
|
||||
std::string db_session_id_;
|
||||
|
||||
private:
|
||||
// REQUIRES db mutex at beginning. may release and re-acquire db mutex
|
||||
Status ProcessManifestWrites(std::deque<ManifestWriter>& writers,
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/log_writer.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/file_system.h"
|
||||
#include "table/block_based/block_based_table_factory.h"
|
||||
#include "table/mock_table.h"
|
||||
@@ -698,21 +699,16 @@ class VersionSetTestBase {
|
||||
options_(),
|
||||
db_options_(options_),
|
||||
cf_options_(options_),
|
||||
immutable_cf_options_(db_options_, cf_options_),
|
||||
immutable_options_(db_options_, cf_options_),
|
||||
mutable_cf_options_(cf_options_),
|
||||
table_cache_(NewLRUCache(50000, 16)),
|
||||
write_buffer_manager_(db_options_.db_write_buffer_size),
|
||||
shutting_down_(false),
|
||||
mock_table_factory_(std::make_shared<mock::MockTableFactory>()) {
|
||||
const char* test_env_uri = getenv("TEST_ENV_URI");
|
||||
if (test_env_uri) {
|
||||
Status s = Env::LoadEnv(test_env_uri, &env_, &env_guard_);
|
||||
EXPECT_OK(s);
|
||||
} else if (getenv("MEM_ENV")) {
|
||||
EXPECT_OK(test::CreateEnvFromSystem(ConfigOptions(), &env_, &env_guard_));
|
||||
if (env_ == Env::Default() && getenv("MEM_ENV")) {
|
||||
env_guard_.reset(NewMemEnv(Env::Default()));
|
||||
env_ = env_guard_.get();
|
||||
} else {
|
||||
env_ = Env::Default();
|
||||
}
|
||||
EXPECT_NE(nullptr, env_);
|
||||
|
||||
@@ -722,14 +718,15 @@ class VersionSetTestBase {
|
||||
options_.env = env_;
|
||||
db_options_.env = env_;
|
||||
db_options_.fs = fs_;
|
||||
immutable_cf_options_.env = env_;
|
||||
immutable_cf_options_.fs = fs_;
|
||||
immutable_cf_options_.clock = env_->GetSystemClock().get();
|
||||
immutable_options_.env = env_;
|
||||
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));
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_session_id*/ ""));
|
||||
reactive_versions_ = std::make_shared<ReactiveVersionSet>(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_, nullptr);
|
||||
@@ -832,7 +829,8 @@ class VersionSetTestBase {
|
||||
versions_.reset(
|
||||
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr));
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_session_id*/ ""));
|
||||
EXPECT_OK(versions_->Recover(column_families_, false));
|
||||
}
|
||||
|
||||
@@ -910,7 +908,7 @@ class VersionSetTestBase {
|
||||
Options options_;
|
||||
ImmutableDBOptions db_options_;
|
||||
ColumnFamilyOptions cf_options_;
|
||||
ImmutableOptions immutable_cf_options_;
|
||||
ImmutableOptions immutable_options_;
|
||||
MutableCFOptions mutable_cf_options_;
|
||||
std::shared_ptr<Cache> table_cache_;
|
||||
WriteController write_controller_;
|
||||
@@ -1336,7 +1334,8 @@ TEST_F(VersionSetTest, WalAddition) {
|
||||
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));
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_session_id*/ ""));
|
||||
ASSERT_OK(new_versions->Recover(column_families_, /*read_only=*/false));
|
||||
const auto& wals = new_versions->GetWalSet().GetWals();
|
||||
ASSERT_EQ(wals.size(), 1);
|
||||
@@ -1402,7 +1401,8 @@ TEST_F(VersionSetTest, WalCloseWithoutSync) {
|
||||
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));
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_session_id*/ ""));
|
||||
ASSERT_OK(new_versions->Recover(column_families_, false));
|
||||
const auto& wals = new_versions->GetWalSet().GetWals();
|
||||
ASSERT_EQ(wals.size(), 2);
|
||||
@@ -1454,7 +1454,8 @@ TEST_F(VersionSetTest, WalDeletion) {
|
||||
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));
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_session_id*/ ""));
|
||||
ASSERT_OK(new_versions->Recover(column_families_, false));
|
||||
const auto& wals = new_versions->GetWalSet().GetWals();
|
||||
ASSERT_EQ(wals.size(), 1);
|
||||
@@ -1491,7 +1492,8 @@ TEST_F(VersionSetTest, WalDeletion) {
|
||||
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));
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_session_id*/ ""));
|
||||
ASSERT_OK(new_versions->Recover(column_families_, false));
|
||||
const auto& wals = new_versions->GetWalSet().GetWals();
|
||||
ASSERT_EQ(wals.size(), 1);
|
||||
@@ -1608,7 +1610,8 @@ TEST_F(VersionSetTest, DeleteWalsBeforeNonExistingWalNumber) {
|
||||
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));
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_session_id*/ ""));
|
||||
ASSERT_OK(new_versions->Recover(column_families_, false));
|
||||
const auto& wals = new_versions->GetWalSet().GetWals();
|
||||
ASSERT_EQ(wals.size(), 1);
|
||||
@@ -1643,7 +1646,8 @@ TEST_F(VersionSetTest, DeleteAllWals) {
|
||||
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));
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_session_id*/ ""));
|
||||
ASSERT_OK(new_versions->Recover(column_families_, false));
|
||||
const auto& wals = new_versions->GetWalSet().GetWals();
|
||||
ASSERT_EQ(wals.size(), 0);
|
||||
@@ -1684,7 +1688,8 @@ TEST_F(VersionSetTest, AtomicGroupWithWalEdits) {
|
||||
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));
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_session_id*/ ""));
|
||||
std::string db_id;
|
||||
ASSERT_OK(
|
||||
new_versions->Recover(column_families_, /*read_only=*/false, &db_id));
|
||||
@@ -1737,7 +1742,8 @@ class VersionSetWithTimestampTest : public VersionSetTest {
|
||||
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));
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_session_id*/ ""));
|
||||
ASSERT_OK(vset->Recover(column_families_, /*read_only=*/false,
|
||||
/*db_id=*/nullptr));
|
||||
for (auto* cfd : *(vset->GetColumnFamilySet())) {
|
||||
@@ -2779,7 +2785,7 @@ class VersionSetTestMissingFiles : public VersionSetTestBase,
|
||||
|
||||
std::unique_ptr<TableBuilder> builder(table_factory_->NewTableBuilder(
|
||||
TableBuilderOptions(
|
||||
immutable_cf_options_, mutable_cf_options_, *internal_comparator_,
|
||||
immutable_options_, mutable_cf_options_, *internal_comparator_,
|
||||
&int_tbl_prop_collector_factories, kNoCompression,
|
||||
CompressionOptions(),
|
||||
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily,
|
||||
|
||||
@@ -54,7 +54,8 @@ class WalManagerTest : public testing::Test {
|
||||
versions_.reset(
|
||||
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr));
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_session_id*/ ""));
|
||||
|
||||
wal_manager_.reset(
|
||||
new WalManager(db_options_, env_options_, nullptr /*IOTracer*/));
|
||||
|
||||
@@ -36,7 +36,7 @@ class WriteThread {
|
||||
// non-parallel informs a follower that its writes have been committed
|
||||
// (-> STATE_COMPLETED), or when a leader that has chosen to perform
|
||||
// updates in parallel and needs this Writer to apply its batch (->
|
||||
// STATE_PARALLEL_FOLLOWER).
|
||||
// STATE_PARALLEL_MEMTABLE_WRITER).
|
||||
STATE_INIT = 1,
|
||||
|
||||
// The state used to inform a waiting Writer that it has become the
|
||||
@@ -129,7 +129,7 @@ class WriteThread {
|
||||
WriteGroup* write_group;
|
||||
SequenceNumber sequence; // the sequence number to use for the first key
|
||||
Status status;
|
||||
Status callback_status; // status returned by callback->Callback()
|
||||
Status callback_status; // status returned by callback->Callback()
|
||||
|
||||
std::aligned_storage<sizeof(std::mutex)>::type state_mutex_bytes;
|
||||
std::aligned_storage<sizeof(std::condition_variable)>::type state_cv_bytes;
|
||||
@@ -246,7 +246,7 @@ class WriteThread {
|
||||
std::condition_variable& StateCV() {
|
||||
assert(made_waitable);
|
||||
return *static_cast<std::condition_variable*>(
|
||||
static_cast<void*>(&state_cv_bytes));
|
||||
static_cast<void*>(&state_cv_bytes));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -273,7 +273,7 @@ class WriteThread {
|
||||
// STATE_GROUP_LEADER. If w has been made part of a sequential batch
|
||||
// group and the leader has performed the write, returns STATE_DONE.
|
||||
// If w has been made part of a parallel batch group and is responsible
|
||||
// for updating the memtable, returns STATE_PARALLEL_FOLLOWER.
|
||||
// for updating the memtable, returns STATE_PARALLEL_MEMTABLE_WRITER.
|
||||
//
|
||||
// The db mutex SHOULD NOT be held when calling this function, because
|
||||
// it will block.
|
||||
@@ -310,8 +310,8 @@ class WriteThread {
|
||||
// the next leader if needed.
|
||||
void ExitAsMemTableWriter(Writer* self, WriteGroup& write_group);
|
||||
|
||||
// Causes JoinBatchGroup to return STATE_PARALLEL_FOLLOWER for all of the
|
||||
// non-leader members of this write batch group. Sets Writer::sequence
|
||||
// Causes JoinBatchGroup to return STATE_PARALLEL_MEMTABLE_WRITER for all of
|
||||
// the non-leader members of this write batch group. Sets Writer::sequence
|
||||
// before waking them up.
|
||||
//
|
||||
// WriteGroup* write_group: Extra state used to coordinate the parallel add
|
||||
|
||||
@@ -21,17 +21,37 @@
|
||||
#include "utilities/fault_injection_fs.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
namespace {
|
||||
|
||||
std::shared_ptr<const FilterPolicy> CreateFilterPolicy() {
|
||||
if (FLAGS_bloom_bits < 0) {
|
||||
return BlockBasedTableOptions().filter_policy;
|
||||
}
|
||||
const FilterPolicy* new_policy;
|
||||
if (FLAGS_use_ribbon_filter) {
|
||||
// Old and new API should be same
|
||||
if (std::random_device()() & 1) {
|
||||
new_policy = NewExperimentalRibbonFilterPolicy(FLAGS_bloom_bits);
|
||||
} else {
|
||||
new_policy = NewRibbonFilterPolicy(FLAGS_bloom_bits);
|
||||
}
|
||||
} else {
|
||||
if (FLAGS_use_block_based_filter) {
|
||||
new_policy = NewBloomFilterPolicy(FLAGS_bloom_bits, true);
|
||||
} else {
|
||||
new_policy = NewBloomFilterPolicy(FLAGS_bloom_bits, false);
|
||||
}
|
||||
}
|
||||
return std::shared_ptr<const FilterPolicy>(new_policy);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
StressTest::StressTest()
|
||||
: cache_(NewCache(FLAGS_cache_size)),
|
||||
compressed_cache_(NewLRUCache(FLAGS_compressed_cache_size)),
|
||||
filter_policy_(
|
||||
FLAGS_bloom_bits >= 0
|
||||
? FLAGS_use_ribbon_filter
|
||||
? NewExperimentalRibbonFilterPolicy(FLAGS_bloom_bits)
|
||||
: FLAGS_use_block_based_filter
|
||||
? NewBloomFilterPolicy(FLAGS_bloom_bits, true)
|
||||
: NewBloomFilterPolicy(FLAGS_bloom_bits, false)
|
||||
: nullptr),
|
||||
filter_policy_(CreateFilterPolicy()),
|
||||
db_(nullptr),
|
||||
#ifndef ROCKSDB_LITE
|
||||
txn_db_(nullptr),
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#ifdef GFLAGS
|
||||
#include "db_stress_tool/db_stress_common.h"
|
||||
#include "db_stress_tool/db_stress_driver.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#ifndef NDEBUG
|
||||
#include "utilities/fault_injection_fs.h"
|
||||
#endif
|
||||
@@ -34,11 +35,6 @@ static std::shared_ptr<ROCKSDB_NAMESPACE::DbStressEnvWrapper> env_wrapper_guard;
|
||||
static std::shared_ptr<CompositeEnvWrapper> fault_env_guard;
|
||||
} // namespace
|
||||
|
||||
static Env* GetCompositeEnv(std::shared_ptr<FileSystem> fs) {
|
||||
static std::shared_ptr<Env> composite_env = NewCompositeEnv(fs);
|
||||
return composite_env.get();
|
||||
}
|
||||
|
||||
KeyGenContext key_gen_ctx;
|
||||
|
||||
int db_stress_tool(int argc, char** argv) {
|
||||
@@ -78,22 +74,14 @@ int db_stress_tool(int argc, char** argv) {
|
||||
|
||||
if (!FLAGS_hdfs.empty()) {
|
||||
raw_env = new ROCKSDB_NAMESPACE::HdfsEnv(FLAGS_hdfs);
|
||||
} else if (!FLAGS_env_uri.empty()) {
|
||||
Status s = Env::LoadEnv(FLAGS_env_uri, &raw_env, &env_guard);
|
||||
if (raw_env == nullptr) {
|
||||
fprintf(stderr, "No Env registered for URI: %s\n", FLAGS_env_uri.c_str());
|
||||
exit(1);
|
||||
}
|
||||
} else if (!FLAGS_fs_uri.empty()) {
|
||||
std::shared_ptr<FileSystem> fs;
|
||||
Status s = FileSystem::Load(FLAGS_fs_uri, &fs);
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "Error: %s\n", s.ToString().c_str());
|
||||
exit(1);
|
||||
}
|
||||
raw_env = GetCompositeEnv(fs);
|
||||
} else {
|
||||
raw_env = Env::Default();
|
||||
Status s = Env::CreateFromUri(ConfigOptions(), FLAGS_env_uri, FLAGS_fs_uri,
|
||||
&raw_env, &env_guard);
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "Error Creating Env URI: %s: %s\n", FLAGS_env_uri.c_str(),
|
||||
s.ToString().c_str());
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
|
||||
@@ -68,3 +68,6 @@ lightmark:
|
||||
fgwu:
|
||||
full_name: Fenggang Wu
|
||||
fbid: 100002297362180
|
||||
|
||||
ltamasi:
|
||||
full_name: Levi Tamasi
|
||||
@@ -0,0 +1,101 @@
|
||||
---
|
||||
title: Integrated BlobDB
|
||||
layout: post
|
||||
author: ltamasi
|
||||
category: blog
|
||||
---
|
||||
## Background
|
||||
|
||||
BlobDB is essentially RocksDB for large-value use cases. The basic idea, which was proposed in the [WiscKey paper](https://www.usenix.org/system/files/conference/fast16/fast16-papers-lu.pdf), is key-value separation: by storing large values in dedicated blob files and storing only small pointers to them in the LSM tree, we avoid copying the values over and over again during compaction, thus reducing write amplification. Historically, BlobDB supported only FIFO and TTL based use cases that can tolerate some data loss. In addition, it was incompatible with many widely used RocksDB features, and required users to adopt a custom API. In 2020, we decided to rearchitect BlobDB from the ground up, taking the lessons learned from WiscKey and the original BlobDB but also drawing inspiration and incorporating ideas from other similar systems. Our goals were to eliminate the above limitations and to create a new integrated version that enables customers to use the well-known RocksDB API, has feature parity with the core of RocksDB, and offers better performance. This new implementation is now available and provides the following improvements over the original:
|
||||
|
||||
* **API.** In contrast with the legacy BlobDB implementation, which had its own `StackableDB`-based interface (`rocksdb::blob_db::BlobDB`), the new version can be used via the well-known `rocksdb::DB` API, and can be configured simply by using a few column family options.
|
||||
* **Consistency.** With the integrated BlobDB implementation, RocksDB’s consistency guarantees and various write options (like using the WAL or synchronous writes) now apply to blobs as well. Moreover, the new BlobDB keeps track of blob files in the RocksDB MANIFEST.
|
||||
* **Write performance.** When using the old BlobDB, blobs are extracted and immediately written to blob files by the BlobDB layer *in the application thread*. This has multiple drawbacks from a performance perspective: first, it requires synchronization; second, it means that expensive operations like compression are performed in the application thread; and finally, it involves flushing the blob file after each blob. The new code takes a completely different approach by *offloading blob file building to RocksDB’s background jobs*, i.e. flushes and compactions. This means that similarly to SSTs, any given blob file is now written by a single background thread, eliminating the need for locking, flushing, or performing compression in the foreground. Note that this approach is also a better fit for network-based file systems where small writes might be expensive and opens up the possibility of file format optimizations that involve buffering (like dictionary compression).
|
||||
* **Read performance.** The old code relies on each read (i.e. `Get`, `MultiGet`, or iterator) taking a snapshot and uses those snapshots when deciding which obsolete blob files can be removed. The new BlobDB improves this by generalizing RocksDB’s Version concept, which historically referred to the set of live SST files at a given point in time, to include the set of live blob files as well. This has performance benefits like [making the read path mostly lock-free by utilizing thread-local storage](https://rocksdb.org/blog/2014/06/27/avoid-expensive-locks-in-get.html). We have also introduced a blob file cache that can be utilized to keep frequently accessed blob files open.
|
||||
* **Garbage collection.** Key-value separation means that if a key pointing to a blob gets overwritten or deleted, the blob becomes unreferenced garbage. To be able to reclaim this space, BlobDB now has garbage collection capabilities. GC is integrated into the compaction process and works by relocating valid blobs residing in old blob files as they are encountered during compaction. Blob files can be marked obsolete (and eventually deleted in one shot) once they contain nothing but garbage. This is more efficient than the method used by WiscKey, which involves performing a `Get` operation to find out whether a blob is still referenced followed by a `Put` to update the reference, which in turn results in garbage collection competing and potentially conflicting with the application’s writes.
|
||||
* **Feature parity with the RocksDB core.** The new BlobDB supports way more features than the original and is near feature parity with vanilla RocksDB. In particular, we support all basic read/write APIs (with the exception of `Merge`, which is coming soon), recovery, compression, atomic flush, column families, compaction filters, checkpoints, backup/restore, transactions, per-file checksums, and the SST file manager. In addition, the new BlobDB’s options can be dynamically adjusted using the `SetOptions` interface.
|
||||
|
||||
## API
|
||||
|
||||
The new BlobDB can be configured (on a per-column family basis if needed) simply by using the following options:
|
||||
|
||||
* `enable_blob_files`: set it to `true` to enable key-value separation.
|
||||
* `min_blob_size`: values at or above this threshold will be written to blob files during flush or compaction.
|
||||
* `blob_file_size`: the size limit for blob files.
|
||||
* `blob_compression_type`: the compression type to use for blob files. All blobs in the same file are compressed using the same algorithm.
|
||||
* `enable_blob_garbage_collection`: set this to `true` to make BlobDB actively relocate valid blobs from the oldest blob files as they are encountered during compaction.
|
||||
* `blob_garbage_collection_age_cutoff`: the threshold that the GC logic uses to determine which blob files should be considered “old.” For example, the default value of 0.25 signals to RocksDB that blobs residing in the oldest 25% of blob files should be relocated by GC. This parameter can be tuned to adjust the trade-off between write amplification and space amplification.
|
||||
|
||||
The above options are all dynamically adjustable via the `SetOptions` API; changing them will affect subsequent flushes and compactions but not ones that are already in progress.
|
||||
|
||||
In terms of compaction styles, we recommend using leveled compaction with BlobDB. The rationale behind universal compaction in general is to provide lower write amplification at the expense of higher read amplification; however, as we will see later in the Performance section, BlobDB can provide very low write amp and good read performance with leveled compaction. Therefore, there is really no reason to take the hit in read performance that comes with universal compaction.
|
||||
|
||||
In addition to the above, consider tuning the following non-BlobDB specific options:
|
||||
|
||||
* `write_buffer_size`: this is the memtable size. You might want to increase it for large-value workloads to ensure that SST and blob files contain a decent number of keys.
|
||||
* `target_file_size_base`: the target size of SST files. Note that even when using BlobDB, it is important to have an LSM tree with a “nice” shape and multiple levels and files per level to prevent heavy compactions. Since BlobDB extracts and writes large values to blob files, it makes sense to make this parameter significantly smaller than the memtable size. One guideline is to set `blob_file_size` to the same value as `write_buffer_size` (adjusted for compression if needed) and make `target_file_size_base` proportionally smaller based on the ratio of key size to value size.
|
||||
* `max_bytes_for_level_base`: consider setting this to a multiple (e.g. 8x or 10x) of `target_file_size_base`.
|
||||
|
||||
As mentioned above, the new BlobDB now also supports compaction filters. Key-value separation actually enables an optimization here: if the compaction filter of an application can make a decision about a key-value solely based on the key, it is unnecessary to read the value from the blob file. Applications can take advantage of this optimization by implementing the new `FilterBlobByKey` method of the `CompactionFilter` interface. This method gets called by RocksDB first whenever it encounters a key-value where the value is stored in a blob file. If this method returns a “final” decision like `kKeep`, `kRemove`, `kChangeValue`, or `kRemoveAndSkipUntil`, RocksDB will honor that decision; on the other hand, if the method returns `kUndetermined`, RocksDB will read the blob from the blob file and call `FilterV2` with the value in the usual fashion.
|
||||
|
||||
## Performance
|
||||
|
||||
We tested the performance of the new BlobDB for six different value sizes between 1 KB and 1 MB using a customized version of our [standard benchmark suite](https://github.com/facebook/rocksdb/wiki/Performance-Benchmarks) on a box with an 18-core Skylake DE CPU (running at 1.6 GHz, with hyperthreading enabled), 64 GB RAM, a 512 GB boot SSD, and two 1.88 TB M.2 SSDs in a RAID0 configuration for data. The RocksDB version used was equivalent to 6.18.1, with some benchmarking and statistics related enhancements. Leveled and universal compaction without key-value separation were used as reference points. Note that for simplicity, we use “leveled compaction” and “universal compaction” as shorthand for leveled and universal compaction without key-value separation, respectively, and “BlobDB” for BlobDB with leveled compaction.
|
||||
|
||||
Our benchmarks cycled through six different workloads: two write-only ones (initial load and overwrite), two read/write ones (point lookup/write mix and range scan/write mix), and finally two read-only ones (point lookups and range scans). The first two phases performed a fixed amount of work (see below), while the final four were run for a fixed amount of time, namely 30 minutes each. Each phase other than the first one started with the database state left behind by the previous one. Here’s a brief description of the workloads:
|
||||
|
||||
* **Initial load**: this workload has two distinct stages, a single-threaded random write stage during which compactions are disabled (so all data is flushed to L0, where it remains for the rest of the stage), followed by a full manual compaction. The random writes are performed with load-optimized settings, namely using the vector memtable implementation and with concurrent memtable writes and WAL disabled. This stage was used to populate the database with 1 TB worth of raw values, e.g. 2^30 (~1 billion) 1 KB values or 2^20 (~1 million) 1 MB values.
|
||||
* **Overwrite**: this is a multi-threaded random write workload using the usual skiplist memtable, with compactions, WAL, and concurrent memtable writes enabled. In our tests, 16 writer threads were used. The total number of writes was set to the same number as in the initial load stage and split up evenly between the writer threads. For instance, for the 1 MB value size, we had 2^20 writes divided up between the 16 threads, resulting in each thread performing 2^16 write operations. At the end of this phase, a “wait for compactions” step was added to prevent this workload from exhibiting artificially low write amp or conversely, the next phase showing inflated write amp.
|
||||
* **Point lookup/write mix**: a single writer thread performing random writes while N (in our case, 16) threads perform random point lookups. WAL is enabled and all writes are synced.
|
||||
* **Range scan/write mix**: similar to the above, with one writer thread and N reader threads (where N was again set to 16 in our tests). The reader threads perform random range scans, with 10 `Next` calls per `Seek`. Again, WAL is enabled, and sync writes are used.
|
||||
* **Point lookups (read-only)**: N=16 threads perform random point lookups.
|
||||
* **Range scans (read-only)**: N=16 threads execute random range scans, with 10 `Next`s per `Seek` like above.
|
||||
|
||||
With that out of the way, let’s see how the new BlobDB performs against traditional leveled and universal compaction. In the next few sections, we’ll be looking at write amplification as well as read and write performance. We’ll also briefly compare the write performance of the new BlobDB with the legacy implementation.
|
||||
|
||||
### Write amplification
|
||||
|
||||
Reducing write amp is the original motivation for key-value separation. Here, we follow RocksDB’s definition of write amplification (as used in compaction statistics and the info log). That is, we define write amp as the total amount of data written by flushes and compactions divided by the amount of data written by flushes, where “data written” includes SST files and blob files as well (if applicable). The following charts show that BlobDB significantly reduces write amplification for all of our (non-read only) workloads.
|
||||
|
||||
For the initial load, where due to the nature of the workload both leveled and universal already have a low write amp factor of 1.6, BlobDB has a write amp close to the theoretical minimum of 1.0, namely in the 1.0..1.02 range, depending on value size. How is this possible? Well, the trick is that when key-value separation is used, the full compaction step only has to sort the keys but not the values. This results in a write amp that is about **36% lower** than the already low write amp you get with either leveled or universal.
|
||||
|
||||
In the case of the overwrite workload, BlobDB had a write amp between 1.4 and 1.7 depending on value size. This is around **75-78% lower** than the write amp of leveled compaction (6.1 to 6.8) and **70-77% lower** than universal (5.7 to 6.2); for this workload, there wasn’t a huge difference between the performance of leveled and universal.
|
||||
|
||||
When it comes to the point lookup/write mix workload, BlobDB had a write amp between 1.4 and 1.8. This is **83-88% lower** than the write amp of leveled compaction, which had values between 10.8 and 12.5. Universal fared much better than leveled under this workload, and had write amp in the 2.2..6.6 range; however, BlobDB still provided significant gains for all value sizes we tested: namely, write amp was **18-77% lower** than that of universal, depending on value size.
|
||||
|
||||
As for the range scan/write mix workload, BlobDB again had a write amp between 1.4 and 1.8, while leveled had values between 13.6 and 14.9, and universal was between 2.8 and 5.0. In other words, BlobDB’s write amp was **88-90% lower** than that of leveled, and **46-70% lower** than that of universal.
|
||||
|
||||

|
||||
{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"}
|
||||
|
||||
### Write performance
|
||||
|
||||
In terms of write performance, there are other factors to consider besides write amplification. The following charts show some interesting metrics for the two write-only workloads (initial load and overwrite). As discussed earlier, these two workloads perform a fixed amount of work; the two charts in the top row show how long it took BlobDB, leveled, and universal to complete that work. Note that each bar is broken down into two, corresponding to the two stages of each workload (random write and full compaction for initial load, and random write and waiting for compactions for overwrite).
|
||||
|
||||
For initial load, note that the random write stage takes the same amount of time regardless of which algorithm is used. This is not surprising considering the fact that compactions are disabled during this stage and thus RocksDB is simply writing L0 files (and in BlobDB’s case, blob files) as fast as it can. The second stage, on the other hand, is very different: as mentioned above, BlobDB essentially only needs to read, sort, and rewrite the keys during compaction, which can be done much much faster (with 1 MB values, more than a hundred times faster) than doing the same for large key-values. Due to this, initial load completed **2.3x to 4.7x faster** overall when using BlobDB.
|
||||
|
||||
As for the overwrite workload, BlobDB performs much better during both stages. The two charts in the bottom row help explain why. In the case of both leveled and universal compaction, compactions can’t keep up with the write rate, which eventually leads to back pressure in the form of write stalls. As shown in the chart below, both leveled and universal stall between ~40% and ~70% of the time; on the other hand, BlobDB is stall-free except for the largest value size tested (1 MB). This naturally leads to higher throughput, namely **2.1x to 3.5x higher** throughput compared to leveled, and **1.6x to 3.0x higher** throughput compared to universal. The overwrite time chart also shows that the catch-up stage that waits for all compactions to finish is much shorter (and in fact, at larger value sizes, negligible) with BlobDB.
|
||||
|
||||

|
||||
{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"}
|
||||
|
||||
### Read/write and read-only performance
|
||||
|
||||
The charts below show the read performance (in terms of operations per second) of BlobDB versus leveled and universal compaction under the two read/write workloads and the two read-only workloads. BlobDB meets or exceeds the read performance of leveled compaction, except for workloads involving range scans at the two smallest value sizes tested (1 KB and 4 KB). It also provides better (in some cases, much better) read performance than universal across the board. In particular, BlobDB provides up **1.4x higher** read performance than leveled (for larger values), and up to **5.6x higher** than universal.
|
||||
|
||||

|
||||
{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"}
|
||||
|
||||
### Comparing the two BlobDB implementations
|
||||
|
||||
To compare the write performance of the new BlobDB with the legacy implementation, we ran two versions of the first (single-threaded random write) stage of the initial load benchmark using 1 KB values: one with WAL disabled, and one with WAL enabled. The new implementation completed the load **4.6x faster** than the old one without WAL, and **2.3x faster** with WAL.
|
||||
|
||||

|
||||
{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"}
|
||||
|
||||
## Future work
|
||||
|
||||
There are a few remaining features that are not yet supported by the new BlobDB. The most important one is `Merge` (and the related `GetMergeOperands` API); in addition, we don’t currently support the `EventListener` interface, the `GetLiveFilesMetaData` and `GetColumnFamilyMetaData` APIs, secondary instances, and ingestion of blob files. We will continue to work on closing this gap.
|
||||
|
||||
We also have further plans when it comes to performance. These include optimizing garbage collection, introducing a dedicated cache for blobs, improving iterator and `MultiGet` performance, and evolving the blob file format amongst others.
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
title: Online Validation
|
||||
layout: post
|
||||
author: sdong
|
||||
category: blog
|
||||
---
|
||||
To prevent or mitigate data corrution in RocksDB when some software or hardware issues happens, we keep adding online consistency checks and improving existing ones.
|
||||
|
||||
We improved ColumnFamilyOptions::force_consistency_checks and enabled it by default. The option does some basic consistency checks to LSM-tree, e.g., files in one level are not overlapping. The DB will be frozen from new writes if a violation is detected. Previously, the feature’s check was too limited and didn’t always freeze the DB in a timely manner. Last year, we made the checking stricter so that it can [catch much more corrupted LSM-tree structures](https://github.com/facebook/rocksdb/pull/6901). We also fixed several issues where the checking failure was swallowed without freezing the DB. After making force_consistency_checks more reliable, we changed the default value to be on.
|
||||
|
||||
ColumnFamilyOptions::paranoid_file_checks does some more expensive extra checking when generating a new SST file. Last year, we advanced coverage to this feature: after every SST file is generated, the SST file is created, read back keys one by one and check two things: (1) the keys are in comparator order (also available and enabled by default during file write via ColumnFamilyOptions::check_flush_compaction_key_order); (2) the hash of all the KVs is the same as calculated when we add KVs into it. These checks detect certain corruptions so we can prevent the corrupt files from being applied to the DB. We suggest users turn it on at least in shadow environments, and consider to run it in production too if you can afford the overheads.
|
||||
|
||||
A recent feature is added to check the count of entries added into memtable while flushing it into an SST file. This feature is to have some online coverage to memtable corruption, caused by either software bug or hardware issue. This feature will be released in the coming release (6.21) and by default on. In the future, we will check more counters during memtables, e.g. number of puts or number of deletes.
|
||||
|
||||
We also improved the reporting of online validation errors to improve debuggability. For example, failure to parse a corrupt key now reports details about the corrupt key. Since we did not want to expose key data in logs, error messages, etc., by default, this reporting is opt-in via DBOptions::allow_data_in_errors.
|
||||
|
||||
More online checking features are planned and some are more sophisticated, including key/value checksums and sample based query validation.
|
||||
@@ -0,0 +1,195 @@
|
||||
---
|
||||
title: RocksDB Secondary Cache
|
||||
layout: post
|
||||
author: anand1976
|
||||
category: blog
|
||||
---
|
||||
## Introduction
|
||||
|
||||
The RocksDB team is implementing support for a block cache on non-volatile media, such as a local flash device or NVM/SCM. It can be viewed as an extension of RocksDB’s current volatile block cache (LRUCache or ClockCache). The non-volatile block cache acts as a second tier cache that contains blocks evicted from the volatile cache. Those blocks are then promoted to the volatile cache as they become hotter due to access.
|
||||
|
||||
This feature is meant for cases where the DB is located on remote storage or cloud storage. The non-volatile cache is officially referred to in RocksDB as the SecondaryCache. By maintaining a SecondaryCache that’s an order of magnitude larger than DRAM, fewer reads would be required from remote storage, thus reducing read latency as well as network bandwidth consumption.
|
||||
|
||||
From the user point of view, the local flash cache will support the following requirements -
|
||||
|
||||
1. Provide a pointer to a secondary cache when opening a DB
|
||||
2. Be able to share the secondary cache across DBs in the same process
|
||||
3. Have multiple secondary caches on a host
|
||||
4. Support persisting the cache across process restarts and reboots by ensuring repeatability of the cache key
|
||||
|
||||

|
||||
{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"}
|
||||
|
||||
## Design
|
||||
|
||||
When designing the API for a SecondaryCache, we had a choice between making it visible to the RocksDB code (table reader) or hiding it behind the RocksDB block cache. There are several advantages of hiding it behind the block cache -
|
||||
|
||||
* Allows flexibility in insertion of blocks into the secondary cache. A block can be inserted on eviction from the RAM tier, or it could be eagerly inserted.
|
||||
* It makes the rest of the RocksDB code less complex by providing a uniform interface regardless of whether a secondary cache is configured or not
|
||||
* Makes parallel reads, peeking in the cache for prefetching, failure handling etc. easier
|
||||
* Makes it easier to extend to compressed data if needed, and allows other persistent media, such as PM, to be added as an additional tier
|
||||
|
||||
|
||||
We decided to make the secondary cache transparent to the rest of RocksDB code by hiding it behind the block cache. A key issue that we needed to address was the allocation and ownership of memory of the cached items - insertion into the secondary cache may require that memory be allocated by the same. This means that parts of the cached object that can be transferred to the secondary cache needs to be copied out (referred to as **unpacking**), and on a lookup the data stored in the secondary cache needs to be provided to the object constructor (referred to as **packing**). For RocksDB cached objects such as data blocks, index and filter blocks, and compression dictionaries, unpacking involves copying out the raw uncompressed BlockContents of the block, and packing involves constructing the corresponding block/index/filter/dictionary object using the raw uncompressed data.
|
||||
|
||||
Another alternative we considered was the existing PersistentCache interface. However, we decided to not pursue it and eventually deprecate it for the following reasons -
|
||||
* It is exposed directly to the table reader code, which makes it more difficult to implement different policies such as inclusive/exclusive cache, as well as extending it to more sophisticated admission control policies
|
||||
* The interface does not allow for custom memory allocation and object packing/unpacking, so new APIs would have to be defined anyway
|
||||
* The current PersistentCache implementation is very simple and does not have any admission control policies
|
||||
|
||||
## API
|
||||
|
||||
The interface between RocksDB’s block cache and the secondary cache is designed to allow pluggable implementations. For FB internal usage, we plan to use Cachelib with a wrapper to provide the plug-in implementation and use folly and other fbcode libraries, which cannot be used directly by RocksDB, to efficiently implement the cache operations. The following diagrams show the flow of insertion and lookup of a block.
|
||||
|
||||

|
||||
{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"}
|
||||
|
||||

|
||||
{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"}
|
||||
|
||||
An item in the secondary cache is referenced by a SecondaryCacheHandle. The handle may not be immediately ready or have a valid value. The caller can call IsReady() to determine if its ready, and can call Wait() in order to block until it becomes ready. The caller must call Value() after it becomes ready to determine if the item was successfully read. Value() must return nullptr on failure.
|
||||
|
||||
```
|
||||
class SecondaryCacheHandle {
|
||||
public:
|
||||
virtual ~SecondaryCacheHandle() {}
|
||||
|
||||
// Returns whether the handle is ready or not
|
||||
virtual bool IsReady() = 0;
|
||||
|
||||
// Block until handle becomes ready
|
||||
virtual void Wait() = 0;
|
||||
|
||||
// Return the value. If nullptr, it means the lookup was unsuccessful
|
||||
virtual void* Value() = 0;
|
||||
|
||||
// Return the size of value
|
||||
virtual size_t Size() = 0;
|
||||
};
|
||||
```
|
||||
|
||||
The user of the secondary cache (for example, BlockBasedTableReader indirectly through LRUCache) must implement the callbacks defined in CacheItemHelper, in order to facilitate the unpacking/packing of objects for saving to and restoring from the secondary cache. The CreateCallback must be implemented to construct a cacheable object from the raw data in secondary cache.
|
||||
|
||||
```
|
||||
// The SizeCallback takes a void* pointer to the object and returns the size
|
||||
// of the persistable data. It can be used by the secondary cache to allocate
|
||||
// memory if needed.
|
||||
using SizeCallback = size_t (*)(void* obj);
|
||||
|
||||
// The SaveToCallback takes a void* object pointer and saves the persistable
|
||||
// data into a buffer. The secondary cache may decide to not store it in a
|
||||
// contiguous buffer, in which case this callback will be called multiple
|
||||
// times with increasing offset
|
||||
using SaveToCallback = Status (*)(void* from_obj, size_t from_offset,
|
||||
size_t length, void* out);
|
||||
|
||||
// A function pointer type for custom destruction of an entry's
|
||||
// value. The Cache is responsible for copying and reclaiming space
|
||||
// for the key, but values are managed by the caller.
|
||||
using DeleterFn = void (*)(const Slice& key, void* value);
|
||||
|
||||
// A struct with pointers to helper functions for spilling items from the
|
||||
// cache into the secondary cache. May be extended in the future. An
|
||||
// instance of this struct is expected to outlive the cache.
|
||||
struct CacheItemHelper {
|
||||
SizeCallback size_cb;
|
||||
SaveToCallback saveto_cb;
|
||||
DeleterFn del_cb;
|
||||
|
||||
CacheItemHelper() : size_cb(nullptr), saveto_cb(nullptr), del_cb(nullptr) {}
|
||||
CacheItemHelper(SizeCallback _size_cb, SaveToCallback _saveto_cb,
|
||||
DeleterFn _del_cb)
|
||||
: size_cb(_size_cb), saveto_cb(_saveto_cb), del_cb(_del_cb) {}
|
||||
};
|
||||
|
||||
// The CreateCallback is passed by the block cache user to Lookup(). It
|
||||
// takes in a buffer from the NVM cache and constructs an object using
|
||||
// it. The callback doesn't have ownership of the buffer and should
|
||||
// copy the contents into its own buffer.
|
||||
// typedef std::function<Status(void* buf, size_t size, void** out_obj,
|
||||
// size_t* charge)>
|
||||
// CreateCallback;
|
||||
using CreateCallback = std::function<Status(void* buf, size_t size,
|
||||
void** out_obj, size_t* charge)>;
|
||||
```
|
||||
|
||||
The secondary cache provider must provide a concrete implementation of the SecondaryCache abstract class.
|
||||
|
||||
```
|
||||
// SecondaryCache
|
||||
//
|
||||
// Cache interface for caching blocks on a secondary tier (which can include
|
||||
// non-volatile media, or alternate forms of caching such as compressed data)
|
||||
class SecondaryCache {
|
||||
public:
|
||||
virtual ~SecondaryCache() {}
|
||||
|
||||
virtual std::string Name() = 0;
|
||||
|
||||
static const std::string Type() { return "SecondaryCache"; }
|
||||
|
||||
// Insert the given value into this cache. The value is not written
|
||||
// directly. Rather, the SaveToCallback provided by helper_cb will be
|
||||
// used to extract the persistable data in value, which will be written
|
||||
// to this tier. The implementation may or may not write it to cache
|
||||
// depending on the admission control policy, even if the return status is
|
||||
// success.
|
||||
virtual Status Insert(const Slice& key, void* value,
|
||||
const Cache::CacheItemHelper* helper) = 0;
|
||||
|
||||
// Lookup the data for the given key in this cache. The create_cb
|
||||
// will be used to create the object. The handle returned may not be
|
||||
// ready yet, unless wait=true, in which case Lookup() will block until
|
||||
// the handle is ready
|
||||
virtual std::unique_ptr<SecondaryCacheHandle> Lookup(
|
||||
const Slice& key, const Cache::CreateCallback& create_cb, bool wait) = 0;
|
||||
|
||||
// At the discretion of the implementation, erase the data associated
|
||||
// with key
|
||||
virtual void Erase(const Slice& key) = 0;
|
||||
|
||||
// Wait for a collection of handles to become ready. This would be used
|
||||
// by MultiGet, for example, to read multitple data blocks in parallel
|
||||
virtual void WaitAll(std::vector<SecondaryCacheHandle*> handles) = 0;
|
||||
|
||||
virtual std::string GetPrintableOptions() const = 0;
|
||||
};
|
||||
```
|
||||
|
||||
A SecondaryCache is configured by the user by providing a pointer to it in LRUCacheOptions -
|
||||
```
|
||||
struct LRUCacheOptions {
|
||||
...
|
||||
// A SecondaryCache instance to use as an additional cache tier
|
||||
std::shared_ptr<SecondaryCache> secondary_cache;
|
||||
...
|
||||
};
|
||||
```
|
||||
|
||||
## Current Status
|
||||
|
||||
The initial RocksDB support for the secondary cache has been merged into the master branch, and will be available in the 6.21 release. This includes providing a way for the user to configure a secondary cache when instantiating RocksDB’s LRU cache (volatile block cache), spilling blocks evicted from the LRU cache to the flash cache, promoting a block read from the SecondaryCache to the LRU cache, update tools such as cache_bench and db_bench to specify a flash cache. The relevant PRs are [#8271](https://github.com/facebook/rocksdb/pull/8271), [#8191](https://github.com/facebook/rocksdb/pull/8191), and [#8312](https://github.com/facebook/rocksdb/pull/8312).
|
||||
|
||||
We prototyped an end-to-end solution, with the above PRs as well as a Cachelib based implementation of the SecondaryCache. We ran a mixgraph benchmark to simulate a realistic read/write workload. The results showed a 15% gain with the local flash cache over no local cache, and a ~25-30% reduction in network reads with a corresponding decrease in cache misses.
|
||||
|
||||

|
||||
{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"}
|
||||
|
||||

|
||||
{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"}
|
||||
|
||||
## Future Work
|
||||
|
||||
In the short term, we plan to do the following in order to fully integrate the SecondaryCache with RocksDB -
|
||||
|
||||
1. Use DB session ID as the cache key prefix to ensure uniqueness and repeatability
|
||||
2. Optimize flash cache usage of MultiGet and iterator workloads
|
||||
3. Stress testing
|
||||
4. More benchmarking
|
||||
|
||||
Longer term, we plan to deploy this in production at Facebook.
|
||||
|
||||
## Call to Action
|
||||
|
||||
We are hoping for a community contribution of a secondary cache implementation, which would make this feature usable by the broader RocksDB userbase. If you are interested in contributing, please reach out to us in [this issue](https://github.com/facebook/rocksdb/issues/8347).
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
---
|
||||
title: Preset Dictionary Compression
|
||||
layout: post
|
||||
author: ajkr
|
||||
category: blog
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Compression algorithms relying on an adaptive dictionary, such as LZ4, zstd, and zlib, struggle to achieve good compression ratios on small inputs when using the basic compress API.
|
||||
With the basic compress API, the compressor starts with an empty dictionary.
|
||||
With small inputs, not much content gets added to the dictionary during the compression.
|
||||
Combined, these factors suggest the dictionary will never have enough contents to achieve great compression ratios.
|
||||
|
||||
RocksDB groups key-value pairs into data blocks before storing them in files.
|
||||
For use cases that are heavy on random accesses, smaller data block size is sometimes desirable for reducing I/O and CPU spent reading blocks.
|
||||
However, as explained above, smaller data block size comes with the downside of worse compression ratio when using the basic compress API.
|
||||
|
||||
Fortunately, zstd and other libraries offer advanced compress APIs that preset the dictionary.
|
||||
A preset dictionary makes it possible for the compressor to start from a useful state instead of from an empty one, making compression immediately effective.
|
||||
|
||||
RocksDB now optionally takes advantage of these dictionary presetting APIs.
|
||||
The challenges in integrating this feature into the storage engine were more substantial than apparent on the surface.
|
||||
First, we need to target a preset dictionary to the relevant data.
|
||||
Second, preset dictionaries need to be trained from data samples, which need to be gathered.
|
||||
Third, preset dictionaries need to be persisted since they are needed at decompression time.
|
||||
Fourth, overhead in accessing the preset dictionary must be minimized to prevent regression in critical code paths.
|
||||
Fifth, we need easy-to-use measurement to evaluate candidate use cases and production impact.
|
||||
|
||||
In production, we have deployed dictionary presetting to save space in multiple RocksDB use cases with data block size 8KB or smaller.
|
||||
We have measured meaningful benefit to compression ratio in use cases with data block size up to 16KB.
|
||||
We have also measured a use case that can save both CPU and space by reducing data block size and turning on dictionary presetting at the same time.
|
||||
|
||||
## Feature design
|
||||
#### Targeting
|
||||
|
||||
Over time we have considered a few possibilities for the scope of a dictionary.
|
||||
|
||||
- Subcompaction
|
||||
- SST file
|
||||
- Column family
|
||||
|
||||
The original choice was subcompaction scope.
|
||||
This enabled an approach with minimal buffering overhead because we could collect samples while generating the first output SST file.
|
||||
The dictionary could then be trained and applied to subsequent SST files in the same subcompaction.
|
||||
|
||||
However, we found a large use case where the proximity of data in the keyspace was more correlated with its similarity than we had predicted.
|
||||
In particular, the approach of training a dictionary on an adjacent file yielded substantially worse ratios than training the dictionary on the same file it would be used to compress.
|
||||
In response to this finding, we changed the preset dictionary scope to per SST file.
|
||||
|
||||
With this change in approach, we had to face the problem we had hoped to avoid: how can we compress all of an SST file's data blocks with the same preset dictionary while that dictionary can only be trained after many data blocks have been sampled?
|
||||
The solutions we considered both involved a new overhead.
|
||||
We could read the input more than once and introduce I/O overhead, or we could buffer the uncompressed output file data blocks until a dictionary is trained, introducing memory overhead.
|
||||
We chose to take the hit on memory overhead.
|
||||
|
||||
Another approach that we considered was associating multiple dictionaries with a column family.
|
||||
For example, in MyRocks there could be a dictionary trained on data from each large table.
|
||||
When compressing a data block, we would look at the table to which its data belongs and pick the corresponding dictionary.
|
||||
However, this approach would introduce many challenges.
|
||||
RocksDB would need to be aware of the key schema to know where are the table boundaries.
|
||||
RocksDB would also need to periodically update the dictionaries to account for changes in data pattern.
|
||||
It would need somewhere to store dictionaries at column family scope.
|
||||
Overall, we thought these challenges were too difficult to pursue the approach.
|
||||
|
||||
#### Training
|
||||
|
||||

|
||||
{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"}
|
||||
<p align="center"><i>
|
||||
Raw samples mode (`zstd_max_train_bytes == 0`)
|
||||
</i></p>
|
||||
|
||||
As mentioned earlier, the approach we took is to build the dictionary from buffered uncompressed data blocks.
|
||||
The first row of data blocks in these diagrams illustrate this buffering.
|
||||
The second row illustrates training samples selected from the buffered blocks.
|
||||
In raw samples mode (above), the final dictionary is simply the concatenation of these samples.
|
||||
Whereas, in zstd training mode (below), these samples will be passed to the trainer to produce the final dictionary.
|
||||
|
||||

|
||||
{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"}
|
||||
<p align="center"><i>
|
||||
zstd training mode (`zstd_max_train_bytes > 0`)
|
||||
</i></p>
|
||||
|
||||
#### Compression path
|
||||
|
||||
Once the preset dictionary is generated by the above process, we apply it to the buffered data blocks and write them to the output file.
|
||||
Thereafter, newly generated data blocks are immediately compressed and written out.
|
||||
|
||||
One optimization here is available to zstd v0.7.0+ users.
|
||||
Instead of deserializing the dictionary on each compress invocation, we can do that work once and reuse it.
|
||||
A `ZSTD_CDict` holds this digested dictionary state and is passed to the compress API.
|
||||
|
||||
#### Persistence
|
||||
|
||||
When an SST file's data blocks are compressed using a preset dictionary, that dictionary is stored inside the file for later use in decompression.
|
||||
|
||||

|
||||
{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"}
|
||||
<p align="center"><i>
|
||||
SST file layout with the preset dictionary in its own (uncompressed) block
|
||||
</i></p>
|
||||
|
||||
#### Decompression path
|
||||
|
||||
To decompress, we need to provide both the data block and the dictionary used to compress it.
|
||||
Since dictionaries are just blocks in a file, we access them through block cache.
|
||||
However this additional load on block cache can be problematic.
|
||||
It can be alleviated by pinning the dictionaries to avoid going through the LRU locks.
|
||||
|
||||
An optimization analogous to the digested dictionary exists for certain zstd users (see User API section for details).
|
||||
When enabled, the block cache stores the digested dictionary state for decompression (`ZSTD_DDict`) instead of the block contents.
|
||||
In some cases we have seen decompression CPU decrease overall when enabling dictionary thanks to this optimization.
|
||||
|
||||
#### Measurement
|
||||
|
||||
Typically our first step in evaluating a candidate use case is an offline analysis of the data.
|
||||
This gives us a quick idea whether presetting dictionary will be beneficial without any code, config, or data changes.
|
||||
Our `sst_dump` tool reports what size SST files would have been using specified compression libraries and options.
|
||||
We can select random SST files and compare the size with vs. without dictionary.
|
||||
|
||||
When that goes well, the next step is to see how it works in a live DB, like a production shadow or canary.
|
||||
There we can observe how it affects application/system metrics.
|
||||
|
||||
Even after dictionary is enabled, there is the question of how much space was finally saved.
|
||||
We provide a way to A/B test size with vs. without dictionary while running in production.
|
||||
This feature picks a sample of data blocks to compress in multiple ways -- one of the outputs is stored, while the other outputs are thrown away after counting their size.
|
||||
Due to API limitations, the stored output always has to be the dictionary-compressed one, so this feature can only be used after enabling dictionary.
|
||||
The size with and without dictionary are stored in the SST file as table properties.
|
||||
These properties can be aggregated across all SST files in a DB (and across all DBs in a tier) to learn the final space saving.
|
||||
|
||||
## User API
|
||||
|
||||
RocksDB allows presetting compression dictionary for users of LZ4, zstd, and zlib.
|
||||
The most advanced capabilities are available to zstd v1.1.4+ users who statically link (see below).
|
||||
Newer versions of zstd (v1.3.6+) have internal changes to the dictionary trainer and digested dictionary management, which significantly improve memory and CPU efficiency.
|
||||
|
||||
Run-time settings:
|
||||
|
||||
- `CompressionOptions::max_dict_bytes`: Limit on per-SST file dictionary size. Increasing this causes dictionaries to consume more space and memory for the possibility of better data block compression. A typical value we use is 16KB.
|
||||
- (**zstd only**) `CompressionOptions::zstd_max_train_bytes`: Limit on training data passed to zstd dictionary trainer. Larger values cause the training to consume more CPU (and take longer) while generating more effective dictionaries. The starting point guidance we received from zstd team is to set it to 100x `CompressionOptions::max_dict_bytes`.
|
||||
- `CompressionOptions::max_dict_buffer_bytes`: Limit on data buffering from which training samples are gathered. By default we buffer up to the target file size per ongoing background job. If this amount of memory is concerning, this option can constrain the buffering with the downside that training samples will cover a smaller portion of the SST file. Work is ongoing to charge this memory usage to block cache so it will not need to be accounted for separately.
|
||||
- `BlockBasedTableOptions::cache_index_and_filter_blocks`: Controls whether metadata blocks including dictionary are accessed through block cache or held in table reader memory (yes, its name is outdated).
|
||||
- `BlockBasedTableOptions::metadata_cache_options`: Controls what metadata blocks are pinned in block cache. Pinning avoids LRU contention at the risk of cold blocks holding memory.
|
||||
- `ColumnFamilyOptions::sample_for_compression`: Controls frequency of measuring extra compressions on data blocks using various libraries with default settings (i.e., without preset dictionary).
|
||||
|
||||
Compile-time setting:
|
||||
|
||||
- (**zstd only**) `EXTRA_CXXFLAGS=-DZSTD_STATIC_LINKING_ONLY`: Hold digested dictionaries in block cache to save repetitive deserialization overhead. This saves a lot of CPU for read-heavy workloads. This compiler flag is necessary because one of the digested dictionary APIs we use is marked as experimental. We still use it in production, however.
|
||||
|
||||
Function:
|
||||
|
||||
- `DB::GetPropertiesOfAllTables()`: The properties `kSlowCompressionEstimatedDataSize` and `kFastCompressionEstimatedDataSize` estimate what the data block size (`kDataSize`) would have been if the corresponding compression library had been used. These properties are only present when `ColumnFamilyOptions::sample_for_compression` causes one or more samples to be measured, and they become more accurate with higher sampling frequency.
|
||||
|
||||
Tool:
|
||||
|
||||
- `sst_dump --command=recompress`: Offline analysis tool that reports what the SST file size would have been using the specified compression library and options.
|
||||
|
After Width: | Height: | Size: 242 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 349 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 175 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 178 KiB |
|
After Width: | Height: | Size: 784 KiB |
|
After Width: | Height: | Size: 908 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 57 KiB |
@@ -16,6 +16,7 @@
|
||||
#include "memory/arena.h"
|
||||
#include "options/db_options.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/system_clock.h"
|
||||
#include "rocksdb/utilities/object_registry.h"
|
||||
@@ -582,11 +583,18 @@ Status Env::NewLogger(const std::string& fname,
|
||||
}
|
||||
|
||||
Status Env::LoadEnv(const std::string& value, Env** result) {
|
||||
return CreateFromString(ConfigOptions(), value, result);
|
||||
}
|
||||
|
||||
Status Env::CreateFromString(const ConfigOptions& config_options,
|
||||
const std::string& value, Env** result) {
|
||||
Env* env = *result;
|
||||
Status s;
|
||||
#ifndef ROCKSDB_LITE
|
||||
(void)config_options;
|
||||
s = ObjectRegistry::NewInstance()->NewStaticObject<Env>(value, &env);
|
||||
#else
|
||||
(void)config_options;
|
||||
s = Status::NotSupported("Cannot load environment in LITE mode", value);
|
||||
#endif
|
||||
if (s.ok()) {
|
||||
@@ -597,18 +605,29 @@ Status Env::LoadEnv(const std::string& value, Env** result) {
|
||||
|
||||
Status Env::LoadEnv(const std::string& value, Env** result,
|
||||
std::shared_ptr<Env>* guard) {
|
||||
return CreateFromString(ConfigOptions(), value, result, guard);
|
||||
}
|
||||
|
||||
Status Env::CreateFromString(const ConfigOptions& config_options,
|
||||
const std::string& value, Env** result,
|
||||
std::shared_ptr<Env>* guard) {
|
||||
assert(result);
|
||||
if (value.empty()) {
|
||||
*result = Env::Default();
|
||||
return Status::OK();
|
||||
}
|
||||
Status s;
|
||||
#ifndef ROCKSDB_LITE
|
||||
Env* env = nullptr;
|
||||
std::unique_ptr<Env> uniq_guard;
|
||||
std::string err_msg;
|
||||
assert(guard != nullptr);
|
||||
(void)config_options;
|
||||
env = ObjectRegistry::NewInstance()->NewObject<Env>(value, &uniq_guard,
|
||||
&err_msg);
|
||||
if (!env) {
|
||||
s = Status::NotFound(std::string("Cannot load ") + Env::Type() + ": " +
|
||||
value);
|
||||
s = Status::NotSupported(std::string("Cannot load ") + Env::Type() + ": " +
|
||||
value);
|
||||
env = Env::Default();
|
||||
}
|
||||
if (s.ok() && uniq_guard) {
|
||||
@@ -618,6 +637,7 @@ Status Env::LoadEnv(const std::string& value, Env** result,
|
||||
*result = env;
|
||||
}
|
||||
#else
|
||||
(void)config_options;
|
||||
(void)result;
|
||||
(void)guard;
|
||||
s = Status::NotSupported("Cannot load environment in LITE mode", value);
|
||||
@@ -625,6 +645,30 @@ Status Env::LoadEnv(const std::string& value, Env** result,
|
||||
return s;
|
||||
}
|
||||
|
||||
Status Env::CreateFromUri(const ConfigOptions& config_options,
|
||||
const std::string& env_uri, const std::string& fs_uri,
|
||||
Env** result, std::shared_ptr<Env>* guard) {
|
||||
*result = config_options.env;
|
||||
if (env_uri.empty() && fs_uri.empty()) {
|
||||
// Neither specified. Use the default
|
||||
guard->reset();
|
||||
return Status::OK();
|
||||
} else if (!env_uri.empty() && !fs_uri.empty()) {
|
||||
// Both specified. Cannot choose. Return Invalid
|
||||
return Status::InvalidArgument("cannot specify both fs_uri and env_uri");
|
||||
} else if (fs_uri.empty()) { // Only have an ENV URI. Create an Env from it
|
||||
return CreateFromString(config_options, env_uri, result, guard);
|
||||
} else {
|
||||
std::shared_ptr<FileSystem> fs;
|
||||
Status s = FileSystem::CreateFromString(config_options, fs_uri, &fs);
|
||||
if (s.ok()) {
|
||||
guard->reset(new CompositeEnvWrapper(*result, fs));
|
||||
*result = guard->get();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
std::string Env::PriorityToString(Env::Priority priority) {
|
||||
switch (priority) {
|
||||
case Env::Priority::BOTTOM:
|
||||
|
||||
@@ -75,19 +75,30 @@ namespace {
|
||||
// The purpose of returning an empty vector (instead of nullptr) is that gtest
|
||||
// ValuesIn() will skip running tests when given an empty collection.
|
||||
std::vector<Env*> GetCustomEnvs() {
|
||||
static Env* custom_env;
|
||||
static bool init = false;
|
||||
static std::vector<Env*> res;
|
||||
if (!init) {
|
||||
init = true;
|
||||
const char* uri = getenv("TEST_ENV_URI");
|
||||
if (uri != nullptr) {
|
||||
Env::LoadEnv(uri, &custom_env);
|
||||
static std::shared_ptr<Env> env_guard;
|
||||
static Env* custom_env;
|
||||
Status s =
|
||||
Env::CreateFromUri(ConfigOptions(), uri, "", &custom_env, &env_guard);
|
||||
if (s.ok()) {
|
||||
res.emplace_back(custom_env);
|
||||
}
|
||||
}
|
||||
uri = getenv("TEST_FS_URI");
|
||||
if (uri != nullptr) {
|
||||
static std::shared_ptr<Env> fs_env_guard;
|
||||
static Env* fs_env;
|
||||
Status s =
|
||||
Env::CreateFromUri(ConfigOptions(), "", uri, &fs_env, &fs_env_guard);
|
||||
if (s.ok()) {
|
||||
res.emplace_back(fs_env);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Env*> res;
|
||||
if (custom_env != nullptr) {
|
||||
res.emplace_back(custom_env);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
//
|
||||
#include "env/composite_env_wrapper.h"
|
||||
#include "rocksdb/file_system.h"
|
||||
|
||||
#include "env/composite_env_wrapper.h"
|
||||
#include "options/db_options.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/utilities/object_registry.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -16,10 +18,18 @@ FileSystem::~FileSystem() {}
|
||||
|
||||
Status FileSystem::Load(const std::string& value,
|
||||
std::shared_ptr<FileSystem>* result) {
|
||||
return CreateFromString(ConfigOptions(), value, result);
|
||||
}
|
||||
|
||||
Status FileSystem::CreateFromString(const ConfigOptions& config_options,
|
||||
const std::string& value,
|
||||
std::shared_ptr<FileSystem>* result) {
|
||||
Status s;
|
||||
#ifndef ROCKSDB_LITE
|
||||
(void)config_options;
|
||||
s = ObjectRegistry::NewInstance()->NewSharedObject<FileSystem>(value, result);
|
||||
#else
|
||||
(void)config_options;
|
||||
(void)result;
|
||||
s = Status::NotSupported("Cannot load FileSystem in LITE mode", value);
|
||||
#endif
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#endif
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
@@ -32,6 +31,7 @@
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <algorithm>
|
||||
// Get nano time includes
|
||||
#if defined(OS_LINUX) || defined(OS_FREEBSD)
|
||||
@@ -81,9 +81,7 @@ inline mode_t GetDBFileMode(bool allow_non_owner_access) {
|
||||
return allow_non_owner_access ? 0644 : 0600;
|
||||
}
|
||||
|
||||
static uint64_t gettid() {
|
||||
return Env::Default()->GetThreadID();
|
||||
}
|
||||
static uint64_t gettid() { return Env::Default()->GetThreadID(); }
|
||||
|
||||
// list of pathnames that are locked
|
||||
// Only used for error message.
|
||||
@@ -267,8 +265,7 @@ class PosixFileSystem : public FileSystem {
|
||||
}
|
||||
|
||||
virtual IOStatus OpenWritableFile(const std::string& fname,
|
||||
const FileOptions& options,
|
||||
bool reopen,
|
||||
const FileOptions& options, bool reopen,
|
||||
std::unique_ptr<FSWritableFile>* result,
|
||||
IODebugContext* /*dbg*/) {
|
||||
result->reset();
|
||||
@@ -551,8 +548,8 @@ class PosixFileSystem : public FileSystem {
|
||||
}
|
||||
|
||||
IOStatus NewLogger(const std::string& fname, const IOOptions& /*opts*/,
|
||||
std::shared_ptr<Logger>* result,
|
||||
IODebugContext* /*dbg*/) override {
|
||||
std::shared_ptr<Logger>* result,
|
||||
IODebugContext* /*dbg*/) override {
|
||||
FILE* f = nullptr;
|
||||
int fd;
|
||||
{
|
||||
@@ -909,7 +906,17 @@ class PosixFileSystem : public FileSystem {
|
||||
return IOError("While doing statvfs", fname, errno);
|
||||
}
|
||||
|
||||
*free_space = ((uint64_t)sbuf.f_bsize * sbuf.f_bfree);
|
||||
// sbuf.bfree is total free space available to root
|
||||
// sbuf.bavail is total free space available to unprivileged user
|
||||
// sbuf.bavail <= sbuf.bfree ... pick correct based upon effective user id
|
||||
if (geteuid()) {
|
||||
// non-zero user is unprivileged, or -1 if error. take more conservative
|
||||
// size
|
||||
*free_space = ((uint64_t)sbuf.f_bsize * sbuf.f_bavail);
|
||||
} else {
|
||||
// root user can access all disk space
|
||||
*free_space = ((uint64_t)sbuf.f_bsize * sbuf.f_bfree);
|
||||
}
|
||||
return IOStatus::OK();
|
||||
}
|
||||
|
||||
@@ -938,7 +945,7 @@ class PosixFileSystem : public FileSystem {
|
||||
}
|
||||
|
||||
FileOptions OptimizeForLogWrite(const FileOptions& file_options,
|
||||
const DBOptions& db_options) const override {
|
||||
const DBOptions& db_options) const override {
|
||||
FileOptions optimized = file_options;
|
||||
optimized.use_mmap_writes = false;
|
||||
optimized.use_direct_writes = false;
|
||||
|
||||