mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 02:52:35 -05:00
Compare commits
197
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd35a845ca | ||
|
|
befd813e95 | ||
|
|
5c04610b9c | ||
|
|
8f0de0554f | ||
|
|
d0a0c91017 | ||
|
|
354588fea4 | ||
|
|
99ad5553eb | ||
|
|
1aedd57a62 | ||
|
|
b8a1b8f361 | ||
|
|
ecd3693770 | ||
|
|
27ec9bc869 | ||
|
|
abe8a29a37 | ||
|
|
1e0b641048 | ||
|
|
7c22a2c549 | ||
|
|
03384bc9d2 | ||
|
|
c2c7d5e916 | ||
|
|
2760c2aef8 | ||
|
|
69c986825e | ||
|
|
6bab3a34e9 | ||
|
|
cc1c3ee54e | ||
|
|
d65d7d657d | ||
|
|
01e460d538 | ||
|
|
f0fca2b1d5 | ||
|
|
e85d8a6517 | ||
|
|
09a9ec3ac0 | ||
|
|
47b424f4bd | ||
|
|
314352761f | ||
|
|
4985cea141 | ||
|
|
596e9008e4 | ||
|
|
09df74c540 | ||
|
|
95f6add746 | ||
|
|
2e5de5a2c3 | ||
|
|
905dd17b35 | ||
|
|
1b99947e99 | ||
|
|
a89740fbc6 | ||
|
|
a345b4d60d | ||
|
|
a376c22066 | ||
|
|
0c6e4674a6 | ||
|
|
b0e20194ea | ||
|
|
c377c2ba15 | ||
|
|
531a5f88a1 | ||
|
|
10196d7edc | ||
|
|
90e245697f | ||
|
|
c871142988 | ||
|
|
4c41e51c07 | ||
|
|
00803d619c | ||
|
|
83031e7343 | ||
|
|
296b47db25 | ||
|
|
b1f62be10e | ||
|
|
d89483098f | ||
|
|
c861fb390d | ||
|
|
fd00f39f97 | ||
|
|
bb75092574 | ||
|
|
8972dd1ffa | ||
|
|
139778dfb3 | ||
|
|
728e5f5750 | ||
|
|
48cd7a3aae | ||
|
|
0be89e87fd | ||
|
|
d52b520d51 | ||
|
|
a4e82a3cca | ||
|
|
35af0433cf | ||
|
|
6db3af1124 | ||
|
|
879357fdb0 | ||
|
|
09528f9fa1 | ||
|
|
c4d0e66d65 | ||
|
|
2d8518f5ea | ||
|
|
ffd3f493e3 | ||
|
|
b2c48a570f | ||
|
|
88c8f7a090 | ||
|
|
96205baa63 | ||
|
|
bd7ddf58cb | ||
|
|
d0d2ab0b1a | ||
|
|
dd3fbbbf95 | ||
|
|
0fccc6225e | ||
|
|
689b13e639 | ||
|
|
5025c7ec09 | ||
|
|
e295344ae3 | ||
|
|
b42d4a8ad4 | ||
|
|
17002365c1 | ||
|
|
c43a37a922 | ||
|
|
a3a943bf63 | ||
|
|
1ba2b8a568 | ||
|
|
8015fc9871 | ||
|
|
a781b103da | ||
|
|
493a4e28d9 | ||
|
|
335c5a6be5 | ||
|
|
e6534900bd | ||
|
|
9418403c4b | ||
|
|
b2c302597d | ||
|
|
f03606cd5c | ||
|
|
2db4e48211 | ||
|
|
ec11c23caa | ||
|
|
04191e1c5d | ||
|
|
a037bb35e9 | ||
|
|
24b7ebee80 | ||
|
|
1b0a1abafa | ||
|
|
c1ae1f143e | ||
|
|
25ae380784 | ||
|
|
1be3867689 | ||
|
|
70e80c91b6 | ||
|
|
524b10bd6e | ||
|
|
ae7a795686 | ||
|
|
5841bbe36c | ||
|
|
7d7f14480e | ||
|
|
0a5d23944d | ||
|
|
ce6de862c1 | ||
|
|
63748c2204 | ||
|
|
a34dafe5ff | ||
|
|
c5f52714fb | ||
|
|
7f27767efa | ||
|
|
06bb45a65a | ||
|
|
303cb23a0f | ||
|
|
af80a78ba4 | ||
|
|
711881bc25 | ||
|
|
c20a7cd6c7 | ||
|
|
9cc94bfbd3 | ||
|
|
33970a392c | ||
|
|
45c65d6dcf | ||
|
|
f06b761185 | ||
|
|
469164dc3c | ||
|
|
5732c039a4 | ||
|
|
0ae90f8e28 | ||
|
|
5a165416ac | ||
|
|
da6b90ab48 | ||
|
|
41e554da2b | ||
|
|
8dc6d8c748 | ||
|
|
9f7c02dad5 | ||
|
|
e1aa8c160f | ||
|
|
4a6bc47b2e | ||
|
|
2a12b80769 | ||
|
|
08144bc2f5 | ||
|
|
0d800dadea | ||
|
|
d6052d381e | ||
|
|
7457c7cd00 | ||
|
|
3bfd3ed2f3 | ||
|
|
c8b0842bcd | ||
|
|
d9be6556aa | ||
|
|
c706324208 | ||
|
|
dd0447ae2c | ||
|
|
7ee41a5d25 | ||
|
|
576cff11da | ||
|
|
063a68b9cd | ||
|
|
c810947184 | ||
|
|
e7a60d01b2 | ||
|
|
08ec5e7321 | ||
|
|
27d57a035e | ||
|
|
c603f2f898 | ||
|
|
326670d265 | ||
|
|
8d9088464b | ||
|
|
1a343bc393 | ||
|
|
0304352882 | ||
|
|
85d4f2c8b3 | ||
|
|
3dff28cf9b | ||
|
|
b8f40f7f7b | ||
|
|
b708b166dc | ||
|
|
01c2ec3fcb | ||
|
|
119dda2195 | ||
|
|
589ea6bec2 | ||
|
|
82b3888433 | ||
|
|
64517d184a | ||
|
|
7a3444bf1f | ||
|
|
cc34da75b5 | ||
|
|
6fad38ebe8 | ||
|
|
670567db09 | ||
|
|
7381dad1b1 | ||
|
|
4b18c46d10 | ||
|
|
847ca9f964 | ||
|
|
0028e3398b | ||
|
|
430842f948 | ||
|
|
8643d63bb4 | ||
|
|
67d72fb5dc | ||
|
|
d2e9eab1ea | ||
|
|
02974c9437 | ||
|
|
ef392fb04e | ||
|
|
0ff0b625a1 | ||
|
|
ce391ff84b | ||
|
|
cb25bc1128 | ||
|
|
4126bdc0e1 | ||
|
|
72d1e258cd | ||
|
|
a9046f3c45 | ||
|
|
a46f080cce | ||
|
|
f19612970d | ||
|
|
1f11d07f24 | ||
|
|
9fdc9fbeea | ||
|
|
a8b3b9a20c | ||
|
|
c370d8aa12 | ||
|
|
cef4a6c49f | ||
|
|
2772eb7735 | ||
|
|
b085ee13e0 | ||
|
|
cd79a00903 | ||
|
|
e017af15c1 | ||
|
|
75c6ffb9de | ||
|
|
f91fd0c944 | ||
|
|
46cf5fbfdd | ||
|
|
daca92c17a | ||
|
|
59d91796d2 | ||
|
|
6790a983eb |
No files matched your search
+58
-1
@@ -178,7 +178,7 @@ jobs:
|
||||
steps:
|
||||
- pre-steps
|
||||
- install-gflags
|
||||
- run: ASSERT_STATUS_CHECKED=1 TEST_UINT128_COMPAT=1 ROCKSDB_MODIFY_NPHASH=1 LIB_MODE=shared OPT="-DROCKSDB_NAMESPACE=alternative_rocksdb_ns" make V=1 -j32 all check_some | .circleci/cat_ignore_eagain
|
||||
- run: ASSERT_STATUS_CHECKED=1 TEST_UINT128_COMPAT=1 ROCKSDB_MODIFY_NPHASH=1 LIB_MODE=shared OPT="-DROCKSDB_NAMESPACE=alternative_rocksdb_ns" make V=1 -j32 check | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-linux-release:
|
||||
@@ -572,6 +572,58 @@ jobs:
|
||||
gtest-parallel $(</tmp/test_list) --output_dir=/tmp | cat # pipe to cat to continuously output status on circleci UI. Otherwise, no status will be printed while the job is running.
|
||||
- post-steps
|
||||
|
||||
build-linux-arm-test-full:
|
||||
machine:
|
||||
image: ubuntu-2004:202101-01
|
||||
resource_class: arm.large
|
||||
steps:
|
||||
- pre-steps
|
||||
- install-gflags
|
||||
- run: make V=1 J=4 -j4 check | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-linux-arm:
|
||||
machine:
|
||||
image: ubuntu-2004:202101-01
|
||||
resource_class: arm.large
|
||||
steps:
|
||||
- pre-steps
|
||||
- install-gflags
|
||||
- run: ROCKSDBTESTS_PLATFORM_DEPENDENT=only make V=1 J=4 -j4 all_but_some_tests check_some | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-linux-arm-cmake-no_test_run:
|
||||
machine:
|
||||
image: ubuntu-2004:202101-01
|
||||
resource_class: arm.large
|
||||
environment:
|
||||
JAVA_HOME: /usr/lib/jvm/java-8-openjdk-arm64
|
||||
steps:
|
||||
- pre-steps
|
||||
- run:
|
||||
name: "Set Java Environment"
|
||||
command: |
|
||||
echo "JAVA_HOME=${JAVA_HOME}"
|
||||
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> $BASH_ENV
|
||||
which java && java -version
|
||||
which javac && javac -version
|
||||
- run:
|
||||
name: "Build with cmake"
|
||||
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 ..
|
||||
make -j4
|
||||
- run:
|
||||
name: "Build Java with cmake"
|
||||
command: |
|
||||
rm -rf build
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DJNI=1 -DCMAKE_BUILD_TYPE=Release -DWITH_GFLAGS=0 ..
|
||||
make -j4 rocksdb rocksdbjni
|
||||
- post-steps
|
||||
|
||||
build-format-compatible:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
@@ -682,6 +734,7 @@ workflows:
|
||||
- build-linux-gcc-8-no_test_run
|
||||
- build-linux-gcc-9-no_test_run
|
||||
- build-linux-gcc-10-cxx20-no_test_run
|
||||
- build-linux-arm-cmake-no_test_run
|
||||
build-macos:
|
||||
jobs:
|
||||
- build-macos
|
||||
@@ -691,6 +744,9 @@ workflows:
|
||||
build-cmake-mingw:
|
||||
jobs:
|
||||
- build-cmake-mingw
|
||||
build-linux-arm:
|
||||
jobs:
|
||||
- build-linux-arm
|
||||
nightly:
|
||||
triggers:
|
||||
- schedule:
|
||||
@@ -701,3 +757,4 @@ workflows:
|
||||
- master
|
||||
jobs:
|
||||
- build-format-compatible
|
||||
- build-linux-arm-test-full
|
||||
+17
-9
@@ -61,6 +61,10 @@ matrix:
|
||||
env: JOB_NAME=make-gcc4.8
|
||||
- os: linux
|
||||
compiler: clang
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: linux
|
||||
arch: arm64
|
||||
env: TEST_GROUP=platform_dependent
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
@@ -93,6 +97,10 @@ matrix:
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: TEST_GROUP=4
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: JOB_NAME=cmake
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/ AND commit_message !~ /java/
|
||||
os : linux
|
||||
arch: arm64
|
||||
@@ -196,30 +204,30 @@ script:
|
||||
- if [ `command -v ccache` ]; then ccache -C; fi
|
||||
- case $TEST_GROUP in
|
||||
platform_dependent)
|
||||
OPT=-DTRAVIS V=1 ROCKSDBTESTS_PLATFORM_DEPENDENT=only make -j4 all_but_some_tests check_some
|
||||
OPT=-DTRAVIS LIB_MODE=shared V=1 ROCKSDBTESTS_PLATFORM_DEPENDENT=only make -j4 all_but_some_tests check_some
|
||||
;;
|
||||
1)
|
||||
OPT=-DTRAVIS V=1 ROCKSDBTESTS_PLATFORM_DEPENDENT=exclude ROCKSDBTESTS_END=backupable_db_test make -j4 check_some
|
||||
OPT=-DTRAVIS LIB_MODE=shared V=1 ROCKSDBTESTS_PLATFORM_DEPENDENT=exclude ROCKSDBTESTS_END=backupable_db_test make -j4 check_some
|
||||
;;
|
||||
2)
|
||||
OPT="-DTRAVIS -DROCKSDB_NAMESPACE=alternative_rocksdb_ns" V=1 make -j4 tools && OPT="-DTRAVIS -DROCKSDB_NAMESPACE=alternative_rocksdb_ns" V=1 ROCKSDBTESTS_PLATFORM_DEPENDENT=exclude ROCKSDBTESTS_START=backupable_db_test ROCKSDBTESTS_END=db_universal_compaction_test make -j4 check_some
|
||||
OPT="-DTRAVIS -DROCKSDB_NAMESPACE=alternative_rocksdb_ns" LIB_MODE=shared V=1 make -j4 tools && OPT="-DTRAVIS -DROCKSDB_NAMESPACE=alternative_rocksdb_ns" LIB_MODE=shared V=1 ROCKSDBTESTS_PLATFORM_DEPENDENT=exclude ROCKSDBTESTS_START=backupable_db_test ROCKSDBTESTS_END=db_universal_compaction_test make -j4 check_some
|
||||
;;
|
||||
3)
|
||||
OPT=-DTRAVIS LIB_MODE=shared V=1 ROCKSDBTESTS_PLATFORM_DEPENDENT=exclude ROCKSDBTESTS_START=db_universal_compaction_test ROCKSDBTESTS_END=table_properties_collector_test make -j4 check_some
|
||||
;;
|
||||
4)
|
||||
OPT=-DTRAVIS V=1 ROCKSDBTESTS_PLATFORM_DEPENDENT=exclude ROCKSDBTESTS_START=table_properties_collector_test make -j4 check_some
|
||||
OPT=-DTRAVIS LIB_MODE=shared V=1 ROCKSDBTESTS_PLATFORM_DEPENDENT=exclude ROCKSDBTESTS_START=table_properties_collector_test make -j4 check_some
|
||||
;;
|
||||
esac
|
||||
- case $JOB_NAME in
|
||||
java_test)
|
||||
OPT=-DTRAVIS V=1 make rocksdbjava jtest
|
||||
OPT=-DTRAVIS LIB_MODE=shared V=1 make rocksdbjava jtest
|
||||
;;
|
||||
lite_build)
|
||||
OPT='-DTRAVIS -DROCKSDB_LITE' V=1 make -j4 all
|
||||
OPT='-DTRAVIS -DROCKSDB_LITE' LIB_MODE=shared V=1 make -j4 all
|
||||
;;
|
||||
examples)
|
||||
OPT=-DTRAVIS V=1 make -j4 static_lib && cd examples && make -j4
|
||||
OPT=-DTRAVIS LIB_MODE=shared V=1 make -j4 static_lib && cd examples && make -j4
|
||||
;;
|
||||
cmake-mingw)
|
||||
sudo update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix;
|
||||
@@ -235,10 +243,10 @@ script:
|
||||
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 .. && make -j4 && cd .. && rm -rf build && mkdir build && cd build && cmake -DJNI=1 .. -DCMAKE_BUILD_TYPE=Release $OPT && make -j4 rocksdb rocksdbjni
|
||||
;;
|
||||
make-gcc4.8)
|
||||
OPT=-DTRAVIS V=1 SKIP_LINK=1 make -j4 all && [ "Linking broken because libgflags compiled with newer ABI" ]
|
||||
OPT=-DTRAVIS LIB_MODE=shared V=1 SKIP_LINK=1 make -j4 all && [ "Linking broken because libgflags compiled with newer ABI" ]
|
||||
;;
|
||||
status_checked)
|
||||
OPT=-DTRAVIS V=1 ASSERT_STATUS_CHECKED=1 make -j4 check_some
|
||||
OPT=-DTRAVIS LIB_MODE=shared V=1 ASSERT_STATUS_CHECKED=1 make -j4 check_some
|
||||
;;
|
||||
esac
|
||||
notifications:
|
||||
|
||||
+65
-20
@@ -88,10 +88,9 @@ if( NOT DEFINED CMAKE_CXX_STANDARD )
|
||||
endif()
|
||||
|
||||
include(CMakeDependentOption)
|
||||
CMAKE_DEPENDENT_OPTION(WITH_GFLAGS "build with GFlags" ON
|
||||
"NOT MSVC;NOT MINGW" OFF)
|
||||
|
||||
if(MSVC)
|
||||
option(WITH_GFLAGS "build with GFlags" OFF)
|
||||
option(WITH_XPRESS "build with windows built in compression" OFF)
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/thirdparty.inc)
|
||||
else()
|
||||
@@ -107,6 +106,11 @@ else()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(MINGW)
|
||||
option(WITH_GFLAGS "build with GFlags" OFF)
|
||||
else()
|
||||
option(WITH_GFLAGS "build with GFlags" ON)
|
||||
endif()
|
||||
set(GFLAGS_LIB)
|
||||
if(WITH_GFLAGS)
|
||||
# Config with namespace available since gflags 2.2.2
|
||||
@@ -208,6 +212,9 @@ if(MSVC)
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W -Wextra -Wall")
|
||||
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")
|
||||
endif()
|
||||
if(MINGW)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-format -fno-asynchronous-unwind-tables")
|
||||
add_definitions(-D_POSIX_C_SOURCE=1)
|
||||
@@ -289,6 +296,7 @@ else()
|
||||
endif()
|
||||
|
||||
include(CheckCXXSourceCompiles)
|
||||
set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
|
||||
if(NOT MSVC)
|
||||
set(CMAKE_REQUIRED_FLAGS "-msse4.2 -mpclmul")
|
||||
endif()
|
||||
@@ -305,7 +313,6 @@ int main() {
|
||||
auto d = _mm_cvtsi128_si64(c);
|
||||
}
|
||||
" HAVE_SSE42)
|
||||
unset(CMAKE_REQUIRED_FLAGS)
|
||||
if(HAVE_SSE42)
|
||||
add_definitions(-DHAVE_SSE42)
|
||||
add_definitions(-DHAVE_PCLMUL)
|
||||
@@ -313,18 +320,50 @@ elseif(FORCE_SSE42)
|
||||
message(FATAL_ERROR "FORCE_SSE42=ON but unable to compile with SSE4.2 enabled")
|
||||
endif()
|
||||
|
||||
# Check if -latomic is required or not
|
||||
if (NOT MSVC)
|
||||
set(CMAKE_REQUIRED_FLAGS "--std=c++11")
|
||||
CHECK_CXX_SOURCE_COMPILES("
|
||||
#include <atomic>
|
||||
std::atomic<uint64_t> x(0);
|
||||
int main() {
|
||||
uint64_t i = x.load(std::memory_order_relaxed);
|
||||
bool b = x.is_lock_free();
|
||||
return 0;
|
||||
}
|
||||
" BUILTIN_ATOMIC)
|
||||
if (NOT BUILTIN_ATOMIC)
|
||||
#TODO: Check if -latomic exists
|
||||
list(APPEND THIRDPARTY_LIBS atomic)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Reset the required flags
|
||||
set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS})
|
||||
|
||||
CHECK_CXX_SOURCE_COMPILES("
|
||||
#if defined(_MSC_VER) && !defined(__thread)
|
||||
#define __thread __declspec(thread)
|
||||
#endif
|
||||
int main() {
|
||||
static __thread int tls;
|
||||
(void)tls;
|
||||
}
|
||||
" HAVE_THREAD_LOCAL)
|
||||
if(HAVE_THREAD_LOCAL)
|
||||
add_definitions(-DROCKSDB_SUPPORT_THREAD_LOCAL)
|
||||
endif()
|
||||
|
||||
option(WITH_IOSTATS_CONTEXT "Enable IO stats context" ON)
|
||||
if (NOT WITH_IOSTATS_CONTEXT)
|
||||
add_definitions(-DNIOSTATS_CONTEXT)
|
||||
endif()
|
||||
|
||||
option(WITH_PERF_CONTEXT "Enable perf context" ON)
|
||||
if (NOT WITH_PERF_CONTEXT)
|
||||
add_definitions(-DNPERF_CONTEXT)
|
||||
endif()
|
||||
|
||||
option(FAIL_ON_WARNINGS "Treat compile warnings as errors" ON)
|
||||
if(FAIL_ON_WARNINGS)
|
||||
if(MSVC)
|
||||
@@ -576,7 +615,6 @@ set(SOURCES
|
||||
db/builder.cc
|
||||
db/c.cc
|
||||
db/column_family.cc
|
||||
db/compacted_db_impl.cc
|
||||
db/compaction/compaction.cc
|
||||
db/compaction/compaction_iterator.cc
|
||||
db/compaction/compaction_picker.cc
|
||||
@@ -587,6 +625,7 @@ set(SOURCES
|
||||
db/compaction/sst_partitioner.cc
|
||||
db/convenience.cc
|
||||
db/db_filesnapshot.cc
|
||||
db/db_impl/compacted_db_impl.cc
|
||||
db/db_impl/db_impl.cc
|
||||
db/db_impl/db_impl_write.cc
|
||||
db/db_impl/db_impl_compaction_flush.cc
|
||||
@@ -644,11 +683,13 @@ set(SOURCES
|
||||
env/env_hdfs.cc
|
||||
env/file_system.cc
|
||||
env/file_system_tracer.cc
|
||||
env/fs_remap.cc
|
||||
env/mock_env.cc
|
||||
file/delete_scheduler.cc
|
||||
file/file_prefetch_buffer.cc
|
||||
file/file_util.cc
|
||||
file/filename.cc
|
||||
file/line_file_reader.cc
|
||||
file/random_access_file_reader.cc
|
||||
file/read_write_util.cc
|
||||
file/readahead_raf.cc
|
||||
@@ -762,6 +803,7 @@ set(SOURCES
|
||||
util/murmurhash.cc
|
||||
util/random.cc
|
||||
util/rate_limiter.cc
|
||||
util/ribbon_config.cc
|
||||
util/slice.cc
|
||||
util/file_checksum_helper.cc
|
||||
util/status.cc
|
||||
@@ -1061,6 +1103,8 @@ if(WITH_TESTS)
|
||||
db/blob/blob_file_garbage_test.cc
|
||||
db/blob/blob_file_reader_test.cc
|
||||
db/blob/db_blob_basic_test.cc
|
||||
db/blob/db_blob_compaction_test.cc
|
||||
db/blob/db_blob_corruption_test.cc
|
||||
db/blob/db_blob_index_test.cc
|
||||
db/column_family_test.cc
|
||||
db/compact_files_test.cc
|
||||
@@ -1091,7 +1135,7 @@ if(WITH_TESTS)
|
||||
db/db_options_test.cc
|
||||
db/db_properties_test.cc
|
||||
db/db_range_del_test.cc
|
||||
db/db_impl/db_secondary_test.cc
|
||||
db/db_secondary_test.cc
|
||||
db/db_sst_test.cc
|
||||
db/db_statistics_test.cc
|
||||
db/db_table_properties_test.cc
|
||||
@@ -1297,40 +1341,41 @@ if(WITH_TESTS)
|
||||
endif()
|
||||
|
||||
if(WITH_BENCHMARK_TOOLS)
|
||||
add_executable(db_bench
|
||||
add_executable(db_bench${ARTIFACT_SUFFIX}
|
||||
tools/db_bench.cc
|
||||
tools/db_bench_tool.cc)
|
||||
target_link_libraries(db_bench
|
||||
target_link_libraries(db_bench${ARTIFACT_SUFFIX}
|
||||
${ROCKSDB_LIB} ${THIRDPARTY_LIBS})
|
||||
|
||||
add_executable(cache_bench
|
||||
cache/cache_bench.cc)
|
||||
target_link_libraries(cache_bench
|
||||
add_executable(cache_bench${ARTIFACT_SUFFIX}
|
||||
cache/cache_bench.cc
|
||||
cache/cache_bench_tool.cc)
|
||||
target_link_libraries(cache_bench${ARTIFACT_SUFFIX}
|
||||
${ROCKSDB_LIB} ${GFLAGS_LIB})
|
||||
|
||||
add_executable(memtablerep_bench
|
||||
add_executable(memtablerep_bench${ARTIFACT_SUFFIX}
|
||||
memtable/memtablerep_bench.cc)
|
||||
target_link_libraries(memtablerep_bench
|
||||
target_link_libraries(memtablerep_bench${ARTIFACT_SUFFIX}
|
||||
${ROCKSDB_LIB} ${GFLAGS_LIB})
|
||||
|
||||
add_executable(range_del_aggregator_bench
|
||||
add_executable(range_del_aggregator_bench${ARTIFACT_SUFFIX}
|
||||
db/range_del_aggregator_bench.cc)
|
||||
target_link_libraries(range_del_aggregator_bench
|
||||
target_link_libraries(range_del_aggregator_bench${ARTIFACT_SUFFIX}
|
||||
${ROCKSDB_LIB} ${GFLAGS_LIB})
|
||||
|
||||
add_executable(table_reader_bench
|
||||
add_executable(table_reader_bench${ARTIFACT_SUFFIX}
|
||||
table/table_reader_bench.cc)
|
||||
target_link_libraries(table_reader_bench
|
||||
target_link_libraries(table_reader_bench${ARTIFACT_SUFFIX}
|
||||
${ROCKSDB_LIB} testharness ${GFLAGS_LIB})
|
||||
|
||||
add_executable(filter_bench
|
||||
add_executable(filter_bench${ARTIFACT_SUFFIX}
|
||||
util/filter_bench.cc)
|
||||
target_link_libraries(filter_bench
|
||||
target_link_libraries(filter_bench${ARTIFACT_SUFFIX}
|
||||
${ROCKSDB_LIB} ${GFLAGS_LIB})
|
||||
|
||||
add_executable(hash_table_bench
|
||||
add_executable(hash_table_bench${ARTIFACT_SUFFIX}
|
||||
utilities/persistent_cache/hash_table_bench.cc)
|
||||
target_link_libraries(hash_table_bench
|
||||
target_link_libraries(hash_table_bench${ARTIFACT_SUFFIX}
|
||||
${ROCKSDB_LIB} ${GFLAGS_LIB})
|
||||
endif()
|
||||
|
||||
|
||||
+74
@@ -1,5 +1,77 @@
|
||||
# Rocksdb Change Log
|
||||
## Unreleased
|
||||
### Bug Fixes
|
||||
* Fixed a bug in handling file rename error in distributed/network file systems when the server succeeds but client returns error. The bug can cause CURRENT file to point to non-existing MANIFEST file, thus DB cannot be opened.
|
||||
* Fixed a bug where ingested files were written with incorrect boundary key metadata. In rare cases this could have led to a level's files being wrongly ordered and queries for the boundary keys returning wrong results.
|
||||
* Fixed a data race between insertion into memtables and the retrieval of the DB properties `rocksdb.cur-size-active-mem-table`, `rocksdb.cur-size-all-mem-tables`, and `rocksdb.size-all-mem-tables`.
|
||||
* Fixed the false-positive alert when recovering from the WAL file. Avoid reporting "SST file is ahead of WAL" on a newly created empty column family, if the previous WAL file is corrupted.
|
||||
|
||||
### Behavior Changes
|
||||
* Due to the fix of false-postive alert of "SST file is ahead of WAL", all the CFs with no SST file (CF empty) will bypass the consistency check. We fixed a false-positive, but introduced a very rare true-negative which will be triggered in the following conditions: A CF with some delete operations in the last a few queries which will result in an empty CF (those are flushed to SST file and a compaction triggered which combines this file and all other SST files and generates an empty CF, or there is another reason to write a manifest entry for this CF after a flush that generates no SST file from an empty CF). The deletion entries are logged in a WAL and this WAL was corrupted, while the CF's log number points to the next WAL (due to the flush). Therefore, the DB can only recover to the point without these trailing deletions and cause the inconsistent DB status.
|
||||
|
||||
### New Features
|
||||
* Add new option allow_stall passed during instance creation of WriteBufferManager. When allow_stall is set, WriteBufferManager will stall all writers shared across multiple DBs and columns if memory usage goes beyond specified WriteBufferManager::buffer_size (soft limit). Stall will be cleared when memory is freed after flush and memory usage goes down below buffer_size.
|
||||
|
||||
## 6.20.0 (04/16/2021)
|
||||
### Behavior Changes
|
||||
* `ColumnFamilyOptions::sample_for_compression` now takes effect for creation of all block-based tables. Previously it only took effect for block-based tables created by flush.
|
||||
* `CompactFiles()` can no longer compact files from lower level to up level, which has the risk to corrupt DB (details: #8063). The validation is also added to all compactions.
|
||||
* Fixed some cases in which DB::OpenForReadOnly() could write to the filesystem. If you want a Logger with a read-only DB, you must now set DBOptions::info_log yourself, such as using CreateLoggerFromOptions().
|
||||
* get_iostats_context() will never return nullptr. If thread-local support is not available, and user does not opt-out iostats context, then compilation will fail. The same applies to perf context as well.
|
||||
|
||||
### Bug Fixes
|
||||
* Use thread-safe `strerror_r()` to get error messages.
|
||||
* Fixed a potential hang in shutdown for a DB whose `Env` has high-pri thread pool disabled (`Env::GetBackgroundThreads(Env::Priority::HIGH) == 0`)
|
||||
* Made BackupEngine thread-safe and added documentation comments to clarify what is safe for multiple BackupEngine objects accessing the same backup directory.
|
||||
* Fixed crash (divide by zero) when compression dictionary is applied to a file containing only range tombstones.
|
||||
* Fixed a backward iteration bug with partitioned filter enabled: not including the prefix of the last key of the previous filter partition in current filter partition can cause wrong iteration result.
|
||||
* Fixed a bug that allowed `DBOptions::max_open_files` to be set with a non-negative integer with `ColumnFamilyOptions::compaction_style = kCompactionStyleFIFO`.
|
||||
|
||||
### Performance Improvements
|
||||
* On ARM platform, use `yield` instead of `wfe` to relax cpu to gain better performance.
|
||||
|
||||
### Public API change
|
||||
* Added `TableProperties::slow_compression_estimated_data_size` and `TableProperties::fast_compression_estimated_data_size`. When `ColumnFamilyOptions::sample_for_compression > 0`, they estimate what `TableProperties::data_size` would have been if the "fast" or "slow" (see `ColumnFamilyOptions::sample_for_compression` API doc for definitions) compression had been used instead.
|
||||
* Update DB::StartIOTrace and remove Env object from the arguments as its redundant and DB already has Env object that is passed down to IOTracer::StartIOTrace
|
||||
* Added `FlushReason::kWalFull`, which is reported when a memtable is flushed due to the WAL reaching its size limit; those flushes were previously reported as `FlushReason::kWriteBufferManager`. Also, changed the reason for flushes triggered by the write buffer manager to `FlushReason::kWriteBufferManager`; they were previously reported as `FlushReason::kWriteBufferFull`.
|
||||
* Extend file_checksum_dump ldb command and DB::GetLiveFilesChecksumInfo API for IntegratedBlobDB and get checksum of blob files along with SST files.
|
||||
|
||||
### New Features
|
||||
* Added the ability to open BackupEngine backups as read-only DBs, using BackupInfo::name_for_open and env_for_open provided by BackupEngine::GetBackupInfo() with include_file_details=true.
|
||||
* Added BackupEngine support for integrated BlobDB, with blob files shared between backups when table files are shared. Because of current limitations, blob files always use the kLegacyCrc32cAndFileSize naming scheme, and incremental backups must read and checksum all blob files in a DB, even for files that are already backed up.
|
||||
* Added an optional output parameter to BackupEngine::CreateNewBackup(WithMetadata) to return the BackupID of the new backup.
|
||||
* Added BackupEngine::GetBackupInfo / GetLatestBackupInfo for querying individual backups.
|
||||
* Made the Ribbon filter a long-term supported feature in terms of the SST schema(compatible with version >= 6.15.0) though the API for enabling it is expected to change.
|
||||
|
||||
|
||||
## 6.19.0 (03/21/2021)
|
||||
### Bug Fixes
|
||||
* Fixed the truncation error found in APIs/tools when dumping block-based SST files in a human-readable format. After fix, the block-based table can be fully dumped as a readable file.
|
||||
* When hitting a write slowdown condition, no write delay (previously 1 millisecond) is imposed until `delayed_write_rate` is actually exceeded, with an initial burst allowance of 1 millisecond worth of bytes. Also, beyond the initial burst allowance, `delayed_write_rate` is now more strictly enforced, especially with multiple column families.
|
||||
|
||||
### Public API change
|
||||
* Changed default `BackupableDBOptions::share_files_with_checksum` to `true` and deprecated `false` because of potential for data loss. Note that accepting this change in behavior can temporarily increase backup data usage because files are not shared between backups using the two different settings. Also removed obsolete option kFlagMatchInterimNaming.
|
||||
* Add a new option BlockBasedTableOptions::max_auto_readahead_size. RocksDB does auto-readahead for iterators on noticing more than two reads for a table file if user doesn't provide readahead_size. The readahead starts at 8KB and doubles on every additional read upto max_auto_readahead_size and now max_auto_readahead_size can be configured dynamically as well. Found that 256 KB readahead size provides the best performance, based on experiments, for auto readahead. Experiment data is in PR #3282. If value is set 0 then no automatic prefetching will be done by rocksdb. Also changing the value will only affect files opened after the change.
|
||||
* Add suppport to extend DB::VerifyFileChecksums API to also verify blob files checksum.
|
||||
* When using the new BlobDB, the amount of data written by flushes/compactions is now broken down into table files and blob files in the compaction statistics; namely, Write(GB) denotes the amount of data written to table files, while Wblob(GB) means the amount of data written to blob files.
|
||||
* New default BlockBasedTableOptions::format_version=5 to enable new Bloom filter implementation by default, compatible with RocksDB versions >= 6.6.0.
|
||||
* Add new SetBufferSize API to WriteBufferManager to allow dynamic management of memory allotted to all write buffers. This allows user code to adjust memory monitoring provided by WriteBufferManager as process memory needs change datasets grow and shrink.
|
||||
* Clarified the required semantics of Read() functions in FileSystem and Env APIs. Please ensure any custom implementations are compliant.
|
||||
* For the new integrated BlobDB implementation, compaction statistics now include the amount of data read from blob files during compaction (due to garbage collection or compaction filters). Write amplification metrics have also been extended to account for data read from blob files.
|
||||
* Add EqualWithoutTimestamp() to Comparator.
|
||||
* Extend support to track blob files in SSTFileManager whenever a blob file is created/deleted. Blob files will be scheduled to delete via SSTFileManager and SStFileManager will now take blob files in account while calculating size and space limits along with SST files.
|
||||
* Add new Append and PositionedAppend API with checksum handoff to legacy Env.
|
||||
|
||||
### New Features
|
||||
* Support compaction filters for the new implementation of BlobDB. Add `FilterBlobByKey()` to `CompactionFilter`. Subclasses can override this method so that compaction filters can determine whether the actual blob value has to be read during compaction. Use a new `kUndetermined` in `CompactionFilter::Decision` to indicated that further action is necessary for compaction filter to make a decision.
|
||||
* Add support to extend retrieval of checksums for blob files from the MANIFEST when checkpointing. During backup, rocksdb can detect corruption in blob files during file copies.
|
||||
* Add new options for db_bench --benchmarks: flush, waitforcompaction, compact0, compact1.
|
||||
* Add an option to BackupEngine::GetBackupInfo to include the name and size of each backed-up file. Especially in the presence of file sharing among backups, this offers detailed insight into backup space usage.
|
||||
* Enable backward iteration on keys with user-defined timestamps.
|
||||
* Add statistics and info log for error handler: counters for bg error, bg io error, bg retryable io error, auto resume count, auto resume total retry number, and auto resume sucess; Histogram for auto resume retry count in each recovery call. Note that, each auto resume attempt will have one or multiple retries.
|
||||
|
||||
### Behavior Changes
|
||||
* During flush, only WAL sync retryable IO error is mapped to hard error, which will stall the writes. When WAL is used but only SST file write has retryable IO error, it will be mapped to soft error and write will not be affected.
|
||||
|
||||
## 6.18.0 (02/19/2021)
|
||||
### Behavior Changes
|
||||
@@ -24,6 +96,7 @@
|
||||
* Add new Append and PositionedAppend APIs to FileSystem to bring the data verification information (data checksum information) from upper layer (e.g., WritableFileWriter) to the storage layer. In this way, the customized FileSystem is able to verify the correctness of data being written to the storage on time. Add checksum_handoff_file_types to DBOptions. User can use this option to control which file types (Currently supported file tyes: kWALFile, kTableFile, kDescriptorFile.) should use the new Append and PositionedAppend APIs to handoff the verification information. Currently, RocksDB only use crc32c to calculate the checksum for write handoff.
|
||||
* Add an option, `CompressionOptions::max_dict_buffer_bytes`, to limit the in-memory buffering for selecting samples for generating/training a dictionary. The limit is currently loosely adhered to.
|
||||
|
||||
|
||||
## 6.17.0 (01/15/2021)
|
||||
### Behavior Changes
|
||||
* When verifying full file checksum with `DB::VerifyFileChecksums()`, we now fail with `Status::InvalidArgument` if the name of the checksum generator used for verification does not match the name of the checksum generator used for protecting the file when it was created.
|
||||
@@ -108,6 +181,7 @@
|
||||
* The settings of the DBOptions and ColumnFamilyOptions are now managed by Configurable objects (see New Features). The same convenience methods to configure these options still exist but the backend implementation has been unified under a common implementation.
|
||||
|
||||
### New Features
|
||||
|
||||
* Methods to configure serialize, and compare -- such as TableFactory -- are exposed directly through the Configurable base class (from which these objects inherit). This change will allow for better and more thorough configuration management and retrieval in the future. The options for a Configurable object can be set via the ConfigureFromMap, ConfigureFromString, or ConfigureOption method. The serialized version of the options of an object can be retrieved via the GetOptionString, ToString, or GetOption methods. The list of options supported by an object can be obtained via the GetOptionNames method. The "raw" object (such as the BlockBasedTableOption) for an option may be retrieved via the GetOptions method. Configurable options can be compared via the AreEquivalent method. The settings within a Configurable object may be validated via the ValidateOptions method. The object may be intialized (at which point only mutable options may be updated) via the PrepareOptions method.
|
||||
* Introduce options.check_flush_compaction_key_order with default value to be true. With this option, during flush and compaction, key order will be checked when writing to each SST file. If the order is violated, the flush or compaction will fail.
|
||||
* Added is_full_compaction to CompactionJobStats, so that the information is available through the EventListener interface.
|
||||
|
||||
+16
-5
@@ -43,6 +43,8 @@ to build a portable binary, add `PORTABLE=1` before your make commands, like thi
|
||||
command line flags processing. You can compile rocksdb library even
|
||||
if you don't have gflags installed.
|
||||
|
||||
* `make check` will also check code formatting, which requires [clang-format](https://clang.llvm.org/docs/ClangFormat.html)
|
||||
|
||||
* If you wish to build the RocksJava static target, then cmake is required for building Snappy.
|
||||
|
||||
## Supported platforms
|
||||
@@ -94,12 +96,21 @@ to build a portable binary, add `PORTABLE=1` before your make commands, like thi
|
||||
sudo yum install libasan
|
||||
|
||||
* Install zstandard:
|
||||
* With [EPEL](https://fedoraproject.org/wiki/EPEL):
|
||||
|
||||
wget https://github.com/facebook/zstd/archive/v1.1.3.tar.gz
|
||||
mv v1.1.3.tar.gz zstd-1.1.3.tar.gz
|
||||
tar zxvf zstd-1.1.3.tar.gz
|
||||
cd zstd-1.1.3
|
||||
make && sudo make install
|
||||
sudo yum install libzstd-devel
|
||||
|
||||
* With CentOS 8:
|
||||
|
||||
sudo dnf install libzstd-devel
|
||||
|
||||
* From source:
|
||||
|
||||
wget https://github.com/facebook/zstd/archive/v1.1.3.tar.gz
|
||||
mv v1.1.3.tar.gz zstd-1.1.3.tar.gz
|
||||
tar zxvf zstd-1.1.3.tar.gz
|
||||
cd zstd-1.1.3
|
||||
make && sudo make install
|
||||
|
||||
* **OS X**:
|
||||
* Install latest C++ compiler that supports C++ 11:
|
||||
|
||||
@@ -55,50 +55,25 @@ DEBUG_LEVEL?=1
|
||||
# Set the default LIB_MODE to static
|
||||
LIB_MODE?=static
|
||||
|
||||
ifeq ($(MAKECMDGOALS),dbg)
|
||||
# OBJ_DIR is where the object files reside. Default to the current directory
|
||||
OBJ_DIR?=.
|
||||
|
||||
# Check the MAKECMDGOALS to set the DEBUG_LEVEL and LIB_MODE appropriately
|
||||
|
||||
ifneq ($(filter clean release install, $(MAKECMDGOALS)),)
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
ifneq ($(filter dbg, $(MAKECMDGOALS)),)
|
||||
DEBUG_LEVEL=2
|
||||
endif
|
||||
|
||||
ifeq ($(MAKECMDGOALS),clean)
|
||||
else ifneq ($(filter shared_lib install-shared, $(MAKECMDGOALS)),)
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
|
||||
ifeq ($(MAKECMDGOALS),release)
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
|
||||
ifeq ($(MAKECMDGOALS),shared_lib)
|
||||
LIB_MODE=shared
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
|
||||
ifeq ($(MAKECMDGOALS),install-shared)
|
||||
LIB_MODE=shared
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
|
||||
ifeq ($(MAKECMDGOALS),static_lib)
|
||||
else ifneq ($(filter static_lib install-static, $(MAKECMDGOALS)),)
|
||||
DEBUG_LEVEL=0
|
||||
LIB_MODE=static
|
||||
endif
|
||||
|
||||
ifeq ($(MAKECMDGOALS),install-static)
|
||||
DEBUG_LEVEL=0
|
||||
LIB_MODE=static
|
||||
endif
|
||||
|
||||
ifeq ($(MAKECMDGOALS),install)
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
|
||||
|
||||
ifneq ($(findstring jtest, $(MAKECMDGOALS)),)
|
||||
else ifneq ($(filter jtest rocksdbjava%, $(MAKECMDGOALS)),)
|
||||
OBJ_DIR=jl
|
||||
LIB_MODE=shared
|
||||
endif
|
||||
|
||||
ifneq ($(findstring rocksdbjava, $(MAKECMDGOALS)),)
|
||||
LIB_MODE=shared
|
||||
ifneq ($(findstring rocksdbjavastatic, $(MAKECMDGOALS)),)
|
||||
OBJ_DIR=jls
|
||||
ifneq ($(DEBUG_LEVEL),2)
|
||||
@@ -107,8 +82,6 @@ ifneq ($(findstring rocksdbjava, $(MAKECMDGOALS)),)
|
||||
ifeq ($(MAKECMDGOALS),rocksdbjavastaticpublish)
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
else
|
||||
OBJ_DIR=jl
|
||||
endif
|
||||
endif
|
||||
|
||||
@@ -259,6 +232,8 @@ AM_SHARE = $(AM_V_CCLD) $(CXX) $(PLATFORM_SHARED_LDFLAGS)$@ -L. $(patsubst lib%.
|
||||
# Export some common variables that might have been passed as Make variables
|
||||
# instead of environment variables.
|
||||
dummy := $(shell (export ROCKSDB_ROOT="$(CURDIR)"; \
|
||||
export CXXFLAGS="$(EXTRA_CXXFLAGS)"; \
|
||||
export LDFLAGS="$(EXTRA_LDFLAGS)"; \
|
||||
export COMPILE_WITH_ASAN="$(COMPILE_WITH_ASAN)"; \
|
||||
export COMPILE_WITH_TSAN="$(COMPILE_WITH_TSAN)"; \
|
||||
export COMPILE_WITH_UBSAN="$(COMPILE_WITH_UBSAN)"; \
|
||||
@@ -436,6 +411,10 @@ default: all
|
||||
WARNING_FLAGS = -W -Wextra -Wall -Wsign-compare -Wshadow \
|
||||
-Wunused-parameter
|
||||
|
||||
ifeq (,$(filter amd64, $(MACHINE)))
|
||||
C_WARNING_FLAGS = -Wstrict-prototypes
|
||||
endif
|
||||
|
||||
ifdef USE_CLANG
|
||||
# Used by some teams in Facebook
|
||||
WARNING_FLAGS += -Wshift-sign-overflow
|
||||
@@ -480,12 +459,11 @@ ifeq ($(NO_THREEWAY_CRC32C), 1)
|
||||
CXXFLAGS += -DNO_THREEWAY_CRC32C
|
||||
endif
|
||||
|
||||
CFLAGS += $(WARNING_FLAGS) -I. -I./include $(PLATFORM_CCFLAGS) $(OPT)
|
||||
CFLAGS += $(C_WARNING_FLAGS) $(WARNING_FLAGS) -I. -I./include $(PLATFORM_CCFLAGS) $(OPT)
|
||||
CXXFLAGS += $(WARNING_FLAGS) -I. -I./include $(PLATFORM_CXXFLAGS) $(OPT) -Woverloaded-virtual -Wnon-virtual-dtor -Wno-missing-field-initializers
|
||||
|
||||
LDFLAGS += $(PLATFORM_LDFLAGS)
|
||||
|
||||
OBJ_DIR?=.
|
||||
LIB_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(LIB_SOURCES))
|
||||
LIB_OBJECTS += $(patsubst %.cc, $(OBJ_DIR)/%.o, $(ROCKSDB_PLUGIN_SOURCES))
|
||||
ifeq ($(HAVE_POWER8),1)
|
||||
@@ -497,6 +475,12 @@ ifeq ($(USE_FOLLY_DISTRIBUTED_MUTEX),1)
|
||||
LIB_OBJECTS += $(patsubst %.cpp, $(OBJ_DIR)/%.o, $(FOLLY_SOURCES))
|
||||
endif
|
||||
|
||||
# range_tree is not compatible with non GNU libc on ppc64
|
||||
# see https://jira.percona.com/browse/PS-7559
|
||||
ifneq ($(PPC_LIBC_IS_GNU),0)
|
||||
LIB_OBJECTS += $(patsubst %.cc, $(OBJ_DIR)/%.o, $(RANGE_TREE_SOURCES))
|
||||
endif
|
||||
|
||||
GTEST = $(OBJ_DIR)/$(GTEST_DIR)/gtest/gtest-all.o
|
||||
TESTUTIL = $(OBJ_DIR)/test_util/testutil.o
|
||||
TESTHARNESS = $(OBJ_DIR)/test_util/testharness.o $(TESTUTIL) $(GTEST)
|
||||
@@ -507,12 +491,14 @@ VALGRIND_OPTS = --error-exitcode=$(VALGRIND_ERROR) --leak-check=full
|
||||
|
||||
TEST_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(TEST_LIB_SOURCES) $(MOCK_LIB_SOURCES)) $(GTEST)
|
||||
BENCH_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(BENCH_LIB_SOURCES))
|
||||
CACHE_BENCH_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(CACHE_BENCH_LIB_SOURCES))
|
||||
TOOL_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(TOOL_LIB_SOURCES))
|
||||
ANALYZE_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(ANALYZER_LIB_SOURCES))
|
||||
STRESS_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(STRESS_LIB_SOURCES))
|
||||
|
||||
# Exclude build_version.cc -- a generated source file -- from all sources. Not needed for dependencies
|
||||
ALL_SOURCES = $(LIB_SOURCES) $(TEST_LIB_SOURCES) $(MOCK_LIB_SOURCES) $(GTEST_DIR)/gtest/gtest-all.cc
|
||||
ALL_SOURCES += $(TOOL_LIB_SOURCES) $(BENCH_LIB_SOURCES) $(ANALYZER_LIB_SOURCES) $(STRESS_LIB_SOURCES)
|
||||
ALL_SOURCES += $(TOOL_LIB_SOURCES) $(BENCH_LIB_SOURCES) $(CACHE_BENCH_LIB_SOURCES) $(ANALYZER_LIB_SOURCES) $(STRESS_LIB_SOURCES)
|
||||
ALL_SOURCES += $(TEST_MAIN_SOURCES) $(TOOL_MAIN_SOURCES) $(BENCH_MAIN_SOURCES)
|
||||
|
||||
TESTS = $(patsubst %.cc, %, $(notdir $(TEST_MAIN_SOURCES)))
|
||||
@@ -523,239 +509,38 @@ ifeq ($(USE_FOLLY_DISTRIBUTED_MUTEX),1)
|
||||
ALL_SOURCES += third-party/folly/folly/synchronization/test/DistributedMutexTest.cc
|
||||
endif
|
||||
|
||||
PARALLEL_TEST = \
|
||||
backupable_db_test \
|
||||
db_bloom_filter_test \
|
||||
db_compaction_filter_test \
|
||||
db_compaction_test \
|
||||
db_merge_operator_test \
|
||||
db_sst_test \
|
||||
db_test \
|
||||
db_test2 \
|
||||
db_universal_compaction_test \
|
||||
db_wal_test \
|
||||
column_family_test \
|
||||
external_sst_file_test \
|
||||
import_column_family_test \
|
||||
fault_injection_test \
|
||||
file_reader_writer_test \
|
||||
inlineskiplist_test \
|
||||
manual_compaction_test \
|
||||
persistent_cache_test \
|
||||
table_test \
|
||||
transaction_test \
|
||||
point_lock_manager_test \
|
||||
range_locking_test \
|
||||
write_prepared_transaction_test \
|
||||
write_unprepared_transaction_test \
|
||||
|
||||
ifeq ($(USE_FOLLY_DISTRIBUTED_MUTEX),1)
|
||||
TESTS += folly_synchronization_distributed_mutex_test
|
||||
PARALLEL_TEST += folly_synchronization_distributed_mutex_test
|
||||
TESTS_PASSING_ASC = folly_synchronization_distributed_mutex_test
|
||||
endif
|
||||
|
||||
# options_settable_test doesn't pass with UBSAN as we use hack in the test
|
||||
ifdef COMPILE_WITH_UBSAN
|
||||
TESTS := $(shell echo $(TESTS) | sed 's/\boptions_settable_test\b//g')
|
||||
endif
|
||||
ifdef ASSERT_STATUS_CHECKED
|
||||
# This is a new check for which we will add support incrementally. This
|
||||
# list can be removed once support is fully added.
|
||||
TESTS_PASSING_ASC = \
|
||||
arena_test \
|
||||
autovector_test \
|
||||
cache_test \
|
||||
lru_cache_test \
|
||||
blob_file_addition_test \
|
||||
blob_file_builder_test \
|
||||
blob_file_cache_test \
|
||||
blob_file_garbage_test \
|
||||
blob_file_reader_test \
|
||||
bloom_test \
|
||||
cassandra_format_test \
|
||||
cassandra_functional_test \
|
||||
cassandra_row_merge_test \
|
||||
cassandra_serialize_test \
|
||||
cleanable_test \
|
||||
checkpoint_test \
|
||||
coding_test \
|
||||
crc32c_test \
|
||||
dbformat_test \
|
||||
db_basic_test \
|
||||
compact_files_test \
|
||||
compaction_picker_test \
|
||||
comparator_db_test \
|
||||
db_encryption_test \
|
||||
db_iter_test \
|
||||
db_iter_stress_test \
|
||||
db_log_iter_test \
|
||||
db_bloom_filter_test \
|
||||
db_blob_basic_test \
|
||||
db_blob_index_test \
|
||||
db_block_cache_test \
|
||||
db_compaction_test \
|
||||
db_compaction_filter_test \
|
||||
db_dynamic_level_test \
|
||||
db_flush_test \
|
||||
db_inplace_update_test \
|
||||
db_io_failure_test \
|
||||
db_iterator_test \
|
||||
db_kv_checksum_test \
|
||||
db_logical_block_size_cache_test \
|
||||
db_memtable_test \
|
||||
db_merge_operand_test \
|
||||
db_merge_operator_test \
|
||||
db_wal_test \
|
||||
db_with_timestamp_basic_test \
|
||||
db_with_timestamp_compaction_test \
|
||||
db_write_test \
|
||||
db_options_test \
|
||||
db_properties_test \
|
||||
db_range_del_test \
|
||||
db_secondary_test \
|
||||
deletefile_test \
|
||||
external_sst_file_test \
|
||||
options_file_test \
|
||||
db_sst_test \
|
||||
db_statistics_test \
|
||||
db_table_properties_test \
|
||||
db_tailing_iter_test \
|
||||
fault_injection_test \
|
||||
listener_test \
|
||||
log_test \
|
||||
manual_compaction_test \
|
||||
obsolete_files_test \
|
||||
perf_context_test \
|
||||
periodic_work_scheduler_test \
|
||||
perf_context_test \
|
||||
version_set_test \
|
||||
wal_manager_test \
|
||||
defer_test \
|
||||
filename_test \
|
||||
dynamic_bloom_test \
|
||||
env_basic_test \
|
||||
env_test \
|
||||
env_logger_test \
|
||||
event_logger_test \
|
||||
error_handler_fs_test \
|
||||
external_sst_file_basic_test \
|
||||
auto_roll_logger_test \
|
||||
file_indexer_test \
|
||||
delete_scheduler_test \
|
||||
flush_job_test \
|
||||
hash_table_test \
|
||||
hash_test \
|
||||
heap_test \
|
||||
histogram_test \
|
||||
inlineskiplist_test \
|
||||
io_posix_test \
|
||||
iostats_context_test \
|
||||
ldb_cmd_test \
|
||||
memkind_kmem_allocator_test \
|
||||
merge_test \
|
||||
merger_test \
|
||||
mock_env_test \
|
||||
object_registry_test \
|
||||
optimistic_transaction_test \
|
||||
prefix_test \
|
||||
plain_table_db_test \
|
||||
repair_test \
|
||||
configurable_test \
|
||||
customizable_test \
|
||||
options_settable_test \
|
||||
options_test \
|
||||
point_lock_manager_test \
|
||||
random_access_file_reader_test \
|
||||
random_test \
|
||||
range_del_aggregator_test \
|
||||
sst_file_reader_test \
|
||||
range_tombstone_fragmenter_test \
|
||||
repeatable_thread_test \
|
||||
ribbon_test \
|
||||
skiplist_test \
|
||||
slice_test \
|
||||
slice_transform_test \
|
||||
sst_dump_test \
|
||||
statistics_test \
|
||||
stats_history_test \
|
||||
stringappend_test \
|
||||
thread_local_test \
|
||||
trace_analyzer_test \
|
||||
transaction_test \
|
||||
env_timed_test \
|
||||
filelock_test \
|
||||
timer_queue_test \
|
||||
timer_test \
|
||||
options_util_test \
|
||||
persistent_cache_test \
|
||||
util_merge_operators_test \
|
||||
block_cache_trace_analyzer_test \
|
||||
block_cache_tracer_test \
|
||||
cache_simulator_test \
|
||||
sim_cache_test \
|
||||
version_builder_test \
|
||||
version_edit_test \
|
||||
work_queue_test \
|
||||
write_buffer_manager_test \
|
||||
write_controller_test \
|
||||
write_prepared_transaction_test \
|
||||
write_unprepared_transaction_test \
|
||||
compaction_iterator_test \
|
||||
compaction_job_test \
|
||||
compaction_job_stats_test \
|
||||
io_tracer_test \
|
||||
io_tracer_parser_test \
|
||||
prefetch_test \
|
||||
merge_helper_test \
|
||||
memtable_list_test \
|
||||
flush_job_test \
|
||||
block_based_filter_block_test \
|
||||
block_fetcher_test \
|
||||
block_test \
|
||||
data_block_hash_index_test \
|
||||
full_filter_block_test \
|
||||
partitioned_filter_block_test \
|
||||
column_family_test \
|
||||
file_reader_writer_test \
|
||||
rate_limiter_test \
|
||||
corruption_test \
|
||||
reduce_levels_test \
|
||||
thread_list_test \
|
||||
compact_on_deletion_collector_test \
|
||||
db_universal_compaction_test \
|
||||
import_column_family_test \
|
||||
option_change_migration_test \
|
||||
cuckoo_table_builder_test \
|
||||
cuckoo_table_db_test \
|
||||
cuckoo_table_reader_test \
|
||||
memory_test \
|
||||
table_test \
|
||||
backupable_db_test \
|
||||
blob_db_test \
|
||||
ttl_test \
|
||||
write_batch_test \
|
||||
write_batch_with_index_test \
|
||||
# TODO: finish fixing all tests to pass this check
|
||||
TESTS_FAILING_ASC = \
|
||||
db_test \
|
||||
db_test2 \
|
||||
range_locking_test \
|
||||
testutil_test \
|
||||
|
||||
ifeq ($(USE_FOLLY_DISTRIBUTED_MUTEX),1)
|
||||
TESTS_PASSING_ASC += folly_synchronization_distributed_mutex_test
|
||||
# Since we have very few ASC exclusions left, excluding them from
|
||||
# the build is the most convenient way to exclude them from testing
|
||||
TESTS := $(filter-out $(TESTS_FAILING_ASC),$(TESTS))
|
||||
endif
|
||||
|
||||
# Enable building all unit tests, but use check_some to run only tests
|
||||
# known to pass ASC (ASSERT_STATUS_CHECKED)
|
||||
ROCKSDBTESTS_SUBSET ?= $(TESTS_PASSING_ASC)
|
||||
# Alternate: only build unit tests known to pass ASC, and run them
|
||||
# with make check
|
||||
#TESTS := $(filter $(TESTS_PASSING_ASC),$(TESTS))
|
||||
#PARALLEL_TEST := $(filter $(TESTS_PASSING_ASC),$(PARALLEL_TEST))
|
||||
else
|
||||
ROCKSDBTESTS_SUBSET ?= $(TESTS)
|
||||
endif
|
||||
ROCKSDBTESTS_SUBSET ?= $(TESTS)
|
||||
|
||||
# env_test - suspicious use of test::TmpDir
|
||||
# deletefile_test - serial because it generates giant temporary files in
|
||||
# its various tests. Parallel can fill up your /dev/shm
|
||||
NON_PARALLEL_TEST = \
|
||||
env_test \
|
||||
deletefile_test \
|
||||
|
||||
PARALLEL_TEST = $(filter-out $(NON_PARALLEL_TEST), $(TESTS))
|
||||
|
||||
# Not necessarily well thought out or up-to-date, but matches old list
|
||||
TESTS_PLATFORM_DEPENDENT := \
|
||||
db_basic_test \
|
||||
db_blob_basic_test \
|
||||
db_with_timestamp_basic_test \
|
||||
db_encryption_test \
|
||||
db_test2 \
|
||||
external_sst_file_basic_test \
|
||||
@@ -839,8 +624,8 @@ else
|
||||
LIBRARY=$(STATIC_LIBRARY)
|
||||
TEST_LIBRARY=$(STATIC_TEST_LIBRARY)
|
||||
TOOLS_LIBRARY=$(STATIC_TOOLS_LIBRARY)
|
||||
STRESS_LIBRARY=$(STATIC_STRESS_LIBRARY)
|
||||
endif
|
||||
STRESS_LIBRARY=$(STATIC_STRESS_LIBRARY)
|
||||
|
||||
ROCKSDB_MAJOR = $(shell egrep "ROCKSDB_MAJOR.[0-9]" include/rocksdb/version.h | cut -d ' ' -f 3)
|
||||
ROCKSDB_MINOR = $(shell egrep "ROCKSDB_MINOR.[0-9]" include/rocksdb/version.h | cut -d ' ' -f 3)
|
||||
@@ -924,7 +709,8 @@ endif # PLATFORM_SHARED_EXT
|
||||
analyze tools tools_lib \
|
||||
blackbox_crash_test_with_atomic_flush whitebox_crash_test_with_atomic_flush \
|
||||
blackbox_crash_test_with_txn whitebox_crash_test_with_txn \
|
||||
blackbox_crash_test_with_best_efforts_recovery
|
||||
blackbox_crash_test_with_best_efforts_recovery \
|
||||
blackbox_crash_test_with_ts whitebox_crash_test_with_ts
|
||||
|
||||
|
||||
all: $(LIBRARY) $(BENCHMARKS) tools tools_lib test_libs $(TESTS)
|
||||
@@ -1040,7 +826,7 @@ gen_parallel_tests:
|
||||
# 107.816 PASS t/DBTest.EncodeDecompressedBlockSizeTest
|
||||
#
|
||||
slow_test_regexp = \
|
||||
^.*SnapshotConcurrentAccessTest.*$$|^t/run-table_test-HarnessTest.Randomized$$|^t/run-db_test-.*(?:FileCreationRandomFailure|EncodeDecompressedBlockSizeTest)$$|^.*RecoverFromCorruptedWALWithoutFlush$$
|
||||
^.*SnapshotConcurrentAccessTest.*$$|^.*SeqAdvanceConcurrentTest.*$$|^t/run-table_test-HarnessTest.Randomized$$|^t/run-db_test-.*(?:FileCreationRandomFailure|EncodeDecompressedBlockSizeTest)$$|^.*RecoverFromCorruptedWALWithoutFlush$$
|
||||
prioritize_long_running_tests = \
|
||||
perl -pe 's,($(slow_test_regexp)),100 $$1,' \
|
||||
| sort -k1,1gr \
|
||||
@@ -1162,6 +948,8 @@ crash_test_with_txn: whitebox_crash_test_with_txn blackbox_crash_test_with_txn
|
||||
|
||||
crash_test_with_best_efforts_recovery: blackbox_crash_test_with_best_efforts_recovery
|
||||
|
||||
crash_test_with_ts: whitebox_crash_test_with_ts blackbox_crash_test_with_ts
|
||||
|
||||
blackbox_crash_test: db_stress
|
||||
$(PYTHON) -u tools/db_crashtest.py --simple blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
$(PYTHON) -u tools/db_crashtest.py blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
@@ -1175,6 +963,9 @@ blackbox_crash_test_with_txn: db_stress
|
||||
blackbox_crash_test_with_best_efforts_recovery: db_stress
|
||||
$(PYTHON) -u tools/db_crashtest.py --test_best_efforts_recovery blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
blackbox_crash_test_with_ts: db_stress
|
||||
$(PYTHON) -u tools/db_crashtest.py --enable_ts blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
ifeq ($(CRASH_TEST_KILL_ODD),)
|
||||
CRASH_TEST_KILL_ODD=888887
|
||||
endif
|
||||
@@ -1193,6 +984,10 @@ whitebox_crash_test_with_txn: db_stress
|
||||
$(PYTHON) -u tools/db_crashtest.py --txn whitebox --random_kill_odd \
|
||||
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
whitebox_crash_test_with_ts: db_stress
|
||||
$(PYTHON) -u tools/db_crashtest.py --enable_ts whitebox --random_kill_odd \
|
||||
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
asan_check: clean
|
||||
COMPILE_WITH_ASAN=1 $(MAKE) check -j32
|
||||
$(MAKE) clean
|
||||
@@ -1338,8 +1133,9 @@ analyze_incremental:
|
||||
$(MAKE) dbg
|
||||
|
||||
CLEAN_FILES += unity.cc
|
||||
unity.cc: Makefile
|
||||
unity.cc: Makefile util/build_version.cc.in
|
||||
rm -f $@ $@-t
|
||||
$(AM_V_at)$(gen_build_version) > util/build_version.cc
|
||||
for source_file in $(LIB_SOURCES); do \
|
||||
echo "#include \"$$source_file\"" >> $@-t; \
|
||||
done
|
||||
@@ -1423,7 +1219,7 @@ $(STATIC_TOOLS_LIBRARY): $(TOOL_OBJECTS)
|
||||
$(AM_V_AR)rm -f $@ $(SHARED_TOOLS_LIBRARY)
|
||||
$(AM_V_at)$(AR) $(ARFLAGS) $@ $^
|
||||
|
||||
$(STATIC_STRESS_LIBRARY): $(ANALYZE_OBJECTS) $(STRESS_OBJECTS)
|
||||
$(STATIC_STRESS_LIBRARY): $(ANALYZE_OBJECTS) $(STRESS_OBJECTS) $(TESTUTIL)
|
||||
$(AM_V_AR)rm -f $@ $(SHARED_STRESS_LIBRARY)
|
||||
$(AM_V_at)$(AR) $(ARFLAGS) $@ $^
|
||||
|
||||
@@ -1435,7 +1231,7 @@ $(SHARED_TOOLS_LIBRARY): $(TOOL_OBJECTS) $(SHARED1)
|
||||
$(AM_V_AR)rm -f $@ $(STATIC_TOOLS_LIBRARY)
|
||||
$(AM_SHARE)
|
||||
|
||||
$(SHARED_STRESS_LIBRARY): $(ANALYZE_OBJECTS) $(STRESS_OBJECTS) $(SHARED_TOOLS_LIBRARY) $(SHARED1)
|
||||
$(SHARED_STRESS_LIBRARY): $(ANALYZE_OBJECTS) $(STRESS_OBJECTS) $(TESTUTIL) $(SHARED_TOOLS_LIBRARY) $(SHARED1)
|
||||
$(AM_V_AR)rm -f $@ $(STATIC_STRESS_LIBRARY)
|
||||
$(AM_SHARE)
|
||||
|
||||
@@ -1457,7 +1253,7 @@ folly_synchronization_distributed_mutex_test: $(OBJ_DIR)/third-party/folly/folly
|
||||
$(AM_LINK)
|
||||
endif
|
||||
|
||||
cache_bench: $(OBJ_DIR)/cache/cache_bench.o $(LIBRARY)
|
||||
cache_bench: $(OBJ_DIR)/cache/cache_bench.o $(CACHE_BENCH_OBJECTS) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
persistent_cache_bench: $(OBJ_DIR)/utilities/persistent_cache/persistent_cache_bench.o $(LIBRARY)
|
||||
@@ -1568,6 +1364,9 @@ db_basic_test: $(OBJ_DIR)/db/db_basic_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
db_blob_basic_test: $(OBJ_DIR)/db/blob/db_blob_basic_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
db_blob_compaction_test: $(OBJ_DIR)/db/blob/db_blob_compaction_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
db_with_timestamp_basic_test: $(OBJ_DIR)/db/db_with_timestamp_basic_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -2011,7 +1810,7 @@ range_tombstone_fragmenter_test: $(OBJ_DIR)/db/range_tombstone_fragmenter_test.o
|
||||
sst_file_reader_test: $(OBJ_DIR)/table/sst_file_reader_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
db_secondary_test: $(OBJ_DIR)/db/db_impl/db_secondary_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
db_secondary_test: $(OBJ_DIR)/db/db_secondary_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
block_cache_tracer_test: $(OBJ_DIR)/trace_replay/block_cache_tracer_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
@@ -2059,6 +1858,11 @@ io_tracer_parser_test: $(OBJ_DIR)/tools/io_tracer_parser_test.o $(OBJ_DIR)/tools
|
||||
io_tracer_parser: $(OBJ_DIR)/tools/io_tracer_parser.o $(TOOLS_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
db_blob_corruption_test: $(OBJ_DIR)/db/blob/db_blob_corruption_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
db_write_buffer_manager_test: $(OBJ_DIR)/db/db_write_buffer_manager_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
#-------------------------------------------------
|
||||
# make install related stuff
|
||||
PREFIX ?= /usr/local
|
||||
@@ -2173,8 +1977,8 @@ SNAPPY_DOWNLOAD_BASE ?= https://github.com/google/snappy/archive
|
||||
LZ4_VER ?= 1.9.3
|
||||
LZ4_SHA256 ?= 030644df4611007ff7dc962d981f390361e6c97a34e5cbc393ddfbe019ffe2c1
|
||||
LZ4_DOWNLOAD_BASE ?= https://github.com/lz4/lz4/archive
|
||||
ZSTD_VER ?= 1.4.7
|
||||
ZSTD_SHA256 ?= 085500c8d0b9c83afbc1dc0d8b4889336ad019eba930c5d6a9c6c86c20c769c8
|
||||
ZSTD_VER ?= 1.4.9
|
||||
ZSTD_SHA256 ?= acf714d98e3db7b876e5b540cbf6dee298f60eb3c0723104f6d3f065cd60d6a8
|
||||
ZSTD_DOWNLOAD_BASE ?= https://github.com/facebook/zstd/archive
|
||||
CURL_SSL_OPTS ?= --tlsv1
|
||||
|
||||
@@ -2474,12 +2278,14 @@ endif
|
||||
# ---------------------------------------------------------------------------
|
||||
# Source files dependencies detection
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# If skip dependencies is ON, skip including the dep files
|
||||
ifneq ($(SKIP_DEPENDS), 1)
|
||||
DEPFILES = $(patsubst %.cc, $(OBJ_DIR)/%.cc.d, $(ALL_SOURCES))
|
||||
DEPFILES+ = $(patsubst %.c, $(OBJ_DIR)/%.c.d, $(LIB_SOURCES_C) $(TEST_MAIN_SOURCES_C))
|
||||
ifeq ($(USE_FOLLY_DISTRIBUTED_MUTEX),1)
|
||||
DEPFILES +=$(patsubst %.cpp, $(OBJ_DIR)/%.cpp.d, $(FOLLY_SOURCES))
|
||||
endif
|
||||
endif
|
||||
|
||||
# Add proper dependency support so changing a .h file forces a .cc file to
|
||||
# rebuild.
|
||||
@@ -2519,28 +2325,9 @@ endif
|
||||
build_subset_tests: $(ROCKSDBTESTS_SUBSET)
|
||||
$(AM_V_GEN)if [ -n "$${ROCKSDBTESTS_SUBSET_TESTS_TO_FILE}" ]; then echo "$(ROCKSDBTESTS_SUBSET)" > "$${ROCKSDBTESTS_SUBSET_TESTS_TO_FILE}"; else echo "$(ROCKSDBTESTS_SUBSET)"; fi
|
||||
|
||||
# if the make goal is either "clean" or "format", we shouldn't
|
||||
# try to import the *.d files.
|
||||
# TODO(kailiu) The unfamiliarity of Make's conditions leads to the ugly
|
||||
# working solution.
|
||||
ifneq ($(MAKECMDGOALS),clean)
|
||||
ifneq ($(MAKECMDGOALS),format)
|
||||
ifneq ($(MAKECMDGOALS),check-format)
|
||||
ifneq ($(MAKECMDGOALS),check-buck-targets)
|
||||
ifneq ($(MAKECMDGOALS),jclean)
|
||||
ifneq ($(MAKECMDGOALS),jtest)
|
||||
ifneq ($(MAKECMDGOALS),rocksdbjavastatic)
|
||||
ifneq ($(MAKECMDGOALS),rocksdbjavastatic_deps)
|
||||
ifneq ($(MAKECMDGOALS),package)
|
||||
ifneq ($(MAKECMDGOALS),analyze)
|
||||
# Remove the rules for which dependencies should not be generated and see if any are left.
|
||||
#If so, include the dependencies; if not, do not include the dependency files
|
||||
ROCKS_DEP_RULES=$(filter-out clean format check-format check-buck-targets jclean jtest package analyze tags rocksdbjavastatic% unity.% unity_test, $(MAKECMDGOALS))
|
||||
ifneq ("$(ROCKS_DEP_RULES)", "")
|
||||
-include $(DEPFILES)
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
@@ -1,3 +1,4 @@
|
||||
This is the list of all known third-party plugins for RocksDB. If something is missing, please open a pull request to add it.
|
||||
|
||||
* [Dedupfs](https://github.com/ajkr/dedupfs): an example for plugin developers to reference
|
||||
* [ZenFS](https://github.com/westerndigitalcorporation/zenfs): a file system for zoned block devices
|
||||
@@ -69,25 +69,25 @@ def get_cc_files(repo_path):
|
||||
return cc_files
|
||||
|
||||
|
||||
# Get parallel tests from Makefile
|
||||
def get_parallel_tests(repo_path):
|
||||
# Get non_parallel tests from Makefile
|
||||
def get_non_parallel_tests(repo_path):
|
||||
Makefile = repo_path + "/Makefile"
|
||||
|
||||
s = set({})
|
||||
|
||||
found_parallel_tests = False
|
||||
found_non_parallel_tests = False
|
||||
for line in open(Makefile):
|
||||
line = line.strip()
|
||||
if line.startswith("PARALLEL_TEST ="):
|
||||
found_parallel_tests = True
|
||||
elif found_parallel_tests:
|
||||
if line.startswith("NON_PARALLEL_TEST ="):
|
||||
found_non_parallel_tests = True
|
||||
elif found_non_parallel_tests:
|
||||
if line.endswith("\\"):
|
||||
# remove the trailing \
|
||||
line = line[:-1]
|
||||
line = line.strip()
|
||||
s.add(line)
|
||||
else:
|
||||
# we consumed all the parallel tests
|
||||
# we consumed all the non_parallel tests
|
||||
break
|
||||
|
||||
return s
|
||||
@@ -123,10 +123,10 @@ def generate_targets(repo_path, deps_map):
|
||||
src_mk = parse_src_mk(repo_path)
|
||||
# get all .cc files
|
||||
cc_files = get_cc_files(repo_path)
|
||||
# get parallel tests from Makefile
|
||||
parallel_tests = get_parallel_tests(repo_path)
|
||||
# get non_parallel tests from Makefile
|
||||
non_parallel_tests = get_non_parallel_tests(repo_path)
|
||||
|
||||
if src_mk is None or cc_files is None or parallel_tests is None:
|
||||
if src_mk is None or cc_files is None or non_parallel_tests is None:
|
||||
return False
|
||||
|
||||
extra_argv = ""
|
||||
@@ -141,11 +141,15 @@ def generate_targets(repo_path, deps_map):
|
||||
TARGETS.add_library(
|
||||
"rocksdb_lib",
|
||||
src_mk["LIB_SOURCES"] +
|
||||
# always add range_tree, it's only excluded on ppc64, which we don't use internally
|
||||
src_mk["RANGE_TREE_SOURCES"] +
|
||||
src_mk["TOOL_LIB_SOURCES"])
|
||||
# rocksdb_whole_archive_lib
|
||||
TARGETS.add_library(
|
||||
"rocksdb_whole_archive_lib",
|
||||
src_mk["LIB_SOURCES"] +
|
||||
# always add range_tree, it's only excluded on ppc64, which we don't use internally
|
||||
src_mk["RANGE_TREE_SOURCES"] +
|
||||
src_mk["TOOL_LIB_SOURCES"],
|
||||
deps=None,
|
||||
headers=None,
|
||||
@@ -169,6 +173,11 @@ def generate_targets(repo_path, deps_map):
|
||||
src_mk.get("ANALYZER_LIB_SOURCES", []) +
|
||||
["test_util/testutil.cc"],
|
||||
[":rocksdb_lib"])
|
||||
# rocksdb_cache_bench_tools_lib
|
||||
TARGETS.add_library(
|
||||
"rocksdb_cache_bench_tools_lib",
|
||||
src_mk.get("CACHE_BENCH_LIB_SOURCES", []),
|
||||
[":rocksdb_lib"])
|
||||
# rocksdb_stress_lib
|
||||
TARGETS.add_rocksdb_library(
|
||||
"rocksdb_stress_lib",
|
||||
@@ -207,7 +216,7 @@ def generate_targets(repo_path, deps_map):
|
||||
TARGETS.register_test(
|
||||
test_target_name,
|
||||
test_src,
|
||||
test in parallel_tests,
|
||||
test not in non_parallel_tests,
|
||||
json.dumps(deps['extra_deps']),
|
||||
json.dumps(deps['extra_compiler_flags']))
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@ cpp_binary(
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
deps = [":rocksdb_test_lib"],
|
||||
) if not is_opt_mode else None
|
||||
|
||||
|
||||
@@ -94,10 +94,12 @@ ROCKSDB_PREPROCESSOR_FLAGS = [
|
||||
|
||||
# Added missing flags from output of build_detect_platform
|
||||
"-DROCKSDB_BACKTRACE",
|
||||
]
|
||||
|
||||
# Directories with files for #include
|
||||
"-I" + REPO_PATH + "include/",
|
||||
"-I" + REPO_PATH,
|
||||
# Directories with files for #include
|
||||
ROCKSDB_INCLUDE_PATHS = [
|
||||
"",
|
||||
"include",
|
||||
]
|
||||
|
||||
ROCKSDB_ARCH_PREPROCESSOR_FLAGS = {{
|
||||
@@ -145,6 +147,7 @@ cpp_library(
|
||||
os_deps = ROCKSDB_OS_DEPS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
deps = [{deps}],
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS{extra_external_deps},
|
||||
link_whole = {link_whole},
|
||||
@@ -161,6 +164,7 @@ cpp_library(
|
||||
os_deps = ROCKSDB_OS_DEPS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
deps = ROCKSDB_LIB_DEPS,
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
)
|
||||
@@ -173,6 +177,7 @@ cpp_binary(
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
deps = [{deps}],
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
)
|
||||
@@ -203,6 +208,7 @@ ROCKS_TESTS = [
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS + extra_compiler_flags,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
deps = [":rocksdb_test_lib"] + extra_deps,
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS + [
|
||||
("googletest", None, "gtest"),
|
||||
|
||||
@@ -177,7 +177,7 @@ case "$TARGET_OS" in
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lpthread -lrt -ldl"
|
||||
if test $ROCKSDB_USE_IO_URING; then
|
||||
# check for liburing
|
||||
$CXX $CFLAGS -x c++ - -luring -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -luring -o /dev/null 2>/dev/null <<EOF
|
||||
#include <liburing.h>
|
||||
int main() {
|
||||
struct io_uring ring;
|
||||
@@ -288,7 +288,7 @@ if [ "$CROSS_COMPILE" = "true" -o "$FBCODE_BUILD" = "true" ]; then
|
||||
else
|
||||
if ! test $ROCKSDB_DISABLE_FALLOCATE; then
|
||||
# Test whether fallocate is available
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <fcntl.h>
|
||||
#include <linux/falloc.h>
|
||||
int main() {
|
||||
@@ -304,7 +304,7 @@ EOF
|
||||
if ! test $ROCKSDB_DISABLE_SNAPPY; then
|
||||
# Test whether Snappy library is installed
|
||||
# http://code.google.com/p/snappy/
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <snappy.h>
|
||||
int main() {}
|
||||
EOF
|
||||
@@ -319,7 +319,7 @@ EOF
|
||||
# Test whether gflags library is installed
|
||||
# http://gflags.github.io/gflags/
|
||||
# check if the namespace is gflags
|
||||
if $CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null << EOF
|
||||
if $CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null 2>/dev/null << EOF
|
||||
#include <gflags/gflags.h>
|
||||
using namespace GFLAGS_NAMESPACE;
|
||||
int main() {}
|
||||
@@ -328,7 +328,7 @@ EOF
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DGFLAGS=1"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags"
|
||||
# check if namespace is gflags
|
||||
elif $CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null << EOF
|
||||
elif $CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null 2>/dev/null << EOF
|
||||
#include <gflags/gflags.h>
|
||||
using namespace gflags;
|
||||
int main() {}
|
||||
@@ -337,7 +337,7 @@ EOF
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DGFLAGS=1 -DGFLAGS_NAMESPACE=gflags"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags"
|
||||
# check if namespace is google
|
||||
elif $CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null << EOF
|
||||
elif $CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null 2>/dev/null << EOF
|
||||
#include <gflags/gflags.h>
|
||||
using namespace google;
|
||||
int main() {}
|
||||
@@ -350,7 +350,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_ZLIB; then
|
||||
# Test whether zlib library is installed
|
||||
$CXX $CFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <zlib.h>
|
||||
int main() {}
|
||||
EOF
|
||||
@@ -363,7 +363,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_BZIP; then
|
||||
# Test whether bzip library is installed
|
||||
$CXX $CFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <bzlib.h>
|
||||
int main() {}
|
||||
EOF
|
||||
@@ -376,7 +376,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_LZ4; then
|
||||
# Test whether lz4 library is installed
|
||||
$CXX $CFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <lz4.h>
|
||||
#include <lz4hc.h>
|
||||
int main() {}
|
||||
@@ -390,7 +390,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_ZSTD; then
|
||||
# Test whether zstd library is installed
|
||||
$CXX $CFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <zstd.h>
|
||||
int main() {}
|
||||
EOF
|
||||
@@ -403,7 +403,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_NUMA; then
|
||||
# Test whether numa is available
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null -lnuma 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null -lnuma 2>/dev/null <<EOF
|
||||
#include <numa.h>
|
||||
#include <numaif.h>
|
||||
int main() {}
|
||||
@@ -417,7 +417,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_TBB; then
|
||||
# Test whether tbb is available
|
||||
$CXX $CFLAGS $LDFLAGS -x c++ - -o /dev/null -ltbb 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS $LDFLAGS -x c++ - -o /dev/null -ltbb 2>/dev/null <<EOF
|
||||
#include <tbb/tbb.h>
|
||||
int main() {}
|
||||
EOF
|
||||
@@ -430,7 +430,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_JEMALLOC; then
|
||||
# Test whether jemalloc is available
|
||||
if echo 'int main() {}' | $CXX $CFLAGS -x c++ - -o /dev/null -ljemalloc \
|
||||
if echo 'int main() {}' | $CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null -ljemalloc \
|
||||
2>/dev/null; then
|
||||
# This will enable some preprocessor identifiers in the Makefile
|
||||
JEMALLOC=1
|
||||
@@ -451,7 +451,7 @@ EOF
|
||||
fi
|
||||
if ! test $JEMALLOC && ! test $ROCKSDB_DISABLE_TCMALLOC; then
|
||||
# jemalloc is not available. Let's try tcmalloc
|
||||
if echo 'int main() {}' | $CXX $CFLAGS -x c++ - -o /dev/null \
|
||||
if echo 'int main() {}' | $CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null \
|
||||
-ltcmalloc 2>/dev/null; then
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -ltcmalloc"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -ltcmalloc"
|
||||
@@ -460,7 +460,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_MALLOC_USABLE_SIZE; then
|
||||
# Test whether malloc_usable_size is available
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <malloc.h>
|
||||
int main() {
|
||||
size_t res = malloc_usable_size(0);
|
||||
@@ -475,7 +475,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_MEMKIND; then
|
||||
# Test whether memkind library is installed
|
||||
$CXX $CFLAGS $COMMON_FLAGS -lmemkind -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS -lmemkind -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <memkind.h>
|
||||
int main() {
|
||||
memkind_malloc(MEMKIND_DAX_KMEM, 1024);
|
||||
@@ -491,7 +491,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_PTHREAD_MUTEX_ADAPTIVE_NP; then
|
||||
# Test whether PTHREAD_MUTEX_ADAPTIVE_NP mutex type is available
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <pthread.h>
|
||||
int main() {
|
||||
int x = PTHREAD_MUTEX_ADAPTIVE_NP;
|
||||
@@ -506,7 +506,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_BACKTRACE; then
|
||||
# Test whether backtrace is available
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <execinfo.h>
|
||||
int main() {
|
||||
void* frames[1];
|
||||
@@ -518,7 +518,7 @@ EOF
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_BACKTRACE"
|
||||
else
|
||||
# Test whether execinfo library is installed
|
||||
$CXX $CFLAGS -lexecinfo -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -lexecinfo -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <execinfo.h>
|
||||
int main() {
|
||||
void* frames[1];
|
||||
@@ -535,7 +535,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_PG; then
|
||||
# Test if -pg is supported
|
||||
$CXX $CFLAGS -pg -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -pg -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
int main() {
|
||||
return 0;
|
||||
}
|
||||
@@ -547,7 +547,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_SYNC_FILE_RANGE; then
|
||||
# Test whether sync_file_range is supported for compatibility with an old glibc
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <fcntl.h>
|
||||
int main() {
|
||||
int fd = open("/dev/null", 0);
|
||||
@@ -561,7 +561,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_SCHED_GETCPU; then
|
||||
# Test whether sched_getcpu is supported
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <sched.h>
|
||||
int main() {
|
||||
int cpuid = sched_getcpu();
|
||||
@@ -575,7 +575,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_AUXV_GETAUXVAL; then
|
||||
# Test whether getauxval is supported
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <sys/auxv.h>
|
||||
int main() {
|
||||
uint64_t auxv = getauxval(AT_HWCAP);
|
||||
@@ -603,7 +603,7 @@ fi
|
||||
# -Wshorten-64-to-32 breaks compilation on FreeBSD i386
|
||||
if ! [ "$TARGET_OS" = FreeBSD -a "$TARGET_ARCHITECTURE" = i386 ]; then
|
||||
# Test whether -Wshorten-64-to-32 is available
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null -Wshorten-64-to-32 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null -Wshorten-64-to-32 2>/dev/null <<EOF
|
||||
int main() {}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
@@ -668,6 +668,23 @@ else
|
||||
fi
|
||||
fi
|
||||
|
||||
if test -n "`echo $TARGET_ARCHITECTURE | grep ^ppc64`"; then
|
||||
# check for GNU libc on ppc64
|
||||
$CXX -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <gnu/libc-version.h>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
printf("GNU libc version: %s\n", gnu_get_libc_version());
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
if [ "$?" != 0 ]; then
|
||||
PPC_LIBC_IS_GNU=0
|
||||
fi
|
||||
fi
|
||||
|
||||
if test "$TRY_SSE_ETC"; then
|
||||
# The USE_SSE flag now means "attempt to compile with widely-available
|
||||
# Intel architecture extensions utilized by specific optimizations in the
|
||||
@@ -861,3 +878,6 @@ echo "LUA_PATH=$LUA_PATH" >> "$OUTPUT"
|
||||
if test -n "$USE_FOLLY_DISTRIBUTED_MUTEX"; then
|
||||
echo "USE_FOLLY_DISTRIBUTED_MUTEX=$USE_FOLLY_DISTRIBUTED_MUTEX" >> "$OUTPUT"
|
||||
fi
|
||||
if test -n "$PPC_LIBC_IS_GNU"; then
|
||||
echo "PPC_LIBC_IS_GNU=$PPC_LIBC_IS_GNU" >> "$OUTPUT"
|
||||
fi
|
||||
@@ -136,9 +136,11 @@ then
|
||||
FORMAT_UPSTREAM_MERGE_BASE="$(git merge-base "$FORMAT_UPSTREAM" HEAD)"
|
||||
# Get the differences
|
||||
diffs=$(git diff -U0 "$FORMAT_UPSTREAM_MERGE_BASE" | $CLANG_FORMAT_DIFF -p 1)
|
||||
echo "Checking format of changes not yet in $FORMAT_UPSTREAM..."
|
||||
else
|
||||
# Check the format of uncommitted lines,
|
||||
diffs=$(git diff -U0 HEAD | $CLANG_FORMAT_DIFF -p 1)
|
||||
echo "Checking format of uncommitted changes..."
|
||||
fi
|
||||
|
||||
if [ -z "$diffs" ]
|
||||
|
||||
@@ -548,6 +548,36 @@ STRESS_CRASH_TEST_WITH_TXN_COMMANDS="[
|
||||
}
|
||||
]"
|
||||
|
||||
#
|
||||
# RocksDB stress/crash test with timestamp
|
||||
#
|
||||
STRESS_CRASH_TEST_WITH_TS_COMMANDS="[
|
||||
{
|
||||
'name':'Rocksdb Stress and Crash Test with ts',
|
||||
'oncall':'$ONCALL',
|
||||
'executeLocal': 'true',
|
||||
'timeout': 86400,
|
||||
'steps': [
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build and run RocksDB debug stress tests',
|
||||
'shell':'cd $WORKING_DIR; $SHM $DEBUG $NON_TSAN_CRASH make J=1 db_stress || $CONTRUN_NAME=db_stress $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
{
|
||||
'name':'Build and run RocksDB debug crash tests with ts',
|
||||
'timeout': 86400,
|
||||
'shell':'cd $WORKING_DIR; $SHM $DEBUG $NON_TSAN_CRASH make J=1 crash_test_with_ts || $CONTRUN_NAME=crash_test_with_ts $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
$UPLOAD_DB_DIR,
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
]"
|
||||
|
||||
# RocksDB write stress test.
|
||||
# We run on disk device on purpose (i.e. no $SHM)
|
||||
# because we want to add some randomness to fsync commands
|
||||
@@ -1220,6 +1250,9 @@ case $1 in
|
||||
stress_crash_with_txn)
|
||||
echo $STRESS_CRASH_TEST_WITH_TXN_COMMANDS
|
||||
;;
|
||||
stress_crash_with_ts)
|
||||
echo $STRESS_CRASH_TEST_WITH_TS_COMMANDS
|
||||
;;
|
||||
write_stress)
|
||||
echo $WRITE_STRESS_COMMANDS
|
||||
;;
|
||||
|
||||
@@ -68,7 +68,7 @@ $BinariesFolder = -Join($RootFolder, "\build\Debug\")
|
||||
|
||||
if($WorkFolder -eq "") {
|
||||
|
||||
# If TEST_TMPDIR is set use it
|
||||
# If TEST_TMPDIR is set use it
|
||||
[string]$var = $Env:TEST_TMPDIR
|
||||
if($var -eq "") {
|
||||
$WorkFolder = -Join($RootFolder, "\db_tests\")
|
||||
@@ -93,7 +93,7 @@ $ExcludeCasesSet = New-Object System.Collections.Generic.HashSet[string]
|
||||
if($ExcludeCases -ne "") {
|
||||
Write-Host "ExcludeCases: $ExcludeCases"
|
||||
$l = $ExcludeCases -split ' '
|
||||
ForEach($t in $l) {
|
||||
ForEach($t in $l) {
|
||||
$ExcludeCasesSet.Add($t) | Out-Null
|
||||
}
|
||||
}
|
||||
@@ -102,7 +102,7 @@ $ExcludeExesSet = New-Object System.Collections.Generic.HashSet[string]
|
||||
if($ExcludeExes -ne "") {
|
||||
Write-Host "ExcludeExe: $ExcludeExes"
|
||||
$l = $ExcludeExes -split ' '
|
||||
ForEach($t in $l) {
|
||||
ForEach($t in $l) {
|
||||
$ExcludeExesSet.Add($t) | Out-Null
|
||||
}
|
||||
}
|
||||
@@ -118,6 +118,10 @@ if($ExcludeExes -ne "") {
|
||||
# MultiThreaded/MultiThreadedDBTest.
|
||||
# MultiThreaded/0 # GetParam() = 0
|
||||
# MultiThreaded/1 # GetParam() = 1
|
||||
# RibbonTypeParamTest/0. # TypeParam = struct DefaultTypesAndSettings
|
||||
# CompactnessAndBacktrackAndFpRate
|
||||
# Extremes
|
||||
# FindOccupancyForSuccessRate
|
||||
#
|
||||
# into this:
|
||||
#
|
||||
@@ -125,6 +129,9 @@ if($ExcludeExes -ne "") {
|
||||
# DBTest.WriteEmptyBatch
|
||||
# MultiThreaded/MultiThreadedDBTest.MultiThreaded/0
|
||||
# MultiThreaded/MultiThreadedDBTest.MultiThreaded/1
|
||||
# RibbonTypeParamTest/0.CompactnessAndBacktrackAndFpRate
|
||||
# RibbonTypeParamTest/0.Extremes
|
||||
# RibbonTypeParamTest/0.FindOccupancyForSuccessRate
|
||||
#
|
||||
# Output into the parameter in a form TestName -> Log File Name
|
||||
function ExtractTestCases([string]$GTestExe, $HashTable) {
|
||||
@@ -138,6 +145,8 @@ function ExtractTestCases([string]$GTestExe, $HashTable) {
|
||||
|
||||
ForEach( $l in $Tests) {
|
||||
|
||||
# remove trailing comment if any
|
||||
$l = $l -replace '\s+\#.*',''
|
||||
# Leading whitespace is fine
|
||||
$l = $l -replace '^\s+',''
|
||||
# Trailing dot is a test group but no whitespace
|
||||
@@ -146,8 +155,7 @@ function ExtractTestCases([string]$GTestExe, $HashTable) {
|
||||
} else {
|
||||
# Otherwise it is a test name, remove leading space
|
||||
$test = $l
|
||||
# remove trailing comment if any and create a log name
|
||||
$test = $test -replace '\s+\#.*',''
|
||||
# create a log name
|
||||
$test = "$Group$test"
|
||||
|
||||
if($ExcludeCasesSet.Contains($test)) {
|
||||
@@ -253,7 +261,7 @@ if($Run -ne "") {
|
||||
|
||||
$DiscoveredExe = @()
|
||||
dir -Path $search_path | ForEach-Object {
|
||||
$DiscoveredExe += ($_.Name)
|
||||
$DiscoveredExe += ($_.Name)
|
||||
}
|
||||
|
||||
# Remove exclusions
|
||||
@@ -293,7 +301,7 @@ if($SuiteRun -ne "") {
|
||||
|
||||
$ListOfExe = @()
|
||||
dir -Path $search_path | ForEach-Object {
|
||||
$ListOfExe += ($_.Name)
|
||||
$ListOfExe += ($_.Name)
|
||||
}
|
||||
|
||||
# Exclude those in RunOnly from running as suites
|
||||
@@ -348,7 +356,7 @@ function RunJobs($Suites, $TestCmds, [int]$ConcurrencyVal)
|
||||
|
||||
# Wait for all to finish and get the results
|
||||
while(($JobToLog.Count -gt 0) -or
|
||||
($TestCmds.Count -gt 0) -or
|
||||
($TestCmds.Count -gt 0) -or
|
||||
($Suites.Count -gt 0)) {
|
||||
|
||||
# Make sure we have maximum concurrent jobs running if anything
|
||||
@@ -468,8 +476,8 @@ RunJobs -Suites $CasesToRun -TestCmds $TestExes -ConcurrencyVal $Concurrency
|
||||
|
||||
$EndDate = (Get-Date)
|
||||
|
||||
New-TimeSpan -Start $StartDate -End $EndDate |
|
||||
ForEach-Object {
|
||||
New-TimeSpan -Start $StartDate -End $EndDate |
|
||||
ForEach-Object {
|
||||
"Elapsed time: {0:g}" -f $_
|
||||
}
|
||||
|
||||
@@ -484,4 +492,4 @@ if(!$script:success) {
|
||||
|
||||
exit 0
|
||||
|
||||
|
||||
|
||||
Vendored
+7
-370
@@ -1,7 +1,11 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// Copyright (c) 2013-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).
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#ifndef GFLAGS
|
||||
#include <cstdio>
|
||||
@@ -10,375 +14,8 @@ int main() {
|
||||
return 1;
|
||||
}
|
||||
#else
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <cinttypes>
|
||||
#include <limits>
|
||||
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/system_clock.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/gflags_compat.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/random.h"
|
||||
|
||||
using GFLAGS_NAMESPACE::ParseCommandLineFlags;
|
||||
|
||||
static constexpr uint32_t KiB = uint32_t{1} << 10;
|
||||
static constexpr uint32_t MiB = KiB << 10;
|
||||
static constexpr uint64_t GiB = MiB << 10;
|
||||
|
||||
DEFINE_uint32(threads, 16, "Number of concurrent threads to run.");
|
||||
DEFINE_uint64(cache_size, 1 * GiB,
|
||||
"Number of bytes to use as a cache of uncompressed data.");
|
||||
DEFINE_uint32(num_shard_bits, 6, "shard_bits.");
|
||||
|
||||
DEFINE_double(resident_ratio, 0.25,
|
||||
"Ratio of keys fitting in cache to keyspace.");
|
||||
DEFINE_uint64(ops_per_thread, 0,
|
||||
"Number of operations per thread. (Default: 5 * keyspace size)");
|
||||
DEFINE_uint32(value_bytes, 8 * KiB, "Size of each value added.");
|
||||
|
||||
DEFINE_uint32(skew, 5, "Degree of skew in key selection");
|
||||
DEFINE_bool(populate_cache, true, "Populate cache before operations");
|
||||
|
||||
DEFINE_uint32(lookup_insert_percent, 87,
|
||||
"Ratio of lookup (+ insert on not found) to total workload "
|
||||
"(expressed as a percentage)");
|
||||
DEFINE_uint32(insert_percent, 2,
|
||||
"Ratio of insert to total workload (expressed as a percentage)");
|
||||
DEFINE_uint32(lookup_percent, 10,
|
||||
"Ratio of lookup to total workload (expressed as a percentage)");
|
||||
DEFINE_uint32(erase_percent, 1,
|
||||
"Ratio of erase to total workload (expressed as a percentage)");
|
||||
|
||||
DEFINE_bool(use_clock_cache, false, "");
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class CacheBench;
|
||||
namespace {
|
||||
// State shared by all concurrent executions of the same benchmark.
|
||||
class SharedState {
|
||||
public:
|
||||
explicit SharedState(CacheBench* cache_bench)
|
||||
: cv_(&mu_),
|
||||
num_initialized_(0),
|
||||
start_(false),
|
||||
num_done_(0),
|
||||
cache_bench_(cache_bench) {}
|
||||
|
||||
~SharedState() {}
|
||||
|
||||
port::Mutex* GetMutex() {
|
||||
return &mu_;
|
||||
}
|
||||
|
||||
port::CondVar* GetCondVar() {
|
||||
return &cv_;
|
||||
}
|
||||
|
||||
CacheBench* GetCacheBench() const {
|
||||
return cache_bench_;
|
||||
}
|
||||
|
||||
void IncInitialized() {
|
||||
num_initialized_++;
|
||||
}
|
||||
|
||||
void IncDone() {
|
||||
num_done_++;
|
||||
}
|
||||
|
||||
bool AllInitialized() const { return num_initialized_ >= FLAGS_threads; }
|
||||
|
||||
bool AllDone() const { return num_done_ >= FLAGS_threads; }
|
||||
|
||||
void SetStart() {
|
||||
start_ = true;
|
||||
}
|
||||
|
||||
bool Started() const {
|
||||
return start_;
|
||||
}
|
||||
|
||||
private:
|
||||
port::Mutex mu_;
|
||||
port::CondVar cv_;
|
||||
|
||||
uint64_t num_initialized_;
|
||||
bool start_;
|
||||
uint64_t num_done_;
|
||||
|
||||
CacheBench* cache_bench_;
|
||||
};
|
||||
|
||||
// Per-thread state for concurrent executions of the same benchmark.
|
||||
struct ThreadState {
|
||||
uint32_t tid;
|
||||
Random64 rnd;
|
||||
SharedState* shared;
|
||||
|
||||
ThreadState(uint32_t index, SharedState* _shared)
|
||||
: tid(index), rnd(1000 + index), shared(_shared) {}
|
||||
};
|
||||
|
||||
struct KeyGen {
|
||||
char key_data[27];
|
||||
|
||||
Slice GetRand(Random64& rnd, uint64_t max_key) {
|
||||
uint64_t raw = rnd.Next();
|
||||
// Skew according to setting
|
||||
for (uint32_t i = 0; i < FLAGS_skew; ++i) {
|
||||
raw = std::min(raw, rnd.Next());
|
||||
}
|
||||
uint64_t key = FastRange64(raw, max_key);
|
||||
// Variable size and alignment
|
||||
size_t off = key % 8;
|
||||
key_data[0] = char{42};
|
||||
EncodeFixed64(key_data + 1, key);
|
||||
key_data[9] = char{11};
|
||||
EncodeFixed64(key_data + 10, key);
|
||||
key_data[18] = char{4};
|
||||
EncodeFixed64(key_data + 19, key);
|
||||
return Slice(&key_data[off], sizeof(key_data) - off);
|
||||
}
|
||||
};
|
||||
|
||||
char* createValue(Random64& rnd) {
|
||||
char* rv = new char[FLAGS_value_bytes];
|
||||
// Fill with some filler data, and take some CPU time
|
||||
for (uint32_t i = 0; i < FLAGS_value_bytes; i += 8) {
|
||||
EncodeFixed64(rv + i, rnd.Next());
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
void deleter(const Slice& /*key*/, void* value) {
|
||||
delete[] static_cast<char*>(value);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
class CacheBench {
|
||||
static constexpr uint64_t kHundredthUint64 =
|
||||
std::numeric_limits<uint64_t>::max() / 100U;
|
||||
|
||||
public:
|
||||
CacheBench()
|
||||
: max_key_(static_cast<uint64_t>(FLAGS_cache_size / FLAGS_resident_ratio /
|
||||
FLAGS_value_bytes)),
|
||||
lookup_insert_threshold_(kHundredthUint64 *
|
||||
FLAGS_lookup_insert_percent),
|
||||
insert_threshold_(lookup_insert_threshold_ +
|
||||
kHundredthUint64 * FLAGS_insert_percent),
|
||||
lookup_threshold_(insert_threshold_ +
|
||||
kHundredthUint64 * FLAGS_lookup_percent),
|
||||
erase_threshold_(lookup_threshold_ +
|
||||
kHundredthUint64 * FLAGS_erase_percent) {
|
||||
if (erase_threshold_ != 100U * kHundredthUint64) {
|
||||
fprintf(stderr, "Percentages must add to 100.\n");
|
||||
exit(1);
|
||||
}
|
||||
if (FLAGS_use_clock_cache) {
|
||||
cache_ = NewClockCache(FLAGS_cache_size, FLAGS_num_shard_bits);
|
||||
if (!cache_) {
|
||||
fprintf(stderr, "Clock cache not supported.\n");
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
cache_ = NewLRUCache(FLAGS_cache_size, FLAGS_num_shard_bits);
|
||||
}
|
||||
if (FLAGS_ops_per_thread == 0) {
|
||||
FLAGS_ops_per_thread = 5 * max_key_;
|
||||
}
|
||||
}
|
||||
|
||||
~CacheBench() {}
|
||||
|
||||
void PopulateCache() {
|
||||
Random64 rnd(1);
|
||||
KeyGen keygen;
|
||||
for (uint64_t i = 0; i < 2 * FLAGS_cache_size; i += FLAGS_value_bytes) {
|
||||
cache_->Insert(keygen.GetRand(rnd, max_key_), createValue(rnd),
|
||||
FLAGS_value_bytes, &deleter);
|
||||
}
|
||||
}
|
||||
|
||||
bool Run() {
|
||||
ROCKSDB_NAMESPACE::Env* env = ROCKSDB_NAMESPACE::Env::Default();
|
||||
const auto& clock = env->GetSystemClock();
|
||||
|
||||
PrintEnv();
|
||||
SharedState shared(this);
|
||||
std::vector<std::unique_ptr<ThreadState> > threads(FLAGS_threads);
|
||||
for (uint32_t i = 0; i < FLAGS_threads; i++) {
|
||||
threads[i].reset(new ThreadState(i, &shared));
|
||||
env->StartThread(ThreadBody, threads[i].get());
|
||||
}
|
||||
{
|
||||
MutexLock l(shared.GetMutex());
|
||||
while (!shared.AllInitialized()) {
|
||||
shared.GetCondVar()->Wait();
|
||||
}
|
||||
// Record start time
|
||||
uint64_t start_time = clock->NowMicros();
|
||||
|
||||
// Start all threads
|
||||
shared.SetStart();
|
||||
shared.GetCondVar()->SignalAll();
|
||||
|
||||
// Wait threads to complete
|
||||
while (!shared.AllDone()) {
|
||||
shared.GetCondVar()->Wait();
|
||||
}
|
||||
|
||||
// Record end time
|
||||
uint64_t end_time = clock->NowMicros();
|
||||
double elapsed = static_cast<double>(end_time - start_time) * 1e-6;
|
||||
uint32_t qps = static_cast<uint32_t>(
|
||||
static_cast<double>(FLAGS_threads * FLAGS_ops_per_thread) / elapsed);
|
||||
fprintf(stdout, "Complete in %.3f s; QPS = %u\n", elapsed, qps);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Cache> cache_;
|
||||
const uint64_t max_key_;
|
||||
// Cumulative thresholds in the space of a random uint64_t
|
||||
const uint64_t lookup_insert_threshold_;
|
||||
const uint64_t insert_threshold_;
|
||||
const uint64_t lookup_threshold_;
|
||||
const uint64_t erase_threshold_;
|
||||
|
||||
static void ThreadBody(void* v) {
|
||||
ThreadState* thread = static_cast<ThreadState*>(v);
|
||||
SharedState* shared = thread->shared;
|
||||
|
||||
{
|
||||
MutexLock l(shared->GetMutex());
|
||||
shared->IncInitialized();
|
||||
if (shared->AllInitialized()) {
|
||||
shared->GetCondVar()->SignalAll();
|
||||
}
|
||||
while (!shared->Started()) {
|
||||
shared->GetCondVar()->Wait();
|
||||
}
|
||||
}
|
||||
thread->shared->GetCacheBench()->OperateCache(thread);
|
||||
|
||||
{
|
||||
MutexLock l(shared->GetMutex());
|
||||
shared->IncDone();
|
||||
if (shared->AllDone()) {
|
||||
shared->GetCondVar()->SignalAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OperateCache(ThreadState* thread) {
|
||||
// To use looked-up values
|
||||
uint64_t result = 0;
|
||||
// To hold handles for a non-trivial amount of time
|
||||
Cache::Handle* handle = nullptr;
|
||||
KeyGen gen;
|
||||
for (uint64_t i = 0; i < FLAGS_ops_per_thread; i++) {
|
||||
Slice key = gen.GetRand(thread->rnd, max_key_);
|
||||
uint64_t random_op = thread->rnd.Next();
|
||||
if (random_op < lookup_insert_threshold_) {
|
||||
if (handle) {
|
||||
cache_->Release(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
// do lookup
|
||||
handle = cache_->Lookup(key);
|
||||
if (handle) {
|
||||
// do something with the data
|
||||
result += NPHash64(static_cast<char*>(cache_->Value(handle)),
|
||||
FLAGS_value_bytes);
|
||||
} else {
|
||||
// do insert
|
||||
cache_->Insert(key, createValue(thread->rnd), FLAGS_value_bytes,
|
||||
&deleter, &handle);
|
||||
}
|
||||
} else if (random_op < insert_threshold_) {
|
||||
if (handle) {
|
||||
cache_->Release(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
// do insert
|
||||
cache_->Insert(key, createValue(thread->rnd), FLAGS_value_bytes,
|
||||
&deleter, &handle);
|
||||
} else if (random_op < lookup_threshold_) {
|
||||
if (handle) {
|
||||
cache_->Release(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
// do lookup
|
||||
handle = cache_->Lookup(key);
|
||||
if (handle) {
|
||||
// do something with the data
|
||||
result += NPHash64(static_cast<char*>(cache_->Value(handle)),
|
||||
FLAGS_value_bytes);
|
||||
}
|
||||
} else if (random_op < erase_threshold_) {
|
||||
// do erase
|
||||
cache_->Erase(key);
|
||||
} else {
|
||||
// Should be extremely unlikely (noop)
|
||||
assert(random_op >= kHundredthUint64 * 100U);
|
||||
}
|
||||
}
|
||||
if (handle) {
|
||||
cache_->Release(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void PrintEnv() const {
|
||||
printf("RocksDB version : %d.%d\n", kMajorVersion, kMinorVersion);
|
||||
printf("Number of threads : %u\n", FLAGS_threads);
|
||||
printf("Ops per thread : %" PRIu64 "\n", FLAGS_ops_per_thread);
|
||||
printf("Cache size : %" PRIu64 "\n", FLAGS_cache_size);
|
||||
printf("Num shard bits : %u\n", FLAGS_num_shard_bits);
|
||||
printf("Max key : %" PRIu64 "\n", max_key_);
|
||||
printf("Resident ratio : %g\n", FLAGS_resident_ratio);
|
||||
printf("Skew degree : %u\n", FLAGS_skew);
|
||||
printf("Populate cache : %d\n", int{FLAGS_populate_cache});
|
||||
printf("Lookup+Insert pct : %u%%\n", FLAGS_lookup_insert_percent);
|
||||
printf("Insert percentage : %u%%\n", FLAGS_insert_percent);
|
||||
printf("Lookup percentage : %u%%\n", FLAGS_lookup_percent);
|
||||
printf("Erase percentage : %u%%\n", FLAGS_erase_percent);
|
||||
printf("----------------------------\n");
|
||||
}
|
||||
};
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#include <rocksdb/cache_bench_tool.h>
|
||||
int main(int argc, char** argv) {
|
||||
ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
if (FLAGS_threads <= 0) {
|
||||
fprintf(stderr, "threads number <= 0\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::CacheBench bench;
|
||||
if (FLAGS_populate_cache) {
|
||||
bench.PopulateCache();
|
||||
printf("Population complete\n");
|
||||
printf("----------------------------\n");
|
||||
}
|
||||
if (bench.Run()) {
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
return ROCKSDB_NAMESPACE::cache_bench_tool(argc, argv);
|
||||
}
|
||||
|
||||
#endif // GFLAGS
|
||||
Vendored
+504
@@ -0,0 +1,504 @@
|
||||
// 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).
|
||||
|
||||
#ifdef GFLAGS
|
||||
#include "rocksdb/cache_bench_tool.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <cinttypes>
|
||||
#include <cstdio>
|
||||
#include <limits>
|
||||
|
||||
#include "options/configurable_helper.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/system_clock.h"
|
||||
#include "rocksdb/tiered_cache.h"
|
||||
#include "rocksdb/utilities/object_registry.h"
|
||||
#include "rocksdb/utilities/options_type.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/gflags_compat.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/random.h"
|
||||
#include "util/stop_watch.h"
|
||||
|
||||
using GFLAGS_NAMESPACE::ParseCommandLineFlags;
|
||||
|
||||
static constexpr uint32_t KiB = uint32_t{1} << 10;
|
||||
static constexpr uint32_t MiB = KiB << 10;
|
||||
static constexpr uint64_t GiB = MiB << 10;
|
||||
|
||||
DEFINE_uint32(threads, 16, "Number of concurrent threads to run.");
|
||||
DEFINE_uint64(cache_size, 1 * GiB,
|
||||
"Number of bytes to use as a cache of uncompressed data.");
|
||||
DEFINE_uint32(num_shard_bits, 6, "shard_bits.");
|
||||
|
||||
DEFINE_double(resident_ratio, 0.25,
|
||||
"Ratio of keys fitting in cache to keyspace.");
|
||||
DEFINE_uint64(ops_per_thread, 0,
|
||||
"Number of operations per thread. (Default: 5 * keyspace size)");
|
||||
DEFINE_uint32(value_bytes, 8 * KiB, "Size of each value added.");
|
||||
|
||||
DEFINE_uint32(skew, 5, "Degree of skew in key selection");
|
||||
DEFINE_bool(populate_cache, true, "Populate cache before operations");
|
||||
|
||||
DEFINE_uint32(lookup_insert_percent, 87,
|
||||
"Ratio of lookup (+ insert on not found) to total workload "
|
||||
"(expressed as a percentage)");
|
||||
DEFINE_uint32(insert_percent, 2,
|
||||
"Ratio of insert to total workload (expressed as a percentage)");
|
||||
DEFINE_uint32(lookup_percent, 10,
|
||||
"Ratio of lookup to total workload (expressed as a percentage)");
|
||||
DEFINE_uint32(erase_percent, 1,
|
||||
"Ratio of erase to total workload (expressed as a percentage)");
|
||||
|
||||
DEFINE_bool(use_clock_cache, false, "");
|
||||
|
||||
DEFINE_bool(skewed, false, "If true, skew the key access distribution");
|
||||
#ifndef ROCKSDB_LITE
|
||||
DEFINE_string(tiered_cache_uri, "",
|
||||
"Full URI for creating a custom NVM cache object");
|
||||
static class std::shared_ptr<ROCKSDB_NAMESPACE::TieredCache> tiered_cache;
|
||||
#endif
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
class CacheBench;
|
||||
|
||||
namespace {
|
||||
// State shared by all concurrent executions of the same benchmark.
|
||||
class SharedState {
|
||||
public:
|
||||
explicit SharedState(CacheBench* cache_bench)
|
||||
: cv_(&mu_),
|
||||
num_initialized_(0),
|
||||
start_(false),
|
||||
num_done_(0),
|
||||
cache_bench_(cache_bench) {}
|
||||
|
||||
~SharedState() {}
|
||||
|
||||
port::Mutex* GetMutex() { return &mu_; }
|
||||
|
||||
port::CondVar* GetCondVar() { return &cv_; }
|
||||
|
||||
CacheBench* GetCacheBench() const { return cache_bench_; }
|
||||
|
||||
void IncInitialized() { num_initialized_++; }
|
||||
|
||||
void IncDone() { num_done_++; }
|
||||
|
||||
bool AllInitialized() const { return num_initialized_ >= FLAGS_threads; }
|
||||
|
||||
bool AllDone() const { return num_done_ >= FLAGS_threads; }
|
||||
|
||||
void SetStart() { start_ = true; }
|
||||
|
||||
bool Started() const { return start_; }
|
||||
|
||||
private:
|
||||
port::Mutex mu_;
|
||||
port::CondVar cv_;
|
||||
|
||||
uint64_t num_initialized_;
|
||||
bool start_;
|
||||
uint64_t num_done_;
|
||||
|
||||
CacheBench* cache_bench_;
|
||||
};
|
||||
|
||||
class Stats {
|
||||
private:
|
||||
uint64_t hits;
|
||||
uint64_t misses;
|
||||
uint64_t lookup_us;
|
||||
uint64_t insert_us;
|
||||
|
||||
public:
|
||||
Stats() : hits(0), misses(0), lookup_us(0), insert_us(0) {}
|
||||
|
||||
void AddHits(uint64_t inc) { hits += inc; }
|
||||
void AddMisses(uint64_t inc) { misses += inc; }
|
||||
void AddLookupUs(uint64_t lookup) { lookup_us += lookup; }
|
||||
void AddInsertUs(uint64_t insert) { insert_us += insert; }
|
||||
void Merge(const Stats& other) {
|
||||
hits += other.hits;
|
||||
misses += other.misses;
|
||||
lookup_us += other.lookup_us;
|
||||
insert_us += other.insert_us;
|
||||
}
|
||||
void Report() {
|
||||
fprintf(stdout, "%llu hits, %llu misses, %llu lookup_us, %llu insert_us\n",
|
||||
(unsigned long long)hits, (unsigned long long)misses,
|
||||
(unsigned long long)lookup_us, (unsigned long long)insert_us);
|
||||
fflush(stdout);
|
||||
}
|
||||
};
|
||||
|
||||
// Per-thread state for concurrent executions of the same benchmark.
|
||||
struct ThreadState {
|
||||
uint32_t tid;
|
||||
Random64 rnd;
|
||||
Stats stats;
|
||||
SharedState* shared;
|
||||
|
||||
ThreadState(uint32_t index, SharedState* _shared)
|
||||
: tid(index), rnd(1000 + index), shared(_shared) {}
|
||||
};
|
||||
|
||||
struct KeyGen {
|
||||
char key_data[27];
|
||||
|
||||
Slice GetRand(Random64& rnd, uint64_t max_key, int max_log) {
|
||||
uint64_t key = 0;
|
||||
if (!FLAGS_skewed) {
|
||||
uint64_t raw = rnd.Next();
|
||||
// Skew according to setting
|
||||
for (uint32_t i = 0; i < FLAGS_skew; ++i) {
|
||||
raw = std::min(raw, rnd.Next());
|
||||
}
|
||||
key = FastRange64(raw, max_key);
|
||||
} else {
|
||||
key = rnd.Skewed(max_log);
|
||||
if (key > max_key) {
|
||||
key -= max_key;
|
||||
}
|
||||
}
|
||||
// Variable size and alignment
|
||||
size_t off = key % 8;
|
||||
key_data[0] = char{42};
|
||||
EncodeFixed64(key_data + 1, key);
|
||||
key_data[9] = char{11};
|
||||
EncodeFixed64(key_data + 10, key);
|
||||
key_data[18] = char{4};
|
||||
EncodeFixed64(key_data + 19, key);
|
||||
return Slice(&key_data[off], sizeof(key_data) - off);
|
||||
}
|
||||
};
|
||||
|
||||
char* createValue(Random64& rnd) {
|
||||
char* rv = new char[FLAGS_value_bytes];
|
||||
// Fill with some filler data, and take some CPU time
|
||||
for (uint32_t i = 0; i < FLAGS_value_bytes; i += 8) {
|
||||
EncodeFixed64(rv + i, rnd.Next());
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
void helperCallback(Cache::SizeCallback* size_cb,
|
||||
Cache::SaveToCallback* save_cb,
|
||||
Cache::DeletionCallback* del_cb) {
|
||||
if (size_cb) {
|
||||
*size_cb = [](void* /*obj*/) -> size_t { return FLAGS_value_bytes; };
|
||||
}
|
||||
if (save_cb) {
|
||||
*save_cb = [](void* obj, size_t /*offset*/, size_t size,
|
||||
void* out) -> Status {
|
||||
memcpy(out, obj, size);
|
||||
return Status::OK();
|
||||
};
|
||||
}
|
||||
if (del_cb) {
|
||||
*del_cb = [](const Slice& /*key*/, void* obj) -> void {
|
||||
delete[] static_cast<char*>(obj);
|
||||
};
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
class CacheBench {
|
||||
static constexpr uint64_t kHundredthUint64 =
|
||||
std::numeric_limits<uint64_t>::max() / 100U;
|
||||
|
||||
public:
|
||||
CacheBench()
|
||||
: max_key_(static_cast<uint64_t>(FLAGS_cache_size / FLAGS_resident_ratio /
|
||||
FLAGS_value_bytes)),
|
||||
lookup_insert_threshold_(kHundredthUint64 *
|
||||
FLAGS_lookup_insert_percent),
|
||||
insert_threshold_(lookup_insert_threshold_ +
|
||||
kHundredthUint64 * FLAGS_insert_percent),
|
||||
lookup_threshold_(insert_threshold_ +
|
||||
kHundredthUint64 * FLAGS_lookup_percent),
|
||||
erase_threshold_(lookup_threshold_ +
|
||||
kHundredthUint64 * FLAGS_erase_percent),
|
||||
skewed_(FLAGS_skewed) {
|
||||
if (erase_threshold_ != 100U * kHundredthUint64) {
|
||||
fprintf(stderr, "Percentages must add to 100.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
max_log_ = 0;
|
||||
if (skewed_) {
|
||||
uint64_t max_key = max_key_;
|
||||
while (max_key >>= 1) max_log_++;
|
||||
if (max_key > (1u << max_log_)) max_log_++;
|
||||
}
|
||||
|
||||
if (FLAGS_use_clock_cache) {
|
||||
cache_ = NewClockCache(FLAGS_cache_size, FLAGS_num_shard_bits);
|
||||
if (!cache_) {
|
||||
fprintf(stderr, "Clock cache not supported.\n");
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
LRUCacheOptions opts(FLAGS_cache_size, FLAGS_num_shard_bits, false, 0.5);
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (!FLAGS_tiered_cache_uri.empty()) {
|
||||
Status s = ObjectRegistry::NewInstance()->NewSharedObject<TieredCache>(
|
||||
FLAGS_tiered_cache_uri, &tiered_cache);
|
||||
if (tiered_cache == nullptr) {
|
||||
fprintf(stderr,
|
||||
"No tiered cache registered matching string: %s status=%s\n",
|
||||
FLAGS_tiered_cache_uri.c_str(), s.ToString().c_str());
|
||||
exit(1);
|
||||
}
|
||||
opts.tiered_cache = tiered_cache;
|
||||
}
|
||||
#endif
|
||||
|
||||
cache_ = NewLRUCache(opts);
|
||||
}
|
||||
if (FLAGS_ops_per_thread == 0) {
|
||||
FLAGS_ops_per_thread = 5 * max_key_;
|
||||
}
|
||||
}
|
||||
|
||||
~CacheBench() {}
|
||||
|
||||
void PopulateCache() {
|
||||
Random64 rnd(1);
|
||||
KeyGen keygen;
|
||||
for (uint64_t i = 0; i < 10 * FLAGS_cache_size; i += FLAGS_value_bytes) {
|
||||
cache_->Insert(keygen.GetRand(rnd, max_key_, max_log_), createValue(rnd),
|
||||
helperCallback, FLAGS_value_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
bool Run() {
|
||||
ROCKSDB_NAMESPACE::Env* env = ROCKSDB_NAMESPACE::Env::Default();
|
||||
const auto& clock = env->GetSystemClock();
|
||||
|
||||
PrintEnv();
|
||||
SharedState shared(this);
|
||||
std::vector<std::unique_ptr<ThreadState> > threads(FLAGS_threads);
|
||||
for (uint32_t i = 0; i < FLAGS_threads; i++) {
|
||||
threads[i].reset(new ThreadState(i, &shared));
|
||||
env->StartThread(ThreadBody, threads[i].get());
|
||||
}
|
||||
{
|
||||
MutexLock l(shared.GetMutex());
|
||||
while (!shared.AllInitialized()) {
|
||||
shared.GetCondVar()->Wait();
|
||||
}
|
||||
// Record start time
|
||||
uint64_t start_time = clock->NowMicros();
|
||||
|
||||
// Start all threads
|
||||
shared.SetStart();
|
||||
shared.GetCondVar()->SignalAll();
|
||||
|
||||
// Wait threads to complete
|
||||
while (!shared.AllDone()) {
|
||||
shared.GetCondVar()->Wait();
|
||||
}
|
||||
|
||||
// Record end time
|
||||
uint64_t end_time = clock->NowMicros();
|
||||
double elapsed = static_cast<double>(end_time - start_time) * 1e-6;
|
||||
uint32_t qps = static_cast<uint32_t>(
|
||||
static_cast<double>(FLAGS_threads * FLAGS_ops_per_thread) / elapsed);
|
||||
fprintf(stdout, "Complete in %.3f s; QPS = %u\n", elapsed, qps);
|
||||
|
||||
Stats merge_stats;
|
||||
for (uint32_t i = 0; i < FLAGS_threads; ++i) {
|
||||
merge_stats.Merge(threads[i]->stats);
|
||||
}
|
||||
merge_stats.Report();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Cache> cache_;
|
||||
const uint64_t max_key_;
|
||||
// Cumulative thresholds in the space of a random uint64_t
|
||||
const uint64_t lookup_insert_threshold_;
|
||||
const uint64_t insert_threshold_;
|
||||
const uint64_t lookup_threshold_;
|
||||
const uint64_t erase_threshold_;
|
||||
const bool skewed_;
|
||||
int max_log_;
|
||||
|
||||
static void ThreadBody(void* v) {
|
||||
ThreadState* thread = static_cast<ThreadState*>(v);
|
||||
SharedState* shared = thread->shared;
|
||||
|
||||
{
|
||||
MutexLock l(shared->GetMutex());
|
||||
shared->IncInitialized();
|
||||
if (shared->AllInitialized()) {
|
||||
shared->GetCondVar()->SignalAll();
|
||||
}
|
||||
while (!shared->Started()) {
|
||||
shared->GetCondVar()->Wait();
|
||||
}
|
||||
}
|
||||
thread->shared->GetCacheBench()->OperateCache(thread);
|
||||
|
||||
{
|
||||
MutexLock l(shared->GetMutex());
|
||||
shared->IncDone();
|
||||
if (shared->AllDone()) {
|
||||
shared->GetCondVar()->SignalAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OperateCache(ThreadState* thread) {
|
||||
// To use looked-up values
|
||||
uint64_t result = 0;
|
||||
// To hold handles for a non-trivial amount of time
|
||||
Cache::Handle* handle = nullptr;
|
||||
KeyGen gen;
|
||||
ROCKSDB_NAMESPACE::Env* env = ROCKSDB_NAMESPACE::Env::Default();
|
||||
SystemClock* clock = env->GetSystemClock().get();
|
||||
for (uint64_t i = 0; i < FLAGS_ops_per_thread; i++) {
|
||||
Slice key = gen.GetRand(thread->rnd, max_key_, max_log_);
|
||||
uint64_t random_op = thread->rnd.Next();
|
||||
Cache::CreateCallback create_cb =
|
||||
[](void* buf, size_t size, void** out_obj, size_t* charge) -> Status {
|
||||
*out_obj = reinterpret_cast<void*>(new char[size]);
|
||||
memcpy(*out_obj, buf, size);
|
||||
*charge = size;
|
||||
return Status::OK();
|
||||
};
|
||||
|
||||
if (random_op < lookup_insert_threshold_) {
|
||||
if (handle) {
|
||||
cache_->Release(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
// do lookup
|
||||
{
|
||||
uint64_t elapsed = 0;
|
||||
{
|
||||
StopWatch sw(clock, nullptr, 0, &elapsed);
|
||||
handle = cache_->Lookup(key, helperCallback, create_cb,
|
||||
Cache::Priority::LOW, true);
|
||||
}
|
||||
thread->stats.AddLookupUs(elapsed);
|
||||
}
|
||||
if (handle) {
|
||||
thread->stats.AddHits(1);
|
||||
// do something with the data
|
||||
result += NPHash64(static_cast<char*>(cache_->Value(handle)),
|
||||
FLAGS_value_bytes);
|
||||
} else {
|
||||
thread->stats.AddMisses(1);
|
||||
// do insert
|
||||
{
|
||||
uint64_t elapsed = 0;
|
||||
{
|
||||
StopWatch sw(clock, nullptr, 0, &elapsed);
|
||||
cache_->Insert(key, createValue(thread->rnd), helperCallback,
|
||||
FLAGS_value_bytes, &handle);
|
||||
}
|
||||
thread->stats.AddInsertUs(elapsed);
|
||||
}
|
||||
}
|
||||
} else if (random_op < insert_threshold_) {
|
||||
if (handle) {
|
||||
cache_->Release(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
// do insert
|
||||
{
|
||||
uint64_t elapsed = 0;
|
||||
{
|
||||
StopWatch sw(clock, nullptr, 0, &elapsed);
|
||||
cache_->Insert(key, createValue(thread->rnd), helperCallback,
|
||||
FLAGS_value_bytes, &handle);
|
||||
}
|
||||
thread->stats.AddInsertUs(elapsed);
|
||||
}
|
||||
} else if (random_op < lookup_threshold_) {
|
||||
if (handle) {
|
||||
cache_->Release(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
// do lookup
|
||||
{
|
||||
uint64_t elapsed = 0;
|
||||
{
|
||||
StopWatch sw(clock, nullptr, 0, &elapsed);
|
||||
handle = cache_->Lookup(key, helperCallback, create_cb,
|
||||
Cache::Priority::LOW, true);
|
||||
}
|
||||
thread->stats.AddLookupUs(elapsed);
|
||||
}
|
||||
if (handle) {
|
||||
thread->stats.AddHits(1);
|
||||
// do something with the data
|
||||
result += NPHash64(static_cast<char*>(cache_->Value(handle)),
|
||||
FLAGS_value_bytes);
|
||||
}
|
||||
} else if (random_op < erase_threshold_) {
|
||||
// do erase
|
||||
cache_->Erase(key);
|
||||
} else {
|
||||
// Should be extremely unlikely (noop)
|
||||
assert(random_op >= kHundredthUint64 * 100U);
|
||||
}
|
||||
}
|
||||
if (handle) {
|
||||
cache_->Release(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void PrintEnv() const {
|
||||
printf("RocksDB version : %d.%d\n", kMajorVersion, kMinorVersion);
|
||||
printf("Number of threads : %u\n", FLAGS_threads);
|
||||
printf("Ops per thread : %" PRIu64 "\n", FLAGS_ops_per_thread);
|
||||
printf("Cache size : %" PRIu64 "\n", FLAGS_cache_size);
|
||||
printf("Num shard bits : %u\n", FLAGS_num_shard_bits);
|
||||
printf("Max key : %" PRIu64 "\n", max_key_);
|
||||
printf("Resident ratio : %g\n", FLAGS_resident_ratio);
|
||||
printf("Skew degree : %u\n", FLAGS_skew);
|
||||
printf("Populate cache : %d\n", int{FLAGS_populate_cache});
|
||||
printf("Lookup+Insert pct : %u%%\n", FLAGS_lookup_insert_percent);
|
||||
printf("Insert percentage : %u%%\n", FLAGS_insert_percent);
|
||||
printf("Lookup percentage : %u%%\n", FLAGS_lookup_percent);
|
||||
printf("Erase percentage : %u%%\n", FLAGS_erase_percent);
|
||||
printf("----------------------------\n");
|
||||
}
|
||||
};
|
||||
|
||||
int cache_bench_tool(int argc, char** argv) {
|
||||
ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
if (FLAGS_threads <= 0) {
|
||||
fprintf(stderr, "threads number <= 0\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::CacheBench bench;
|
||||
if (FLAGS_populate_cache) {
|
||||
bench.PopulateCache();
|
||||
printf("Population complete\n");
|
||||
printf("----------------------------\n");
|
||||
}
|
||||
if (bench.Run()) {
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
#endif
|
||||
Vendored
+22
@@ -271,7 +271,27 @@ class ClockCacheShard final : public CacheShard {
|
||||
Status Insert(const Slice& key, uint32_t hash, void* value, size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value),
|
||||
Cache::Handle** handle, Cache::Priority priority) override;
|
||||
Status Insert(const Slice& key, uint32_t hash, void* value,
|
||||
Cache::CacheItemHelperCallback helper_cb, size_t charge,
|
||||
Cache::Handle** handle, Cache::Priority priority) override {
|
||||
Cache::DeletionCallback delete_cb;
|
||||
(*helper_cb)(nullptr, nullptr, &delete_cb);
|
||||
return Insert(key, hash, value, charge, delete_cb, handle, priority);
|
||||
}
|
||||
Cache::Handle* Lookup(const Slice& key, uint32_t hash) override;
|
||||
Cache::Handle* Lookup(const Slice& key, uint32_t hash,
|
||||
Cache::CacheItemHelperCallback /*helper_cb*/,
|
||||
const Cache::CreateCallback& /*create_cb*/,
|
||||
Cache::Priority /*priority*/, bool /*wait*/) override {
|
||||
return Lookup(key, hash);
|
||||
}
|
||||
bool Release(Cache::Handle* handle, bool /*useful*/,
|
||||
bool force_erase) override {
|
||||
return Release(handle, force_erase);
|
||||
}
|
||||
bool isReady(Cache::Handle* /*handle*/) override { return true; }
|
||||
void Wait(Cache::Handle* /*handle*/) override {}
|
||||
|
||||
// If the entry in in cache, increase reference count and return true.
|
||||
// Return false otherwise.
|
||||
//
|
||||
@@ -748,6 +768,8 @@ class ClockCache final : public ShardedCache {
|
||||
|
||||
void DisownData() override { shards_ = nullptr; }
|
||||
|
||||
void WaitAll(std::vector<Handle*>& /*handles*/) override {}
|
||||
|
||||
private:
|
||||
ClockCacheShard* shards_;
|
||||
};
|
||||
|
||||
Vendored
+164
-84
@@ -9,8 +9,8 @@
|
||||
|
||||
#include "cache/lru_cache.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
#include "util/mutexlock.h"
|
||||
@@ -97,7 +97,8 @@ void LRUHandleTable::Resize() {
|
||||
LRUCacheShard::LRUCacheShard(size_t capacity, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio,
|
||||
bool use_adaptive_mutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy)
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
const std::shared_ptr<TieredCache>& tiered_cache)
|
||||
: capacity_(0),
|
||||
high_pri_pool_usage_(0),
|
||||
strict_capacity_limit_(strict_capacity_limit),
|
||||
@@ -105,7 +106,8 @@ LRUCacheShard::LRUCacheShard(size_t capacity, bool strict_capacity_limit,
|
||||
high_pri_pool_capacity_(0),
|
||||
usage_(0),
|
||||
lru_usage_(0),
|
||||
mutex_(use_adaptive_mutex) {
|
||||
mutex_(use_adaptive_mutex),
|
||||
tiered_cache_(tiered_cache) {
|
||||
set_metadata_charge_policy(metadata_charge_policy);
|
||||
// Make empty circular linked list
|
||||
lru_.next = &lru_;
|
||||
@@ -256,8 +258,14 @@ void LRUCacheShard::SetCapacity(size_t capacity) {
|
||||
EvictFromLRU(0, &last_reference_list);
|
||||
}
|
||||
|
||||
// Try to insert the evicted entries into tiered cache
|
||||
// Free the entries outside of mutex for performance reasons
|
||||
for (auto entry : last_reference_list) {
|
||||
if (tiered_cache_ && entry->IsTieredCacheCompatible() &&
|
||||
!entry->IsPromoted()) {
|
||||
tiered_cache_->Insert(entry->key(), entry->value, entry->info_.helper_cb)
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
entry->Free();
|
||||
}
|
||||
}
|
||||
@@ -267,17 +275,126 @@ void LRUCacheShard::SetStrictCapacityLimit(bool strict_capacity_limit) {
|
||||
strict_capacity_limit_ = strict_capacity_limit;
|
||||
}
|
||||
|
||||
Cache::Handle* LRUCacheShard::Lookup(const Slice& key, uint32_t hash) {
|
||||
MutexLock l(&mutex_);
|
||||
LRUHandle* e = table_.Lookup(key, hash);
|
||||
if (e != nullptr) {
|
||||
assert(e->InCache());
|
||||
if (!e->HasRefs()) {
|
||||
// The entry is in LRU since it's in hash and has no external references
|
||||
LRU_Remove(e);
|
||||
Status LRUCacheShard::InsertItem(LRUHandle* e, Cache::Handle** handle) {
|
||||
Status s = Status::OK();
|
||||
autovector<LRUHandle*> last_reference_list;
|
||||
size_t total_charge = e->CalcTotalCharge(metadata_charge_policy_);
|
||||
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
|
||||
// Free the space following strict LRU policy until enough space
|
||||
// is freed or the lru list is empty
|
||||
EvictFromLRU(total_charge, &last_reference_list);
|
||||
|
||||
if ((usage_ + total_charge) > capacity_ &&
|
||||
(strict_capacity_limit_ || handle == nullptr)) {
|
||||
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;
|
||||
s = Status::Incomplete("Insert failed due to LRU cache being full.");
|
||||
}
|
||||
} else {
|
||||
// Insert into the cache. Note that the cache might get larger than its
|
||||
// capacity if not enough space was freed up.
|
||||
LRUHandle* old = table_.Insert(e);
|
||||
usage_ += total_charge;
|
||||
if (old != nullptr) {
|
||||
s = Status::OkOverwritten();
|
||||
assert(old->InCache());
|
||||
old->SetInCache(false);
|
||||
if (!old->HasRefs()) {
|
||||
// old is on LRU because it's in cache and its reference count is 0
|
||||
LRU_Remove(old);
|
||||
size_t old_total_charge =
|
||||
old->CalcTotalCharge(metadata_charge_policy_);
|
||||
assert(usage_ >= old_total_charge);
|
||||
usage_ -= old_total_charge;
|
||||
last_reference_list.push_back(old);
|
||||
}
|
||||
}
|
||||
if (handle == nullptr) {
|
||||
LRU_Insert(e);
|
||||
} else {
|
||||
e->Ref();
|
||||
*handle = reinterpret_cast<Cache::Handle*>(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to insert the evicted entries into NVM cache
|
||||
// Free the entries here outside of mutex for performance reasons
|
||||
for (auto entry : last_reference_list) {
|
||||
if (tiered_cache_ && entry->IsTieredCacheCompatible() &&
|
||||
!entry->IsPromoted()) {
|
||||
tiered_cache_->Insert(entry->key(), entry->value, entry->info_.helper_cb)
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
entry->Free();
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
Cache::Handle* LRUCacheShard::Lookup(
|
||||
const Slice& key, uint32_t hash,
|
||||
ShardedCache::CacheItemHelperCallback helper_cb,
|
||||
const ShardedCache::CreateCallback& create_cb, Cache::Priority priority,
|
||||
bool wait) {
|
||||
LRUHandle* e = nullptr;
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
e = table_.Lookup(key, hash);
|
||||
if (e != nullptr) {
|
||||
assert(e->InCache());
|
||||
if (!e->HasRefs()) {
|
||||
// The entry is in LRU since it's in hash and has no external references
|
||||
LRU_Remove(e);
|
||||
}
|
||||
e->Ref();
|
||||
e->SetHit();
|
||||
}
|
||||
}
|
||||
|
||||
// If handle table lookup failed, then allocate a handle outside the
|
||||
// mutex if we're going to lookup in the NVM cache
|
||||
// Only support synchronous for now
|
||||
// TODO: Support asynchronous lookup in NVM cache
|
||||
if (!e && tiered_cache_ && helper_cb && wait) {
|
||||
assert(create_cb);
|
||||
std::unique_ptr<TieredCacheHandle> tiered_handle =
|
||||
tiered_cache_->Lookup(key, create_cb, wait);
|
||||
if (tiered_handle != nullptr) {
|
||||
e = reinterpret_cast<LRUHandle*>(
|
||||
new char[sizeof(LRUHandle) - 1 + key.size()]);
|
||||
|
||||
e->flags = 0;
|
||||
e->SetPromoted(true);
|
||||
e->SetTieredCacheCompatible(true);
|
||||
e->info_.helper_cb = helper_cb;
|
||||
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 = tiered_handle->Value();
|
||||
e->charge = tiered_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
|
||||
InsertItem(e, reinterpret_cast<Cache::Handle**>(&e))
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
e->Ref();
|
||||
e->SetHit();
|
||||
}
|
||||
return reinterpret_cast<Cache::Handle*>(e);
|
||||
}
|
||||
@@ -338,81 +455,32 @@ bool LRUCacheShard::Release(Cache::Handle* handle, bool force_erase) {
|
||||
Status LRUCacheShard::Insert(const Slice& key, uint32_t hash, void* value,
|
||||
size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value),
|
||||
Cache::CacheItemHelperCallback helper_cb,
|
||||
Cache::Handle** handle, Cache::Priority priority) {
|
||||
// Allocate the memory here outside of the mutex
|
||||
// If the cache is full, we'll have to release it
|
||||
// It shouldn't happen very often though.
|
||||
LRUHandle* e = reinterpret_cast<LRUHandle*>(
|
||||
new char[sizeof(LRUHandle) - 1 + key.size()]);
|
||||
Status s = Status::OK();
|
||||
autovector<LRUHandle*> last_reference_list;
|
||||
|
||||
e->value = value;
|
||||
e->deleter = deleter;
|
||||
e->flags = 0;
|
||||
if (helper_cb) {
|
||||
e->SetTieredCacheCompatible(true);
|
||||
e->info_.helper_cb = helper_cb;
|
||||
} else {
|
||||
e->info_.deleter = deleter;
|
||||
}
|
||||
e->charge = charge;
|
||||
e->key_length = key.size();
|
||||
e->flags = 0;
|
||||
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());
|
||||
size_t total_charge = e->CalcTotalCharge(metadata_charge_policy_);
|
||||
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
|
||||
// Free the space following strict LRU policy until enough space
|
||||
// is freed or the lru list is empty
|
||||
EvictFromLRU(total_charge, &last_reference_list);
|
||||
|
||||
if ((usage_ + total_charge) > capacity_ &&
|
||||
(strict_capacity_limit_ || handle == nullptr)) {
|
||||
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;
|
||||
s = Status::Incomplete("Insert failed due to LRU cache being full.");
|
||||
}
|
||||
} else {
|
||||
// Insert into the cache. Note that the cache might get larger than its
|
||||
// capacity if not enough space was freed up.
|
||||
LRUHandle* old = table_.Insert(e);
|
||||
usage_ += total_charge;
|
||||
if (old != nullptr) {
|
||||
s = Status::OkOverwritten();
|
||||
assert(old->InCache());
|
||||
old->SetInCache(false);
|
||||
if (!old->HasRefs()) {
|
||||
// old is on LRU because it's in cache and its reference count is 0
|
||||
LRU_Remove(old);
|
||||
size_t old_total_charge =
|
||||
old->CalcTotalCharge(metadata_charge_policy_);
|
||||
assert(usage_ >= old_total_charge);
|
||||
usage_ -= old_total_charge;
|
||||
last_reference_list.push_back(old);
|
||||
}
|
||||
}
|
||||
if (handle == nullptr) {
|
||||
LRU_Insert(e);
|
||||
} else {
|
||||
e->Ref();
|
||||
*handle = reinterpret_cast<Cache::Handle*>(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Free the entries here outside of mutex for performance reasons
|
||||
for (auto entry : last_reference_list) {
|
||||
entry->Free();
|
||||
}
|
||||
|
||||
return s;
|
||||
return InsertItem(e, handle);
|
||||
}
|
||||
|
||||
void LRUCacheShard::Erase(const Slice& key, uint32_t hash) {
|
||||
@@ -468,7 +536,8 @@ LRUCache::LRUCache(size_t capacity, int num_shard_bits,
|
||||
bool strict_capacity_limit, double high_pri_pool_ratio,
|
||||
std::shared_ptr<MemoryAllocator> allocator,
|
||||
bool use_adaptive_mutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy)
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
const std::shared_ptr<TieredCache>& tiered_cache)
|
||||
: ShardedCache(capacity, num_shard_bits, strict_capacity_limit,
|
||||
std::move(allocator)) {
|
||||
num_shards_ = 1 << num_shard_bits;
|
||||
@@ -478,7 +547,7 @@ LRUCache::LRUCache(size_t capacity, int num_shard_bits,
|
||||
for (int i = 0; i < num_shards_; i++) {
|
||||
new (&shards_[i])
|
||||
LRUCacheShard(per_shard, strict_capacity_limit, high_pri_pool_ratio,
|
||||
use_adaptive_mutex, metadata_charge_policy);
|
||||
use_adaptive_mutex, metadata_charge_policy, tiered_cache);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -543,19 +612,12 @@ double LRUCache::GetHighPriPoolRatio() {
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<Cache> NewLRUCache(const LRUCacheOptions& cache_opts) {
|
||||
return NewLRUCache(cache_opts.capacity, cache_opts.num_shard_bits,
|
||||
cache_opts.strict_capacity_limit,
|
||||
cache_opts.high_pri_pool_ratio,
|
||||
cache_opts.memory_allocator, cache_opts.use_adaptive_mutex,
|
||||
cache_opts.metadata_charge_policy);
|
||||
}
|
||||
|
||||
std::shared_ptr<Cache> NewLRUCache(
|
||||
size_t capacity, int num_shard_bits, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio,
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator, bool use_adaptive_mutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy) {
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
const std::shared_ptr<TieredCache>& tiered_cache) {
|
||||
if (num_shard_bits >= 20) {
|
||||
return nullptr; // the cache cannot be sharded into too many fine pieces
|
||||
}
|
||||
@@ -568,7 +630,25 @@ std::shared_ptr<Cache> NewLRUCache(
|
||||
}
|
||||
return std::make_shared<LRUCache>(
|
||||
capacity, num_shard_bits, strict_capacity_limit, high_pri_pool_ratio,
|
||||
std::move(memory_allocator), use_adaptive_mutex, metadata_charge_policy);
|
||||
std::move(memory_allocator), use_adaptive_mutex, metadata_charge_policy,
|
||||
tiered_cache);
|
||||
}
|
||||
|
||||
std::shared_ptr<Cache> NewLRUCache(const LRUCacheOptions& cache_opts) {
|
||||
return NewLRUCache(
|
||||
cache_opts.capacity, cache_opts.num_shard_bits,
|
||||
cache_opts.strict_capacity_limit, cache_opts.high_pri_pool_ratio,
|
||||
cache_opts.memory_allocator, cache_opts.use_adaptive_mutex,
|
||||
cache_opts.metadata_charge_policy, cache_opts.tiered_cache);
|
||||
}
|
||||
|
||||
std::shared_ptr<Cache> NewLRUCache(
|
||||
size_t capacity, int num_shard_bits, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio,
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator, bool use_adaptive_mutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy) {
|
||||
return NewLRUCache(capacity, num_shard_bits, strict_capacity_limit,
|
||||
high_pri_pool_ratio, memory_allocator, use_adaptive_mutex,
|
||||
metadata_charge_policy, nullptr);
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+90
-10
@@ -11,9 +11,9 @@
|
||||
#include <string>
|
||||
|
||||
#include "cache/sharded_cache.h"
|
||||
|
||||
#include "port/malloc.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/tiered_cache.h"
|
||||
#include "util/autovector.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -49,7 +49,14 @@ namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
struct LRUHandle {
|
||||
void* value;
|
||||
void (*deleter)(const Slice&, void* value);
|
||||
union Info {
|
||||
Info() {}
|
||||
~Info() {}
|
||||
void (*deleter)(const Slice&, void* value);
|
||||
ShardedCache::CacheItemHelperCallback helper_cb;
|
||||
// This needs to be explicitly constructed and destructed
|
||||
std::unique_ptr<TieredCacheHandle> tiered_handle;
|
||||
} info_;
|
||||
LRUHandle* next_hash;
|
||||
LRUHandle* next;
|
||||
LRUHandle* prev;
|
||||
@@ -69,6 +76,12 @@ struct LRUHandle {
|
||||
IN_HIGH_PRI_POOL = (1 << 2),
|
||||
// Whether this entry has had any lookups (hits).
|
||||
HAS_HIT = (1 << 3),
|
||||
// Can this be inserted into the tiered cache
|
||||
IS_TIERED_CACHE_COMPATIBLE = (1 << 4),
|
||||
// Is the handle still being read from a lower tier
|
||||
IS_PENDING = (1 << 5),
|
||||
// Has the item been promoted from a lower tier
|
||||
IS_PROMOTED = (1 << 6),
|
||||
};
|
||||
|
||||
uint8_t flags;
|
||||
@@ -95,6 +108,11 @@ struct LRUHandle {
|
||||
bool IsHighPri() const { return flags & IS_HIGH_PRI; }
|
||||
bool InHighPriPool() const { return flags & IN_HIGH_PRI_POOL; }
|
||||
bool HasHit() const { return flags & HAS_HIT; }
|
||||
bool IsTieredCacheCompatible() const {
|
||||
return flags & IS_TIERED_CACHE_COMPATIBLE;
|
||||
}
|
||||
bool IsPending() const { return flags & IS_PENDING; }
|
||||
bool IsPromoted() const { return flags & IS_PROMOTED; }
|
||||
|
||||
void SetInCache(bool in_cache) {
|
||||
if (in_cache) {
|
||||
@@ -122,10 +140,38 @@ struct LRUHandle {
|
||||
|
||||
void SetHit() { flags |= HAS_HIT; }
|
||||
|
||||
void SetTieredCacheCompatible(bool tiered) {
|
||||
if (tiered) {
|
||||
flags |= IS_TIERED_CACHE_COMPATIBLE;
|
||||
} else {
|
||||
flags &= ~IS_TIERED_CACHE_COMPATIBLE;
|
||||
}
|
||||
}
|
||||
|
||||
void SetIncomplete(bool incomp) {
|
||||
if (incomp) {
|
||||
flags |= IS_PENDING;
|
||||
} else {
|
||||
flags &= ~IS_PENDING;
|
||||
}
|
||||
}
|
||||
|
||||
void SetPromoted(bool promoted) {
|
||||
if (promoted) {
|
||||
flags |= IS_PROMOTED;
|
||||
} else {
|
||||
flags &= ~IS_PROMOTED;
|
||||
}
|
||||
}
|
||||
|
||||
void Free() {
|
||||
assert(refs == 0);
|
||||
if (deleter) {
|
||||
(*deleter)(key(), value);
|
||||
if (!IsTieredCacheCompatible() && info_.deleter) {
|
||||
(*info_.deleter)(key(), value);
|
||||
} else if (IsTieredCacheCompatible()) {
|
||||
ShardedCache::DeletionCallback del_cb;
|
||||
(*info_.helper_cb)(nullptr, nullptr, &del_cb);
|
||||
(*del_cb)(key(), value);
|
||||
}
|
||||
delete[] reinterpret_cast<char*>(this);
|
||||
}
|
||||
@@ -193,7 +239,8 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
public:
|
||||
LRUCacheShard(size_t capacity, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio, bool use_adaptive_mutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy);
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
const std::shared_ptr<TieredCache>& tiered_cache);
|
||||
virtual ~LRUCacheShard() override = default;
|
||||
|
||||
// Separate from constructor so caller can easily make an array of LRUCache
|
||||
@@ -212,8 +259,32 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value),
|
||||
Cache::Handle** handle,
|
||||
Cache::Priority priority) override;
|
||||
virtual Cache::Handle* Lookup(const Slice& key, uint32_t hash) override;
|
||||
Cache::Priority priority) override {
|
||||
return Insert(key, hash, value, charge, deleter, nullptr, handle, priority);
|
||||
}
|
||||
virtual Status Insert(const Slice& key, uint32_t hash, void* value,
|
||||
Cache::CacheItemHelperCallback helper_cb, size_t charge,
|
||||
Cache::Handle** handle,
|
||||
Cache::Priority priority) override {
|
||||
return Insert(key, hash, value, charge, nullptr, helper_cb, handle,
|
||||
priority);
|
||||
}
|
||||
// If helper_cb is null, the values of the following arguments don't
|
||||
// matter
|
||||
virtual Cache::Handle* Lookup(const Slice& key, uint32_t hash,
|
||||
ShardedCache::CacheItemHelperCallback helper_cb,
|
||||
const ShardedCache::CreateCallback& create_cb,
|
||||
ShardedCache::Priority priority,
|
||||
bool wait) override;
|
||||
virtual Cache::Handle* Lookup(const Slice& key, uint32_t hash) override {
|
||||
return Lookup(key, hash, nullptr, nullptr, Cache::Priority::LOW, true);
|
||||
}
|
||||
virtual bool Release(Cache::Handle* handle, bool /*useful*/,
|
||||
bool force_erase) override {
|
||||
return Release(handle, force_erase);
|
||||
}
|
||||
virtual bool isReady(Cache::Handle* /*handle*/) override { return true; }
|
||||
virtual void Wait(Cache::Handle* /*handle*/) override {}
|
||||
virtual bool Ref(Cache::Handle* handle) override;
|
||||
virtual bool Release(Cache::Handle* handle,
|
||||
bool force_erase = false) override;
|
||||
@@ -239,10 +310,15 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
// not threadsafe
|
||||
size_t TEST_GetLRUSize();
|
||||
|
||||
// Retrives high pri pool ratio
|
||||
// Retrieves high pri pool ratio
|
||||
double GetHighPriPoolRatio();
|
||||
|
||||
private:
|
||||
Status InsertItem(LRUHandle* item, Cache::Handle** handle);
|
||||
Status Insert(const Slice& key, uint32_t hash, void* value, size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value),
|
||||
Cache::CacheItemHelperCallback helper_cb,
|
||||
Cache::Handle** handle, Cache::Priority priority);
|
||||
void LRU_Remove(LRUHandle* e);
|
||||
void LRU_Insert(LRUHandle* e);
|
||||
|
||||
@@ -303,6 +379,8 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
// We don't count mutex_ as the cache's internal state so semantically we
|
||||
// don't mind mutex_ invoking the non-const actions.
|
||||
mutable port::Mutex mutex_;
|
||||
|
||||
std::shared_ptr<TieredCache> tiered_cache_;
|
||||
};
|
||||
|
||||
class LRUCache
|
||||
@@ -316,7 +394,8 @@ class LRUCache
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator = nullptr,
|
||||
bool use_adaptive_mutex = kDefaultToAdaptiveMutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy =
|
||||
kDontChargeCacheMetadata);
|
||||
kDontChargeCacheMetadata,
|
||||
const std::shared_ptr<TieredCache>& tiered_cache = nullptr);
|
||||
virtual ~LRUCache();
|
||||
virtual const char* Name() const override { return "LRUCache"; }
|
||||
virtual CacheShard* GetShard(int shard) override;
|
||||
@@ -325,10 +404,11 @@ class LRUCache
|
||||
virtual size_t GetCharge(Handle* handle) const override;
|
||||
virtual uint32_t GetHash(Handle* handle) const override;
|
||||
virtual void DisownData() override;
|
||||
virtual void WaitAll(std::vector<Handle*>& /*handles*/) override {}
|
||||
|
||||
// Retrieves number of elements in LRU, for unit test purpose only
|
||||
size_t TEST_GetLRUSize();
|
||||
// Retrives high pri pool ratio
|
||||
// Retrieves high pri pool ratio
|
||||
double GetHighPriPoolRatio();
|
||||
|
||||
private:
|
||||
|
||||
Vendored
+176
-3
@@ -7,8 +7,12 @@
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/random.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -30,9 +34,9 @@ class LRUCacheTest : public testing::Test {
|
||||
DeleteCache();
|
||||
cache_ = reinterpret_cast<LRUCacheShard*>(
|
||||
port::cacheline_aligned_alloc(sizeof(LRUCacheShard)));
|
||||
new (cache_) LRUCacheShard(capacity, false /*strict_capcity_limit*/,
|
||||
high_pri_pool_ratio, use_adaptive_mutex,
|
||||
kDontChargeCacheMetadata);
|
||||
new (cache_) LRUCacheShard(
|
||||
capacity, false /*strict_capcity_limit*/, high_pri_pool_ratio,
|
||||
use_adaptive_mutex, kDontChargeCacheMetadata, nullptr /*tiered_cache*/);
|
||||
}
|
||||
|
||||
void Insert(const std::string& key,
|
||||
@@ -191,6 +195,175 @@ TEST_F(LRUCacheTest, EntriesWithPriority) {
|
||||
ValidateLRUList({"e", "f", "g", "Z", "d"}, 2);
|
||||
}
|
||||
|
||||
class TestTieredCache : public TieredCache {
|
||||
public:
|
||||
TestTieredCache(size_t capacity) : num_inserts_(0), num_lookups_(0) {
|
||||
cache_ = NewLRUCache(capacity, 0, false, 0.5, nullptr,
|
||||
kDefaultToAdaptiveMutex, kDontChargeCacheMetadata);
|
||||
}
|
||||
~TestTieredCache() { cache_.reset(); }
|
||||
|
||||
std::string Name() override { return "TestTieredCache"; }
|
||||
|
||||
Status Insert(const Slice& key, void* value,
|
||||
Cache::CacheItemHelperCallback helper_cb) override {
|
||||
Cache::SizeCallback size_cb;
|
||||
Cache::SaveToCallback save_cb;
|
||||
size_t size;
|
||||
char* buf;
|
||||
Status s;
|
||||
|
||||
num_inserts_++;
|
||||
(*helper_cb)(&size_cb, &save_cb, nullptr);
|
||||
size = (*size_cb)(value);
|
||||
buf = new char[size + sizeof(uint64_t)];
|
||||
EncodeFixed64(buf, size);
|
||||
s = (*save_cb)(value, 0, size, buf + sizeof(uint64_t));
|
||||
EXPECT_OK(s);
|
||||
return cache_->Insert(key, buf, size,
|
||||
[](const Slice& /*key*/, void* val) -> void {
|
||||
delete[] reinterpret_cast<char*>(val);
|
||||
});
|
||||
}
|
||||
|
||||
std::unique_ptr<TieredCacheHandle> Lookup(
|
||||
const Slice& key, const Cache::CreateCallback& create_cb,
|
||||
bool /*wait*/) override {
|
||||
std::unique_ptr<TieredCacheHandle> tiered_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);
|
||||
EXPECT_OK(s);
|
||||
tiered_handle.reset(
|
||||
new TestTieredCacheHandle(cache_.get(), handle, value, charge));
|
||||
}
|
||||
return tiered_handle;
|
||||
}
|
||||
|
||||
void Erase(const Slice& /*key*/) override {}
|
||||
|
||||
void WaitAll(std::vector<TieredCacheHandle*> /*handles*/) override {}
|
||||
|
||||
std::string GetPrintableOptions() const override { return ""; }
|
||||
|
||||
uint32_t num_inserts() { return num_inserts_; }
|
||||
|
||||
uint32_t num_lookups() { return num_lookups_; }
|
||||
|
||||
private:
|
||||
class TestTieredCacheHandle : public TieredCacheHandle {
|
||||
public:
|
||||
TestTieredCacheHandle(Cache* cache, Cache::Handle* handle, void* value,
|
||||
size_t size)
|
||||
: cache_(cache), handle_(handle), value_(value), size_(size) {}
|
||||
~TestTieredCacheHandle() { cache_->Release(handle_); }
|
||||
|
||||
bool isReady() override { return true; }
|
||||
|
||||
void Wait() override {}
|
||||
|
||||
void* Value() override { return value_; }
|
||||
|
||||
size_t Size() override { return size_; }
|
||||
|
||||
private:
|
||||
Cache* cache_;
|
||||
Cache::Handle* handle_;
|
||||
void* value_;
|
||||
size_t size_;
|
||||
};
|
||||
|
||||
std::shared_ptr<Cache> cache_;
|
||||
uint32_t num_inserts_;
|
||||
uint32_t num_lookups_;
|
||||
};
|
||||
|
||||
TEST_F(LRUCacheTest, TestTieredCache) {
|
||||
LRUCacheOptions opts(1024, 0, false, 0.5, nullptr, kDefaultToAdaptiveMutex,
|
||||
kDontChargeCacheMetadata);
|
||||
std::shared_ptr<TestTieredCache> tiered_cache(new TestTieredCache(2048));
|
||||
opts.tiered_cache = tiered_cache;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(opts);
|
||||
|
||||
class TestItem {
|
||||
public:
|
||||
TestItem(const char* buf, size_t size) : buf_(new char[size]), size_(size) {
|
||||
memcpy(buf_.get(), buf, size);
|
||||
}
|
||||
~TestItem() {}
|
||||
|
||||
char* Buf() { return buf_.get(); }
|
||||
size_t Size() { return size_; }
|
||||
|
||||
private:
|
||||
std::unique_ptr<char[]> buf_;
|
||||
size_t size_;
|
||||
};
|
||||
|
||||
Cache::CacheItemHelperCallback helper_cb =
|
||||
[](Cache::SizeCallback* size_cb, Cache::SaveToCallback* saveto_cb,
|
||||
Cache::DeletionCallback* del_cb) -> void {
|
||||
if (size_cb) {
|
||||
*size_cb = [](void* obj) -> size_t {
|
||||
return reinterpret_cast<TestItem*>(obj)->Size();
|
||||
};
|
||||
}
|
||||
if (saveto_cb) {
|
||||
*saveto_cb = [](void* obj, size_t offset, size_t size,
|
||||
void* out) -> Status {
|
||||
TestItem* item = reinterpret_cast<TestItem*>(obj);
|
||||
char* buf = item->Buf();
|
||||
EXPECT_EQ(size, item->Size());
|
||||
EXPECT_EQ(offset, 0);
|
||||
memcpy(out, buf, size);
|
||||
return Status::OK();
|
||||
};
|
||||
}
|
||||
if (del_cb) {
|
||||
*del_cb = [](const Slice& /*key*/, void* obj) -> void {
|
||||
delete reinterpret_cast<TestItem*>(obj);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
int create_count = 0;
|
||||
Cache::CreateCallback test_item_creator =
|
||||
[&create_count](void* buf, size_t size, void** out_obj,
|
||||
size_t* charge) -> Status {
|
||||
create_count++;
|
||||
*out_obj = reinterpret_cast<void*>(new TestItem((char*)buf, size));
|
||||
*charge = size;
|
||||
return Status::OK();
|
||||
};
|
||||
|
||||
Random rnd(301);
|
||||
std::string str1 = rnd.RandomString(1020);
|
||||
TestItem* item1 = new TestItem(str1.data(), str1.length());
|
||||
ASSERT_OK(cache->Insert("k1", item1, helper_cb, str1.length()));
|
||||
std::string str2 = rnd.RandomString(1020);
|
||||
TestItem* item2 = new TestItem(str2.data(), str2.length());
|
||||
// k2 should be demoted to NVM
|
||||
ASSERT_OK(cache->Insert("k2", item2, helper_cb, str2.length()));
|
||||
|
||||
Cache::Handle* handle;
|
||||
handle = cache->Lookup("k2", helper_cb, test_item_creator,
|
||||
Cache::Priority::LOW, true);
|
||||
ASSERT_NE(handle, nullptr);
|
||||
cache->Release(handle);
|
||||
// This lookup should promote k1 and demote k2
|
||||
handle = cache->Lookup("k1", helper_cb, test_item_creator,
|
||||
Cache::Priority::LOW, true);
|
||||
ASSERT_NE(handle, nullptr);
|
||||
cache->Release(handle);
|
||||
ASSERT_EQ(tiered_cache->num_inserts(), 2u);
|
||||
ASSERT_EQ(tiered_cache->num_lookups(), 1u);
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
Vendored
+33
@@ -51,11 +51,39 @@ Status ShardedCache::Insert(const Slice& key, void* value, size_t charge,
|
||||
->Insert(key, hash, value, charge, deleter, handle, priority);
|
||||
}
|
||||
|
||||
Status ShardedCache::Insert(const Slice& key, void* value,
|
||||
CacheItemHelperCallback helper_cb, size_t charge,
|
||||
Handle** handle, Priority priority) {
|
||||
uint32_t hash = HashSlice(key);
|
||||
return GetShard(Shard(hash))
|
||||
->Insert(key, hash, value, helper_cb, charge, handle, priority);
|
||||
}
|
||||
|
||||
Cache::Handle* ShardedCache::Lookup(const Slice& key, Statistics* /*stats*/) {
|
||||
uint32_t hash = HashSlice(key);
|
||||
return GetShard(Shard(hash))->Lookup(key, hash);
|
||||
}
|
||||
|
||||
Cache::Handle* ShardedCache::Lookup(const Slice& key,
|
||||
CacheItemHelperCallback helper_cb,
|
||||
const CreateCallback& create_cb,
|
||||
Priority priority, bool wait,
|
||||
Statistics* /*stats*/) {
|
||||
uint32_t hash = HashSlice(key);
|
||||
return GetShard(Shard(hash))
|
||||
->Lookup(key, hash, helper_cb, create_cb, priority, wait);
|
||||
}
|
||||
|
||||
bool ShardedCache::isReady(Handle* handle) {
|
||||
uint32_t hash = GetHash(handle);
|
||||
return GetShard(Shard(hash))->isReady(handle);
|
||||
}
|
||||
|
||||
void ShardedCache::Wait(Handle* handle) {
|
||||
uint32_t hash = GetHash(handle);
|
||||
GetShard(Shard(hash))->Wait(handle);
|
||||
}
|
||||
|
||||
bool ShardedCache::Ref(Handle* handle) {
|
||||
uint32_t hash = GetHash(handle);
|
||||
return GetShard(Shard(hash))->Ref(handle);
|
||||
@@ -66,6 +94,11 @@ bool ShardedCache::Release(Handle* handle, bool force_erase) {
|
||||
return GetShard(Shard(hash))->Release(handle, force_erase);
|
||||
}
|
||||
|
||||
bool ShardedCache::Release(Handle* handle, bool useful, bool force_erase) {
|
||||
uint32_t hash = GetHash(handle);
|
||||
return GetShard(Shard(hash))->Release(handle, useful, force_erase);
|
||||
}
|
||||
|
||||
void ShardedCache::Erase(const Slice& key) {
|
||||
uint32_t hash = HashSlice(key);
|
||||
GetShard(Shard(hash))->Erase(key, hash);
|
||||
|
||||
Vendored
+24
-1
@@ -28,9 +28,20 @@ class CacheShard {
|
||||
size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value),
|
||||
Cache::Handle** handle, Cache::Priority priority) = 0;
|
||||
virtual Status Insert(const Slice& key, uint32_t hash, void* value,
|
||||
Cache::CacheItemHelperCallback helper_cb, size_t charge,
|
||||
Cache::Handle** handle, Cache::Priority priority) = 0;
|
||||
virtual Cache::Handle* Lookup(const Slice& key, uint32_t hash) = 0;
|
||||
virtual Cache::Handle* Lookup(const Slice& key, uint32_t hash,
|
||||
Cache::CacheItemHelperCallback helper_cb,
|
||||
const Cache::CreateCallback& create_cb,
|
||||
Cache::Priority priority, bool wait) = 0;
|
||||
virtual bool Release(Cache::Handle* handle, bool useful,
|
||||
bool force_erase) = 0;
|
||||
virtual bool isReady(Cache::Handle* handle) = 0;
|
||||
virtual void Wait(Cache::Handle* handle) = 0;
|
||||
virtual bool Ref(Cache::Handle* handle) = 0;
|
||||
virtual bool Release(Cache::Handle* handle, bool force_erase = false) = 0;
|
||||
virtual bool Release(Cache::Handle* handle, bool force_erase) = 0;
|
||||
virtual void Erase(const Slice& key, uint32_t hash) = 0;
|
||||
virtual void SetCapacity(size_t capacity) = 0;
|
||||
virtual void SetStrictCapacityLimit(bool strict_capacity_limit) = 0;
|
||||
@@ -62,6 +73,7 @@ class ShardedCache : public Cache {
|
||||
virtual const CacheShard* GetShard(int shard) const = 0;
|
||||
virtual void* Value(Handle* handle) override = 0;
|
||||
virtual size_t GetCharge(Handle* handle) const override = 0;
|
||||
virtual void WaitAll(std::vector<Handle*>& handles) override = 0;
|
||||
|
||||
virtual uint32_t GetHash(Handle* handle) const = 0;
|
||||
virtual void DisownData() override = 0;
|
||||
@@ -72,7 +84,18 @@ class ShardedCache : public Cache {
|
||||
virtual Status Insert(const Slice& key, void* value, size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value),
|
||||
Handle** handle, Priority priority) override;
|
||||
virtual Status Insert(const Slice& key, void* value,
|
||||
CacheItemHelperCallback helper_cb, size_t chargge,
|
||||
Handle** handle = nullptr,
|
||||
Priority priority = Priority::LOW) override;
|
||||
virtual Handle* Lookup(const Slice& key, Statistics* stats) override;
|
||||
virtual Handle* Lookup(const Slice& key, CacheItemHelperCallback helper_cb,
|
||||
const CreateCallback& create_cb, Priority priority,
|
||||
bool wait, Statistics* stats = nullptr) override;
|
||||
virtual bool Release(Handle* handle, bool useful,
|
||||
bool force_erase = false) override;
|
||||
virtual bool isReady(Handle* handle) override;
|
||||
virtual void Wait(Handle* handle) override;
|
||||
virtual bool Ref(Handle* handle) override;
|
||||
virtual bool Release(Handle* handle, bool force_erase = false) override;
|
||||
virtual void Erase(const Slice& key) override;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <cassert>
|
||||
|
||||
#include "db/blob/blob_file_addition.h"
|
||||
#include "db/blob/blob_file_completion_callback.h"
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "db/blob/blob_log_writer.h"
|
||||
@@ -27,29 +28,31 @@
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
BlobFileBuilder::BlobFileBuilder(
|
||||
VersionSet* versions, Env* env, FileSystem* fs,
|
||||
VersionSet* versions, FileSystem* fs,
|
||||
const ImmutableCFOptions* immutable_cf_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,
|
||||
Env::WriteLifeTimeHint write_hint,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
BlobFileCompletionCallback* blob_callback,
|
||||
std::vector<std::string>* blob_file_paths,
|
||||
std::vector<BlobFileAddition>* blob_file_additions)
|
||||
: BlobFileBuilder([versions]() { return versions->NewFileNumber(); }, env,
|
||||
fs, immutable_cf_options, mutable_cf_options,
|
||||
file_options, job_id, column_family_id,
|
||||
column_family_name, io_priority, write_hint, io_tracer,
|
||||
blob_file_paths, blob_file_additions) {}
|
||||
: BlobFileBuilder([versions]() { return versions->NewFileNumber(); }, fs,
|
||||
immutable_cf_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, Env* env, FileSystem* fs,
|
||||
std::function<uint64_t()> file_number_generator, FileSystem* fs,
|
||||
const ImmutableCFOptions* immutable_cf_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,
|
||||
Env::WriteLifeTimeHint write_hint,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
BlobFileCompletionCallback* blob_callback,
|
||||
std::vector<std::string>* blob_file_paths,
|
||||
std::vector<BlobFileAddition>* blob_file_additions)
|
||||
: file_number_generator_(std::move(file_number_generator)),
|
||||
@@ -65,12 +68,12 @@ BlobFileBuilder::BlobFileBuilder(
|
||||
io_priority_(io_priority),
|
||||
write_hint_(write_hint),
|
||||
io_tracer_(io_tracer),
|
||||
blob_callback_(blob_callback),
|
||||
blob_file_paths_(blob_file_paths),
|
||||
blob_file_additions_(blob_file_additions),
|
||||
blob_count_(0),
|
||||
blob_bytes_(0) {
|
||||
assert(file_number_generator_);
|
||||
assert(env);
|
||||
assert(fs_);
|
||||
assert(immutable_cf_options_);
|
||||
assert(file_options_);
|
||||
@@ -78,7 +81,6 @@ BlobFileBuilder::BlobFileBuilder(
|
||||
assert(blob_file_paths_->empty());
|
||||
assert(blob_file_additions_);
|
||||
assert(blob_file_additions_->empty());
|
||||
clock_ = env->GetSystemClock();
|
||||
}
|
||||
|
||||
BlobFileBuilder::~BlobFileBuilder() = default;
|
||||
@@ -185,16 +187,17 @@ Status BlobFileBuilder::OpenBlobFileIfNeeded() {
|
||||
FileTypeSet tmp_set = immutable_cf_options_->checksum_handoff_file_types;
|
||||
Statistics* const statistics = immutable_cf_options_->statistics;
|
||||
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
|
||||
std::move(file), blob_file_paths_->back(), *file_options_, clock_,
|
||||
io_tracer_, statistics, immutable_cf_options_->listeners,
|
||||
immutable_cf_options_->file_checksum_gen_factory,
|
||||
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(),
|
||||
tmp_set.Contains(FileType::kBlobFile)));
|
||||
|
||||
constexpr bool do_flush = false;
|
||||
|
||||
std::unique_ptr<BlobLogWriter> blob_log_writer(new BlobLogWriter(
|
||||
std::move(file_writer), clock_, statistics, blob_file_number,
|
||||
immutable_cf_options_->use_fsync, do_flush));
|
||||
std::move(file_writer), immutable_cf_options_->clock, statistics,
|
||||
blob_file_number, immutable_cf_options_->use_fsync, do_flush));
|
||||
|
||||
constexpr bool has_ttl = false;
|
||||
constexpr ExpirationRange expiration_range;
|
||||
@@ -303,12 +306,15 @@ Status BlobFileBuilder::CloseBlobFile() {
|
||||
" total blobs, %" PRIu64 " total bytes",
|
||||
column_family_name_.c_str(), job_id_, blob_file_number,
|
||||
blob_count_, blob_bytes_);
|
||||
if (blob_callback_) {
|
||||
s = blob_callback_->OnBlobFileCompleted(blob_file_paths_->back());
|
||||
}
|
||||
|
||||
writer_.reset();
|
||||
blob_count_ = 0;
|
||||
blob_bytes_ = 0;
|
||||
|
||||
return Status::OK();
|
||||
return s;
|
||||
}
|
||||
|
||||
Status BlobFileBuilder::CloseBlobFileIfNeeded() {
|
||||
@@ -324,4 +330,20 @@ Status BlobFileBuilder::CloseBlobFileIfNeeded() {
|
||||
return CloseBlobFile();
|
||||
}
|
||||
|
||||
void BlobFileBuilder::Abandon() {
|
||||
if (!IsBlobFileOpen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (blob_callback_) {
|
||||
// BlobFileBuilder::Abandon() is called because of error while writing to
|
||||
// Blob files. So we can ignore the below error.
|
||||
blob_callback_->OnBlobFileCompleted(blob_file_paths_->back())
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
|
||||
writer_.reset();
|
||||
blob_count_ = 0;
|
||||
blob_bytes_ = 0;
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -27,10 +27,11 @@ class Status;
|
||||
class Slice;
|
||||
class BlobLogWriter;
|
||||
class IOTracer;
|
||||
class BlobFileCompletionCallback;
|
||||
|
||||
class BlobFileBuilder {
|
||||
public:
|
||||
BlobFileBuilder(VersionSet* versions, Env* env, FileSystem* fs,
|
||||
BlobFileBuilder(VersionSet* versions, FileSystem* fs,
|
||||
const ImmutableCFOptions* immutable_cf_options,
|
||||
const MutableCFOptions* mutable_cf_options,
|
||||
const FileOptions* file_options, int job_id,
|
||||
@@ -39,10 +40,11 @@ class BlobFileBuilder {
|
||||
Env::IOPriority io_priority,
|
||||
Env::WriteLifeTimeHint write_hint,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
BlobFileCompletionCallback* blob_callback,
|
||||
std::vector<std::string>* blob_file_paths,
|
||||
std::vector<BlobFileAddition>* blob_file_additions);
|
||||
|
||||
BlobFileBuilder(std::function<uint64_t()> file_number_generator, Env* env,
|
||||
BlobFileBuilder(std::function<uint64_t()> file_number_generator,
|
||||
FileSystem* fs,
|
||||
const ImmutableCFOptions* immutable_cf_options,
|
||||
const MutableCFOptions* mutable_cf_options,
|
||||
@@ -52,6 +54,7 @@ class BlobFileBuilder {
|
||||
Env::IOPriority io_priority,
|
||||
Env::WriteLifeTimeHint write_hint,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
BlobFileCompletionCallback* blob_callback,
|
||||
std::vector<std::string>* blob_file_paths,
|
||||
std::vector<BlobFileAddition>* blob_file_additions);
|
||||
|
||||
@@ -62,6 +65,7 @@ class BlobFileBuilder {
|
||||
|
||||
Status Add(const Slice& key, const Slice& value, std::string* blob_index);
|
||||
Status Finish();
|
||||
void Abandon();
|
||||
|
||||
private:
|
||||
bool IsBlobFileOpen() const;
|
||||
@@ -74,7 +78,6 @@ class BlobFileBuilder {
|
||||
|
||||
std::function<uint64_t()> file_number_generator_;
|
||||
FileSystem* fs_;
|
||||
std::shared_ptr<SystemClock> clock_;
|
||||
const ImmutableCFOptions* immutable_cf_options_;
|
||||
uint64_t min_blob_size_;
|
||||
uint64_t blob_file_size_;
|
||||
@@ -86,6 +89,7 @@ class BlobFileBuilder {
|
||||
Env::IOPriority io_priority_;
|
||||
Env::WriteLifeTimeHint write_hint_;
|
||||
std::shared_ptr<IOTracer> io_tracer_;
|
||||
BlobFileCompletionCallback* blob_callback_;
|
||||
std::vector<std::string>* blob_file_paths_;
|
||||
std::vector<BlobFileAddition>* blob_file_additions_;
|
||||
std::unique_ptr<BlobLogWriter> writer_;
|
||||
|
||||
@@ -41,7 +41,7 @@ class BlobFileBuilderTest : public testing::Test {
|
||||
protected:
|
||||
BlobFileBuilderTest() : mock_env_(Env::Default()) {
|
||||
fs_ = mock_env_.GetFileSystem().get();
|
||||
clock_ = mock_env_.GetSystemClock();
|
||||
clock_ = mock_env_.GetSystemClock().get();
|
||||
}
|
||||
|
||||
void VerifyBlobFile(uint64_t blob_file_number,
|
||||
@@ -110,7 +110,7 @@ class BlobFileBuilderTest : public testing::Test {
|
||||
|
||||
MockEnv mock_env_;
|
||||
FileSystem* fs_;
|
||||
std::shared_ptr<SystemClock> clock_;
|
||||
SystemClock* clock_;
|
||||
FileOptions file_options_;
|
||||
};
|
||||
|
||||
@@ -127,6 +127,7 @@ TEST_F(BlobFileBuilderTest, BuildAndCheckOneFile) {
|
||||
"BlobFileBuilderTest_BuildAndCheckOneFile"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
options.env = &mock_env_;
|
||||
|
||||
ImmutableCFOptions immutable_cf_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
@@ -140,11 +141,12 @@ TEST_F(BlobFileBuilderTest, BuildAndCheckOneFile) {
|
||||
std::vector<std::string> blob_file_paths;
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
|
||||
BlobFileBuilder builder(
|
||||
TestFileNumberGenerator(), &mock_env_, fs_, &immutable_cf_options,
|
||||
&mutable_cf_options, &file_options_, job_id, column_family_id,
|
||||
column_family_name, io_priority, write_hint, nullptr /*IOTracer*/,
|
||||
&blob_file_paths, &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);
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> expected_key_value_pairs(
|
||||
number_of_blobs);
|
||||
@@ -210,6 +212,7 @@ TEST_F(BlobFileBuilderTest, BuildAndCheckMultipleFiles) {
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
options.blob_file_size = value_size;
|
||||
options.env = &mock_env_;
|
||||
|
||||
ImmutableCFOptions immutable_cf_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
@@ -223,11 +226,12 @@ TEST_F(BlobFileBuilderTest, BuildAndCheckMultipleFiles) {
|
||||
std::vector<std::string> blob_file_paths;
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
|
||||
BlobFileBuilder builder(
|
||||
TestFileNumberGenerator(), &mock_env_, fs_, &immutable_cf_options,
|
||||
&mutable_cf_options, &file_options_, job_id, column_family_id,
|
||||
column_family_name, io_priority, write_hint, nullptr /*IOTracer*/,
|
||||
&blob_file_paths, &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);
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> expected_key_value_pairs(
|
||||
number_of_blobs);
|
||||
@@ -295,6 +299,7 @@ TEST_F(BlobFileBuilderTest, InlinedValues) {
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 1024;
|
||||
options.env = &mock_env_;
|
||||
|
||||
ImmutableCFOptions immutable_cf_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
@@ -308,11 +313,12 @@ TEST_F(BlobFileBuilderTest, InlinedValues) {
|
||||
std::vector<std::string> blob_file_paths;
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
|
||||
BlobFileBuilder builder(
|
||||
TestFileNumberGenerator(), &mock_env_, fs_, &immutable_cf_options,
|
||||
&mutable_cf_options, &file_options_, job_id, column_family_id,
|
||||
column_family_name, io_priority, write_hint, nullptr /*IOTracer*/,
|
||||
&blob_file_paths, &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);
|
||||
|
||||
for (size_t i = 0; i < number_of_blobs; ++i) {
|
||||
const std::string key = std::to_string(i);
|
||||
@@ -347,6 +353,7 @@ TEST_F(BlobFileBuilderTest, Compression) {
|
||||
test::PerThreadDBPath(&mock_env_, "BlobFileBuilderTest_Compression"), 0);
|
||||
options.enable_blob_files = true;
|
||||
options.blob_compression_type = kSnappyCompression;
|
||||
options.env = &mock_env_;
|
||||
|
||||
ImmutableCFOptions immutable_cf_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
@@ -360,11 +367,12 @@ TEST_F(BlobFileBuilderTest, Compression) {
|
||||
std::vector<std::string> blob_file_paths;
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
|
||||
BlobFileBuilder builder(
|
||||
TestFileNumberGenerator(), &mock_env_, fs_, &immutable_cf_options,
|
||||
&mutable_cf_options, &file_options_, job_id, column_family_id,
|
||||
column_family_name, io_priority, write_hint, nullptr /*IOTracer*/,
|
||||
&blob_file_paths, &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);
|
||||
|
||||
const std::string key("1");
|
||||
const std::string uncompressed_value(value_size, 'x');
|
||||
@@ -429,7 +437,7 @@ TEST_F(BlobFileBuilderTest, CompressionError) {
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
options.blob_compression_type = kSnappyCompression;
|
||||
|
||||
options.env = &mock_env_;
|
||||
ImmutableCFOptions immutable_cf_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
|
||||
@@ -442,11 +450,12 @@ TEST_F(BlobFileBuilderTest, CompressionError) {
|
||||
std::vector<std::string> blob_file_paths;
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
|
||||
BlobFileBuilder builder(
|
||||
TestFileNumberGenerator(), &mock_env_, fs_, &immutable_cf_options,
|
||||
&mutable_cf_options, &file_options_, job_id, column_family_id,
|
||||
column_family_name, io_priority, write_hint, nullptr /*IOTracer*/,
|
||||
&blob_file_paths, &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);
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack("CompressData:TamperWithReturnValue",
|
||||
[](void* arg) {
|
||||
@@ -506,6 +515,7 @@ TEST_F(BlobFileBuilderTest, Checksum) {
|
||||
options.enable_blob_files = true;
|
||||
options.file_checksum_gen_factory =
|
||||
std::make_shared<DummyFileChecksumGenFactory>();
|
||||
options.env = &mock_env_;
|
||||
|
||||
ImmutableCFOptions immutable_cf_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
@@ -519,11 +529,12 @@ TEST_F(BlobFileBuilderTest, Checksum) {
|
||||
std::vector<std::string> blob_file_paths;
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
|
||||
BlobFileBuilder builder(
|
||||
TestFileNumberGenerator(), &mock_env_, fs_, &immutable_cf_options,
|
||||
&mutable_cf_options, &file_options_, job_id, column_family_id,
|
||||
column_family_name, io_priority, write_hint, nullptr /*IOTracer*/,
|
||||
&blob_file_paths, &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);
|
||||
|
||||
const std::string key("1");
|
||||
const std::string value("deadbeef");
|
||||
@@ -615,11 +626,12 @@ TEST_P(BlobFileBuilderIOErrorTest, IOError) {
|
||||
std::vector<std::string> blob_file_paths;
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
|
||||
BlobFileBuilder builder(
|
||||
TestFileNumberGenerator(), &mock_env_, fs_, &immutable_cf_options,
|
||||
&mutable_cf_options, &file_options_, job_id, column_family_id,
|
||||
column_family_name, io_priority, write_hint, nullptr /*IOTracer*/,
|
||||
&blob_file_paths, &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);
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(sync_point_, [this](void* arg) {
|
||||
Status* const s = static_cast<Status*>(arg);
|
||||
|
||||
@@ -42,15 +42,15 @@ void WriteBlobFile(uint32_t column_family_id,
|
||||
|
||||
std::unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(file), blob_file_path, FileOptions(),
|
||||
immutable_cf_options.env->GetSystemClock()));
|
||||
immutable_cf_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.env->GetSystemClock(),
|
||||
statistics, blob_file_number, use_fsync, do_flush);
|
||||
BlobLogWriter blob_log_writer(std::move(file_writer),
|
||||
immutable_cf_options.clock, statistics,
|
||||
blob_file_number, use_fsync, do_flush);
|
||||
|
||||
constexpr bool has_ttl = false;
|
||||
constexpr ExpirationRange expiration_range;
|
||||
@@ -64,7 +64,6 @@ void WriteBlobFile(uint32_t column_family_id,
|
||||
constexpr char blob[] = "blob";
|
||||
|
||||
std::string compressed_blob;
|
||||
Slice blob_to_write;
|
||||
|
||||
uint64_t key_offset = 0;
|
||||
uint64_t blob_offset = 0;
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
// 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).
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
#pragma once
|
||||
|
||||
#include "db/error_handler.h"
|
||||
#include "file/sst_file_manager_impl.h"
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class BlobFileCompletionCallback {
|
||||
public:
|
||||
#ifdef ROCKSDB_LITE
|
||||
BlobFileCompletionCallback(SstFileManager* /*sst_file_manager*/,
|
||||
InstrumentedMutex* /*mutex*/,
|
||||
ErrorHandler* /*error_handler*/) {}
|
||||
Status OnBlobFileCompleted(const std::string& /*file_name*/) {
|
||||
return Status::OK();
|
||||
}
|
||||
#else
|
||||
BlobFileCompletionCallback(SstFileManager* sst_file_manager,
|
||||
InstrumentedMutex* mutex,
|
||||
ErrorHandler* error_handler)
|
||||
: sst_file_manager_(sst_file_manager),
|
||||
mutex_(mutex),
|
||||
error_handler_(error_handler) {}
|
||||
|
||||
Status OnBlobFileCompleted(const std::string& file_name) {
|
||||
Status s;
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(sst_file_manager_);
|
||||
if (sfm) {
|
||||
// Report new blob files to SstFileManagerImpl
|
||||
s = sfm->OnAddFile(file_name);
|
||||
if (sfm->IsMaxAllowedSpaceReached()) {
|
||||
s = Status::SpaceLimit("Max allowed space was reached");
|
||||
TEST_SYNC_POINT(
|
||||
"BlobFileCompletionCallback::CallBack::MaxAllowedSpaceReached");
|
||||
InstrumentedMutexLock l(mutex_);
|
||||
error_handler_->SetBGError(s, BackgroundErrorReason::kFlush);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
private:
|
||||
SstFileManager* sst_file_manager_;
|
||||
InstrumentedMutex* mutex_;
|
||||
ErrorHandler* error_handler_;
|
||||
#endif // ROCKSDB_LITE
|
||||
};
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -118,10 +118,9 @@ Status BlobFileReader::OpenFile(
|
||||
}
|
||||
|
||||
file_reader->reset(new RandomAccessFileReader(
|
||||
std::move(file), blob_file_path,
|
||||
immutable_cf_options.env->GetSystemClock(), io_tracer,
|
||||
std::move(file), blob_file_path, immutable_cf_options.clock, io_tracer,
|
||||
immutable_cf_options.statistics, BLOB_DB_BLOB_FILE_READ_MICROS,
|
||||
blob_file_read_hist, immutable_cf_options.rate_limiter,
|
||||
blob_file_read_hist, immutable_cf_options.rate_limiter.get(),
|
||||
immutable_cf_options.listeners));
|
||||
|
||||
return Status::OK();
|
||||
@@ -271,7 +270,8 @@ Status BlobFileReader::GetBlob(const ReadOptions& read_options,
|
||||
const Slice& user_key, uint64_t offset,
|
||||
uint64_t value_size,
|
||||
CompressionType compression_type,
|
||||
PinnableSlice* value) const {
|
||||
PinnableSlice* value,
|
||||
uint64_t* bytes_read) const {
|
||||
assert(value);
|
||||
|
||||
const uint64_t key_size = user_key.size();
|
||||
@@ -294,6 +294,9 @@ Status BlobFileReader::GetBlob(const ReadOptions& read_options,
|
||||
: 0;
|
||||
assert(offset >= adjustment);
|
||||
|
||||
const uint64_t record_offset = offset - adjustment;
|
||||
const uint64_t record_size = value_size + adjustment;
|
||||
|
||||
Slice record_slice;
|
||||
Buffer buf;
|
||||
AlignedBuf aligned_buf;
|
||||
@@ -301,9 +304,6 @@ Status BlobFileReader::GetBlob(const ReadOptions& read_options,
|
||||
{
|
||||
TEST_SYNC_POINT("BlobFileReader::GetBlob:ReadFromFile");
|
||||
|
||||
const uint64_t record_offset = offset - adjustment;
|
||||
const uint64_t record_size = value_size + adjustment;
|
||||
|
||||
const Status s = ReadFromFile(file_reader_.get(), record_offset,
|
||||
static_cast<size_t>(record_size),
|
||||
&record_slice, &buf, &aligned_buf);
|
||||
@@ -332,6 +332,10 @@ Status BlobFileReader::GetBlob(const ReadOptions& read_options,
|
||||
}
|
||||
}
|
||||
|
||||
if (bytes_read) {
|
||||
*bytes_read = record_size;
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,8 @@ class BlobFileReader {
|
||||
|
||||
Status GetBlob(const ReadOptions& read_options, const Slice& user_key,
|
||||
uint64_t offset, uint64_t value_size,
|
||||
CompressionType compression_type, PinnableSlice* value) const;
|
||||
CompressionType compression_type, PinnableSlice* value,
|
||||
uint64_t* bytes_read) const;
|
||||
|
||||
private:
|
||||
BlobFileReader(std::unique_ptr<RandomAccessFileReader>&& file_reader,
|
||||
|
||||
@@ -50,15 +50,15 @@ void WriteBlobFile(const ImmutableCFOptions& immutable_cf_options,
|
||||
|
||||
std::unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(file), blob_file_path, FileOptions(),
|
||||
immutable_cf_options.env->GetSystemClock()));
|
||||
immutable_cf_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.env->GetSystemClock(),
|
||||
statistics, blob_file_number, use_fsync, do_flush);
|
||||
BlobLogWriter blob_log_writer(std::move(file_writer),
|
||||
immutable_cf_options.clock, statistics,
|
||||
blob_file_number, use_fsync, do_flush);
|
||||
|
||||
BlobLogHeader header(column_family_id, compression_type, has_ttl,
|
||||
expiration_range_header);
|
||||
@@ -152,83 +152,103 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
|
||||
{
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_OK(reader->GetBlob(read_options, key, blob_offset, blob_size,
|
||||
kNoCompression, &value));
|
||||
kNoCompression, &value, &bytes_read));
|
||||
ASSERT_EQ(value, blob);
|
||||
ASSERT_EQ(bytes_read, blob_size);
|
||||
}
|
||||
|
||||
read_options.verify_checksums = true;
|
||||
|
||||
{
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_OK(reader->GetBlob(read_options, key, blob_offset, blob_size,
|
||||
kNoCompression, &value));
|
||||
kNoCompression, &value, &bytes_read));
|
||||
ASSERT_EQ(value, blob);
|
||||
|
||||
constexpr uint64_t key_size = sizeof(key) - 1;
|
||||
ASSERT_EQ(bytes_read,
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(key_size) +
|
||||
blob_size);
|
||||
}
|
||||
|
||||
// Invalid offset (too close to start of file)
|
||||
{
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(read_options, key, blob_offset - 1, blob_size,
|
||||
kNoCompression, &value)
|
||||
kNoCompression, &value, &bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
}
|
||||
|
||||
// Invalid offset (too close to end of file)
|
||||
{
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(read_options, key, blob_offset + 1, blob_size,
|
||||
kNoCompression, &value)
|
||||
kNoCompression, &value, &bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
}
|
||||
|
||||
// Incorrect compression type
|
||||
{
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(
|
||||
reader
|
||||
->GetBlob(read_options, key, blob_offset, blob_size, kZSTD, &value)
|
||||
.IsCorruption());
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(read_options, key, blob_offset, blob_size, kZSTD,
|
||||
&value, &bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
}
|
||||
|
||||
// Incorrect key size
|
||||
{
|
||||
constexpr char shorter_key[] = "k";
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(read_options, shorter_key,
|
||||
blob_offset - (sizeof(key) - sizeof(shorter_key)),
|
||||
blob_size, kNoCompression, &value)
|
||||
blob_size, kNoCompression, &value, &bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
}
|
||||
|
||||
// Incorrect key
|
||||
{
|
||||
constexpr char incorrect_key[] = "foo";
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(read_options, incorrect_key, blob_offset,
|
||||
blob_size, kNoCompression, &value)
|
||||
blob_size, kNoCompression, &value, &bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
}
|
||||
|
||||
// Incorrect value size
|
||||
{
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(read_options, key, blob_offset, blob_size + 1,
|
||||
kNoCompression, &value)
|
||||
kNoCompression, &value, &bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,15 +280,15 @@ TEST_F(BlobFileReaderTest, Malformed) {
|
||||
|
||||
std::unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(file), blob_file_path, FileOptions(),
|
||||
immutable_cf_options.env->GetSystemClock()));
|
||||
immutable_cf_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.env->GetSystemClock(),
|
||||
statistics, blob_file_number, use_fsync, do_flush);
|
||||
BlobLogWriter blob_log_writer(std::move(file_writer),
|
||||
immutable_cf_options.clock, statistics,
|
||||
blob_file_number, use_fsync, do_flush);
|
||||
|
||||
BlobLogHeader header(column_family_id, kNoCompression, has_ttl,
|
||||
expiration_range);
|
||||
@@ -479,11 +499,13 @@ TEST_F(BlobFileReaderTest, BlobCRCError) {
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(ReadOptions(), key, blob_offset, blob_size,
|
||||
kNoCompression, &value)
|
||||
kNoCompression, &value, &bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
@@ -530,20 +552,28 @@ TEST_F(BlobFileReaderTest, Compression) {
|
||||
|
||||
{
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_OK(reader->GetBlob(read_options, key, blob_offset, blob_size,
|
||||
kSnappyCompression, &value));
|
||||
kSnappyCompression, &value, &bytes_read));
|
||||
ASSERT_EQ(value, blob);
|
||||
ASSERT_EQ(bytes_read, blob_size);
|
||||
}
|
||||
|
||||
read_options.verify_checksums = true;
|
||||
|
||||
{
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_OK(reader->GetBlob(read_options, key, blob_offset, blob_size,
|
||||
kSnappyCompression, &value));
|
||||
kSnappyCompression, &value, &bytes_read));
|
||||
ASSERT_EQ(value, blob);
|
||||
|
||||
constexpr uint64_t key_size = sizeof(key) - 1;
|
||||
ASSERT_EQ(bytes_read,
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(key_size) +
|
||||
blob_size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -596,11 +626,13 @@ TEST_F(BlobFileReaderTest, UncompressionError) {
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(ReadOptions(), key, blob_offset, blob_size,
|
||||
kSnappyCompression, &value)
|
||||
kSnappyCompression, &value, &bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
@@ -678,11 +710,13 @@ TEST_P(BlobFileReaderIOErrorTest, IOError) {
|
||||
ASSERT_OK(s);
|
||||
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(ReadOptions(), key, blob_offset, blob_size,
|
||||
kNoCompression, &value)
|
||||
kNoCompression, &value, &bytes_read)
|
||||
.IsIOError());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
}
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
@@ -758,11 +792,13 @@ TEST_P(BlobFileReaderDecodingErrorTest, DecodingError) {
|
||||
ASSERT_OK(s);
|
||||
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(ReadOptions(), key, blob_offset, blob_size,
|
||||
kNoCompression, &value)
|
||||
kNoCompression, &value, &bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
}
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
BlobLogSequentialReader::BlobLogSequentialReader(
|
||||
std::unique_ptr<RandomAccessFileReader>&& file_reader,
|
||||
const std::shared_ptr<SystemClock>& clock, Statistics* statistics)
|
||||
std::unique_ptr<RandomAccessFileReader>&& file_reader, SystemClock* clock,
|
||||
Statistics* statistics)
|
||||
: file_(std::move(file_reader)),
|
||||
clock_(clock),
|
||||
statistics_(statistics),
|
||||
|
||||
@@ -36,8 +36,7 @@ class BlobLogSequentialReader {
|
||||
|
||||
// Create a reader that will return log records from "*file_reader".
|
||||
BlobLogSequentialReader(std::unique_ptr<RandomAccessFileReader>&& file_reader,
|
||||
const std::shared_ptr<SystemClock>& clock,
|
||||
Statistics* statistics);
|
||||
SystemClock* clock, Statistics* statistics);
|
||||
|
||||
// No copying allowed
|
||||
BlobLogSequentialReader(const BlobLogSequentialReader&) = delete;
|
||||
@@ -65,7 +64,7 @@ class BlobLogSequentialReader {
|
||||
Status ReadSlice(uint64_t size, Slice* slice, char* buf);
|
||||
|
||||
const std::unique_ptr<RandomAccessFileReader> file_;
|
||||
std::shared_ptr<SystemClock> clock_;
|
||||
SystemClock* clock_;
|
||||
|
||||
Statistics* statistics_;
|
||||
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
BlobLogWriter::BlobLogWriter(std::unique_ptr<WritableFileWriter>&& dest,
|
||||
const std::shared_ptr<SystemClock>& clock,
|
||||
Statistics* statistics, uint64_t log_number,
|
||||
bool use_fs, bool do_flush, uint64_t boffset)
|
||||
SystemClock* clock, Statistics* statistics,
|
||||
uint64_t log_number, bool use_fs, bool do_flush,
|
||||
uint64_t boffset)
|
||||
: dest_(std::move(dest)),
|
||||
clock_(clock),
|
||||
statistics_(statistics),
|
||||
|
||||
@@ -31,8 +31,7 @@ class BlobLogWriter {
|
||||
// Create a writer that will append data to "*dest".
|
||||
// "*dest" must be initially empty.
|
||||
// "*dest" must remain live while this BlobLogWriter is in use.
|
||||
BlobLogWriter(std::unique_ptr<WritableFileWriter>&& dest,
|
||||
const std::shared_ptr<SystemClock>& clock,
|
||||
BlobLogWriter(std::unique_ptr<WritableFileWriter>&& dest, SystemClock* clock,
|
||||
Statistics* statistics, uint64_t log_number, bool use_fsync,
|
||||
bool do_flush, uint64_t boffset = 0);
|
||||
// No copying allowed
|
||||
@@ -69,7 +68,7 @@ class BlobLogWriter {
|
||||
|
||||
private:
|
||||
std::unique_ptr<WritableFileWriter> dest_;
|
||||
std::shared_ptr<SystemClock> clock_;
|
||||
SystemClock* clock_;
|
||||
Statistics* statistics_;
|
||||
uint64_t log_number_;
|
||||
uint64_t block_offset_; // Current offset in block
|
||||
|
||||
@@ -223,7 +223,7 @@ TEST_F(DBBlobBasicTest, GenerateIOTracing) {
|
||||
std::unique_ptr<TraceWriter> trace_writer;
|
||||
ASSERT_OK(
|
||||
NewFileTraceWriter(env_, EnvOptions(), trace_file, &trace_writer));
|
||||
ASSERT_OK(db_->StartIOTrace(env_, TraceOptions(), std::move(trace_writer)));
|
||||
ASSERT_OK(db_->StartIOTrace(TraceOptions(), std::move(trace_writer)));
|
||||
|
||||
constexpr char key[] = "key";
|
||||
constexpr char blob_value[] = "blob_value";
|
||||
@@ -236,7 +236,7 @@ TEST_F(DBBlobBasicTest, GenerateIOTracing) {
|
||||
ASSERT_OK(env_->FileExists(trace_file));
|
||||
}
|
||||
{
|
||||
// Parse trace file to check file opertions related to blob files are
|
||||
// Parse trace file to check file operations related to blob files are
|
||||
// recorded.
|
||||
std::unique_ptr<TraceReader> trace_reader;
|
||||
ASSERT_OK(
|
||||
@@ -267,6 +267,49 @@ TEST_F(DBBlobBasicTest, GenerateIOTracing) {
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBBlobBasicTest, BestEffortsRecovery_MissingNewestBlobFile) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
options.create_if_missing = true;
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(dbfull()->DisableFileDeletions());
|
||||
constexpr int kNumTableFiles = 2;
|
||||
for (int i = 0; i < kNumTableFiles; ++i) {
|
||||
for (char ch = 'a'; ch != 'c'; ++ch) {
|
||||
std::string key(1, ch);
|
||||
ASSERT_OK(Put(key, "value" + std::to_string(i)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
Close();
|
||||
|
||||
std::vector<std::string> files;
|
||||
ASSERT_OK(env_->GetChildren(dbname_, &files));
|
||||
std::string blob_file_path;
|
||||
uint64_t max_blob_file_num = kInvalidBlobFileNumber;
|
||||
for (const auto& fname : files) {
|
||||
uint64_t file_num = 0;
|
||||
FileType type;
|
||||
if (ParseFileName(fname, &file_num, /*info_log_name_prefix=*/"", &type) &&
|
||||
type == kBlobFile) {
|
||||
if (file_num > max_blob_file_num) {
|
||||
max_blob_file_num = file_num;
|
||||
blob_file_path = dbname_ + "/" + fname;
|
||||
}
|
||||
}
|
||||
}
|
||||
ASSERT_OK(env_->DeleteFile(blob_file_path));
|
||||
|
||||
options.best_efforts_recovery = true;
|
||||
Reopen(options);
|
||||
std::string value;
|
||||
ASSERT_OK(db_->Get(ReadOptions(), "a", &value));
|
||||
ASSERT_EQ("value" + std::to_string(kNumTableFiles - 2), value);
|
||||
}
|
||||
|
||||
class DBBlobBasicIOErrorTest : public DBBlobBasicTest,
|
||||
public testing::WithParamInterface<std::string> {
|
||||
protected:
|
||||
@@ -355,6 +398,59 @@ TEST_P(DBBlobBasicIOErrorTest, MultiGetBlobs_IOError) {
|
||||
ASSERT_TRUE(statuses[1].IsIOError());
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class ReadBlobCompactionFilter : public CompactionFilter {
|
||||
public:
|
||||
ReadBlobCompactionFilter() = default;
|
||||
const char* Name() const override {
|
||||
return "rocksdb.compaction.filter.read.blob";
|
||||
}
|
||||
CompactionFilter::Decision FilterV2(
|
||||
int /*level*/, const Slice& /*key*/, ValueType value_type,
|
||||
const Slice& existing_value, std::string* new_value,
|
||||
std::string* /*skip_until*/) const override {
|
||||
if (value_type != CompactionFilter::ValueType::kValue) {
|
||||
return CompactionFilter::Decision::kKeep;
|
||||
}
|
||||
assert(new_value);
|
||||
new_value->assign(existing_value.data(), existing_value.size());
|
||||
return CompactionFilter::Decision::kChangeValue;
|
||||
}
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
TEST_P(DBBlobBasicIOErrorTest, CompactionFilterReadBlob_IOError) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_injection_env_.get();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
options.create_if_missing = true;
|
||||
std::unique_ptr<CompactionFilter> compaction_filter_guard(
|
||||
new ReadBlobCompactionFilter);
|
||||
options.compaction_filter = compaction_filter_guard.get();
|
||||
|
||||
DestroyAndReopen(options);
|
||||
constexpr char key[] = "foo";
|
||||
constexpr char blob_value[] = "foo_blob_value";
|
||||
ASSERT_OK(Put(key, blob_value));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(sync_point_, [this](void* /* arg */) {
|
||||
fault_injection_env_->SetFilesystemActive(false,
|
||||
Status::IOError(sync_point_));
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_TRUE(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr)
|
||||
.IsIOError());
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -0,0 +1,399 @@
|
||||
// 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_index.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "utilities/fault_injection_env.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class DBBlobCompactionTest : public DBTestBase {
|
||||
public:
|
||||
explicit DBBlobCompactionTest()
|
||||
: DBTestBase("/db_blob_compaction_test", /*env_do_fsync=*/false) {}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
const std::vector<InternalStats::CompactionStats>& GetCompactionStats() {
|
||||
VersionSet* const versions = dbfull()->TEST_GetVersionSet();
|
||||
assert(versions);
|
||||
assert(versions->GetColumnFamilySet());
|
||||
|
||||
ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault();
|
||||
assert(cfd);
|
||||
|
||||
const InternalStats* const internal_stats = cfd->internal_stats();
|
||||
assert(internal_stats);
|
||||
|
||||
return internal_stats->TEST_GetCompactionStats();
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
class FilterByKeyLength : public CompactionFilter {
|
||||
public:
|
||||
explicit FilterByKeyLength(size_t len) : length_threshold_(len) {}
|
||||
const char* Name() const override {
|
||||
return "rocksdb.compaction.filter.by.key.length";
|
||||
}
|
||||
CompactionFilter::Decision FilterBlobByKey(
|
||||
int /*level*/, const Slice& key, std::string* /*new_value*/,
|
||||
std::string* /*skip_until*/) const override {
|
||||
if (key.size() < length_threshold_) {
|
||||
return CompactionFilter::Decision::kRemove;
|
||||
}
|
||||
return CompactionFilter::Decision::kKeep;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t length_threshold_;
|
||||
};
|
||||
|
||||
class BadBlobCompactionFilter : public CompactionFilter {
|
||||
public:
|
||||
explicit BadBlobCompactionFilter(std::string prefix,
|
||||
CompactionFilter::Decision filter_by_key,
|
||||
CompactionFilter::Decision filter_v2)
|
||||
: prefix_(std::move(prefix)),
|
||||
filter_blob_by_key_(filter_by_key),
|
||||
filter_v2_(filter_v2) {}
|
||||
const char* Name() const override { return "rocksdb.compaction.filter.bad"; }
|
||||
CompactionFilter::Decision FilterBlobByKey(
|
||||
int /*level*/, const Slice& key, std::string* /*new_value*/,
|
||||
std::string* /*skip_until*/) const override {
|
||||
if (key.size() >= prefix_.size() &&
|
||||
0 == strncmp(prefix_.data(), key.data(), prefix_.size())) {
|
||||
return CompactionFilter::Decision::kUndetermined;
|
||||
}
|
||||
return filter_blob_by_key_;
|
||||
}
|
||||
CompactionFilter::Decision FilterV2(
|
||||
int /*level*/, const Slice& /*key*/, ValueType /*value_type*/,
|
||||
const Slice& /*existing_value*/, std::string* /*new_value*/,
|
||||
std::string* /*skip_until*/) const override {
|
||||
return filter_v2_;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::string prefix_;
|
||||
const CompactionFilter::Decision filter_blob_by_key_;
|
||||
const CompactionFilter::Decision filter_v2_;
|
||||
};
|
||||
|
||||
class ValueBlindWriteFilter : public CompactionFilter {
|
||||
public:
|
||||
explicit ValueBlindWriteFilter(std::string new_val)
|
||||
: new_value_(std::move(new_val)) {}
|
||||
const char* Name() const override {
|
||||
return "rocksdb.compaction.filter.blind.write";
|
||||
}
|
||||
CompactionFilter::Decision FilterBlobByKey(
|
||||
int level, const Slice& key, std::string* new_value,
|
||||
std::string* skip_until) const override;
|
||||
|
||||
private:
|
||||
const std::string new_value_;
|
||||
};
|
||||
|
||||
CompactionFilter::Decision ValueBlindWriteFilter::FilterBlobByKey(
|
||||
int /*level*/, const Slice& /*key*/, std::string* new_value,
|
||||
std::string* /*skip_until*/) const {
|
||||
assert(new_value);
|
||||
new_value->assign(new_value_);
|
||||
return CompactionFilter::Decision::kChangeValue;
|
||||
}
|
||||
|
||||
class ValueMutationFilter : public CompactionFilter {
|
||||
public:
|
||||
explicit ValueMutationFilter(std::string padding)
|
||||
: padding_(std::move(padding)) {}
|
||||
const char* Name() const override {
|
||||
return "rocksdb.compaction.filter.value.mutation";
|
||||
}
|
||||
CompactionFilter::Decision FilterV2(int level, const Slice& key,
|
||||
ValueType value_type,
|
||||
const Slice& existing_value,
|
||||
std::string* new_value,
|
||||
std::string* skip_until) const override;
|
||||
|
||||
private:
|
||||
const std::string padding_;
|
||||
};
|
||||
|
||||
CompactionFilter::Decision ValueMutationFilter::FilterV2(
|
||||
int /*level*/, const Slice& /*key*/, ValueType value_type,
|
||||
const Slice& existing_value, std::string* new_value,
|
||||
std::string* /*skip_until*/) const {
|
||||
assert(CompactionFilter::ValueType::kBlobIndex != value_type);
|
||||
if (CompactionFilter::ValueType::kValue != value_type) {
|
||||
return CompactionFilter::Decision::kKeep;
|
||||
}
|
||||
assert(new_value);
|
||||
new_value->assign(existing_value.data(), existing_value.size());
|
||||
new_value->append(padding_);
|
||||
return CompactionFilter::Decision::kChangeValue;
|
||||
}
|
||||
|
||||
class AlwaysKeepFilter : public CompactionFilter {
|
||||
public:
|
||||
explicit AlwaysKeepFilter() = default;
|
||||
const char* Name() const override {
|
||||
return "rocksdb.compaction.filter.always.keep";
|
||||
}
|
||||
CompactionFilter::Decision FilterV2(
|
||||
int /*level*/, const Slice& /*key*/, ValueType /*value_type*/,
|
||||
const Slice& /*existing_value*/, std::string* /*new_value*/,
|
||||
std::string* /*skip_until*/) const override {
|
||||
return CompactionFilter::Decision::kKeep;
|
||||
}
|
||||
};
|
||||
} // anonymous namespace
|
||||
|
||||
class DBBlobBadCompactionFilterTest
|
||||
: public DBBlobCompactionTest,
|
||||
public testing::WithParamInterface<
|
||||
std::tuple<std::string, CompactionFilter::Decision,
|
||||
CompactionFilter::Decision>> {
|
||||
public:
|
||||
explicit DBBlobBadCompactionFilterTest()
|
||||
: compaction_filter_guard_(new BadBlobCompactionFilter(
|
||||
std::get<0>(GetParam()), std::get<1>(GetParam()),
|
||||
std::get<2>(GetParam()))) {}
|
||||
|
||||
protected:
|
||||
std::unique_ptr<CompactionFilter> compaction_filter_guard_;
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
BadCompactionFilter, DBBlobBadCompactionFilterTest,
|
||||
testing::Combine(
|
||||
testing::Values("a"),
|
||||
testing::Values(CompactionFilter::Decision::kChangeBlobIndex,
|
||||
CompactionFilter::Decision::kIOError),
|
||||
testing::Values(CompactionFilter::Decision::kUndetermined,
|
||||
CompactionFilter::Decision::kChangeBlobIndex,
|
||||
CompactionFilter::Decision::kIOError)));
|
||||
|
||||
TEST_F(DBBlobCompactionTest, FilterByKeyLength) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
options.create_if_missing = true;
|
||||
constexpr size_t kKeyLength = 2;
|
||||
std::unique_ptr<CompactionFilter> compaction_filter_guard(
|
||||
new FilterByKeyLength(kKeyLength));
|
||||
options.compaction_filter = compaction_filter_guard.get();
|
||||
|
||||
constexpr char short_key[] = "a";
|
||||
constexpr char long_key[] = "abc";
|
||||
constexpr char blob_value[] = "value";
|
||||
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put(short_key, blob_value));
|
||||
ASSERT_OK(Put(long_key, blob_value));
|
||||
ASSERT_OK(Flush());
|
||||
CompactRangeOptions cro;
|
||||
ASSERT_OK(db_->CompactRange(cro, /*begin=*/nullptr, /*end=*/nullptr));
|
||||
std::string value;
|
||||
ASSERT_TRUE(db_->Get(ReadOptions(), short_key, &value).IsNotFound());
|
||||
value.clear();
|
||||
ASSERT_OK(db_->Get(ReadOptions(), long_key, &value));
|
||||
ASSERT_EQ("value", value);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
const auto& compaction_stats = GetCompactionStats();
|
||||
ASSERT_GE(compaction_stats.size(), 2);
|
||||
|
||||
// Filter decides between kKeep and kRemove solely based on key;
|
||||
// this involves neither reading nor writing blobs
|
||||
ASSERT_EQ(compaction_stats[1].bytes_read_blob, 0);
|
||||
ASSERT_EQ(compaction_stats[1].bytes_written_blob, 0);
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBlobCompactionTest, BlindWriteFilter) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
options.create_if_missing = true;
|
||||
constexpr char new_blob_value[] = "new_blob_value";
|
||||
std::unique_ptr<CompactionFilter> compaction_filter_guard(
|
||||
new ValueBlindWriteFilter(new_blob_value));
|
||||
options.compaction_filter = compaction_filter_guard.get();
|
||||
DestroyAndReopen(options);
|
||||
const std::vector<std::string> keys = {"a", "b", "c"};
|
||||
const std::vector<std::string> values = {"a_value", "b_value", "c_value"};
|
||||
assert(keys.size() == values.size());
|
||||
for (size_t i = 0; i < keys.size(); ++i) {
|
||||
ASSERT_OK(Put(keys[i], values[i]));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr));
|
||||
for (const auto& key : keys) {
|
||||
ASSERT_EQ(new_blob_value, Get(key));
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
const auto& compaction_stats = GetCompactionStats();
|
||||
ASSERT_GE(compaction_stats.size(), 2);
|
||||
|
||||
// Filter unconditionally changes value in FilterBlobByKey;
|
||||
// this involves writing but not reading blobs
|
||||
ASSERT_EQ(compaction_stats[1].bytes_read_blob, 0);
|
||||
ASSERT_GT(compaction_stats[1].bytes_written_blob, 0);
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_P(DBBlobBadCompactionFilterTest, BadDecisionFromCompactionFilter) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
options.create_if_missing = true;
|
||||
options.compaction_filter = compaction_filter_guard_.get();
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("b", "value"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_TRUE(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr)
|
||||
.IsNotSupported());
|
||||
Close();
|
||||
|
||||
DestroyAndReopen(options);
|
||||
std::string key(std::get<0>(GetParam()));
|
||||
ASSERT_OK(Put(key, "value"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_TRUE(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr)
|
||||
.IsNotSupported());
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBlobCompactionTest, CompactionFilter_InlinedTTLIndex) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
std::unique_ptr<CompactionFilter> compaction_filter_guard(
|
||||
new ValueMutationFilter(""));
|
||||
options.compaction_filter = compaction_filter_guard.get();
|
||||
DestroyAndReopen(options);
|
||||
constexpr char key[] = "key";
|
||||
constexpr char blob[] = "blob";
|
||||
// Fake an inlined TTL blob index.
|
||||
std::string blob_index;
|
||||
constexpr uint64_t expiration = 1234567890;
|
||||
BlobIndex::EncodeInlinedTTL(&blob_index, expiration, blob);
|
||||
WriteBatch batch;
|
||||
ASSERT_OK(WriteBatchInternal::PutBlobIndex(&batch, 0, key, blob_index));
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &batch));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_TRUE(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr)
|
||||
.IsCorruption());
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBlobCompactionTest, CompactionFilter) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
constexpr char padding[] = "_delta";
|
||||
std::unique_ptr<CompactionFilter> compaction_filter_guard(
|
||||
new ValueMutationFilter(padding));
|
||||
options.compaction_filter = compaction_filter_guard.get();
|
||||
DestroyAndReopen(options);
|
||||
const std::vector<std::pair<std::string, std::string>> kvs = {
|
||||
{"a", "a_value"}, {"b", "b_value"}, {"c", "c_value"}};
|
||||
for (const auto& kv : kvs) {
|
||||
ASSERT_OK(Put(kv.first, kv.second));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr));
|
||||
for (const auto& kv : kvs) {
|
||||
ASSERT_EQ(kv.second + std::string(padding), Get(kv.first));
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
const auto& compaction_stats = GetCompactionStats();
|
||||
ASSERT_GE(compaction_stats.size(), 2);
|
||||
|
||||
// Filter changes the value using the previous value in FilterV2;
|
||||
// this involves reading and writing blobs
|
||||
ASSERT_GT(compaction_stats[1].bytes_read_blob, 0);
|
||||
ASSERT_GT(compaction_stats[1].bytes_written_blob, 0);
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBlobCompactionTest, CorruptedBlobIndex) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
std::unique_ptr<CompactionFilter> compaction_filter_guard(
|
||||
new ValueMutationFilter(""));
|
||||
options.compaction_filter = compaction_filter_guard.get();
|
||||
DestroyAndReopen(options);
|
||||
// Mock a corrupted blob index
|
||||
constexpr char key[] = "key";
|
||||
std::string blob_idx("blob_idx");
|
||||
WriteBatch write_batch;
|
||||
ASSERT_OK(WriteBatchInternal::PutBlobIndex(&write_batch, 0, key, blob_idx));
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &write_batch));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_TRUE(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr)
|
||||
.IsCorruption());
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBlobCompactionTest, CompactionFilterReadBlobAndKeep) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
std::unique_ptr<CompactionFilter> compaction_filter_guard(
|
||||
new AlwaysKeepFilter());
|
||||
options.compaction_filter = compaction_filter_guard.get();
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("foo", "foo_value"));
|
||||
ASSERT_OK(Flush());
|
||||
std::vector<uint64_t> blob_files = GetBlobFileNumbers();
|
||||
ASSERT_EQ(1, blob_files.size());
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr));
|
||||
ASSERT_EQ(blob_files, GetBlobFileNumbers());
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
const auto& compaction_stats = GetCompactionStats();
|
||||
ASSERT_GE(compaction_stats.size(), 2);
|
||||
|
||||
// Filter decides to keep the existing value in FilterV2;
|
||||
// this involves reading but not writing blobs
|
||||
ASSERT_GT(compaction_stats[1].bytes_read_blob, 0);
|
||||
ASSERT_EQ(compaction_stats[1].bytes_written_blob, 0);
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// 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/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "test_util/sync_point.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class DBBlobCorruptionTest : public DBTestBase {
|
||||
protected:
|
||||
DBBlobCorruptionTest()
|
||||
: DBTestBase("/db_blob_corruption_test", /* env_do_fsync */ false) {}
|
||||
|
||||
void Corrupt(FileType filetype, int offset, int bytes_to_corrupt) {
|
||||
// Pick file to corrupt
|
||||
std::vector<std::string> filenames;
|
||||
ASSERT_OK(env_->GetChildren(dbname_, &filenames));
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
std::string fname;
|
||||
uint64_t picked_number = kInvalidBlobFileNumber;
|
||||
for (size_t i = 0; i < filenames.size(); i++) {
|
||||
if (ParseFileName(filenames[i], &number, &type) && type == filetype &&
|
||||
number > picked_number) { // Pick latest file
|
||||
fname = dbname_ + "/" + filenames[i];
|
||||
picked_number = number;
|
||||
}
|
||||
}
|
||||
ASSERT_TRUE(!fname.empty()) << filetype;
|
||||
ASSERT_OK(test::CorruptFile(env_, fname, offset, bytes_to_corrupt));
|
||||
}
|
||||
};
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBBlobCorruptionTest, VerifyWholeBlobFileChecksum) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
options.create_if_missing = true;
|
||||
options.file_checksum_gen_factory =
|
||||
ROCKSDB_NAMESPACE::GetFileChecksumGenCrc32cFactory();
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(Put(Slice("key_1"), Slice("blob_value_1")));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put(Slice("key_2"), Slice("blob_value_2")));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(db_->VerifyFileChecksums(ReadOptions()));
|
||||
Close();
|
||||
|
||||
Corrupt(kBlobFile, 0, 2);
|
||||
|
||||
ASSERT_OK(TryReopen(options));
|
||||
|
||||
int count{0};
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::VerifyFullFileChecksum:mismatch", [&](void* arg) {
|
||||
const Status* s = static_cast<Status*>(arg);
|
||||
ASSERT_NE(s, nullptr);
|
||||
++count;
|
||||
ASSERT_NOK(*s);
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_TRUE(db_->VerifyFileChecksums(ReadOptions()).IsCorruption());
|
||||
ASSERT_EQ(1, count);
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
+29
-27
@@ -23,6 +23,7 @@
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "db/table_cache.h"
|
||||
#include "db/version_edit.h"
|
||||
#include "file/file_util.h"
|
||||
#include "file/filename.h"
|
||||
#include "file/read_write_util.h"
|
||||
#include "file/writable_file_writer.h"
|
||||
@@ -51,8 +52,8 @@ TableBuilder* NewTableBuilder(
|
||||
int_tbl_prop_collector_factories,
|
||||
uint32_t column_family_id, const std::string& column_family_name,
|
||||
WritableFileWriter* file, const CompressionType compression_type,
|
||||
uint64_t sample_for_compression, const CompressionOptions& compression_opts,
|
||||
int level, const bool skip_filters, const uint64_t creation_time,
|
||||
const CompressionOptions& compression_opts, int level,
|
||||
const bool skip_filters, const uint64_t creation_time,
|
||||
const uint64_t oldest_key_time, const uint64_t target_file_size,
|
||||
const uint64_t file_creation_time, const std::string& db_id,
|
||||
const std::string& db_session_id) {
|
||||
@@ -62,10 +63,10 @@ TableBuilder* NewTableBuilder(
|
||||
return ioptions.table_factory->NewTableBuilder(
|
||||
TableBuilderOptions(ioptions, moptions, internal_comparator,
|
||||
int_tbl_prop_collector_factories, compression_type,
|
||||
sample_for_compression, compression_opts,
|
||||
skip_filters, column_family_name, level,
|
||||
creation_time, oldest_key_time, target_file_size,
|
||||
file_creation_time, db_id, db_session_id),
|
||||
compression_opts, skip_filters, column_family_name,
|
||||
level, creation_time, oldest_key_time,
|
||||
target_file_size, file_creation_time, db_id,
|
||||
db_session_id),
|
||||
column_family_id, file);
|
||||
}
|
||||
|
||||
@@ -84,15 +85,15 @@ Status BuildTable(
|
||||
std::vector<SequenceNumber> snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
SnapshotChecker* snapshot_checker, const CompressionType compression,
|
||||
uint64_t sample_for_compression, const CompressionOptions& compression_opts,
|
||||
bool paranoid_file_checks, InternalStats* internal_stats,
|
||||
TableFileCreationReason reason, IOStatus* io_status,
|
||||
const std::shared_ptr<IOTracer>& io_tracer, EventLogger* event_logger,
|
||||
int job_id, const Env::IOPriority io_priority,
|
||||
const CompressionOptions& compression_opts, bool paranoid_file_checks,
|
||||
InternalStats* internal_stats, TableFileCreationReason reason,
|
||||
IOStatus* io_status, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
EventLogger* event_logger, int job_id, const Env::IOPriority io_priority,
|
||||
TableProperties* table_properties, int level, const uint64_t creation_time,
|
||||
const uint64_t oldest_key_time, Env::WriteLifeTimeHint write_hint,
|
||||
const uint64_t file_creation_time, const std::string& db_id,
|
||||
const std::string& db_session_id, const std::string* full_history_ts_low) {
|
||||
const std::string& db_session_id, const std::string* full_history_ts_low,
|
||||
BlobFileCompletionCallback* blob_callback) {
|
||||
assert((column_family_id ==
|
||||
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily) ==
|
||||
column_family_name.empty());
|
||||
@@ -125,7 +126,6 @@ Status BuildTable(
|
||||
assert(env);
|
||||
FileSystem* fs = db_options.fs.get();
|
||||
assert(fs);
|
||||
const auto& clock = env->GetSystemClock();
|
||||
|
||||
TableProperties tp;
|
||||
if (iter->Valid() || !range_del_agg->IsEmpty()) {
|
||||
@@ -154,33 +154,32 @@ Status BuildTable(
|
||||
file->SetIOPriority(io_priority);
|
||||
file->SetWriteLifeTimeHint(write_hint);
|
||||
file_writer.reset(new WritableFileWriter(
|
||||
std::move(file), fname, file_options, clock, io_tracer,
|
||||
std::move(file), fname, file_options, ioptions.clock, io_tracer,
|
||||
ioptions.statistics, ioptions.listeners,
|
||||
ioptions.file_checksum_gen_factory,
|
||||
ioptions.file_checksum_gen_factory.get(),
|
||||
tmp_set.Contains(FileType::kTableFile)));
|
||||
|
||||
builder = NewTableBuilder(
|
||||
ioptions, mutable_cf_options, internal_comparator,
|
||||
int_tbl_prop_collector_factories, column_family_id,
|
||||
column_family_name, file_writer.get(), compression,
|
||||
sample_for_compression, compression_opts, level,
|
||||
false /* skip_filters */, creation_time, oldest_key_time,
|
||||
column_family_name, file_writer.get(), compression, compression_opts,
|
||||
level, false /* skip_filters */, creation_time, oldest_key_time,
|
||||
0 /*target_file_size*/, file_creation_time, db_id, db_session_id);
|
||||
}
|
||||
|
||||
MergeHelper merge(env, internal_comparator.user_comparator(),
|
||||
ioptions.merge_operator, nullptr, ioptions.info_log,
|
||||
ioptions.merge_operator.get(), nullptr, ioptions.info_log,
|
||||
true /* internal key corruption is not ok */,
|
||||
snapshots.empty() ? 0 : snapshots.back(),
|
||||
snapshot_checker);
|
||||
|
||||
std::unique_ptr<BlobFileBuilder> blob_file_builder(
|
||||
(mutable_cf_options.enable_blob_files && blob_file_additions)
|
||||
? new BlobFileBuilder(versions, env, fs, &ioptions,
|
||||
&mutable_cf_options, &file_options, job_id,
|
||||
column_family_id, column_family_name,
|
||||
io_priority, write_hint, io_tracer,
|
||||
&blob_file_paths, blob_file_additions)
|
||||
? new BlobFileBuilder(versions, fs, &ioptions, &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)
|
||||
: nullptr);
|
||||
|
||||
CompactionIterator c_iter(
|
||||
@@ -258,7 +257,7 @@ Status BuildTable(
|
||||
// Finish and check for file errors
|
||||
TEST_SYNC_POINT("BuildTable:BeforeSyncTable");
|
||||
if (s.ok() && !empty) {
|
||||
StopWatch sw(clock, ioptions.statistics, TABLE_SYNC_MICROS);
|
||||
StopWatch sw(ioptions.clock, ioptions.statistics, TABLE_SYNC_MICROS);
|
||||
*io_status = file_writer->Sync(ioptions.use_fsync);
|
||||
}
|
||||
TEST_SYNC_POINT("BuildTable:BeforeCloseTableFile");
|
||||
@@ -280,8 +279,9 @@ Status BuildTable(
|
||||
if (blob_file_builder) {
|
||||
if (s.ok()) {
|
||||
s = blob_file_builder->Finish();
|
||||
} else {
|
||||
blob_file_builder->Abandon();
|
||||
}
|
||||
|
||||
blob_file_builder.reset();
|
||||
}
|
||||
|
||||
@@ -341,8 +341,10 @@ Status BuildTable(
|
||||
|
||||
if (blob_file_additions) {
|
||||
for (const std::string& blob_file_path : blob_file_paths) {
|
||||
ignored = fs->DeleteFile(blob_file_path, IOOptions(), dbg);
|
||||
ignored = DeleteDBFile(&db_options, blob_file_path, dbname,
|
||||
/*force_bg=*/false, /*force_fg=*/false);
|
||||
ignored.PermitUncheckedError();
|
||||
TEST_SYNC_POINT("BuildTable::AfterDeleteFile");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -38,6 +38,7 @@ class VersionEdit;
|
||||
class TableBuilder;
|
||||
class WritableFileWriter;
|
||||
class InternalStats;
|
||||
class BlobFileCompletionCallback;
|
||||
|
||||
// @param column_family_name Name of the column family that is also identified
|
||||
// by column_family_id, or empty string if unknown. It must outlive the
|
||||
@@ -49,7 +50,6 @@ TableBuilder* NewTableBuilder(
|
||||
int_tbl_prop_collector_factories,
|
||||
uint32_t column_family_id, const std::string& column_family_name,
|
||||
WritableFileWriter* file, const CompressionType compression_type,
|
||||
const uint64_t sample_for_compression,
|
||||
const CompressionOptions& compression_opts, int level,
|
||||
const bool skip_filters = false, const uint64_t creation_time = 0,
|
||||
const uint64_t oldest_key_time = 0, const uint64_t target_file_size = 0,
|
||||
@@ -79,7 +79,6 @@ extern Status BuildTable(
|
||||
std::vector<SequenceNumber> snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
SnapshotChecker* snapshot_checker, const CompressionType compression,
|
||||
const uint64_t sample_for_compression,
|
||||
const CompressionOptions& compression_opts, bool paranoid_file_checks,
|
||||
InternalStats* internal_stats, TableFileCreationReason reason,
|
||||
IOStatus* io_status, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
@@ -90,6 +89,7 @@ extern Status BuildTable(
|
||||
Env::WriteLifeTimeHint write_hint = Env::WLTH_NOT_SET,
|
||||
const uint64_t file_creation_time = 0, const std::string& db_id = "",
|
||||
const std::string& db_session_id = "",
|
||||
const std::string* full_history_ts_low = nullptr);
|
||||
const std::string* full_history_ts_low = nullptr,
|
||||
BlobFileCompletionCallback* blob_callback = nullptr);
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -11,7 +11,11 @@
|
||||
|
||||
#include "rocksdb/c.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <cstdlib>
|
||||
#include <map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/compaction_filter.h"
|
||||
@@ -24,6 +28,7 @@
|
||||
#include "rocksdb/memtablerep.h"
|
||||
#include "rocksdb/merge_operator.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/perf_context.h"
|
||||
#include "rocksdb/rate_limiter.h"
|
||||
#include "rocksdb/slice_transform.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
@@ -39,13 +44,8 @@
|
||||
#include "rocksdb/utilities/transaction_db.h"
|
||||
#include "rocksdb/utilities/write_batch_with_index.h"
|
||||
#include "rocksdb/write_batch.h"
|
||||
#include "rocksdb/perf_context.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
|
||||
#include <vector>
|
||||
#include <unordered_set>
|
||||
#include <map>
|
||||
|
||||
using ROCKSDB_NAMESPACE::BackupableDBOptions;
|
||||
using ROCKSDB_NAMESPACE::BackupEngine;
|
||||
using ROCKSDB_NAMESPACE::BackupID;
|
||||
@@ -79,6 +79,8 @@ using ROCKSDB_NAMESPACE::IngestExternalFileOptions;
|
||||
using ROCKSDB_NAMESPACE::Iterator;
|
||||
using ROCKSDB_NAMESPACE::LiveFileMetaData;
|
||||
using ROCKSDB_NAMESPACE::Logger;
|
||||
using ROCKSDB_NAMESPACE::LRUCacheOptions;
|
||||
using ROCKSDB_NAMESPACE::MemoryAllocator;
|
||||
using ROCKSDB_NAMESPACE::MemoryUtil;
|
||||
using ROCKSDB_NAMESPACE::MergeOperator;
|
||||
using ROCKSDB_NAMESPACE::NewBloomFilterPolicy;
|
||||
@@ -150,6 +152,12 @@ struct rocksdb_filelock_t { FileLock* rep; };
|
||||
struct rocksdb_logger_t {
|
||||
std::shared_ptr<Logger> rep;
|
||||
};
|
||||
struct rocksdb_lru_cache_options_t {
|
||||
LRUCacheOptions rep;
|
||||
};
|
||||
struct rocksdb_memory_allocator_t {
|
||||
std::shared_ptr<MemoryAllocator> rep;
|
||||
};
|
||||
struct rocksdb_cache_t {
|
||||
std::shared_ptr<Cache> rep;
|
||||
};
|
||||
@@ -2682,6 +2690,59 @@ unsigned char rocksdb_options_get_skip_checking_sst_file_sizes_on_db_open(
|
||||
return opt->rep.skip_checking_sst_file_sizes_on_db_open;
|
||||
}
|
||||
|
||||
/* Blob Options Settings */
|
||||
void rocksdb_options_set_enable_blob_files(rocksdb_options_t* opt,
|
||||
unsigned char val) {
|
||||
opt->rep.enable_blob_files = val;
|
||||
}
|
||||
extern ROCKSDB_LIBRARY_API unsigned char rocksdb_options_get_enable_blob_files(
|
||||
rocksdb_options_t* opt) {
|
||||
return opt->rep.enable_blob_files;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_min_blob_size(rocksdb_options_t* opt, uint64_t val) {
|
||||
opt->rep.min_blob_size = val;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_options_get_min_blob_size(rocksdb_options_t* opt) {
|
||||
return opt->rep.min_blob_size;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_blob_file_size(rocksdb_options_t* opt, uint64_t val) {
|
||||
opt->rep.blob_file_size = val;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_options_get_blob_file_size(rocksdb_options_t* opt) {
|
||||
return opt->rep.blob_file_size;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_blob_compression_type(rocksdb_options_t* opt,
|
||||
int val) {
|
||||
opt->rep.blob_compression_type = static_cast<CompressionType>(val);
|
||||
}
|
||||
|
||||
int rocksdb_options_get_blob_compression_type(rocksdb_options_t* opt) {
|
||||
return opt->rep.blob_compression_type;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_enable_blob_gc(rocksdb_options_t* opt,
|
||||
unsigned char val) {
|
||||
opt->rep.enable_blob_garbage_collection = val;
|
||||
}
|
||||
|
||||
unsigned char rocksdb_options_get_enable_blob_gc(rocksdb_options_t* opt) {
|
||||
return opt->rep.enable_blob_garbage_collection;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_blob_gc_age_cutoff(rocksdb_options_t* opt,
|
||||
double val) {
|
||||
opt->rep.blob_garbage_collection_age_cutoff = val;
|
||||
}
|
||||
|
||||
double rocksdb_options_get_blob_gc_age_cutoff(rocksdb_options_t* opt) {
|
||||
return opt->rep.blob_garbage_collection_age_cutoff;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_num_levels(rocksdb_options_t* opt, int n) {
|
||||
opt->rep.num_levels = n;
|
||||
}
|
||||
@@ -2959,6 +3020,8 @@ void rocksdb_options_set_access_hint_on_compaction_start(
|
||||
opt->rep.access_hint_on_compaction_start =
|
||||
ROCKSDB_NAMESPACE::Options::WILLNEED;
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4100,16 +4163,58 @@ unsigned char rocksdb_flushoptions_get_wait(rocksdb_flushoptions_t* opt) {
|
||||
return opt->rep.wait;
|
||||
}
|
||||
|
||||
rocksdb_memory_allocator_t* rocksdb_jemalloc_nodump_allocator_create(
|
||||
char** errptr) {
|
||||
rocksdb_memory_allocator_t* allocator = new rocksdb_memory_allocator_t;
|
||||
ROCKSDB_NAMESPACE::JemallocAllocatorOptions options;
|
||||
SaveError(errptr, ROCKSDB_NAMESPACE::NewJemallocNodumpAllocator(
|
||||
options, &allocator->rep));
|
||||
return allocator;
|
||||
}
|
||||
|
||||
void rocksdb_memory_allocator_destroy(rocksdb_memory_allocator_t* allocator) {
|
||||
delete allocator;
|
||||
}
|
||||
|
||||
rocksdb_lru_cache_options_t* rocksdb_lru_cache_options_create() {
|
||||
return new rocksdb_lru_cache_options_t;
|
||||
}
|
||||
|
||||
void rocksdb_lru_cache_options_destroy(rocksdb_lru_cache_options_t* opt) {
|
||||
delete opt;
|
||||
}
|
||||
|
||||
void rocksdb_lru_cache_options_set_capacity(rocksdb_lru_cache_options_t* opt,
|
||||
size_t capacity) {
|
||||
opt->rep.capacity = capacity;
|
||||
}
|
||||
|
||||
void rocksdb_lru_cache_options_set_memory_allocator(
|
||||
rocksdb_lru_cache_options_t* opt, rocksdb_memory_allocator_t* allocator) {
|
||||
opt->rep.memory_allocator = allocator->rep;
|
||||
}
|
||||
|
||||
rocksdb_cache_t* rocksdb_cache_create_lru(size_t capacity) {
|
||||
rocksdb_cache_t* c = new rocksdb_cache_t;
|
||||
c->rep = NewLRUCache(capacity);
|
||||
return c;
|
||||
}
|
||||
|
||||
rocksdb_cache_t* rocksdb_cache_create_lru_opts(
|
||||
rocksdb_lru_cache_options_t* opt) {
|
||||
rocksdb_cache_t* c = new rocksdb_cache_t;
|
||||
c->rep = NewLRUCache(opt->rep);
|
||||
return c;
|
||||
}
|
||||
|
||||
void rocksdb_cache_destroy(rocksdb_cache_t* cache) {
|
||||
delete cache;
|
||||
}
|
||||
|
||||
void rocksdb_cache_disown_data(rocksdb_cache_t* cache) {
|
||||
cache->rep->DisownData();
|
||||
}
|
||||
|
||||
void rocksdb_cache_set_capacity(rocksdb_cache_t* cache, size_t capacity) {
|
||||
cache->rep->SetCapacity(capacity);
|
||||
}
|
||||
@@ -4788,7 +4893,10 @@ void rocksdb_transaction_destroy(rocksdb_transaction_t* txn) {
|
||||
|
||||
const rocksdb_snapshot_t* rocksdb_transaction_get_snapshot(
|
||||
rocksdb_transaction_t* txn) {
|
||||
rocksdb_snapshot_t* result = new rocksdb_snapshot_t;
|
||||
// This will be freed later on using free, so use malloc here to avoid a
|
||||
// mismatch
|
||||
rocksdb_snapshot_t* result =
|
||||
(rocksdb_snapshot_t*)malloc(sizeof(rocksdb_snapshot_t));
|
||||
result->rep = txn->rep->GetSnapshot();
|
||||
return result;
|
||||
}
|
||||
|
||||
+54
-4
@@ -1119,9 +1119,8 @@ int main(int argc, char** argv) {
|
||||
// Essentially a fingerprint of the block-based Bloom schema
|
||||
CheckCondition(hits == 241);
|
||||
} else {
|
||||
// Essentially a fingerprint of the full Bloom schema(s),
|
||||
// format_version < 5, which vary for three different CACHE_LINE_SIZEs
|
||||
CheckCondition(hits == 224 || hits == 180 || hits == 125);
|
||||
// Essentially a fingerprint of full Bloom schema, format_version=5
|
||||
CheckCondition(hits == 188);
|
||||
}
|
||||
CheckCondition(
|
||||
(keys_to_query - hits) ==
|
||||
@@ -1760,6 +1759,25 @@ int main(int argc, char** argv) {
|
||||
rocksdb_options_set_atomic_flush(o, 1);
|
||||
CheckCondition(1 == rocksdb_options_get_atomic_flush(o));
|
||||
|
||||
/* Blob Options */
|
||||
rocksdb_options_set_enable_blob_files(o, 1);
|
||||
CheckCondition(1 == rocksdb_options_get_enable_blob_files(o));
|
||||
|
||||
rocksdb_options_set_min_blob_size(o, 29);
|
||||
CheckCondition(29 == rocksdb_options_get_min_blob_size(o));
|
||||
|
||||
rocksdb_options_set_blob_file_size(o, 30);
|
||||
CheckCondition(30 == rocksdb_options_get_blob_file_size(o));
|
||||
|
||||
rocksdb_options_set_blob_compression_type(o, 4);
|
||||
CheckCondition(4 == rocksdb_options_get_blob_compression_type(o));
|
||||
|
||||
rocksdb_options_set_enable_blob_gc(o, 1);
|
||||
CheckCondition(1 == rocksdb_options_get_enable_blob_gc(o));
|
||||
|
||||
rocksdb_options_set_blob_gc_age_cutoff(o, 0.75);
|
||||
CheckCondition(0.75 == rocksdb_options_get_blob_gc_age_cutoff(o));
|
||||
|
||||
// Create a copy that should be equal to the original.
|
||||
rocksdb_options_t* copy;
|
||||
copy = rocksdb_options_create_copy(o);
|
||||
@@ -2363,6 +2381,37 @@ int main(int argc, char** argv) {
|
||||
rocksdb_cache_destroy(co);
|
||||
}
|
||||
|
||||
StartPhase("jemalloc_nodump_allocator");
|
||||
{
|
||||
rocksdb_memory_allocator_t* allocator;
|
||||
allocator = rocksdb_jemalloc_nodump_allocator_create(&err);
|
||||
if (err != NULL) {
|
||||
// not supported on all platforms, allow unsupported error
|
||||
const char* ni = "Not implemented: ";
|
||||
size_t ni_len = strlen(ni);
|
||||
size_t err_len = strlen(err);
|
||||
|
||||
CheckCondition(err_len >= ni_len);
|
||||
CheckCondition(memcmp(ni, err, ni_len) == 0);
|
||||
Free(&err);
|
||||
} else {
|
||||
rocksdb_cache_t* co;
|
||||
rocksdb_lru_cache_options_t* copts;
|
||||
|
||||
copts = rocksdb_lru_cache_options_create();
|
||||
|
||||
rocksdb_lru_cache_options_set_capacity(copts, 100);
|
||||
rocksdb_lru_cache_options_set_memory_allocator(copts, allocator);
|
||||
|
||||
co = rocksdb_cache_create_lru_opts(copts);
|
||||
CheckCondition(100 == rocksdb_cache_get_capacity(co));
|
||||
|
||||
rocksdb_cache_destroy(co);
|
||||
rocksdb_lru_cache_options_destroy(copts);
|
||||
}
|
||||
rocksdb_memory_allocator_destroy(allocator);
|
||||
}
|
||||
|
||||
StartPhase("env");
|
||||
{
|
||||
rocksdb_env_t* e;
|
||||
@@ -2870,6 +2919,7 @@ int main(int argc, char** argv) {
|
||||
rocksdb_readoptions_destroy(roptions);
|
||||
rocksdb_writeoptions_destroy(woptions);
|
||||
rocksdb_compactoptions_destroy(coptions);
|
||||
rocksdb_cache_disown_data(cache);
|
||||
rocksdb_cache_destroy(cache);
|
||||
rocksdb_comparator_destroy(cmp);
|
||||
rocksdb_dbpath_destroy(dbpath);
|
||||
@@ -2881,7 +2931,7 @@ int main(int argc, char** argv) {
|
||||
|
||||
#else
|
||||
|
||||
int main() {
|
||||
int main(void) {
|
||||
fprintf(stderr, "SKIPPED\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
+16
-6
@@ -557,8 +557,8 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
|
||||
// if _dummy_versions is nullptr, then this is a dummy column family.
|
||||
if (_dummy_versions != nullptr) {
|
||||
internal_stats_.reset(new InternalStats(
|
||||
ioptions_.num_levels, db_options.env->GetSystemClock(), this));
|
||||
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));
|
||||
blob_file_cache_.reset(
|
||||
@@ -633,7 +633,7 @@ ColumnFamilyData::~ColumnFamilyData() {
|
||||
|
||||
if (dummy_versions_ != nullptr) {
|
||||
// List must be empty
|
||||
assert(dummy_versions_->TEST_Next() == dummy_versions_);
|
||||
assert(dummy_versions_->Next() == dummy_versions_);
|
||||
bool deleted __attribute__((__unused__));
|
||||
deleted = dummy_versions_->Unref();
|
||||
assert(deleted);
|
||||
@@ -835,7 +835,8 @@ std::pair<WriteStallCondition, ColumnFamilyData::WriteStallCause>
|
||||
ColumnFamilyData::GetWriteStallConditionAndCause(
|
||||
int num_unflushed_memtables, int num_l0_files,
|
||||
uint64_t num_compaction_needed_bytes,
|
||||
const MutableCFOptions& mutable_cf_options) {
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const ImmutableCFOptions& immutable_cf_options) {
|
||||
if (num_unflushed_memtables >= mutable_cf_options.max_write_buffer_number) {
|
||||
return {WriteStallCondition::kStopped, WriteStallCause::kMemtableLimit};
|
||||
} else if (!mutable_cf_options.disable_auto_compactions &&
|
||||
@@ -849,7 +850,9 @@ ColumnFamilyData::GetWriteStallConditionAndCause(
|
||||
WriteStallCause::kPendingCompactionBytes};
|
||||
} else if (mutable_cf_options.max_write_buffer_number > 3 &&
|
||||
num_unflushed_memtables >=
|
||||
mutable_cf_options.max_write_buffer_number - 1) {
|
||||
mutable_cf_options.max_write_buffer_number - 1 &&
|
||||
num_unflushed_memtables - 1 >=
|
||||
immutable_cf_options.min_write_buffer_number_to_merge) {
|
||||
return {WriteStallCondition::kDelayed, WriteStallCause::kMemtableLimit};
|
||||
} else if (!mutable_cf_options.disable_auto_compactions &&
|
||||
mutable_cf_options.level0_slowdown_writes_trigger >= 0 &&
|
||||
@@ -877,7 +880,8 @@ WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
|
||||
auto write_stall_condition_and_cause = GetWriteStallConditionAndCause(
|
||||
imm()->NumNotFlushed(), vstorage->l0_delay_trigger_count(),
|
||||
vstorage->estimated_compaction_needed_bytes(), mutable_cf_options);
|
||||
vstorage->estimated_compaction_needed_bytes(), mutable_cf_options,
|
||||
*ioptions());
|
||||
write_stall_condition = write_stall_condition_and_cause.first;
|
||||
auto write_stall_cause = write_stall_condition_and_cause.second;
|
||||
|
||||
@@ -1351,6 +1355,12 @@ Status ColumnFamilyData::ValidateOptions(
|
||||
"[0.0, 1.0].");
|
||||
}
|
||||
|
||||
if (cf_options.compaction_style == kCompactionStyleFIFO &&
|
||||
db_options.max_open_files != -1 && cf_options.ttl > 0) {
|
||||
return Status::NotSupported(
|
||||
"FIFO compaction only supported with max_open_files = -1.");
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
+7
-5
@@ -253,7 +253,7 @@ extern Status CheckCFPathsSupported(const DBOptions& db_options,
|
||||
|
||||
extern ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
|
||||
const ColumnFamilyOptions& src);
|
||||
// Wrap user defined table proproties collector factories `from cf_options`
|
||||
// Wrap user defined table properties collector factories `from cf_options`
|
||||
// into internal ones in int_tbl_prop_collector_factories. Add a system internal
|
||||
// one too.
|
||||
extern void GetIntTblPropCollectorFactory(
|
||||
@@ -441,7 +441,7 @@ class ColumnFamilyData {
|
||||
// Get SuperVersion stored in thread local storage. If it does not exist,
|
||||
// get a reference from a current SuperVersion.
|
||||
SuperVersion* GetThreadLocalSuperVersion(DBImpl* db);
|
||||
// Try to return SuperVersion back to thread local storage. Retrun true on
|
||||
// Try to return SuperVersion back to thread local storage. Return true on
|
||||
// success and false on failure. It fails when the thread local storage
|
||||
// contains anything other than SuperVersion::kSVInUse flag.
|
||||
bool ReturnThreadLocalSuperVersion(SuperVersion* sv);
|
||||
@@ -475,9 +475,11 @@ class ColumnFamilyData {
|
||||
kPendingCompactionBytes,
|
||||
};
|
||||
static std::pair<WriteStallCondition, WriteStallCause>
|
||||
GetWriteStallConditionAndCause(int num_unflushed_memtables, int num_l0_files,
|
||||
uint64_t num_compaction_needed_bytes,
|
||||
const MutableCFOptions& mutable_cf_options);
|
||||
GetWriteStallConditionAndCause(
|
||||
int num_unflushed_memtables, int num_l0_files,
|
||||
uint64_t num_compaction_needed_bytes,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const ImmutableCFOptions& immutable_cf_options);
|
||||
|
||||
// Recalculate some small conditions, which are changed only during
|
||||
// compaction, adding new memtable and/or
|
||||
|
||||
@@ -118,6 +118,78 @@ TEST_F(CompactFilesTest, L0ConflictsFiles) {
|
||||
delete db;
|
||||
}
|
||||
|
||||
TEST_F(CompactFilesTest, MultipleLevel) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
options.num_levels = 6;
|
||||
// Add listener
|
||||
FlushedFileCollector* collector = new FlushedFileCollector();
|
||||
options.listeners.emplace_back(collector);
|
||||
|
||||
DB* db = nullptr;
|
||||
DestroyDB(db_name_, options);
|
||||
Status s = DB::Open(options, db_name_, &db);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_NE(db, nullptr);
|
||||
|
||||
// create couple files in L0, L3, L4 and L5
|
||||
for (int i = 5; i > 2; --i) {
|
||||
collector->ClearFlushedFiles();
|
||||
ASSERT_OK(db->Put(WriteOptions(), ToString(i), ""));
|
||||
ASSERT_OK(db->Flush(FlushOptions()));
|
||||
auto l0_files = collector->GetFlushedFiles();
|
||||
ASSERT_OK(db->CompactFiles(CompactionOptions(), l0_files, i));
|
||||
|
||||
std::string prop;
|
||||
ASSERT_TRUE(
|
||||
db->GetProperty("rocksdb.num-files-at-level" + ToString(i), &prop));
|
||||
ASSERT_EQ("1", prop);
|
||||
}
|
||||
ASSERT_OK(db->Put(WriteOptions(), ToString(0), ""));
|
||||
ASSERT_OK(db->Flush(FlushOptions()));
|
||||
|
||||
ColumnFamilyMetaData meta;
|
||||
db->GetColumnFamilyMetaData(&meta);
|
||||
// Compact files except the file in L3
|
||||
std::vector<std::string> files;
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
if (i == 3) continue;
|
||||
for (auto& file : meta.levels[i].files) {
|
||||
files.push_back(file.db_path + "/" + file.name);
|
||||
}
|
||||
}
|
||||
|
||||
SyncPoint::GetInstance()->LoadDependency({
|
||||
{"CompactionJob::Run():Start", "CompactFilesTest.MultipleLevel:0"},
|
||||
{"CompactFilesTest.MultipleLevel:1", "CompactFilesImpl:3"},
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::thread thread([&] {
|
||||
TEST_SYNC_POINT("CompactFilesTest.MultipleLevel:0");
|
||||
ASSERT_OK(db->Put(WriteOptions(), "bar", "v2"));
|
||||
ASSERT_OK(db->Put(WriteOptions(), "foo", "v2"));
|
||||
ASSERT_OK(db->Flush(FlushOptions()));
|
||||
TEST_SYNC_POINT("CompactFilesTest.MultipleLevel:1");
|
||||
});
|
||||
|
||||
// Compaction cannot move up the data to higher level
|
||||
// here we have input file from level 5, so the output level has to be >= 5
|
||||
for (int invalid_output_level = 0; invalid_output_level < 5;
|
||||
invalid_output_level++) {
|
||||
s = db->CompactFiles(CompactionOptions(), files, invalid_output_level);
|
||||
std::cout << s.ToString() << std::endl;
|
||||
ASSERT_TRUE(s.IsInvalidArgument());
|
||||
}
|
||||
|
||||
ASSERT_OK(db->CompactFiles(CompactionOptions(), files, 5));
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
thread.join();
|
||||
|
||||
delete db;
|
||||
}
|
||||
|
||||
TEST_F(CompactFilesTest, ObsoleteFiles) {
|
||||
Options options;
|
||||
// to trigger compaction more easily
|
||||
|
||||
@@ -519,7 +519,7 @@ uint64_t Compaction::OutputFilePreallocationSize() const {
|
||||
|
||||
// Over-estimate slightly so we don't end up just barely crossing
|
||||
// the threshold
|
||||
// No point to prellocate more than 1GB.
|
||||
// No point to preallocate more than 1GB.
|
||||
return std::min(uint64_t{1073741824},
|
||||
preallocation_size + (preallocation_size / 10));
|
||||
}
|
||||
|
||||
@@ -341,7 +341,7 @@ class Compaction {
|
||||
const uint32_t output_path_id_;
|
||||
CompressionType output_compression_;
|
||||
CompressionOptions output_compression_opts_;
|
||||
// If true, then the comaction can be done by simply deleting input files.
|
||||
// If true, then the compaction can be done by simply deleting input files.
|
||||
const bool deletion_compaction_;
|
||||
|
||||
// Compaction input files organized by level. Constant after construction
|
||||
|
||||
@@ -34,4 +34,8 @@ struct CompactionIterationStats {
|
||||
// Single-Delete diagnostics for exceptional situations
|
||||
uint64_t num_single_del_fallthru = 0;
|
||||
uint64_t num_single_del_mismatch = 0;
|
||||
|
||||
// Blob related statistics
|
||||
uint64_t num_blobs_read = 0;
|
||||
uint64_t total_blob_bytes_read = 0;
|
||||
};
|
||||
@@ -80,7 +80,7 @@ CompactionIterator::CompactionIterator(
|
||||
earliest_write_conflict_snapshot_(earliest_write_conflict_snapshot),
|
||||
snapshot_checker_(snapshot_checker),
|
||||
env_(env),
|
||||
clock_(env_->GetSystemClock()),
|
||||
clock_(env_->GetSystemClock().get()),
|
||||
report_detailed_time_(report_detailed_time),
|
||||
expect_valid_internal_key_(expect_valid_internal_key),
|
||||
range_del_agg_(range_del_agg),
|
||||
@@ -135,7 +135,7 @@ CompactionIterator::CompactionIterator(
|
||||
}
|
||||
|
||||
CompactionIterator::~CompactionIterator() {
|
||||
// input_ Iteartor lifetime is longer than pinned_iters_mgr_ lifetime
|
||||
// input_ Iterator lifetime is longer than pinned_iters_mgr_ lifetime
|
||||
input_->SetPinnedItersMgr(nullptr);
|
||||
}
|
||||
|
||||
@@ -204,72 +204,146 @@ void CompactionIterator::Next() {
|
||||
|
||||
bool CompactionIterator::InvokeFilterIfNeeded(bool* need_skip,
|
||||
Slice* skip_until) {
|
||||
if (compaction_filter_ != nullptr &&
|
||||
(ikey_.type == kTypeValue || ikey_.type == kTypeBlobIndex)) {
|
||||
// If the user has specified a compaction filter and the sequence
|
||||
// number is greater than any external snapshot, then invoke the
|
||||
// filter. If the return value of the compaction filter is true,
|
||||
// replace the entry with a deletion marker.
|
||||
CompactionFilter::Decision filter;
|
||||
compaction_filter_value_.clear();
|
||||
compaction_filter_skip_until_.Clear();
|
||||
CompactionFilter::ValueType value_type =
|
||||
ikey_.type == kTypeValue ? CompactionFilter::ValueType::kValue
|
||||
: CompactionFilter::ValueType::kBlobIndex;
|
||||
// Hack: pass internal key to BlobIndexCompactionFilter since it needs
|
||||
// to get sequence number.
|
||||
Slice& filter_key = ikey_.type == kTypeValue ? ikey_.user_key : key_;
|
||||
{
|
||||
StopWatchNano timer(clock_, report_detailed_time_);
|
||||
if (!compaction_filter_ ||
|
||||
(ikey_.type != kTypeValue && ikey_.type != kTypeBlobIndex)) {
|
||||
return true;
|
||||
}
|
||||
bool error = false;
|
||||
// If the user has specified a compaction filter and the sequence
|
||||
// number is greater than any external snapshot, then invoke the
|
||||
// filter. If the return value of the compaction filter is true,
|
||||
// replace the entry with a deletion marker.
|
||||
CompactionFilter::Decision filter = CompactionFilter::Decision::kUndetermined;
|
||||
compaction_filter_value_.clear();
|
||||
compaction_filter_skip_until_.Clear();
|
||||
CompactionFilter::ValueType value_type =
|
||||
ikey_.type == kTypeValue ? CompactionFilter::ValueType::kValue
|
||||
: CompactionFilter::ValueType::kBlobIndex;
|
||||
// Hack: pass internal key to BlobIndexCompactionFilter since it needs
|
||||
// to get sequence number.
|
||||
assert(compaction_filter_);
|
||||
Slice& filter_key =
|
||||
(ikey_.type == kTypeValue ||
|
||||
!compaction_filter_->IsStackedBlobDbInternalCompactionFilter())
|
||||
? ikey_.user_key
|
||||
: key_;
|
||||
{
|
||||
StopWatchNano timer(clock_, report_detailed_time_);
|
||||
if (kTypeBlobIndex == ikey_.type) {
|
||||
blob_value_.Reset();
|
||||
filter = compaction_filter_->FilterBlobByKey(
|
||||
compaction_->level(), filter_key, &compaction_filter_value_,
|
||||
compaction_filter_skip_until_.rep());
|
||||
if (CompactionFilter::Decision::kUndetermined == filter &&
|
||||
!compaction_filter_->IsStackedBlobDbInternalCompactionFilter()) {
|
||||
// For integrated BlobDB impl, CompactionIterator reads blob value.
|
||||
// For Stacked BlobDB impl, the corresponding CompactionFilter's
|
||||
// FilterV2 method should read the blob value.
|
||||
BlobIndex blob_index;
|
||||
Status s = blob_index.DecodeFrom(value_);
|
||||
if (!s.ok()) {
|
||||
status_ = s;
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
if (blob_index.HasTTL() || blob_index.IsInlined()) {
|
||||
status_ = Status::Corruption("Unexpected TTL/inlined blob index");
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
const Version* const version = compaction_->input_version();
|
||||
assert(version);
|
||||
|
||||
uint64_t bytes_read = 0;
|
||||
s = version->GetBlob(ReadOptions(), ikey_.user_key, blob_index,
|
||||
&blob_value_, &bytes_read);
|
||||
if (!s.ok()) {
|
||||
status_ = s;
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
++iter_stats_.num_blobs_read;
|
||||
iter_stats_.total_blob_bytes_read += bytes_read;
|
||||
|
||||
value_type = CompactionFilter::ValueType::kValue;
|
||||
}
|
||||
}
|
||||
if (CompactionFilter::Decision::kUndetermined == filter) {
|
||||
filter = compaction_filter_->FilterV2(
|
||||
compaction_->level(), filter_key, value_type, value_,
|
||||
&compaction_filter_value_, compaction_filter_skip_until_.rep());
|
||||
iter_stats_.total_filter_time +=
|
||||
env_ != nullptr && report_detailed_time_ ? timer.ElapsedNanos() : 0;
|
||||
compaction_->level(), filter_key, value_type,
|
||||
blob_value_.empty() ? value_ : blob_value_, &compaction_filter_value_,
|
||||
compaction_filter_skip_until_.rep());
|
||||
}
|
||||
iter_stats_.total_filter_time +=
|
||||
env_ != nullptr && report_detailed_time_ ? timer.ElapsedNanos() : 0;
|
||||
}
|
||||
|
||||
if (filter == CompactionFilter::Decision::kRemoveAndSkipUntil &&
|
||||
cmp_->Compare(*compaction_filter_skip_until_.rep(), ikey_.user_key) <=
|
||||
0) {
|
||||
// Can't skip to a key smaller than the current one.
|
||||
// Keep the key as per FilterV2 documentation.
|
||||
filter = CompactionFilter::Decision::kKeep;
|
||||
if (CompactionFilter::Decision::kUndetermined == filter) {
|
||||
// Should not reach here, since FilterV2 should never return kUndetermined.
|
||||
status_ =
|
||||
Status::NotSupported("FilterV2() should never return kUndetermined");
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filter == CompactionFilter::Decision::kRemoveAndSkipUntil &&
|
||||
cmp_->Compare(*compaction_filter_skip_until_.rep(), ikey_.user_key) <=
|
||||
0) {
|
||||
// Can't skip to a key smaller than the current one.
|
||||
// Keep the key as per FilterV2 documentation.
|
||||
filter = CompactionFilter::Decision::kKeep;
|
||||
}
|
||||
|
||||
if (filter == CompactionFilter::Decision::kRemove) {
|
||||
// convert the current key to a delete; key_ is pointing into
|
||||
// current_key_ at this point, so updating current_key_ updates key()
|
||||
ikey_.type = kTypeDeletion;
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, kTypeDeletion);
|
||||
// no value associated with delete
|
||||
value_.clear();
|
||||
iter_stats_.num_record_drop_user++;
|
||||
} else if (filter == CompactionFilter::Decision::kChangeValue) {
|
||||
if (ikey_.type == kTypeBlobIndex) {
|
||||
// value transfer from blob file to inlined data
|
||||
ikey_.type = kTypeValue;
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type);
|
||||
}
|
||||
|
||||
if (filter == CompactionFilter::Decision::kRemove) {
|
||||
// convert the current key to a delete; key_ is pointing into
|
||||
// current_key_ at this point, so updating current_key_ updates key()
|
||||
ikey_.type = kTypeDeletion;
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, kTypeDeletion);
|
||||
// no value associated with delete
|
||||
value_.clear();
|
||||
iter_stats_.num_record_drop_user++;
|
||||
} else if (filter == CompactionFilter::Decision::kChangeValue) {
|
||||
if (ikey_.type == kTypeBlobIndex) {
|
||||
// value transfer from blob file to inlined data
|
||||
ikey_.type = kTypeValue;
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type);
|
||||
}
|
||||
value_ = compaction_filter_value_;
|
||||
} else if (filter == CompactionFilter::Decision::kRemoveAndSkipUntil) {
|
||||
*need_skip = true;
|
||||
compaction_filter_skip_until_.ConvertFromUserKey(kMaxSequenceNumber,
|
||||
kValueTypeForSeek);
|
||||
*skip_until = compaction_filter_skip_until_.Encode();
|
||||
} else if (filter == CompactionFilter::Decision::kChangeBlobIndex) {
|
||||
if (ikey_.type == kTypeValue) {
|
||||
// value transfer from inlined data to blob file
|
||||
ikey_.type = kTypeBlobIndex;
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type);
|
||||
}
|
||||
value_ = compaction_filter_value_;
|
||||
} else if (filter == CompactionFilter::Decision::kIOError) {
|
||||
status_ =
|
||||
Status::IOError("Failed to access blob during compaction filter");
|
||||
value_ = compaction_filter_value_;
|
||||
} else if (filter == CompactionFilter::Decision::kRemoveAndSkipUntil) {
|
||||
*need_skip = true;
|
||||
compaction_filter_skip_until_.ConvertFromUserKey(kMaxSequenceNumber,
|
||||
kValueTypeForSeek);
|
||||
*skip_until = compaction_filter_skip_until_.Encode();
|
||||
} else if (filter == CompactionFilter::Decision::kChangeBlobIndex) {
|
||||
// Only the StackableDB-based BlobDB impl's compaction filter should return
|
||||
// kChangeBlobIndex. Decision about rewriting blob and changing blob index
|
||||
// in the integrated BlobDB impl is made in subsequent call to
|
||||
// PrepareOutput() and its callees.
|
||||
if (!compaction_filter_->IsStackedBlobDbInternalCompactionFilter()) {
|
||||
status_ = Status::NotSupported(
|
||||
"Only stacked BlobDB's internal compaction filter can return "
|
||||
"kChangeBlobIndex.");
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
if (ikey_.type == kTypeValue) {
|
||||
// value transfer from inlined data to blob file
|
||||
ikey_.type = kTypeBlobIndex;
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type);
|
||||
}
|
||||
value_ = compaction_filter_value_;
|
||||
} else if (filter == CompactionFilter::Decision::kIOError) {
|
||||
if (!compaction_filter_->IsStackedBlobDbInternalCompactionFilter()) {
|
||||
status_ = Status::NotSupported(
|
||||
"CompactionFilter for integrated BlobDB should not return kIOError");
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
status_ = Status::IOError("Failed to access blob during compaction filter");
|
||||
error = true;
|
||||
}
|
||||
return true;
|
||||
return !error;
|
||||
}
|
||||
|
||||
void CompactionIterator::NextFromInput() {
|
||||
@@ -316,13 +390,11 @@ void CompactionIterator::NextFromInput() {
|
||||
// merge_helper_->compaction_filter_skip_until_.
|
||||
Slice skip_until;
|
||||
|
||||
int cmp_user_key_without_ts = 0;
|
||||
bool user_key_equal_without_ts = false;
|
||||
int cmp_ts = 0;
|
||||
if (has_current_user_key_) {
|
||||
cmp_user_key_without_ts =
|
||||
timestamp_size_
|
||||
? cmp_->CompareWithoutTimestamp(ikey_.user_key, current_user_key_)
|
||||
: cmp_->Compare(ikey_.user_key, current_user_key_);
|
||||
user_key_equal_without_ts =
|
||||
cmp_->EqualWithoutTimestamp(ikey_.user_key, current_user_key_);
|
||||
// if timestamp_size_ > 0, then curr_ts_ has been initialized by a
|
||||
// previous key.
|
||||
cmp_ts = timestamp_size_ ? cmp_->CompareTimestamp(
|
||||
@@ -335,7 +407,7 @@ void CompactionIterator::NextFromInput() {
|
||||
// Check whether the user key changed. After this if statement current_key_
|
||||
// is a copy of the current input key (maybe converted to a delete by the
|
||||
// compaction filter). ikey_.user_key is pointing to the copy.
|
||||
if (!has_current_user_key_ || cmp_user_key_without_ts != 0 || cmp_ts != 0) {
|
||||
if (!has_current_user_key_ || !user_key_equal_without_ts || cmp_ts != 0) {
|
||||
// First occurrence of this user key
|
||||
// Copy key for output
|
||||
key_ = current_key_.SetInternalKey(key_, &ikey_);
|
||||
@@ -356,7 +428,7 @@ void CompactionIterator::NextFromInput() {
|
||||
// consider this key for GC, e.g. it may be dropped if certain conditions
|
||||
// match.
|
||||
if (!has_current_user_key_ || !timestamp_size_ || !full_history_ts_low_ ||
|
||||
0 != cmp_user_key_without_ts || cmp_with_history_ts_low_ >= 0) {
|
||||
!user_key_equal_without_ts || cmp_with_history_ts_low_ >= 0) {
|
||||
// Initialize for future comparison for rule (A) and etc.
|
||||
current_user_key_sequence_ = kMaxSequenceNumber;
|
||||
current_user_key_snapshot_ = 0;
|
||||
@@ -650,8 +722,7 @@ void CompactionIterator::NextFromInput() {
|
||||
input_->Valid() &&
|
||||
(ParseInternalKey(input_->key(), &next_ikey, allow_data_in_errors_)
|
||||
.ok()) &&
|
||||
0 == cmp_->CompareWithoutTimestamp(ikey_.user_key,
|
||||
next_ikey.user_key) &&
|
||||
cmp_->EqualWithoutTimestamp(ikey_.user_key, next_ikey.user_key) &&
|
||||
(prev_snapshot == 0 ||
|
||||
DEFINITELY_NOT_IN_SNAPSHOT(next_ikey.sequence, prev_snapshot))) {
|
||||
input_->Next();
|
||||
@@ -661,8 +732,7 @@ void CompactionIterator::NextFromInput() {
|
||||
if (input_->Valid() &&
|
||||
(ParseInternalKey(input_->key(), &next_ikey, allow_data_in_errors_)
|
||||
.ok()) &&
|
||||
0 == cmp_->CompareWithoutTimestamp(ikey_.user_key,
|
||||
next_ikey.user_key)) {
|
||||
cmp_->EqualWithoutTimestamp(ikey_.user_key, next_ikey.user_key)) {
|
||||
valid_ = true;
|
||||
at_next_ = true;
|
||||
}
|
||||
@@ -815,9 +885,11 @@ void CompactionIterator::GarbageCollectBlobIfNeeded() {
|
||||
const Version* const version = compaction_->input_version();
|
||||
assert(version);
|
||||
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
{
|
||||
const Status s =
|
||||
version->GetBlob(ReadOptions(), user_key(), blob_index, &blob_value_);
|
||||
const Status s = version->GetBlob(ReadOptions(), user_key(), blob_index,
|
||||
&blob_value_, &bytes_read);
|
||||
|
||||
if (!s.ok()) {
|
||||
status_ = s;
|
||||
@@ -827,6 +899,9 @@ void CompactionIterator::GarbageCollectBlobIfNeeded() {
|
||||
}
|
||||
}
|
||||
|
||||
++iter_stats_.num_blobs_read;
|
||||
iter_stats_.total_blob_bytes_read += bytes_read;
|
||||
|
||||
value_ = blob_value_;
|
||||
|
||||
if (ExtractLargeValueIfNeededImpl()) {
|
||||
@@ -840,7 +915,8 @@ void CompactionIterator::GarbageCollectBlobIfNeeded() {
|
||||
}
|
||||
|
||||
// GC for stacked BlobDB
|
||||
if (compaction_filter_) {
|
||||
if (compaction_filter_ &&
|
||||
compaction_filter_->IsStackedBlobDbInternalCompactionFilter()) {
|
||||
const auto blob_decision = compaction_filter_->PrepareBlobOutput(
|
||||
user_key(), value_, &compaction_filter_value_);
|
||||
|
||||
|
||||
@@ -248,7 +248,7 @@ class CompactionIterator {
|
||||
const SequenceNumber earliest_write_conflict_snapshot_;
|
||||
const SnapshotChecker* const snapshot_checker_;
|
||||
Env* env_;
|
||||
std::shared_ptr<SystemClock> clock_;
|
||||
SystemClock* clock_;
|
||||
bool report_detailed_time_;
|
||||
bool expect_valid_internal_key_;
|
||||
CompactionRangeDelAggregator* range_del_agg_;
|
||||
|
||||
@@ -38,7 +38,7 @@ class NoMergingMergeOp : public MergeOperator {
|
||||
|
||||
// Compaction filter that gets stuck when it sees a particular key,
|
||||
// then gets unstuck when told to.
|
||||
// Always returns Decition::kRemove.
|
||||
// Always returns Decision::kRemove.
|
||||
class StallingFilter : public CompactionFilter {
|
||||
public:
|
||||
Decision FilterV2(int /*level*/, const Slice& key, ValueType /*type*/,
|
||||
@@ -189,7 +189,7 @@ class FakeCompaction : public CompactionIterator::CompactionProxy {
|
||||
bool is_allow_ingest_behind = false;
|
||||
};
|
||||
|
||||
// A simplifed snapshot checker which assumes each snapshot has a global
|
||||
// A simplified snapshot checker which assumes each snapshot has a global
|
||||
// last visible sequence.
|
||||
class TestSnapshotChecker : public SnapshotChecker {
|
||||
public:
|
||||
@@ -711,7 +711,7 @@ TEST_P(CompactionIteratorTest, ZeroOutSequenceAtBottomLevel) {
|
||||
RunTest({test::KeyStr("a", 1, kTypeValue), test::KeyStr("b", 2, kTypeValue)},
|
||||
{"v1", "v2"},
|
||||
{test::KeyStr("a", 0, kTypeValue), test::KeyStr("b", 2, kTypeValue)},
|
||||
{"v1", "v2"}, kMaxSequenceNumber /*last_commited_seq*/,
|
||||
{"v1", "v2"}, kMaxSequenceNumber /*last_committed_seq*/,
|
||||
nullptr /*merge_operator*/, nullptr /*compaction_filter*/,
|
||||
true /*bottommost_level*/);
|
||||
}
|
||||
@@ -720,15 +720,14 @@ TEST_P(CompactionIteratorTest, ZeroOutSequenceAtBottomLevel) {
|
||||
// permanently.
|
||||
TEST_P(CompactionIteratorTest, RemoveDeletionAtBottomLevel) {
|
||||
AddSnapshot(1);
|
||||
RunTest({test::KeyStr("a", 1, kTypeDeletion),
|
||||
test::KeyStr("b", 3, kTypeDeletion),
|
||||
test::KeyStr("b", 1, kTypeValue)},
|
||||
{"", "", ""},
|
||||
{test::KeyStr("b", 3, kTypeDeletion),
|
||||
test::KeyStr("b", 0, kTypeValue)},
|
||||
{"", ""},
|
||||
kMaxSequenceNumber /*last_commited_seq*/, nullptr /*merge_operator*/,
|
||||
nullptr /*compaction_filter*/, true /*bottommost_level*/);
|
||||
RunTest(
|
||||
{test::KeyStr("a", 1, kTypeDeletion), test::KeyStr("b", 3, kTypeDeletion),
|
||||
test::KeyStr("b", 1, kTypeValue)},
|
||||
{"", "", ""},
|
||||
{test::KeyStr("b", 3, kTypeDeletion), test::KeyStr("b", 0, kTypeValue)},
|
||||
{"", ""}, kMaxSequenceNumber /*last_committed_seq*/,
|
||||
nullptr /*merge_operator*/, nullptr /*compaction_filter*/,
|
||||
true /*bottommost_level*/);
|
||||
}
|
||||
|
||||
// In bottommost level, single deletions earlier than earliest snapshot can be
|
||||
@@ -738,7 +737,7 @@ TEST_P(CompactionIteratorTest, RemoveSingleDeletionAtBottomLevel) {
|
||||
RunTest({test::KeyStr("a", 1, kTypeSingleDeletion),
|
||||
test::KeyStr("b", 2, kTypeSingleDeletion)},
|
||||
{"", ""}, {test::KeyStr("b", 2, kTypeSingleDeletion)}, {""},
|
||||
kMaxSequenceNumber /*last_commited_seq*/, nullptr /*merge_operator*/,
|
||||
kMaxSequenceNumber /*last_committed_seq*/, nullptr /*merge_operator*/,
|
||||
nullptr /*compaction_filter*/, true /*bottommost_level*/);
|
||||
}
|
||||
|
||||
@@ -895,7 +894,7 @@ TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
{"v1", "v2", "v3"},
|
||||
{test::KeyStr("a", 0, kTypeValue), test::KeyStr("b", 2, kTypeValue),
|
||||
test::KeyStr("c", 3, kTypeValue)},
|
||||
{"v1", "v2", "v3"}, kMaxSequenceNumber /*last_commited_seq*/,
|
||||
{"v1", "v2", "v3"}, kMaxSequenceNumber /*last_committed_seq*/,
|
||||
nullptr /*merge_operator*/, nullptr /*compaction_filter*/,
|
||||
true /*bottommost_level*/);
|
||||
}
|
||||
@@ -906,9 +905,7 @@ TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
RunTest(
|
||||
{test::KeyStr("a", 1, kTypeDeletion), test::KeyStr("b", 2, kTypeDeletion),
|
||||
test::KeyStr("c", 3, kTypeDeletion)},
|
||||
{"", "", ""},
|
||||
{},
|
||||
{"", ""}, kMaxSequenceNumber /*last_commited_seq*/,
|
||||
{"", "", ""}, {}, {"", ""}, kMaxSequenceNumber /*last_committed_seq*/,
|
||||
nullptr /*merge_operator*/, nullptr /*compaction_filter*/,
|
||||
true /*bottommost_level*/);
|
||||
}
|
||||
@@ -916,15 +913,14 @@ TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
NotRemoveDeletionIfValuePresentToEarlierSnapshot) {
|
||||
AddSnapshot(2,1);
|
||||
RunTest(
|
||||
{test::KeyStr("a", 4, kTypeDeletion), test::KeyStr("a", 1, kTypeValue),
|
||||
test::KeyStr("b", 3, kTypeValue)},
|
||||
{"", "", ""},
|
||||
{test::KeyStr("a", 4, kTypeDeletion), test::KeyStr("a", 0, kTypeValue),
|
||||
test::KeyStr("b", 3, kTypeValue)},
|
||||
{"", "", ""}, kMaxSequenceNumber /*last_commited_seq*/,
|
||||
nullptr /*merge_operator*/, nullptr /*compaction_filter*/,
|
||||
true /*bottommost_level*/);
|
||||
RunTest({test::KeyStr("a", 4, kTypeDeletion),
|
||||
test::KeyStr("a", 1, kTypeValue), test::KeyStr("b", 3, kTypeValue)},
|
||||
{"", "", ""},
|
||||
{test::KeyStr("a", 4, kTypeDeletion),
|
||||
test::KeyStr("a", 0, kTypeValue), test::KeyStr("b", 3, kTypeValue)},
|
||||
{"", "", ""}, kMaxSequenceNumber /*last_committed_seq*/,
|
||||
nullptr /*merge_operator*/, nullptr /*compaction_filter*/,
|
||||
true /*bottommost_level*/);
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
@@ -936,7 +932,7 @@ TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
{"", "", ""},
|
||||
{test::KeyStr("b", 2, kTypeSingleDeletion),
|
||||
test::KeyStr("c", 3, kTypeSingleDeletion)},
|
||||
{"", ""}, kMaxSequenceNumber /*last_commited_seq*/,
|
||||
{"", ""}, kMaxSequenceNumber /*last_committed_seq*/,
|
||||
nullptr /*merge_operator*/, nullptr /*compaction_filter*/,
|
||||
true /*bottommost_level*/);
|
||||
}
|
||||
@@ -986,8 +982,8 @@ TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
}
|
||||
|
||||
// Compaction filter should keep uncommitted key as-is, and
|
||||
// * Convert the latest velue to deletion, and/or
|
||||
// * if latest value is a merge, apply filter to all suequent merges.
|
||||
// * Convert the latest value to deletion, and/or
|
||||
// * if latest value is a merge, apply filter to all subsequent merges.
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest, CompactionFilter_Value) {
|
||||
std::unique_ptr<CompactionFilter> compaction_filter(
|
||||
|
||||
+201
-58
@@ -150,7 +150,7 @@ struct CompactionJob::SubcompactionState {
|
||||
// This subcompaction's output could be empty if compaction was aborted
|
||||
// before this subcompaction had a chance to generate any output files.
|
||||
// When subcompactions are executed sequentially this is more likely and
|
||||
// will be particulalry likely for the later subcompactions to be empty.
|
||||
// will be particularly likely for the later subcompactions to be empty.
|
||||
// Once they are run in parallel however it should be much rarer.
|
||||
return nullptr;
|
||||
} else {
|
||||
@@ -310,18 +310,23 @@ CompactionJob::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, const std::string& db_id,
|
||||
const std::string& db_session_id, std::string full_history_ts_low)
|
||||
: job_id_(job_id),
|
||||
compact_(new CompactionState(compaction)),
|
||||
compaction_job_stats_(compaction_job_stats),
|
||||
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),
|
||||
log_buffer_(log_buffer),
|
||||
output_directory_(output_directory),
|
||||
stats_(stats),
|
||||
bottommost_level_(false),
|
||||
write_hint_(Env::WLTH_NOT_SET),
|
||||
job_id_(job_id),
|
||||
compaction_job_stats_(compaction_job_stats),
|
||||
dbname_(dbname),
|
||||
db_id_(db_id),
|
||||
db_session_id_(db_session_id),
|
||||
db_options_(db_options),
|
||||
file_options_(file_options),
|
||||
env_(db_options.env),
|
||||
clock_(env_->GetSystemClock()),
|
||||
io_tracer_(io_tracer),
|
||||
fs_(db_options.fs, io_tracer),
|
||||
file_options_for_read_(
|
||||
@@ -330,11 +335,8 @@ CompactionJob::CompactionJob(
|
||||
shutting_down_(shutting_down),
|
||||
manual_compaction_paused_(manual_compaction_paused),
|
||||
preserve_deletes_seqnum_(preserve_deletes_seqnum),
|
||||
log_buffer_(log_buffer),
|
||||
db_directory_(db_directory),
|
||||
output_directory_(output_directory),
|
||||
blob_output_directory_(blob_output_directory),
|
||||
stats_(stats),
|
||||
db_mutex_(db_mutex),
|
||||
db_error_handler_(db_error_handler),
|
||||
existing_snapshots_(std::move(existing_snapshots)),
|
||||
@@ -342,12 +344,11 @@ CompactionJob::CompactionJob(
|
||||
snapshot_checker_(snapshot_checker),
|
||||
table_cache_(std::move(table_cache)),
|
||||
event_logger_(event_logger),
|
||||
bottommost_level_(false),
|
||||
paranoid_file_checks_(paranoid_file_checks),
|
||||
measure_io_stats_(measure_io_stats),
|
||||
write_hint_(Env::WLTH_NOT_SET),
|
||||
thread_pri_(thread_pri),
|
||||
full_history_ts_low_(std::move(full_history_ts_low)) {
|
||||
full_history_ts_low_(std::move(full_history_ts_low)),
|
||||
blob_callback_(blob_callback) {
|
||||
assert(compaction_job_stats_ != nullptr);
|
||||
assert(log_buffer_ != nullptr);
|
||||
const auto* cfd = compact_->compaction->column_family_data();
|
||||
@@ -409,7 +410,7 @@ void CompactionJob::Prepare() {
|
||||
AutoThreadOperationStageUpdater stage_updater(
|
||||
ThreadStatus::STAGE_COMPACTION_PREPARE);
|
||||
|
||||
// Generate file_levels_ for compaction berfore making Iterator
|
||||
// Generate file_levels_ for compaction before making Iterator
|
||||
auto* c = compact_->compaction;
|
||||
assert(c->column_family_data() != nullptr);
|
||||
assert(c->column_family_data()->current()->storage_info()->NumLevelFiles(
|
||||
@@ -421,7 +422,7 @@ void CompactionJob::Prepare() {
|
||||
|
||||
if (c->ShouldFormSubcompactions()) {
|
||||
{
|
||||
StopWatch sw(clock_, stats_, SUBCOMPACTION_SETUP_TIME);
|
||||
StopWatch sw(db_options_.clock, stats_, SUBCOMPACTION_SETUP_TIME);
|
||||
GenSubcompactionBoundaries();
|
||||
}
|
||||
assert(sizes_.size() == boundaries_.size() + 1);
|
||||
@@ -587,7 +588,7 @@ Status CompactionJob::Run() {
|
||||
|
||||
const size_t num_threads = compact_->sub_compact_states.size();
|
||||
assert(num_threads > 0);
|
||||
const uint64_t start_micros = clock_->NowMicros();
|
||||
const uint64_t start_micros = db_options_.clock->NowMicros();
|
||||
|
||||
// Launch a thread for each of subcompactions 1...num_threads-1
|
||||
std::vector<port::Thread> thread_pool;
|
||||
@@ -606,7 +607,7 @@ Status CompactionJob::Run() {
|
||||
thread.join();
|
||||
}
|
||||
|
||||
compaction_stats_.micros = clock_->NowMicros() - start_micros;
|
||||
compaction_stats_.micros = db_options_.clock->NowMicros() - start_micros;
|
||||
compaction_stats_.cpu_micros = 0;
|
||||
for (size_t i = 0; i < compact_->sub_compact_states.size(); i++) {
|
||||
compaction_stats_.cpu_micros +=
|
||||
@@ -794,39 +795,45 @@ Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options) {
|
||||
double bytes_read_per_sec = 0;
|
||||
double bytes_written_per_sec = 0;
|
||||
|
||||
if (stats.bytes_read_non_output_levels > 0) {
|
||||
read_write_amp = (stats.bytes_written + stats.bytes_read_output_level +
|
||||
stats.bytes_read_non_output_levels) /
|
||||
static_cast<double>(stats.bytes_read_non_output_levels);
|
||||
write_amp = stats.bytes_written /
|
||||
static_cast<double>(stats.bytes_read_non_output_levels);
|
||||
const uint64_t bytes_read_non_output_and_blob =
|
||||
stats.bytes_read_non_output_levels + stats.bytes_read_blob;
|
||||
const uint64_t bytes_read_all =
|
||||
stats.bytes_read_output_level + bytes_read_non_output_and_blob;
|
||||
const uint64_t bytes_written_all =
|
||||
stats.bytes_written + stats.bytes_written_blob;
|
||||
|
||||
if (bytes_read_non_output_and_blob > 0) {
|
||||
read_write_amp = (bytes_written_all + bytes_read_all) /
|
||||
static_cast<double>(bytes_read_non_output_and_blob);
|
||||
write_amp =
|
||||
bytes_written_all / static_cast<double>(bytes_read_non_output_and_blob);
|
||||
}
|
||||
if (stats.micros > 0) {
|
||||
bytes_read_per_sec =
|
||||
(stats.bytes_read_non_output_levels + stats.bytes_read_output_level) /
|
||||
static_cast<double>(stats.micros);
|
||||
bytes_read_per_sec = bytes_read_all / static_cast<double>(stats.micros);
|
||||
bytes_written_per_sec =
|
||||
stats.bytes_written / static_cast<double>(stats.micros);
|
||||
bytes_written_all / static_cast<double>(stats.micros);
|
||||
}
|
||||
|
||||
const std::string& column_family_name = cfd->GetName();
|
||||
|
||||
constexpr double kMB = 1048576.0;
|
||||
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer_,
|
||||
"[%s] compacted to: %s, MB/sec: %.1f rd, %.1f wr, level %d, "
|
||||
"files in(%d, %d) out(%d) "
|
||||
"MB in(%.1f, %.1f) out(%.1f), read-write-amplify(%.1f) "
|
||||
"write-amplify(%.1f) %s, records in: %" PRIu64
|
||||
"files in(%d, %d) out(%d +%d blob) "
|
||||
"MB in(%.1f, %.1f +%.1f blob) out(%.1f +%.1f blob), "
|
||||
"read-write-amplify(%.1f) write-amplify(%.1f) %s, records in: %" PRIu64
|
||||
", records dropped: %" PRIu64 " output_compression: %s\n",
|
||||
column_family_name.c_str(), vstorage->LevelSummary(&tmp),
|
||||
bytes_read_per_sec, bytes_written_per_sec,
|
||||
compact_->compaction->output_level(),
|
||||
stats.num_input_files_in_non_output_levels,
|
||||
stats.num_input_files_in_output_level, stats.num_output_files,
|
||||
stats.bytes_read_non_output_levels / 1048576.0,
|
||||
stats.bytes_read_output_level / 1048576.0,
|
||||
stats.bytes_written / 1048576.0, read_write_amp, write_amp,
|
||||
status.ToString().c_str(), stats.num_input_records,
|
||||
stats.num_output_files_blob, stats.bytes_read_non_output_levels / kMB,
|
||||
stats.bytes_read_output_level / kMB, stats.bytes_read_blob / kMB,
|
||||
stats.bytes_written / kMB, stats.bytes_written_blob / kMB, read_write_amp,
|
||||
write_amp, status.ToString().c_str(), stats.num_input_records,
|
||||
stats.num_dropped_records,
|
||||
CompressionTypeToString(compact_->compaction->output_compression())
|
||||
.c_str());
|
||||
@@ -896,7 +903,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
assert(sub_compact);
|
||||
assert(sub_compact->compaction);
|
||||
|
||||
uint64_t prev_cpu_micros = clock_->CPUNanos() / 1000;
|
||||
uint64_t prev_cpu_micros = db_options_.clock->CPUNanos() / 1000;
|
||||
|
||||
ColumnFamilyData* cfd = sub_compact->compaction->column_family_data();
|
||||
|
||||
@@ -958,7 +965,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
}
|
||||
|
||||
MergeHelper merge(
|
||||
env_, cfd->user_comparator(), cfd->ioptions()->merge_operator,
|
||||
env_, cfd->user_comparator(), cfd->ioptions()->merge_operator.get(),
|
||||
compaction_filter, db_options_.info_log.get(),
|
||||
false /* internal key corruption is expected */,
|
||||
existing_snapshots_.empty() ? 0 : existing_snapshots_.back(),
|
||||
@@ -973,12 +980,13 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
|
||||
std::unique_ptr<BlobFileBuilder> blob_file_builder(
|
||||
mutable_cf_options->enable_blob_files
|
||||
? new BlobFileBuilder(
|
||||
versions_, env_, fs_.get(),
|
||||
sub_compact->compaction->immutable_cf_options(),
|
||||
mutable_cf_options, &file_options_, job_id_, cfd->GetID(),
|
||||
cfd->GetName(), Env::IOPriority::IO_LOW, write_hint_,
|
||||
io_tracer_, &blob_file_paths, &sub_compact->blob_file_additions)
|
||||
? new BlobFileBuilder(versions_, fs_.get(),
|
||||
sub_compact->compaction->immutable_cf_options(),
|
||||
mutable_cf_options, &file_options_, job_id_,
|
||||
cfd->GetID(), cfd->GetName(),
|
||||
Env::IOPriority::IO_LOW, write_hint_,
|
||||
io_tracer_, blob_callback_, &blob_file_paths,
|
||||
&sub_compact->blob_file_additions)
|
||||
: nullptr);
|
||||
|
||||
TEST_SYNC_POINT("CompactionJob::Run():Inprogress");
|
||||
@@ -1121,6 +1129,10 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
}
|
||||
}
|
||||
|
||||
sub_compact->compaction_job_stats.num_blobs_read =
|
||||
c_iter_stats.num_blobs_read;
|
||||
sub_compact->compaction_job_stats.total_blob_bytes_read =
|
||||
c_iter_stats.total_blob_bytes_read;
|
||||
sub_compact->compaction_job_stats.num_input_deletion_records =
|
||||
c_iter_stats.num_input_deletion_records;
|
||||
sub_compact->compaction_job_stats.num_corrupt_keys =
|
||||
@@ -1180,13 +1192,14 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
if (blob_file_builder) {
|
||||
if (status.ok()) {
|
||||
status = blob_file_builder->Finish();
|
||||
} else {
|
||||
blob_file_builder->Abandon();
|
||||
}
|
||||
|
||||
blob_file_builder.reset();
|
||||
}
|
||||
|
||||
sub_compact->compaction_job_stats.cpu_micros =
|
||||
clock_->CPUNanos() / 1000 - prev_cpu_micros;
|
||||
db_options_.clock->CPUNanos() / 1000 - prev_cpu_micros;
|
||||
|
||||
if (measure_io_stats_) {
|
||||
sub_compact->compaction_job_stats.file_write_nanos +=
|
||||
@@ -1465,7 +1478,7 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
|
||||
// Finish and check for file errors
|
||||
if (s.ok()) {
|
||||
StopWatch sw(clock_, stats_, COMPACTION_OUTFILE_SYNC_MICROS);
|
||||
StopWatch sw(db_options_.clock, stats_, COMPACTION_OUTFILE_SYNC_MICROS);
|
||||
io_s = sub_compact->outfile->Sync(db_options_.use_fsync);
|
||||
}
|
||||
if (s.ok() && io_s.ok()) {
|
||||
@@ -1537,9 +1550,7 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
FileDescriptor output_fd;
|
||||
uint64_t oldest_blob_file_number = kInvalidBlobFileNumber;
|
||||
if (meta != nullptr) {
|
||||
fname =
|
||||
TableFileName(sub_compact->compaction->immutable_cf_options()->cf_paths,
|
||||
meta->fd.GetNumber(), meta->fd.GetPathId());
|
||||
fname = GetTableFileName(meta->fd.GetNumber());
|
||||
output_fd = meta->fd;
|
||||
oldest_blob_file_number = meta->oldest_blob_file_number;
|
||||
} else {
|
||||
@@ -1659,9 +1670,7 @@ Status CompactionJob::OpenCompactionOutputFile(
|
||||
assert(sub_compact->builder == nullptr);
|
||||
// no need to lock because VersionSet::next_file_number_ is atomic
|
||||
uint64_t file_number = versions_->NewFileNumber();
|
||||
std::string fname =
|
||||
TableFileName(sub_compact->compaction->immutable_cf_options()->cf_paths,
|
||||
file_number, sub_compact->compaction->output_path_id());
|
||||
std::string fname = GetTableFileName(file_number);
|
||||
// Fire events.
|
||||
ColumnFamilyData* cfd = sub_compact->compaction->column_family_data();
|
||||
#ifndef ROCKSDB_LITE
|
||||
@@ -1704,7 +1713,7 @@ Status CompactionJob::OpenCompactionOutputFile(
|
||||
|
||||
// Try to figure out the output file's oldest ancester time.
|
||||
int64_t temp_current_time = 0;
|
||||
auto get_time_status = env_->GetCurrentTime(&temp_current_time);
|
||||
auto get_time_status = db_options_.clock->GetCurrentTime(&temp_current_time);
|
||||
// Safe to proceed even if GetCurrentTime fails. So, log and proceed.
|
||||
if (!get_time_status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
@@ -1741,8 +1750,8 @@ Status CompactionJob::OpenCompactionOutputFile(
|
||||
const auto& listeners =
|
||||
sub_compact->compaction->immutable_cf_options()->listeners;
|
||||
sub_compact->outfile.reset(new WritableFileWriter(
|
||||
std::move(writable_file), fname, file_options_, clock_, io_tracer_,
|
||||
db_options_.statistics.get(), listeners,
|
||||
std::move(writable_file), fname, file_options_, db_options_.clock,
|
||||
io_tracer_, db_options_.statistics.get(), listeners,
|
||||
db_options_.file_checksum_gen_factory.get(),
|
||||
tmp_set.Contains(FileType::kTableFile)));
|
||||
|
||||
@@ -1757,7 +1766,6 @@ Status CompactionJob::OpenCompactionOutputFile(
|
||||
cfd->internal_comparator(), cfd->int_tbl_prop_collector_factories(),
|
||||
cfd->GetID(), cfd->GetName(), sub_compact->outfile.get(),
|
||||
sub_compact->compaction->output_compression(),
|
||||
0 /*sample_for_compression */,
|
||||
sub_compact->compaction->output_compression_opts(),
|
||||
sub_compact->compaction->output_level(), skip_filters,
|
||||
oldest_ancester_time, 0 /* oldest_key_time */,
|
||||
@@ -1824,11 +1832,16 @@ void CompactionJob::UpdateCompactionStats() {
|
||||
}
|
||||
}
|
||||
|
||||
assert(compaction_job_stats_);
|
||||
compaction_stats_.bytes_read_blob =
|
||||
compaction_job_stats_->total_blob_bytes_read;
|
||||
|
||||
compaction_stats_.num_output_files =
|
||||
static_cast<int>(compact_->num_output_files) +
|
||||
static_cast<int>(compact_->num_output_files);
|
||||
compaction_stats_.num_output_files_blob =
|
||||
static_cast<int>(compact_->num_blob_output_files);
|
||||
compaction_stats_.bytes_written =
|
||||
compact_->total_bytes + compact_->total_blob_bytes;
|
||||
compaction_stats_.bytes_written = compact_->total_bytes;
|
||||
compaction_stats_.bytes_written_blob = compact_->total_blob_bytes;
|
||||
|
||||
if (compaction_stats_.num_input_records > compact_->num_output_records) {
|
||||
compaction_stats_.num_dropped_records =
|
||||
@@ -1868,8 +1881,10 @@ void CompactionJob::UpdateCompactionJobStats(
|
||||
|
||||
// output information
|
||||
compaction_job_stats_->total_output_bytes = stats.bytes_written;
|
||||
compaction_job_stats_->total_output_bytes_blob = stats.bytes_written_blob;
|
||||
compaction_job_stats_->num_output_records = compact_->num_output_records;
|
||||
compaction_job_stats_->num_output_files = stats.num_output_files;
|
||||
compaction_job_stats_->num_output_files_blob = stats.num_output_files_blob;
|
||||
|
||||
if (stats.num_output_files > 0) {
|
||||
CopyPrefix(compact_->SmallestUserKey(),
|
||||
@@ -1918,4 +1933,132 @@ void CompactionJob::LogCompaction() {
|
||||
}
|
||||
}
|
||||
|
||||
std::string CompactionJob::GetTableFileName(uint64_t file_number) {
|
||||
return TableFileName(compact_->compaction->immutable_cf_options()->cf_paths,
|
||||
file_number, compact_->compaction->output_path_id());
|
||||
}
|
||||
|
||||
std::string CompactionServiceCompactionJob::GetTableFileName(
|
||||
uint64_t file_number) {
|
||||
return MakeTableFileName(output_path_, file_number);
|
||||
}
|
||||
|
||||
CompactionServiceCompactionJob::CompactionServiceCompactionJob(
|
||||
int job_id, Compaction* compaction, const ImmutableDBOptions& db_options,
|
||||
const FileOptions& file_options, VersionSet* versions,
|
||||
const std::atomic<bool>* shutting_down, LogBuffer* log_buffer,
|
||||
FSDirectory* output_directory, Statistics* stats,
|
||||
InstrumentedMutex* db_mutex, ErrorHandler* db_error_handler,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
std::shared_ptr<Cache> table_cache, EventLogger* event_logger,
|
||||
const std::string& dbname, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::string& db_id, const std::string& db_session_id,
|
||||
const std::string& output_path,
|
||||
const CompactionServiceInput& compaction_service_input,
|
||||
CompactionServiceResult* compaction_service_result)
|
||||
: CompactionJob(
|
||||
job_id, compaction, db_options, file_options, versions, shutting_down,
|
||||
0, log_buffer, nullptr, output_directory, nullptr, stats, db_mutex,
|
||||
db_error_handler, existing_snapshots, kMaxSequenceNumber, nullptr,
|
||||
table_cache, event_logger,
|
||||
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,
|
||||
compaction->column_family_data()->GetFullHistoryTsLow()),
|
||||
output_path_(output_path),
|
||||
compaction_input_(compaction_service_input),
|
||||
compaction_result_(compaction_service_result) {}
|
||||
|
||||
Status CompactionServiceCompactionJob::Run() {
|
||||
AutoThreadOperationStageUpdater stage_updater(
|
||||
ThreadStatus::STAGE_COMPACTION_RUN);
|
||||
|
||||
auto* c = compact_->compaction;
|
||||
assert(c->column_family_data() != nullptr);
|
||||
assert(c->column_family_data()->current()->storage_info()->NumLevelFiles(
|
||||
compact_->compaction->level()) > 0);
|
||||
|
||||
write_hint_ =
|
||||
c->column_family_data()->CalculateSSTWriteHint(c->output_level());
|
||||
bottommost_level_ = c->bottommost_level();
|
||||
|
||||
compact_->sub_compact_states.emplace_back(c, compaction_input_.begin,
|
||||
compaction_input_.end,
|
||||
compaction_input_.approx_size);
|
||||
|
||||
log_buffer_->FlushBufferToLog();
|
||||
LogCompaction();
|
||||
const uint64_t start_micros = db_options_.clock->NowMicros();
|
||||
// Pick the only sub-compaction we should have
|
||||
assert(compact_->sub_compact_states.size() == 1);
|
||||
SubcompactionState* sub_compact = compact_->sub_compact_states.data();
|
||||
|
||||
ProcessKeyValueCompaction(sub_compact);
|
||||
|
||||
compaction_stats_.micros = db_options_.clock->NowMicros() - start_micros;
|
||||
compaction_stats_.cpu_micros = sub_compact->compaction_job_stats.cpu_micros;
|
||||
|
||||
RecordTimeToHistogram(stats_, COMPACTION_TIME, compaction_stats_.micros);
|
||||
RecordTimeToHistogram(stats_, COMPACTION_CPU_TIME,
|
||||
compaction_stats_.cpu_micros);
|
||||
|
||||
Status status = sub_compact->status;
|
||||
IOStatus io_s = sub_compact->io_status;
|
||||
|
||||
if (io_status_.ok()) {
|
||||
io_status_ = io_s;
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
constexpr IODebugContext* dbg = nullptr;
|
||||
|
||||
if (output_directory_) {
|
||||
io_s = output_directory_->Fsync(IOOptions(), dbg);
|
||||
}
|
||||
}
|
||||
if (io_status_.ok()) {
|
||||
io_status_ = io_s;
|
||||
}
|
||||
if (status.ok()) {
|
||||
status = io_s;
|
||||
}
|
||||
if (status.ok()) {
|
||||
// TODO: Add verify_table() and VerifyCompactionFileConsistency()
|
||||
}
|
||||
|
||||
// Finish up all book-keeping to unify the subcompaction results
|
||||
AggregateStatistics();
|
||||
UpdateCompactionStats();
|
||||
|
||||
compaction_result_->bytes_written = IOSTATS(bytes_written);
|
||||
compaction_result_->bytes_read = IOSTATS(bytes_read);
|
||||
RecordCompactionIOStats();
|
||||
|
||||
LogFlush(db_options_.info_log);
|
||||
compact_->status = status;
|
||||
compact_->status.PermitUncheckedError();
|
||||
|
||||
// Build compaction result
|
||||
compaction_result_->output_level = compact_->compaction->output_level();
|
||||
compaction_result_->output_path = output_path_;
|
||||
for (const auto& output_file : sub_compact->outputs) {
|
||||
auto& meta = output_file.meta;
|
||||
compaction_result_->output_files.emplace_back(
|
||||
MakeTableFileName(meta.fd.GetNumber()), meta.fd.smallest_seqno,
|
||||
meta.fd.largest_seqno, meta.smallest.Encode().ToString(),
|
||||
meta.largest.Encode().ToString(), meta.oldest_ancester_time,
|
||||
meta.file_creation_time, output_file.validator.GetHash(),
|
||||
meta.marked_for_compaction);
|
||||
}
|
||||
compaction_result_->num_output_records = sub_compact->num_output_records;
|
||||
compaction_result_->total_bytes = sub_compact->total_bytes;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void CompactionServiceCompactionJob::CleanupCompaction() {
|
||||
CompactionJob::CleanupCompaction();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+143
-24
@@ -17,6 +17,7 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "db/blob/blob_file_completion_callback.h"
|
||||
#include "db/column_family.h"
|
||||
#include "db/compaction/compaction_iterator.h"
|
||||
#include "db/dbformat.h"
|
||||
@@ -80,9 +81,10 @@ class CompactionJob {
|
||||
Env::Priority thread_pri, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::atomic<int>* manual_compaction_paused = nullptr,
|
||||
const std::string& db_id = "", const std::string& db_session_id = "",
|
||||
std::string full_history_ts_low = "");
|
||||
std::string full_history_ts_low = "",
|
||||
BlobFileCompletionCallback* blob_callback = nullptr);
|
||||
|
||||
~CompactionJob();
|
||||
virtual ~CompactionJob();
|
||||
|
||||
// no copy/move
|
||||
CompactionJob(CompactionJob&& job) = delete;
|
||||
@@ -105,11 +107,35 @@ class CompactionJob {
|
||||
// Return the IO status
|
||||
IOStatus io_status() const { return io_status_; }
|
||||
|
||||
private:
|
||||
protected:
|
||||
struct SubcompactionState;
|
||||
// CompactionJob state
|
||||
struct CompactionState;
|
||||
|
||||
void AggregateStatistics();
|
||||
void UpdateCompactionStats();
|
||||
void LogCompaction();
|
||||
void RecordCompactionIOStats();
|
||||
void CleanupCompaction();
|
||||
|
||||
// Call compaction filter. Then iterate through input and compact the
|
||||
// kv-pairs
|
||||
void ProcessKeyValueCompaction(SubcompactionState* sub_compact);
|
||||
|
||||
CompactionState* compact_;
|
||||
InternalStats::CompactionStats compaction_stats_;
|
||||
const ImmutableDBOptions& db_options_;
|
||||
LogBuffer* log_buffer_;
|
||||
FSDirectory* output_directory_;
|
||||
Statistics* stats_;
|
||||
// Is this compaction creating a file in the bottom most level?
|
||||
bool bottommost_level_;
|
||||
|
||||
Env::WriteLifeTimeHint write_hint_;
|
||||
|
||||
IOStatus io_status_;
|
||||
|
||||
private:
|
||||
// Generates a histogram representing potential divisions of key ranges from
|
||||
// the input. It adds the starting and/or ending keys of certain input files
|
||||
// to the working set and then finds the approximate size of data in between
|
||||
@@ -120,9 +146,6 @@ class CompactionJob {
|
||||
// update the thread status for starting a compaction.
|
||||
void ReportStartedCompaction(Compaction* compaction);
|
||||
void AllocateCompactionOutputFileNumbers();
|
||||
// Call compaction filter. Then iterate through input and compact the
|
||||
// kv-pairs
|
||||
void ProcessKeyValueCompaction(SubcompactionState* sub_compact);
|
||||
|
||||
Status FinishCompactionOutputFile(
|
||||
const Status& input_status, SubcompactionState* sub_compact,
|
||||
@@ -130,37 +153,26 @@ class CompactionJob {
|
||||
CompactionIterationStats* range_del_out_stats,
|
||||
const Slice* next_table_min_key = nullptr);
|
||||
Status InstallCompactionResults(const MutableCFOptions& mutable_cf_options);
|
||||
void RecordCompactionIOStats();
|
||||
Status OpenCompactionOutputFile(SubcompactionState* sub_compact);
|
||||
void CleanupCompaction();
|
||||
void UpdateCompactionJobStats(
|
||||
const InternalStats::CompactionStats& stats) const;
|
||||
void RecordDroppedKeys(const CompactionIterationStats& c_iter_stats,
|
||||
CompactionJobStats* compaction_job_stats = nullptr);
|
||||
|
||||
void UpdateCompactionStats();
|
||||
void UpdateCompactionInputStatsHelper(
|
||||
int* num_files, uint64_t* bytes_read, int input_level);
|
||||
|
||||
void LogCompaction();
|
||||
|
||||
int job_id_;
|
||||
|
||||
// CompactionJob state
|
||||
struct CompactionState;
|
||||
CompactionState* compact_;
|
||||
CompactionJobStats* compaction_job_stats_;
|
||||
InternalStats::CompactionStats compaction_stats_;
|
||||
|
||||
// DBImpl state
|
||||
const std::string& dbname_;
|
||||
const std::string db_id_;
|
||||
const std::string db_session_id_;
|
||||
const ImmutableDBOptions& db_options_;
|
||||
const FileOptions file_options_;
|
||||
|
||||
Env* env_;
|
||||
std::shared_ptr<SystemClock> clock_;
|
||||
std::shared_ptr<IOTracer> io_tracer_;
|
||||
FileSystemPtr fs_;
|
||||
// env_option optimized for compaction table reads
|
||||
@@ -169,11 +181,8 @@ class CompactionJob {
|
||||
const std::atomic<bool>* shutting_down_;
|
||||
const std::atomic<int>* manual_compaction_paused_;
|
||||
const SequenceNumber preserve_deletes_seqnum_;
|
||||
LogBuffer* log_buffer_;
|
||||
FSDirectory* db_directory_;
|
||||
FSDirectory* output_directory_;
|
||||
FSDirectory* blob_output_directory_;
|
||||
Statistics* stats_;
|
||||
InstrumentedMutex* db_mutex_;
|
||||
ErrorHandler* db_error_handler_;
|
||||
// If there were two snapshots with seq numbers s1 and
|
||||
@@ -193,18 +202,128 @@ class CompactionJob {
|
||||
|
||||
EventLogger* event_logger_;
|
||||
|
||||
// Is this compaction creating a file in the bottom most level?
|
||||
bool bottommost_level_;
|
||||
bool paranoid_file_checks_;
|
||||
bool measure_io_stats_;
|
||||
// Stores the Slices that designate the boundaries for each subcompaction
|
||||
std::vector<Slice> boundaries_;
|
||||
// Stores the approx size of keys covered in the range of each subcompaction
|
||||
std::vector<uint64_t> sizes_;
|
||||
Env::WriteLifeTimeHint write_hint_;
|
||||
Env::Priority thread_pri_;
|
||||
IOStatus io_status_;
|
||||
std::string full_history_ts_low_;
|
||||
BlobFileCompletionCallback* blob_callback_;
|
||||
|
||||
// Get table file name in where it's outputting to, which should also be in
|
||||
// `output_directory_`.
|
||||
virtual std::string GetTableFileName(uint64_t file_number);
|
||||
};
|
||||
|
||||
// CompactionServiceInput is used the pass compaction information between two
|
||||
// db instances. It contains the information needed to do a compaction. It
|
||||
// doesn't contain the LSM tree information, which is passed though MANIFEST
|
||||
// file.
|
||||
struct CompactionServiceInput {
|
||||
ColumnFamilyDescriptor column_family;
|
||||
|
||||
DBOptions db_options;
|
||||
|
||||
std::vector<SequenceNumber> snapshots;
|
||||
|
||||
// SST files for compaction, it should already be expended to include all the
|
||||
// files needed for this compaction, for both input level files and output
|
||||
// level files.
|
||||
std::vector<std::string> input_files;
|
||||
int output_level;
|
||||
|
||||
// information for subcompaction
|
||||
Slice* begin = nullptr;
|
||||
Slice* end = nullptr;
|
||||
uint64_t approx_size = 0;
|
||||
};
|
||||
|
||||
// CompactionServiceOutputFile is the metadata for the output SST file
|
||||
struct CompactionServiceOutputFile {
|
||||
std::string file_name;
|
||||
SequenceNumber smallest_seqno;
|
||||
SequenceNumber largest_seqno;
|
||||
std::string smallest_internal_key;
|
||||
std::string largest_internal_key;
|
||||
uint64_t oldest_ancester_time;
|
||||
uint64_t file_creation_time;
|
||||
uint64_t paranoid_hash;
|
||||
bool marked_for_compaction;
|
||||
|
||||
CompactionServiceOutputFile() = default;
|
||||
CompactionServiceOutputFile(
|
||||
const std::string& name, SequenceNumber smallest, SequenceNumber largest,
|
||||
std::string _smallest_internal_key, std::string _largest_internal_key,
|
||||
uint64_t _oldest_ancester_time, uint64_t _file_creation_time,
|
||||
uint64_t _paranoid_hash, bool _marked_for_compaction)
|
||||
: file_name(name),
|
||||
smallest_seqno(smallest),
|
||||
largest_seqno(largest),
|
||||
smallest_internal_key(std::move(_smallest_internal_key)),
|
||||
largest_internal_key(std::move(_largest_internal_key)),
|
||||
oldest_ancester_time(_oldest_ancester_time),
|
||||
file_creation_time(_file_creation_time),
|
||||
paranoid_hash(_paranoid_hash),
|
||||
marked_for_compaction(_marked_for_compaction) {}
|
||||
};
|
||||
|
||||
// CompactionServiceResult contains the compaction result from a different db
|
||||
// instance, with these information, the primary db instance with write
|
||||
// permission is able to install the result to the DB.
|
||||
struct CompactionServiceResult {
|
||||
std::vector<CompactionServiceOutputFile> output_files;
|
||||
int output_level;
|
||||
|
||||
// location of the output files
|
||||
std::string output_path;
|
||||
|
||||
// some statistics about the compaction
|
||||
uint64_t num_output_records;
|
||||
uint64_t total_bytes;
|
||||
uint64_t bytes_read;
|
||||
uint64_t bytes_written;
|
||||
CompactionJobStats stats;
|
||||
};
|
||||
|
||||
// CompactionServiceCompactionJob is an read-only compaction job, it takes
|
||||
// input information from `compaction_service_input` and put result information
|
||||
// in `compaction_service_result`, the SST files are generated to `output_path`.
|
||||
class CompactionServiceCompactionJob : private CompactionJob {
|
||||
public:
|
||||
CompactionServiceCompactionJob(
|
||||
int job_id, Compaction* compaction, const ImmutableDBOptions& db_options,
|
||||
const FileOptions& file_options, VersionSet* versions,
|
||||
const std::atomic<bool>* shutting_down, LogBuffer* log_buffer,
|
||||
FSDirectory* output_directory, Statistics* stats,
|
||||
InstrumentedMutex* db_mutex, ErrorHandler* db_error_handler,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
std::shared_ptr<Cache> table_cache, EventLogger* event_logger,
|
||||
const std::string& dbname, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::string& db_id, const std::string& db_session_id,
|
||||
const std::string& output_path,
|
||||
const CompactionServiceInput& compaction_service_input,
|
||||
CompactionServiceResult* compaction_service_result);
|
||||
|
||||
// Run the compaction in current thread and return the result
|
||||
Status Run();
|
||||
|
||||
void CleanupCompaction();
|
||||
|
||||
IOStatus io_status() const { return CompactionJob::io_status(); }
|
||||
|
||||
private:
|
||||
// Get table file name in output_path
|
||||
std::string GetTableFileName(uint64_t file_number) override;
|
||||
// Specific the compaction output path, otherwise it uses default DB path
|
||||
const std::string output_path_;
|
||||
|
||||
// Compaction job input
|
||||
const CompactionServiceInput& compaction_input_;
|
||||
|
||||
// Compaction job result
|
||||
CompactionServiceResult* compaction_result_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -1004,6 +1004,7 @@ Status CompactionPicker::SanitizeCompactionInputFiles(
|
||||
// any currently-existing files.
|
||||
for (auto file_num : *input_files) {
|
||||
bool found = false;
|
||||
int input_file_level = -1;
|
||||
for (const auto& level_meta : cf_meta.levels) {
|
||||
for (const auto& file_meta : level_meta.files) {
|
||||
if (file_num == TableFileNameToNumber(file_meta.name)) {
|
||||
@@ -1013,6 +1014,7 @@ Status CompactionPicker::SanitizeCompactionInputFiles(
|
||||
" is already being compacted.");
|
||||
}
|
||||
found = true;
|
||||
input_file_level = level_meta.level;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1025,6 +1027,13 @@ Status CompactionPicker::SanitizeCompactionInputFiles(
|
||||
"Specified compaction input file " + MakeTableFileName("", file_num) +
|
||||
" does not exist in column family " + cf_meta.name + ".");
|
||||
}
|
||||
if (input_file_level > output_level) {
|
||||
return Status::InvalidArgument(
|
||||
"Cannot compact file to up level, input file: " +
|
||||
MakeTableFileName("", file_num) + " level " +
|
||||
ToString(input_file_level) + " > output level " +
|
||||
ToString(output_level));
|
||||
}
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
|
||||
@@ -45,7 +45,7 @@ Compaction* FIFOCompactionPicker::PickTTLCompaction(
|
||||
uint64_t total_size = GetTotalFilesSize(level_files);
|
||||
|
||||
int64_t _current_time;
|
||||
auto status = ioptions_.env->GetCurrentTime(&_current_time);
|
||||
auto status = ioptions_.clock->GetCurrentTime(&_current_time);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"[%s] FIFO compaction: Couldn't get current time: %s. "
|
||||
|
||||
@@ -650,7 +650,7 @@ TEST_F(CompactionPickerTest, UniversalPeriodicCompaction3) {
|
||||
|
||||
TEST_F(CompactionPickerTest, UniversalPeriodicCompaction4) {
|
||||
// The case where universal periodic compaction couldn't form
|
||||
// a compaction that inlcudes any file marked for periodic compaction.
|
||||
// a compaction that includes any file marked for periodic compaction.
|
||||
// Right now we form the compaction anyway if it is more than one
|
||||
// sorted run. Just put the case here to validate that it doesn't
|
||||
// crash.
|
||||
@@ -800,7 +800,7 @@ TEST_F(CompactionPickerTest, CompactionPriMinOverlapping2) {
|
||||
Add(2, 6U, "150", "175",
|
||||
60000000U); // Overlaps with file 26, 27, total size 521M
|
||||
Add(2, 7U, "176", "200", 60000000U); // Overlaps with file 27, 28, total size
|
||||
// 520M, the smalelst overlapping
|
||||
// 520M, the smallest overlapping
|
||||
Add(2, 8U, "201", "300",
|
||||
60000000U); // Overlaps with file 28, 29, total size 521M
|
||||
|
||||
@@ -1228,7 +1228,7 @@ TEST_F(CompactionPickerTest, NotScheduleL1IfL0WithHigherPri1) {
|
||||
Add(0, 32U, "001", "400", 1000000000U, 0, 0);
|
||||
Add(0, 33U, "001", "400", 1000000000U, 0, 0);
|
||||
|
||||
// L1 total size 2GB, score 2.2. If one file being comapcted, score 1.1.
|
||||
// L1 total size 2GB, score 2.2. If one file being compacted, score 1.1.
|
||||
Add(1, 4U, "050", "300", 1000000000U, 0, 0);
|
||||
file_map_[4u].first->being_compacted = true;
|
||||
Add(1, 5U, "301", "350", 1000000000U, 0, 0);
|
||||
@@ -1261,7 +1261,7 @@ TEST_F(CompactionPickerTest, NotScheduleL1IfL0WithHigherPri2) {
|
||||
Add(0, 32U, "001", "400", 1000000000U, 0, 0);
|
||||
Add(0, 33U, "001", "400", 1000000000U, 0, 0);
|
||||
|
||||
// L1 total size 2GB, score 2.2. If one file being comapcted, score 1.1.
|
||||
// L1 total size 2GB, score 2.2. If one file being compacted, score 1.1.
|
||||
Add(1, 4U, "050", "300", 1000000000U, 0, 0);
|
||||
Add(1, 5U, "301", "350", 1000000000U, 0, 0);
|
||||
|
||||
|
||||
@@ -733,7 +733,7 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
|
||||
}
|
||||
|
||||
// Look at overall size amplification. If size amplification
|
||||
// exceeeds the configured value, then do a compaction
|
||||
// exceeds the configured value, then do a compaction
|
||||
// of the candidate files all the way upto the earliest
|
||||
// base file (overrides configured values of file-size ratios,
|
||||
// min_merge_width and max_merge_width).
|
||||
|
||||
@@ -882,7 +882,7 @@ TEST_F(CorruptionTest, VerifyWholeTableChecksum) {
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
int count{0};
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::VerifySstFileChecksum:mismatch", [&](void* arg) {
|
||||
"DBImpl::VerifyFullFileChecksum:mismatch", [&](void* arg) {
|
||||
auto* s = reinterpret_cast<Status*>(arg);
|
||||
ASSERT_NE(s, nullptr);
|
||||
++count;
|
||||
|
||||
@@ -2820,6 +2820,7 @@ class DBBasicTestMultiGet : public DBTestBase {
|
||||
|
||||
const char* Name() const override { return "MyBlockCache"; }
|
||||
|
||||
using Cache::Insert;
|
||||
Status Insert(const Slice& key, void* value, size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value),
|
||||
Handle** handle = nullptr,
|
||||
@@ -2828,6 +2829,7 @@ class DBBasicTestMultiGet : public DBTestBase {
|
||||
return target_->Insert(key, value, charge, deleter, handle, priority);
|
||||
}
|
||||
|
||||
using Cache::Lookup;
|
||||
Handle* Lookup(const Slice& key, Statistics* stats = nullptr) override {
|
||||
num_lookups_++;
|
||||
Handle* handle = target_->Lookup(key, stats);
|
||||
|
||||
@@ -446,6 +446,7 @@ class MockCache : public LRUCache {
|
||||
false /*strict_capacity_limit*/, 0.0 /*high_pri_pool_ratio*/) {
|
||||
}
|
||||
|
||||
using ShardedCache::Insert;
|
||||
Status Insert(const Slice& key, void* value, size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value), Handle** handle,
|
||||
Priority priority) override {
|
||||
@@ -533,6 +534,7 @@ class LookupLiarCache : public CacheWrapper {
|
||||
explicit LookupLiarCache(std::shared_ptr<Cache> target)
|
||||
: CacheWrapper(std::move(target)) {}
|
||||
|
||||
using Cache::Lookup;
|
||||
Handle* Lookup(const Slice& key, Statistics* stats) override {
|
||||
if (nth_lookup_not_found_ == 1) {
|
||||
nth_lookup_not_found_ = 0;
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "options/options_helper.h"
|
||||
#include "port/stack_trace.h"
|
||||
@@ -807,6 +810,7 @@ class TestingContextCustomFilterPolicy
|
||||
TEST_F(DBBloomFilterTest, ContextCustomFilterPolicy) {
|
||||
for (bool fifo : {true, false}) {
|
||||
Options options = CurrentOptions();
|
||||
options.max_open_files = fifo ? -1 : options.max_open_files;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
options.compaction_style =
|
||||
fifo ? kCompactionStyleFIFO : kCompactionStyleLevel;
|
||||
@@ -817,6 +821,7 @@ TEST_F(DBBloomFilterTest, ContextCustomFilterPolicy) {
|
||||
table_options.format_version = 5;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
TryReopen(options);
|
||||
CreateAndReopenWithCF({fifo ? "abe" : "bob"}, options);
|
||||
|
||||
const int maxKey = 10000;
|
||||
@@ -2117,6 +2122,54 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBloomFilterTest, SeekForPrevWithPartitionedFilters) {
|
||||
Options options = CurrentOptions();
|
||||
constexpr size_t kNumKeys = 10000;
|
||||
static_assert(kNumKeys <= 10000, "kNumKeys have to be <= 10000");
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeys + 10));
|
||||
options.create_if_missing = true;
|
||||
constexpr size_t kPrefixLength = 4;
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(kPrefixLength));
|
||||
options.compression = kNoCompression;
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(50));
|
||||
bbto.index_shortening =
|
||||
BlockBasedTableOptions::IndexShorteningMode::kNoShortening;
|
||||
bbto.block_size = 128;
|
||||
bbto.metadata_block_size = 128;
|
||||
bbto.partition_filters = true;
|
||||
bbto.index_type = BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
const std::string value(64, '\0');
|
||||
|
||||
WriteOptions write_opts;
|
||||
write_opts.disableWAL = true;
|
||||
for (size_t i = 0; i < kNumKeys; ++i) {
|
||||
std::ostringstream oss;
|
||||
oss << std::setfill('0') << std::setw(4) << std::fixed << i;
|
||||
ASSERT_OK(db_->Put(write_opts, oss.str(), value));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ReadOptions read_opts;
|
||||
// Use legacy, implicit prefix seek
|
||||
read_opts.total_order_seek = false;
|
||||
read_opts.auto_prefix_mode = false;
|
||||
std::unique_ptr<Iterator> it(db_->NewIterator(read_opts));
|
||||
for (size_t i = 0; i < kNumKeys; ++i) {
|
||||
// Seek with a key after each one added but with same prefix. One will
|
||||
// surely cross a partition boundary.
|
||||
std::ostringstream oss;
|
||||
oss << std::setfill('0') << std::setw(4) << std::fixed << i << "a";
|
||||
it->SeekForPrev(oss.str());
|
||||
ASSERT_OK(it->status());
|
||||
ASSERT_TRUE(it->Valid());
|
||||
}
|
||||
it.reset();
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
+96
-43
@@ -33,31 +33,6 @@ class DBCompactionTest : public DBTestBase {
|
||||
public:
|
||||
DBCompactionTest()
|
||||
: DBTestBase("/db_compaction_test", /*env_do_fsync=*/true) {}
|
||||
|
||||
std::vector<uint64_t> GetBlobFileNumbers() {
|
||||
VersionSet* const versions = dbfull()->TEST_GetVersionSet();
|
||||
assert(versions);
|
||||
|
||||
ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault();
|
||||
assert(cfd);
|
||||
|
||||
Version* const current = cfd->current();
|
||||
assert(current);
|
||||
|
||||
const VersionStorageInfo* const storage_info = current->storage_info();
|
||||
assert(storage_info);
|
||||
|
||||
const auto& blob_files = storage_info->GetBlobFiles();
|
||||
|
||||
std::vector<uint64_t> result;
|
||||
result.reserve(blob_files.size());
|
||||
|
||||
for (const auto& blob_file : blob_files) {
|
||||
result.emplace_back(blob_file.first);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
class DBCompactionTestWithParam
|
||||
@@ -206,6 +181,7 @@ Options DeletionTriggerOptions(Options options) {
|
||||
options.target_file_size_base * options.target_file_size_multiplier;
|
||||
options.max_bytes_for_level_multiplier = 2;
|
||||
options.disable_auto_compactions = false;
|
||||
options.compaction_options_universal.max_size_amplification_percent = 100;
|
||||
return options;
|
||||
}
|
||||
|
||||
@@ -360,12 +336,34 @@ TEST_P(DBCompactionTestWithParam, CompactionDeletionTrigger) {
|
||||
for (int k = 0; k < kTestSize; ++k) {
|
||||
ASSERT_OK(Delete(Key(k)));
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_OK(Size(Key(0), Key(kTestSize - 1), &db_size[1]));
|
||||
|
||||
// must have much smaller db size.
|
||||
ASSERT_GT(db_size[0] / 3, db_size[1]);
|
||||
if (options.compaction_style == kCompactionStyleUniversal) {
|
||||
// Claim: in universal compaction none of the original data will remain
|
||||
// once compactions settle.
|
||||
//
|
||||
// Proof: The compensated size of the file containing the most tombstones
|
||||
// is enough on its own to trigger size amp compaction. Size amp
|
||||
// compaction is a full compaction, so all tombstones meet the obsolete
|
||||
// keys they cover.
|
||||
ASSERT_EQ(0, db_size[1]);
|
||||
} else {
|
||||
// Claim: in level compaction at most `db_size[0] / 2` of the original
|
||||
// data will remain once compactions settle.
|
||||
//
|
||||
// Proof: Assume the original data is all in the bottom level. If it were
|
||||
// not, it would meet its tombstone sooner. The original data size is
|
||||
// large enough to require fanout to bottom level to be greater than
|
||||
// `max_bytes_for_level_multiplier == 2`. In the level just above,
|
||||
// tombstones must cover less than `db_size[0] / 4` bytes since fanout >=
|
||||
// 2 and file size is compensated by doubling the size of values we expect
|
||||
// are covered (`kDeletionWeightOnCompaction == 2`). The tombstones in
|
||||
// levels above must cover less than `db_size[0] / 8` bytes of original
|
||||
// data, `db_size[0] / 16`, and so on.
|
||||
ASSERT_GT(db_size[0] / 2, db_size[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // ROCKSDB_VALGRIND_RUN
|
||||
@@ -632,9 +630,8 @@ TEST_P(DBCompactionTestWithParam, CompactionDeletionTriggerReopen) {
|
||||
}
|
||||
ASSERT_OK(Size(Key(0), Key(kTestSize - 1), &db_size[1]));
|
||||
Close();
|
||||
// as auto_compaction is off, we shouldn't see too much reduce
|
||||
// in db size.
|
||||
ASSERT_LT(db_size[0] / 3, db_size[1]);
|
||||
// as auto_compaction is off, we shouldn't see any reduction in db size.
|
||||
ASSERT_LE(db_size[0], db_size[1]);
|
||||
|
||||
// round 3 --- reopen db with auto_compaction on and see if
|
||||
// deletion compensation still work.
|
||||
@@ -648,7 +645,13 @@ TEST_P(DBCompactionTestWithParam, CompactionDeletionTriggerReopen) {
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_OK(Size(Key(0), Key(kTestSize - 1), &db_size[2]));
|
||||
// this time we're expecting significant drop in size.
|
||||
ASSERT_GT(db_size[0] / 3, db_size[2]);
|
||||
//
|
||||
// See "CompactionDeletionTrigger" test for proof that at most
|
||||
// `db_size[0] / 2` of the original data remains. In addition to that, this
|
||||
// test inserts `db_size[0] / 10` to push the tombstones into SST files and
|
||||
// then through automatic compactions. So in total `3 * db_size[0] / 5` of
|
||||
// the original data may remain.
|
||||
ASSERT_GT(3 * db_size[0] / 5, db_size[2]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -735,8 +738,15 @@ TEST_F(DBCompactionTest, DisableStatsUpdateReopen) {
|
||||
values.push_back(rnd.RandomString(kCDTValueSize));
|
||||
ASSERT_OK(Put(Key(k), values[k]));
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
// L1 and L2 can fit deletions iff size compensation does not take effect,
|
||||
// i.e., when `skip_stats_update_on_db_open == true`. Move any remaining
|
||||
// files at or above L2 down to L3 to ensure obsolete data does not
|
||||
// accidentally meet its tombstone above L3. This makes the final size more
|
||||
// deterministic and easy to see whether size compensation for deletions
|
||||
// took effect.
|
||||
MoveFilesToLevel(3 /* level */);
|
||||
ASSERT_OK(Size(Key(0), Key(kTestSize - 1), &db_size[0]));
|
||||
Close();
|
||||
|
||||
@@ -752,9 +762,8 @@ TEST_F(DBCompactionTest, DisableStatsUpdateReopen) {
|
||||
}
|
||||
ASSERT_OK(Size(Key(0), Key(kTestSize - 1), &db_size[1]));
|
||||
Close();
|
||||
// as auto_compaction is off, we shouldn't see too much reduce
|
||||
// in db size.
|
||||
ASSERT_LT(db_size[0] / 3, db_size[1]);
|
||||
// as auto_compaction is off, we shouldn't see any reduction in db size.
|
||||
ASSERT_LE(db_size[0], db_size[1]);
|
||||
|
||||
// round 3 --- reopen db with auto_compaction on and see if
|
||||
// deletion compensation still work.
|
||||
@@ -767,10 +776,17 @@ TEST_F(DBCompactionTest, DisableStatsUpdateReopen) {
|
||||
if (options.skip_stats_update_on_db_open) {
|
||||
// If update stats on DB::Open is disable, we don't expect
|
||||
// deletion entries taking effect.
|
||||
ASSERT_LT(db_size[0] / 3, db_size[2]);
|
||||
//
|
||||
// The deletions are small enough to fit in L1 and L2, and obsolete keys
|
||||
// were moved to L3+, so none of the original data should have been
|
||||
// dropped.
|
||||
ASSERT_LE(db_size[0], db_size[2]);
|
||||
} else {
|
||||
// Otherwise, we should see a significant drop in db size.
|
||||
ASSERT_GT(db_size[0] / 3, db_size[2]);
|
||||
//
|
||||
// See "CompactionDeletionTrigger" test for proof that at most
|
||||
// `db_size[0] / 2` of the original data remains.
|
||||
ASSERT_GT(db_size[0] / 2, db_size[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5946,13 +5962,14 @@ TEST_F(DBCompactionTest, CompactionWithBlob) {
|
||||
const InternalStats* const internal_stats = cfd->internal_stats();
|
||||
ASSERT_NE(internal_stats, nullptr);
|
||||
|
||||
const uint64_t expected_bytes =
|
||||
table_file->fd.GetFileSize() + blob_file->GetTotalBlobBytes();
|
||||
|
||||
const auto& compaction_stats = internal_stats->TEST_GetCompactionStats();
|
||||
ASSERT_GE(compaction_stats.size(), 2);
|
||||
ASSERT_EQ(compaction_stats[1].bytes_written, expected_bytes);
|
||||
ASSERT_EQ(compaction_stats[1].num_output_files, 2);
|
||||
ASSERT_EQ(compaction_stats[1].bytes_read_blob, 0);
|
||||
ASSERT_EQ(compaction_stats[1].bytes_written, table_file->fd.GetFileSize());
|
||||
ASSERT_EQ(compaction_stats[1].bytes_written_blob,
|
||||
blob_file->GetTotalBlobBytes());
|
||||
ASSERT_EQ(compaction_stats[1].num_output_files, 1);
|
||||
ASSERT_EQ(compaction_stats[1].num_output_files_blob, 1);
|
||||
}
|
||||
|
||||
class DBCompactionTestBlobError
|
||||
@@ -6039,12 +6056,18 @@ TEST_P(DBCompactionTestBlobError, CompactionError) {
|
||||
ASSERT_GE(compaction_stats.size(), 2);
|
||||
|
||||
if (sync_point_ == "BlobFileBuilder::WriteBlobToFile:AddRecord") {
|
||||
ASSERT_EQ(compaction_stats[1].bytes_read_blob, 0);
|
||||
ASSERT_EQ(compaction_stats[1].bytes_written, 0);
|
||||
ASSERT_EQ(compaction_stats[1].bytes_written_blob, 0);
|
||||
ASSERT_EQ(compaction_stats[1].num_output_files, 0);
|
||||
ASSERT_EQ(compaction_stats[1].num_output_files_blob, 0);
|
||||
} else {
|
||||
// SST file writing succeeded; blob file writing failed (during Finish)
|
||||
ASSERT_EQ(compaction_stats[1].bytes_read_blob, 0);
|
||||
ASSERT_GT(compaction_stats[1].bytes_written, 0);
|
||||
ASSERT_EQ(compaction_stats[1].bytes_written_blob, 0);
|
||||
ASSERT_EQ(compaction_stats[1].num_output_files, 1);
|
||||
ASSERT_EQ(compaction_stats[1].num_output_files_blob, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6129,6 +6152,36 @@ TEST_P(DBCompactionTestBlobGC, CompactionWithBlobGC) {
|
||||
for (size_t i = cutoff_index; i < original_blob_files.size(); ++i) {
|
||||
ASSERT_EQ(new_blob_files[i - cutoff_index], original_blob_files[i]);
|
||||
}
|
||||
|
||||
VersionSet* const versions = dbfull()->TEST_GetVersionSet();
|
||||
assert(versions);
|
||||
assert(versions->GetColumnFamilySet());
|
||||
|
||||
ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault();
|
||||
assert(cfd);
|
||||
|
||||
const InternalStats* const internal_stats = cfd->internal_stats();
|
||||
assert(internal_stats);
|
||||
|
||||
const auto& compaction_stats = internal_stats->TEST_GetCompactionStats();
|
||||
ASSERT_GE(compaction_stats.size(), 2);
|
||||
|
||||
if (blob_gc_age_cutoff_ > 0.0) {
|
||||
ASSERT_GT(compaction_stats[1].bytes_read_blob, 0);
|
||||
|
||||
if (updated_enable_blob_files_) {
|
||||
// GC relocated some blobs to new blob files
|
||||
ASSERT_GT(compaction_stats[1].bytes_written_blob, 0);
|
||||
ASSERT_EQ(compaction_stats[1].bytes_read_blob,
|
||||
compaction_stats[1].bytes_written_blob);
|
||||
} else {
|
||||
// GC moved some blobs back to the LSM, no new blob files
|
||||
ASSERT_EQ(compaction_stats[1].bytes_written_blob, 0);
|
||||
}
|
||||
} else {
|
||||
ASSERT_EQ(compaction_stats[1].bytes_read_blob, 0);
|
||||
ASSERT_EQ(compaction_stats[1].bytes_written_blob, 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, CompactionWithBlobGCError_CorruptIndex) {
|
||||
|
||||
+75
-24
@@ -82,29 +82,16 @@ TEST_F(DBFlushTest, SyncFail) {
|
||||
options.env = fault_injection_env.get();
|
||||
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBFlushTest::SyncFail:GetVersionRefCount:1",
|
||||
"DBImpl::FlushMemTableToOutputFile:BeforePickMemtables"},
|
||||
{"DBImpl::FlushMemTableToOutputFile:AfterPickMemtables",
|
||||
"DBFlushTest::SyncFail:GetVersionRefCount:2"},
|
||||
{"DBFlushTest::SyncFail:1", "DBImpl::SyncClosedLogs:Start"},
|
||||
{{"DBFlushTest::SyncFail:1", "DBImpl::SyncClosedLogs:Start"},
|
||||
{"DBImpl::SyncClosedLogs:Failed", "DBFlushTest::SyncFail:2"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
ASSERT_OK(Put("key", "value"));
|
||||
auto* cfd =
|
||||
static_cast_with_check<ColumnFamilyHandleImpl>(db_->DefaultColumnFamily())
|
||||
->cfd();
|
||||
FlushOptions flush_options;
|
||||
flush_options.wait = false;
|
||||
ASSERT_OK(dbfull()->Flush(flush_options));
|
||||
// Flush installs a new super-version. Get the ref count after that.
|
||||
auto current_before = cfd->current();
|
||||
int refs_before = cfd->current()->TEST_refs();
|
||||
TEST_SYNC_POINT("DBFlushTest::SyncFail:GetVersionRefCount:1");
|
||||
TEST_SYNC_POINT("DBFlushTest::SyncFail:GetVersionRefCount:2");
|
||||
int refs_after_picking_memtables = cfd->current()->TEST_refs();
|
||||
ASSERT_EQ(refs_before + 1, refs_after_picking_memtables);
|
||||
fault_injection_env->SetFilesystemActive(false);
|
||||
TEST_SYNC_POINT("DBFlushTest::SyncFail:1");
|
||||
TEST_SYNC_POINT("DBFlushTest::SyncFail:2");
|
||||
@@ -115,9 +102,6 @@ TEST_F(DBFlushTest, SyncFail) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
ASSERT_EQ("", FilesPerLevel()); // flush failed.
|
||||
#endif // ROCKSDB_LITE
|
||||
// Backgroun flush job should release ref count to current version.
|
||||
ASSERT_EQ(current_before, cfd->current());
|
||||
ASSERT_EQ(refs_before, cfd->current()->TEST_refs());
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
@@ -182,6 +166,66 @@ TEST_F(DBFlushTest, FlushInLowPriThreadPool) {
|
||||
ASSERT_EQ(1, num_compactions);
|
||||
}
|
||||
|
||||
// Test when flush job is submitted to low priority thread pool and when DB is
|
||||
// closed in the meanwhile, CloseHelper doesn't hang.
|
||||
TEST_F(DBFlushTest, CloseDBWhenFlushInLowPri) {
|
||||
Options options = CurrentOptions();
|
||||
options.max_background_flushes = 1;
|
||||
options.max_total_wal_size = 8192;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
CreateColumnFamilies({"cf1", "cf2"}, options);
|
||||
|
||||
env_->SetBackgroundThreads(0, Env::HIGH);
|
||||
env_->SetBackgroundThreads(1, Env::LOW);
|
||||
test::SleepingBackgroundTask sleeping_task_low;
|
||||
int num_flushes = 0;
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack("DBImpl::BGWorkFlush",
|
||||
[&](void* /*arg*/) { ++num_flushes; });
|
||||
|
||||
int num_low_flush_unscheduled = 0;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::UnscheduleLowFlushCallback", [&](void* /*arg*/) {
|
||||
num_low_flush_unscheduled++;
|
||||
// There should be one flush job in low pool that needs to be
|
||||
// unscheduled
|
||||
ASSERT_EQ(num_low_flush_unscheduled, 1);
|
||||
});
|
||||
|
||||
int num_high_flush_unscheduled = 0;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::UnscheduleHighFlushCallback", [&](void* /*arg*/) {
|
||||
num_high_flush_unscheduled++;
|
||||
// There should be no flush job in high pool
|
||||
ASSERT_EQ(num_high_flush_unscheduled, 0);
|
||||
});
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(Put(0, "key1", DummyString(8192)));
|
||||
// Block thread so that flush cannot be run and can be removed from the queue
|
||||
// when called Unschedule.
|
||||
env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task_low,
|
||||
Env::Priority::LOW);
|
||||
sleeping_task_low.WaitUntilSleeping();
|
||||
|
||||
// Trigger flush and flush job will be scheduled to LOW priority thread.
|
||||
ASSERT_OK(Put(0, "key2", DummyString(8192)));
|
||||
|
||||
// Close DB and flush job in low priority queue will be removed without
|
||||
// running.
|
||||
Close();
|
||||
sleeping_task_low.WakeUp();
|
||||
sleeping_task_low.WaitUntilDone();
|
||||
ASSERT_EQ(0, num_flushes);
|
||||
|
||||
TryReopenWithColumnFamilies({"default", "cf1", "cf2"}, options);
|
||||
ASSERT_OK(Put(0, "key3", DummyString(8192)));
|
||||
ASSERT_OK(Flush(0));
|
||||
ASSERT_EQ(1, num_flushes);
|
||||
}
|
||||
|
||||
TEST_F(DBFlushTest, ManualFlushWithMinWriteBufferNumberToMerge) {
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 100;
|
||||
@@ -513,16 +557,18 @@ TEST_F(DBFlushTest, FlushWithBlob) {
|
||||
const InternalStats* const internal_stats = cfd->internal_stats();
|
||||
assert(internal_stats);
|
||||
|
||||
const uint64_t expected_bytes =
|
||||
table_file->fd.GetFileSize() + blob_file->GetTotalBlobBytes();
|
||||
|
||||
const auto& compaction_stats = internal_stats->TEST_GetCompactionStats();
|
||||
ASSERT_FALSE(compaction_stats.empty());
|
||||
ASSERT_EQ(compaction_stats[0].bytes_written, expected_bytes);
|
||||
ASSERT_EQ(compaction_stats[0].num_output_files, 2);
|
||||
ASSERT_EQ(compaction_stats[0].bytes_written, table_file->fd.GetFileSize());
|
||||
ASSERT_EQ(compaction_stats[0].bytes_written_blob,
|
||||
blob_file->GetTotalBlobBytes());
|
||||
ASSERT_EQ(compaction_stats[0].num_output_files, 1);
|
||||
ASSERT_EQ(compaction_stats[0].num_output_files_blob, 1);
|
||||
|
||||
const uint64_t* const cf_stats_value = internal_stats->TEST_GetCFStatsValue();
|
||||
ASSERT_EQ(cf_stats_value[InternalStats::BYTES_FLUSHED], expected_bytes);
|
||||
ASSERT_EQ(cf_stats_value[InternalStats::BYTES_FLUSHED],
|
||||
compaction_stats[0].bytes_written +
|
||||
compaction_stats[0].bytes_written_blob);
|
||||
#endif // ROCKSDB_LITE
|
||||
}
|
||||
|
||||
@@ -802,16 +848,21 @@ TEST_P(DBFlushTestBlobError, FlushError) {
|
||||
|
||||
if (sync_point_ == "BlobFileBuilder::WriteBlobToFile:AddRecord") {
|
||||
ASSERT_EQ(compaction_stats[0].bytes_written, 0);
|
||||
ASSERT_EQ(compaction_stats[0].bytes_written_blob, 0);
|
||||
ASSERT_EQ(compaction_stats[0].num_output_files, 0);
|
||||
ASSERT_EQ(compaction_stats[0].num_output_files_blob, 0);
|
||||
} else {
|
||||
// SST file writing succeeded; blob file writing failed (during Finish)
|
||||
ASSERT_GT(compaction_stats[0].bytes_written, 0);
|
||||
ASSERT_EQ(compaction_stats[0].bytes_written_blob, 0);
|
||||
ASSERT_EQ(compaction_stats[0].num_output_files, 1);
|
||||
ASSERT_EQ(compaction_stats[0].num_output_files_blob, 0);
|
||||
}
|
||||
|
||||
const uint64_t* const cf_stats_value = internal_stats->TEST_GetCFStatsValue();
|
||||
ASSERT_EQ(cf_stats_value[InternalStats::BYTES_FLUSHED],
|
||||
compaction_stats[0].bytes_written);
|
||||
compaction_stats[0].bytes_written +
|
||||
compaction_stats[0].bytes_written_blob);
|
||||
#endif // ROCKSDB_LITE
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
#include "db/compacted_db_impl.h"
|
||||
#include "db/db_impl/compacted_db_impl.h"
|
||||
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/version_set.h"
|
||||
@@ -17,11 +17,13 @@ extern void MarkKeyMayExist(void* arg);
|
||||
extern bool SaveValue(void* arg, const ParsedInternalKey& parsed_key,
|
||||
const Slice& v, bool hit_and_return);
|
||||
|
||||
CompactedDBImpl::CompactedDBImpl(
|
||||
const DBOptions& options, const std::string& dbname)
|
||||
: DBImpl(options, dbname), cfd_(nullptr), version_(nullptr),
|
||||
user_comparator_(nullptr) {
|
||||
}
|
||||
CompactedDBImpl::CompactedDBImpl(const DBOptions& options,
|
||||
const std::string& dbname)
|
||||
: DBImpl(options, dbname, /*seq_per_batch*/ false, +/*batch_per_txn*/ true,
|
||||
/*read_only*/ true),
|
||||
cfd_(nullptr),
|
||||
version_(nullptr),
|
||||
user_comparator_(nullptr) {}
|
||||
|
||||
CompactedDBImpl::~CompactedDBImpl() {
|
||||
}
|
||||
@@ -78,6 +80,7 @@ std::vector<Status> CompactedDBImpl::MultiGet(const ReadOptions& options,
|
||||
nullptr, nullptr, nullptr, true, nullptr, nullptr);
|
||||
LookupKey lkey(keys[idx], kMaxSequenceNumber);
|
||||
Status s = r->Get(options, lkey.internal_key(), &get_context, nullptr);
|
||||
assert(static_cast<size_t>(idx) < statuses.size());
|
||||
if (!s.ok() && !s.IsNotFound()) {
|
||||
statuses[idx] = s;
|
||||
} else {
|
||||
@@ -18,7 +18,7 @@ class CompactedDBImpl : public DBImpl {
|
||||
CompactedDBImpl(const CompactedDBImpl&) = delete;
|
||||
void operator=(const CompactedDBImpl&) = delete;
|
||||
|
||||
virtual ~CompactedDBImpl();
|
||||
~CompactedDBImpl() override;
|
||||
|
||||
static Status Open(const Options& options, const std::string& dbname,
|
||||
DB** dbptr);
|
||||
+98
-57
@@ -146,28 +146,31 @@ void DumpSupportInfo(Logger* logger) {
|
||||
} // namespace
|
||||
|
||||
DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
const bool seq_per_batch, const bool batch_per_txn)
|
||||
const bool seq_per_batch, const bool batch_per_txn,
|
||||
bool read_only)
|
||||
: dbname_(dbname),
|
||||
own_info_log_(options.info_log == nullptr),
|
||||
initial_db_options_(SanitizeOptions(dbname, options)),
|
||||
initial_db_options_(SanitizeOptions(dbname, options, read_only)),
|
||||
env_(initial_db_options_.env),
|
||||
clock_(initial_db_options_.env->GetSystemClock()),
|
||||
io_tracer_(std::make_shared<IOTracer>()),
|
||||
immutable_db_options_(initial_db_options_),
|
||||
fs_(immutable_db_options_.fs, io_tracer_),
|
||||
mutable_db_options_(initial_db_options_),
|
||||
stats_(immutable_db_options_.statistics.get()),
|
||||
mutex_(stats_, clock_, DB_MUTEX_WAIT_MICROS,
|
||||
mutex_(stats_, immutable_db_options_.clock, DB_MUTEX_WAIT_MICROS,
|
||||
immutable_db_options_.use_adaptive_mutex),
|
||||
default_cf_handle_(nullptr),
|
||||
error_handler_(this, immutable_db_options_, &mutex_),
|
||||
event_logger_(immutable_db_options_.info_log.get()),
|
||||
max_total_in_memory_state_(0),
|
||||
file_options_(BuildDBOptions(immutable_db_options_, mutable_db_options_)),
|
||||
file_options_for_compaction_(fs_->OptimizeForCompactionTableWrite(
|
||||
file_options_, immutable_db_options_)),
|
||||
seq_per_batch_(seq_per_batch),
|
||||
batch_per_txn_(batch_per_txn),
|
||||
db_lock_(nullptr),
|
||||
next_job_id_(1),
|
||||
shutting_down_(false),
|
||||
db_lock_(nullptr),
|
||||
manual_compaction_paused_(false),
|
||||
bg_cv_(&mutex_),
|
||||
logfile_number_(0),
|
||||
@@ -192,9 +195,8 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
bg_purge_scheduled_(0),
|
||||
disable_delete_obsolete_files_(0),
|
||||
pending_purge_obsolete_files_(0),
|
||||
delete_obsolete_files_last_run_(clock_->NowMicros()),
|
||||
delete_obsolete_files_last_run_(immutable_db_options_.clock->NowMicros()),
|
||||
last_stats_dump_time_microsec_(0),
|
||||
next_job_id_(1),
|
||||
has_unpersisted_data_(false),
|
||||
unable_to_release_oldest_log_(false),
|
||||
num_running_ingest_file_(0),
|
||||
@@ -202,7 +204,6 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
wal_manager_(immutable_db_options_, file_options_, io_tracer_,
|
||||
seq_per_batch),
|
||||
#endif // ROCKSDB_LITE
|
||||
event_logger_(immutable_db_options_.info_log.get()),
|
||||
bg_work_paused_(0),
|
||||
bg_compaction_paused_(0),
|
||||
refitting_level_(false),
|
||||
@@ -231,8 +232,9 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
own_sfm_(options.sst_file_manager == nullptr),
|
||||
preserve_deletes_(options.preserve_deletes),
|
||||
closed_(false),
|
||||
error_handler_(this, immutable_db_options_, &mutex_),
|
||||
atomic_flush_install_cv_(&mutex_) {
|
||||
atomic_flush_install_cv_(&mutex_),
|
||||
blob_callback_(immutable_db_options_.sst_file_manager.get(), &mutex_,
|
||||
&error_handler_) {
|
||||
// !batch_per_trx_ implies seq_per_batch_ because it is only unset for
|
||||
// WriteUnprepared, which should use seq_per_batch_.
|
||||
assert(batch_per_txn_ || seq_per_batch_);
|
||||
@@ -268,6 +270,10 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
// we won't drop any deletion markers until SetPreserveDeletesSequenceNumber()
|
||||
// is called by client and this seqnum is advanced.
|
||||
preserve_deletes_seqnum_.store(0);
|
||||
|
||||
if (write_buffer_manager_) {
|
||||
wbm_stall_.reset(new WBMStallInterface());
|
||||
}
|
||||
}
|
||||
|
||||
Status DBImpl::Resume() {
|
||||
@@ -512,19 +518,20 @@ Status DBImpl::CloseHelper() {
|
||||
}
|
||||
mutex_.Unlock();
|
||||
|
||||
// Below check is added as recovery_error_ is not checked and it causes crash
|
||||
// in DBSSTTest.DBWithMaxSpaceAllowedWithBlobFiles when space limit is
|
||||
// reached.
|
||||
error_handler_.GetRecoveryError().PermitUncheckedError();
|
||||
|
||||
// CancelAllBackgroundWork called with false means we just set the shutdown
|
||||
// marker. After this we do a variant of the waiting and unschedule work
|
||||
// (to consider: moving all the waiting into CancelAllBackgroundWork(true))
|
||||
CancelAllBackgroundWork(false);
|
||||
int bottom_compactions_unscheduled =
|
||||
env_->UnSchedule(this, Env::Priority::BOTTOM);
|
||||
int compactions_unscheduled = env_->UnSchedule(this, Env::Priority::LOW);
|
||||
int flushes_unscheduled = env_->UnSchedule(this, Env::Priority::HIGH);
|
||||
Status ret = Status::OK();
|
||||
mutex_.Lock();
|
||||
bg_bottom_compaction_scheduled_ -= bottom_compactions_unscheduled;
|
||||
bg_compaction_scheduled_ -= compactions_unscheduled;
|
||||
bg_flush_scheduled_ -= flushes_unscheduled;
|
||||
env_->UnSchedule(this, Env::Priority::BOTTOM);
|
||||
env_->UnSchedule(this, Env::Priority::LOW);
|
||||
env_->UnSchedule(this, Env::Priority::HIGH);
|
||||
Status ret = Status::OK();
|
||||
|
||||
// Wait for background work to finish
|
||||
while (bg_bottom_compaction_scheduled_ || bg_compaction_scheduled_ ||
|
||||
@@ -657,6 +664,10 @@ Status DBImpl::CloseHelper() {
|
||||
}
|
||||
}
|
||||
|
||||
if (write_buffer_manager_ && wbm_stall_) {
|
||||
write_buffer_manager_->RemoveDBFromQueue(wbm_stall_.get());
|
||||
}
|
||||
|
||||
if (ret.IsAborted()) {
|
||||
// Reserve IsAborted() error for those where users didn't release
|
||||
// certain resource and they can release them and come back and
|
||||
@@ -686,8 +697,8 @@ void DBImpl::MaybeIgnoreError(Status* s) const {
|
||||
}
|
||||
|
||||
const Status DBImpl::CreateArchivalDirectory() {
|
||||
if (immutable_db_options_.wal_ttl_seconds > 0 ||
|
||||
immutable_db_options_.wal_size_limit_mb > 0) {
|
||||
if (immutable_db_options_.WAL_ttl_seconds > 0 ||
|
||||
immutable_db_options_.WAL_size_limit_MB > 0) {
|
||||
std::string archivalPath = ArchivalDirectory(immutable_db_options_.wal_dir);
|
||||
return env_->CreateDirIfMissing(archivalPath);
|
||||
}
|
||||
@@ -753,7 +764,8 @@ void DBImpl::PersistStats() {
|
||||
return;
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::PersistStats:StartRunning");
|
||||
uint64_t now_seconds = clock_->NowMicros() / kMicrosInSecond;
|
||||
uint64_t now_seconds =
|
||||
immutable_db_options_.clock->NowMicros() / kMicrosInSecond;
|
||||
|
||||
Statistics* statistics = immutable_db_options_.statistics.get();
|
||||
if (!statistics) {
|
||||
@@ -1654,8 +1666,8 @@ Status DBImpl::GetImpl(const ReadOptions& read_options, const Slice& key,
|
||||
}
|
||||
#endif // NDEBUG
|
||||
|
||||
PERF_CPU_TIMER_GUARD(get_cpu_nanos, clock_);
|
||||
StopWatch sw(clock_, stats_, DB_GET);
|
||||
PERF_CPU_TIMER_GUARD(get_cpu_nanos, immutable_db_options_.clock);
|
||||
StopWatch sw(immutable_db_options_.clock, stats_, DB_GET);
|
||||
PERF_TIMER_GUARD(get_snapshot_time);
|
||||
|
||||
auto cfh = static_cast_with_check<ColumnFamilyHandleImpl>(
|
||||
@@ -1845,8 +1857,8 @@ std::vector<Status> DBImpl::MultiGet(
|
||||
const std::vector<ColumnFamilyHandle*>& column_family,
|
||||
const std::vector<Slice>& keys, std::vector<std::string>* values,
|
||||
std::vector<std::string>* timestamps) {
|
||||
PERF_CPU_TIMER_GUARD(get_cpu_nanos, clock_);
|
||||
StopWatch sw(clock_, stats_, DB_MULTIGET);
|
||||
PERF_CPU_TIMER_GUARD(get_cpu_nanos, immutable_db_options_.clock);
|
||||
StopWatch sw(immutable_db_options_.clock, stats_, DB_MULTIGET);
|
||||
PERF_TIMER_GUARD(get_snapshot_time);
|
||||
|
||||
#ifndef NDEBUG
|
||||
@@ -1975,9 +1987,8 @@ std::vector<Status> DBImpl::MultiGet(
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (read_options.deadline.count() &&
|
||||
clock_->NowMicros() >
|
||||
immutable_db_options_.clock->NowMicros() >
|
||||
static_cast<uint64_t>(read_options.deadline.count())) {
|
||||
break;
|
||||
}
|
||||
@@ -1986,7 +1997,7 @@ std::vector<Status> DBImpl::MultiGet(
|
||||
if (keys_read < num_keys) {
|
||||
// The only reason to break out of the loop is when the deadline is
|
||||
// exceeded
|
||||
assert(clock_->NowMicros() >
|
||||
assert(immutable_db_options_.clock->NowMicros() >
|
||||
static_cast<uint64_t>(read_options.deadline.count()));
|
||||
for (++keys_read; keys_read < num_keys; ++keys_read) {
|
||||
stat_list[keys_read] = Status::TimedOut();
|
||||
@@ -2426,8 +2437,8 @@ Status DBImpl::MultiGetImpl(
|
||||
autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE>* sorted_keys,
|
||||
SuperVersion* super_version, SequenceNumber snapshot,
|
||||
ReadCallback* callback) {
|
||||
PERF_CPU_TIMER_GUARD(get_cpu_nanos, clock_);
|
||||
StopWatch sw(clock_, stats_, DB_MULTIGET);
|
||||
PERF_CPU_TIMER_GUARD(get_cpu_nanos, immutable_db_options_.clock);
|
||||
StopWatch sw(immutable_db_options_.clock, stats_, DB_MULTIGET);
|
||||
|
||||
// For each of the given keys, apply the entire "get" process as follows:
|
||||
// First look in the memtable, then in the immutable memtable (if any).
|
||||
@@ -2438,7 +2449,7 @@ Status DBImpl::MultiGetImpl(
|
||||
uint64_t curr_value_size = 0;
|
||||
while (keys_left) {
|
||||
if (read_options.deadline.count() &&
|
||||
clock_->NowMicros() >
|
||||
immutable_db_options_.clock->NowMicros() >
|
||||
static_cast<uint64_t>(read_options.deadline.count())) {
|
||||
s = Status::TimedOut();
|
||||
break;
|
||||
@@ -2991,7 +3002,8 @@ const Snapshot* DBImpl::GetSnapshotForWriteConflictBoundary() {
|
||||
SnapshotImpl* DBImpl::GetSnapshotImpl(bool is_write_conflict_boundary,
|
||||
bool lock) {
|
||||
int64_t unix_time = 0;
|
||||
env_->GetCurrentTime(&unix_time).PermitUncheckedError(); // Ignore error
|
||||
immutable_db_options_.clock->GetCurrentTime(&unix_time)
|
||||
.PermitUncheckedError(); // Ignore error
|
||||
SnapshotImpl* s = new SnapshotImpl;
|
||||
|
||||
if (lock) {
|
||||
@@ -3136,12 +3148,16 @@ FileSystem* DBImpl::GetFileSystem() const {
|
||||
return immutable_db_options_.fs.get();
|
||||
}
|
||||
|
||||
SystemClock* DBImpl::GetSystemClock() const {
|
||||
return immutable_db_options_.clock;
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
Status DBImpl::StartIOTrace(Env* env, const TraceOptions& trace_options,
|
||||
Status DBImpl::StartIOTrace(const TraceOptions& trace_options,
|
||||
std::unique_ptr<TraceWriter>&& trace_writer) {
|
||||
assert(trace_writer != nullptr);
|
||||
return io_tracer_->StartIOTrace(env->GetSystemClock(), trace_options,
|
||||
return io_tracer_->StartIOTrace(GetSystemClock(), trace_options,
|
||||
std::move(trace_writer));
|
||||
}
|
||||
|
||||
@@ -3939,7 +3955,8 @@ Status DestroyDB(const std::string& dbname, const Options& options,
|
||||
std::string path_to_delete = dbname + "/" + fname;
|
||||
if (type == kMetaDatabase) {
|
||||
del = DestroyDB(path_to_delete, options);
|
||||
} else if (type == kTableFile || type == kWalFile) {
|
||||
} else if (type == kTableFile || type == kWalFile ||
|
||||
type == kBlobFile) {
|
||||
del = DeleteDBFile(&soptions, path_to_delete, dbname,
|
||||
/*force_bg=*/false, /*force_fg=*/!wal_in_db_path);
|
||||
} else {
|
||||
@@ -3964,9 +3981,10 @@ Status DestroyDB(const std::string& dbname, const Options& options,
|
||||
if (env->GetChildren(path, &filenames).ok()) {
|
||||
for (const auto& fname : filenames) {
|
||||
if (ParseFileName(fname, &number, &type) &&
|
||||
type == kTableFile) { // Lock file will be deleted at end
|
||||
std::string table_path = path + "/" + fname;
|
||||
Status del = DeleteDBFile(&soptions, table_path, dbname,
|
||||
(type == kTableFile ||
|
||||
type == kBlobFile)) { // Lock file will be deleted at end
|
||||
std::string file_path = path + "/" + fname;
|
||||
Status del = DeleteDBFile(&soptions, file_path, dbname,
|
||||
/*force_bg=*/false, /*force_fg=*/false);
|
||||
if (!del.ok() && result.ok()) {
|
||||
result = del;
|
||||
@@ -4422,9 +4440,9 @@ Status DBImpl::IngestExternalFiles(
|
||||
std::vector<ExternalSstFileIngestionJob> ingestion_jobs;
|
||||
for (const auto& arg : args) {
|
||||
auto* cfd = static_cast<ColumnFamilyHandleImpl*>(arg.column_family)->cfd();
|
||||
ingestion_jobs.emplace_back(
|
||||
clock_, versions_.get(), cfd, immutable_db_options_, file_options_,
|
||||
&snapshots_, arg.options, &directories_, &event_logger_, io_tracer_);
|
||||
ingestion_jobs.emplace_back(versions_.get(), cfd, immutable_db_options_,
|
||||
file_options_, &snapshots_, arg.options,
|
||||
&directories_, &event_logger_, io_tracer_);
|
||||
}
|
||||
|
||||
// TODO(yanqin) maybe make jobs run in parallel
|
||||
@@ -4691,9 +4709,9 @@ Status DBImpl::CreateColumnFamilyWithImport(
|
||||
// Import sst files from metadata.
|
||||
auto cfh = static_cast_with_check<ColumnFamilyHandleImpl>(*handle);
|
||||
auto cfd = cfh->cfd();
|
||||
ImportColumnFamilyJob import_job(clock_, versions_.get(), cfd,
|
||||
immutable_db_options_, file_options_,
|
||||
import_options, metadata.files, io_tracer_);
|
||||
ImportColumnFamilyJob import_job(versions_.get(), cfd, immutable_db_options_,
|
||||
file_options_, import_options,
|
||||
metadata.files, io_tracer_);
|
||||
|
||||
SuperVersionContext dummy_sv_ctx(/* create_superversion */ true);
|
||||
VersionEdit dummy_edit;
|
||||
@@ -4855,17 +4873,37 @@ Status DBImpl::VerifyChecksumInternal(const ReadOptions& read_options,
|
||||
std::string fname = TableFileName(cfd->ioptions()->cf_paths,
|
||||
fd.GetNumber(), fd.GetPathId());
|
||||
if (use_file_checksum) {
|
||||
s = VerifySstFileChecksum(*fmeta, fname, read_options);
|
||||
s = VerifyFullFileChecksum(fmeta->file_checksum,
|
||||
fmeta->file_checksum_func_name, fname,
|
||||
read_options);
|
||||
} else {
|
||||
s = ROCKSDB_NAMESPACE::VerifySstFileChecksum(opts, file_options_,
|
||||
read_options, fname);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok() && use_file_checksum) {
|
||||
const auto& blob_files = vstorage->GetBlobFiles();
|
||||
for (const auto& pair : blob_files) {
|
||||
const uint64_t blob_file_number = pair.first;
|
||||
const auto& meta = pair.second;
|
||||
assert(meta);
|
||||
const std::string blob_file_name = BlobFileName(
|
||||
cfd->ioptions()->cf_paths.front().path, blob_file_number);
|
||||
s = VerifyFullFileChecksum(meta->GetChecksumValue(),
|
||||
meta->GetChecksumMethod(), blob_file_name,
|
||||
read_options);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool defer_purge =
|
||||
immutable_db_options().avoid_unnecessary_blocking_io;
|
||||
{
|
||||
@@ -4890,29 +4928,31 @@ Status DBImpl::VerifyChecksumInternal(const ReadOptions& read_options,
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::VerifySstFileChecksum(const FileMetaData& fmeta,
|
||||
const std::string& fname,
|
||||
const ReadOptions& read_options) {
|
||||
Status DBImpl::VerifyFullFileChecksum(const std::string& file_checksum_expected,
|
||||
const std::string& func_name_expected,
|
||||
const std::string& fname,
|
||||
const ReadOptions& read_options) {
|
||||
Status s;
|
||||
if (fmeta.file_checksum == kUnknownFileChecksum) {
|
||||
if (file_checksum_expected == kUnknownFileChecksum) {
|
||||
return s;
|
||||
}
|
||||
std::string file_checksum;
|
||||
std::string func_name;
|
||||
s = ROCKSDB_NAMESPACE::GenerateOneFileChecksum(
|
||||
fs_.get(), fname, immutable_db_options_.file_checksum_gen_factory.get(),
|
||||
fmeta.file_checksum_func_name, &file_checksum, &func_name,
|
||||
func_name_expected, &file_checksum, &func_name,
|
||||
read_options.readahead_size, immutable_db_options_.allow_mmap_reads,
|
||||
io_tracer_, immutable_db_options_.rate_limiter.get());
|
||||
if (s.ok()) {
|
||||
assert(fmeta.file_checksum_func_name == func_name);
|
||||
if (file_checksum != fmeta.file_checksum) {
|
||||
assert(func_name_expected == func_name);
|
||||
if (file_checksum != file_checksum_expected) {
|
||||
std::ostringstream oss;
|
||||
oss << fname << " file checksum mismatch, ";
|
||||
oss << "expecting " << Slice(fmeta.file_checksum).ToString(/*hex=*/true);
|
||||
oss << "expecting "
|
||||
<< Slice(file_checksum_expected).ToString(/*hex=*/true);
|
||||
oss << ", but actual " << Slice(file_checksum).ToString(/*hex=*/true);
|
||||
s = Status::Corruption(oss.str());
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::VerifySstFileChecksum:mismatch", &s);
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::VerifyFullFileChecksum:mismatch", &s);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
@@ -4947,7 +4987,8 @@ void DBImpl::WaitForIngestFile() {
|
||||
Status DBImpl::StartTrace(const TraceOptions& trace_options,
|
||||
std::unique_ptr<TraceWriter>&& trace_writer) {
|
||||
InstrumentedMutexLock lock(&trace_mutex_);
|
||||
tracer_.reset(new Tracer(clock_, trace_options, std::move(trace_writer)));
|
||||
tracer_.reset(new Tracer(immutable_db_options_.clock, trace_options,
|
||||
std::move(trace_writer)));
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -4966,8 +5007,8 @@ Status DBImpl::EndTrace() {
|
||||
Status DBImpl::StartBlockCacheTrace(
|
||||
const TraceOptions& trace_options,
|
||||
std::unique_ptr<TraceWriter>&& trace_writer) {
|
||||
return block_cache_tracer_.StartTrace(env_, trace_options,
|
||||
std::move(trace_writer));
|
||||
return block_cache_tracer_.StartTrace(immutable_db_options_.clock,
|
||||
trace_options, std::move(trace_writer));
|
||||
}
|
||||
|
||||
Status DBImpl::EndBlockCacheTrace() {
|
||||
|
||||
+95
-24
@@ -129,7 +129,8 @@ class Directories {
|
||||
class DBImpl : public DB {
|
||||
public:
|
||||
DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
const bool seq_per_batch = false, const bool batch_per_txn = true);
|
||||
const bool seq_per_batch = false, const bool batch_per_txn = true,
|
||||
bool read_only = false);
|
||||
// No copying allowed
|
||||
DBImpl(const DBImpl&) = delete;
|
||||
void operator=(const DBImpl&) = delete;
|
||||
@@ -447,9 +448,10 @@ class DBImpl : public DB {
|
||||
Status VerifyChecksumInternal(const ReadOptions& read_options,
|
||||
bool use_file_checksum);
|
||||
|
||||
Status VerifySstFileChecksum(const FileMetaData& fmeta,
|
||||
const std::string& fpath,
|
||||
const ReadOptions& read_options);
|
||||
Status VerifyFullFileChecksum(const std::string& file_checksum_expected,
|
||||
const std::string& func_name_expected,
|
||||
const std::string& fpath,
|
||||
const ReadOptions& read_options);
|
||||
|
||||
using DB::StartTrace;
|
||||
virtual Status StartTrace(
|
||||
@@ -468,7 +470,7 @@ class DBImpl : public DB {
|
||||
Status EndBlockCacheTrace() override;
|
||||
|
||||
using DB::StartIOTrace;
|
||||
Status StartIOTrace(Env* env, const TraceOptions& options,
|
||||
Status StartIOTrace(const TraceOptions& options,
|
||||
std::unique_ptr<TraceWriter>&& trace_writer) override;
|
||||
|
||||
using DB::EndIOTrace;
|
||||
@@ -485,6 +487,7 @@ class DBImpl : public DB {
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// ---- End of implementations of the DB interface ----
|
||||
SystemClock* GetSystemClock() const;
|
||||
|
||||
struct GetImplOptions {
|
||||
ColumnFamilyHandle* column_family = nullptr;
|
||||
@@ -1045,6 +1048,56 @@ class DBImpl : public DB {
|
||||
// flush LOG out of application buffer
|
||||
void FlushInfoLog();
|
||||
|
||||
// Interface to block and signal the DB in case of stalling writes by
|
||||
// WriteBufferManager. Each DBImpl object contains ptr to WBMStallInterface.
|
||||
// When DB needs to be blocked or signalled by WriteBufferManager,
|
||||
// state_ is changed accordingly.
|
||||
class WBMStallInterface : public StallInterface {
|
||||
public:
|
||||
enum State {
|
||||
BLOCKED = 0,
|
||||
RUNNING,
|
||||
};
|
||||
|
||||
WBMStallInterface() : state_cv_(&state_mutex_) {
|
||||
MutexLock lock(&state_mutex_);
|
||||
state_ = State::RUNNING;
|
||||
}
|
||||
|
||||
void SetState(State state) {
|
||||
MutexLock lock(&state_mutex_);
|
||||
state_ = state;
|
||||
}
|
||||
|
||||
// Change the state_ to State::BLOCKED and wait until its state is
|
||||
// changed by WriteBufferManager. When stall is cleared, Signal() is
|
||||
// called to change the state and unblock the DB.
|
||||
void Block() override {
|
||||
MutexLock lock(&state_mutex_);
|
||||
while (state_ == State::BLOCKED) {
|
||||
TEST_SYNC_POINT("WBMStallInterface::BlockDB");
|
||||
state_cv_.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
// Called from WriteBufferManager. This function changes the state_
|
||||
// to State::RUNNING indicating the stall is cleared and DB can proceed.
|
||||
void Signal() override {
|
||||
MutexLock lock(&state_mutex_);
|
||||
state_ = State::RUNNING;
|
||||
state_cv_.Signal();
|
||||
}
|
||||
|
||||
private:
|
||||
// Conditional variable and mutex to block and
|
||||
// signal the DB during stalling process.
|
||||
port::Mutex state_mutex_;
|
||||
port::CondVar state_cv_;
|
||||
// state represting whether DB is running or blocked because of stall by
|
||||
// WriteBufferManager.
|
||||
State state_;
|
||||
};
|
||||
|
||||
protected:
|
||||
const std::string dbname_;
|
||||
std::string db_id_;
|
||||
@@ -1056,7 +1109,6 @@ class DBImpl : public DB {
|
||||
bool own_info_log_;
|
||||
const DBOptions initial_db_options_;
|
||||
Env* const env_;
|
||||
std::shared_ptr<SystemClock> clock_;
|
||||
std::shared_ptr<IOTracer> io_tracer_;
|
||||
const ImmutableDBOptions immutable_db_options_;
|
||||
FileSystemPtr fs_;
|
||||
@@ -1078,6 +1130,14 @@ class DBImpl : public DB {
|
||||
ColumnFamilyHandleImpl* default_cf_handle_;
|
||||
InternalStats* default_cf_internal_stats_;
|
||||
|
||||
// table_cache_ provides its own synchronization
|
||||
std::shared_ptr<Cache> table_cache_;
|
||||
|
||||
ErrorHandler error_handler_;
|
||||
|
||||
// Unified interface for logging events
|
||||
EventLogger event_logger_;
|
||||
|
||||
// only used for dynamically adjusting max_total_wal_size. it is a sum of
|
||||
// [write_buffer_size * max_write_buffer_number] over all column families
|
||||
uint64_t max_total_in_memory_state_;
|
||||
@@ -1108,6 +1168,12 @@ class DBImpl : public DB {
|
||||
// Default: true
|
||||
const bool batch_per_txn_;
|
||||
|
||||
// Each flush or compaction gets its own job id. this counter makes sure
|
||||
// they're unique
|
||||
std::atomic<int> next_job_id_;
|
||||
|
||||
std::atomic<bool> shutting_down_;
|
||||
|
||||
// Except in DB::Open(), WriteOptionsFile can only be called when:
|
||||
// Persist options to options file.
|
||||
// If need_mutex_lock = false, the method will lock DB mutex.
|
||||
@@ -1235,7 +1301,7 @@ class DBImpl : public DB {
|
||||
virtual bool OwnTablesAndLogs() const { return true; }
|
||||
|
||||
// Set DB identity file, and write DB ID to manifest if necessary.
|
||||
Status SetDBId();
|
||||
Status SetDBId(bool read_only);
|
||||
|
||||
// REQUIRES: db mutex held when calling this function, but the db mutex can
|
||||
// be released and re-acquired. Db mutex will be held when the function
|
||||
@@ -1307,6 +1373,7 @@ class DBImpl : public DB {
|
||||
|
||||
struct LogFileNumberSize {
|
||||
explicit LogFileNumberSize(uint64_t _number) : number(_number) {}
|
||||
LogFileNumberSize() {}
|
||||
void AddSize(uint64_t new_size) { size += new_size; }
|
||||
uint64_t number;
|
||||
uint64_t size = 0;
|
||||
@@ -1412,6 +1479,7 @@ class DBImpl : public DB {
|
||||
DBImpl* db;
|
||||
// background compaction takes ownership of `prepicked_compaction`.
|
||||
PrepickedCompaction* prepicked_compaction;
|
||||
Env::Priority compaction_pri_;
|
||||
};
|
||||
|
||||
// Initialize the built-in column family for persistent stats. Depending on
|
||||
@@ -1506,6 +1574,12 @@ class DBImpl : public DB {
|
||||
Status WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
MemTable* mem, VersionEdit* edit);
|
||||
|
||||
// Get the size of a log file and, if truncate is true, truncate the
|
||||
// log file to its actual size, thereby freeing preallocated space.
|
||||
// Return success even if truncate fails
|
||||
Status GetLogSizeAndMaybeTruncate(uint64_t wal_number, bool truncate,
|
||||
LogFileNumberSize* log);
|
||||
|
||||
// Restore alive_log_files_ and total_log_size_ after recovery.
|
||||
// It needs to run only when there's no flush during recovery
|
||||
// (e.g. avoid_flush_during_recovery=true). May also trigger flush
|
||||
@@ -1516,6 +1590,10 @@ class DBImpl : public DB {
|
||||
// `num_bytes` going through.
|
||||
Status DelayWrite(uint64_t num_bytes, const WriteOptions& write_options);
|
||||
|
||||
// Begin stalling of writes when memory usage increases beyond a certain
|
||||
// threshold.
|
||||
void WriteBufferManagerStallWrites();
|
||||
|
||||
Status ThrottleLowPriWritesIfNeeded(const WriteOptions& write_options,
|
||||
WriteBatch* my_batch);
|
||||
|
||||
@@ -1595,7 +1673,7 @@ class DBImpl : public DB {
|
||||
Status SwitchWAL(WriteContext* write_context);
|
||||
|
||||
// REQUIRES: mutex locked and in write thread.
|
||||
Status HandleWriteBufferFull(WriteContext* write_context);
|
||||
Status HandleWriteBufferManagerFlush(WriteContext* write_context);
|
||||
|
||||
// REQUIRES: mutex locked
|
||||
Status PreprocessWrite(const WriteOptions& write_options, bool* need_log_sync,
|
||||
@@ -1876,9 +1954,6 @@ class DBImpl : public DB {
|
||||
|
||||
Status IncreaseFullHistoryTsLow(ColumnFamilyData* cfd, std::string ts_low);
|
||||
|
||||
// table_cache_ provides its own synchronization
|
||||
std::shared_ptr<Cache> table_cache_;
|
||||
|
||||
// Lock over the persistent DB state. Non-nullptr iff successfully acquired.
|
||||
FileLock* db_lock_;
|
||||
|
||||
@@ -1892,8 +1967,6 @@ class DBImpl : public DB {
|
||||
// mutex_, the order should be first mutex_ and then log_write_mutex_.
|
||||
InstrumentedMutex log_write_mutex_;
|
||||
|
||||
std::atomic<bool> shutting_down_;
|
||||
|
||||
// If zero, manual compactions are allowed to proceed. If non-zero, manual
|
||||
// compactions may still be running, but will quickly fail with
|
||||
// `Status::Incomplete`. The value indicates how many threads have paused
|
||||
@@ -2102,10 +2175,6 @@ class DBImpl : public DB {
|
||||
// Number of threads intending to write to memtable
|
||||
std::atomic<size_t> pending_memtable_writes_ = {};
|
||||
|
||||
// Each flush or compaction gets its own job id. this counter makes sure
|
||||
// they're unique
|
||||
std::atomic<int> next_job_id_;
|
||||
|
||||
// A flag indicating whether the current rocksdb database has any
|
||||
// data that is not yet persisted into either WAL or SST file.
|
||||
// Used when disableWAL is true.
|
||||
@@ -2134,9 +2203,6 @@ class DBImpl : public DB {
|
||||
WalManager wal_manager_;
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// Unified interface for logging events
|
||||
EventLogger event_logger_;
|
||||
|
||||
// A value of > 0 temporarily disables scheduling of background work
|
||||
int bg_work_paused_;
|
||||
|
||||
@@ -2204,8 +2270,6 @@ class DBImpl : public DB {
|
||||
// Flag to check whether Close() has been called on this DB
|
||||
bool closed_;
|
||||
|
||||
ErrorHandler error_handler_;
|
||||
|
||||
// Conditional variable to coordinate installation of atomic flush results.
|
||||
// With atomic flush, each bg thread installs the result of flushing multiple
|
||||
// column families, and different threads can flush different column
|
||||
@@ -2218,11 +2282,18 @@ class DBImpl : public DB {
|
||||
InstrumentedCondVar atomic_flush_install_cv_;
|
||||
|
||||
bool wal_in_db_path_;
|
||||
|
||||
BlobFileCompletionCallback blob_callback_;
|
||||
|
||||
// Pointer to WriteBufferManager stalling interface.
|
||||
std::unique_ptr<StallInterface> wbm_stall_;
|
||||
};
|
||||
|
||||
extern Options SanitizeOptions(const std::string& db, const Options& src);
|
||||
extern Options SanitizeOptions(const std::string& db, const Options& src,
|
||||
bool read_only = false);
|
||||
|
||||
extern DBOptions SanitizeOptions(const std::string& db, const DBOptions& src);
|
||||
extern DBOptions SanitizeOptions(const std::string& db, const DBOptions& src,
|
||||
bool read_only = false);
|
||||
|
||||
extern CompressionType GetCompressionFlush(
|
||||
const ImmutableCFOptions& ioptions,
|
||||
|
||||
@@ -131,16 +131,11 @@ IOStatus DBImpl::SyncClosedLogs(JobContext* job_context) {
|
||||
MarkLogsNotSynced(current_log_number - 1);
|
||||
}
|
||||
if (!io_s.ok()) {
|
||||
if (total_log_size_ > 0) {
|
||||
error_handler_.SetBGError(io_s, BackgroundErrorReason::kFlush);
|
||||
} else {
|
||||
// If the WAL is empty, we use different error reason
|
||||
error_handler_.SetBGError(io_s, BackgroundErrorReason::kFlushNoWAL);
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::SyncClosedLogs:Failed");
|
||||
return io_s;
|
||||
}
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::SyncClosedLogs:end");
|
||||
return io_s;
|
||||
}
|
||||
|
||||
@@ -166,20 +161,18 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
GetCompressionFlush(*cfd->ioptions(), mutable_cf_options), stats_,
|
||||
&event_logger_, mutable_cf_options.report_bg_io_stats,
|
||||
true /* sync_output_directory */, true /* write_manifest */, thread_pri,
|
||||
io_tracer_, db_id_, db_session_id_, cfd->GetFullHistoryTsLow());
|
||||
io_tracer_, db_id_, db_session_id_, cfd->GetFullHistoryTsLow(),
|
||||
&blob_callback_);
|
||||
FileMetaData file_meta;
|
||||
|
||||
TEST_SYNC_POINT("DBImpl::FlushMemTableToOutputFile:BeforePickMemtables");
|
||||
flush_job.PickMemTable();
|
||||
TEST_SYNC_POINT("DBImpl::FlushMemTableToOutputFile:AfterPickMemtables");
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// may temporarily unlock and lock the mutex.
|
||||
NotifyOnFlushBegin(cfd, &file_meta, mutable_cf_options, job_context->job_id);
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
Status s;
|
||||
IOStatus io_s = IOStatus::OK();
|
||||
bool need_cancel = false;
|
||||
IOStatus log_io_s = IOStatus::OK();
|
||||
if (logfile_number_ > 0 &&
|
||||
versions_->GetColumnFamilySet()->NumberOfColumnFamilies() > 1) {
|
||||
// If there are more than one column families, we need to make sure that
|
||||
@@ -188,15 +181,24 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
// flushed SST may contain data from write batches whose updates to
|
||||
// other column families are missing.
|
||||
// SyncClosedLogs() may unlock and re-lock the db_mutex.
|
||||
io_s = SyncClosedLogs(job_context);
|
||||
s = io_s;
|
||||
if (!io_s.ok() && !io_s.IsShutdownInProgress() &&
|
||||
!io_s.IsColumnFamilyDropped()) {
|
||||
error_handler_.SetBGError(io_s, BackgroundErrorReason::kFlush);
|
||||
log_io_s = SyncClosedLogs(job_context);
|
||||
if (!log_io_s.ok() && !log_io_s.IsShutdownInProgress() &&
|
||||
!log_io_s.IsColumnFamilyDropped()) {
|
||||
error_handler_.SetBGError(log_io_s, BackgroundErrorReason::kFlush);
|
||||
}
|
||||
} else {
|
||||
TEST_SYNC_POINT("DBImpl::SyncClosedLogs:Skip");
|
||||
}
|
||||
s = log_io_s;
|
||||
|
||||
// If the log sync failed, we do not need to pick memtable. Otherwise,
|
||||
// num_flush_not_started_ needs to be rollback.
|
||||
TEST_SYNC_POINT("DBImpl::FlushMemTableToOutputFile:BeforePickMemtables");
|
||||
if (s.ok()) {
|
||||
flush_job.PickMemTable();
|
||||
need_cancel = true;
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::FlushMemTableToOutputFile:AfterPickMemtables");
|
||||
|
||||
// Within flush_job.Run, rocksdb may call event listener to notify
|
||||
// file creation and deletion.
|
||||
@@ -206,11 +208,16 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
// is unlocked by the current thread.
|
||||
if (s.ok()) {
|
||||
s = flush_job.Run(&logs_with_prep_tracker_, &file_meta);
|
||||
} else {
|
||||
need_cancel = false;
|
||||
}
|
||||
|
||||
if (!s.ok() && need_cancel) {
|
||||
flush_job.Cancel();
|
||||
}
|
||||
if (io_s.ok()) {
|
||||
io_s = flush_job.io_status();
|
||||
IOStatus io_s = IOStatus::OK();
|
||||
io_s = flush_job.io_status();
|
||||
if (s.ok()) {
|
||||
s = io_s;
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
@@ -246,29 +253,30 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
if (!s.ok() && !s.IsShutdownInProgress() && !s.IsColumnFamilyDropped()) {
|
||||
if (!io_s.ok() && !io_s.IsShutdownInProgress() &&
|
||||
!io_s.IsColumnFamilyDropped()) {
|
||||
assert(log_io_s.ok());
|
||||
// Error while writing to MANIFEST.
|
||||
// In fact, versions_->io_status() can also be the result of renaming
|
||||
// CURRENT file. With current code, it's just difficult to tell. So just
|
||||
// be pessimistic and try write to a new MANIFEST.
|
||||
// TODO: distinguish between MANIFEST write and CURRENT renaming
|
||||
if (!versions_->io_status().ok()) {
|
||||
if (total_log_size_ > 0) {
|
||||
// If the WAL is empty, we use different error reason
|
||||
error_handler_.SetBGError(io_s,
|
||||
BackgroundErrorReason::kManifestWrite);
|
||||
} else {
|
||||
error_handler_.SetBGError(io_s,
|
||||
BackgroundErrorReason::kManifestWriteNoWAL);
|
||||
}
|
||||
} else if (total_log_size_ > 0) {
|
||||
error_handler_.SetBGError(io_s, BackgroundErrorReason::kFlush);
|
||||
// If WAL sync is successful (either WAL size is 0 or there is no IO
|
||||
// error), all the Manifest write will be map to soft error.
|
||||
// TODO: kManifestWriteNoWAL and kFlushNoWAL are misleading. Refactor is
|
||||
// needed.
|
||||
error_handler_.SetBGError(io_s,
|
||||
BackgroundErrorReason::kManifestWriteNoWAL);
|
||||
} else {
|
||||
// If the WAL is empty, we use different error reason
|
||||
// If WAL sync is successful (either WAL size is 0 or there is no IO
|
||||
// error), all the other SST file write errors will be set as
|
||||
// kFlushNoWAL.
|
||||
error_handler_.SetBGError(io_s, BackgroundErrorReason::kFlushNoWAL);
|
||||
}
|
||||
} else {
|
||||
Status new_bg_error = s;
|
||||
error_handler_.SetBGError(new_bg_error, BackgroundErrorReason::kFlush);
|
||||
if (log_io_s.ok()) {
|
||||
Status new_bg_error = s;
|
||||
error_handler_.SetBGError(new_bg_error, BackgroundErrorReason::kFlush);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If we got here, then we decided not to care about the i_os status (either
|
||||
@@ -401,12 +409,11 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
false /* sync_output_directory */, false /* write_manifest */,
|
||||
thread_pri, io_tracer_, db_id_, db_session_id_,
|
||||
cfd->GetFullHistoryTsLow()));
|
||||
jobs.back()->PickMemTable();
|
||||
}
|
||||
|
||||
std::vector<FileMetaData> file_meta(num_cfs);
|
||||
Status s;
|
||||
IOStatus io_s;
|
||||
IOStatus log_io_s = IOStatus::OK();
|
||||
assert(num_cfs == static_cast<int>(jobs.size()));
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
@@ -421,18 +428,36 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
if (logfile_number_ > 0) {
|
||||
// TODO (yanqin) investigate whether we should sync the closed logs for
|
||||
// single column family case.
|
||||
io_s = SyncClosedLogs(job_context);
|
||||
s = io_s;
|
||||
log_io_s = SyncClosedLogs(job_context);
|
||||
if (!log_io_s.ok() && !log_io_s.IsShutdownInProgress() &&
|
||||
!log_io_s.IsColumnFamilyDropped()) {
|
||||
if (total_log_size_ > 0) {
|
||||
error_handler_.SetBGError(log_io_s, BackgroundErrorReason::kFlush);
|
||||
} else {
|
||||
// If the WAL is empty, we use different error reason
|
||||
error_handler_.SetBGError(log_io_s, BackgroundErrorReason::kFlushNoWAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
s = log_io_s;
|
||||
|
||||
// exec_status stores the execution status of flush_jobs as
|
||||
// <bool /* executed */, Status /* status code */>
|
||||
autovector<std::pair<bool, Status>> exec_status;
|
||||
autovector<IOStatus> io_status;
|
||||
std::vector<bool> pick_status;
|
||||
for (int i = 0; i != num_cfs; ++i) {
|
||||
// Initially all jobs are not executed, with status OK.
|
||||
exec_status.emplace_back(false, Status::OK());
|
||||
io_status.emplace_back(IOStatus::OK());
|
||||
pick_status.push_back(false);
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
for (int i = 0; i != num_cfs; ++i) {
|
||||
jobs[i]->PickMemTable();
|
||||
pick_status[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
@@ -473,6 +498,7 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
s = error_status.ok() ? s : error_status;
|
||||
}
|
||||
|
||||
IOStatus io_s = IOStatus::OK();
|
||||
if (io_s.ok()) {
|
||||
IOStatus io_error = IOStatus::OK();
|
||||
for (int i = 0; i != static_cast<int>(io_status.size()); i++) {
|
||||
@@ -508,12 +534,12 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
// Have to cancel the flush jobs that have NOT executed because we need to
|
||||
// unref the versions.
|
||||
for (int i = 0; i != num_cfs; ++i) {
|
||||
if (!exec_status[i].first) {
|
||||
if (pick_status[i] && !exec_status[i].first) {
|
||||
jobs[i]->Cancel();
|
||||
}
|
||||
}
|
||||
for (int i = 0; i != num_cfs; ++i) {
|
||||
if (exec_status[i].first && exec_status[i].second.ok()) {
|
||||
if (exec_status[i].second.ok() && exec_status[i].first) {
|
||||
auto& mems = jobs[i]->GetMemTables();
|
||||
cfds[i]->imm()->RollbackMemtableFlush(mems,
|
||||
file_meta[i].fd.GetNumber());
|
||||
@@ -652,29 +678,30 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
// it is not because of CF drop.
|
||||
if (!s.ok() && !s.IsColumnFamilyDropped()) {
|
||||
if (!io_s.ok() && !io_s.IsColumnFamilyDropped()) {
|
||||
assert(log_io_s.ok());
|
||||
// Error while writing to MANIFEST.
|
||||
// In fact, versions_->io_status() can also be the result of renaming
|
||||
// CURRENT file. With current code, it's just difficult to tell. So just
|
||||
// be pessimistic and try write to a new MANIFEST.
|
||||
// TODO: distinguish between MANIFEST write and CURRENT renaming
|
||||
if (!versions_->io_status().ok()) {
|
||||
if (total_log_size_ > 0) {
|
||||
// If the WAL is empty, we use different error reason
|
||||
error_handler_.SetBGError(io_s,
|
||||
BackgroundErrorReason::kManifestWrite);
|
||||
} else {
|
||||
error_handler_.SetBGError(io_s,
|
||||
BackgroundErrorReason::kManifestWriteNoWAL);
|
||||
}
|
||||
} else if (total_log_size_ > 0) {
|
||||
error_handler_.SetBGError(io_s, BackgroundErrorReason::kFlush);
|
||||
// If WAL sync is successful (either WAL size is 0 or there is no IO
|
||||
// error), all the Manifest write will be map to soft error.
|
||||
// TODO: kManifestWriteNoWAL and kFlushNoWAL are misleading. Refactor
|
||||
// is needed.
|
||||
error_handler_.SetBGError(io_s,
|
||||
BackgroundErrorReason::kManifestWriteNoWAL);
|
||||
} else {
|
||||
// If the WAL is empty, we use different error reason
|
||||
// If WAL sync is successful (either WAL size is 0 or there is no IO
|
||||
// error), all the other SST file write errors will be set as
|
||||
// kFlushNoWAL.
|
||||
error_handler_.SetBGError(io_s, BackgroundErrorReason::kFlushNoWAL);
|
||||
}
|
||||
} else {
|
||||
Status new_bg_error = s;
|
||||
error_handler_.SetBGError(new_bg_error, BackgroundErrorReason::kFlush);
|
||||
if (log_io_s.ok()) {
|
||||
Status new_bg_error = s;
|
||||
error_handler_.SetBGError(new_bg_error, BackgroundErrorReason::kFlush);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -776,6 +803,10 @@ Status DBImpl::CompactRange(const CompactRangeOptions& options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice* begin_without_ts,
|
||||
const Slice* end_without_ts) {
|
||||
if (manual_compaction_paused_.load(std::memory_order_acquire) > 0) {
|
||||
return Status::Incomplete(Status::SubCode::kManualCompactionPaused);
|
||||
}
|
||||
|
||||
const Comparator* const ucmp = column_family->GetComparator();
|
||||
assert(ucmp);
|
||||
size_t ts_sz = ucmp->timestamp_size();
|
||||
@@ -1714,6 +1745,7 @@ Status DBImpl::RunManualCompaction(
|
||||
}
|
||||
ca = new CompactionArg;
|
||||
ca->db = this;
|
||||
ca->compaction_pri_ = Env::Priority::LOW;
|
||||
ca->prepicked_compaction = new PrepickedCompaction;
|
||||
ca->prepicked_compaction->manual_compaction_state = &manual;
|
||||
ca->prepicked_compaction->compaction = compaction;
|
||||
@@ -2061,12 +2093,12 @@ Status DBImpl::WaitUntilFlushWouldNotStallWrites(ColumnFamilyData* cfd,
|
||||
// check whether one extra immutable memtable or an extra L0 file would
|
||||
// cause write stalling mode to be entered. It could still enter stall
|
||||
// mode due to pending compaction bytes, but that's less common
|
||||
write_stall_condition =
|
||||
ColumnFamilyData::GetWriteStallConditionAndCause(
|
||||
cfd->imm()->NumNotFlushed() + 1,
|
||||
vstorage->l0_delay_trigger_count() + 1,
|
||||
vstorage->estimated_compaction_needed_bytes(), mutable_cf_options)
|
||||
.first;
|
||||
write_stall_condition = ColumnFamilyData::GetWriteStallConditionAndCause(
|
||||
cfd->imm()->NumNotFlushed() + 1,
|
||||
vstorage->l0_delay_trigger_count() + 1,
|
||||
vstorage->estimated_compaction_needed_bytes(),
|
||||
mutable_cf_options, *cfd->ioptions())
|
||||
.first;
|
||||
} while (write_stall_condition != WriteStallCondition::kNormal);
|
||||
}
|
||||
return Status::OK();
|
||||
@@ -2245,6 +2277,7 @@ void DBImpl::MaybeScheduleFlushOrCompaction() {
|
||||
unscheduled_compactions_ > 0) {
|
||||
CompactionArg* ca = new CompactionArg;
|
||||
ca->db = this;
|
||||
ca->compaction_pri_ = Env::Priority::LOW;
|
||||
ca->prepicked_compaction = nullptr;
|
||||
bg_compaction_scheduled_++;
|
||||
unscheduled_compactions_--;
|
||||
@@ -2432,7 +2465,16 @@ void DBImpl::BGWorkPurge(void* db) {
|
||||
}
|
||||
|
||||
void DBImpl::UnscheduleCompactionCallback(void* arg) {
|
||||
CompactionArg ca = *(reinterpret_cast<CompactionArg*>(arg));
|
||||
CompactionArg* ca_ptr = reinterpret_cast<CompactionArg*>(arg);
|
||||
Env::Priority compaction_pri = ca_ptr->compaction_pri_;
|
||||
if (Env::Priority::BOTTOM == compaction_pri) {
|
||||
// Decrement bg_bottom_compaction_scheduled_ if priority is BOTTOM
|
||||
ca_ptr->db->bg_bottom_compaction_scheduled_--;
|
||||
} else if (Env::Priority::LOW == compaction_pri) {
|
||||
// Decrement bg_compaction_scheduled_ if priority is LOW
|
||||
ca_ptr->db->bg_compaction_scheduled_--;
|
||||
}
|
||||
CompactionArg ca = *(ca_ptr);
|
||||
delete reinterpret_cast<CompactionArg*>(arg);
|
||||
if (ca.prepicked_compaction != nullptr) {
|
||||
if (ca.prepicked_compaction->compaction != nullptr) {
|
||||
@@ -2444,6 +2486,14 @@ void DBImpl::UnscheduleCompactionCallback(void* arg) {
|
||||
}
|
||||
|
||||
void DBImpl::UnscheduleFlushCallback(void* arg) {
|
||||
// Decrement bg_flush_scheduled_ in flush callback
|
||||
reinterpret_cast<FlushThreadArg*>(arg)->db_->bg_flush_scheduled_--;
|
||||
Env::Priority flush_pri = reinterpret_cast<FlushThreadArg*>(arg)->thread_pri_;
|
||||
if (Env::Priority::LOW == flush_pri) {
|
||||
TEST_SYNC_POINT("DBImpl::UnscheduleLowFlushCallback");
|
||||
} else if (Env::Priority::HIGH == flush_pri) {
|
||||
TEST_SYNC_POINT("DBImpl::UnscheduleHighFlushCallback");
|
||||
}
|
||||
delete reinterpret_cast<FlushThreadArg*>(arg);
|
||||
TEST_SYNC_POINT("DBImpl::UnscheduleFlushCallback");
|
||||
}
|
||||
@@ -2536,6 +2586,8 @@ void DBImpl::BackgroundCallFlush(Env::Priority thread_pri) {
|
||||
|
||||
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL,
|
||||
immutable_db_options_.info_log.get());
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCallFlush:Start:1");
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCallFlush:Start:2");
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
assert(bg_flush_scheduled_);
|
||||
@@ -2564,7 +2616,7 @@ void DBImpl::BackgroundCallFlush(Env::Priority thread_pri) {
|
||||
s.ToString().c_str(), error_cnt);
|
||||
log_buffer.FlushBufferToLog();
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
clock_->SleepForMicroseconds(1000000);
|
||||
immutable_db_options_.clock->SleepForMicroseconds(1000000);
|
||||
mutex_.Lock();
|
||||
}
|
||||
|
||||
@@ -2637,7 +2689,8 @@ void DBImpl::BackgroundCallCompaction(PrepickedCompaction* prepicked_compaction,
|
||||
if (s.IsBusy()) {
|
||||
bg_cv_.SignalAll(); // In case a waiter can proceed despite the error
|
||||
mutex_.Unlock();
|
||||
clock_->SleepForMicroseconds(10000); // prevent hot loop
|
||||
immutable_db_options_.clock->SleepForMicroseconds(
|
||||
10000); // prevent hot loop
|
||||
mutex_.Lock();
|
||||
} else if (!s.ok() && !s.IsShutdownInProgress() &&
|
||||
!s.IsManualCompactionPaused() && !s.IsColumnFamilyDropped()) {
|
||||
@@ -2655,7 +2708,7 @@ void DBImpl::BackgroundCallCompaction(PrepickedCompaction* prepicked_compaction,
|
||||
"Accumulated background error counts: %" PRIu64,
|
||||
s.ToString().c_str(), error_cnt);
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
clock_->SleepForMicroseconds(1000000);
|
||||
immutable_db_options_.clock->SleepForMicroseconds(1000000);
|
||||
mutex_.Lock();
|
||||
} else if (s.IsManualCompactionPaused()) {
|
||||
ManualCompactionState* m = prepicked_compaction->manual_compaction_state;
|
||||
@@ -3045,6 +3098,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCompaction:ForwardToBottomPriPool");
|
||||
CompactionArg* ca = new CompactionArg;
|
||||
ca->db = this;
|
||||
ca->compaction_pri_ = Env::Priority::BOTTOM;
|
||||
ca->prepicked_compaction = new PrepickedCompaction;
|
||||
ca->prepicked_compaction->compaction = c.release();
|
||||
ca->prepicked_compaction->manual_compaction_state = nullptr;
|
||||
|
||||
@@ -120,7 +120,7 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
mutable_db_options_.delete_obsolete_files_period_micros == 0) {
|
||||
doing_the_full_scan = true;
|
||||
} else {
|
||||
const uint64_t now_micros = clock_->NowMicros();
|
||||
const uint64_t now_micros = immutable_db_options_.clock->NowMicros();
|
||||
if ((delete_obsolete_files_last_run_ +
|
||||
mutable_db_options_.delete_obsolete_files_period_micros) <
|
||||
now_micros) {
|
||||
@@ -558,8 +558,8 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (type == kWalFile && (immutable_db_options_.wal_ttl_seconds > 0 ||
|
||||
immutable_db_options_.wal_size_limit_mb > 0)) {
|
||||
if (type == kWalFile && (immutable_db_options_.WAL_ttl_seconds > 0 ||
|
||||
immutable_db_options_.WAL_size_limit_MB > 0)) {
|
||||
wal_manager_.ArchiveWALFile(fname, number);
|
||||
continue;
|
||||
}
|
||||
@@ -854,7 +854,7 @@ uint64_t PrecomputeMinLogNumberToKeep2PC(
|
||||
return min_log_number_to_keep;
|
||||
}
|
||||
|
||||
Status DBImpl::SetDBId() {
|
||||
Status DBImpl::SetDBId(bool read_only) {
|
||||
Status s;
|
||||
// Happens when immutable_db_options_.write_dbid_to_manifest is set to true
|
||||
// the very first time.
|
||||
@@ -865,9 +865,15 @@ Status DBImpl::SetDBId() {
|
||||
// it is no longer available then at this point DB ID is not in Identity
|
||||
// file or Manifest.
|
||||
if (s.IsNotFound()) {
|
||||
s = SetIdentityFile(env_, dbname_);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
// Create a new DB ID, saving to file only if allowed
|
||||
if (read_only) {
|
||||
db_id_ = env_->GenerateUniqueId();
|
||||
return Status::OK();
|
||||
} else {
|
||||
s = SetIdentityFile(env_, dbname_);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
} else if (!s.ok()) {
|
||||
assert(s.IsIOError());
|
||||
@@ -884,7 +890,7 @@ Status DBImpl::SetDBId() {
|
||||
mutable_cf_options, &edit, &mutex_, nullptr,
|
||||
/* new_descriptor_log */ false);
|
||||
}
|
||||
} else {
|
||||
} else if (!read_only) {
|
||||
s = SetIdentityFile(env_, dbname_, db_id_);
|
||||
}
|
||||
return s;
|
||||
@@ -937,7 +943,7 @@ Status DBImpl::DeleteUnreferencedSstFiles() {
|
||||
return s;
|
||||
}
|
||||
|
||||
if (largest_file_number > next_file_number) {
|
||||
if (largest_file_number >= next_file_number) {
|
||||
versions_->next_file_number_.store(largest_file_number + 1);
|
||||
}
|
||||
|
||||
|
||||
+104
-59
@@ -24,15 +24,17 @@
|
||||
#include "util/rate_limiter.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
Options SanitizeOptions(const std::string& dbname, const Options& src) {
|
||||
auto db_options = SanitizeOptions(dbname, DBOptions(src));
|
||||
Options SanitizeOptions(const std::string& dbname, const Options& src,
|
||||
bool read_only) {
|
||||
auto db_options = SanitizeOptions(dbname, DBOptions(src), read_only);
|
||||
ImmutableDBOptions immutable_db_options(db_options);
|
||||
auto cf_options =
|
||||
SanitizeOptions(immutable_db_options, ColumnFamilyOptions(src));
|
||||
return Options(db_options, cf_options);
|
||||
}
|
||||
|
||||
DBOptions SanitizeOptions(const std::string& dbname, const DBOptions& src) {
|
||||
DBOptions SanitizeOptions(const std::string& dbname, const DBOptions& src,
|
||||
bool read_only) {
|
||||
DBOptions result(src);
|
||||
|
||||
if (result.env == nullptr) {
|
||||
@@ -50,7 +52,7 @@ DBOptions SanitizeOptions(const std::string& dbname, const DBOptions& src) {
|
||||
&result.max_open_files);
|
||||
}
|
||||
|
||||
if (result.info_log == nullptr) {
|
||||
if (result.info_log == nullptr && !read_only) {
|
||||
Status s = CreateLoggerFromOptions(dbname, result, &result.info_log);
|
||||
if (!s.ok()) {
|
||||
// No place suitable for logging
|
||||
@@ -175,7 +177,7 @@ DBOptions SanitizeOptions(const std::string& dbname, const DBOptions& src) {
|
||||
NewSstFileManager(result.env, result.info_log));
|
||||
result.sst_file_manager = sst_file_manager;
|
||||
}
|
||||
#endif
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
if (!result.paranoid_checks) {
|
||||
result.skip_checking_sst_file_sizes_on_db_open = true;
|
||||
@@ -283,6 +285,9 @@ Status DBImpl::NewDB(std::vector<std::string>* new_filenames) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "Creating manifest 1 \n");
|
||||
const std::string manifest = DescriptorFileName(dbname_, 1);
|
||||
{
|
||||
if (fs_->FileExists(manifest, IOOptions(), nullptr).ok()) {
|
||||
fs_->DeleteFile(manifest, IOOptions(), nullptr).PermitUncheckedError();
|
||||
}
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
FileOptions file_options = fs_->OptimizeForManifestWrite(file_options_);
|
||||
s = NewWritableFile(fs_.get(), manifest, &file, file_options);
|
||||
@@ -293,15 +298,15 @@ Status DBImpl::NewDB(std::vector<std::string>* new_filenames) {
|
||||
file->SetPreallocationBlockSize(
|
||||
immutable_db_options_.manifest_preallocation_size);
|
||||
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
|
||||
std::move(file), manifest, file_options, clock_, io_tracer_,
|
||||
nullptr /* stats */, immutable_db_options_.listeners, nullptr,
|
||||
tmp_set.Contains(FileType::kDescriptorFile)));
|
||||
std::move(file), manifest, file_options, immutable_db_options_.clock,
|
||||
io_tracer_, nullptr /* stats */, immutable_db_options_.listeners,
|
||||
nullptr, tmp_set.Contains(FileType::kDescriptorFile)));
|
||||
log::Writer log(std::move(file_writer), 0, false);
|
||||
std::string record;
|
||||
new_db.EncodeTo(&record);
|
||||
s = log.AddRecord(record);
|
||||
if (s.ok()) {
|
||||
s = SyncManifest(clock_, &immutable_db_options_, log.file());
|
||||
s = SyncManifest(&immutable_db_options_, log.file());
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
@@ -312,7 +317,7 @@ Status DBImpl::NewDB(std::vector<std::string>* new_filenames) {
|
||||
manifest.substr(manifest.find_last_of("/\\") + 1));
|
||||
}
|
||||
} else {
|
||||
fs_->DeleteFile(manifest, IOOptions(), nullptr);
|
||||
fs_->DeleteFile(manifest, IOOptions(), nullptr).PermitUncheckedError();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -488,7 +493,7 @@ Status DBImpl::Recover(
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
s = SetDBId();
|
||||
s = SetDBId(read_only);
|
||||
if (s.ok() && !read_only) {
|
||||
s = DeleteUnreferencedSstFiles();
|
||||
}
|
||||
@@ -1132,11 +1137,29 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& wal_numbers,
|
||||
immutable_db_options_.wal_recovery_mode ==
|
||||
WALRecoveryMode::kTolerateCorruptedTailRecords)) {
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (cfd->GetLogNumber() > corrupted_wal_number) {
|
||||
// One special case cause cfd->GetLogNumber() > corrupted_wal_number but
|
||||
// the CF is still consistent: If a new column family is created during
|
||||
// the flush and the WAL sync fails at the same time, the new CF points to
|
||||
// the new WAL but the old WAL is curropted. Since the new CF is empty, it
|
||||
// is still consistent. We add the check of CF sst file size to avoid the
|
||||
// false positive alert.
|
||||
|
||||
// Note that, the check of (cfd->GetLiveSstFilesSize() > 0) may leads to
|
||||
// the ignorance of a very rare inconsistency case caused in data
|
||||
// canclation. One CF is empty due to KV deletion. But those operations
|
||||
// are in the WAL. If the WAL is corrupted, the status of this CF might
|
||||
// not be consistent with others. However, the consistency check will be
|
||||
// bypassed due to empty CF.
|
||||
// TODO: a better and complete implementation is needed to ensure strict
|
||||
// consistency check in WAL recovery including hanlding the tailing
|
||||
// issues.
|
||||
if (cfd->GetLogNumber() > corrupted_wal_number &&
|
||||
cfd->GetLiveSstFilesSize() > 0) {
|
||||
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
|
||||
"Column family inconsistency: SST file contains data"
|
||||
" beyond the point of corruption.");
|
||||
return Status::Corruption("SST file is ahead of WALs");
|
||||
return Status::Corruption("SST file is ahead of WALs in CF " +
|
||||
cfd->GetName());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1229,8 +1252,16 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& wal_numbers,
|
||||
}
|
||||
}
|
||||
|
||||
if (status.ok() && data_seen && !flushed) {
|
||||
status = RestoreAliveLogFiles(wal_numbers);
|
||||
if (status.ok()) {
|
||||
if (data_seen && !flushed) {
|
||||
status = RestoreAliveLogFiles(wal_numbers);
|
||||
} else {
|
||||
// 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)
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
}
|
||||
|
||||
event_logger_.Log() << "job" << job_id << "event"
|
||||
@@ -1239,6 +1270,40 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& wal_numbers,
|
||||
return status;
|
||||
}
|
||||
|
||||
Status DBImpl::GetLogSizeAndMaybeTruncate(uint64_t wal_number, bool truncate,
|
||||
LogFileNumberSize* log_ptr) {
|
||||
LogFileNumberSize log(wal_number);
|
||||
std::string fname = LogFileName(immutable_db_options_.wal_dir, wal_number);
|
||||
Status s;
|
||||
// This gets the appear size of the wals, not including preallocated space.
|
||||
s = env_->GetFileSize(fname, &log.size);
|
||||
if (s.ok() && truncate) {
|
||||
std::unique_ptr<FSWritableFile> last_log;
|
||||
Status truncate_status = fs_->ReopenWritableFile(
|
||||
fname,
|
||||
fs_->OptimizeForLogWrite(
|
||||
file_options_,
|
||||
BuildDBOptions(immutable_db_options_, mutable_db_options_)),
|
||||
&last_log, nullptr);
|
||||
if (truncate_status.ok()) {
|
||||
truncate_status = last_log->Truncate(log.size, IOOptions(), nullptr);
|
||||
}
|
||||
if (truncate_status.ok()) {
|
||||
truncate_status = last_log->Close(IOOptions(), nullptr);
|
||||
}
|
||||
// Not a critical error if fail to truncate.
|
||||
if (!truncate_status.ok()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"Failed to truncate log #%" PRIu64 ": %s", wal_number,
|
||||
truncate_status.ToString().c_str());
|
||||
}
|
||||
}
|
||||
if (log_ptr) {
|
||||
*log_ptr = log;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::RestoreAliveLogFiles(const std::vector<uint64_t>& wal_numbers) {
|
||||
if (wal_numbers.empty()) {
|
||||
return Status::OK();
|
||||
@@ -1254,39 +1319,17 @@ Status DBImpl::RestoreAliveLogFiles(const std::vector<uint64_t>& wal_numbers) {
|
||||
total_log_size_ = 0;
|
||||
log_empty_ = false;
|
||||
for (auto wal_number : wal_numbers) {
|
||||
LogFileNumberSize log(wal_number);
|
||||
std::string fname = LogFileName(immutable_db_options_.wal_dir, wal_number);
|
||||
// This gets the appear size of the wals, not including preallocated space.
|
||||
s = env_->GetFileSize(fname, &log.size);
|
||||
// We preallocate space for wals, but then after a crash and restart, those
|
||||
// preallocated space are not needed anymore. It is likely only the last
|
||||
// log has such preallocated space, so we only truncate for the last log.
|
||||
LogFileNumberSize log;
|
||||
s = GetLogSizeAndMaybeTruncate(
|
||||
wal_number, /*truncate=*/(wal_number == wal_numbers.back()), &log);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
total_log_size_ += log.size;
|
||||
alive_log_files_.push_back(log);
|
||||
// We preallocate space for wals, but then after a crash and restart, those
|
||||
// preallocated space are not needed anymore. It is likely only the last
|
||||
// log has such preallocated space, so we only truncate for the last log.
|
||||
if (wal_number == wal_numbers.back()) {
|
||||
std::unique_ptr<FSWritableFile> last_log;
|
||||
Status truncate_status = fs_->ReopenWritableFile(
|
||||
fname,
|
||||
fs_->OptimizeForLogWrite(
|
||||
file_options_,
|
||||
BuildDBOptions(immutable_db_options_, mutable_db_options_)),
|
||||
&last_log, nullptr);
|
||||
if (truncate_status.ok()) {
|
||||
truncate_status = last_log->Truncate(log.size, IOOptions(), nullptr);
|
||||
}
|
||||
if (truncate_status.ok()) {
|
||||
truncate_status = last_log->Close(IOOptions(), nullptr);
|
||||
}
|
||||
// Not a critical error if fail to truncate.
|
||||
if (!truncate_status.ok()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"Failed to truncate log #%" PRIu64 ": %s", wal_number,
|
||||
truncate_status.ToString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (two_write_queues_) {
|
||||
log_write_mutex_.Unlock();
|
||||
@@ -1297,7 +1340,7 @@ Status DBImpl::RestoreAliveLogFiles(const std::vector<uint64_t>& wal_numbers) {
|
||||
Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
MemTable* mem, VersionEdit* edit) {
|
||||
mutex_.AssertHeld();
|
||||
const uint64_t start_micros = clock_->NowMicros();
|
||||
const uint64_t start_micros = immutable_db_options_.clock->NowMicros();
|
||||
|
||||
FileMetaData meta;
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
@@ -1325,7 +1368,7 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
cfd->GetLatestMutableCFOptions()->paranoid_file_checks;
|
||||
|
||||
int64_t _current_time = 0;
|
||||
env_->GetCurrentTime(&_current_time)
|
||||
immutable_db_options_.clock->GetCurrentTime(&_current_time)
|
||||
.PermitUncheckedError(); // ignore error
|
||||
const uint64_t current_time = static_cast<uint64_t>(_current_time);
|
||||
meta.oldest_ancester_time = current_time;
|
||||
@@ -1358,13 +1401,13 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
cfd->GetID(), cfd->GetName(), snapshot_seqs,
|
||||
earliest_write_conflict_snapshot, snapshot_checker,
|
||||
GetCompressionFlush(*cfd->ioptions(), mutable_cf_options),
|
||||
mutable_cf_options.sample_for_compression,
|
||||
mutable_cf_options.compression_opts, paranoid_file_checks,
|
||||
cfd->internal_stats(), TableFileCreationReason::kRecovery, &io_s,
|
||||
io_tracer_, &event_logger_, job_id, Env::IO_HIGH,
|
||||
nullptr /* table_properties */, -1 /* level */, current_time,
|
||||
nullptr /* table_properties */, 0 /* level */, current_time,
|
||||
0 /* oldest_key_time */, write_hint, 0 /* file_creation_time */,
|
||||
db_id_, db_session_id_);
|
||||
db_id_, db_session_id_, nullptr /*full_history_ts_low*/,
|
||||
&blob_callback_);
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
ROCKS_LOG_DEBUG(immutable_db_options_.info_log,
|
||||
"[%s] [WriteLevel0TableForRecovery]"
|
||||
@@ -1399,7 +1442,7 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
}
|
||||
|
||||
InternalStats::CompactionStats stats(CompactionReason::kFlush, 1);
|
||||
stats.micros = clock_->NowMicros() - start_micros;
|
||||
stats.micros = immutable_db_options_.clock->NowMicros() - start_micros;
|
||||
|
||||
if (has_output) {
|
||||
stats.bytes_written = meta.fd.GetFileSize();
|
||||
@@ -1408,14 +1451,15 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
|
||||
const auto& blobs = edit->GetBlobFileAdditions();
|
||||
for (const auto& blob : blobs) {
|
||||
stats.bytes_written += blob.GetTotalBlobBytes();
|
||||
stats.bytes_written_blob += blob.GetTotalBlobBytes();
|
||||
}
|
||||
|
||||
stats.num_output_files += static_cast<int>(blobs.size());
|
||||
stats.num_output_files_blob = static_cast<int>(blobs.size());
|
||||
|
||||
cfd->internal_stats()->AddCompactionStats(level, Env::Priority::USER, stats);
|
||||
cfd->internal_stats()->AddCFStats(InternalStats::BYTES_FLUSHED,
|
||||
stats.bytes_written);
|
||||
cfd->internal_stats()->AddCFStats(
|
||||
InternalStats::BYTES_FLUSHED,
|
||||
stats.bytes_written + stats.bytes_written_blob);
|
||||
RecordTick(stats_, COMPACT_WRITE_BYTES, meta.fd.GetFileSize());
|
||||
return s;
|
||||
}
|
||||
@@ -1491,9 +1535,9 @@ IOStatus DBImpl::CreateWAL(uint64_t log_file_num, uint64_t recycle_log_number,
|
||||
const auto& listeners = immutable_db_options_.listeners;
|
||||
FileTypeSet tmp_set = immutable_db_options_.checksum_handoff_file_types;
|
||||
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
|
||||
std::move(lfile), log_fname, opt_file_options, clock_, io_tracer_,
|
||||
nullptr /* stats */, listeners, nullptr,
|
||||
tmp_set.Contains(FileType::kWalFile)));
|
||||
std::move(lfile), log_fname, opt_file_options,
|
||||
immutable_db_options_.clock, io_tracer_, nullptr /* stats */, listeners,
|
||||
nullptr, tmp_set.Contains(FileType::kWalFile)));
|
||||
*new_log = new log::Writer(std::move(file_writer), log_file_num,
|
||||
immutable_db_options_.recycle_log_file_num > 0,
|
||||
immutable_db_options_.manual_wal_flush);
|
||||
@@ -1722,6 +1766,8 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
|
||||
std::vector<LiveFileMetaData> metadata;
|
||||
|
||||
// TODO: Once GetLiveFilesMetaData supports blob files, update the logic
|
||||
// below to get known_file_sizes for blob files.
|
||||
impl->mutex_.Lock();
|
||||
impl->versions_->GetLiveFilesMetaData(&metadata);
|
||||
impl->mutex_.Unlock();
|
||||
@@ -1754,13 +1800,12 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
FileType file_type;
|
||||
std::string file_path = path + "/" + file_name;
|
||||
if (ParseFileName(file_name, &file_number, &file_type) &&
|
||||
file_type == kTableFile) {
|
||||
(file_type == kTableFile || file_type == kBlobFile)) {
|
||||
// TODO: Check for errors from OnAddFile?
|
||||
if (known_file_sizes.count(file_name)) {
|
||||
// We're assuming that each sst file name exists in at most one of
|
||||
// the paths.
|
||||
sfm->OnAddFile(file_path, known_file_sizes.at(file_name),
|
||||
/* compaction */ false)
|
||||
sfm->OnAddFile(file_path, known_file_sizes.at(file_name))
|
||||
.PermitUncheckedError();
|
||||
} else {
|
||||
sfm->OnAddFile(file_path).PermitUncheckedError();
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include "db/db_impl/db_impl_readonly.h"
|
||||
|
||||
#include "db/arena_wrapped_db_iter.h"
|
||||
#include "db/compacted_db_impl.h"
|
||||
#include "db/db_impl/compacted_db_impl.h"
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/db_iter.h"
|
||||
#include "db/merge_context.h"
|
||||
@@ -19,7 +19,8 @@ namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
DBImplReadOnly::DBImplReadOnly(const DBOptions& db_options,
|
||||
const std::string& dbname)
|
||||
: DBImpl(db_options, dbname) {
|
||||
: DBImpl(db_options, dbname, /*seq_per_batch*/ false,
|
||||
/*batch_per_txn*/ true, /*read_only*/ true) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Opening the db in read only mode");
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
@@ -131,8 +132,8 @@ Status DBImplReadOnly::NewIterators(
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Return OK if dbname exists in the file system
|
||||
// or create_if_missing is false
|
||||
// Return OK if dbname exists in the file system or create it if
|
||||
// create_if_missing
|
||||
Status OpenForReadOnlyCheckExistence(const DBOptions& db_options,
|
||||
const std::string& dbname) {
|
||||
Status s;
|
||||
@@ -143,6 +144,9 @@ Status OpenForReadOnlyCheckExistence(const DBOptions& db_options,
|
||||
uint64_t manifest_file_number;
|
||||
s = VersionSet::GetCurrentManifestPath(dbname, fs.get(), &manifest_path,
|
||||
&manifest_file_number);
|
||||
} else {
|
||||
// Historic behavior that doesn't necessarily make sense
|
||||
s = db_options.env->CreateDirIfMissing(dbname);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -150,7 +154,6 @@ Status OpenForReadOnlyCheckExistence(const DBOptions& db_options,
|
||||
|
||||
Status DB::OpenForReadOnly(const Options& options, const std::string& dbname,
|
||||
DB** dbptr, bool /*error_if_wal_file_exists*/) {
|
||||
// If dbname does not exist in the file system, should not do anything
|
||||
Status s = OpenForReadOnlyCheckExistence(options, dbname);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
|
||||
@@ -17,8 +17,10 @@ namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
DBImplSecondary::DBImplSecondary(const DBOptions& db_options,
|
||||
const std::string& dbname)
|
||||
: DBImpl(db_options, dbname) {
|
||||
const std::string& dbname,
|
||||
std::string secondary_path)
|
||||
: DBImpl(db_options, dbname, false, true, true),
|
||||
secondary_path_(std::move(secondary_path)) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Opening the db in secondary mode");
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
@@ -327,8 +329,8 @@ Status DBImplSecondary::GetImpl(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice& key, PinnableSlice* pinnable_val) {
|
||||
assert(pinnable_val != nullptr);
|
||||
PERF_CPU_TIMER_GUARD(get_cpu_nanos, clock_);
|
||||
StopWatch sw(clock_, stats_, DB_GET);
|
||||
PERF_CPU_TIMER_GUARD(get_cpu_nanos, immutable_db_options_.clock);
|
||||
StopWatch sw(immutable_db_options_.clock, stats_, DB_GET);
|
||||
PERF_TIMER_GUARD(get_snapshot_time);
|
||||
|
||||
auto cfh = static_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
@@ -519,7 +521,8 @@ Status DBImplSecondary::TryCatchUpWithPrimary() {
|
||||
{
|
||||
InstrumentedMutexLock lock_guard(&mutex_);
|
||||
s = static_cast_with_check<ReactiveVersionSet>(versions_.get())
|
||||
->ReadAndApply(&mutex_, &manifest_reader_, &cfds_changed);
|
||||
->ReadAndApply(&mutex_, &manifest_reader_,
|
||||
manifest_reader_status_.get(), &cfds_changed);
|
||||
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "Last sequence is %" PRIu64,
|
||||
static_cast<uint64_t>(versions_->LastSequence()));
|
||||
@@ -616,7 +619,7 @@ Status DB::OpenAsSecondary(
|
||||
}
|
||||
|
||||
handles->clear();
|
||||
DBImplSecondary* impl = new DBImplSecondary(tmp_opts, dbname);
|
||||
DBImplSecondary* impl = new DBImplSecondary(tmp_opts, dbname, secondary_path);
|
||||
impl->versions_.reset(new ReactiveVersionSet(
|
||||
dbname, &impl->immutable_db_options_, impl->file_options_,
|
||||
impl->table_cache_.get(), impl->write_buffer_manager_,
|
||||
@@ -662,6 +665,86 @@ Status DB::OpenAsSecondary(
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImplSecondary::CompactWithoutInstallation(
|
||||
ColumnFamilyHandle* cfh, const CompactionServiceInput& input,
|
||||
CompactionServiceResult* result) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
auto cfd = static_cast_with_check<ColumnFamilyHandleImpl>(cfh)->cfd();
|
||||
if (!cfd) {
|
||||
return Status::InvalidArgument("Cannot find column family" +
|
||||
cfh->GetName());
|
||||
}
|
||||
|
||||
std::unordered_set<uint64_t> input_set;
|
||||
for (const auto& file_name : input.input_files) {
|
||||
input_set.insert(TableFileNameToNumber(file_name));
|
||||
}
|
||||
|
||||
auto* version = cfd->current();
|
||||
|
||||
ColumnFamilyMetaData cf_meta;
|
||||
version->GetColumnFamilyMetaData(&cf_meta);
|
||||
|
||||
const MutableCFOptions* mutable_cf_options = cfd->GetLatestMutableCFOptions();
|
||||
ColumnFamilyOptions cf_options = cfd->GetLatestCFOptions();
|
||||
VersionStorageInfo* vstorage = version->storage_info();
|
||||
|
||||
// Use comp_options to reuse some CompactFiles functions
|
||||
CompactionOptions comp_options;
|
||||
comp_options.compression = kDisableCompressionOption;
|
||||
comp_options.output_file_size_limit = MaxFileSizeForLevel(
|
||||
*mutable_cf_options, input.output_level, cf_options.compaction_style,
|
||||
vstorage->base_level(), cf_options.level_compaction_dynamic_level_bytes);
|
||||
|
||||
std::vector<CompactionInputFiles> input_files;
|
||||
Status s = cfd->compaction_picker()->GetCompactionInputsFromFileNumbers(
|
||||
&input_files, &input_set, vstorage, comp_options);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
std::unique_ptr<Compaction> c;
|
||||
assert(cfd->compaction_picker());
|
||||
c.reset(cfd->compaction_picker()->CompactFiles(
|
||||
comp_options, input_files, input.output_level, vstorage,
|
||||
*mutable_cf_options, mutable_db_options_, 0));
|
||||
assert(c != nullptr);
|
||||
|
||||
c->SetInputVersion(version);
|
||||
|
||||
// Create output directory if it's not existed yet
|
||||
std::unique_ptr<FSDirectory> output_dir;
|
||||
s = CreateAndNewDirectory(fs_.get(), secondary_path_, &output_dir);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL,
|
||||
immutable_db_options_.info_log.get());
|
||||
|
||||
const int job_id = next_job_id_.fetch_add(1);
|
||||
|
||||
CompactionServiceCompactionJob compaction_job(
|
||||
job_id, c.get(), immutable_db_options_, file_options_for_compaction_,
|
||||
versions_.get(), &shutting_down_, &log_buffer, output_dir.get(), stats_,
|
||||
&mutex_, &error_handler_, input.snapshots, table_cache_, &event_logger_,
|
||||
dbname_, io_tracer_, db_id_, db_session_id_, secondary_path_, input,
|
||||
result);
|
||||
|
||||
mutex_.Unlock();
|
||||
s = compaction_job.Run();
|
||||
mutex_.Lock();
|
||||
|
||||
// clean up
|
||||
compaction_job.io_status().PermitUncheckedError();
|
||||
compaction_job.CleanupCompaction();
|
||||
c->ReleaseCompactionFiles(s);
|
||||
c.reset();
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
#else // !ROCKSDB_LITE
|
||||
|
||||
Status DB::OpenAsSecondary(const Options& /*options*/,
|
||||
|
||||
@@ -71,7 +71,8 @@ class LogReaderContainer {
|
||||
// effort attempts to catch up with the primary.
|
||||
class DBImplSecondary : public DBImpl {
|
||||
public:
|
||||
DBImplSecondary(const DBOptions& options, const std::string& dbname);
|
||||
DBImplSecondary(const DBOptions& options, const std::string& dbname,
|
||||
std::string secondary_path);
|
||||
~DBImplSecondary() override;
|
||||
|
||||
// Recover by replaying MANIFEST and WAL. Also initialize manifest_reader_
|
||||
@@ -222,6 +223,14 @@ class DBImplSecondary : public DBImpl {
|
||||
// not flag the missing file as inconsistency.
|
||||
Status CheckConsistency() override;
|
||||
|
||||
#ifndef NDEBUG
|
||||
Status TEST_CompactWithoutInstallation(ColumnFamilyHandle* cfh,
|
||||
const CompactionServiceInput& input,
|
||||
CompactionServiceResult* result) {
|
||||
return CompactWithoutInstallation(cfh, input, result);
|
||||
}
|
||||
#endif // NDEBUG
|
||||
|
||||
protected:
|
||||
// ColumnFamilyCollector is a write batch handler which does nothing
|
||||
// except recording unique column family IDs
|
||||
@@ -316,6 +325,13 @@ class DBImplSecondary : public DBImpl {
|
||||
std::unordered_set<ColumnFamilyData*>* cfds_changed,
|
||||
JobContext* job_context);
|
||||
|
||||
// Run compaction without installation, the output files will be placed in the
|
||||
// secondary DB path. The LSM tree won't be changed, the secondary DB is still
|
||||
// in read-only mode.
|
||||
Status CompactWithoutInstallation(ColumnFamilyHandle* cfh,
|
||||
const CompactionServiceInput& input,
|
||||
CompactionServiceResult* result);
|
||||
|
||||
std::unique_ptr<log::FragmentBufferedReader> manifest_reader_;
|
||||
std::unique_ptr<log::Reader::Reporter> manifest_reporter_;
|
||||
std::unique_ptr<Status> manifest_reader_status_;
|
||||
@@ -326,6 +342,8 @@ class DBImplSecondary : public DBImpl {
|
||||
|
||||
// Current WAL number replayed for each column family.
|
||||
std::unordered_map<ColumnFamilyData*, uint64_t> cfd_to_current_log_;
|
||||
|
||||
const std::string secondary_path_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
+64
-19
@@ -160,7 +160,8 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
RecordTick(stats_, WRITE_WITH_WAL);
|
||||
}
|
||||
|
||||
StopWatch write_sw(clock_, immutable_db_options_.statistics.get(), DB_WRITE);
|
||||
StopWatch write_sw(immutable_db_options_.clock,
|
||||
immutable_db_options_.statistics.get(), DB_WRITE);
|
||||
|
||||
write_thread_.JoinBatchGroup(&w);
|
||||
if (w.state == WriteThread::STATE_PARALLEL_MEMTABLE_WRITER) {
|
||||
@@ -465,7 +466,8 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
uint64_t* log_used, uint64_t log_ref,
|
||||
bool disable_memtable, uint64_t* seq_used) {
|
||||
PERF_TIMER_GUARD(write_pre_and_post_process_time);
|
||||
StopWatch write_sw(clock_, immutable_db_options_.statistics.get(), DB_WRITE);
|
||||
StopWatch write_sw(immutable_db_options_.clock,
|
||||
immutable_db_options_.statistics.get(), DB_WRITE);
|
||||
|
||||
WriteContext write_context;
|
||||
|
||||
@@ -621,7 +623,8 @@ Status DBImpl::UnorderedWriteMemtable(const WriteOptions& write_options,
|
||||
SequenceNumber seq,
|
||||
const size_t sub_batch_cnt) {
|
||||
PERF_TIMER_GUARD(write_pre_and_post_process_time);
|
||||
StopWatch write_sw(clock_, immutable_db_options_.statistics.get(), DB_WRITE);
|
||||
StopWatch write_sw(immutable_db_options_.clock,
|
||||
immutable_db_options_.statistics.get(), DB_WRITE);
|
||||
|
||||
WriteThread::Writer w(write_options, my_batch, callback, log_ref,
|
||||
false /*disable_memtable*/);
|
||||
@@ -676,7 +679,8 @@ Status DBImpl::WriteImplWALOnly(
|
||||
WriteThread::Writer w(write_options, my_batch, callback, log_ref,
|
||||
disable_memtable, sub_batch_cnt, pre_release_callback);
|
||||
RecordTick(stats_, WRITE_WITH_WAL);
|
||||
StopWatch write_sw(clock_, immutable_db_options_.statistics.get(), DB_WRITE);
|
||||
StopWatch write_sw(immutable_db_options_.clock,
|
||||
immutable_db_options_.statistics.get(), DB_WRITE);
|
||||
|
||||
write_thread->JoinBatchGroup(&w);
|
||||
assert(w.state != WriteThread::STATE_PARALLEL_MEMTABLE_WRITER);
|
||||
@@ -933,7 +937,7 @@ Status DBImpl::PreprocessWrite(const WriteOptions& write_options,
|
||||
// be flushed. We may end up with flushing much more DBs than needed. It's
|
||||
// suboptimal but still correct.
|
||||
WaitForPendingWrites();
|
||||
status = HandleWriteBufferFull(write_context);
|
||||
status = HandleWriteBufferManagerFlush(write_context);
|
||||
}
|
||||
|
||||
if (UNLIKELY(status.ok() && !trim_history_scheduler_.Empty())) {
|
||||
@@ -960,6 +964,20 @@ Status DBImpl::PreprocessWrite(const WriteOptions& write_options,
|
||||
PERF_TIMER_START(write_pre_and_post_process_time);
|
||||
}
|
||||
|
||||
// If memory usage exceeded beyond a certain threshold,
|
||||
// write_buffer_manager_->ShouldStall() returns true to all threads writing to
|
||||
// all DBs and writers will be stalled.
|
||||
// It does soft checking because WriteBufferManager::buffer_limit_ has already
|
||||
// exceeded at this point so no new write (including current one) will go
|
||||
// through until memory usage is decreased.
|
||||
if (UNLIKELY(status.ok() && write_buffer_manager_->ShouldStall())) {
|
||||
if (write_options.no_slowdown) {
|
||||
status = Status::Incomplete("Write stall");
|
||||
} else {
|
||||
WriteBufferManagerStallWrites();
|
||||
}
|
||||
}
|
||||
|
||||
if (status.ok() && *need_log_sync) {
|
||||
// Wait until the parallel syncs are finished. Any sync process has to sync
|
||||
// the front log too so it is enough to check the status of front()
|
||||
@@ -1093,7 +1111,7 @@ IOStatus DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
}
|
||||
|
||||
if (io_s.ok() && need_log_sync) {
|
||||
StopWatch sw(clock_, stats_, WAL_FILE_SYNC_MICROS);
|
||||
StopWatch sw(immutable_db_options_.clock, stats_, WAL_FILE_SYNC_MICROS);
|
||||
// It's safe to access logs_ with unlocked mutex_ here because:
|
||||
// - we've set getting_synced=true for all logs,
|
||||
// so other threads won't pop from logs_ while we're here,
|
||||
@@ -1344,20 +1362,20 @@ Status DBImpl::SwitchWAL(WriteContext* write_context) {
|
||||
if (!immutable_db_options_.atomic_flush) {
|
||||
FlushRequest flush_req;
|
||||
GenerateFlushRequest({cfd}, &flush_req);
|
||||
SchedulePendingFlush(flush_req, FlushReason::kWriteBufferManager);
|
||||
SchedulePendingFlush(flush_req, FlushReason::kWalFull);
|
||||
}
|
||||
}
|
||||
if (immutable_db_options_.atomic_flush) {
|
||||
FlushRequest flush_req;
|
||||
GenerateFlushRequest(cfds, &flush_req);
|
||||
SchedulePendingFlush(flush_req, FlushReason::kWriteBufferManager);
|
||||
SchedulePendingFlush(flush_req, FlushReason::kWalFull);
|
||||
}
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
Status DBImpl::HandleWriteBufferFull(WriteContext* write_context) {
|
||||
Status DBImpl::HandleWriteBufferManagerFlush(WriteContext* write_context) {
|
||||
mutex_.AssertHeld();
|
||||
assert(write_context != nullptr);
|
||||
Status status;
|
||||
@@ -1369,7 +1387,7 @@ Status DBImpl::HandleWriteBufferFull(WriteContext* write_context) {
|
||||
// suboptimal but still correct.
|
||||
ROCKS_LOG_INFO(
|
||||
immutable_db_options_.info_log,
|
||||
"Flushing column family with oldest memtable entry. Write buffer is "
|
||||
"Flushing column family with oldest memtable entry. Write buffers are "
|
||||
"using %" ROCKSDB_PRIszt " bytes out of a total of %" ROCKSDB_PRIszt ".",
|
||||
write_buffer_manager_->memory_usage(),
|
||||
write_buffer_manager_->buffer_size());
|
||||
@@ -1430,13 +1448,13 @@ Status DBImpl::HandleWriteBufferFull(WriteContext* write_context) {
|
||||
if (!immutable_db_options_.atomic_flush) {
|
||||
FlushRequest flush_req;
|
||||
GenerateFlushRequest({cfd}, &flush_req);
|
||||
SchedulePendingFlush(flush_req, FlushReason::kWriteBufferFull);
|
||||
SchedulePendingFlush(flush_req, FlushReason::kWriteBufferManager);
|
||||
}
|
||||
}
|
||||
if (immutable_db_options_.atomic_flush) {
|
||||
FlushRequest flush_req;
|
||||
GenerateFlushRequest(cfds, &flush_req);
|
||||
SchedulePendingFlush(flush_req, FlushReason::kWriteBufferFull);
|
||||
SchedulePendingFlush(flush_req, FlushReason::kWriteBufferManager);
|
||||
}
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
@@ -1457,8 +1475,10 @@ Status DBImpl::DelayWrite(uint64_t num_bytes,
|
||||
uint64_t time_delayed = 0;
|
||||
bool delayed = false;
|
||||
{
|
||||
StopWatch sw(clock_, stats_, WRITE_STALL, &time_delayed);
|
||||
uint64_t delay = write_controller_.GetDelay(clock_, num_bytes);
|
||||
StopWatch sw(immutable_db_options_.clock, stats_, WRITE_STALL,
|
||||
&time_delayed);
|
||||
uint64_t delay =
|
||||
write_controller_.GetDelay(immutable_db_options_.clock, num_bytes);
|
||||
if (delay > 0) {
|
||||
if (write_options.no_slowdown) {
|
||||
return Status::Incomplete("Write stall");
|
||||
@@ -1470,19 +1490,21 @@ Status DBImpl::DelayWrite(uint64_t num_bytes,
|
||||
write_thread_.BeginWriteStall();
|
||||
TEST_SYNC_POINT("DBImpl::DelayWrite:BeginWriteStallDone");
|
||||
mutex_.Unlock();
|
||||
// We will delay the write until we have slept for delay ms or
|
||||
// we don't need a delay anymore
|
||||
const uint64_t kDelayInterval = 1000;
|
||||
// We will delay the write until we have slept for `delay` microseconds
|
||||
// or we don't need a delay anymore. We check for cancellation every 1ms
|
||||
// (slightly longer because WriteController minimum delay is 1ms, in
|
||||
// case of sleep imprecision, rounding, etc.)
|
||||
const uint64_t kDelayInterval = 1001;
|
||||
uint64_t stall_end = sw.start_time() + delay;
|
||||
while (write_controller_.NeedsDelay()) {
|
||||
if (clock_->NowMicros() >= stall_end) {
|
||||
if (immutable_db_options_.clock->NowMicros() >= stall_end) {
|
||||
// We already delayed this write `delay` microseconds
|
||||
break;
|
||||
}
|
||||
|
||||
delayed = true;
|
||||
// Sleep for 0.001 seconds
|
||||
clock_->SleepForMicroseconds(kDelayInterval);
|
||||
immutable_db_options_.clock->SleepForMicroseconds(kDelayInterval);
|
||||
}
|
||||
mutex_.Lock();
|
||||
write_thread_.EndWriteStall();
|
||||
@@ -1528,6 +1550,29 @@ Status DBImpl::DelayWrite(uint64_t num_bytes,
|
||||
return s;
|
||||
}
|
||||
|
||||
// REQUIRES: mutex_ is held
|
||||
// REQUIRES: this thread is currently at the front of the writer queue
|
||||
void DBImpl::WriteBufferManagerStallWrites() {
|
||||
mutex_.AssertHeld();
|
||||
// First block future writer threads who want to add themselves to the queue
|
||||
// of WriteThread.
|
||||
write_thread_.BeginWriteStall();
|
||||
mutex_.Unlock();
|
||||
|
||||
// Change the state to State::Blocked.
|
||||
static_cast<WBMStallInterface*>(wbm_stall_.get())
|
||||
->SetState(WBMStallInterface::State::BLOCKED);
|
||||
// Then WriteBufferManager will add DB instance to its queue
|
||||
// and block this thread by calling WBMStallInterface::Block().
|
||||
write_buffer_manager_->BeginWriteStall(wbm_stall_.get());
|
||||
wbm_stall_->Block();
|
||||
|
||||
mutex_.Lock();
|
||||
// Stall has ended. Signal writer threads so that they can add
|
||||
// themselves to the WriteThread queue for writes.
|
||||
write_thread_.EndWriteStall();
|
||||
}
|
||||
|
||||
Status DBImpl::ThrottleLowPriWritesIfNeeded(const WriteOptions& write_options,
|
||||
WriteBatch* my_batch) {
|
||||
assert(write_options.low_pri);
|
||||
|
||||
+85
-51
@@ -45,10 +45,10 @@ 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_(_env->GetSystemClock()),
|
||||
clock_(cf_options.clock),
|
||||
logger_(cf_options.info_log),
|
||||
user_comparator_(cmp),
|
||||
merge_operator_(cf_options.merge_operator),
|
||||
merge_operator_(cf_options.merge_operator.get()),
|
||||
iter_(iter),
|
||||
version_(version),
|
||||
read_callback_(read_callback),
|
||||
@@ -114,8 +114,7 @@ Status DBIter::GetProperty(std::string prop_name, std::string* prop) {
|
||||
}
|
||||
|
||||
bool DBIter::ParseKey(ParsedInternalKey* ikey) {
|
||||
Status s =
|
||||
ParseInternalKey(iter_.key(), ikey, false /* log_err_key */); // TODO
|
||||
Status s = ParseInternalKey(iter_.key(), ikey, false /* log_err_key */);
|
||||
if (!s.ok()) {
|
||||
status_ = Status::Corruption("In DBIter: ", s.getState());
|
||||
valid_ = false;
|
||||
@@ -193,8 +192,10 @@ bool DBIter::SetBlobValueIfNeeded(const Slice& user_key,
|
||||
read_options.read_tier = read_tier_;
|
||||
read_options.verify_checksums = verify_checksums_;
|
||||
|
||||
const Status s =
|
||||
version_->GetBlob(read_options, user_key, blob_index, &blob_value_);
|
||||
constexpr uint64_t* bytes_read = nullptr;
|
||||
|
||||
const Status s = version_->GetBlob(read_options, user_key, blob_index,
|
||||
&blob_value_, bytes_read);
|
||||
|
||||
if (!s.ok()) {
|
||||
status_ = s;
|
||||
@@ -487,7 +488,7 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
|
||||
&last_key,
|
||||
ParsedInternalKey(saved_key_.GetUserKey(), 0, kTypeDeletion));
|
||||
} else {
|
||||
const std::string kTsMin(timestamp_size_, static_cast<char>(0));
|
||||
const std::string kTsMin(timestamp_size_, '\0');
|
||||
AppendInternalKeyWithDifferentTimestamp(
|
||||
&last_key,
|
||||
ParsedInternalKey(saved_key_.GetUserKey(), 0, kTypeDeletion),
|
||||
@@ -633,13 +634,6 @@ bool DBIter::MergeValuesNewToOld() {
|
||||
}
|
||||
|
||||
void DBIter::Prev() {
|
||||
if (timestamp_size_ > 0) {
|
||||
valid_ = false;
|
||||
status_ = Status::NotSupported(
|
||||
"SeekToLast/SeekForPrev/Prev currently not supported with timestamp.");
|
||||
return;
|
||||
}
|
||||
|
||||
assert(valid_);
|
||||
assert(status_.ok());
|
||||
|
||||
@@ -678,9 +672,16 @@ bool DBIter::ReverseToForward() {
|
||||
// If that's the case, seek iter_ to current key.
|
||||
if (!expect_total_order_inner_iter() || !iter_.Valid()) {
|
||||
IterKey last_key;
|
||||
last_key.SetInternalKey(ParsedInternalKey(
|
||||
saved_key_.GetUserKey(), kMaxSequenceNumber, kValueTypeForSeek));
|
||||
ParsedInternalKey pikey(saved_key_.GetUserKey(), kMaxSequenceNumber,
|
||||
kValueTypeForSeek);
|
||||
if (timestamp_size_ > 0) {
|
||||
// TODO: pre-create kTsMax.
|
||||
const std::string kTsMax(timestamp_size_, '\xff');
|
||||
pikey.SetTimestamp(kTsMax);
|
||||
}
|
||||
last_key.SetInternalKey(pikey);
|
||||
iter_.Seek(last_key.GetInternalKey());
|
||||
RecordTick(statistics_, NUMBER_OF_RESEEKS_IN_ITERATION);
|
||||
}
|
||||
|
||||
direction_ = kForward;
|
||||
@@ -731,6 +732,7 @@ bool DBIter::ReverseToBackward() {
|
||||
iter_.SeekToLast();
|
||||
}
|
||||
}
|
||||
RecordTick(statistics_, NUMBER_OF_RESEEKS_IN_ITERATION);
|
||||
}
|
||||
|
||||
direction_ = kReverse;
|
||||
@@ -756,11 +758,13 @@ void DBIter::PrevInternal(const Slice* prefix) {
|
||||
}
|
||||
|
||||
assert(iterate_lower_bound_ == nullptr || iter_.MayBeOutOfLowerBound() ||
|
||||
user_comparator_.Compare(saved_key_.GetUserKey(),
|
||||
*iterate_lower_bound_) >= 0);
|
||||
user_comparator_.CompareWithoutTimestamp(
|
||||
saved_key_.GetUserKey(), /*a_has_ts=*/true,
|
||||
*iterate_lower_bound_, /*b_has_ts=*/false) >= 0);
|
||||
if (iterate_lower_bound_ != nullptr && iter_.MayBeOutOfLowerBound() &&
|
||||
user_comparator_.Compare(saved_key_.GetUserKey(),
|
||||
*iterate_lower_bound_) < 0) {
|
||||
user_comparator_.CompareWithoutTimestamp(
|
||||
saved_key_.GetUserKey(), /*a_has_ts=*/true, *iterate_lower_bound_,
|
||||
/*b_has_ts=*/false) < 0) {
|
||||
// We've iterated earlier than the user-specified lower bound.
|
||||
valid_ = false;
|
||||
return;
|
||||
@@ -805,8 +809,8 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
assert(iter_.Valid());
|
||||
merge_context_.Clear();
|
||||
current_entry_is_merged_ = false;
|
||||
// last entry before merge (could be kTypeDeletion, kTypeSingleDeletion or
|
||||
// kTypeValue)
|
||||
// last entry before merge (could be kTypeDeletion,
|
||||
// kTypeDeletionWithTimestamp, kTypeSingleDeletion or kTypeValue)
|
||||
ValueType last_not_merge_type = kTypeDeletion;
|
||||
ValueType last_key_entry_type = kTypeDeletion;
|
||||
|
||||
@@ -827,9 +831,13 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
timestamp_size_);
|
||||
}
|
||||
if (!IsVisible(ikey.sequence, ts) ||
|
||||
!user_comparator_.Equal(ikey.user_key, saved_key_.GetUserKey())) {
|
||||
!user_comparator_.EqualWithoutTimestamp(ikey.user_key,
|
||||
saved_key_.GetUserKey())) {
|
||||
break;
|
||||
}
|
||||
if (!ts.empty()) {
|
||||
saved_timestamp_.assign(ts.data(), ts.size());
|
||||
}
|
||||
if (TooManyInternalKeysSkipped()) {
|
||||
return false;
|
||||
}
|
||||
@@ -869,6 +877,7 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
}
|
||||
break;
|
||||
case kTypeDeletion:
|
||||
case kTypeDeletionWithTimestamp:
|
||||
case kTypeSingleDeletion:
|
||||
merge_context_.Clear();
|
||||
last_not_merge_type = last_key_entry_type;
|
||||
@@ -912,6 +921,7 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
is_blob_ = false;
|
||||
switch (last_key_entry_type) {
|
||||
case kTypeDeletion:
|
||||
case kTypeDeletionWithTimestamp:
|
||||
case kTypeSingleDeletion:
|
||||
case kTypeRangeDeletion:
|
||||
valid_ = false;
|
||||
@@ -972,8 +982,17 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
// FindValueForCurrentKeyUsingSeek()
|
||||
assert(pinned_iters_mgr_.PinningEnabled());
|
||||
std::string last_key;
|
||||
AppendInternalKey(&last_key, ParsedInternalKey(saved_key_.GetUserKey(),
|
||||
sequence_, kValueTypeForSeek));
|
||||
if (0 == timestamp_size_) {
|
||||
AppendInternalKey(&last_key,
|
||||
ParsedInternalKey(saved_key_.GetUserKey(), sequence_,
|
||||
kValueTypeForSeek));
|
||||
} else {
|
||||
AppendInternalKeyWithDifferentTimestamp(
|
||||
&last_key,
|
||||
ParsedInternalKey(saved_key_.GetUserKey(), sequence_,
|
||||
kValueTypeForSeek),
|
||||
*timestamp_ub_);
|
||||
}
|
||||
iter_.Seek(last_key);
|
||||
RecordTick(statistics_, NUMBER_OF_RESEEKS_IN_ITERATION);
|
||||
|
||||
@@ -997,7 +1016,8 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
timestamp_size_);
|
||||
}
|
||||
|
||||
if (!user_comparator_.Equal(ikey.user_key, saved_key_.GetUserKey())) {
|
||||
if (!user_comparator_.EqualWithoutTimestamp(ikey.user_key,
|
||||
saved_key_.GetUserKey())) {
|
||||
// No visible values for this key, even though FindValueForCurrentKey()
|
||||
// has seen some. This is possible if we're using a tailing iterator, and
|
||||
// the entries were discarded in a compaction.
|
||||
@@ -1014,7 +1034,8 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
|
||||
if (ikey.type == kTypeDeletion || ikey.type == kTypeSingleDeletion ||
|
||||
range_del_agg_.ShouldDelete(
|
||||
ikey, RangeDelPositioningMode::kBackwardTraversal)) {
|
||||
ikey, RangeDelPositioningMode::kBackwardTraversal) ||
|
||||
kTypeDeletionWithTimestamp == ikey.type) {
|
||||
valid_ = false;
|
||||
return true;
|
||||
}
|
||||
@@ -1022,6 +1043,10 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
if (timestamp_size_ > 0) {
|
||||
Slice ts = ExtractTimestampFromUserKey(ikey.user_key, timestamp_size_);
|
||||
saved_timestamp_.assign(ts.data(), ts.size());
|
||||
}
|
||||
if (ikey.type == kTypeValue || ikey.type == kTypeBlobIndex) {
|
||||
assert(iter_.iter()->IsValuePinned());
|
||||
pinned_value_ = iter_.value();
|
||||
@@ -1138,7 +1163,8 @@ bool DBIter::FindUserKeyBeforeSavedKey() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (user_comparator_.Compare(ikey.user_key, saved_key_.GetUserKey()) < 0) {
|
||||
if (user_comparator_.CompareWithoutTimestamp(ikey.user_key,
|
||||
saved_key_.GetUserKey()) < 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1162,8 +1188,14 @@ bool DBIter::FindUserKeyBeforeSavedKey() {
|
||||
if (num_skipped >= max_skip_) {
|
||||
num_skipped = 0;
|
||||
IterKey last_key;
|
||||
last_key.SetInternalKey(ParsedInternalKey(
|
||||
saved_key_.GetUserKey(), kMaxSequenceNumber, kValueTypeForSeek));
|
||||
ParsedInternalKey pikey(saved_key_.GetUserKey(), kMaxSequenceNumber,
|
||||
kValueTypeForSeek);
|
||||
if (timestamp_size_ > 0) {
|
||||
// TODO: pre-create kTsMax.
|
||||
const std::string kTsMax(timestamp_size_, '\xff');
|
||||
pikey.SetTimestamp(kTsMax);
|
||||
}
|
||||
last_key.SetInternalKey(pikey);
|
||||
// It would be more efficient to use SeekForPrev() here, but some
|
||||
// iterators may not support it.
|
||||
iter_.Seek(last_key.GetInternalKey());
|
||||
@@ -1240,13 +1272,27 @@ void DBIter::SetSavedKeyToSeekForPrevTarget(const Slice& target) {
|
||||
saved_key_.Clear();
|
||||
// now saved_key is used to store internal key.
|
||||
saved_key_.SetInternalKey(target, 0 /* sequence_number */,
|
||||
kValueTypeForSeekForPrev);
|
||||
kValueTypeForSeekForPrev, timestamp_ub_);
|
||||
|
||||
if (timestamp_size_ > 0) {
|
||||
const std::string kTsMin(timestamp_size_, '\0');
|
||||
Slice ts = kTsMin;
|
||||
saved_key_.UpdateInternalKey(/*seq=*/0, kValueTypeForSeekForPrev, &ts);
|
||||
}
|
||||
|
||||
if (iterate_upper_bound_ != nullptr &&
|
||||
user_comparator_.Compare(saved_key_.GetUserKey(),
|
||||
*iterate_upper_bound_) >= 0) {
|
||||
user_comparator_.CompareWithoutTimestamp(
|
||||
saved_key_.GetUserKey(), /*a_has_ts=*/true, *iterate_upper_bound_,
|
||||
/*b_has_ts=*/false) >= 0) {
|
||||
saved_key_.Clear();
|
||||
saved_key_.SetInternalKey(*iterate_upper_bound_, kMaxSequenceNumber);
|
||||
saved_key_.SetInternalKey(*iterate_upper_bound_, kMaxSequenceNumber,
|
||||
kValueTypeForSeekForPrev, timestamp_ub_);
|
||||
if (timestamp_size_ > 0) {
|
||||
const std::string kTsMax(timestamp_size_, '\xff');
|
||||
Slice ts = kTsMax;
|
||||
saved_key_.UpdateInternalKey(kMaxSequenceNumber, kValueTypeForSeekForPrev,
|
||||
&ts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1297,7 +1343,7 @@ void DBIter::Seek(const Slice& target) {
|
||||
// we need to find out the next key that is visible to the user.
|
||||
ClearSavedValue();
|
||||
if (prefix_same_as_start_) {
|
||||
// The case where the iterator needs to be invalidated if it has exausted
|
||||
// The case where the iterator needs to be invalidated if it has exhausted
|
||||
// keys within the same prefix of the seek key.
|
||||
assert(prefix_extractor_ != nullptr);
|
||||
Slice target_prefix = prefix_extractor_->Transform(target);
|
||||
@@ -1349,13 +1395,6 @@ void DBIter::SeekForPrev(const Slice& target) {
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
if (timestamp_size_ > 0) {
|
||||
valid_ = false;
|
||||
status_ = Status::NotSupported(
|
||||
"SeekToLast/SeekForPrev/Prev currently not supported with timestamp.");
|
||||
return;
|
||||
}
|
||||
|
||||
status_ = Status::OK();
|
||||
ReleaseTempPinnedData();
|
||||
ResetInternalKeysSkippedCounter();
|
||||
@@ -1379,7 +1418,7 @@ void DBIter::SeekForPrev(const Slice& target) {
|
||||
// backward direction.
|
||||
ClearSavedValue();
|
||||
if (prefix_same_as_start_) {
|
||||
// The case where the iterator needs to be invalidated if it has exausted
|
||||
// The case where the iterator needs to be invalidated if it has exhausted
|
||||
// keys within the same prefix of the seek key.
|
||||
assert(prefix_extractor_ != nullptr);
|
||||
Slice target_prefix = prefix_extractor_->Transform(target);
|
||||
@@ -1450,17 +1489,12 @@ void DBIter::SeekToFirst() {
|
||||
}
|
||||
|
||||
void DBIter::SeekToLast() {
|
||||
if (timestamp_size_ > 0) {
|
||||
valid_ = false;
|
||||
status_ = Status::NotSupported(
|
||||
"SeekToLast/SeekForPrev/Prev currently not supported with timestamp.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (iterate_upper_bound_ != nullptr) {
|
||||
// Seek to last key strictly less than ReadOptions.iterate_upper_bound.
|
||||
SeekForPrev(*iterate_upper_bound_);
|
||||
if (Valid() && user_comparator_.Equal(*iterate_upper_bound_, key())) {
|
||||
if (Valid() && 0 == user_comparator_.CompareWithoutTimestamp(
|
||||
*iterate_upper_bound_, /*a_has_ts=*/false, key(),
|
||||
/*b_has_ts=*/false)) {
|
||||
ReleaseTempPinnedData();
|
||||
PrevInternal(nullptr);
|
||||
}
|
||||
|
||||
+9
-5
@@ -8,8 +8,9 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
@@ -21,7 +22,6 @@
|
||||
#include "util/autovector.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
class SystemClock_;
|
||||
class Version;
|
||||
|
||||
// This file declares the factory functions of DBIter, in its original form
|
||||
@@ -68,7 +68,7 @@ class DBIter final : public Iterator {
|
||||
// this->key().
|
||||
// (2) When moving backwards, the internal iterator is positioned
|
||||
// just before all entries whose user key == this->key().
|
||||
enum Direction { kForward, kReverse };
|
||||
enum Direction : uint8_t { kForward, kReverse };
|
||||
|
||||
// LocalStatistics contain Statistics counters that will be aggregated per
|
||||
// each iterator instance and then will be sent to the global statistics when
|
||||
@@ -185,6 +185,9 @@ class DBIter final : public Iterator {
|
||||
Slice timestamp() const override {
|
||||
assert(valid_);
|
||||
assert(timestamp_size_ > 0);
|
||||
if (direction_ == kReverse) {
|
||||
return saved_timestamp_;
|
||||
}
|
||||
const Slice ukey_and_ts = saved_key_.GetUserKey();
|
||||
assert(timestamp_size_ < ukey_and_ts.size());
|
||||
return ExtractTimestampFromUserKey(ukey_and_ts, timestamp_size_);
|
||||
@@ -232,7 +235,7 @@ class DBIter final : public Iterator {
|
||||
// If `skipping_saved_key` is true, the function will keep iterating until it
|
||||
// finds a user key that is larger than `saved_key_`.
|
||||
// If `prefix` is not null, the iterator needs to stop when all keys for the
|
||||
// prefix are exhausted and the interator is set to invalid.
|
||||
// prefix are exhausted and the iterator is set to invalid.
|
||||
bool FindNextUserEntry(bool skipping_saved_key, const Slice* prefix);
|
||||
// Internal implementation of FindNextUserEntry().
|
||||
bool FindNextUserEntryInternal(bool skipping_saved_key, const Slice* prefix);
|
||||
@@ -298,7 +301,7 @@ class DBIter final : public Iterator {
|
||||
|
||||
const SliceTransform* prefix_extractor_;
|
||||
Env* const env_;
|
||||
std::shared_ptr<SystemClock> clock_;
|
||||
SystemClock* clock_;
|
||||
Logger* logger_;
|
||||
UserComparatorWrapper user_comparator_;
|
||||
const MergeOperator* const merge_operator_;
|
||||
@@ -373,6 +376,7 @@ class DBIter final : public Iterator {
|
||||
const Slice* const timestamp_ub_;
|
||||
const Slice* const timestamp_lb_;
|
||||
const size_t timestamp_size_;
|
||||
std::string saved_timestamp_;
|
||||
};
|
||||
|
||||
// Return a new iterator that converts internal keys (yielded by
|
||||
|
||||
+44
-5
@@ -18,6 +18,7 @@
|
||||
#include "rocksdb/perf_context.h"
|
||||
#include "table/block_based/flush_block_policy.h"
|
||||
#include "util/random.h"
|
||||
#include "utilities/merge_operators/string_append/stringappend2.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -619,6 +620,40 @@ TEST_P(DBIteratorTest, IterReseek) {
|
||||
delete iter;
|
||||
}
|
||||
|
||||
TEST_F(DBIteratorTest, ReseekUponDirectionChange) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(1));
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
options.merge_operator.reset(
|
||||
new StringAppendTESTOperator(/*delim_char=*/' '));
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("foo", "value"));
|
||||
ASSERT_OK(Put("bar", "value"));
|
||||
{
|
||||
std::unique_ptr<Iterator> it(db_->NewIterator(ReadOptions()));
|
||||
it->SeekToLast();
|
||||
it->Prev();
|
||||
it->Next();
|
||||
}
|
||||
ASSERT_EQ(1,
|
||||
options.statistics->getTickerCount(NUMBER_OF_RESEEKS_IN_ITERATION));
|
||||
|
||||
const std::string merge_key("good");
|
||||
ASSERT_OK(Put(merge_key, "orig"));
|
||||
ASSERT_OK(Merge(merge_key, "suffix"));
|
||||
{
|
||||
std::unique_ptr<Iterator> it(db_->NewIterator(ReadOptions()));
|
||||
it->Seek(merge_key);
|
||||
ASSERT_TRUE(it->Valid());
|
||||
const uint64_t prev_reseek_count =
|
||||
options.statistics->getTickerCount(NUMBER_OF_RESEEKS_IN_ITERATION);
|
||||
it->Prev();
|
||||
ASSERT_EQ(prev_reseek_count + 1, options.statistics->getTickerCount(
|
||||
NUMBER_OF_RESEEKS_IN_ITERATION));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(DBIteratorTest, IterSmallAndLargeMix) {
|
||||
do {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
@@ -1518,6 +1553,10 @@ TEST_P(DBIteratorTestForPinnedData, PinnedDataIteratorRandomizedFlush) {
|
||||
PinnedDataIteratorRandomized(TestConfig::FLUSH_EVERY_1000);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBIteratorTestForPinnedDataInstance,
|
||||
DBIteratorTestForPinnedData,
|
||||
testing::Values(true, false));
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_P(DBIteratorTest, PinnedDataIteratorMultipleFiles) {
|
||||
Options options = CurrentOptions();
|
||||
@@ -2997,18 +3036,18 @@ TEST_P(DBIteratorTest, Blob) {
|
||||
iter->Prev();
|
||||
ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 5);
|
||||
iter->Next();
|
||||
ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 5);
|
||||
ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 6);
|
||||
ASSERT_EQ(IterStatus(iter), "b->vb3");
|
||||
|
||||
// Switch from forward to reverse
|
||||
iter->SeekToFirst();
|
||||
ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 5);
|
||||
iter->Next();
|
||||
ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 5);
|
||||
ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 6);
|
||||
iter->Next();
|
||||
ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 6);
|
||||
iter->Prev();
|
||||
iter->Next();
|
||||
ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 7);
|
||||
iter->Prev();
|
||||
ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 8);
|
||||
ASSERT_EQ(IterStatus(iter), "b->vb3");
|
||||
}
|
||||
|
||||
|
||||
@@ -1175,6 +1175,61 @@ class CountingDeleteTabPropCollectorFactory
|
||||
}
|
||||
};
|
||||
|
||||
class BlockCountingTablePropertiesCollector : public TablePropertiesCollector {
|
||||
public:
|
||||
static const std::string kNumSampledBlocksPropertyName;
|
||||
|
||||
const char* Name() const override {
|
||||
return "BlockCountingTablePropertiesCollector";
|
||||
}
|
||||
|
||||
Status Finish(UserCollectedProperties* properties) override {
|
||||
(*properties)[kNumSampledBlocksPropertyName] =
|
||||
ToString(num_sampled_blocks_);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status AddUserKey(const Slice& /*user_key*/, const Slice& /*value*/,
|
||||
EntryType /*type*/, SequenceNumber /*seq*/,
|
||||
uint64_t /*file_size*/) override {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void BlockAdd(uint64_t /* block_raw_bytes */,
|
||||
uint64_t block_compressed_bytes_fast,
|
||||
uint64_t block_compressed_bytes_slow) override {
|
||||
if (block_compressed_bytes_fast > 0 || block_compressed_bytes_slow > 0) {
|
||||
num_sampled_blocks_++;
|
||||
}
|
||||
}
|
||||
|
||||
UserCollectedProperties GetReadableProperties() const override {
|
||||
return UserCollectedProperties{
|
||||
{kNumSampledBlocksPropertyName, ToString(num_sampled_blocks_)},
|
||||
};
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t num_sampled_blocks_ = 0;
|
||||
};
|
||||
|
||||
const std::string
|
||||
BlockCountingTablePropertiesCollector::kNumSampledBlocksPropertyName =
|
||||
"NumSampledBlocks";
|
||||
|
||||
class BlockCountingTablePropertiesCollectorFactory
|
||||
: public TablePropertiesCollectorFactory {
|
||||
public:
|
||||
const char* Name() const override {
|
||||
return "BlockCountingTablePropertiesCollectorFactory";
|
||||
}
|
||||
|
||||
TablePropertiesCollector* CreateTablePropertiesCollector(
|
||||
TablePropertiesCollectorFactory::Context /* context */) override {
|
||||
return new BlockCountingTablePropertiesCollector();
|
||||
}
|
||||
};
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBPropertiesTest, GetUserDefinedTableProperties) {
|
||||
Options options = CurrentOptions();
|
||||
@@ -1413,6 +1468,132 @@ TEST_F(DBPropertiesTest, NeedCompactHintPersistentTest) {
|
||||
}
|
||||
}
|
||||
|
||||
// Excluded from RocksDB lite tests due to `GetPropertiesOfAllTables()` usage.
|
||||
TEST_F(DBPropertiesTest, BlockAddForCompressionSampling) {
|
||||
// Sampled compression requires at least one of the following four types.
|
||||
if (!Snappy_Supported() && !Zlib_Supported() && !LZ4_Supported() &&
|
||||
!ZSTD_Supported()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.table_properties_collector_factories.emplace_back(
|
||||
std::make_shared<BlockCountingTablePropertiesCollectorFactory>());
|
||||
|
||||
for (bool sample_for_compression : {false, true}) {
|
||||
// For simplicity/determinism, sample 100% when enabled, or 0% when disabled
|
||||
options.sample_for_compression = sample_for_compression ? 1 : 0;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Setup the following LSM:
|
||||
//
|
||||
// L0_0 ["a", "b"]
|
||||
// L1_0 ["a", "b"]
|
||||
//
|
||||
// L0_0 was created by flush. L1_0 was created by compaction. Each file
|
||||
// contains one data block.
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ASSERT_OK(Put("a", "val"));
|
||||
ASSERT_OK(Put("b", "val"));
|
||||
ASSERT_OK(Flush());
|
||||
if (i == 1) {
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
// A `BlockAdd()` should have been seen for files generated by flush or
|
||||
// compaction when `sample_for_compression` is enabled.
|
||||
TablePropertiesCollection file_to_props;
|
||||
ASSERT_OK(db_->GetPropertiesOfAllTables(&file_to_props));
|
||||
ASSERT_EQ(2, file_to_props.size());
|
||||
for (const auto& file_and_props : file_to_props) {
|
||||
auto& user_props = file_and_props.second->user_collected_properties;
|
||||
ASSERT_TRUE(user_props.find(BlockCountingTablePropertiesCollector::
|
||||
kNumSampledBlocksPropertyName) !=
|
||||
user_props.end());
|
||||
ASSERT_EQ(user_props.at(BlockCountingTablePropertiesCollector::
|
||||
kNumSampledBlocksPropertyName),
|
||||
ToString(sample_for_compression ? 1 : 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CompressionSamplingDBPropertiesTest
|
||||
: public DBPropertiesTest,
|
||||
public ::testing::WithParamInterface<bool> {
|
||||
public:
|
||||
CompressionSamplingDBPropertiesTest() : fast_(GetParam()) {}
|
||||
|
||||
protected:
|
||||
const bool fast_;
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(CompressionSamplingDBPropertiesTest,
|
||||
CompressionSamplingDBPropertiesTest, ::testing::Bool());
|
||||
|
||||
// Excluded from RocksDB lite tests due to `GetPropertiesOfAllTables()` usage.
|
||||
TEST_P(CompressionSamplingDBPropertiesTest,
|
||||
EstimateDataSizeWithCompressionSampling) {
|
||||
Options options = CurrentOptions();
|
||||
if (fast_) {
|
||||
// One of the following light compression libraries must be present.
|
||||
if (LZ4_Supported()) {
|
||||
options.compression = kLZ4Compression;
|
||||
} else if (Snappy_Supported()) {
|
||||
options.compression = kSnappyCompression;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// One of the following heavy compression libraries must be present.
|
||||
if (ZSTD_Supported()) {
|
||||
options.compression = kZSTD;
|
||||
} else if (Zlib_Supported()) {
|
||||
options.compression = kZlibCompression;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
options.disable_auto_compactions = true;
|
||||
// For simplicity/determinism, sample 100%.
|
||||
options.sample_for_compression = 1;
|
||||
Reopen(options);
|
||||
|
||||
// Setup the following LSM:
|
||||
//
|
||||
// L0_0 ["a", "b"]
|
||||
// L1_0 ["a", "b"]
|
||||
//
|
||||
// L0_0 was created by flush. L1_0 was created by compaction. Each file
|
||||
// contains one data block. The value consists of compressible data so the
|
||||
// data block should be stored compressed.
|
||||
std::string val(1024, 'a');
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ASSERT_OK(Put("a", val));
|
||||
ASSERT_OK(Put("b", val));
|
||||
ASSERT_OK(Flush());
|
||||
if (i == 1) {
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
TablePropertiesCollection file_to_props;
|
||||
ASSERT_OK(db_->GetPropertiesOfAllTables(&file_to_props));
|
||||
ASSERT_EQ(2, file_to_props.size());
|
||||
for (const auto& file_and_props : file_to_props) {
|
||||
ASSERT_GT(file_and_props.second->data_size, 0);
|
||||
if (fast_) {
|
||||
ASSERT_EQ(file_and_props.second->data_size,
|
||||
file_and_props.second->fast_compression_estimated_data_size);
|
||||
} else {
|
||||
ASSERT_EQ(file_and_props.second->data_size,
|
||||
file_and_props.second->slow_compression_estimated_data_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBPropertiesTest, EstimateNumKeysUnderflow) {
|
||||
Options options = CurrentOptions();
|
||||
Reopen(options);
|
||||
@@ -1445,6 +1626,7 @@ TEST_F(DBPropertiesTest, EstimateOldestKeyTime) {
|
||||
|
||||
options.compaction_style = kCompactionStyleFIFO;
|
||||
options.ttl = 300;
|
||||
options.max_open_files = -1;
|
||||
options.compaction_options_fifo.allow_compaction = false;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
|
||||
@@ -73,6 +73,15 @@ TEST_F(DBRangeDelTest, FlushOutputHasOnlyRangeTombstones) {
|
||||
} while (ChangeOptions(kRangeDelSkipConfigs));
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, DictionaryCompressionWithOnlyRangeTombstones) {
|
||||
Options opts = CurrentOptions();
|
||||
opts.compression_opts.max_dict_bytes = 16384;
|
||||
Reopen(opts);
|
||||
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "dr1",
|
||||
"dr2"));
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, CompactionOutputHasOnlyRangeTombstone) {
|
||||
do {
|
||||
Options opts = CurrentOptions();
|
||||
|
||||
@@ -147,6 +147,206 @@ TEST_F(DBSecondaryTest, ReopenAsSecondary) {
|
||||
ASSERT_EQ(2, count);
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, SimpleInternalCompaction) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
Reopen(options);
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ASSERT_OK(Put("foo", "foo_value" + std::to_string(i)));
|
||||
ASSERT_OK(Put("bar", "bar_value" + std::to_string(i)));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
CompactionServiceInput input;
|
||||
|
||||
ColumnFamilyMetaData meta;
|
||||
db_->GetColumnFamilyMetaData(&meta);
|
||||
for (auto& file : meta.levels[0].files) {
|
||||
ASSERT_EQ(0, meta.levels[0].level);
|
||||
input.input_files.push_back(file.name);
|
||||
}
|
||||
ASSERT_EQ(input.input_files.size(), 3);
|
||||
|
||||
input.output_level = 1;
|
||||
Close();
|
||||
|
||||
options.max_open_files = -1;
|
||||
OpenSecondary(options);
|
||||
auto cfh = db_secondary_->DefaultColumnFamily();
|
||||
|
||||
CompactionServiceResult result;
|
||||
ASSERT_OK(db_secondary_full()->TEST_CompactWithoutInstallation(cfh, input,
|
||||
&result));
|
||||
|
||||
ASSERT_EQ(result.output_files.size(), 1);
|
||||
InternalKey smallest, largest;
|
||||
smallest.DecodeFrom(result.output_files[0].smallest_internal_key);
|
||||
largest.DecodeFrom(result.output_files[0].largest_internal_key);
|
||||
ASSERT_EQ(smallest.user_key().ToString(), "bar");
|
||||
ASSERT_EQ(largest.user_key().ToString(), "foo");
|
||||
ASSERT_EQ(result.output_level, 1);
|
||||
ASSERT_EQ(result.output_path, this->secondary_path_);
|
||||
ASSERT_EQ(result.num_output_records, 2);
|
||||
ASSERT_GT(result.bytes_written, 0);
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, InternalCompactionMultiLevels) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.disable_auto_compactions = true;
|
||||
Reopen(options);
|
||||
const int kRangeL2 = 10;
|
||||
const int kRangeL1 = 30;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ASSERT_OK(Put(Key(i * kRangeL2), "value" + ToString(i)));
|
||||
ASSERT_OK(Put(Key((i + 1) * kRangeL2 - 1), "value" + ToString(i)));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
MoveFilesToLevel(2);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
ASSERT_OK(Put(Key(i * kRangeL1), "value" + ToString(i)));
|
||||
ASSERT_OK(Put(Key((i + 1) * kRangeL1 - 1), "value" + ToString(i)));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
MoveFilesToLevel(1);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
ASSERT_OK(Put(Key(i * 30), "value" + ToString(i)));
|
||||
ASSERT_OK(Put(Key(i * 30 + 50), "value" + ToString(i)));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
ColumnFamilyMetaData meta;
|
||||
db_->GetColumnFamilyMetaData(&meta);
|
||||
|
||||
// pick 2 files on level 0 for compaction, which has 3 overlap files on L1
|
||||
CompactionServiceInput input1;
|
||||
input1.input_files.push_back(meta.levels[0].files[2].name);
|
||||
input1.input_files.push_back(meta.levels[0].files[3].name);
|
||||
input1.input_files.push_back(meta.levels[1].files[0].name);
|
||||
input1.input_files.push_back(meta.levels[1].files[1].name);
|
||||
input1.input_files.push_back(meta.levels[1].files[2].name);
|
||||
|
||||
input1.output_level = 1;
|
||||
|
||||
options.max_open_files = -1;
|
||||
Close();
|
||||
|
||||
OpenSecondary(options);
|
||||
auto cfh = db_secondary_->DefaultColumnFamily();
|
||||
CompactionServiceResult result;
|
||||
ASSERT_OK(db_secondary_full()->TEST_CompactWithoutInstallation(cfh, input1,
|
||||
&result));
|
||||
|
||||
// pick 2 files on level 1 for compaction, which has 6 overlap files on L2
|
||||
CompactionServiceInput input2;
|
||||
input2.input_files.push_back(meta.levels[1].files[1].name);
|
||||
input2.input_files.push_back(meta.levels[1].files[2].name);
|
||||
for (int i = 3; i < 9; i++) {
|
||||
input2.input_files.push_back(meta.levels[2].files[i].name);
|
||||
}
|
||||
|
||||
input2.output_level = 2;
|
||||
ASSERT_OK(db_secondary_full()->TEST_CompactWithoutInstallation(cfh, input2,
|
||||
&result));
|
||||
|
||||
CloseSecondary();
|
||||
|
||||
// delete all l2 files, without update manifest
|
||||
for (auto& file : meta.levels[2].files) {
|
||||
ASSERT_OK(env_->DeleteFile(dbname_ + file.name));
|
||||
}
|
||||
OpenSecondary(options);
|
||||
cfh = db_secondary_->DefaultColumnFamily();
|
||||
Status s = db_secondary_full()->TEST_CompactWithoutInstallation(cfh, input2,
|
||||
&result);
|
||||
ASSERT_TRUE(s.IsInvalidArgument());
|
||||
|
||||
// TODO: L0 -> L1 compaction should success, currently version is not built
|
||||
// if files is missing.
|
||||
// ASSERT_OK(db_secondary_full()->TEST_CompactWithoutInstallation(cfh,
|
||||
// input1, &result));
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, InternalCompactionCompactedFiles) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
Reopen(options);
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ASSERT_OK(Put("foo", "foo_value" + std::to_string(i)));
|
||||
ASSERT_OK(Put("bar", "bar_value" + std::to_string(i)));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
CompactionServiceInput input;
|
||||
|
||||
ColumnFamilyMetaData meta;
|
||||
db_->GetColumnFamilyMetaData(&meta);
|
||||
for (auto& file : meta.levels[0].files) {
|
||||
ASSERT_EQ(0, meta.levels[0].level);
|
||||
input.input_files.push_back(file.name);
|
||||
}
|
||||
ASSERT_EQ(input.input_files.size(), 3);
|
||||
|
||||
input.output_level = 1;
|
||||
|
||||
// trigger compaction to delete the files for secondary instance compaction
|
||||
ASSERT_OK(Put("foo", "foo_value" + std::to_string(3)));
|
||||
ASSERT_OK(Put("bar", "bar_value" + std::to_string(3)));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
Close();
|
||||
|
||||
options.max_open_files = -1;
|
||||
OpenSecondary(options);
|
||||
auto cfh = db_secondary_->DefaultColumnFamily();
|
||||
|
||||
CompactionServiceResult result;
|
||||
Status s =
|
||||
db_secondary_full()->TEST_CompactWithoutInstallation(cfh, input, &result);
|
||||
ASSERT_TRUE(s.IsInvalidArgument());
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, InternalCompactionMissingFiles) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
Reopen(options);
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ASSERT_OK(Put("foo", "foo_value" + std::to_string(i)));
|
||||
ASSERT_OK(Put("bar", "bar_value" + std::to_string(i)));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
CompactionServiceInput input;
|
||||
|
||||
ColumnFamilyMetaData meta;
|
||||
db_->GetColumnFamilyMetaData(&meta);
|
||||
for (auto& file : meta.levels[0].files) {
|
||||
ASSERT_EQ(0, meta.levels[0].level);
|
||||
input.input_files.push_back(file.name);
|
||||
}
|
||||
ASSERT_EQ(input.input_files.size(), 3);
|
||||
|
||||
input.output_level = 1;
|
||||
|
||||
Close();
|
||||
|
||||
ASSERT_OK(env_->DeleteFile(dbname_ + input.input_files[0]));
|
||||
|
||||
options.max_open_files = -1;
|
||||
OpenSecondary(options);
|
||||
auto cfh = db_secondary_->DefaultColumnFamily();
|
||||
|
||||
CompactionServiceResult result;
|
||||
Status s =
|
||||
db_secondary_full()->TEST_CompactWithoutInstallation(cfh, input, &result);
|
||||
ASSERT_TRUE(s.IsInvalidArgument());
|
||||
|
||||
input.input_files.erase(input.input_files.begin());
|
||||
|
||||
ASSERT_OK(db_secondary_full()->TEST_CompactWithoutInstallation(cfh, input,
|
||||
&result));
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, OpenAsSecondary) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
@@ -459,20 +659,6 @@ TEST_F(DBSecondaryTest, MissingTableFileDuringOpen) {
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, MissingTableFile) {
|
||||
int table_files_not_exist = 0;
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"ReactiveVersionSet::ApplyOneVersionEditToBuilder:AfterLoadTableHandlers",
|
||||
[&](void* arg) {
|
||||
Status s = *reinterpret_cast<Status*>(arg);
|
||||
if (s.IsPathNotFound()) {
|
||||
++table_files_not_exist;
|
||||
} else if (!s.ok()) {
|
||||
assert(false); // Should not reach here
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
@@ -499,7 +685,6 @@ TEST_F(DBSecondaryTest, MissingTableFile) {
|
||||
ASSERT_NOK(db_secondary_->Get(ropts, "bar", &value));
|
||||
|
||||
ASSERT_OK(db_secondary_->TryCatchUpWithPrimary());
|
||||
ASSERT_EQ(options.level0_file_num_compaction_trigger, table_files_not_exist);
|
||||
ASSERT_OK(db_secondary_->Get(ropts, "foo", &value));
|
||||
ASSERT_EQ("foo_value" +
|
||||
std::to_string(options.level0_file_num_compaction_trigger - 1),
|
||||
@@ -615,10 +800,7 @@ TEST_F(DBSecondaryTest, SwitchManifest) {
|
||||
range_scan_db();
|
||||
}
|
||||
|
||||
// Here, "Snapshot" refers to the version edits written by
|
||||
// VersionSet::WriteSnapshot() at the beginning of the new MANIFEST after
|
||||
// switching from the old one.
|
||||
TEST_F(DBSecondaryTest, SkipSnapshotAfterManifestSwitch) {
|
||||
TEST_F(DBSecondaryTest, SwitchManifestTwice) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.disable_auto_compactions = true;
|
||||
@@ -640,10 +822,15 @@ TEST_F(DBSecondaryTest, SkipSnapshotAfterManifestSwitch) {
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_OK(dbfull()->SetOptions({{"disable_auto_compactions", "false"}}));
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("0", "value1"));
|
||||
ASSERT_OK(db_secondary_->TryCatchUpWithPrimary());
|
||||
|
||||
ASSERT_OK(db_secondary_->Get(ropts, "0", &value));
|
||||
ASSERT_EQ("value1", value);
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, SwitchWAL) {
|
||||
TEST_F(DBSecondaryTest, DISABLED_SwitchWAL) {
|
||||
const int kNumKeysPerMemtable = 1;
|
||||
Options options;
|
||||
options.env = env_;
|
||||
@@ -692,7 +879,7 @@ TEST_F(DBSecondaryTest, SwitchWAL) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, SwitchWALMultiColumnFamilies) {
|
||||
TEST_F(DBSecondaryTest, DISABLED_SwitchWALMultiColumnFamilies) {
|
||||
const int kNumKeysPerMemtable = 1;
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
+327
-8
@@ -306,13 +306,13 @@ TEST_F(DBSSTTest, DBWithSstFileManager) {
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
// Verify that we are tracking all sst files in dbname_
|
||||
std::unordered_map<std::string, uint64_t> files_in_db;
|
||||
ASSERT_OK(GetAllSSTFiles(&files_in_db));
|
||||
ASSERT_OK(GetAllDataFiles(kTableFile, &files_in_db));
|
||||
ASSERT_EQ(sfm->GetTrackedFiles(), files_in_db);
|
||||
}
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
|
||||
std::unordered_map<std::string, uint64_t> files_in_db;
|
||||
ASSERT_OK(GetAllSSTFiles(&files_in_db));
|
||||
ASSERT_OK(GetAllDataFiles(kTableFile, &files_in_db));
|
||||
// Verify that we are tracking all sst files in dbname_
|
||||
ASSERT_EQ(sfm->GetTrackedFiles(), files_in_db);
|
||||
// Verify the total files size
|
||||
@@ -346,6 +346,262 @@ TEST_F(DBSSTTest, DBWithSstFileManager) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, DBWithSstFileManagerForBlobFiles) {
|
||||
std::shared_ptr<SstFileManager> sst_file_manager(NewSstFileManager(env_));
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(sst_file_manager.get());
|
||||
|
||||
int files_added = 0;
|
||||
int files_deleted = 0;
|
||||
int files_moved = 0;
|
||||
int files_scheduled_to_delete = 0;
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"SstFileManagerImpl::OnAddFile", [&](void* arg) {
|
||||
const std::string* const file_path =
|
||||
static_cast<const std::string*>(arg);
|
||||
if (file_path->find(".blob") != std::string::npos) {
|
||||
files_added++;
|
||||
}
|
||||
});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"SstFileManagerImpl::OnDeleteFile", [&](void* arg) {
|
||||
const std::string* const file_path =
|
||||
static_cast<const std::string*>(arg);
|
||||
if (file_path->find(".blob") != std::string::npos) {
|
||||
files_deleted++;
|
||||
}
|
||||
});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"SstFileManagerImpl::ScheduleFileDeletion", [&](void* arg) {
|
||||
assert(arg);
|
||||
const std::string* const file_path =
|
||||
static_cast<const std::string*>(arg);
|
||||
if (file_path->find(".blob") != std::string::npos) {
|
||||
++files_scheduled_to_delete;
|
||||
}
|
||||
});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"SstFileManagerImpl::OnMoveFile", [&](void* /*arg*/) { files_moved++; });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.sst_file_manager = sst_file_manager;
|
||||
options.enable_blob_files = true;
|
||||
options.blob_file_size = 32; // create one blob per file
|
||||
DestroyAndReopen(options);
|
||||
Random rnd(301);
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ASSERT_OK(Put("Key_" + std::to_string(i), "Value_" + std::to_string(i)));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
// Verify that we are tracking all sst and blob files in dbname_
|
||||
std::unordered_map<std::string, uint64_t> files_in_db;
|
||||
ASSERT_OK(GetAllDataFiles(kTableFile, &files_in_db));
|
||||
ASSERT_OK(GetAllDataFiles(kBlobFile, &files_in_db));
|
||||
ASSERT_EQ(sfm->GetTrackedFiles(), files_in_db);
|
||||
}
|
||||
|
||||
std::vector<uint64_t> blob_files = GetBlobFileNumbers();
|
||||
ASSERT_EQ(files_added, blob_files.size());
|
||||
// No blob file is obsoleted.
|
||||
ASSERT_EQ(files_deleted, 0);
|
||||
ASSERT_EQ(files_scheduled_to_delete, 0);
|
||||
// No files were moved.
|
||||
ASSERT_EQ(files_moved, 0);
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
|
||||
std::unordered_map<std::string, uint64_t> files_in_db;
|
||||
ASSERT_OK(GetAllDataFiles(kTableFile, &files_in_db));
|
||||
ASSERT_OK(GetAllDataFiles(kBlobFile, &files_in_db));
|
||||
|
||||
// Verify that we are tracking all sst and blob files in dbname_
|
||||
ASSERT_EQ(sfm->GetTrackedFiles(), files_in_db);
|
||||
// Verify the total files size
|
||||
uint64_t total_files_size = 0;
|
||||
for (auto& file_to_size : files_in_db) {
|
||||
total_files_size += file_to_size.second;
|
||||
}
|
||||
ASSERT_EQ(sfm->GetTotalSize(), total_files_size);
|
||||
Close();
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_EQ(sfm->GetTrackedFiles(), files_in_db);
|
||||
ASSERT_EQ(sfm->GetTotalSize(), total_files_size);
|
||||
|
||||
// Verify that we track all the files again after the DB is closed and opened.
|
||||
Close();
|
||||
|
||||
sst_file_manager.reset(NewSstFileManager(env_));
|
||||
options.sst_file_manager = sst_file_manager;
|
||||
sfm = static_cast<SstFileManagerImpl*>(sst_file_manager.get());
|
||||
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_EQ(sfm->GetTrackedFiles(), files_in_db);
|
||||
ASSERT_EQ(sfm->GetTotalSize(), total_files_size);
|
||||
|
||||
// Destroy DB and it will remove all the blob files from sst file manager and
|
||||
// blob files deletion will go through ScheduleFileDeletion.
|
||||
ASSERT_EQ(files_deleted, 0);
|
||||
ASSERT_EQ(files_scheduled_to_delete, 0);
|
||||
Close();
|
||||
ASSERT_OK(DestroyDB(dbname_, options));
|
||||
ASSERT_EQ(files_deleted, blob_files.size());
|
||||
ASSERT_EQ(files_scheduled_to_delete, blob_files.size());
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, DBWithSstFileManagerForBlobFilesWithGC) {
|
||||
std::shared_ptr<SstFileManager> sst_file_manager(NewSstFileManager(env_));
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(sst_file_manager.get());
|
||||
Options options = CurrentOptions();
|
||||
options.sst_file_manager = sst_file_manager;
|
||||
options.enable_blob_files = true;
|
||||
options.blob_file_size = 32; // create one blob per file
|
||||
options.disable_auto_compactions = true;
|
||||
options.enable_blob_garbage_collection = true;
|
||||
options.blob_garbage_collection_age_cutoff = 0.5;
|
||||
|
||||
int files_added = 0;
|
||||
int files_deleted = 0;
|
||||
int files_moved = 0;
|
||||
int files_scheduled_to_delete = 0;
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"SstFileManagerImpl::OnAddFile", [&](void* arg) {
|
||||
const std::string* const file_path =
|
||||
static_cast<const std::string*>(arg);
|
||||
if (file_path->find(".blob") != std::string::npos) {
|
||||
files_added++;
|
||||
}
|
||||
});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"SstFileManagerImpl::OnDeleteFile", [&](void* arg) {
|
||||
const std::string* const file_path =
|
||||
static_cast<const std::string*>(arg);
|
||||
if (file_path->find(".blob") != std::string::npos) {
|
||||
files_deleted++;
|
||||
}
|
||||
});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"SstFileManagerImpl::ScheduleFileDeletion", [&](void* arg) {
|
||||
assert(arg);
|
||||
const std::string* const file_path =
|
||||
static_cast<const std::string*>(arg);
|
||||
if (file_path->find(".blob") != std::string::npos) {
|
||||
++files_scheduled_to_delete;
|
||||
}
|
||||
});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"SstFileManagerImpl::OnMoveFile", [&](void* /*arg*/) { files_moved++; });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
DestroyAndReopen(options);
|
||||
Random rnd(301);
|
||||
|
||||
constexpr char first_key[] = "first_key";
|
||||
constexpr char first_value[] = "first_value";
|
||||
constexpr char second_key[] = "second_key";
|
||||
constexpr char second_value[] = "second_value";
|
||||
|
||||
ASSERT_OK(Put(first_key, first_value));
|
||||
ASSERT_OK(Put(second_key, second_value));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
constexpr char third_key[] = "third_key";
|
||||
constexpr char third_value[] = "third_value";
|
||||
constexpr char fourth_key[] = "fourth_key";
|
||||
constexpr char fourth_value[] = "fourth_value";
|
||||
constexpr char fifth_key[] = "fifth_key";
|
||||
constexpr char fifth_value[] = "fifth_value";
|
||||
|
||||
ASSERT_OK(Put(third_key, third_value));
|
||||
ASSERT_OK(Put(fourth_key, fourth_value));
|
||||
ASSERT_OK(Put(fifth_key, fifth_value));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
const std::vector<uint64_t> original_blob_files = GetBlobFileNumbers();
|
||||
|
||||
ASSERT_EQ(original_blob_files.size(), 5);
|
||||
ASSERT_EQ(files_added, 5);
|
||||
ASSERT_EQ(files_deleted, 0);
|
||||
ASSERT_EQ(files_scheduled_to_delete, 0);
|
||||
ASSERT_EQ(files_moved, 0);
|
||||
{
|
||||
// Verify that we are tracking all sst and blob files in dbname_
|
||||
std::unordered_map<std::string, uint64_t> files_in_db;
|
||||
ASSERT_OK(GetAllDataFiles(kTableFile, &files_in_db));
|
||||
ASSERT_OK(GetAllDataFiles(kBlobFile, &files_in_db));
|
||||
ASSERT_EQ(sfm->GetTrackedFiles(), files_in_db);
|
||||
}
|
||||
|
||||
const size_t cutoff_index = static_cast<size_t>(
|
||||
options.blob_garbage_collection_age_cutoff * original_blob_files.size());
|
||||
|
||||
size_t expected_number_of_files = original_blob_files.size();
|
||||
// Note: turning off enable_blob_files before the compaction results in
|
||||
// garbage collected values getting inlined.
|
||||
ASSERT_OK(db_->SetOptions({{"enable_blob_files", "false"}}));
|
||||
expected_number_of_files -= cutoff_index;
|
||||
files_added = 0;
|
||||
|
||||
constexpr Slice* begin = nullptr;
|
||||
constexpr Slice* end = nullptr;
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), begin, end));
|
||||
sfm->WaitForEmptyTrash();
|
||||
|
||||
ASSERT_EQ(Get(first_key), first_value);
|
||||
ASSERT_EQ(Get(second_key), second_value);
|
||||
ASSERT_EQ(Get(third_key), third_value);
|
||||
ASSERT_EQ(Get(fourth_key), fourth_value);
|
||||
ASSERT_EQ(Get(fifth_key), fifth_value);
|
||||
|
||||
const std::vector<uint64_t> new_blob_files = GetBlobFileNumbers();
|
||||
|
||||
ASSERT_EQ(new_blob_files.size(), expected_number_of_files);
|
||||
// No new file is added.
|
||||
ASSERT_EQ(files_added, 0);
|
||||
ASSERT_EQ(files_deleted, cutoff_index);
|
||||
ASSERT_EQ(files_scheduled_to_delete, cutoff_index);
|
||||
ASSERT_EQ(files_moved, 0);
|
||||
|
||||
// Original blob files below the cutoff should be gone, original blob files at
|
||||
// or above the cutoff should be still there
|
||||
for (size_t i = cutoff_index; i < original_blob_files.size(); ++i) {
|
||||
ASSERT_EQ(new_blob_files[i - cutoff_index], original_blob_files[i]);
|
||||
}
|
||||
|
||||
{
|
||||
// Verify that we are tracking all sst and blob files in dbname_
|
||||
std::unordered_map<std::string, uint64_t> files_in_db;
|
||||
ASSERT_OK(GetAllDataFiles(kTableFile, &files_in_db));
|
||||
ASSERT_OK(GetAllDataFiles(kBlobFile, &files_in_db));
|
||||
ASSERT_EQ(sfm->GetTrackedFiles(), files_in_db);
|
||||
}
|
||||
|
||||
Close();
|
||||
ASSERT_OK(DestroyDB(dbname_, options));
|
||||
sfm->WaitForEmptyTrash();
|
||||
ASSERT_EQ(files_deleted, 5);
|
||||
ASSERT_EQ(files_scheduled_to_delete, 5);
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, RateLimitedDelete) {
|
||||
Destroy(last_options_);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({
|
||||
@@ -497,10 +753,11 @@ TEST_F(DBSSTTest, RateLimitedWALDelete) {
|
||||
}
|
||||
|
||||
class DBWALTestWithParam
|
||||
: public DBSSTTest,
|
||||
: public DBTestBase,
|
||||
public testing::WithParamInterface<std::tuple<std::string, bool>> {
|
||||
public:
|
||||
DBWALTestWithParam() {
|
||||
explicit DBWALTestWithParam()
|
||||
: DBTestBase("/db_wal_test_with_params", /*env_do_fsync=*/true) {
|
||||
wal_dir_ = std::get<0>(GetParam());
|
||||
wal_dir_same_as_dbname_ = std::get<1>(GetParam());
|
||||
}
|
||||
@@ -780,7 +1037,7 @@ TEST_F(DBSSTTest, DBWithMaxSpaceAllowed) {
|
||||
|
||||
uint64_t first_file_size = 0;
|
||||
std::unordered_map<std::string, uint64_t> files_in_db;
|
||||
ASSERT_OK(GetAllSSTFiles(&files_in_db, &first_file_size));
|
||||
ASSERT_OK(GetAllDataFiles(kTableFile, &files_in_db, &first_file_size));
|
||||
ASSERT_EQ(sfm->GetTotalSize(), first_file_size);
|
||||
|
||||
// Set the maximum allowed space usage to the current total size
|
||||
@@ -791,6 +1048,68 @@ TEST_F(DBSSTTest, DBWithMaxSpaceAllowed) {
|
||||
ASSERT_NOK(Flush());
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, DBWithMaxSpaceAllowedWithBlobFiles) {
|
||||
std::shared_ptr<SstFileManager> sst_file_manager(NewSstFileManager(env_));
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(sst_file_manager.get());
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.sst_file_manager = sst_file_manager;
|
||||
options.disable_auto_compactions = true;
|
||||
options.enable_blob_files = true;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
|
||||
// Generate a file containing keys.
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ASSERT_OK(Put(Key(i), rnd.RandomString(50)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
uint64_t files_size = 0;
|
||||
uint64_t total_files_size = 0;
|
||||
std::unordered_map<std::string, uint64_t> files_in_db;
|
||||
|
||||
ASSERT_OK(GetAllDataFiles(kBlobFile, &files_in_db, &files_size));
|
||||
// Make sure blob files are considered by SSTFileManage in size limits.
|
||||
ASSERT_GT(files_size, 0);
|
||||
total_files_size = files_size;
|
||||
ASSERT_OK(GetAllDataFiles(kTableFile, &files_in_db, &files_size));
|
||||
total_files_size += files_size;
|
||||
ASSERT_EQ(sfm->GetTotalSize(), total_files_size);
|
||||
|
||||
// Set the maximum allowed space usage to the current total size.
|
||||
sfm->SetMaxAllowedSpaceUsage(files_size + 1);
|
||||
|
||||
bool max_allowed_space_reached = false;
|
||||
bool delete_blob_file = false;
|
||||
// Sync point called after blob file is closed and max allowed space is
|
||||
// checked.
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlobFileCompletionCallback::CallBack::MaxAllowedSpaceReached",
|
||||
[&](void* /*arg*/) { max_allowed_space_reached = true; });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"BuildTable::AfterDeleteFile",
|
||||
[&](void* /*arg*/) { delete_blob_file = true; });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({
|
||||
{
|
||||
"BuildTable::AfterDeleteFile",
|
||||
"DBSSTTest::DBWithMaxSpaceAllowedWithBlobFiles:1",
|
||||
},
|
||||
});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(Put("key1", "val1"));
|
||||
// This flush will fail
|
||||
ASSERT_NOK(Flush());
|
||||
ASSERT_TRUE(max_allowed_space_reached);
|
||||
|
||||
TEST_SYNC_POINT("DBSSTTest::DBWithMaxSpaceAllowedWithBlobFiles:1");
|
||||
ASSERT_TRUE(delete_blob_file);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, CancellingCompactionsWorks) {
|
||||
std::shared_ptr<SstFileManager> sst_file_manager(NewSstFileManager(env_));
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(sst_file_manager.get());
|
||||
@@ -821,7 +1140,7 @@ TEST_F(DBSSTTest, CancellingCompactionsWorks) {
|
||||
ASSERT_OK(Flush());
|
||||
uint64_t total_file_size = 0;
|
||||
std::unordered_map<std::string, uint64_t> files_in_db;
|
||||
ASSERT_OK(GetAllSSTFiles(&files_in_db, &total_file_size));
|
||||
ASSERT_OK(GetAllDataFiles(kTableFile, &files_in_db, &total_file_size));
|
||||
// Set the maximum allowed space usage to the current total size
|
||||
sfm->SetMaxAllowedSpaceUsage(2 * total_file_size + 1);
|
||||
|
||||
@@ -869,7 +1188,7 @@ TEST_F(DBSSTTest, CancellingManualCompactionsWorks) {
|
||||
ASSERT_OK(Flush());
|
||||
uint64_t total_file_size = 0;
|
||||
std::unordered_map<std::string, uint64_t> files_in_db;
|
||||
ASSERT_OK(GetAllSSTFiles(&files_in_db, &total_file_size));
|
||||
ASSERT_OK(GetAllDataFiles(kTableFile, &files_in_db, &total_file_size));
|
||||
// Set the maximum allowed space usage to the current total size
|
||||
sfm->SetMaxAllowedSpaceUsage(2 * total_file_size + 1);
|
||||
|
||||
@@ -986,7 +1305,7 @@ TEST_F(DBSSTTest, DBWithMaxSpaceAllowedRandomized) {
|
||||
ASSERT_TRUE(bg_error_set);
|
||||
uint64_t total_sst_files_size = 0;
|
||||
std::unordered_map<std::string, uint64_t> files_in_db;
|
||||
ASSERT_OK(GetAllSSTFiles(&files_in_db, &total_sst_files_size));
|
||||
ASSERT_OK(GetAllDataFiles(kTableFile, &files_in_db, &total_sst_files_size));
|
||||
ASSERT_GE(total_sst_files_size, limit_mb * 1024 * 1024);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
+34
-17
@@ -247,17 +247,21 @@ TEST_F(DBTest, SkipDelay) {
|
||||
wo.sync = sync;
|
||||
wo.disableWAL = disableWAL;
|
||||
wo.no_slowdown = true;
|
||||
dbfull()->Put(wo, "foo", "bar");
|
||||
// Large enough to exceed allowance for one time interval
|
||||
std::string large_value(1024, 'x');
|
||||
// Perhaps ideally this first write would fail because of delay, but
|
||||
// the current implementation does not guarantee that.
|
||||
dbfull()->Put(wo, "foo", large_value).PermitUncheckedError();
|
||||
// We need the 2nd write to trigger delay. This is because delay is
|
||||
// estimated based on the last write size which is 0 for the first write.
|
||||
ASSERT_NOK(dbfull()->Put(wo, "foo2", "bar2"));
|
||||
ASSERT_NOK(dbfull()->Put(wo, "foo2", large_value));
|
||||
ASSERT_GE(sleep_count.load(), 0);
|
||||
ASSERT_GE(wait_count.load(), 0);
|
||||
token.reset();
|
||||
|
||||
token = dbfull()->TEST_write_controler().GetDelayToken(1000000000);
|
||||
token = dbfull()->TEST_write_controler().GetDelayToken(1000000);
|
||||
wo.no_slowdown = false;
|
||||
ASSERT_OK(dbfull()->Put(wo, "foo3", "bar3"));
|
||||
ASSERT_OK(dbfull()->Put(wo, "foo3", large_value));
|
||||
ASSERT_GE(sleep_count.load(), 1);
|
||||
token.reset();
|
||||
}
|
||||
@@ -904,6 +908,9 @@ TEST_F(DBTest, FlushSchedule) {
|
||||
static_cast<int64_t>(options.write_buffer_size);
|
||||
options.max_write_buffer_number = 2;
|
||||
options.write_buffer_size = 120 * 1024;
|
||||
auto flush_listener = std::make_shared<FlushCounterListener>();
|
||||
flush_listener->expected_flush_reason = FlushReason::kWriteBufferFull;
|
||||
options.listeners.push_back(flush_listener);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
std::vector<port::Thread> threads;
|
||||
|
||||
@@ -2332,6 +2339,13 @@ TEST_F(DBTest, ReadonlyDBGetLiveManifestSize) {
|
||||
}
|
||||
|
||||
TEST_F(DBTest, GetLiveBlobFiles) {
|
||||
// Note: the following prevents an otherwise harmless data race between the
|
||||
// test setup code (AddBlobFile) below and the periodic stat dumping thread.
|
||||
Options options = CurrentOptions();
|
||||
options.stats_dump_period_sec = 0;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
VersionSet* const versions = dbfull()->TEST_GetVersionSet();
|
||||
assert(versions);
|
||||
assert(versions->GetColumnFamilySet());
|
||||
@@ -3493,17 +3507,21 @@ TEST_F(DBTest, FIFOCompactionStyleWithCompactionAndDelete) {
|
||||
}
|
||||
|
||||
// Check that FIFO-with-TTL is not supported with max_open_files != -1.
|
||||
// Github issue #8014
|
||||
TEST_F(DBTest, FIFOCompactionWithTTLAndMaxOpenFilesTest) {
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleFIFO;
|
||||
options.create_if_missing = true;
|
||||
options.ttl = 600; // seconds
|
||||
|
||||
// TTL is now supported with max_open_files != -1.
|
||||
options.max_open_files = 100;
|
||||
options = CurrentOptions(options);
|
||||
ASSERT_OK(TryReopen(options));
|
||||
// TTL is not supported with max_open_files != -1.
|
||||
options.max_open_files = 0;
|
||||
ASSERT_TRUE(TryReopen(options).IsNotSupported());
|
||||
|
||||
options.max_open_files = 100;
|
||||
ASSERT_TRUE(TryReopen(options).IsNotSupported());
|
||||
|
||||
// TTL is supported with unlimited max_open_files
|
||||
options.max_open_files = -1;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
}
|
||||
@@ -6683,20 +6701,19 @@ TEST_F(DBTest, MemoryUsageWithMaxWriteBufferSizeToMaintain) {
|
||||
Reopen(options);
|
||||
Random rnd(301);
|
||||
bool memory_limit_exceeded = false;
|
||||
uint64_t size_all_mem_table = 0;
|
||||
uint64_t cur_active_mem = 0;
|
||||
|
||||
ColumnFamilyData* cfd =
|
||||
static_cast<ColumnFamilyHandleImpl*>(db_->DefaultColumnFamily())->cfd();
|
||||
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
std::string value = rnd.RandomString(1000);
|
||||
ASSERT_OK(Put("keykey_" + std::to_string(i), value));
|
||||
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
|
||||
ASSERT_TRUE(db_->GetIntProperty(db_->DefaultColumnFamily(),
|
||||
DB::Properties::kSizeAllMemTables,
|
||||
&size_all_mem_table));
|
||||
ASSERT_TRUE(db_->GetIntProperty(db_->DefaultColumnFamily(),
|
||||
DB::Properties::kCurSizeActiveMemTable,
|
||||
&cur_active_mem));
|
||||
const uint64_t cur_active_mem = cfd->mem()->ApproximateMemoryUsage();
|
||||
const uint64_t size_all_mem_table =
|
||||
cur_active_mem + cfd->imm()->ApproximateMemoryUsage();
|
||||
|
||||
// Errors out if memory usage keeps on increasing beyond the limit.
|
||||
// Once memory limit exceeds, memory_limit_exceeded is set and if
|
||||
|
||||
+149
-10
@@ -154,8 +154,14 @@ class PartitionedIndexTestListener : public EventListener {
|
||||
};
|
||||
|
||||
TEST_F(DBTest2, PartitionedIndexUserToInternalKey) {
|
||||
const int kValueSize = 10500;
|
||||
const int kNumEntriesPerFile = 1000;
|
||||
const int kNumFiles = 3;
|
||||
const int kNumDistinctKeys = 30;
|
||||
|
||||
BlockBasedTableOptions table_options;
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
table_options.index_type = BlockBasedTableOptions::kTwoLevelIndexSearch;
|
||||
PartitionedIndexTestListener* listener = new PartitionedIndexTestListener();
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
@@ -164,14 +170,15 @@ TEST_F(DBTest2, PartitionedIndexUserToInternalKey) {
|
||||
Reopen(options);
|
||||
Random rnd(301);
|
||||
|
||||
for (int i = 0; i < 3000; i++) {
|
||||
int j = i % 30;
|
||||
std::string value = rnd.RandomString(10500);
|
||||
ASSERT_OK(Put("keykey_" + std::to_string(j), value));
|
||||
snapshots.push_back(db_->GetSnapshot());
|
||||
for (int i = 0; i < kNumFiles; i++) {
|
||||
for (int j = 0; j < kNumEntriesPerFile; j++) {
|
||||
int key_id = (i * kNumEntriesPerFile + j) % kNumDistinctKeys;
|
||||
std::string value = rnd.RandomString(kValueSize);
|
||||
ASSERT_OK(Put("keykey_" + std::to_string(key_id), value));
|
||||
snapshots.push_back(db_->GetSnapshot());
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
|
||||
for (auto s : snapshots) {
|
||||
db_->ReleaseSnapshot(s);
|
||||
@@ -337,6 +344,10 @@ class DBTestSharedWriteBufferAcrossCFs
|
||||
TEST_P(DBTestSharedWriteBufferAcrossCFs, SharedWriteBufferAcrossCFs) {
|
||||
Options options = CurrentOptions();
|
||||
options.arena_block_size = 4096;
|
||||
auto flush_listener = std::make_shared<FlushCounterListener>();
|
||||
options.listeners.push_back(flush_listener);
|
||||
// Don't trip the listener at shutdown.
|
||||
options.avoid_flush_during_shutdown = true;
|
||||
|
||||
// Avoid undeterministic value by malloc_usable_size();
|
||||
// Force arena block size to 1
|
||||
@@ -380,6 +391,7 @@ TEST_P(DBTestSharedWriteBufferAcrossCFs, SharedWriteBufferAcrossCFs) {
|
||||
|
||||
// Create some data and flush "default" and "nikitich" so that they
|
||||
// are newer CFs created.
|
||||
flush_listener->expected_flush_reason = FlushReason::kManualFlush;
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(1), wo));
|
||||
Flush(3);
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(1), wo));
|
||||
@@ -390,6 +402,7 @@ TEST_P(DBTestSharedWriteBufferAcrossCFs, SharedWriteBufferAcrossCFs) {
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
|
||||
static_cast<uint64_t>(1));
|
||||
|
||||
flush_listener->expected_flush_reason = FlushReason::kWriteBufferManager;
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(30000), wo));
|
||||
if (cost_cache_) {
|
||||
ASSERT_GE(cache->GetUsage(), 256 * 1024);
|
||||
@@ -514,6 +527,10 @@ TEST_F(DBTest2, SharedWriteBufferLimitAcrossDB) {
|
||||
std::string dbname2 = test::PerThreadDBPath("db_shared_wb_db2");
|
||||
Options options = CurrentOptions();
|
||||
options.arena_block_size = 4096;
|
||||
auto flush_listener = std::make_shared<FlushCounterListener>();
|
||||
options.listeners.push_back(flush_listener);
|
||||
// Don't trip the listener at shutdown.
|
||||
options.avoid_flush_during_shutdown = true;
|
||||
// Avoid undeterministic value by malloc_usable_size();
|
||||
// Force arena block size to 1
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
@@ -551,6 +568,7 @@ TEST_F(DBTest2, SharedWriteBufferLimitAcrossDB) {
|
||||
};
|
||||
|
||||
// Trigger a flush on cf2
|
||||
flush_listener->expected_flush_reason = FlushReason::kWriteBufferManager;
|
||||
ASSERT_OK(Put(2, Key(1), DummyString(70000), wo));
|
||||
wait_flush();
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(20000), wo));
|
||||
@@ -4109,7 +4127,7 @@ TEST_F(DBTest2, TraceWithFilter) {
|
||||
|
||||
// Open another db, replay, and verify the data
|
||||
std::string value;
|
||||
std::string dbname2 = test::TmpDir(env_) + "/db_replay";
|
||||
std::string dbname2 = test::PerThreadDBPath(env_, "db_replay");
|
||||
ASSERT_OK(DestroyDB(dbname2, options));
|
||||
|
||||
// Using a different name than db2, to pacify infer's use-after-lifetime
|
||||
@@ -4160,7 +4178,7 @@ TEST_F(DBTest2, TraceWithFilter) {
|
||||
ASSERT_OK(DestroyDB(dbname2, options));
|
||||
|
||||
// Set up a new db.
|
||||
std::string dbname3 = test::TmpDir(env_) + "/db_not_trace_read";
|
||||
std::string dbname3 = test::PerThreadDBPath(env_, "db_not_trace_read");
|
||||
ASSERT_OK(DestroyDB(dbname3, options));
|
||||
|
||||
DB* db3_init = nullptr;
|
||||
@@ -4577,7 +4595,7 @@ TEST_F(DBTest2, MultiDBParallelOpenTest) {
|
||||
Options options = CurrentOptions();
|
||||
std::vector<std::string> dbnames;
|
||||
for (int i = 0; i < kNumDbs; ++i) {
|
||||
dbnames.emplace_back(test::TmpDir(env_) + "/db" + ToString(i));
|
||||
dbnames.emplace_back(test::PerThreadDBPath(env_, "db" + ToString(i)));
|
||||
ASSERT_OK(DestroyDB(dbnames.back(), options));
|
||||
}
|
||||
|
||||
@@ -5421,6 +5439,98 @@ TEST_F(DBTest2, AutoPrefixMode1) {
|
||||
ASSERT_EQ("a1", iterator->key().ToString());
|
||||
}
|
||||
}
|
||||
|
||||
class RenameCurrentTest : public DBTestBase,
|
||||
public testing::WithParamInterface<std::string> {
|
||||
public:
|
||||
RenameCurrentTest()
|
||||
: DBTestBase("rename_current_test", /*env_do_fsync=*/true),
|
||||
sync_point_(GetParam()) {}
|
||||
|
||||
~RenameCurrentTest() override {}
|
||||
|
||||
void SetUp() override {
|
||||
env_->no_file_overwrite_.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
env_->no_file_overwrite_.store(false, std::memory_order_release);
|
||||
}
|
||||
|
||||
void SetupSyncPoints() {
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->SetCallBack(sync_point_, [&](void* arg) {
|
||||
Status* s = reinterpret_cast<Status*>(arg);
|
||||
assert(s);
|
||||
*s = Status::IOError("Injected IO error.");
|
||||
});
|
||||
}
|
||||
|
||||
const std::string sync_point_;
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DistributedFS, RenameCurrentTest,
|
||||
::testing::Values("SetCurrentFile:BeforeRename",
|
||||
"SetCurrentFile:AfterRename"));
|
||||
|
||||
TEST_P(RenameCurrentTest, Open) {
|
||||
Destroy(last_options_);
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
SetupSyncPoints();
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Status s = TryReopen(options);
|
||||
ASSERT_NOK(s);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
Reopen(options);
|
||||
}
|
||||
|
||||
TEST_P(RenameCurrentTest, Flush) {
|
||||
Destroy(last_options_);
|
||||
Options options = GetDefaultOptions();
|
||||
options.max_manifest_file_size = 1;
|
||||
options.create_if_missing = true;
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("key", "value"));
|
||||
SetupSyncPoints();
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
ASSERT_NOK(Flush());
|
||||
|
||||
ASSERT_NOK(Put("foo", "value"));
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
Reopen(options);
|
||||
ASSERT_EQ("value", Get("key"));
|
||||
ASSERT_EQ("NOT_FOUND", Get("foo"));
|
||||
}
|
||||
|
||||
TEST_P(RenameCurrentTest, Compaction) {
|
||||
Destroy(last_options_);
|
||||
Options options = GetDefaultOptions();
|
||||
options.max_manifest_file_size = 1;
|
||||
options.create_if_missing = true;
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("a", "a_value"));
|
||||
ASSERT_OK(Put("c", "c_value"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(Put("b", "b_value"));
|
||||
ASSERT_OK(Put("d", "d_value"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
SetupSyncPoints();
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
ASSERT_NOK(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr));
|
||||
|
||||
ASSERT_NOK(Put("foo", "value"));
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
Reopen(options);
|
||||
ASSERT_EQ("NOT_FOUND", Get("foo"));
|
||||
ASSERT_EQ("d_value", Get("d"));
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// WAL recovery mode is WALRecoveryMode::kPointInTimeRecovery.
|
||||
@@ -5448,6 +5558,35 @@ TEST_F(DBTest2, PointInTimeRecoveryWithIOErrorWhileReadingWal) {
|
||||
Status s = TryReopen(options);
|
||||
ASSERT_TRUE(s.IsIOError());
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, PointInTimeRecoveryWithSyncFailureInCFCreation) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::BackgroundCallFlush:Start:1",
|
||||
"PointInTimeRecoveryWithSyncFailureInCFCreation:1"},
|
||||
{"PointInTimeRecoveryWithSyncFailureInCFCreation:2",
|
||||
"DBImpl::BackgroundCallFlush:Start:2"}});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
CreateColumnFamilies({"test1"}, Options());
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
|
||||
// Creating a CF when a flush is going on, log is synced but the
|
||||
// closed log file is not synced and corrupted.
|
||||
port::Thread flush_thread([&]() { ASSERT_NOK(Flush()); });
|
||||
TEST_SYNC_POINT("PointInTimeRecoveryWithSyncFailureInCFCreation:1");
|
||||
CreateColumnFamilies({"test2"}, Options());
|
||||
env_->corrupt_in_sync_ = true;
|
||||
TEST_SYNC_POINT("PointInTimeRecoveryWithSyncFailureInCFCreation:2");
|
||||
flush_thread.join();
|
||||
env_->corrupt_in_sync_ = false;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
// Reopening the DB should not corrupt anything
|
||||
Options options = CurrentOptions();
|
||||
options.wal_recovery_mode = WALRecoveryMode::kPointInTimeRecovery;
|
||||
ReopenWithColumnFamilies({"default", "test1", "test2"}, options);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
|
||||
+36
-7
@@ -44,6 +44,7 @@ SpecialEnv::SpecialEnv(Env* base, bool time_elapse_only_sleep)
|
||||
manifest_sync_error_.store(false, std::memory_order_release);
|
||||
manifest_write_error_.store(false, std::memory_order_release);
|
||||
log_write_error_.store(false, std::memory_order_release);
|
||||
no_file_overwrite_.store(false, std::memory_order_release);
|
||||
random_file_open_counter_.store(0, std::memory_order_relaxed);
|
||||
delete_count_.store(0, std::memory_order_relaxed);
|
||||
num_open_wal_file_.store(0);
|
||||
@@ -487,6 +488,7 @@ Options DBTestBase::GetOptions(
|
||||
}
|
||||
case kFIFOCompaction: {
|
||||
options.compaction_style = kCompactionStyleFIFO;
|
||||
options.max_open_files = -1;
|
||||
break;
|
||||
}
|
||||
case kBlockBasedTableWithPrefixHashIndex: {
|
||||
@@ -500,6 +502,7 @@ Options DBTestBase::GetOptions(
|
||||
break;
|
||||
}
|
||||
case kBlockBasedTableWithPartitionedIndex: {
|
||||
table_options.format_version = 3;
|
||||
table_options.index_type = BlockBasedTableOptions::kTwoLevelIndexSearch;
|
||||
options.prefix_extractor.reset(NewNoopTransform());
|
||||
break;
|
||||
@@ -1126,8 +1129,34 @@ std::string DBTestBase::FilesPerLevel(int cf) {
|
||||
result.resize(last_non_zero_offset);
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
std::vector<uint64_t> DBTestBase::GetBlobFileNumbers() {
|
||||
VersionSet* const versions = dbfull()->TEST_GetVersionSet();
|
||||
assert(versions);
|
||||
|
||||
ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault();
|
||||
assert(cfd);
|
||||
|
||||
Version* const current = cfd->current();
|
||||
assert(current);
|
||||
|
||||
const VersionStorageInfo* const storage_info = current->storage_info();
|
||||
assert(storage_info);
|
||||
|
||||
const auto& blob_files = storage_info->GetBlobFiles();
|
||||
|
||||
std::vector<uint64_t> result;
|
||||
result.reserve(blob_files.size());
|
||||
|
||||
for (const auto& blob_file : blob_files) {
|
||||
result.emplace_back(blob_file.first);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t DBTestBase::CountFiles() {
|
||||
size_t count = 0;
|
||||
std::vector<std::string> files;
|
||||
@@ -1436,26 +1465,26 @@ void DBTestBase::CopyFile(const std::string& source,
|
||||
ASSERT_OK(destfile->Close());
|
||||
}
|
||||
|
||||
Status DBTestBase::GetAllSSTFiles(
|
||||
std::unordered_map<std::string, uint64_t>* sst_files,
|
||||
Status DBTestBase::GetAllDataFiles(
|
||||
const FileType file_type, std::unordered_map<std::string, uint64_t>* files,
|
||||
uint64_t* total_size /* = nullptr */) {
|
||||
if (total_size) {
|
||||
*total_size = 0;
|
||||
}
|
||||
std::vector<std::string> files;
|
||||
Status s = env_->GetChildren(dbname_, &files);
|
||||
std::vector<std::string> children;
|
||||
Status s = env_->GetChildren(dbname_, &children);
|
||||
if (s.ok()) {
|
||||
for (auto& file_name : files) {
|
||||
for (auto& file_name : children) {
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
if (ParseFileName(file_name, &number, &type) && type == kTableFile) {
|
||||
if (ParseFileName(file_name, &number, &type) && type == file_type) {
|
||||
std::string file_path = dbname_ + "/" + file_name;
|
||||
uint64_t file_size = 0;
|
||||
s = env_->GetFileSize(file_path, &file_size);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
(*sst_files)[file_path] = file_size;
|
||||
(*files)[file_path] = file_size;
|
||||
if (total_size) {
|
||||
*total_size += file_size;
|
||||
}
|
||||
|
||||
+69
-5
@@ -229,6 +229,11 @@ class SpecialEnv : public EnvWrapper {
|
||||
return base_->Append(data);
|
||||
}
|
||||
}
|
||||
Status Append(
|
||||
const Slice& data,
|
||||
const DataVerificationInfo& /* verification_info */) override {
|
||||
return Append(data);
|
||||
}
|
||||
Status PositionedAppend(const Slice& data, uint64_t offset) override {
|
||||
if (env_->table_write_callback_) {
|
||||
(*env_->table_write_callback_)();
|
||||
@@ -243,6 +248,11 @@ class SpecialEnv : public EnvWrapper {
|
||||
return base_->PositionedAppend(data, offset);
|
||||
}
|
||||
}
|
||||
Status PositionedAppend(
|
||||
const Slice& data, uint64_t offset,
|
||||
const DataVerificationInfo& /* verification_info */) override {
|
||||
return PositionedAppend(data, offset);
|
||||
}
|
||||
Status Truncate(uint64_t size) override { return base_->Truncate(size); }
|
||||
Status RangeSync(uint64_t offset, uint64_t nbytes) override {
|
||||
Status s = base_->RangeSync(offset, nbytes);
|
||||
@@ -305,6 +315,12 @@ class SpecialEnv : public EnvWrapper {
|
||||
return base_->Append(data);
|
||||
}
|
||||
}
|
||||
Status Append(
|
||||
const Slice& data,
|
||||
const DataVerificationInfo& /*verification_info*/) override {
|
||||
return Append(data);
|
||||
}
|
||||
|
||||
Status Truncate(uint64_t size) override { return base_->Truncate(size); }
|
||||
Status Close() override { return base_->Close(); }
|
||||
Status Flush() override { return base_->Flush(); }
|
||||
@@ -356,15 +372,26 @@ class SpecialEnv : public EnvWrapper {
|
||||
#endif
|
||||
return s;
|
||||
}
|
||||
Status Append(
|
||||
const Slice& data,
|
||||
const DataVerificationInfo& /* verification_info */) override {
|
||||
return Append(data);
|
||||
}
|
||||
Status Truncate(uint64_t size) override { return base_->Truncate(size); }
|
||||
void PrepareWrite(size_t offset, size_t len) override {
|
||||
base_->PrepareWrite(offset, len);
|
||||
}
|
||||
void SetPreallocationBlockSize(size_t size) override {
|
||||
base_->SetPreallocationBlockSize(size);
|
||||
}
|
||||
Status Close() override {
|
||||
// SyncPoint is not supported in Released Windows Mode.
|
||||
#if !(defined NDEBUG) || !defined(OS_WIN)
|
||||
// Check preallocation size
|
||||
// preallocation size is never passed to base file.
|
||||
size_t preallocation_size = preallocation_block_size();
|
||||
size_t block_size, last_allocated_block;
|
||||
base_->GetPreallocationStatus(&block_size, &last_allocated_block);
|
||||
TEST_SYNC_POINT_CALLBACK("DBTestWalFile.GetPreallocationStatus",
|
||||
&preallocation_size);
|
||||
&block_size);
|
||||
#endif // !(defined NDEBUG) || !defined(OS_WIN)
|
||||
|
||||
return base_->Close();
|
||||
@@ -372,6 +399,10 @@ class SpecialEnv : public EnvWrapper {
|
||||
Status Flush() override { return base_->Flush(); }
|
||||
Status Sync() override {
|
||||
++env_->sync_counter_;
|
||||
if (env_->corrupt_in_sync_) {
|
||||
Append(std::string(33000, ' '));
|
||||
return Status::IOError("Ingested Sync Failure");
|
||||
}
|
||||
if (env_->skip_fsync_) {
|
||||
return Status::OK();
|
||||
} else {
|
||||
@@ -394,6 +425,11 @@ class SpecialEnv : public EnvWrapper {
|
||||
OtherFile(SpecialEnv* env, std::unique_ptr<WritableFile>&& b)
|
||||
: env_(env), base_(std::move(b)) {}
|
||||
Status Append(const Slice& data) override { return base_->Append(data); }
|
||||
Status Append(
|
||||
const Slice& data,
|
||||
const DataVerificationInfo& /*verification_info*/) override {
|
||||
return Append(data);
|
||||
}
|
||||
Status Truncate(uint64_t size) override { return base_->Truncate(size); }
|
||||
Status Close() override { return base_->Close(); }
|
||||
Status Flush() override { return base_->Flush(); }
|
||||
@@ -414,6 +450,11 @@ class SpecialEnv : public EnvWrapper {
|
||||
std::unique_ptr<WritableFile> base_;
|
||||
};
|
||||
|
||||
if (no_file_overwrite_.load(std::memory_order_acquire) &&
|
||||
target()->FileExists(f).ok()) {
|
||||
return Status::NotSupported("SpecialEnv::no_file_overwrite_ is true.");
|
||||
}
|
||||
|
||||
if (non_writeable_rate_.load(std::memory_order_acquire) > 0) {
|
||||
uint32_t random_number;
|
||||
{
|
||||
@@ -661,6 +702,9 @@ class SpecialEnv : public EnvWrapper {
|
||||
// Slow down every log write, in micro-seconds.
|
||||
std::atomic<int> log_write_slowdown_;
|
||||
|
||||
// If true, returns Status::NotSupported for file overwrite.
|
||||
std::atomic<bool> no_file_overwrite_;
|
||||
|
||||
// Number of WAL files that are still open for write.
|
||||
std::atomic<int> num_open_wal_file_;
|
||||
|
||||
@@ -683,6 +727,9 @@ class SpecialEnv : public EnvWrapper {
|
||||
// If true, all fsync to files and directories are skipped.
|
||||
bool skip_fsync_ = false;
|
||||
|
||||
// If true, ingest the corruption to file during sync.
|
||||
bool corrupt_in_sync_ = false;
|
||||
|
||||
std::atomic<uint32_t> non_writeable_rate_;
|
||||
|
||||
std::atomic<uint32_t> new_writable_count_;
|
||||
@@ -735,6 +782,17 @@ class OnFileDeletionListener : public EventListener {
|
||||
size_t matched_count_;
|
||||
std::string expected_file_name_;
|
||||
};
|
||||
|
||||
class FlushCounterListener : public EventListener {
|
||||
public:
|
||||
std::atomic<int> count{0};
|
||||
std::atomic<FlushReason> expected_flush_reason{FlushReason::kOthers};
|
||||
|
||||
void OnFlushBegin(DB* /*db*/, const FlushJobInfo& flush_job_info) override {
|
||||
count++;
|
||||
ASSERT_EQ(expected_flush_reason.load(), flush_job_info.flush_reason);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
// A test merge operator mimics put but also fails if one of merge operands is
|
||||
@@ -768,6 +826,7 @@ class CacheWrapper : public Cache {
|
||||
|
||||
const char* Name() const override { return target_->Name(); }
|
||||
|
||||
using Cache::Insert;
|
||||
Status Insert(const Slice& key, void* value, size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value),
|
||||
Handle** handle = nullptr,
|
||||
@@ -775,12 +834,14 @@ class CacheWrapper : public Cache {
|
||||
return target_->Insert(key, value, charge, deleter, handle, priority);
|
||||
}
|
||||
|
||||
using Cache::Lookup;
|
||||
Handle* Lookup(const Slice& key, Statistics* stats = nullptr) override {
|
||||
return target_->Lookup(key, stats);
|
||||
}
|
||||
|
||||
bool Ref(Handle* handle) override { return target_->Ref(handle); }
|
||||
|
||||
using Cache::Release;
|
||||
bool Release(Handle* handle, bool force_erase = false) override {
|
||||
return target_->Release(handle, force_erase);
|
||||
}
|
||||
@@ -1064,6 +1125,8 @@ class DBTestBase : public testing::Test {
|
||||
int TotalTableFiles(int cf = 0, int levels = -1);
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
std::vector<uint64_t> GetBlobFileNumbers();
|
||||
|
||||
// Return spread of files per level
|
||||
std::string FilesPerLevel(int cf = 0);
|
||||
|
||||
@@ -1153,8 +1216,9 @@ class DBTestBase : public testing::Test {
|
||||
void CopyFile(const std::string& source, const std::string& destination,
|
||||
uint64_t size = 0);
|
||||
|
||||
Status GetAllSSTFiles(std::unordered_map<std::string, uint64_t>* sst_files,
|
||||
uint64_t* total_size = nullptr);
|
||||
Status GetAllDataFiles(const FileType file_type,
|
||||
std::unordered_map<std::string, uint64_t>* sst_files,
|
||||
uint64_t* total_size = nullptr);
|
||||
|
||||
std::vector<std::uint64_t> ListTableFiles(Env* env, const std::string& path);
|
||||
|
||||
|
||||
+150
-35
@@ -24,13 +24,37 @@ class DBWALTestBase : public DBTestBase {
|
||||
|
||||
#if defined(ROCKSDB_PLATFORM_POSIX)
|
||||
public:
|
||||
#if defined(ROCKSDB_FALLOCATE_PRESENT)
|
||||
bool IsFallocateSupported() {
|
||||
// Test fallocate support of running file system.
|
||||
// Skip this test if fallocate is not supported.
|
||||
std::string fname_test_fallocate = dbname_ + "/preallocate_testfile";
|
||||
int fd = -1;
|
||||
do {
|
||||
fd = open(fname_test_fallocate.c_str(), O_CREAT | O_RDWR | O_TRUNC, 0644);
|
||||
} while (fd < 0 && errno == EINTR);
|
||||
assert(fd > 0);
|
||||
int alloc_status = fallocate(fd, 0, 0, 1);
|
||||
int err_number = errno;
|
||||
close(fd);
|
||||
assert(env_->DeleteFile(fname_test_fallocate) == Status::OK());
|
||||
if (err_number == ENOSYS || err_number == EOPNOTSUPP) {
|
||||
fprintf(stderr, "Skipped preallocated space check: %s\n",
|
||||
errnoStr(err_number).c_str());
|
||||
return false;
|
||||
}
|
||||
assert(alloc_status == 0);
|
||||
return true;
|
||||
}
|
||||
#endif // ROCKSDB_FALLOCATE_PRESENT
|
||||
|
||||
uint64_t GetAllocatedFileSize(std::string file_name) {
|
||||
struct stat sbuf;
|
||||
int err = stat(file_name.c_str(), &sbuf);
|
||||
assert(err == 0);
|
||||
return sbuf.st_blocks * 512;
|
||||
}
|
||||
#endif
|
||||
#endif // ROCKSDB_PLATFORM_POSIX
|
||||
};
|
||||
|
||||
class DBWALTest : public DBWALTestBase {
|
||||
@@ -425,16 +449,18 @@ TEST_F(DBWALTest, RecoverWithBlob) {
|
||||
const InternalStats* const internal_stats = cfd->internal_stats();
|
||||
ASSERT_NE(internal_stats, nullptr);
|
||||
|
||||
const uint64_t expected_bytes =
|
||||
table_file->fd.GetFileSize() + blob_file->GetTotalBlobBytes();
|
||||
|
||||
const auto& compaction_stats = internal_stats->TEST_GetCompactionStats();
|
||||
ASSERT_FALSE(compaction_stats.empty());
|
||||
ASSERT_EQ(compaction_stats[0].bytes_written, expected_bytes);
|
||||
ASSERT_EQ(compaction_stats[0].num_output_files, 2);
|
||||
ASSERT_EQ(compaction_stats[0].bytes_written, table_file->fd.GetFileSize());
|
||||
ASSERT_EQ(compaction_stats[0].bytes_written_blob,
|
||||
blob_file->GetTotalBlobBytes());
|
||||
ASSERT_EQ(compaction_stats[0].num_output_files, 1);
|
||||
ASSERT_EQ(compaction_stats[0].num_output_files_blob, 1);
|
||||
|
||||
const uint64_t* const cf_stats_value = internal_stats->TEST_GetCFStatsValue();
|
||||
ASSERT_EQ(cf_stats_value[InternalStats::BYTES_FLUSHED], expected_bytes);
|
||||
ASSERT_EQ(cf_stats_value[InternalStats::BYTES_FLUSHED],
|
||||
compaction_stats[0].bytes_written +
|
||||
compaction_stats[0].bytes_written_blob);
|
||||
#endif // ROCKSDB_LITE
|
||||
}
|
||||
|
||||
@@ -1775,19 +1801,8 @@ TEST_P(DBWALTestWithParamsVaryingRecoveryMode,
|
||||
// avoid_flush_during_recovery=true.
|
||||
// Flush should trigger if max_total_wal_size is reached.
|
||||
TEST_F(DBWALTest, RestoreTotalLogSizeAfterRecoverWithoutFlush) {
|
||||
class TestFlushListener : public EventListener {
|
||||
public:
|
||||
std::atomic<int> count{0};
|
||||
|
||||
TestFlushListener() = default;
|
||||
|
||||
void OnFlushBegin(DB* /*db*/, const FlushJobInfo& flush_job_info) override {
|
||||
count++;
|
||||
ASSERT_EQ(FlushReason::kWriteBufferManager, flush_job_info.flush_reason);
|
||||
}
|
||||
};
|
||||
std::shared_ptr<TestFlushListener> test_listener =
|
||||
std::make_shared<TestFlushListener>();
|
||||
auto test_listener = std::make_shared<FlushCounterListener>();
|
||||
test_listener->expected_flush_reason = FlushReason::kWalFull;
|
||||
|
||||
constexpr size_t kKB = 1024;
|
||||
constexpr size_t kMB = 1024 * 1024;
|
||||
@@ -1847,23 +1862,9 @@ TEST_F(DBWALTest, TruncateLastLogAfterRecoverWithoutFlush) {
|
||||
ROCKSDB_GTEST_SKIP("Test requires non-mem environment");
|
||||
return;
|
||||
}
|
||||
// Test fallocate support of running file system.
|
||||
// Skip this test if fallocate is not supported.
|
||||
std::string fname_test_fallocate = dbname_ + "/preallocate_testfile";
|
||||
int fd = -1;
|
||||
do {
|
||||
fd = open(fname_test_fallocate.c_str(), O_CREAT | O_RDWR | O_TRUNC, 0644);
|
||||
} while (fd < 0 && errno == EINTR);
|
||||
ASSERT_GT(fd, 0);
|
||||
int alloc_status = fallocate(fd, 0, 0, 1);
|
||||
int err_number = errno;
|
||||
close(fd);
|
||||
ASSERT_OK(options.env->DeleteFile(fname_test_fallocate));
|
||||
if (err_number == ENOSYS || err_number == EOPNOTSUPP) {
|
||||
fprintf(stderr, "Skipped preallocated space check: %s\n", strerror(err_number));
|
||||
if (!IsFallocateSupported()) {
|
||||
return;
|
||||
}
|
||||
ASSERT_EQ(0, alloc_status);
|
||||
|
||||
DestroyAndReopen(options);
|
||||
size_t preallocated_size =
|
||||
@@ -1886,6 +1887,120 @@ TEST_F(DBWALTest, TruncateLastLogAfterRecoverWithoutFlush) {
|
||||
ASSERT_LT(GetAllocatedFileSize(dbname_ + file_before->PathName()),
|
||||
preallocated_size);
|
||||
}
|
||||
// Tests that we will truncate the preallocated space of the last log from
|
||||
// previous.
|
||||
TEST_F(DBWALTest, TruncateLastLogAfterRecoverWithFlush) {
|
||||
constexpr size_t kKB = 1024;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.avoid_flush_during_recovery = false;
|
||||
options.avoid_flush_during_shutdown = true;
|
||||
if (mem_env_) {
|
||||
ROCKSDB_GTEST_SKIP("Test requires non-mem environment");
|
||||
return;
|
||||
}
|
||||
if (!IsFallocateSupported()) {
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
ASSERT_GE(GetAllocatedFileSize(dbname_ + file_before->PathName()),
|
||||
preallocated_size);
|
||||
// The log file has preallocated space.
|
||||
Close();
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::PurgeObsoleteFiles:Begin",
|
||||
"DBWALTest::TruncateLastLogAfterRecoverWithFlush:AfterRecover"},
|
||||
{"DBWALTest::TruncateLastLogAfterRecoverWithFlush:AfterTruncate",
|
||||
"DBImpl::DeleteObsoleteFileImpl::BeforeDeletion"}});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
port::Thread reopen_thread([&]() { Reopen(options); });
|
||||
|
||||
TEST_SYNC_POINT(
|
||||
"DBWALTest::TruncateLastLogAfterRecoverWithFlush:AfterRecover");
|
||||
// After the flush during Open, the log file should get deleted. However,
|
||||
// if the process is in a crash loop, the log file may not get
|
||||
// deleted and thte preallocated space will keep accumulating. So we need
|
||||
// to ensure it gets trtuncated.
|
||||
EXPECT_LT(GetAllocatedFileSize(dbname_ + file_before->PathName()),
|
||||
preallocated_size);
|
||||
TEST_SYNC_POINT(
|
||||
"DBWALTest::TruncateLastLogAfterRecoverWithFlush:AfterTruncate");
|
||||
reopen_thread.join();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBWALTest, TruncateLastLogAfterRecoverWALEmpty) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.avoid_flush_during_recovery = false;
|
||||
if (mem_env_ || encrypted_env_) {
|
||||
ROCKSDB_GTEST_SKIP("Test requires non-mem/non-encrypted environment");
|
||||
return;
|
||||
}
|
||||
if (!IsFallocateSupported()) {
|
||||
return;
|
||||
}
|
||||
|
||||
DestroyAndReopen(options);
|
||||
size_t preallocated_size =
|
||||
dbfull()->TEST_GetWalPreallocateBlockSize(options.write_buffer_size);
|
||||
Close();
|
||||
std::vector<std::string> filenames;
|
||||
std::string last_log;
|
||||
uint64_t last_log_num = 0;
|
||||
ASSERT_OK(env_->GetChildren(dbname_, &filenames));
|
||||
for (auto fname : filenames) {
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
if (ParseFileName(fname, &number, &type, nullptr)) {
|
||||
if (type == kWalFile && number > last_log_num) {
|
||||
last_log = fname;
|
||||
}
|
||||
}
|
||||
}
|
||||
ASSERT_NE(last_log, "");
|
||||
last_log = dbname_ + '/' + last_log;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::PurgeObsoleteFiles:Begin",
|
||||
"DBWALTest::TruncateLastLogAfterRecoverWithFlush:AfterRecover"},
|
||||
{"DBWALTest::TruncateLastLogAfterRecoverWithFlush:AfterTruncate",
|
||||
"DBImpl::DeleteObsoleteFileImpl::BeforeDeletion"}});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"PosixWritableFile::Close",
|
||||
[](void* arg) { *(reinterpret_cast<size_t*>(arg)) = 0; });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
// Preallocate space for the empty log file. This could happen if WAL data
|
||||
// was buffered in memory and the process crashed.
|
||||
std::unique_ptr<WritableFile> log_file;
|
||||
ASSERT_OK(env_->ReopenWritableFile(last_log, &log_file, EnvOptions()));
|
||||
log_file->SetPreallocationBlockSize(preallocated_size);
|
||||
log_file->PrepareWrite(0, 4096);
|
||||
log_file.reset();
|
||||
|
||||
ASSERT_GE(GetAllocatedFileSize(last_log), preallocated_size);
|
||||
|
||||
port::Thread reopen_thread([&]() { Reopen(options); });
|
||||
|
||||
TEST_SYNC_POINT(
|
||||
"DBWALTest::TruncateLastLogAfterRecoverWithFlush:AfterRecover");
|
||||
// The preallocated space should be truncated.
|
||||
EXPECT_LT(GetAllocatedFileSize(last_log), preallocated_size);
|
||||
TEST_SYNC_POINT(
|
||||
"DBWALTest::TruncateLastLogAfterRecoverWithFlush:AfterTruncate");
|
||||
reopen_thread.join();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
#endif // ROCKSDB_FALLOCATE_PRESENT
|
||||
#endif // ROCKSDB_PLATFORM_POSIX
|
||||
|
||||
|
||||
@@ -402,12 +402,11 @@ TEST_F(DBBasicTestWithTimestamp, GetApproximateSizes) {
|
||||
ASSERT_OK(
|
||||
db_->GetApproximateSizes(size_approx_options, default_cf, &r, 1, &size));
|
||||
ASSERT_EQ(size, 0);
|
||||
std::cout << size << std::endl;
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, SimpleForwardIterate) {
|
||||
TEST_F(DBBasicTestWithTimestamp, SimpleIterate) {
|
||||
const int kNumKeysPerFile = 128;
|
||||
const uint64_t kMaxKey = 1024;
|
||||
Options options = CurrentOptions();
|
||||
@@ -439,6 +438,7 @@ TEST_F(DBBasicTestWithTimestamp, SimpleForwardIterate) {
|
||||
std::unique_ptr<Iterator> it(db_->NewIterator(read_opts));
|
||||
int count = 0;
|
||||
uint64_t key = 0;
|
||||
// Forward iterate.
|
||||
for (it->Seek(Key1(0)), key = start_keys[i]; it->Valid();
|
||||
it->Next(), ++count, ++key) {
|
||||
CheckIterUserEntry(it.get(), Key1(key), kTypeValue,
|
||||
@@ -447,7 +447,16 @@ TEST_F(DBBasicTestWithTimestamp, SimpleForwardIterate) {
|
||||
size_t expected_count = kMaxKey - start_keys[i] + 1;
|
||||
ASSERT_EQ(expected_count, count);
|
||||
|
||||
// SeekToFirst() with lower bound.
|
||||
// Backward iterate.
|
||||
count = 0;
|
||||
for (it->SeekForPrev(Key1(kMaxKey)), key = kMaxKey; it->Valid();
|
||||
it->Prev(), ++count, --key) {
|
||||
CheckIterUserEntry(it.get(), Key1(key), kTypeValue,
|
||||
"value" + std::to_string(i), write_timestamps[i]);
|
||||
}
|
||||
ASSERT_EQ(static_cast<size_t>(kMaxKey) - start_keys[i] + 1, count);
|
||||
|
||||
// SeekToFirst()/SeekToLast() with lower/upper bounds.
|
||||
// Then iter with lower and upper bounds.
|
||||
uint64_t l = 0;
|
||||
uint64_t r = kMaxKey + 1;
|
||||
@@ -465,6 +474,12 @@ TEST_F(DBBasicTestWithTimestamp, SimpleForwardIterate) {
|
||||
"value" + std::to_string(i), write_timestamps[i]);
|
||||
}
|
||||
ASSERT_EQ(r - std::max(l, start_keys[i]), count);
|
||||
|
||||
for (it->SeekToLast(), key = std::min(r, kMaxKey + 1), count = 0;
|
||||
it->Valid(); it->Prev(), --key, ++count) {
|
||||
CheckIterUserEntry(it.get(), Key1(key - 1), kTypeValue,
|
||||
"value" + std::to_string(i), write_timestamps[i]);
|
||||
}
|
||||
l += (kMaxKey / 100);
|
||||
r -= (kMaxKey / 100);
|
||||
}
|
||||
@@ -472,7 +487,93 @@ TEST_F(DBBasicTestWithTimestamp, SimpleForwardIterate) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, SeekWithPrefixLessThanKey) {
|
||||
class DBBasicTestWithTimestampTableOptions
|
||||
: public DBBasicTestWithTimestampBase,
|
||||
public testing::WithParamInterface<BlockBasedTableOptions::IndexType> {
|
||||
public:
|
||||
explicit DBBasicTestWithTimestampTableOptions()
|
||||
: DBBasicTestWithTimestampBase(
|
||||
"db_basic_test_with_timestamp_table_options") {}
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
Timestamp, DBBasicTestWithTimestampTableOptions,
|
||||
testing::Values(
|
||||
BlockBasedTableOptions::IndexType::kBinarySearch,
|
||||
BlockBasedTableOptions::IndexType::kHashSearch,
|
||||
BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch,
|
||||
BlockBasedTableOptions::IndexType::kBinarySearchWithFirstKey));
|
||||
|
||||
TEST_P(DBBasicTestWithTimestampTableOptions, GetAndMultiGet) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(3));
|
||||
options.compression = kNoCompression;
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.index_type = GetParam();
|
||||
bbto.block_size = 100;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator cmp(kTimestampSize);
|
||||
options.comparator = &cmp;
|
||||
DestroyAndReopen(options);
|
||||
constexpr uint64_t kNumKeys = 1024;
|
||||
for (uint64_t k = 0; k < kNumKeys; ++k) {
|
||||
WriteOptions write_opts;
|
||||
std::string ts_str = Timestamp(1, 0);
|
||||
Slice ts = ts_str;
|
||||
write_opts.timestamp = &ts;
|
||||
ASSERT_OK(db_->Put(write_opts, Key1(k), "value" + std::to_string(k)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
{
|
||||
ReadOptions read_opts;
|
||||
read_opts.total_order_seek = true;
|
||||
std::string ts_str = Timestamp(2, 0);
|
||||
Slice ts = ts_str;
|
||||
read_opts.timestamp = &ts;
|
||||
std::unique_ptr<Iterator> it(db_->NewIterator(read_opts));
|
||||
// verify Get()
|
||||
for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
||||
std::string value_from_get;
|
||||
std::string key_str(it->key().data(), it->key().size());
|
||||
std::string timestamp;
|
||||
ASSERT_OK(db_->Get(read_opts, key_str, &value_from_get, ×tamp));
|
||||
ASSERT_EQ(it->value(), value_from_get);
|
||||
ASSERT_EQ(Timestamp(1, 0), timestamp);
|
||||
}
|
||||
|
||||
// verify MultiGet()
|
||||
constexpr uint64_t step = 2;
|
||||
static_assert(0 == (kNumKeys % step),
|
||||
"kNumKeys must be a multiple of step");
|
||||
for (uint64_t k = 0; k < kNumKeys; k += 2) {
|
||||
std::vector<std::string> key_strs;
|
||||
std::vector<Slice> keys;
|
||||
for (size_t i = 0; i < step; ++i) {
|
||||
key_strs.push_back(Key1(k + i));
|
||||
}
|
||||
for (size_t i = 0; i < step; ++i) {
|
||||
keys.emplace_back(key_strs[i]);
|
||||
}
|
||||
std::vector<std::string> values;
|
||||
std::vector<std::string> timestamps;
|
||||
std::vector<Status> statuses =
|
||||
db_->MultiGet(read_opts, keys, &values, ×tamps);
|
||||
ASSERT_EQ(step, statuses.size());
|
||||
ASSERT_EQ(step, values.size());
|
||||
ASSERT_EQ(step, timestamps.size());
|
||||
for (uint64_t i = 0; i < step; ++i) {
|
||||
ASSERT_OK(statuses[i]);
|
||||
ASSERT_EQ("value" + std::to_string(k + i), values[i]);
|
||||
ASSERT_EQ(Timestamp(1, 0), timestamps[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_P(DBBasicTestWithTimestampTableOptions, SeekWithPrefixLessThanKey) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
@@ -483,6 +584,7 @@ TEST_F(DBBasicTestWithTimestamp, SeekWithPrefixLessThanKey) {
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
bbto.cache_index_and_filter_blocks = true;
|
||||
bbto.whole_key_filtering = true;
|
||||
bbto.index_type = GetParam();
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
@@ -527,7 +629,7 @@ TEST_F(DBBasicTestWithTimestamp, SeekWithPrefixLessThanKey) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, SeekWithPrefixLargerThanKey) {
|
||||
TEST_P(DBBasicTestWithTimestampTableOptions, SeekWithPrefixLongerThanKey) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
@@ -538,6 +640,7 @@ TEST_F(DBBasicTestWithTimestamp, SeekWithPrefixLargerThanKey) {
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
bbto.cache_index_and_filter_blocks = true;
|
||||
bbto.whole_key_filtering = true;
|
||||
bbto.index_type = GetParam();
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
@@ -580,7 +683,7 @@ TEST_F(DBBasicTestWithTimestamp, SeekWithPrefixLargerThanKey) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, SeekWithBound) {
|
||||
TEST_P(DBBasicTestWithTimestampTableOptions, SeekWithBound) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
@@ -589,6 +692,7 @@ TEST_F(DBBasicTestWithTimestamp, SeekWithBound) {
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
bbto.cache_index_and_filter_blocks = true;
|
||||
bbto.whole_key_filtering = true;
|
||||
bbto.index_type = GetParam();
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
@@ -600,38 +704,135 @@ TEST_F(DBBasicTestWithTimestamp, SeekWithBound) {
|
||||
Slice ts = ts_str;
|
||||
write_opts.timestamp = &ts;
|
||||
|
||||
ASSERT_OK(db_->Put(write_opts, "foo1", "bar"));
|
||||
ASSERT_OK(db_->Put(write_opts, "foo1", "bar1"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(db_->Put(write_opts, "foo2", "bar"));
|
||||
ASSERT_OK(db_->Put(write_opts, "foo2", "bar2"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Move sst file to next level
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
|
||||
ASSERT_OK(db_->Put(write_opts, "foo3", "bar"));
|
||||
for (int i = 3; i < 9; ++i) {
|
||||
ASSERT_OK(db_->Put(write_opts, "foo" + std::to_string(i),
|
||||
"bar" + std::to_string(i)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ReadOptions read_opts;
|
||||
std::string read_ts = Timestamp(2, 0);
|
||||
ts = read_ts;
|
||||
read_opts.timestamp = &ts;
|
||||
std::string up_bound = "foo5";
|
||||
std::string up_bound = "foo5"; // exclusive
|
||||
Slice up_bound_slice = up_bound;
|
||||
std::string lo_bound = "foo2"; // inclusive
|
||||
Slice lo_bound_slice = lo_bound;
|
||||
read_opts.iterate_upper_bound = &up_bound_slice;
|
||||
read_opts.iterate_lower_bound = &lo_bound_slice;
|
||||
read_opts.auto_prefix_mode = true;
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_opts));
|
||||
// Make sure the prefix extractor doesn't include timestamp, otherwise it
|
||||
// may return invalid result.
|
||||
iter->Seek("foo");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
CheckIterUserEntry(iter.get(), lo_bound, kTypeValue, "bar2",
|
||||
Timestamp(1, 0));
|
||||
iter->SeekToFirst();
|
||||
CheckIterUserEntry(iter.get(), lo_bound, kTypeValue, "bar2",
|
||||
Timestamp(1, 0));
|
||||
iter->SeekForPrev("g");
|
||||
CheckIterUserEntry(iter.get(), "foo4", kTypeValue, "bar4", Timestamp(1, 0));
|
||||
iter->SeekToLast();
|
||||
CheckIterUserEntry(iter.get(), "foo4", kTypeValue, "bar4", Timestamp(1, 0));
|
||||
}
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, ChangeIterationDirection) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.env = env_;
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(1));
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
DestroyAndReopen(options);
|
||||
const std::vector<std::string> timestamps = {Timestamp(1, 1), Timestamp(0, 2),
|
||||
Timestamp(4, 3)};
|
||||
const std::vector<std::tuple<std::string, std::string>> kvs = {
|
||||
std::make_tuple("aa", "value1"), std::make_tuple("ab", "value2")};
|
||||
for (const auto& ts : timestamps) {
|
||||
WriteBatch wb(0, 0, kTimestampSize);
|
||||
for (const auto& kv : kvs) {
|
||||
const std::string& key = std::get<0>(kv);
|
||||
const std::string& value = std::get<1>(kv);
|
||||
ASSERT_OK(wb.Put(key, value));
|
||||
}
|
||||
|
||||
ASSERT_OK(wb.AssignTimestamp(ts));
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &wb));
|
||||
}
|
||||
std::string read_ts_str = Timestamp(5, 3);
|
||||
Slice read_ts = read_ts_str;
|
||||
ReadOptions read_opts;
|
||||
read_opts.timestamp = &read_ts;
|
||||
std::unique_ptr<Iterator> it(db_->NewIterator(read_opts));
|
||||
|
||||
it->SeekToFirst();
|
||||
ASSERT_TRUE(it->Valid());
|
||||
it->Prev();
|
||||
ASSERT_FALSE(it->Valid());
|
||||
|
||||
it->SeekToLast();
|
||||
ASSERT_TRUE(it->Valid());
|
||||
uint64_t prev_reseek_count =
|
||||
options.statistics->getTickerCount(NUMBER_OF_RESEEKS_IN_ITERATION);
|
||||
ASSERT_EQ(0, prev_reseek_count);
|
||||
it->Next();
|
||||
ASSERT_FALSE(it->Valid());
|
||||
ASSERT_EQ(1 + prev_reseek_count,
|
||||
options.statistics->getTickerCount(NUMBER_OF_RESEEKS_IN_ITERATION));
|
||||
|
||||
it->Seek(std::get<0>(kvs[0]));
|
||||
CheckIterUserEntry(it.get(), std::get<0>(kvs[0]), kTypeValue,
|
||||
std::get<1>(kvs[0]), Timestamp(4, 3));
|
||||
it->Next();
|
||||
CheckIterUserEntry(it.get(), std::get<0>(kvs[1]), kTypeValue,
|
||||
std::get<1>(kvs[1]), Timestamp(4, 3));
|
||||
it->Prev();
|
||||
CheckIterUserEntry(it.get(), std::get<0>(kvs[0]), kTypeValue,
|
||||
std::get<1>(kvs[0]), Timestamp(4, 3));
|
||||
|
||||
prev_reseek_count =
|
||||
options.statistics->getTickerCount(NUMBER_OF_RESEEKS_IN_ITERATION);
|
||||
ASSERT_EQ(1, prev_reseek_count);
|
||||
it->Next();
|
||||
CheckIterUserEntry(it.get(), std::get<0>(kvs[1]), kTypeValue,
|
||||
std::get<1>(kvs[1]), Timestamp(4, 3));
|
||||
ASSERT_EQ(1 + prev_reseek_count,
|
||||
options.statistics->getTickerCount(NUMBER_OF_RESEEKS_IN_ITERATION));
|
||||
|
||||
it->SeekForPrev(std::get<0>(kvs[1]));
|
||||
CheckIterUserEntry(it.get(), std::get<0>(kvs[1]), kTypeValue,
|
||||
std::get<1>(kvs[1]), Timestamp(4, 3));
|
||||
it->Prev();
|
||||
CheckIterUserEntry(it.get(), std::get<0>(kvs[0]), kTypeValue,
|
||||
std::get<1>(kvs[0]), Timestamp(4, 3));
|
||||
|
||||
prev_reseek_count =
|
||||
options.statistics->getTickerCount(NUMBER_OF_RESEEKS_IN_ITERATION);
|
||||
it->Next();
|
||||
CheckIterUserEntry(it.get(), std::get<0>(kvs[1]), kTypeValue,
|
||||
std::get<1>(kvs[1]), Timestamp(4, 3));
|
||||
ASSERT_EQ(1 + prev_reseek_count,
|
||||
options.statistics->getTickerCount(NUMBER_OF_RESEEKS_IN_ITERATION));
|
||||
|
||||
it.reset();
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, SimpleForwardIterateLowerTsBound) {
|
||||
constexpr int kNumKeysPerFile = 128;
|
||||
constexpr uint64_t kMaxKey = 1024;
|
||||
@@ -830,6 +1031,16 @@ TEST_F(DBBasicTestWithTimestamp, ReseekToTargetTimestamp) {
|
||||
CheckIterUserEntry(iter.get(), "foo", kTypeValue, "value0", ts_str);
|
||||
ASSERT_EQ(
|
||||
1, options.statistics->getTickerCount(NUMBER_OF_RESEEKS_IN_ITERATION));
|
||||
|
||||
ts_str = Timestamp(kNumKeys, 0);
|
||||
ts = ts_str;
|
||||
read_opts.timestamp = &ts;
|
||||
iter.reset(db_->NewIterator(read_opts));
|
||||
iter->SeekToLast();
|
||||
CheckIterUserEntry(iter.get(), "foo", kTypeValue,
|
||||
"value" + std::to_string(kNumKeys - 1), ts_str);
|
||||
ASSERT_EQ(
|
||||
2, options.statistics->getTickerCount(NUMBER_OF_RESEEKS_IN_ITERATION));
|
||||
}
|
||||
Close();
|
||||
}
|
||||
@@ -880,6 +1091,47 @@ TEST_F(DBBasicTestWithTimestamp, ReseekToNextUserKey) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, ReseekToUserKeyBeforeSavedKey) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
constexpr size_t kNumKeys = 16;
|
||||
options.max_sequential_skip_in_iterations = kNumKeys / 2;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
DestroyAndReopen(options);
|
||||
for (size_t i = 0; i < kNumKeys; ++i) {
|
||||
std::string ts_str = Timestamp(static_cast<uint64_t>(i + 1), 0);
|
||||
Slice ts = ts_str;
|
||||
WriteOptions write_opts;
|
||||
write_opts.timestamp = &ts;
|
||||
Status s = db_->Put(write_opts, "b", "value" + std::to_string(i));
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
{
|
||||
std::string ts_str = Timestamp(1, 0);
|
||||
Slice ts = ts_str;
|
||||
WriteOptions write_opts;
|
||||
write_opts.timestamp = &ts;
|
||||
ASSERT_OK(db_->Put(write_opts, "a", "value"));
|
||||
}
|
||||
{
|
||||
ReadOptions read_opts;
|
||||
std::string ts_str = Timestamp(1, 0);
|
||||
Slice ts = ts_str;
|
||||
read_opts.timestamp = &ts;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_opts));
|
||||
iter->SeekToLast();
|
||||
iter->Prev();
|
||||
CheckIterUserEntry(iter.get(), "a", kTypeValue, "value", ts_str);
|
||||
ASSERT_EQ(
|
||||
1, options.statistics->getTickerCount(NUMBER_OF_RESEEKS_IN_ITERATION));
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, MultiGetWithFastLocalBloom) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
@@ -920,7 +1172,7 @@ TEST_F(DBBasicTestWithTimestamp, MultiGetWithFastLocalBloom) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, MultiGetWithPrefix) {
|
||||
TEST_P(DBBasicTestWithTimestampTableOptions, MultiGetWithPrefix) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
@@ -929,6 +1181,7 @@ TEST_F(DBBasicTestWithTimestamp, MultiGetWithPrefix) {
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
bbto.cache_index_and_filter_blocks = true;
|
||||
bbto.whole_key_filtering = false;
|
||||
bbto.index_type = GetParam();
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
@@ -961,7 +1214,7 @@ TEST_F(DBBasicTestWithTimestamp, MultiGetWithPrefix) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, MultiGetWithMemBloomFilter) {
|
||||
TEST_P(DBBasicTestWithTimestampTableOptions, MultiGetWithMemBloomFilter) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
@@ -970,6 +1223,7 @@ TEST_F(DBBasicTestWithTimestamp, MultiGetWithMemBloomFilter) {
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
bbto.cache_index_and_filter_blocks = true;
|
||||
bbto.whole_key_filtering = false;
|
||||
bbto.index_type = GetParam();
|
||||
options.memtable_prefix_bloom_size_ratio = 0.1;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
@@ -1059,7 +1313,7 @@ TEST_F(DBBasicTestWithTimestamp, MultiGetRangeFiltering) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, MultiGetPrefixFilter) {
|
||||
TEST_P(DBBasicTestWithTimestampTableOptions, MultiGetPrefixFilter) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
@@ -1068,6 +1322,7 @@ TEST_F(DBBasicTestWithTimestamp, MultiGetPrefixFilter) {
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
bbto.cache_index_and_filter_blocks = true;
|
||||
bbto.whole_key_filtering = false;
|
||||
bbto.index_type = GetParam();
|
||||
options.memtable_prefix_bloom_size_ratio = 0.1;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
@@ -1102,7 +1357,7 @@ TEST_F(DBBasicTestWithTimestamp, MultiGetPrefixFilter) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, MaxKeysSkipped) {
|
||||
TEST_F(DBBasicTestWithTimestamp, MaxKeysSkippedDuringNext) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
@@ -1141,6 +1396,45 @@ TEST_F(DBBasicTestWithTimestamp, MaxKeysSkipped) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, MaxKeysSkippedDuringPrev) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
DestroyAndReopen(options);
|
||||
constexpr size_t max_skippable_internal_keys = 2;
|
||||
const size_t kNumKeys = max_skippable_internal_keys + 2;
|
||||
WriteOptions write_opts;
|
||||
Status s;
|
||||
{
|
||||
std::string ts_str = Timestamp(1, 0);
|
||||
Slice ts = ts_str;
|
||||
write_opts.timestamp = &ts;
|
||||
ASSERT_OK(db_->Put(write_opts, "b", "value"));
|
||||
}
|
||||
for (size_t i = 0; i < kNumKeys; ++i) {
|
||||
std::string ts_str = Timestamp(static_cast<uint64_t>(i + 1), 0);
|
||||
Slice ts = ts_str;
|
||||
write_opts.timestamp = &ts;
|
||||
s = db_->Put(write_opts, "a", "value" + std::to_string(i));
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
{
|
||||
ReadOptions read_opts;
|
||||
read_opts.max_skippable_internal_keys = max_skippable_internal_keys;
|
||||
std::string ts_str = Timestamp(1, 0);
|
||||
Slice ts = ts_str;
|
||||
read_opts.timestamp = &ts;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_opts));
|
||||
iter->SeekToLast();
|
||||
iter->Prev();
|
||||
ASSERT_TRUE(iter->status().IsIncomplete());
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
// Create two L0, and compact them to a new L1. In this test, L1 is L_bottom.
|
||||
// Two L0s:
|
||||
// f1 f2
|
||||
@@ -1204,7 +1498,8 @@ class DBBasicTestWithTimestampFilterPrefixSettings
|
||||
: public DBBasicTestWithTimestampBase,
|
||||
public testing::WithParamInterface<
|
||||
std::tuple<std::shared_ptr<const FilterPolicy>, bool, bool,
|
||||
std::shared_ptr<const SliceTransform>, bool, double>> {
|
||||
std::shared_ptr<const SliceTransform>, bool, double,
|
||||
BlockBasedTableOptions::IndexType>> {
|
||||
public:
|
||||
DBBasicTestWithTimestampFilterPrefixSettings()
|
||||
: DBBasicTestWithTimestampBase(
|
||||
@@ -1219,6 +1514,7 @@ TEST_P(DBBasicTestWithTimestampFilterPrefixSettings, GetAndMultiGet) {
|
||||
bbto.filter_policy = std::get<0>(GetParam());
|
||||
bbto.whole_key_filtering = std::get<1>(GetParam());
|
||||
bbto.cache_index_and_filter_blocks = std::get<2>(GetParam());
|
||||
bbto.index_type = std::get<6>(GetParam());
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
options.prefix_extractor = std::get<3>(GetParam());
|
||||
options.memtable_whole_key_filtering = std::get<4>(GetParam());
|
||||
@@ -1323,7 +1619,12 @@ INSTANTIATE_TEST_CASE_P(
|
||||
std::shared_ptr<const SliceTransform>(NewFixedPrefixTransform(4)),
|
||||
std::shared_ptr<const SliceTransform>(NewFixedPrefixTransform(7)),
|
||||
std::shared_ptr<const SliceTransform>(NewFixedPrefixTransform(8))),
|
||||
::testing::Bool(), ::testing::Values(0, 0.1)));
|
||||
::testing::Bool(), ::testing::Values(0, 0.1),
|
||||
::testing::Values(
|
||||
BlockBasedTableOptions::IndexType::kBinarySearch,
|
||||
BlockBasedTableOptions::IndexType::kHashSearch,
|
||||
BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch,
|
||||
BlockBasedTableOptions::IndexType::kBinarySearchWithFirstKey)));
|
||||
|
||||
class DataVisibilityTest : public DBBasicTestWithTimestampBase {
|
||||
public:
|
||||
@@ -2404,14 +2705,15 @@ class DBBasicTestWithTimestampPrefixSeek
|
||||
: public DBBasicTestWithTimestampBase,
|
||||
public testing::WithParamInterface<
|
||||
std::tuple<std::shared_ptr<const SliceTransform>,
|
||||
std::shared_ptr<const FilterPolicy>, bool>> {
|
||||
std::shared_ptr<const FilterPolicy>, bool,
|
||||
BlockBasedTableOptions::IndexType>> {
|
||||
public:
|
||||
DBBasicTestWithTimestampPrefixSeek()
|
||||
: DBBasicTestWithTimestampBase(
|
||||
"/db_basic_test_with_timestamp_prefix_seek") {}
|
||||
};
|
||||
|
||||
TEST_P(DBBasicTestWithTimestampPrefixSeek, ForwardIterateWithPrefix) {
|
||||
TEST_P(DBBasicTestWithTimestampPrefixSeek, IterateWithPrefix) {
|
||||
const size_t kNumKeysPerFile = 128;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
@@ -2423,6 +2725,7 @@ TEST_P(DBBasicTestWithTimestampPrefixSeek, ForwardIterateWithPrefix) {
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile));
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.filter_policy = std::get<1>(GetParam());
|
||||
bbto.index_type = std::get<3>(GetParam());
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
@@ -2461,6 +2764,13 @@ TEST_P(DBBasicTestWithTimestampPrefixSeek, ForwardIterateWithPrefix) {
|
||||
"value" + std::to_string(i), write_ts_list[i]);
|
||||
iter->Next();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
|
||||
// Seek to kMinKey
|
||||
iter->Seek(Key1(kMinKey));
|
||||
CheckIterUserEntry(iter.get(), Key1(kMinKey), kTypeValue,
|
||||
"value" + std::to_string(i), write_ts_list[i]);
|
||||
iter->Prev();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
}
|
||||
const std::vector<uint64_t> targets = {kMinKey, kMinKey + 0x10,
|
||||
kMinKey + 0x100, kMaxKey};
|
||||
@@ -2476,6 +2786,7 @@ TEST_P(DBBasicTestWithTimestampPrefixSeek, ForwardIterateWithPrefix) {
|
||||
Slice read_ts = read_ts_list[i];
|
||||
read_opts.timestamp = &read_ts;
|
||||
std::unique_ptr<Iterator> it(db_->NewIterator(read_opts));
|
||||
// Forward and backward iterate.
|
||||
for (size_t j = 0; j != targets.size(); ++j) {
|
||||
std::string start_key = Key1(targets[j]);
|
||||
uint64_t expected_ub =
|
||||
@@ -2499,6 +2810,24 @@ TEST_P(DBBasicTestWithTimestampPrefixSeek, ForwardIterateWithPrefix) {
|
||||
it->Next();
|
||||
}
|
||||
ASSERT_EQ(expected_ub - targets[j] + 1, count);
|
||||
|
||||
count = 0;
|
||||
expected_key = targets[j];
|
||||
it->SeekForPrev(start_key);
|
||||
uint64_t expected_lb = (targets[j] & kPrefixMask);
|
||||
while (it->Valid()) {
|
||||
// Out of prefix
|
||||
if (!read_opts.prefix_same_as_start &&
|
||||
pe->Transform(it->key()) != pe->Transform(start_key)) {
|
||||
break;
|
||||
}
|
||||
CheckIterUserEntry(it.get(), Key1(expected_key), kTypeValue,
|
||||
"value" + std::to_string(i), write_ts_list[i]);
|
||||
++count;
|
||||
--expected_key;
|
||||
it->Prev();
|
||||
}
|
||||
ASSERT_EQ(targets[j] - std::max(expected_lb, kMinKey) + 1, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2521,19 +2850,25 @@ INSTANTIATE_TEST_CASE_P(
|
||||
std::shared_ptr<const FilterPolicy>(
|
||||
NewBloomFilterPolicy(20 /*bits_per_key*/,
|
||||
false))),
|
||||
::testing::Bool()));
|
||||
::testing::Bool(),
|
||||
::testing::Values(
|
||||
BlockBasedTableOptions::IndexType::kBinarySearch,
|
||||
BlockBasedTableOptions::IndexType::kHashSearch,
|
||||
BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch,
|
||||
BlockBasedTableOptions::IndexType::kBinarySearchWithFirstKey)));
|
||||
|
||||
class DBBasicTestWithTsIterTombstones
|
||||
: public DBBasicTestWithTimestampBase,
|
||||
public testing::WithParamInterface<
|
||||
std::tuple<std::shared_ptr<const SliceTransform>,
|
||||
std::shared_ptr<const FilterPolicy>, int>> {
|
||||
std::shared_ptr<const FilterPolicy>, int,
|
||||
BlockBasedTableOptions::IndexType>> {
|
||||
public:
|
||||
DBBasicTestWithTsIterTombstones()
|
||||
: DBBasicTestWithTimestampBase("/db_basic_ts_iter_tombstones") {}
|
||||
};
|
||||
|
||||
TEST_P(DBBasicTestWithTsIterTombstones, ForwardIterDelete) {
|
||||
TEST_P(DBBasicTestWithTsIterTombstones, IterWithDelete) {
|
||||
constexpr size_t kNumKeysPerFile = 128;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
@@ -2544,6 +2879,7 @@ TEST_P(DBBasicTestWithTsIterTombstones, ForwardIterDelete) {
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile));
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.filter_policy = std::get<1>(GetParam());
|
||||
bbto.index_type = std::get<3>(GetParam());
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
options.num_levels = std::get<2>(GetParam());
|
||||
DestroyAndReopen(options);
|
||||
@@ -2563,7 +2899,7 @@ TEST_P(DBBasicTestWithTsIterTombstones, ForwardIterDelete) {
|
||||
}
|
||||
++key;
|
||||
} while (true);
|
||||
// Delete them all
|
||||
|
||||
ts = write_ts_strs[1];
|
||||
write_opts.timestamp = &ts;
|
||||
for (key = kMaxKey; key >= kMinKey; --key) {
|
||||
@@ -2590,6 +2926,13 @@ TEST_P(DBBasicTestWithTsIterTombstones, ForwardIterDelete) {
|
||||
ASSERT_EQ("value1" + std::to_string(key), iter->value());
|
||||
}
|
||||
ASSERT_EQ((kMaxKey - kMinKey + 1) / 2, count);
|
||||
|
||||
for (iter->SeekToLast(), count = 0, key = kMaxKey; iter->Valid();
|
||||
key -= 2, ++count, iter->Prev()) {
|
||||
ASSERT_EQ(Key1(key), iter->key());
|
||||
ASSERT_EQ("value1" + std::to_string(key), iter->value());
|
||||
}
|
||||
ASSERT_EQ((kMaxKey - kMinKey + 1) / 2, count);
|
||||
}
|
||||
Close();
|
||||
}
|
||||
@@ -2605,7 +2948,12 @@ INSTANTIATE_TEST_CASE_P(
|
||||
NewBloomFilterPolicy(10, false)),
|
||||
std::shared_ptr<const FilterPolicy>(
|
||||
NewBloomFilterPolicy(20, false))),
|
||||
::testing::Values(2, 6)));
|
||||
::testing::Values(2, 6),
|
||||
::testing::Values(
|
||||
BlockBasedTableOptions::IndexType::kBinarySearch,
|
||||
BlockBasedTableOptions::IndexType::kHashSearch,
|
||||
BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch,
|
||||
BlockBasedTableOptions::IndexType::kBinarySearchWithFirstKey)));
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
|
||||
@@ -0,0 +1,801 @@
|
||||
// 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).
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "db/write_thread.h"
|
||||
#include "port/stack_trace.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class DBWriteBufferManagerTest : public DBTestBase,
|
||||
public testing::WithParamInterface<bool> {
|
||||
public:
|
||||
DBWriteBufferManagerTest()
|
||||
: DBTestBase("/db_write_buffer_manager_test", /*env_do_fsync=*/false) {}
|
||||
bool cost_cache_;
|
||||
};
|
||||
|
||||
TEST_P(DBWriteBufferManagerTest, SharedBufferAcrossCFs1) {
|
||||
Options options = CurrentOptions();
|
||||
options.arena_block_size = 4096;
|
||||
options.write_buffer_size = 500000; // this is never hit
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(4 * 1024 * 1024, 2);
|
||||
ASSERT_LT(cache->GetUsage(), 256 * 1024);
|
||||
cost_cache_ = GetParam();
|
||||
|
||||
if (cost_cache_) {
|
||||
options.write_buffer_manager.reset(
|
||||
new WriteBufferManager(100000, cache, true));
|
||||
} else {
|
||||
options.write_buffer_manager.reset(
|
||||
new WriteBufferManager(100000, nullptr, true));
|
||||
}
|
||||
|
||||
WriteOptions wo;
|
||||
wo.disableWAL = true;
|
||||
|
||||
CreateAndReopenWithCF({"cf1", "cf2", "cf3"}, options);
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(1), wo));
|
||||
Flush(3);
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(1), wo));
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(1), wo));
|
||||
Flush(0);
|
||||
|
||||
// Write to "Default", "cf2" and "cf3".
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(30000), wo));
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(40000), wo));
|
||||
ASSERT_OK(Put(2, Key(1), DummyString(1), wo));
|
||||
|
||||
ASSERT_OK(Put(3, Key(2), DummyString(40000), wo));
|
||||
// WriteBufferManager::buffer_size_ has exceeded after the previous write is
|
||||
// completed.
|
||||
|
||||
// This make sures write will go through and if stall was in effect, it will
|
||||
// end.
|
||||
ASSERT_OK(Put(0, Key(2), DummyString(1), wo));
|
||||
}
|
||||
|
||||
// Test Single DB with multiple writer threads get blocked when
|
||||
// WriteBufferManager execeeds buffer_size_ and flush is waiting to be
|
||||
// finished.
|
||||
TEST_P(DBWriteBufferManagerTest, SharedWriteBufferAcrossCFs2) {
|
||||
Options options = CurrentOptions();
|
||||
options.arena_block_size = 4096;
|
||||
options.write_buffer_size = 500000; // this is never hit
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(4 * 1024 * 1024, 2);
|
||||
ASSERT_LT(cache->GetUsage(), 256 * 1024);
|
||||
cost_cache_ = GetParam();
|
||||
|
||||
if (cost_cache_) {
|
||||
options.write_buffer_manager.reset(
|
||||
new WriteBufferManager(100000, cache, true));
|
||||
} else {
|
||||
options.write_buffer_manager.reset(
|
||||
new WriteBufferManager(100000, nullptr, true));
|
||||
}
|
||||
WriteOptions wo;
|
||||
wo.disableWAL = true;
|
||||
|
||||
CreateAndReopenWithCF({"cf1", "cf2", "cf3"}, options);
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(1), wo));
|
||||
Flush(3);
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(1), wo));
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(1), wo));
|
||||
Flush(0);
|
||||
|
||||
// Write to "Default", "cf2" and "cf3". No flush will be triggered.
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(30000), wo));
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(40000), wo));
|
||||
ASSERT_OK(Put(2, Key(1), DummyString(1), wo));
|
||||
|
||||
ASSERT_OK(Put(3, Key(2), DummyString(40000), wo));
|
||||
// WriteBufferManager::buffer_size_ has exceeded after the previous write is
|
||||
// completed.
|
||||
|
||||
std::unordered_set<WriteThread::Writer*> w_set;
|
||||
std::vector<port::Thread> threads;
|
||||
int wait_count_db = 0;
|
||||
int num_writers = 4;
|
||||
InstrumentedMutex mutex;
|
||||
InstrumentedCondVar cv(&mutex);
|
||||
std::atomic<int> thread_num(0);
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0",
|
||||
"DBImpl::BackgroundCallFlush:start"}});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"WBMStallInterface::BlockDB", [&](void*) {
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
wait_count_db++;
|
||||
cv.SignalAll();
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"WriteThread::WriteStall::Wait", [&](void* arg) {
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
WriteThread::Writer* w = reinterpret_cast<WriteThread::Writer*>(arg);
|
||||
w_set.insert(w);
|
||||
// Allow the flush to continue if all writer threads are blocked.
|
||||
if (w_set.size() == (unsigned long)num_writers) {
|
||||
TEST_SYNC_POINT(
|
||||
"DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0");
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
bool s = true;
|
||||
|
||||
std::function<void(int)> writer = [&](int cf) {
|
||||
int a = thread_num.fetch_add(1);
|
||||
std::string key = "foo" + std::to_string(a);
|
||||
Status tmp = Put(cf, Slice(key), DummyString(1), wo);
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
s = s && tmp.ok();
|
||||
};
|
||||
|
||||
// Flow:
|
||||
// main_writer thread will write but will be blocked (as Flush will on hold,
|
||||
// buffer_size_ has exceeded, thus will create stall in effect).
|
||||
// |
|
||||
// |
|
||||
// multiple writer threads will be created to write across multiple columns
|
||||
// and they will be blocked.
|
||||
// |
|
||||
// |
|
||||
// Last writer thread will write and when its blocked it will signal Flush to
|
||||
// continue to clear the stall.
|
||||
|
||||
threads.emplace_back(writer, 1);
|
||||
// Wait untill first thread (main_writer) writing to DB is blocked and then
|
||||
// create the multiple writers which will be blocked from getting added to the
|
||||
// queue because stall is in effect.
|
||||
{
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
while (wait_count_db != 1) {
|
||||
cv.Wait();
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < num_writers; i++) {
|
||||
threads.emplace_back(writer, i % 4);
|
||||
}
|
||||
for (auto& t : threads) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
ASSERT_TRUE(s);
|
||||
|
||||
// Number of DBs blocked.
|
||||
ASSERT_EQ(wait_count_db, 1);
|
||||
// Number of Writer threads blocked.
|
||||
ASSERT_EQ(w_set.size(), num_writers);
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
// Test multiple DBs get blocked when WriteBufferManager limit exceeds and flush
|
||||
// is waiting to be finished but DBs tries to write meanwhile.
|
||||
TEST_P(DBWriteBufferManagerTest, SharedWriteBufferLimitAcrossDB) {
|
||||
std::vector<std::string> dbnames;
|
||||
std::vector<DB*> dbs;
|
||||
int num_dbs = 3;
|
||||
|
||||
for (int i = 0; i < num_dbs; i++) {
|
||||
dbs.push_back(nullptr);
|
||||
dbnames.push_back(
|
||||
test::PerThreadDBPath("db_shared_wb_db" + std::to_string(i)));
|
||||
}
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.arena_block_size = 4096;
|
||||
options.write_buffer_size = 500000; // this is never hit
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(4 * 1024 * 1024, 2);
|
||||
ASSERT_LT(cache->GetUsage(), 256 * 1024);
|
||||
cost_cache_ = GetParam();
|
||||
|
||||
if (cost_cache_) {
|
||||
options.write_buffer_manager.reset(
|
||||
new WriteBufferManager(100000, cache, true));
|
||||
} else {
|
||||
options.write_buffer_manager.reset(
|
||||
new WriteBufferManager(100000, nullptr, true));
|
||||
}
|
||||
CreateAndReopenWithCF({"cf1", "cf2"}, options);
|
||||
|
||||
for (int i = 0; i < num_dbs; i++) {
|
||||
ASSERT_OK(DestroyDB(dbnames[i], options));
|
||||
ASSERT_OK(DB::Open(options, dbnames[i], &(dbs[i])));
|
||||
}
|
||||
WriteOptions wo;
|
||||
wo.disableWAL = true;
|
||||
|
||||
for (int i = 0; i < num_dbs; i++) {
|
||||
ASSERT_OK(dbs[i]->Put(wo, Key(1), DummyString(20000)));
|
||||
}
|
||||
// Insert to db_.
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(30000), wo));
|
||||
|
||||
// WriteBufferManager Limit exceeded.
|
||||
std::vector<port::Thread> threads;
|
||||
int wait_count_db = 0;
|
||||
InstrumentedMutex mutex;
|
||||
InstrumentedCondVar cv(&mutex);
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0",
|
||||
"DBImpl::BackgroundCallFlush:start"}});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"WBMStallInterface::BlockDB", [&](void*) {
|
||||
{
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
wait_count_db++;
|
||||
cv.Signal();
|
||||
// Since this is the last DB, signal Flush to continue.
|
||||
if (wait_count_db == num_dbs + 1) {
|
||||
TEST_SYNC_POINT(
|
||||
"DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0");
|
||||
}
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
bool s = true;
|
||||
|
||||
// Write to DB.
|
||||
std::function<void(DB*)> write_db = [&](DB* db) {
|
||||
Status tmp = db->Put(wo, Key(3), DummyString(1));
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
s = s && tmp.ok();
|
||||
};
|
||||
|
||||
// Flow:
|
||||
// db_ will write and will be blocked (as Flush will on hold and will create
|
||||
// stall in effect).
|
||||
// |
|
||||
// multiple dbs writers will be created to write to that db and they will be
|
||||
// blocked.
|
||||
// |
|
||||
// |
|
||||
// Last writer will write and when its blocked it will signal Flush to
|
||||
// continue to clear the stall.
|
||||
|
||||
threads.emplace_back(write_db, db_);
|
||||
// Wait untill first DB is blocked and then create the multiple writers for
|
||||
// different DBs which will be blocked from getting added to the queue because
|
||||
// stall is in effect.
|
||||
{
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
while (wait_count_db != 1) {
|
||||
cv.Wait();
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < num_dbs; i++) {
|
||||
threads.emplace_back(write_db, dbs[i]);
|
||||
}
|
||||
for (auto& t : threads) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
ASSERT_TRUE(s);
|
||||
ASSERT_EQ(num_dbs + 1, wait_count_db);
|
||||
// Clean up DBs.
|
||||
for (int i = 0; i < num_dbs; i++) {
|
||||
ASSERT_OK(dbs[i]->Close());
|
||||
ASSERT_OK(DestroyDB(dbnames[i], options));
|
||||
delete dbs[i];
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
// Test multiple threads writing across multiple DBs and multiple columns get
|
||||
// blocked when stall by WriteBufferManager is in effect.
|
||||
TEST_P(DBWriteBufferManagerTest, SharedWriteBufferLimitAcrossDB1) {
|
||||
std::vector<std::string> dbnames;
|
||||
std::vector<DB*> dbs;
|
||||
int num_dbs = 3;
|
||||
|
||||
for (int i = 0; i < num_dbs; i++) {
|
||||
dbs.push_back(nullptr);
|
||||
dbnames.push_back(
|
||||
test::PerThreadDBPath("db_shared_wb_db" + std::to_string(i)));
|
||||
}
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.arena_block_size = 4096;
|
||||
options.write_buffer_size = 500000; // this is never hit
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(4 * 1024 * 1024, 2);
|
||||
ASSERT_LT(cache->GetUsage(), 256 * 1024);
|
||||
cost_cache_ = GetParam();
|
||||
|
||||
if (cost_cache_) {
|
||||
options.write_buffer_manager.reset(
|
||||
new WriteBufferManager(100000, cache, true));
|
||||
} else {
|
||||
options.write_buffer_manager.reset(
|
||||
new WriteBufferManager(100000, nullptr, true));
|
||||
}
|
||||
CreateAndReopenWithCF({"cf1", "cf2"}, options);
|
||||
|
||||
for (int i = 0; i < num_dbs; i++) {
|
||||
ASSERT_OK(DestroyDB(dbnames[i], options));
|
||||
ASSERT_OK(DB::Open(options, dbnames[i], &(dbs[i])));
|
||||
}
|
||||
WriteOptions wo;
|
||||
wo.disableWAL = true;
|
||||
|
||||
for (int i = 0; i < num_dbs; i++) {
|
||||
ASSERT_OK(dbs[i]->Put(wo, Key(1), DummyString(20000)));
|
||||
}
|
||||
// Insert to db_.
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(30000), wo));
|
||||
|
||||
// WriteBufferManager::buffer_size_ has exceeded after the previous write to
|
||||
// dbs[0] is completed.
|
||||
std::vector<port::Thread> threads;
|
||||
int wait_count_db = 0;
|
||||
InstrumentedMutex mutex;
|
||||
InstrumentedCondVar cv(&mutex);
|
||||
std::unordered_set<WriteThread::Writer*> w_set;
|
||||
std::vector<port::Thread> writer_threads;
|
||||
std::atomic<int> thread_num(0);
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0",
|
||||
"DBImpl::BackgroundCallFlush:start"}});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"WBMStallInterface::BlockDB", [&](void*) {
|
||||
{
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
wait_count_db++;
|
||||
thread_num.fetch_add(1);
|
||||
cv.Signal();
|
||||
// Allow the flush to continue if all writer threads are blocked.
|
||||
if (thread_num.load(std::memory_order_relaxed) == 2 * num_dbs + 1) {
|
||||
TEST_SYNC_POINT(
|
||||
"DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0");
|
||||
}
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"WriteThread::WriteStall::Wait", [&](void* arg) {
|
||||
WriteThread::Writer* w = reinterpret_cast<WriteThread::Writer*>(arg);
|
||||
{
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
w_set.insert(w);
|
||||
thread_num.fetch_add(1);
|
||||
// Allow the flush continue if all writer threads are blocked.
|
||||
if (thread_num.load(std::memory_order_relaxed) == 2 * num_dbs + 1) {
|
||||
TEST_SYNC_POINT(
|
||||
"DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0");
|
||||
}
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
bool s1 = true, s2 = true;
|
||||
// Write to multiple columns of db_.
|
||||
std::function<void(int)> write_cf = [&](int cf) {
|
||||
Status tmp = Put(cf, Key(3), DummyString(1), wo);
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
s1 = s1 && tmp.ok();
|
||||
};
|
||||
// Write to multiple DBs.
|
||||
std::function<void(DB*)> write_db = [&](DB* db) {
|
||||
Status tmp = db->Put(wo, Key(3), DummyString(1));
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
s2 = s2 && tmp.ok();
|
||||
};
|
||||
|
||||
// Flow:
|
||||
// thread will write to db_ will be blocked (as Flush will on hold,
|
||||
// buffer_size_ has exceeded and will create stall in effect).
|
||||
// |
|
||||
// |
|
||||
// multiple writers threads writing to different DBs and to db_ across
|
||||
// multiple columns will be created and they will be blocked due to stall.
|
||||
// |
|
||||
// |
|
||||
// Last writer thread will write and when its blocked it will signal Flush to
|
||||
// continue to clear the stall.
|
||||
threads.emplace_back(write_db, db_);
|
||||
// Wait untill first thread is blocked and then create the multiple writer
|
||||
// threads.
|
||||
{
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
while (wait_count_db != 1) {
|
||||
cv.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < num_dbs; i++) {
|
||||
// Write to multiple columns of db_.
|
||||
writer_threads.emplace_back(write_cf, i % 3);
|
||||
// Write to different dbs.
|
||||
threads.emplace_back(write_db, dbs[i]);
|
||||
}
|
||||
for (auto& t : threads) {
|
||||
t.join();
|
||||
}
|
||||
for (auto& t : writer_threads) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
ASSERT_TRUE(s1);
|
||||
ASSERT_TRUE(s2);
|
||||
|
||||
// Number of DBs blocked.
|
||||
ASSERT_EQ(num_dbs + 1, wait_count_db);
|
||||
// Number of Writer threads blocked.
|
||||
ASSERT_EQ(w_set.size(), num_dbs);
|
||||
// Clean up DBs.
|
||||
for (int i = 0; i < num_dbs; i++) {
|
||||
ASSERT_OK(dbs[i]->Close());
|
||||
ASSERT_OK(DestroyDB(dbnames[i], options));
|
||||
delete dbs[i];
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
// Test multiple threads writing across multiple columns of db_ by passing
|
||||
// different values to WriteOption.no_slown_down.
|
||||
TEST_P(DBWriteBufferManagerTest, MixedSlowDownOptionsSingleDB) {
|
||||
Options options = CurrentOptions();
|
||||
options.arena_block_size = 4096;
|
||||
options.write_buffer_size = 500000; // this is never hit
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(4 * 1024 * 1024, 2);
|
||||
ASSERT_LT(cache->GetUsage(), 256 * 1024);
|
||||
cost_cache_ = GetParam();
|
||||
|
||||
if (cost_cache_) {
|
||||
options.write_buffer_manager.reset(
|
||||
new WriteBufferManager(100000, cache, true));
|
||||
} else {
|
||||
options.write_buffer_manager.reset(
|
||||
new WriteBufferManager(100000, nullptr, true));
|
||||
}
|
||||
WriteOptions wo;
|
||||
wo.disableWAL = true;
|
||||
|
||||
CreateAndReopenWithCF({"cf1", "cf2", "cf3"}, options);
|
||||
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(1), wo));
|
||||
Flush(3);
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(1), wo));
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(1), wo));
|
||||
Flush(0);
|
||||
|
||||
// Write to "Default", "cf2" and "cf3". No flush will be triggered.
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(30000), wo));
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(40000), wo));
|
||||
ASSERT_OK(Put(2, Key(1), DummyString(1), wo));
|
||||
ASSERT_OK(Put(3, Key(2), DummyString(40000), wo));
|
||||
|
||||
// WriteBufferManager::buffer_size_ has exceeded after the previous write to
|
||||
// db_ is completed.
|
||||
|
||||
std::unordered_set<WriteThread::Writer*> w_slowdown_set;
|
||||
std::vector<port::Thread> threads;
|
||||
int wait_count_db = 0;
|
||||
int num_writers = 4;
|
||||
InstrumentedMutex mutex;
|
||||
InstrumentedCondVar cv(&mutex);
|
||||
std::atomic<int> thread_num(0);
|
||||
std::atomic<int> w_no_slowdown(0);
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0",
|
||||
"DBImpl::BackgroundCallFlush:start"}});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"WBMStallInterface::BlockDB", [&](void*) {
|
||||
{
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
wait_count_db++;
|
||||
cv.SignalAll();
|
||||
}
|
||||
});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"WriteThread::WriteStall::Wait", [&](void* arg) {
|
||||
{
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
WriteThread::Writer* w = reinterpret_cast<WriteThread::Writer*>(arg);
|
||||
w_slowdown_set.insert(w);
|
||||
// Allow the flush continue if all writer threads are blocked.
|
||||
if (w_slowdown_set.size() + (unsigned long)w_no_slowdown.load(
|
||||
std::memory_order_relaxed) ==
|
||||
(unsigned long)num_writers) {
|
||||
TEST_SYNC_POINT(
|
||||
"DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0");
|
||||
}
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
bool s1 = true, s2 = true;
|
||||
|
||||
std::function<void(int)> write_slow_down = [&](int cf) {
|
||||
int a = thread_num.fetch_add(1);
|
||||
std::string key = "foo" + std::to_string(a);
|
||||
WriteOptions write_op;
|
||||
write_op.no_slowdown = false;
|
||||
Status tmp = Put(cf, Slice(key), DummyString(1), write_op);
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
s1 = s1 && tmp.ok();
|
||||
};
|
||||
|
||||
std::function<void(int)> write_no_slow_down = [&](int cf) {
|
||||
int a = thread_num.fetch_add(1);
|
||||
std::string key = "foo" + std::to_string(a);
|
||||
WriteOptions write_op;
|
||||
write_op.no_slowdown = true;
|
||||
Status tmp = Put(cf, Slice(key), DummyString(1), write_op);
|
||||
{
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
s2 = s2 && !tmp.ok();
|
||||
w_no_slowdown.fetch_add(1);
|
||||
// Allow the flush continue if all writer threads are blocked.
|
||||
if (w_slowdown_set.size() +
|
||||
(unsigned long)w_no_slowdown.load(std::memory_order_relaxed) ==
|
||||
(unsigned long)num_writers) {
|
||||
TEST_SYNC_POINT(
|
||||
"DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Flow:
|
||||
// main_writer thread will write but will be blocked (as Flush will on hold,
|
||||
// buffer_size_ has exceeded, thus will create stall in effect).
|
||||
// |
|
||||
// |
|
||||
// multiple writer threads will be created to write across multiple columns
|
||||
// with different values of WriteOptions.no_slowdown. Some of them will
|
||||
// be blocked and some of them will return with Incomplete status.
|
||||
// |
|
||||
// |
|
||||
// Last writer thread will write and when its blocked/return it will signal
|
||||
// Flush to continue to clear the stall.
|
||||
threads.emplace_back(write_slow_down, 1);
|
||||
// Wait untill first thread (main_writer) writing to DB is blocked and then
|
||||
// create the multiple writers which will be blocked from getting added to the
|
||||
// queue because stall is in effect.
|
||||
{
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
while (wait_count_db != 1) {
|
||||
cv.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < num_writers; i += 2) {
|
||||
threads.emplace_back(write_no_slow_down, (i) % 4);
|
||||
threads.emplace_back(write_slow_down, (i + 1) % 4);
|
||||
}
|
||||
for (auto& t : threads) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
ASSERT_TRUE(s1);
|
||||
ASSERT_TRUE(s2);
|
||||
// Number of DBs blocked.
|
||||
ASSERT_EQ(wait_count_db, 1);
|
||||
// Number of Writer threads blocked.
|
||||
ASSERT_EQ(w_slowdown_set.size(), num_writers / 2);
|
||||
// Number of Writer threads with WriteOptions.no_slowdown = true.
|
||||
ASSERT_EQ(w_no_slowdown.load(std::memory_order_relaxed), num_writers / 2);
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
// Test multiple threads writing across multiple columns of db_ and different
|
||||
// dbs by passing different values to WriteOption.no_slown_down.
|
||||
TEST_P(DBWriteBufferManagerTest, MixedSlowDownOptionsMultipleDB) {
|
||||
std::vector<std::string> dbnames;
|
||||
std::vector<DB*> dbs;
|
||||
int num_dbs = 4;
|
||||
|
||||
for (int i = 0; i < num_dbs; i++) {
|
||||
dbs.push_back(nullptr);
|
||||
dbnames.push_back(
|
||||
test::PerThreadDBPath("db_shared_wb_db" + std::to_string(i)));
|
||||
}
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.arena_block_size = 4096;
|
||||
options.write_buffer_size = 500000; // this is never hit
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(4 * 1024 * 1024, 2);
|
||||
ASSERT_LT(cache->GetUsage(), 256 * 1024);
|
||||
cost_cache_ = GetParam();
|
||||
|
||||
if (cost_cache_) {
|
||||
options.write_buffer_manager.reset(
|
||||
new WriteBufferManager(100000, cache, true));
|
||||
} else {
|
||||
options.write_buffer_manager.reset(
|
||||
new WriteBufferManager(100000, nullptr, true));
|
||||
}
|
||||
CreateAndReopenWithCF({"cf1", "cf2"}, options);
|
||||
|
||||
for (int i = 0; i < num_dbs; i++) {
|
||||
ASSERT_OK(DestroyDB(dbnames[i], options));
|
||||
ASSERT_OK(DB::Open(options, dbnames[i], &(dbs[i])));
|
||||
}
|
||||
WriteOptions wo;
|
||||
wo.disableWAL = true;
|
||||
|
||||
for (int i = 0; i < num_dbs; i++) {
|
||||
ASSERT_OK(dbs[i]->Put(wo, Key(1), DummyString(20000)));
|
||||
}
|
||||
// Insert to db_.
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(30000), wo));
|
||||
|
||||
// WriteBufferManager::buffer_size_ has exceeded after the previous write to
|
||||
// dbs[0] is completed.
|
||||
std::vector<port::Thread> threads;
|
||||
int wait_count_db = 0;
|
||||
InstrumentedMutex mutex;
|
||||
InstrumentedCondVar cv(&mutex);
|
||||
std::unordered_set<WriteThread::Writer*> w_slowdown_set;
|
||||
std::vector<port::Thread> writer_threads;
|
||||
std::atomic<int> thread_num(0);
|
||||
std::atomic<int> w_no_slowdown(0);
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0",
|
||||
"DBImpl::BackgroundCallFlush:start"}});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"WBMStallInterface::BlockDB", [&](void*) {
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
wait_count_db++;
|
||||
cv.Signal();
|
||||
// Allow the flush continue if all writer threads are blocked.
|
||||
if (w_slowdown_set.size() +
|
||||
(unsigned long)(w_no_slowdown.load(std::memory_order_relaxed) +
|
||||
wait_count_db) ==
|
||||
(unsigned long)(2 * num_dbs + 1)) {
|
||||
TEST_SYNC_POINT(
|
||||
"DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0");
|
||||
}
|
||||
});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"WriteThread::WriteStall::Wait", [&](void* arg) {
|
||||
WriteThread::Writer* w = reinterpret_cast<WriteThread::Writer*>(arg);
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
w_slowdown_set.insert(w);
|
||||
// Allow the flush continue if all writer threads are blocked.
|
||||
if (w_slowdown_set.size() +
|
||||
(unsigned long)(w_no_slowdown.load(std::memory_order_relaxed) +
|
||||
wait_count_db) ==
|
||||
(unsigned long)(2 * num_dbs + 1)) {
|
||||
TEST_SYNC_POINT(
|
||||
"DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0");
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
bool s1 = true, s2 = true;
|
||||
std::function<void(DB*)> write_slow_down = [&](DB* db) {
|
||||
int a = thread_num.fetch_add(1);
|
||||
std::string key = "foo" + std::to_string(a);
|
||||
WriteOptions write_op;
|
||||
write_op.no_slowdown = false;
|
||||
Status tmp = db->Put(write_op, Slice(key), DummyString(1));
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
s1 = s1 && tmp.ok();
|
||||
};
|
||||
|
||||
std::function<void(DB*)> write_no_slow_down = [&](DB* db) {
|
||||
int a = thread_num.fetch_add(1);
|
||||
std::string key = "foo" + std::to_string(a);
|
||||
WriteOptions write_op;
|
||||
write_op.no_slowdown = true;
|
||||
Status tmp = db->Put(write_op, Slice(key), DummyString(1));
|
||||
{
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
s2 = s2 && !tmp.ok();
|
||||
w_no_slowdown.fetch_add(1);
|
||||
if (w_slowdown_set.size() +
|
||||
(unsigned long)(w_no_slowdown.load(std::memory_order_relaxed) +
|
||||
wait_count_db) ==
|
||||
(unsigned long)(2 * num_dbs + 1)) {
|
||||
TEST_SYNC_POINT(
|
||||
"DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Flow:
|
||||
// first thread will write but will be blocked (as Flush will on hold,
|
||||
// buffer_size_ has exceeded, thus will create stall in effect).
|
||||
// |
|
||||
// |
|
||||
// multiple writer threads will be created to write across multiple columns
|
||||
// of db_ and different DBs with different values of
|
||||
// WriteOptions.no_slowdown. Some of them will be blocked and some of them
|
||||
// will return with Incomplete status.
|
||||
// |
|
||||
// |
|
||||
// Last writer thread will write and when its blocked/return it will signal
|
||||
// Flush to continue to clear the stall.
|
||||
threads.emplace_back(write_slow_down, db_);
|
||||
// Wait untill first thread writing to DB is blocked and then
|
||||
// create the multiple writers.
|
||||
{
|
||||
InstrumentedMutexLock lock(&mutex);
|
||||
while (wait_count_db != 1) {
|
||||
cv.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < num_dbs; i += 2) {
|
||||
// Write to multiple columns of db_.
|
||||
writer_threads.emplace_back(write_slow_down, db_);
|
||||
writer_threads.emplace_back(write_no_slow_down, db_);
|
||||
// Write to different DBs.
|
||||
threads.emplace_back(write_slow_down, dbs[i]);
|
||||
threads.emplace_back(write_no_slow_down, dbs[i + 1]);
|
||||
}
|
||||
|
||||
for (auto& t : threads) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
for (auto& t : writer_threads) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
ASSERT_TRUE(s1);
|
||||
ASSERT_TRUE(s2);
|
||||
// Number of DBs blocked.
|
||||
ASSERT_EQ((num_dbs / 2) + 1, wait_count_db);
|
||||
// Number of writer threads writing to db_ blocked from getting added to the
|
||||
// queue.
|
||||
ASSERT_EQ(w_slowdown_set.size(), num_dbs / 2);
|
||||
// Number of threads with WriteOptions.no_slowdown = true.
|
||||
ASSERT_EQ(w_no_slowdown.load(std::memory_order_relaxed), num_dbs);
|
||||
|
||||
// Clean up DBs.
|
||||
for (int i = 0; i < num_dbs; i++) {
|
||||
ASSERT_OK(dbs[i]->Close());
|
||||
ASSERT_OK(DestroyDB(dbnames[i], options));
|
||||
delete dbs[i];
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBWriteBufferManagerTest, DBWriteBufferManagerTest,
|
||||
testing::Bool());
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
extern "C" {
|
||||
void RegisterCustomObjects(int argc, char** argv);
|
||||
}
|
||||
#else
|
||||
void RegisterCustomObjects(int /*argc*/, char** /*argv*/) {}
|
||||
#endif // !ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
RegisterCustomObjects(argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
+3
-2
@@ -122,7 +122,7 @@ struct ParsedInternalKey {
|
||||
|
||||
void SetTimestamp(const Slice& ts) {
|
||||
assert(ts.size() <= user_key.size());
|
||||
const char* addr = user_key.data() - ts.size();
|
||||
const char* addr = user_key.data() + user_key.size() - ts.size();
|
||||
memcpy(const_cast<char*>(addr), ts.data(), ts.size());
|
||||
}
|
||||
};
|
||||
@@ -512,6 +512,7 @@ class IterKey {
|
||||
|
||||
bool IsKeyPinned() const { return (key_ != buf_); }
|
||||
|
||||
// user_key does not have timestamp.
|
||||
void SetInternalKey(const Slice& key_prefix, const Slice& user_key,
|
||||
SequenceNumber s,
|
||||
ValueType value_type = kValueTypeForSeek,
|
||||
@@ -615,7 +616,7 @@ class IterKey {
|
||||
void EnlargeBuffer(size_t key_size);
|
||||
};
|
||||
|
||||
// Convert from a SliceTranform of user keys, to a SliceTransform of
|
||||
// Convert from a SliceTransform of user keys, to a SliceTransform of
|
||||
// user keys.
|
||||
class InternalKeySliceTransform : public SliceTransform {
|
||||
public:
|
||||
|
||||
+63
-2
@@ -4,9 +4,11 @@
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
//
|
||||
#include "db/error_handler.h"
|
||||
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/event_helpers.h"
|
||||
#include "file/sst_file_manager_impl.h"
|
||||
#include "logging/logging.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -274,6 +276,12 @@ const Status& ErrorHandler::SetBGError(const Status& bg_err,
|
||||
return bg_err;
|
||||
}
|
||||
|
||||
if (bg_error_stats_ != nullptr) {
|
||||
RecordTick(bg_error_stats_.get(), ERROR_HANDLER_BG_ERROR_COUNT);
|
||||
}
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
"ErrorHandler: Set regular background error\n");
|
||||
|
||||
bool paranoid = db_options_.paranoid_checks;
|
||||
Status::Severity sev = Status::Severity::kFatalError;
|
||||
Status new_bg_err;
|
||||
@@ -399,6 +407,13 @@ const Status& ErrorHandler::SetBGError(const IOStatus& bg_io_err,
|
||||
if (recovery_in_prog_ && recovery_error_.ok()) {
|
||||
recovery_error_ = bg_err;
|
||||
}
|
||||
if (bg_error_stats_ != nullptr) {
|
||||
RecordTick(bg_error_stats_.get(), ERROR_HANDLER_BG_ERROR_COUNT);
|
||||
RecordTick(bg_error_stats_.get(), ERROR_HANDLER_BG_IO_ERROR_COUNT);
|
||||
}
|
||||
ROCKS_LOG_INFO(
|
||||
db_options_.info_log,
|
||||
"ErrorHandler: Set background IO error as unrecoverable error\n");
|
||||
EventHelpers::NotifyOnBackgroundError(db_options_.listeners, reason,
|
||||
&bg_err, db_mutex_, &auto_recovery);
|
||||
recover_context_ = context;
|
||||
@@ -416,12 +431,26 @@ const Status& ErrorHandler::SetBGError(const IOStatus& bg_io_err,
|
||||
EventHelpers::NotifyOnBackgroundError(db_options_.listeners, reason,
|
||||
&new_bg_io_err, db_mutex_,
|
||||
&auto_recovery);
|
||||
if (bg_error_stats_ != nullptr) {
|
||||
RecordTick(bg_error_stats_.get(), ERROR_HANDLER_BG_ERROR_COUNT);
|
||||
RecordTick(bg_error_stats_.get(), ERROR_HANDLER_BG_IO_ERROR_COUNT);
|
||||
RecordTick(bg_error_stats_.get(),
|
||||
ERROR_HANDLER_BG_RETRYABLE_IO_ERROR_COUNT);
|
||||
}
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
"ErrorHandler: Set background retryable IO error\n");
|
||||
if (BackgroundErrorReason::kCompaction == reason) {
|
||||
// We map the retryable IO error during compaction to soft error. Since
|
||||
// compaction can reschedule by itself. We will not set the BG error in
|
||||
// this case
|
||||
// TODO: a better way to set or clean the retryable IO error which
|
||||
// happens during compaction SST file write.
|
||||
if (bg_error_stats_ != nullptr) {
|
||||
RecordTick(bg_error_stats_.get(), ERROR_HANDLER_AUTORESUME_COUNT);
|
||||
}
|
||||
ROCKS_LOG_INFO(
|
||||
db_options_.info_log,
|
||||
"ErrorHandler: Compaction will schedule by itself to resume\n");
|
||||
return bg_error_;
|
||||
} else if (BackgroundErrorReason::kFlushNoWAL == reason ||
|
||||
BackgroundErrorReason::kManifestWriteNoWAL == reason) {
|
||||
@@ -455,6 +484,9 @@ const Status& ErrorHandler::SetBGError(const IOStatus& bg_io_err,
|
||||
return StartRecoverFromRetryableBGIOError(bg_io_err);
|
||||
}
|
||||
} else {
|
||||
if (bg_error_stats_ != nullptr) {
|
||||
RecordTick(bg_error_stats_.get(), ERROR_HANDLER_BG_IO_ERROR_COUNT);
|
||||
}
|
||||
return SetBGError(new_bg_io_err, reason);
|
||||
}
|
||||
}
|
||||
@@ -603,7 +635,12 @@ const Status& ErrorHandler::StartRecoverFromRetryableBGIOError(
|
||||
// Auto resume BG error is not enabled, directly return bg_error_.
|
||||
return bg_error_;
|
||||
}
|
||||
|
||||
if (bg_error_stats_ != nullptr) {
|
||||
RecordTick(bg_error_stats_.get(), ERROR_HANDLER_AUTORESUME_COUNT);
|
||||
}
|
||||
ROCKS_LOG_INFO(
|
||||
db_options_.info_log,
|
||||
"ErrorHandler: Call StartRecoverFromRetryableBGIOError to resume\n");
|
||||
if (recovery_thread_) {
|
||||
// In this case, if recovery_in_prog_ is false, current thread should
|
||||
// wait the previous recover thread to finish and create a new thread
|
||||
@@ -642,6 +679,7 @@ void ErrorHandler::RecoverFromRetryableBGIOError() {
|
||||
DBRecoverContext context = recover_context_;
|
||||
int resume_count = db_options_.max_bgerror_resume_count;
|
||||
uint64_t wait_interval = db_options_.bgerror_resume_retry_interval;
|
||||
uint64_t retry_count = 0;
|
||||
// Recover from the retryable error. Create a separate thread to do it.
|
||||
while (resume_count > 0) {
|
||||
if (end_recovery_) {
|
||||
@@ -651,15 +689,24 @@ void ErrorHandler::RecoverFromRetryableBGIOError() {
|
||||
TEST_SYNC_POINT("RecoverFromRetryableBGIOError:BeforeResume1");
|
||||
recovery_io_error_ = IOStatus::OK();
|
||||
recovery_error_ = Status::OK();
|
||||
retry_count++;
|
||||
Status s = db_->ResumeImpl(context);
|
||||
TEST_SYNC_POINT("RecoverFromRetryableBGIOError:AfterResume0");
|
||||
TEST_SYNC_POINT("RecoverFromRetryableBGIOError:AfterResume1");
|
||||
if (bg_error_stats_ != nullptr) {
|
||||
RecordTick(bg_error_stats_.get(),
|
||||
ERROR_HANDLER_AUTORESUME_RETRY_TOTAL_COUNT);
|
||||
}
|
||||
if (s.IsShutdownInProgress() ||
|
||||
bg_error_.severity() >= Status::Severity::kFatalError) {
|
||||
// If DB shutdown in progress or the error severity is higher than
|
||||
// Hard Error, stop auto resume and returns.
|
||||
TEST_SYNC_POINT("RecoverFromRetryableBGIOError:RecoverFail0");
|
||||
recovery_in_prog_ = false;
|
||||
if (bg_error_stats_ != nullptr) {
|
||||
RecordInHistogram(bg_error_stats_.get(),
|
||||
ERROR_HANDLER_AUTORESUME_RETRY_COUNT, retry_count);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!recovery_io_error_.ok() &&
|
||||
@@ -670,7 +717,7 @@ void ErrorHandler::RecoverFromRetryableBGIOError() {
|
||||
// a period of time and redo auto resume if it is allowed.
|
||||
TEST_SYNC_POINT("RecoverFromRetryableBGIOError:BeforeWait0");
|
||||
TEST_SYNC_POINT("RecoverFromRetryableBGIOError:BeforeWait1");
|
||||
int64_t wait_until = db_->clock_->NowMicros() + wait_interval;
|
||||
int64_t wait_until = db_options_.clock->NowMicros() + wait_interval;
|
||||
cv_.TimedWait(wait_until);
|
||||
TEST_SYNC_POINT("RecoverFromRetryableBGIOError:AfterWait0");
|
||||
} else {
|
||||
@@ -686,6 +733,12 @@ void ErrorHandler::RecoverFromRetryableBGIOError() {
|
||||
bg_error_.PermitUncheckedError();
|
||||
EventHelpers::NotifyOnErrorRecoveryCompleted(db_options_.listeners,
|
||||
old_bg_error, db_mutex_);
|
||||
if (bg_error_stats_ != nullptr) {
|
||||
RecordTick(bg_error_stats_.get(),
|
||||
ERROR_HANDLER_AUTORESUME_SUCCESS_COUNT);
|
||||
RecordInHistogram(bg_error_stats_.get(),
|
||||
ERROR_HANDLER_AUTORESUME_RETRY_COUNT, retry_count);
|
||||
}
|
||||
recovery_in_prog_ = false;
|
||||
if (soft_error_no_bg_work_) {
|
||||
soft_error_no_bg_work_ = false;
|
||||
@@ -696,6 +749,10 @@ void ErrorHandler::RecoverFromRetryableBGIOError() {
|
||||
// In this case: 1) recovery_io_error is more serious or not retryable
|
||||
// 2) other Non IO recovery_error happens. The auto recovery stops.
|
||||
recovery_in_prog_ = false;
|
||||
if (bg_error_stats_ != nullptr) {
|
||||
RecordInHistogram(bg_error_stats_.get(),
|
||||
ERROR_HANDLER_AUTORESUME_RETRY_COUNT, retry_count);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -703,6 +760,10 @@ void ErrorHandler::RecoverFromRetryableBGIOError() {
|
||||
}
|
||||
recovery_in_prog_ = false;
|
||||
TEST_SYNC_POINT("RecoverFromRetryableBGIOError:LoopOut");
|
||||
if (bg_error_stats_ != nullptr) {
|
||||
RecordInHistogram(bg_error_stats_.get(),
|
||||
ERROR_HANDLER_AUTORESUME_RETRY_COUNT, retry_count);
|
||||
}
|
||||
return;
|
||||
#else
|
||||
return;
|
||||
|
||||
+6
-2
@@ -37,7 +37,8 @@ class ErrorHandler {
|
||||
db_mutex_(db_mutex),
|
||||
auto_recovery_(false),
|
||||
recovery_in_prog_(false),
|
||||
soft_error_no_bg_work_(false) {
|
||||
soft_error_no_bg_work_(false),
|
||||
bg_error_stats_(db_options.statistics) {
|
||||
// Clear the checked flag for uninitialized errors
|
||||
bg_error_.PermitUncheckedError();
|
||||
recovery_error_.PermitUncheckedError();
|
||||
@@ -102,12 +103,15 @@ class ErrorHandler {
|
||||
bool auto_recovery_;
|
||||
bool recovery_in_prog_;
|
||||
// A flag to indicate that for the soft error, we should not allow any
|
||||
// backrgound work execpt the work is from recovery.
|
||||
// background work except the work is from recovery.
|
||||
bool soft_error_no_bg_work_;
|
||||
|
||||
// Used to store the context for recover, such as flush reason.
|
||||
DBRecoverContext recover_context_;
|
||||
|
||||
// The pointer of DB statistics.
|
||||
std::shared_ptr<Statistics> bg_error_stats_;
|
||||
|
||||
Status OverrideNoSpaceError(const Status& bg_error, bool* auto_recovery);
|
||||
void RecoverFromNoSpace();
|
||||
const Status& StartRecoverFromRetryableBGIOError(const IOStatus& io_error);
|
||||
|
||||
+199
-240
@@ -158,6 +158,7 @@ TEST_F(DBErrorHandlingFSTest, FLushWriteError) {
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
options.listeners.emplace_back(listener);
|
||||
options.statistics = CreateDBStatistics();
|
||||
Status s;
|
||||
|
||||
listener->EnableAutoRecovery(false);
|
||||
@@ -174,13 +175,25 @@ TEST_F(DBErrorHandlingFSTest, FLushWriteError) {
|
||||
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));
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_EQ("val", Get(Key(0)));
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWritRetryableError) {
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWriteRetryableError) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
Options options = GetDefaultOptions();
|
||||
@@ -188,6 +201,7 @@ TEST_F(DBErrorHandlingFSTest, FLushWritRetryableError) {
|
||||
options.create_if_missing = true;
|
||||
options.listeners.emplace_back(listener);
|
||||
options.max_bgerror_resume_count = 0;
|
||||
options.statistics = CreateDBStatistics();
|
||||
Status s;
|
||||
|
||||
listener->EnableAutoRecovery(false);
|
||||
@@ -202,11 +216,23 @@ TEST_F(DBErrorHandlingFSTest, FLushWritRetryableError) {
|
||||
[&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError);
|
||||
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(1, 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));
|
||||
Reopen(options);
|
||||
ASSERT_EQ("val1", Get(Key(1)));
|
||||
|
||||
@@ -216,7 +242,7 @@ TEST_F(DBErrorHandlingFSTest, FLushWritRetryableError) {
|
||||
[&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
s = dbfull()->Resume();
|
||||
@@ -230,7 +256,7 @@ TEST_F(DBErrorHandlingFSTest, FLushWritRetryableError) {
|
||||
[&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
s = dbfull()->Resume();
|
||||
@@ -241,7 +267,7 @@ TEST_F(DBErrorHandlingFSTest, FLushWritRetryableError) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWritFileScopeError) {
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWriteFileScopeError) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
Options options = GetDefaultOptions();
|
||||
@@ -266,7 +292,7 @@ TEST_F(DBErrorHandlingFSTest, FLushWritFileScopeError) {
|
||||
[&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
s = dbfull()->Resume();
|
||||
@@ -280,7 +306,7 @@ TEST_F(DBErrorHandlingFSTest, FLushWritFileScopeError) {
|
||||
[&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
s = dbfull()->Resume();
|
||||
@@ -294,7 +320,7 @@ TEST_F(DBErrorHandlingFSTest, FLushWritFileScopeError) {
|
||||
[&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
s = dbfull()->Resume();
|
||||
@@ -314,7 +340,7 @@ TEST_F(DBErrorHandlingFSTest, FLushWritFileScopeError) {
|
||||
[&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
s = dbfull()->Resume();
|
||||
@@ -325,6 +351,95 @@ TEST_F(DBErrorHandlingFSTest, FLushWritFileScopeError) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWALWriteRetryableError) {
|
||||
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 = 0;
|
||||
Status s;
|
||||
|
||||
IOStatus error_msg = IOStatus::IOError("Retryable IO Error");
|
||||
error_msg.SetRetryable(true);
|
||||
|
||||
listener->EnableAutoRecovery(false);
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::SyncClosedLogs:Start",
|
||||
[&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
CreateAndReopenWithCF({"pikachu, sdfsdfsdf"}, options);
|
||||
|
||||
WriteOptions wo = WriteOptions();
|
||||
wo.disableWAL = false;
|
||||
ASSERT_OK(Put(Key(1), "val1", wo));
|
||||
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
auto cfh = dbfull()->GetColumnFamilyHandle(1);
|
||||
s = dbfull()->DropColumnFamily(cfh);
|
||||
|
||||
s = dbfull()->Resume();
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ("val1", Get(Key(1)));
|
||||
ASSERT_OK(Put(Key(3), "val3", wo));
|
||||
ASSERT_EQ("val3", Get(Key(3)));
|
||||
s = Flush();
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ("val3", Get(Key(3)));
|
||||
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWALAtomicWriteRetryableError) {
|
||||
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 = 0;
|
||||
options.atomic_flush = true;
|
||||
Status s;
|
||||
|
||||
IOStatus error_msg = IOStatus::IOError("Retryable IO Error");
|
||||
error_msg.SetRetryable(true);
|
||||
|
||||
listener->EnableAutoRecovery(false);
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::SyncClosedLogs:Start",
|
||||
[&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
CreateAndReopenWithCF({"pikachu, sdfsdfsdf"}, options);
|
||||
|
||||
WriteOptions wo = WriteOptions();
|
||||
wo.disableWAL = false;
|
||||
ASSERT_OK(Put(Key(1), "val1", wo));
|
||||
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
auto cfh = dbfull()->GetColumnFamilyHandle(1);
|
||||
s = dbfull()->DropColumnFamily(cfh);
|
||||
|
||||
s = dbfull()->Resume();
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ("val1", Get(Key(1)));
|
||||
ASSERT_OK(Put(Key(3), "val3", wo));
|
||||
ASSERT_EQ("val3", Get(Key(3)));
|
||||
s = Flush();
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ("val3", Get(Key(3)));
|
||||
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableError1) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
@@ -333,6 +448,7 @@ TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableError1) {
|
||||
options.create_if_missing = true;
|
||||
options.listeners.emplace_back(listener);
|
||||
options.max_bgerror_resume_count = 0;
|
||||
options.statistics = CreateDBStatistics();
|
||||
Status s;
|
||||
|
||||
listener->EnableAutoRecovery(false);
|
||||
@@ -363,11 +479,23 @@ TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableError1) {
|
||||
s = Flush();
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ("val3", Get(Key(3)));
|
||||
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(1, 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, FLushWritNoWALRetryableError2) {
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWriteNoWALRetryableError2) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
Options options = GetDefaultOptions();
|
||||
@@ -410,7 +538,7 @@ TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableError2) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableError3) {
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWriteNoWALRetryableError3) {
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
Options options = GetDefaultOptions();
|
||||
@@ -521,7 +649,7 @@ TEST_F(DBErrorHandlingFSTest, ManifestWriteRetryableError) {
|
||||
[&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError);
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
@@ -567,7 +695,7 @@ TEST_F(DBErrorHandlingFSTest, ManifestWriteFileScopeError) {
|
||||
[&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError);
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
@@ -1010,6 +1138,7 @@ TEST_F(DBErrorHandlingFSTest, AutoRecoverFlushError) {
|
||||
options.env = fault_env_.get();
|
||||
options.create_if_missing = true;
|
||||
options.listeners.emplace_back(listener);
|
||||
options.statistics = CreateDBStatistics();
|
||||
Status s;
|
||||
|
||||
listener->EnableAutoRecovery();
|
||||
@@ -1028,6 +1157,18 @@ TEST_F(DBErrorHandlingFSTest, AutoRecoverFlushError) {
|
||||
|
||||
s = Put(Key(1), "val");
|
||||
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));
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_EQ("val", Get(Key(0)));
|
||||
@@ -1557,7 +1698,7 @@ TEST_F(DBErrorHandlingFSTest, MultiDBVariousErrors) {
|
||||
// to soft error and trigger auto resume. During auto resume, SwitchMemtable
|
||||
// is disabled to avoid small SST tables. Write can still be applied before
|
||||
// the bg error is cleaned unless the memtable is full.
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableeErrorAutoRecover1) {
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableErrorAutoRecover1) {
|
||||
// Activate the FS before the first resume
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
@@ -1567,6 +1708,7 @@ TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableeErrorAutoRecover1) {
|
||||
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);
|
||||
@@ -1594,6 +1736,22 @@ TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableeErrorAutoRecover1) {
|
||||
ASSERT_EQ("val1", Get(Key(1)));
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
ASSERT_EQ(3, options.statistics->getAndResetTickerCount(
|
||||
ERROR_HANDLER_BG_ERROR_COUNT));
|
||||
ASSERT_EQ(3, options.statistics->getAndResetTickerCount(
|
||||
ERROR_HANDLER_BG_IO_ERROR_COUNT));
|
||||
ASSERT_EQ(3, options.statistics->getAndResetTickerCount(
|
||||
ERROR_HANDLER_BG_RETRYABLE_IO_ERROR_COUNT));
|
||||
ASSERT_EQ(1, options.statistics->getAndResetTickerCount(
|
||||
ERROR_HANDLER_AUTORESUME_COUNT));
|
||||
ASSERT_LE(0, options.statistics->getAndResetTickerCount(
|
||||
ERROR_HANDLER_AUTORESUME_RETRY_TOTAL_COUNT));
|
||||
ASSERT_LE(0, options.statistics->getAndResetTickerCount(
|
||||
ERROR_HANDLER_AUTORESUME_SUCCESS_COUNT));
|
||||
HistogramData autoresume_retry;
|
||||
options.statistics->histogramData(ERROR_HANDLER_AUTORESUME_RETRY_COUNT,
|
||||
&autoresume_retry);
|
||||
ASSERT_GE(autoresume_retry.max, 0);
|
||||
ASSERT_OK(Put(Key(2), "val2", wo));
|
||||
s = Flush();
|
||||
// Since auto resume fails, the bg error is not cleand, flush will
|
||||
@@ -1610,7 +1768,7 @@ TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableeErrorAutoRecover1) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableeErrorAutoRecover2) {
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableErrorAutoRecover2) {
|
||||
// Activate the FS before the first resume
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
@@ -1620,6 +1778,7 @@ TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableeErrorAutoRecover2) {
|
||||
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);
|
||||
@@ -1643,6 +1802,22 @@ TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableeErrorAutoRecover2) {
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
ASSERT_EQ(listener->WaitForRecovery(5000000), true);
|
||||
ASSERT_EQ("val1", Get(Key(1)));
|
||||
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(1, options.statistics->getAndResetTickerCount(
|
||||
ERROR_HANDLER_BG_RETRYABLE_IO_ERROR_COUNT));
|
||||
ASSERT_EQ(1, options.statistics->getAndResetTickerCount(
|
||||
ERROR_HANDLER_AUTORESUME_COUNT));
|
||||
ASSERT_LE(0, options.statistics->getAndResetTickerCount(
|
||||
ERROR_HANDLER_AUTORESUME_RETRY_TOTAL_COUNT));
|
||||
ASSERT_LE(0, options.statistics->getAndResetTickerCount(
|
||||
ERROR_HANDLER_AUTORESUME_SUCCESS_COUNT));
|
||||
HistogramData autoresume_retry;
|
||||
options.statistics->histogramData(ERROR_HANDLER_AUTORESUME_RETRY_COUNT,
|
||||
&autoresume_retry);
|
||||
ASSERT_GE(autoresume_retry.max, 0);
|
||||
ASSERT_OK(Put(Key(2), "val2", wo));
|
||||
s = Flush();
|
||||
// Since auto resume is successful, the bg error is cleaned, flush will
|
||||
@@ -1652,56 +1827,7 @@ TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableeErrorAutoRecover2) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, DISABLED_FLushWritRetryableeErrorAutoRecover1) {
|
||||
// Fail the first resume and make the second resume successful
|
||||
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
|
||||
Status s;
|
||||
|
||||
listener->EnableAutoRecovery(false);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
IOStatus error_msg = IOStatus::IOError("Retryable IO Error");
|
||||
error_msg.SetRetryable(true);
|
||||
|
||||
ASSERT_OK(Put(Key(1), "val1"));
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"RecoverFromRetryableBGIOError:BeforeWait0",
|
||||
"FLushWritRetryableeErrorAutoRecover1:0"},
|
||||
{"FLushWritRetryableeErrorAutoRecover1:1",
|
||||
"RecoverFromRetryableBGIOError:BeforeWait1"},
|
||||
{"RecoverFromRetryableBGIOError:RecoverSuccess",
|
||||
"FLushWritRetryableeErrorAutoRecover1:2"}});
|
||||
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);
|
||||
TEST_SYNC_POINT("FLushWritRetryableeErrorAutoRecover1:0");
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
TEST_SYNC_POINT("FLushWritRetryableeErrorAutoRecover1:1");
|
||||
TEST_SYNC_POINT("FLushWritRetryableeErrorAutoRecover1:2");
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
ASSERT_EQ("val1", Get(Key(1)));
|
||||
Reopen(options);
|
||||
ASSERT_EQ("val1", Get(Key(1)));
|
||||
ASSERT_OK(Put(Key(2), "val2"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ("val2", Get(Key(2)));
|
||||
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWritRetryableeErrorAutoRecover2) {
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWritRetryableErrorAutoRecover1) {
|
||||
// Activate the FS before the first resume
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
@@ -1726,7 +1852,7 @@ TEST_F(DBErrorHandlingFSTest, FLushWritRetryableeErrorAutoRecover2) {
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
ASSERT_EQ(listener->WaitForRecovery(5000000), true);
|
||||
@@ -1741,7 +1867,7 @@ TEST_F(DBErrorHandlingFSTest, FLushWritRetryableeErrorAutoRecover2) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWritRetryableeErrorAutoRecover3) {
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWritRetryableErrorAutoRecover2) {
|
||||
// Fail all the resume and let user to resume
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
@@ -1761,18 +1887,18 @@ TEST_F(DBErrorHandlingFSTest, FLushWritRetryableeErrorAutoRecover3) {
|
||||
|
||||
ASSERT_OK(Put(Key(1), "val1"));
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"FLushWritRetryableeErrorAutoRecover3:0",
|
||||
{{"FLushWritRetryableeErrorAutoRecover2:0",
|
||||
"RecoverFromRetryableBGIOError:BeforeStart"},
|
||||
{"RecoverFromRetryableBGIOError:LoopOut",
|
||||
"FLushWritRetryableeErrorAutoRecover3:1"}});
|
||||
"FLushWritRetryableeErrorAutoRecover2:1"}});
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BuildTable:BeforeFinishBuildTable",
|
||||
[&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
TEST_SYNC_POINT("FLushWritRetryableeErrorAutoRecover3:0");
|
||||
TEST_SYNC_POINT("FLushWritRetryableeErrorAutoRecover3:1");
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError);
|
||||
TEST_SYNC_POINT("FLushWritRetryableeErrorAutoRecover2:0");
|
||||
TEST_SYNC_POINT("FLushWritRetryableeErrorAutoRecover2:1");
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
@@ -1790,173 +1916,6 @@ TEST_F(DBErrorHandlingFSTest, FLushWritRetryableeErrorAutoRecover3) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, DISABLED_FLushWritRetryableeErrorAutoRecover4) {
|
||||
// Fail the first resume and does not do resume second time because
|
||||
// the IO error severity is Fatal Error and not Retryable.
|
||||
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 = 10; // 0.1 second
|
||||
Status s;
|
||||
|
||||
listener->EnableAutoRecovery(false);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
IOStatus error_msg = IOStatus::IOError("Retryable IO Error");
|
||||
error_msg.SetRetryable(true);
|
||||
IOStatus nr_msg = IOStatus::IOError("No Retryable Fatal IO Error");
|
||||
nr_msg.SetRetryable(false);
|
||||
|
||||
ASSERT_OK(Put(Key(1), "val1"));
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"RecoverFromRetryableBGIOError:BeforeStart",
|
||||
"FLushWritRetryableeErrorAutoRecover4:0"},
|
||||
{"FLushWritRetryableeErrorAutoRecover4:2",
|
||||
"RecoverFromRetryableBGIOError:RecoverFail0"}});
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BuildTable:BeforeFinishBuildTable",
|
||||
[&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); });
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"RecoverFromRetryableBGIOError:BeforeResume1",
|
||||
[&](void*) { fault_fs_->SetFilesystemActive(false, nr_msg); });
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
TEST_SYNC_POINT("FLushWritRetryableeErrorAutoRecover4:0");
|
||||
TEST_SYNC_POINT("FLushWritRetryableeErrorAutoRecover4:2");
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
// Even the FS is recoverd, due to the Fatal Error in bg_error_ the resume
|
||||
// and flush will all fail.
|
||||
ASSERT_EQ("val1", Get(Key(1)));
|
||||
ASSERT_NOK(dbfull()->Resume());
|
||||
ASSERT_EQ("val1", Get(Key(1)));
|
||||
ASSERT_OK(Put(Key(2), "val2"));
|
||||
ASSERT_NOK(Flush());
|
||||
ASSERT_EQ("NOT_FOUND", Get(Key(2)));
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_EQ("val1", Get(Key(1)));
|
||||
ASSERT_OK(Put(Key(2), "val2"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ("val2", Get(Key(2)));
|
||||
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, DISABLED_FLushWritRetryableeErrorAutoRecover5) {
|
||||
// During the resume, call DB->CLose, make sure the resume thread exist
|
||||
// before close continues. Due to the shutdown, the resume is not successful
|
||||
// and the FS does not become active, so close status is still IO error
|
||||
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 = 10; // 0.1 second
|
||||
Status s;
|
||||
|
||||
listener->EnableAutoRecovery(false);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
IOStatus error_msg = IOStatus::IOError("Retryable IO Error");
|
||||
error_msg.SetRetryable(true);
|
||||
|
||||
ASSERT_OK(Put(Key(1), "val1"));
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"RecoverFromRetryableBGIOError:BeforeStart",
|
||||
"FLushWritRetryableeErrorAutoRecover5:0"}});
|
||||
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);
|
||||
TEST_SYNC_POINT("FLushWritRetryableeErrorAutoRecover5:0");
|
||||
// The first resume will cause recovery_error and its severity is the
|
||||
// Fatal error
|
||||
s = dbfull()->Close();
|
||||
ASSERT_NOK(s);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_NE("val1", Get(Key(1)));
|
||||
ASSERT_OK(Put(Key(2), "val2"));
|
||||
s = Flush();
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ("val2", Get(Key(2)));
|
||||
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWritRetryableeErrorAutoRecover6) {
|
||||
// During the resume, call DB->CLose, make sure the resume thread exist
|
||||
// before close continues. Due to the shutdown, the resume is not successful
|
||||
// and the FS does not become active, so close status is still IO error
|
||||
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 = 10; // 0.1 second
|
||||
Status s;
|
||||
|
||||
listener->EnableAutoRecovery(false);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
IOStatus error_msg = IOStatus::IOError("Retryable IO Error");
|
||||
error_msg.SetRetryable(true);
|
||||
|
||||
ASSERT_OK(Put(Key(1), "val1"));
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"FLushWritRetryableeErrorAutoRecover6:0",
|
||||
"RecoverFromRetryableBGIOError:BeforeStart"},
|
||||
{"RecoverFromRetryableBGIOError:BeforeWait0",
|
||||
"FLushWritRetryableeErrorAutoRecover6:1"},
|
||||
{"FLushWritRetryableeErrorAutoRecover6:2",
|
||||
"RecoverFromRetryableBGIOError:BeforeWait1"},
|
||||
{"RecoverFromRetryableBGIOError:AfterWait0",
|
||||
"FLushWritRetryableeErrorAutoRecover6:3"}});
|
||||
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);
|
||||
TEST_SYNC_POINT("FLushWritRetryableeErrorAutoRecover6:0");
|
||||
TEST_SYNC_POINT("FLushWritRetryableeErrorAutoRecover6:1");
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
TEST_SYNC_POINT("FLushWritRetryableeErrorAutoRecover6:2");
|
||||
TEST_SYNC_POINT("FLushWritRetryableeErrorAutoRecover6:3");
|
||||
// The first resume will cause recovery_error and its severity is the
|
||||
// Fatal error
|
||||
s = dbfull()->Close();
|
||||
ASSERT_OK(s);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_EQ("val1", Get(Key(1)));
|
||||
ASSERT_OK(Put(Key(2), "val2"));
|
||||
s = Flush();
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ("val2", Get(Key(2)));
|
||||
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, ManifestWriteRetryableErrorAutoRecover) {
|
||||
// Fail the first resume and let the second resume be successful
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
@@ -1993,7 +1952,7 @@ TEST_F(DBErrorHandlingFSTest, ManifestWriteRetryableErrorAutoRecover) {
|
||||
[&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError);
|
||||
TEST_SYNC_POINT("ManifestWriteRetryableErrorAutoRecover:0");
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
+6
-2
@@ -125,8 +125,12 @@ void EventHelpers::LogAndNotifyTableFileCreationFinished(
|
||||
<< table_properties.compression_options << "creation_time"
|
||||
<< table_properties.creation_time << "oldest_key_time"
|
||||
<< table_properties.oldest_key_time << "file_creation_time"
|
||||
<< table_properties.file_creation_time << "db_id"
|
||||
<< table_properties.db_id << "db_session_id"
|
||||
<< table_properties.file_creation_time
|
||||
<< "slow_compression_estimated_data_size"
|
||||
<< table_properties.slow_compression_estimated_data_size
|
||||
<< "fast_compression_estimated_data_size"
|
||||
<< table_properties.fast_compression_estimated_data_size
|
||||
<< "db_id" << table_properties.db_id << "db_session_id"
|
||||
<< table_properties.db_session_id;
|
||||
|
||||
// user collected properties
|
||||
|
||||
@@ -1542,6 +1542,44 @@ TEST_F(ExternalSSTFileBasicTest, OverlappingFiles) {
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(0));
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileBasicTest, IngestFileAfterDBPut) {
|
||||
// Repro https://github.com/facebook/rocksdb/issues/6245.
|
||||
// Flush three files to L0. Ingest one more file to trigger L0->L1 compaction
|
||||
// via trivial move. The bug happened when L1 files were incorrectly sorted
|
||||
// resulting in an old value for "k" returned by `Get()`.
|
||||
Options options = CurrentOptions();
|
||||
|
||||
ASSERT_OK(Put("k", "a"));
|
||||
Flush();
|
||||
ASSERT_OK(Put("k", "a"));
|
||||
Flush();
|
||||
ASSERT_OK(Put("k", "a"));
|
||||
Flush();
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
|
||||
// Current file size should be 0 after sst_file_writer init and before open a
|
||||
// file.
|
||||
ASSERT_EQ(sst_file_writer.FileSize(), 0);
|
||||
|
||||
std::string file1 = sst_files_dir_ + "file1.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file1));
|
||||
ASSERT_OK(sst_file_writer.Put("k", "b"));
|
||||
|
||||
ExternalSstFileInfo file1_info;
|
||||
Status s = sst_file_writer.Finish(&file1_info);
|
||||
ASSERT_OK(s) << s.ToString();
|
||||
|
||||
// Current file size should be non-zero after success write.
|
||||
ASSERT_GT(sst_file_writer.FileSize(), 0);
|
||||
|
||||
IngestExternalFileOptions ifo;
|
||||
s = db_->IngestExternalFile({file1}, ifo);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
ASSERT_EQ(Get("k"), "b");
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(ExternalSSTFileBasicTest, ExternalSSTFileBasicTest,
|
||||
testing::Values(std::make_tuple(true, true),
|
||||
std::make_tuple(true, false),
|
||||
|
||||
@@ -40,16 +40,25 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
files_to_ingest_.push_back(file_to_ingest);
|
||||
}
|
||||
|
||||
for (const IngestedFileInfo& f : files_to_ingest_) {
|
||||
if (f.cf_id !=
|
||||
if (file_to_ingest.cf_id !=
|
||||
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily &&
|
||||
f.cf_id != cfd_->GetID()) {
|
||||
file_to_ingest.cf_id != cfd_->GetID()) {
|
||||
return Status::InvalidArgument(
|
||||
"External file column family id don't match");
|
||||
}
|
||||
|
||||
if (file_to_ingest.num_entries == 0 &&
|
||||
file_to_ingest.num_range_deletions == 0) {
|
||||
return Status::InvalidArgument("File contain no entries");
|
||||
}
|
||||
|
||||
if (!file_to_ingest.smallest_internal_key.Valid() ||
|
||||
!file_to_ingest.largest_internal_key.Valid()) {
|
||||
return Status::Corruption("Generated table have corrupted keys");
|
||||
}
|
||||
|
||||
files_to_ingest_.emplace_back(std::move(file_to_ingest));
|
||||
}
|
||||
|
||||
const Comparator* ucmp = cfd_->internal_comparator().user_comparator();
|
||||
@@ -83,16 +92,6 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
return Status::NotSupported("Files have overlapping ranges");
|
||||
}
|
||||
|
||||
for (IngestedFileInfo& f : files_to_ingest_) {
|
||||
if (f.num_entries == 0 && f.num_range_deletions == 0) {
|
||||
return Status::InvalidArgument("File contain no entries");
|
||||
}
|
||||
|
||||
if (!f.smallest_internal_key.Valid() || !f.largest_internal_key.Valid()) {
|
||||
return Status::Corruption("Generated table have corrupted keys");
|
||||
}
|
||||
}
|
||||
|
||||
// Copy/Move external files into DB
|
||||
std::unordered_set<size_t> ingestion_path_ids;
|
||||
for (IngestedFileInfo& f : files_to_ingest_) {
|
||||
@@ -368,9 +367,32 @@ Status ExternalSstFileIngestionJob::Run() {
|
||||
super_version, force_global_seqno, cfd_->ioptions()->compaction_style,
|
||||
last_seqno, &f, &assigned_seqno);
|
||||
}
|
||||
|
||||
// Modify the smallest/largest internal key to include the sequence number
|
||||
// that we just learned. Only overwrite sequence number zero. There could
|
||||
// be a nonzero sequence number already to indicate a range tombstone's
|
||||
// exclusive endpoint.
|
||||
ParsedInternalKey smallest_parsed, largest_parsed;
|
||||
if (status.ok()) {
|
||||
status = ParseInternalKey(*f.smallest_internal_key.rep(),
|
||||
&smallest_parsed, false /* log_err_key */);
|
||||
}
|
||||
if (status.ok()) {
|
||||
status = ParseInternalKey(*f.largest_internal_key.rep(), &largest_parsed,
|
||||
false /* log_err_key */);
|
||||
}
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
if (smallest_parsed.sequence == 0) {
|
||||
UpdateInternalKey(f.smallest_internal_key.rep(), assigned_seqno,
|
||||
smallest_parsed.type);
|
||||
}
|
||||
if (largest_parsed.sequence == 0) {
|
||||
UpdateInternalKey(f.largest_internal_key.rep(), assigned_seqno,
|
||||
largest_parsed.type);
|
||||
}
|
||||
|
||||
status = AssignGlobalSeqnoForIngestedFile(&f, assigned_seqno);
|
||||
TEST_SYNC_POINT_CALLBACK("ExternalSstFileIngestionJob::Run",
|
||||
&assigned_seqno);
|
||||
|
||||
@@ -74,13 +74,13 @@ struct IngestedFileInfo {
|
||||
class ExternalSstFileIngestionJob {
|
||||
public:
|
||||
ExternalSstFileIngestionJob(
|
||||
const std::shared_ptr<SystemClock>& clock, VersionSet* versions,
|
||||
ColumnFamilyData* cfd, const ImmutableDBOptions& db_options,
|
||||
const EnvOptions& env_options, SnapshotList* db_snapshots,
|
||||
VersionSet* versions, ColumnFamilyData* cfd,
|
||||
const ImmutableDBOptions& db_options, const EnvOptions& env_options,
|
||||
SnapshotList* db_snapshots,
|
||||
const IngestExternalFileOptions& ingestion_options,
|
||||
Directories* directories, EventLogger* event_logger,
|
||||
const std::shared_ptr<IOTracer>& io_tracer)
|
||||
: clock_(clock),
|
||||
: clock_(db_options.clock),
|
||||
fs_(db_options.fs, io_tracer),
|
||||
versions_(versions),
|
||||
cfd_(cfd),
|
||||
@@ -170,7 +170,7 @@ class ExternalSstFileIngestionJob {
|
||||
template <typename TWritableFile>
|
||||
Status SyncIngestedFile(TWritableFile* file);
|
||||
|
||||
std::shared_ptr<SystemClock> clock_;
|
||||
SystemClock* clock_;
|
||||
FileSystemPtr fs_;
|
||||
VersionSet* versions_;
|
||||
ColumnFamilyData* cfd_;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "rocksdb/sst_file_writer.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/random.h"
|
||||
#include "util/thread_guard.h"
|
||||
#include "utilities/fault_injection_env.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -1305,38 +1306,38 @@ TEST_F(ExternalSSTFileTest, PickedLevelBug) {
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// While writing the MANIFEST start a thread that will ask for compaction
|
||||
Status bg_compact_status;
|
||||
ROCKSDB_NAMESPACE::port::Thread bg_compact([&]() {
|
||||
bg_compact_status =
|
||||
db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
});
|
||||
TEST_SYNC_POINT("ExternalSSTFileTest::PickedLevelBug:2");
|
||||
|
||||
// Start a thread that will ingest a new file
|
||||
Status bg_addfile_status;
|
||||
ROCKSDB_NAMESPACE::port::Thread bg_addfile([&]() {
|
||||
file_keys = {1, 2, 3};
|
||||
bg_addfile_status = GenerateAndAddExternalFile(options, file_keys, 1);
|
||||
});
|
||||
|
||||
// Wait for AddFile to start picking levels and writing MANIFEST
|
||||
TEST_SYNC_POINT("ExternalSSTFileTest::PickedLevelBug:0");
|
||||
{
|
||||
// While writing the MANIFEST start a thread that will ask for compaction
|
||||
ThreadGuard bg_compact(port::Thread([&]() {
|
||||
bg_compact_status =
|
||||
db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
}));
|
||||
TEST_SYNC_POINT("ExternalSSTFileTest::PickedLevelBug:2");
|
||||
|
||||
TEST_SYNC_POINT("ExternalSSTFileTest::PickedLevelBug:3");
|
||||
// Start a thread that will ingest a new file
|
||||
ThreadGuard bg_addfile(port::Thread([&]() {
|
||||
file_keys = {1, 2, 3};
|
||||
bg_addfile_status = GenerateAndAddExternalFile(options, file_keys, 1);
|
||||
}));
|
||||
|
||||
// We need to verify that no compactions can run while AddFile is
|
||||
// ingesting the files into the levels it find suitable. So we will
|
||||
// wait for 2 seconds to give a chance for compactions to run during
|
||||
// this period, and then make sure that no compactions where able to run
|
||||
env_->SleepForMicroseconds(1000000 * 2);
|
||||
ASSERT_FALSE(bg_compact_started.load());
|
||||
// Wait for AddFile to start picking levels and writing MANIFEST
|
||||
TEST_SYNC_POINT("ExternalSSTFileTest::PickedLevelBug:0");
|
||||
|
||||
// Hold AddFile from finishing writing the MANIFEST
|
||||
TEST_SYNC_POINT("ExternalSSTFileTest::PickedLevelBug:1");
|
||||
TEST_SYNC_POINT("ExternalSSTFileTest::PickedLevelBug:3");
|
||||
|
||||
bg_addfile.join();
|
||||
bg_compact.join();
|
||||
// We need to verify that no compactions can run while AddFile is
|
||||
// ingesting the files into the levels it find suitable. So we will
|
||||
// wait for 2 seconds to give a chance for compactions to run during
|
||||
// this period, and then make sure that no compactions where able to run
|
||||
env_->SleepForMicroseconds(1000000 * 2);
|
||||
ASSERT_FALSE(bg_compact_started.load());
|
||||
|
||||
// Hold AddFile from finishing writing the MANIFEST
|
||||
TEST_SYNC_POINT("ExternalSSTFileTest::PickedLevelBug:1");
|
||||
}
|
||||
|
||||
ASSERT_OK(bg_addfile_status);
|
||||
ASSERT_OK(bg_compact_status);
|
||||
|
||||
+28
-27
@@ -75,29 +75,29 @@ const char* GetFlushReasonString (FlushReason flush_reason) {
|
||||
return "Manual Flush";
|
||||
case FlushReason::kErrorRecovery:
|
||||
return "Error Recovery";
|
||||
case FlushReason::kWalFull:
|
||||
return "WAL Full";
|
||||
default:
|
||||
return "Invalid";
|
||||
}
|
||||
}
|
||||
|
||||
FlushJob::FlushJob(const std::string& dbname, ColumnFamilyData* cfd,
|
||||
const ImmutableDBOptions& db_options,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
uint64_t max_memtable_id, const FileOptions& file_options,
|
||||
VersionSet* versions, InstrumentedMutex* db_mutex,
|
||||
std::atomic<bool>* shutting_down,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
SnapshotChecker* snapshot_checker, JobContext* job_context,
|
||||
LogBuffer* log_buffer, FSDirectory* db_directory,
|
||||
FSDirectory* output_file_directory,
|
||||
CompressionType output_compression, Statistics* stats,
|
||||
EventLogger* event_logger, bool measure_io_stats,
|
||||
const bool sync_output_directory, const bool write_manifest,
|
||||
Env::Priority thread_pri,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::string& db_id, const std::string& db_session_id,
|
||||
std::string full_history_ts_low)
|
||||
FlushJob::FlushJob(
|
||||
const std::string& dbname, ColumnFamilyData* cfd,
|
||||
const ImmutableDBOptions& db_options,
|
||||
const MutableCFOptions& mutable_cf_options, uint64_t max_memtable_id,
|
||||
const FileOptions& file_options, VersionSet* versions,
|
||||
InstrumentedMutex* db_mutex, std::atomic<bool>* shutting_down,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
SnapshotChecker* snapshot_checker, JobContext* job_context,
|
||||
LogBuffer* log_buffer, FSDirectory* db_directory,
|
||||
FSDirectory* output_file_directory, CompressionType output_compression,
|
||||
Statistics* stats, EventLogger* event_logger, bool measure_io_stats,
|
||||
const bool sync_output_directory, const bool write_manifest,
|
||||
Env::Priority thread_pri, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::string& db_id, const std::string& db_session_id,
|
||||
std::string full_history_ts_low, BlobFileCompletionCallback* blob_callback)
|
||||
: dbname_(dbname),
|
||||
db_id_(db_id),
|
||||
db_session_id_(db_session_id),
|
||||
@@ -127,8 +127,9 @@ FlushJob::FlushJob(const std::string& dbname, ColumnFamilyData* cfd,
|
||||
pick_memtable_called(false),
|
||||
thread_pri_(thread_pri),
|
||||
io_tracer_(io_tracer),
|
||||
clock_(db_options_.env->GetSystemClock()),
|
||||
full_history_ts_low_(std::move(full_history_ts_low)) {
|
||||
clock_(db_options_.clock),
|
||||
full_history_ts_low_(std::move(full_history_ts_low)),
|
||||
blob_callback_(blob_callback) {
|
||||
// Update the thread status to indicate flush.
|
||||
ReportStartedFlush();
|
||||
TEST_SYNC_POINT("FlushJob::FlushJob()");
|
||||
@@ -412,13 +413,12 @@ Status FlushJob::WriteLevel0Table() {
|
||||
cfd_->internal_comparator(), cfd_->int_tbl_prop_collector_factories(),
|
||||
cfd_->GetID(), cfd_->GetName(), existing_snapshots_,
|
||||
earliest_write_conflict_snapshot_, snapshot_checker_,
|
||||
output_compression_, mutable_cf_options_.sample_for_compression,
|
||||
mutable_cf_options_.compression_opts,
|
||||
output_compression_, mutable_cf_options_.compression_opts,
|
||||
mutable_cf_options_.paranoid_file_checks, cfd_->internal_stats(),
|
||||
TableFileCreationReason::kFlush, &io_s, io_tracer_, event_logger_,
|
||||
job_context_->job_id, Env::IO_HIGH, &table_properties_, 0 /* level */,
|
||||
creation_time, oldest_key_time, write_hint, current_time, db_id_,
|
||||
db_session_id_, full_history_ts_low);
|
||||
db_session_id_, full_history_ts_low, blob_callback_);
|
||||
if (!io_s.ok()) {
|
||||
io_status_ = io_s;
|
||||
}
|
||||
@@ -477,15 +477,16 @@ Status FlushJob::WriteLevel0Table() {
|
||||
|
||||
const auto& blobs = edit_->GetBlobFileAdditions();
|
||||
for (const auto& blob : blobs) {
|
||||
stats.bytes_written += blob.GetTotalBlobBytes();
|
||||
stats.bytes_written_blob += blob.GetTotalBlobBytes();
|
||||
}
|
||||
|
||||
stats.num_output_files += static_cast<int>(blobs.size());
|
||||
stats.num_output_files_blob = static_cast<int>(blobs.size());
|
||||
|
||||
RecordTimeToHistogram(stats_, FLUSH_TIME, stats.micros);
|
||||
cfd_->internal_stats()->AddCompactionStats(0 /* level */, thread_pri_, stats);
|
||||
cfd_->internal_stats()->AddCFStats(InternalStats::BYTES_FLUSHED,
|
||||
stats.bytes_written);
|
||||
cfd_->internal_stats()->AddCFStats(
|
||||
InternalStats::BYTES_FLUSHED,
|
||||
stats.bytes_written + stats.bytes_written_blob);
|
||||
RecordFlushIOStats();
|
||||
return s;
|
||||
}
|
||||
|
||||
+5
-2
@@ -17,6 +17,7 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "db/blob/blob_file_completion_callback.h"
|
||||
#include "db/column_family.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/flush_scheduler.h"
|
||||
@@ -73,7 +74,8 @@ class FlushJob {
|
||||
const bool sync_output_directory, const bool write_manifest,
|
||||
Env::Priority thread_pri, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::string& db_id = "", const std::string& db_session_id = "",
|
||||
std::string full_history_ts_low = "");
|
||||
std::string full_history_ts_low = "",
|
||||
BlobFileCompletionCallback* blob_callback = nullptr);
|
||||
|
||||
~FlushJob();
|
||||
|
||||
@@ -162,9 +164,10 @@ class FlushJob {
|
||||
IOStatus io_status_;
|
||||
|
||||
const std::shared_ptr<IOTracer> io_tracer_;
|
||||
const std::shared_ptr<SystemClock> clock_;
|
||||
SystemClock* clock_;
|
||||
|
||||
const std::string full_history_ts_low_;
|
||||
BlobFileCompletionCallback* blob_callback_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -48,6 +48,8 @@ class FlushJobTestBase : public testing::Test {
|
||||
if (getenv("KEEP_DB")) {
|
||||
fprintf(stdout, "db is still in %s\n", dbname_.c_str());
|
||||
} else {
|
||||
// destroy versions_ to release all file handles
|
||||
versions_.reset();
|
||||
EXPECT_OK(DestroyDir(env_, dbname_));
|
||||
}
|
||||
}
|
||||
|
||||
Loaded 100 of 408 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user