mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-07 11:32:34 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73fc71f933 | ||
|
|
07109c4d84 | ||
|
|
44302dd00c | ||
|
|
005c34f0b8 | ||
|
|
843738d8dc | ||
|
|
cc0a65f428 | ||
|
|
4837609ce2 | ||
|
|
48171df2bc | ||
|
|
d4f30520f9 | ||
|
|
c87e28ddf3 | ||
|
|
8969445642 | ||
|
|
250d2a6791 | ||
|
|
68d69899a1 | ||
|
|
b4fc156fb8 | ||
|
|
34ccab0210 | ||
|
|
4f51078c44 | ||
|
|
2c3ceec5a3 | ||
|
|
5ae32b86ac | ||
|
|
9e9192c8c6 | ||
|
|
b04c5d4277 | ||
|
|
8d47fc7199 | ||
|
|
ec5843dca9 | ||
|
|
3ae0047278 | ||
|
|
c287c098a4 | ||
|
|
d6336563e5 | ||
|
|
ad05cbb182 | ||
|
|
7eb292da14 | ||
|
|
dfbe52e099 | ||
|
|
30649dc6a1 | ||
|
|
90eca1e616 | ||
|
|
243220d08a | ||
|
|
4624edc440 | ||
|
|
aba3409740 | ||
|
|
f4a030ce81 | ||
|
|
b092977643 | ||
|
|
828211e901 | ||
|
|
8ada876dfe | ||
|
|
e9c31ab159 | ||
|
|
1960e73e21 | ||
|
|
5263da6396 | ||
|
|
ab446dc22d | ||
|
|
c178da053b | ||
|
|
97307d888f | ||
|
|
0454f781c2 | ||
|
|
989d12313c | ||
|
|
8eb1d445c3 | ||
|
|
4e7a182d09 | ||
|
|
c88c57cde1 | ||
|
|
6a30b98fdc | ||
|
|
ba6ee1f749 | ||
|
|
b3c5351335 | ||
|
|
d08d05cb62 | ||
|
|
dadf01672a | ||
|
|
74748611a8 | ||
|
|
3c380fdffd | ||
|
|
b555ed30a4 | ||
|
|
8a04ee4fd1 | ||
|
|
ee1c802675 | ||
|
|
0a0fad447b | ||
|
|
3f1bb07351 | ||
|
|
ef29d2a234 | ||
|
|
fe228da0a9 | ||
|
|
9fc72d6f16 | ||
|
|
d62af7fa4f | ||
|
|
945f618ba5 | ||
|
|
e78715c29a | ||
|
|
a0931b3185 | ||
|
|
821e0b1683 | ||
|
|
1336a7742d | ||
|
|
8feee28020 | ||
|
|
8f8eb4f1c0 | ||
|
|
81736d8afe | ||
|
|
2f29991701 | ||
|
|
88d8b2a2f5 | ||
|
|
4b124fb9d3 | ||
|
|
c3401846ef | ||
|
|
cb5b8f2090 | ||
|
|
cd5092e168 | ||
|
|
a247617e6f | ||
|
|
6e5b341e8b | ||
|
|
e502839e25 | ||
|
|
ba8aa8fdc8 | ||
|
|
b78ed0460b | ||
|
|
813719e952 | ||
|
|
e62a763752 | ||
|
|
1edac32b77 | ||
|
|
5eccf0b9d5 | ||
|
|
6efa8e270c | ||
|
|
5400800a56 | ||
|
|
ab5ab36ac2 | ||
|
|
ec225d2e97 | ||
|
|
ab43ff58b5 | ||
|
|
77dc069eb9 | ||
|
|
f3fe6f883b | ||
|
|
4bdf06e78f | ||
|
|
3073b1c573 | ||
|
|
e6605e5302 | ||
|
|
3fe0937180 | ||
|
|
e2d4b0efb1 | ||
|
|
b8eb32f8cf | ||
|
|
9f7ccc8445 | ||
|
|
3f666f79af | ||
|
|
65cd6cd4b6 | ||
|
|
46acdc9883 | ||
|
|
439855a774 | ||
|
|
4927b4e662 | ||
|
|
7fcc1d0ddf | ||
|
|
0e6e405fec | ||
|
|
d938226af4 | ||
|
|
65d431639b | ||
|
|
1039133f2d | ||
|
|
c7226428dd | ||
|
|
d6fdd59c63 | ||
|
|
35d8e65a04 | ||
|
|
d46e832e94 | ||
|
|
edc258127e | ||
|
|
ec0167eecb | ||
|
|
0ea7170d7d | ||
|
|
7e3d3326ce | ||
|
|
7291a3f813 | ||
|
|
c615689bb5 | ||
|
|
6f5ba0bf5b | ||
|
|
f2f034ef3b | ||
|
|
5568aec421 | ||
|
|
47ad6b81ff | ||
|
|
f1cb83fcf4 | ||
|
|
b9873162f0 | ||
|
|
4decff6fa8 | ||
|
|
46e599fc6b | ||
|
|
266d85fbec | ||
|
|
dc360df81e | ||
|
|
bf6f03f3cd | ||
|
|
af92d4ad11 | ||
|
|
d0f1b49ab6 | ||
|
|
68829ed89c | ||
|
|
43549c7d59 | ||
|
|
ba295cda29 | ||
|
|
e446d14093 | ||
|
|
6d7e3b9faf | ||
|
|
45828c7215 | ||
|
|
0a7ba0e548 | ||
|
|
9c2f64e148 | ||
|
|
204af1eccc | ||
|
|
d4da02d147 | ||
|
|
a53c571d2d | ||
|
|
b010116d82 | ||
|
|
0c6e8be9e2 | ||
|
|
199405192d | ||
|
|
bafec6bb30 | ||
|
|
a478e85697 |
No files matched your search
+45
-21
@@ -34,12 +34,15 @@
|
||||
|
||||
cmake_minimum_required(VERSION 2.8.12)
|
||||
project(rocksdb)
|
||||
enable_language(CXX)
|
||||
enable_language(C)
|
||||
enable_language(ASM)
|
||||
|
||||
if(POLICY CMP0042)
|
||||
cmake_policy(SET CMP0042 NEW)
|
||||
endif()
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules/")
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/modules/")
|
||||
|
||||
option(WITH_JEMALLOC "build with JeMalloc" OFF)
|
||||
option(WITH_SNAPPY "build with SNAPPY" OFF)
|
||||
@@ -129,20 +132,19 @@ endif()
|
||||
|
||||
string(REGEX REPLACE "[^0-9a-f]+" "" GIT_SHA "${GIT_SHA}")
|
||||
|
||||
if(NOT WIN32)
|
||||
execute_process(COMMAND
|
||||
"./build_tools/version.sh" "full"
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE ROCKSDB_VERSION
|
||||
)
|
||||
string(STRIP "${ROCKSDB_VERSION}" ROCKSDB_VERSION)
|
||||
execute_process(COMMAND
|
||||
"./build_tools/version.sh" "major"
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE ROCKSDB_VERSION_MAJOR
|
||||
)
|
||||
string(STRIP "${ROCKSDB_VERSION_MAJOR}" ROCKSDB_VERSION_MAJOR)
|
||||
endif()
|
||||
set(SH_CMD "sh")
|
||||
execute_process(COMMAND
|
||||
${SH_CMD} -c "build_tools/version.sh full"
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE ROCKSDB_VERSION
|
||||
)
|
||||
string(STRIP "${ROCKSDB_VERSION}" ROCKSDB_VERSION)
|
||||
execute_process(COMMAND
|
||||
${SH_CMD} -c "build_tools/version.sh major"
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE ROCKSDB_VERSION_MAJOR
|
||||
)
|
||||
string(STRIP "${ROCKSDB_VERSION_MAJOR}" ROCKSDB_VERSION_MAJOR)
|
||||
|
||||
option(WITH_MD_LIBRARY "build with MD" ON)
|
||||
if(WIN32 AND MSVC)
|
||||
@@ -178,6 +180,18 @@ else()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
include(CheckCCompilerFlag)
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "ppc64le")
|
||||
CHECK_C_COMPILER_FLAG("-maltivec" HAS_ALTIVEC)
|
||||
if(HAS_ALTIVEC)
|
||||
message(STATUS " HAS_ALTIVEC yes")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -maltivec")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -maltivec")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mcpu=power8")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mcpu=power8")
|
||||
endif(HAS_ALTIVEC)
|
||||
endif(CMAKE_SYSTEM_PROCESSOR MATCHES "ppc64le")
|
||||
|
||||
option(PORTABLE "build a portable binary" OFF)
|
||||
option(FORCE_SSE42 "force building with SSE4.2, even when PORTABLE=ON" OFF)
|
||||
if(PORTABLE)
|
||||
@@ -190,7 +204,9 @@ else()
|
||||
if(MSVC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /arch:AVX2")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native")
|
||||
if(NOT HAVE_POWER8)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -586,6 +602,7 @@ set(SOURCES
|
||||
utilities/leveldb_options/leveldb_options.cc
|
||||
utilities/lua/rocks_lua_compaction_filter.cc
|
||||
utilities/memory/memory_util.cc
|
||||
utilities/merge_operators/bytesxor.cc
|
||||
utilities/merge_operators/max.cc
|
||||
utilities/merge_operators/put.cc
|
||||
utilities/merge_operators/string_append/stringappend.cc
|
||||
@@ -626,6 +643,12 @@ set_source_files_properties(
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(HAVE_POWER8)
|
||||
list(APPEND SOURCES
|
||||
util/crc32c_ppc.c
|
||||
util/crc32c_ppc_asm.S)
|
||||
endif(HAVE_POWER8)
|
||||
|
||||
if(WIN32)
|
||||
list(APPEND SOURCES
|
||||
port/win/io_win.cc
|
||||
@@ -720,7 +743,7 @@ if(NOT WIN32 OR ROCKSDB_INSTALL_ON_WINDOWS)
|
||||
set(package_config_destination ${CMAKE_INSTALL_LIBDIR}/cmake/rocksdb)
|
||||
|
||||
configure_package_config_file(
|
||||
${CMAKE_SOURCE_DIR}/cmake/RocksDBConfig.cmake.in RocksDBConfig.cmake
|
||||
${CMAKE_CURRENT_LIST_DIR}/cmake/RocksDBConfig.cmake.in RocksDBConfig.cmake
|
||||
INSTALL_DESTINATION ${package_config_destination}
|
||||
)
|
||||
|
||||
@@ -950,17 +973,18 @@ if(WITH_TESTS)
|
||||
|
||||
foreach(sourcefile ${TEST_EXES})
|
||||
get_filename_component(exename ${sourcefile} NAME_WE)
|
||||
add_executable(${exename}${ARTIFACT_SUFFIX} ${sourcefile}
|
||||
add_executable(${CMAKE_PROJECT_NAME}_${exename}${ARTIFACT_SUFFIX} ${sourcefile}
|
||||
$<TARGET_OBJECTS:testharness>)
|
||||
set_target_properties(${exename}${ARTIFACT_SUFFIX}
|
||||
set_target_properties(${CMAKE_PROJECT_NAME}_${exename}${ARTIFACT_SUFFIX}
|
||||
PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD_RELEASE 1
|
||||
EXCLUDE_FROM_DEFAULT_BUILD_MINRELEASE 1
|
||||
EXCLUDE_FROM_DEFAULT_BUILD_RELWITHDEBINFO 1
|
||||
OUTPUT_NAME ${exename}${ARTIFACT_SUFFIX}
|
||||
)
|
||||
target_link_libraries(${exename}${ARTIFACT_SUFFIX} testutillib${ARTIFACT_SUFFIX} gtest ${LIBS})
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME}_${exename}${ARTIFACT_SUFFIX} testutillib${ARTIFACT_SUFFIX} gtest ${LIBS})
|
||||
if(NOT "${exename}" MATCHES "db_sanity_test")
|
||||
add_test(NAME ${exename} COMMAND ${exename}${ARTIFACT_SUFFIX})
|
||||
add_dependencies(check ${exename}${ARTIFACT_SUFFIX})
|
||||
add_dependencies(check ${CMAKE_PROJECT_NAME}_${exename}${ARTIFACT_SUFFIX})
|
||||
endif()
|
||||
endforeach(sourcefile ${TEST_EXES})
|
||||
|
||||
|
||||
+64
-1
@@ -1,5 +1,68 @@
|
||||
# Rocksdb Change Log
|
||||
## Unreleased
|
||||
## 5.12.5 (6/18/2018)
|
||||
### Bug Fixes
|
||||
* Fix regression bug of Prev() with ReadOptions.iterate_upper_bound.
|
||||
|
||||
## 5.12.3 (04/09/2018)
|
||||
### Bug Fixes
|
||||
* Fix memory leak when pin_l0_filter_and_index_blocks_in_cache is used with partitioned filters
|
||||
* Fix memory leak in two_level_iterator
|
||||
|
||||
## 5.12.2 (3/23/2018)
|
||||
### Bug Fixes
|
||||
* Fsync after writing global seq number to the ingestion file in ExternalSstFileIngestionJob.
|
||||
|
||||
### Java API Changes
|
||||
* Add `BlockBasedTableConfig.setBlockCache` to allow sharing a block cache across DB instances.
|
||||
|
||||
## 5.12.1 (3/16/2018)
|
||||
### Public API Change
|
||||
* RocksDBOptionsParser::Parse()'s `ignore_unknown_options` argument will only be effective if the option file shows it is generated using a higher version of RocksDB than the current version.
|
||||
|
||||
### New Features
|
||||
* Avoid unnecessarily flushing in `CompactRange()` when the range specified by the user does not overlap unflushed memtables.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix WAL corruption caused by race condition between user write thread and backup/checkpoint thread.
|
||||
|
||||
## 5.12.0 (2/14/2018)
|
||||
### Public API Change
|
||||
* Iterator::SeekForPrev is now a pure virtual method. This is to prevent user who implement the Iterator interface fail to implement SeekForPrev by mistake.
|
||||
* Add `include_end` option to make the range end exclusive when `include_end == false` in `DeleteFilesInRange()`.
|
||||
* Add `CompactRangeOptions::allow_write_stall`, which makes `CompactRange` start working immediately, even if it causes user writes to stall. The default value is false, meaning we add delay to `CompactRange` calls until stalling can be avoided when possible. Note this delay is not present in previous RocksDB versions.
|
||||
* Creating checkpoint with empty directory now returns `Status::InvalidArgument`; previously, it returned `Status::IOError`.
|
||||
* Adds a BlockBasedTableOption to turn off index block compression.
|
||||
* Close() method now returns a status when closing a db.
|
||||
|
||||
### New Features
|
||||
* Improve the performance of iterators doing long range scans by using readahead.
|
||||
* Add new function `DeleteFilesInRanges()` to delete files in multiple ranges at once for better performance.
|
||||
* FreeBSD build support for RocksDB and RocksJava.
|
||||
* Improved performance of long range scans with readahead.
|
||||
* Updated to and now continuously tested in Visual Studio 2017.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix `DisableFileDeletions()` followed by `GetSortedWalFiles()` to not return obsolete WAL files that `PurgeObsoleteFiles()` is going to delete.
|
||||
* Fix Handle error return from WriteBuffer() during WAL file close and DB close.
|
||||
* Fix advance reservation of arena block addresses.
|
||||
* Fix handling of empty string as checkpoint directory.
|
||||
|
||||
## 5.11.0 (01/08/2018)
|
||||
### Public API Change
|
||||
* Add `autoTune` and `getBytesPerSecond()` to RocksJava RateLimiter
|
||||
|
||||
### New Features
|
||||
* Add a new histogram stat called rocksdb.db.flush.micros for memtable flush.
|
||||
* Add "--use_txn" option to use transactional API in db_stress.
|
||||
* Disable onboard cache for compaction output in Windows platform.
|
||||
* Improve the performance of iterators doing long range scans by using readahead.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a stack-use-after-scope bug in ForwardIterator.
|
||||
* Fix builds on platforms including Linux, Windows, and PowerPC.
|
||||
* Fix buffer overrun in backup engine for DBs with huge number of files.
|
||||
* Fix a mislabel bug for bottom-pri compaction threads.
|
||||
* Fix DB::Flush() keep waiting after flush finish under certain condition.
|
||||
|
||||
## 5.10.0 (12/11/2017)
|
||||
### Public API Change
|
||||
|
||||
+35
@@ -107,6 +107,41 @@ to build a portable binary, add `PORTABLE=1` before your make commands, like thi
|
||||
* run `brew tap homebrew/versions; brew install gcc48 --use-llvm` to install gcc 4.8 (or higher).
|
||||
* run `brew install rocksdb`
|
||||
|
||||
* **FreeBSD** (11.01):
|
||||
|
||||
* You can either install RocksDB from the Ports system using `cd /usr/ports/databases/rocksdb && make install`, or you can follow the details below to install dependencies and compile from source code:
|
||||
|
||||
* Install the dependencies for RocksDB:
|
||||
|
||||
export BATCH=YES
|
||||
cd /usr/ports/devel/gmake && make install
|
||||
cd /usr/ports/devel/gflags && make install
|
||||
|
||||
cd /usr/ports/archivers/snappy && make install
|
||||
cd /usr/ports/archivers/bzip2 && make install
|
||||
cd /usr/ports/archivers/liblz4 && make install
|
||||
cd /usr/ports/archivesrs/zstd && make install
|
||||
|
||||
cd /usr/ports/devel/git && make install
|
||||
|
||||
|
||||
* Install the dependencies for RocksJava (optional):
|
||||
|
||||
export BATCH=yes
|
||||
cd /usr/ports/java/openjdk7 && make install
|
||||
|
||||
* Build RocksDB from source:
|
||||
cd ~
|
||||
git clone https://github.com/facebook/rocksdb.git
|
||||
cd rocksdb
|
||||
gmake static_lib
|
||||
|
||||
* Build RocksJava from source (optional):
|
||||
cd rocksdb
|
||||
export JAVA_HOME=/usr/local/openjdk7
|
||||
gmake rocksdbjava
|
||||
|
||||
|
||||
* **iOS**:
|
||||
* Run: `TARGET_OS=IOS make static_lib`. When building the project which uses rocksdb iOS library, make sure to define two important pre-processing macros: `ROCKSDB_LITE` and `IOS_CROSS_COMPILE`.
|
||||
|
||||
|
||||
@@ -15,3 +15,4 @@ This is the list of all known third-party language bindings for RocksDB. If some
|
||||
* D programming language - https://github.com/b1naryth1ef/rocksdb
|
||||
* Erlang - https://gitlab.com/barrel-db/erlang-rocksdb
|
||||
* Elixir - https://github.com/urbint/rox
|
||||
* Nim - https://github.com/status-im/nim-rocksdb
|
||||
@@ -232,9 +232,13 @@ endif
|
||||
# USAN doesn't work well with jemalloc. If we're compiling with USAN, we should use regular malloc.
|
||||
ifdef COMPILE_WITH_UBSAN
|
||||
DISABLE_JEMALLOC=1
|
||||
EXEC_LDFLAGS += -fsanitize=undefined
|
||||
PLATFORM_CCFLAGS += -fsanitize=undefined -DROCKSDB_UBSAN_RUN
|
||||
PLATFORM_CXXFLAGS += -fsanitize=undefined -DROCKSDB_UBSAN_RUN
|
||||
# Suppress alignment warning because murmurhash relies on casting unaligned
|
||||
# memory to integer. Fixing it may cause performance regression. 3-way crc32
|
||||
# relies on it too, although it can be rewritten to eliminate with minimal
|
||||
# performance regression.
|
||||
EXEC_LDFLAGS += -fsanitize=undefined -fno-sanitize-recover=all
|
||||
PLATFORM_CCFLAGS += -fsanitize=undefined -fno-sanitize-recover=all -DROCKSDB_UBSAN_RUN
|
||||
PLATFORM_CXXFLAGS += -fsanitize=undefined -fno-sanitize-recover=all -DROCKSDB_UBSAN_RUN
|
||||
endif
|
||||
|
||||
ifdef ROCKSDB_VALGRIND_RUN
|
||||
@@ -523,6 +527,10 @@ PARALLEL_TEST = \
|
||||
transaction_test \
|
||||
write_prepared_transaction_test \
|
||||
|
||||
# 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
|
||||
SUBSET := $(TESTS)
|
||||
ifdef ROCKSDBTESTS_START
|
||||
SUBSET := $(shell echo $(SUBSET) | sed 's/^.*$(ROCKSDBTESTS_START)/$(ROCKSDBTESTS_START)/')
|
||||
@@ -1553,8 +1561,8 @@ ZLIB_DOWNLOAD_BASE ?= http://zlib.net
|
||||
BZIP2_VER ?= 1.0.6
|
||||
BZIP2_SHA256 ?= a2848f34fcd5d6cf47def00461fcb528a0484d8edef8208d6d2e2909dc61d9cd
|
||||
BZIP2_DOWNLOAD_BASE ?= http://www.bzip.org
|
||||
SNAPPY_VER ?= 1.1.7
|
||||
SNAPPY_SHA256 ?= 3dfa02e873ff51a11ee02b9ca391807f0c8ea0529a4924afa645fbf97163f9d4
|
||||
SNAPPY_VER ?= 1.1.4
|
||||
SNAPPY_SHA256 ?= 134bfe122fd25599bb807bb8130e7ba6d9bdb851e0b16efcb83ac4f5d0b70057
|
||||
SNAPPY_DOWNLOAD_BASE ?= https://github.com/google/snappy/releases/download
|
||||
LZ4_VER ?= 1.8.0
|
||||
LZ4_SHA256 ?= 2ca482ea7a9bb103603108b5a7510b7592b90158c151ff50a28f1ca8389fccf6
|
||||
@@ -1574,7 +1582,7 @@ else
|
||||
endif
|
||||
endif
|
||||
ifeq ($(PLATFORM), OS_FREEBSD)
|
||||
JAVA_INCLUDE += -I$(JAVA_HOME)/include/freebsd
|
||||
JAVA_INCLUDE = -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/freebsd
|
||||
ROCKSDBJNILIB = librocksdbjni-freebsd$(ARCH).so
|
||||
ROCKSDB_JAR = rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH)-freebsd$(ARCH).jar
|
||||
endif
|
||||
@@ -1600,7 +1608,7 @@ libz.a:
|
||||
exit 1; \
|
||||
fi
|
||||
tar xvzf zlib-$(ZLIB_VER).tar.gz
|
||||
cd zlib-$(ZLIB_VER) && CFLAGS='-fPIC ${EXTRA_CFLAGS}' LDFLAGS='${EXTRA_LDFLAGS}' ./configure --static && make
|
||||
cd zlib-$(ZLIB_VER) && CFLAGS='-fPIC ${EXTRA_CFLAGS}' LDFLAGS='${EXTRA_LDFLAGS}' ./configure --static && $(MAKE)
|
||||
cp zlib-$(ZLIB_VER)/libz.a .
|
||||
|
||||
libbz2.a:
|
||||
@@ -1612,7 +1620,7 @@ libbz2.a:
|
||||
exit 1; \
|
||||
fi
|
||||
tar xvzf bzip2-$(BZIP2_VER).tar.gz
|
||||
cd bzip2-$(BZIP2_VER) && make CFLAGS='-fPIC -O2 -g -D_FILE_OFFSET_BITS=64 ${EXTRA_CFLAGS}' AR='ar ${EXTRA_ARFLAGS}'
|
||||
cd bzip2-$(BZIP2_VER) && $(MAKE) CFLAGS='-fPIC -O2 -g -D_FILE_OFFSET_BITS=64 ${EXTRA_CFLAGS}' AR='ar ${EXTRA_ARFLAGS}'
|
||||
cp bzip2-$(BZIP2_VER)/libbz2.a .
|
||||
|
||||
libsnappy.a:
|
||||
@@ -1625,7 +1633,7 @@ libsnappy.a:
|
||||
fi
|
||||
tar xvzf snappy-$(SNAPPY_VER).tar.gz
|
||||
cd snappy-$(SNAPPY_VER) && CFLAGS='${EXTRA_CFLAGS}' CXXFLAGS='${EXTRA_CXXFLAGS}' LDFLAGS='${EXTRA_LDFLAGS}' ./configure --with-pic --enable-static --disable-shared
|
||||
cd snappy-$(SNAPPY_VER) && make ${SNAPPY_MAKE_TARGET}
|
||||
cd snappy-$(SNAPPY_VER) && $(MAKE) ${SNAPPY_MAKE_TARGET}
|
||||
cp snappy-$(SNAPPY_VER)/.libs/libsnappy.a .
|
||||
|
||||
liblz4.a:
|
||||
@@ -1638,7 +1646,7 @@ liblz4.a:
|
||||
exit 1; \
|
||||
fi
|
||||
tar xvzf lz4-$(LZ4_VER).tar.gz
|
||||
cd lz4-$(LZ4_VER)/lib && make CFLAGS='-fPIC -O2 ${EXTRA_CFLAGS}' all
|
||||
cd lz4-$(LZ4_VER)/lib && $(MAKE) CFLAGS='-fPIC -O2 ${EXTRA_CFLAGS}' all
|
||||
cp lz4-$(LZ4_VER)/lib/liblz4.a .
|
||||
|
||||
libzstd.a:
|
||||
@@ -1651,7 +1659,7 @@ libzstd.a:
|
||||
exit 1; \
|
||||
fi
|
||||
tar xvzf zstd-$(ZSTD_VER).tar.gz
|
||||
cd zstd-$(ZSTD_VER)/lib && DESTDIR=. PREFIX= make CFLAGS='-fPIC -O2 ${EXTRA_CFLAGS}' install
|
||||
cd zstd-$(ZSTD_VER)/lib && DESTDIR=. PREFIX= $(MAKE) CFLAGS='-fPIC -O2 ${EXTRA_CFLAGS}' install
|
||||
cp zstd-$(ZSTD_VER)/lib/libzstd.a .
|
||||
|
||||
# A version of each $(LIBOBJECTS) compiled with -fPIC and a fixed set of static compression libraries
|
||||
@@ -1750,7 +1758,7 @@ JAVA_C_LIBOBJECTS = $(patsubst %.c.o,jl/%.c.o,$(JAVA_C_OBJECTS))
|
||||
JAVA_ASM_LIBOBJECTS = $(patsubst %.S.o,jl/%.S.o,$(JAVA_ASM_OBJECTS))
|
||||
endif
|
||||
|
||||
java_libobjects = $(patsubst %,jl/%,$(LIBOBJECTS))
|
||||
java_libobjects = $(patsubst %,jl/%,$(LIB_CC_OBJECTS))
|
||||
CLEAN_FILES += jl
|
||||
java_all_libobjects = $(java_libobjects)
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
## RocksDB: A Persistent Key-Value Store for Flash and RAM Storage
|
||||
|
||||
[](https://travis-ci.org/facebook/rocksdb)
|
||||
[](https://ci.appveyor.com/project/Facebook/rocksdb/branch/master)
|
||||
[](https://travis-ci.org/facebook/rocksdb)
|
||||
[](https://ci.appveyor.com/project/Facebook/rocksdb/branch/master)
|
||||
[](http://140.211.168.68:8080/job/Rocksdb)
|
||||
|
||||
|
||||
RocksDB is developed and maintained by Facebook Database Engineering Team.
|
||||
|
||||
@@ -242,6 +242,7 @@ cpp_library(
|
||||
"utilities/leveldb_options/leveldb_options.cc",
|
||||
"utilities/lua/rocks_lua_compaction_filter.cc",
|
||||
"utilities/memory/memory_util.cc",
|
||||
"utilities/merge_operators/bytesxor.cc",
|
||||
"utilities/merge_operators/max.cc",
|
||||
"utilities/merge_operators/put.cc",
|
||||
"utilities/merge_operators/string_append/stringappend.cc",
|
||||
|
||||
Vendored
+5
@@ -14,6 +14,11 @@ Vagrant.configure("2") do |config|
|
||||
box.vm.box = "chef/centos-6.5"
|
||||
end
|
||||
|
||||
config.vm.define "centos7" do |box|
|
||||
box.vm.box = "centos/7"
|
||||
box.vm.provision "shell", path: "build_tools/setup_centos7.sh"
|
||||
end
|
||||
|
||||
config.vm.define "FreeBSD10" do |box|
|
||||
box.vm.guest = :freebsd
|
||||
box.vm.box = "robin/freebsd-10"
|
||||
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
version: 1.0.{build}
|
||||
image: Visual Studio 2015
|
||||
image: Visual Studio 2017
|
||||
before_build:
|
||||
- md %APPVEYOR_BUILD_FOLDER%\build
|
||||
- cd %APPVEYOR_BUILD_FOLDER%\build
|
||||
- cmake -G "Visual Studio 14 Win64" -DOPTDBG=1 -DWITH_XPRESS=1 -DPORTABLE=1 ..
|
||||
- cmake -G "Visual Studio 15 Win64" -DOPTDBG=1 -DWITH_XPRESS=1 -DPORTABLE=1 ..
|
||||
- cd ..
|
||||
build:
|
||||
project: build\rocksdb.sln
|
||||
|
||||
@@ -36,7 +36,7 @@ def parse_src_mk(repo_path):
|
||||
# get all .cc / .c files
|
||||
def get_cc_files(repo_path):
|
||||
cc_files = []
|
||||
for root, dirnames, filenames in os.walk(repo_path):
|
||||
for root, dirnames, filenames in os.walk(repo_path): # noqa: B007 T25377293 Grandfathered in
|
||||
root = root[(len(repo_path) + 1):]
|
||||
if "java" in root:
|
||||
# Skip java
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Create a tmp directory for the test to use
|
||||
TEST_DIR=$(mktemp -d /dev/shm/fbcode_rocksdb_XXXXXXX)
|
||||
# shellcheck disable=SC2068
|
||||
TEST_TMPDIR="$TEST_DIR" $@ && rm -rf "$TEST_DIR"
|
||||
@@ -51,11 +51,13 @@ if [ -z "$ROCKSDB_NO_FBCODE" -a -d /mnt/gvfs/third-party ]; then
|
||||
FBCODE_BUILD="true"
|
||||
# If we're compiling with TSAN we need pic build
|
||||
PIC_BUILD=$COMPILE_WITH_TSAN
|
||||
if [ -z "$ROCKSDB_FBCODE_BUILD_WITH_481" ]; then
|
||||
source "$PWD/build_tools/fbcode_config.sh"
|
||||
else
|
||||
if [ -n "$ROCKSDB_FBCODE_BUILD_WITH_481" ]; then
|
||||
# we need this to build with MySQL. Don't use for other purposes.
|
||||
source "$PWD/build_tools/fbcode_config4.8.1.sh"
|
||||
elif [ -n "$ROCKSDB_FBCODE_BUILD_WITH_5xx" ]; then
|
||||
source "$PWD/build_tools/fbcode_config.sh"
|
||||
else
|
||||
source "$PWD/build_tools/fbcode_config_platform007.sh"
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -141,6 +143,7 @@ case "$TARGET_OS" in
|
||||
;;
|
||||
FreeBSD)
|
||||
PLATFORM=OS_FREEBSD
|
||||
CXX=clang++
|
||||
COMMON_FLAGS="$COMMON_FLAGS -fno-builtin-memcmp -D_REENTRANT -DOS_FREEBSD"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lpthread"
|
||||
# PORT_FILES=port/freebsd/freebsd_specific.cc
|
||||
@@ -481,6 +484,9 @@ if test -z "$PORTABLE"; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -mcpu=$POWER -mtune=$POWER "
|
||||
elif test -n "`echo $TARGET_ARCHITECTURE | grep ^s390x`"; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -march=z10 "
|
||||
elif test -n "`echo $TARGET_ARCHITECTURE | grep ^arm`"; then
|
||||
# TODO: Handle this with approprite options.
|
||||
COMMON_FLAGS="$COMMON_FLAGS"
|
||||
elif [ "$TARGET_OS" != AIX ] && [ "$TARGET_OS" != SunOS ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -march=native "
|
||||
elif test "$USE_SSE"; then
|
||||
|
||||
@@ -13,10 +13,12 @@ error=0
|
||||
|
||||
function log {
|
||||
DATE=`date +%Y-%m-%d:%H:%M:%S`
|
||||
# shellcheck disable=SC2068
|
||||
echo $DATE $@
|
||||
}
|
||||
|
||||
function log_err {
|
||||
# shellcheck disable=SC2145
|
||||
log "ERROR: $@ Error code: $error."
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# shellcheck disable=SC2148
|
||||
GCC_BASE=/mnt/gvfs/third-party2/gcc/8219ec1bcedf8ad9da05e121e193364de2cc4f61/5.x/centos6-native/c447969
|
||||
CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/64d8d58e3d84f8bde7a029763d4f5baf39d0d5b9/stable/centos6-native/6aaf4de
|
||||
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/ba9be983c81de7299b59fe71950c664a84dcb5f8/5.x/gcc-5-glibc-2.23/339d858
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# shellcheck disable=SC2148
|
||||
GCC_BASE=/mnt/gvfs/third-party2/gcc/cf7d14c625ce30bae1a4661c2319c5a283e4dd22/4.8.1/centos6-native/cc6c9dc
|
||||
CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/8598c375b0e94e1448182eb3df034704144a838d/stable/centos6-native/3f16ddd
|
||||
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/d6e0a7da6faba45f5e5b1638f9edd7afc2f34e7d/4.8.1/gcc-4.8.1-glibc-2.17/8aac7fc
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
GCC_BASE=/mnt/gvfs/third-party2/gcc/6e8e715624fd15256a7970073387793dfcf79b46/7.x/centos7-native/b2ef2b6
|
||||
CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/ef37e1faa1c29782abfac1ae65a291b9b7966f6d/stable/centos7-native/c9f9104
|
||||
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/c67031f0f739ac61575a061518d6ef5038f99f90/7.x/platform007/5620abc
|
||||
GLIBC_BASE=/mnt/gvfs/third-party2/glibc/60d6f124a78798b73944f5ba87c2306ae3460153/2.26/platform007/f259413
|
||||
SNAPPY_BASE=/mnt/gvfs/third-party2/snappy/7f9bdaada18f59bc27ec2b0871eb8a6144343aef/1.1.3/platform007/ca4da3d
|
||||
ZLIB_BASE=/mnt/gvfs/third-party2/zlib/22c2d65676fb7c23cfa797c4f6937f38b026f3cf/1.2.8/platform007/ca4da3d
|
||||
BZIP2_BASE=/mnt/gvfs/third-party2/bzip2/dc49a21c5fceec6456a7a28a94dcd16690af1337/1.0.6/platform007/ca4da3d
|
||||
LZ4_BASE=/mnt/gvfs/third-party2/lz4/907b498203d297947f3bb70b9466f47e100f1873/r131/platform007/ca4da3d
|
||||
ZSTD_BASE=/mnt/gvfs/third-party2/zstd/3ee276cbacfad3074e3f07bf826ac47f06970f4e/1.3.5/platform007/15a3614
|
||||
GFLAGS_BASE=/mnt/gvfs/third-party2/gflags/0b9929d2588991c65a57168bf88aff2db87c5d48/2.2.0/platform007/ca4da3d
|
||||
JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/9c910d36d6235cc40e8ff559358f1833452300ca/master/platform007/5b0f53e
|
||||
NUMA_BASE=/mnt/gvfs/third-party2/numa/9cbf2460284c669ed19c3ccb200a71f7dd7e53c7/2.0.11/platform007/ca4da3d
|
||||
LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/bf3d7497fe4e6d007354f0adffa16ce3003f8338/1.3/platform007/6f3e0a9
|
||||
TBB_BASE=/mnt/gvfs/third-party2/tbb/ff4e0b093534704d8abab678a4fd7f5ea7b094c7/2018_U5/platform007/ca4da3d
|
||||
KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/b5c4a61a5c483ba24722005ae07895971a2ac707/fb/platform007/da39a3e
|
||||
BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/92ff90349e2f43ea0a8246d8b1cf17b6869013e3/2.29.1/centos7-native/da39a3e
|
||||
VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/f3f697a28122e6bcd513273dd9c1ff23852fc59f/3.13.0/platform007/ca4da3d
|
||||
LUA_BASE=/mnt/gvfs/third-party2/lua/f0cd714433206d5139df61659eb7b28b1dea6683/5.3.4/platform007/5007832
|
||||
@@ -0,0 +1,157 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Set environment variables so that we can compile rocksdb using
|
||||
# fbcode settings. It uses the latest g++ and clang compilers and also
|
||||
# uses jemalloc
|
||||
# Environment variables that change the behavior of this script:
|
||||
# PIC_BUILD -- if true, it will only take pic versions of libraries from fbcode. libraries that don't have pic variant will not be included
|
||||
|
||||
|
||||
BASEDIR=`dirname $BASH_SOURCE`
|
||||
source "$BASEDIR/dependencies_platform007.sh"
|
||||
|
||||
CFLAGS=""
|
||||
|
||||
# libgcc
|
||||
LIBGCC_INCLUDE="$LIBGCC_BASE/include/c++/7.3.0"
|
||||
LIBGCC_LIBS=" -L $LIBGCC_BASE/lib"
|
||||
|
||||
# glibc
|
||||
GLIBC_INCLUDE="$GLIBC_BASE/include"
|
||||
GLIBC_LIBS=" -L $GLIBC_BASE/lib"
|
||||
|
||||
# snappy
|
||||
SNAPPY_INCLUDE=" -I $SNAPPY_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
SNAPPY_LIBS=" $SNAPPY_BASE/lib/libsnappy.a"
|
||||
else
|
||||
SNAPPY_LIBS=" $SNAPPY_BASE/lib/libsnappy_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DSNAPPY"
|
||||
|
||||
if test -z $PIC_BUILD; then
|
||||
# location of zlib headers and libraries
|
||||
ZLIB_INCLUDE=" -I $ZLIB_BASE/include/"
|
||||
ZLIB_LIBS=" $ZLIB_BASE/lib/libz.a"
|
||||
CFLAGS+=" -DZLIB"
|
||||
|
||||
# location of bzip headers and libraries
|
||||
BZIP_INCLUDE=" -I $BZIP2_BASE/include/"
|
||||
BZIP_LIBS=" $BZIP2_BASE/lib/libbz2.a"
|
||||
CFLAGS+=" -DBZIP2"
|
||||
|
||||
LZ4_INCLUDE=" -I $LZ4_BASE/include/"
|
||||
LZ4_LIBS=" $LZ4_BASE/lib/liblz4.a"
|
||||
CFLAGS+=" -DLZ4"
|
||||
fi
|
||||
|
||||
ZSTD_INCLUDE=" -I $ZSTD_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd.a"
|
||||
else
|
||||
ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DZSTD"
|
||||
|
||||
# location of gflags headers and libraries
|
||||
GFLAGS_INCLUDE=" -I $GFLAGS_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
GFLAGS_LIBS=" $GFLAGS_BASE/lib/libgflags.a"
|
||||
else
|
||||
GFLAGS_LIBS=" $GFLAGS_BASE/lib/libgflags_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DGFLAGS=gflags"
|
||||
|
||||
# location of jemalloc
|
||||
JEMALLOC_INCLUDE=" -I $JEMALLOC_BASE/include/"
|
||||
JEMALLOC_LIB=" $JEMALLOC_BASE/lib/libjemalloc.a"
|
||||
|
||||
if test -z $PIC_BUILD; then
|
||||
# location of numa
|
||||
NUMA_INCLUDE=" -I $NUMA_BASE/include/"
|
||||
NUMA_LIB=" $NUMA_BASE/lib/libnuma.a"
|
||||
CFLAGS+=" -DNUMA"
|
||||
|
||||
# location of libunwind
|
||||
LIBUNWIND="$LIBUNWIND_BASE/lib/libunwind.a"
|
||||
fi
|
||||
|
||||
# location of TBB
|
||||
TBB_INCLUDE=" -isystem $TBB_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
TBB_LIBS="$TBB_BASE/lib/libtbb.a"
|
||||
else
|
||||
TBB_LIBS="$TBB_BASE/lib/libtbb_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DTBB"
|
||||
|
||||
# use Intel SSE support for checksum calculations
|
||||
export USE_SSE=1
|
||||
export PORTABLE=1
|
||||
|
||||
BINUTILS="$BINUTILS_BASE/bin"
|
||||
AR="$BINUTILS/ar"
|
||||
|
||||
DEPS_INCLUDE="$SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP_INCLUDE $LZ4_INCLUDE $ZSTD_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE $TBB_INCLUDE"
|
||||
|
||||
STDLIBS="-L $GCC_BASE/lib64"
|
||||
|
||||
CLANG_BIN="$CLANG_BASE/bin"
|
||||
CLANG_LIB="$CLANG_BASE/lib"
|
||||
CLANG_SRC="$CLANG_BASE/../../src"
|
||||
|
||||
CLANG_ANALYZER="$CLANG_BIN/clang++"
|
||||
CLANG_SCAN_BUILD="$CLANG_SRC/llvm/tools/clang/tools/scan-build/bin/scan-build"
|
||||
|
||||
if [ -z "$USE_CLANG" ]; then
|
||||
# gcc
|
||||
CC="$GCC_BASE/bin/gcc"
|
||||
CXX="$GCC_BASE/bin/g++"
|
||||
|
||||
CFLAGS+=" -B$BINUTILS/gold"
|
||||
CFLAGS+=" -isystem $LIBGCC_INCLUDE"
|
||||
CFLAGS+=" -isystem $GLIBC_INCLUDE"
|
||||
JEMALLOC=1
|
||||
else
|
||||
# clang
|
||||
CLANG_INCLUDE="$CLANG_LIB/clang/stable/include"
|
||||
CC="$CLANG_BIN/clang"
|
||||
CXX="$CLANG_BIN/clang++"
|
||||
|
||||
KERNEL_HEADERS_INCLUDE="$KERNEL_HEADERS_BASE/include"
|
||||
|
||||
CFLAGS+=" -B$BINUTILS/gold -nostdinc -nostdlib"
|
||||
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/7.x "
|
||||
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/7.x/x86_64-facebook-linux "
|
||||
CFLAGS+=" -isystem $GLIBC_INCLUDE"
|
||||
CFLAGS+=" -isystem $LIBGCC_INCLUDE"
|
||||
CFLAGS+=" -isystem $CLANG_INCLUDE"
|
||||
CFLAGS+=" -isystem $KERNEL_HEADERS_INCLUDE/linux "
|
||||
CFLAGS+=" -isystem $KERNEL_HEADERS_INCLUDE "
|
||||
CFLAGS+=" -Wno-expansion-to-defined "
|
||||
CXXFLAGS="-nostdinc++"
|
||||
fi
|
||||
|
||||
CFLAGS+=" $DEPS_INCLUDE"
|
||||
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX -DROCKSDB_FALLOCATE_PRESENT -DROCKSDB_MALLOC_USABLE_SIZE -DROCKSDB_RANGESYNC_PRESENT -DROCKSDB_SCHED_GETCPU_PRESENT -DROCKSDB_SUPPORT_THREAD_LOCAL -DHAVE_SSE42"
|
||||
CXXFLAGS+=" $CFLAGS"
|
||||
|
||||
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB $TBB_LIBS"
|
||||
EXEC_LDFLAGS+=" -B$BINUTILS/gold"
|
||||
EXEC_LDFLAGS+=" -Wl,--dynamic-linker,/usr/local/fbcode/platform007/lib/ld.so"
|
||||
EXEC_LDFLAGS+=" $LIBUNWIND"
|
||||
EXEC_LDFLAGS+=" -Wl,-rpath=/usr/local/fbcode/platform007/lib"
|
||||
# required by libtbb
|
||||
EXEC_LDFLAGS+=" -ldl"
|
||||
|
||||
PLATFORM_LDFLAGS="$LIBGCC_LIBS $GLIBC_LIBS $STDLIBS -lgcc -lstdc++"
|
||||
|
||||
EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $TBB_LIBS"
|
||||
|
||||
VALGRIND_VER="$VALGRIND_BASE/bin/"
|
||||
|
||||
# lua not supported because it's on track for deprecation, I think
|
||||
LUA_PATH=
|
||||
LUA_LIB=
|
||||
|
||||
export CC CXX AR CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD LUA_PATH LUA_LIB
|
||||
@@ -1,3 +1,4 @@
|
||||
# shellcheck disable=SC1113
|
||||
#/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
@@ -28,12 +29,14 @@ function package() {
|
||||
if dpkg --get-selections | grep --quiet $1; then
|
||||
log "$1 is already installed. skipping."
|
||||
else
|
||||
# shellcheck disable=SC2068
|
||||
apt-get install $@ -y
|
||||
fi
|
||||
elif [[ $OS = "centos" ]]; then
|
||||
if rpm -qa | grep --quiet $1; then
|
||||
log "$1 is already installed. skipping."
|
||||
else
|
||||
# shellcheck disable=SC2068
|
||||
yum install $@ -y
|
||||
fi
|
||||
fi
|
||||
@@ -52,6 +55,7 @@ function gem_install() {
|
||||
if gem list | grep --quiet $1; then
|
||||
log "$1 is already installed. skipping."
|
||||
else
|
||||
# shellcheck disable=SC2068
|
||||
gem install $@
|
||||
fi
|
||||
}
|
||||
@@ -125,4 +129,5 @@ function main() {
|
||||
include $LIB_DIR
|
||||
}
|
||||
|
||||
# shellcheck disable=SC2068
|
||||
main $@
|
||||
@@ -85,8 +85,9 @@ NON_SHM="TMPD=/tmp/rocksdb_test_tmp"
|
||||
GCC_481="ROCKSDB_FBCODE_BUILD_WITH_481=1"
|
||||
ASAN="COMPILE_WITH_ASAN=1"
|
||||
CLANG="USE_CLANG=1"
|
||||
LITE="OPT=\"-DROCKSDB_LITE -g\""
|
||||
TSAN="COMPILE_WITH_TSAN=1"
|
||||
# in gcc-5 there are known problems with TSAN like https://gcc.gnu.org/bugzilla/show_bug.cgi?id=71090.
|
||||
# using platform007 gives us gcc-8 or higher which has that bug fixed.
|
||||
TSAN="ROCKSDB_FBCODE_BUILD_WITH_PLATFORM007=1 COMPILE_WITH_TSAN=1"
|
||||
UBSAN="COMPILE_WITH_UBSAN=1"
|
||||
DISABLE_JEMALLOC="DISABLE_JEMALLOC=1"
|
||||
HTTP_PROXY="https_proxy=http://fwdproxy.29.prn1:8080 http_proxy=http://fwdproxy.29.prn1:8080 ftp_proxy=http://fwdproxy.29.prn1:8080"
|
||||
@@ -352,6 +353,22 @@ LITE_BUILD_COMMANDS="[
|
||||
}
|
||||
]"
|
||||
|
||||
#
|
||||
# Report RocksDB lite binary size to scuba
|
||||
REPORT_LITE_BINARY_SIZE_COMMANDS="[
|
||||
{
|
||||
'name':'Rocksdb Lite Binary Size',
|
||||
'oncall':'$ONCALL',
|
||||
'steps': [
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Report RocksDB Lite binary size to scuba',
|
||||
'shell':'tools/report_lite_binary_size.sh',
|
||||
'user':'root',
|
||||
},
|
||||
],
|
||||
]"
|
||||
|
||||
#
|
||||
# RocksDB stress/crash test
|
||||
#
|
||||
@@ -728,6 +745,9 @@ case $1 in
|
||||
lite)
|
||||
echo $LITE_BUILD_COMMANDS
|
||||
;;
|
||||
report_lite_binary_size)
|
||||
echo $REPORT_LITE_BINARY_SIZE_COMMANDS
|
||||
;;
|
||||
stress_crash)
|
||||
echo $STRESS_CRASH_TEST_COMMANDS
|
||||
;;
|
||||
|
||||
Executable
+43
@@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
ROCKSDB_VERSION="5.10.3"
|
||||
ZSTD_VERSION="1.1.3"
|
||||
|
||||
echo "This script configures CentOS with everything needed to build and run RocksDB"
|
||||
|
||||
yum update -y && yum install epel-release -y
|
||||
|
||||
yum install -y \
|
||||
wget \
|
||||
gcc-c++ \
|
||||
snappy snappy-devel \
|
||||
zlib zlib-devel \
|
||||
bzip2 bzip2-devel \
|
||||
lz4-devel \
|
||||
libasan \
|
||||
gflags
|
||||
|
||||
mkdir -pv /usr/local/rocksdb-${ROCKSDB_VERSION}
|
||||
ln -sfT /usr/local/rocksdb-${ROCKSDB_VERSION} /usr/local/rocksdb
|
||||
|
||||
wget -qO /tmp/zstd-${ZSTD_VERSION}.tar.gz https://github.com/facebook/zstd/archive/v${ZSTD_VERSION}.tar.gz
|
||||
wget -qO /tmp/rocksdb-${ROCKSDB_VERSION}.tar.gz https://github.com/facebook/rocksdb/archive/v${ROCKSDB_VERSION}.tar.gz
|
||||
|
||||
cd /tmp
|
||||
|
||||
tar xzvf zstd-${ZSTD_VERSION}.tar.gz
|
||||
tar xzvf rocksdb-${ROCKSDB_VERSION}.tar.gz -C /usr/local/
|
||||
|
||||
echo "Installing ZSTD..."
|
||||
pushd zstd-${ZSTD_VERSION}
|
||||
make && make install
|
||||
popd
|
||||
|
||||
echo "Compiling RocksDB..."
|
||||
cd /usr/local/rocksdb
|
||||
chown -R vagrant:vagrant /usr/local/rocksdb/
|
||||
sudo -u vagrant make static_lib
|
||||
cd examples/
|
||||
sudo -u vagrant make all
|
||||
sudo -u vagrant ./c_simple_example
|
||||
@@ -53,6 +53,45 @@ function get_lib_base()
|
||||
log_variable $__res_var
|
||||
}
|
||||
|
||||
###########################################################
|
||||
# platform007 dependencies #
|
||||
###########################################################
|
||||
|
||||
OUTPUT="$BASEDIR/dependencies_platform007.sh"
|
||||
|
||||
rm -f "$OUTPUT"
|
||||
touch "$OUTPUT"
|
||||
|
||||
echo "Writing dependencies to $OUTPUT"
|
||||
|
||||
# Compilers locations
|
||||
GCC_BASE=`readlink -f $TP2_LATEST/gcc/7.x/centos7-native/*/`
|
||||
CLANG_BASE=`readlink -f $TP2_LATEST/llvm-fb/stable/centos7-native/*/`
|
||||
|
||||
log_variable GCC_BASE
|
||||
log_variable CLANG_BASE
|
||||
|
||||
# Libraries locations
|
||||
get_lib_base libgcc 7.x platform007
|
||||
get_lib_base glibc 2.26 platform007
|
||||
get_lib_base snappy LATEST platform007
|
||||
get_lib_base zlib LATEST platform007
|
||||
get_lib_base bzip2 LATEST platform007
|
||||
get_lib_base lz4 LATEST platform007
|
||||
get_lib_base zstd LATEST platform007
|
||||
get_lib_base gflags LATEST platform007
|
||||
get_lib_base jemalloc LATEST platform007
|
||||
get_lib_base numa LATEST platform007
|
||||
get_lib_base libunwind LATEST platform007
|
||||
get_lib_base tbb LATEST platform007
|
||||
|
||||
get_lib_base kernel-headers fb platform007
|
||||
get_lib_base binutils LATEST centos7-native
|
||||
get_lib_base valgrind LATEST platform007
|
||||
get_lib_base lua 5.3.4 platform007
|
||||
|
||||
git diff $OUTPUT
|
||||
|
||||
###########################################################
|
||||
# 5.x dependencies #
|
||||
###########################################################
|
||||
|
||||
Vendored
+1
-1
@@ -57,7 +57,7 @@ struct LRUHandle {
|
||||
// Include the following flags:
|
||||
// in_cache: whether this entry is referenced by the hash table.
|
||||
// is_high_pri: whether this entry is high priority entry.
|
||||
// in_high_pro_pool: whether this entry is in high-pri pool.
|
||||
// in_high_pri_pool: whether this entry is in high-pri pool.
|
||||
char flags;
|
||||
|
||||
uint32_t hash; // Hash of key(); used for fast sharding and comparisons
|
||||
|
||||
@@ -72,7 +72,7 @@ def display_file_coverage(per_file_coverage, total_coverage):
|
||||
header_template = \
|
||||
"%" + str(max_file_name_length) + "s\t%s\t%s"
|
||||
separator = "-" * (max_file_name_length + 10 + 20)
|
||||
print header_template % ("Filename", "Coverage", "Lines")
|
||||
print header_template % ("Filename", "Coverage", "Lines") # noqa: E999 T25377293 Grandfathered in
|
||||
print separator
|
||||
|
||||
# -- Print body
|
||||
|
||||
+2
-1
@@ -131,7 +131,8 @@ Status BuildTable(
|
||||
MergeHelper merge(env, internal_comparator.user_comparator(),
|
||||
ioptions.merge_operator, nullptr, ioptions.info_log,
|
||||
true /* internal key corruption is not ok */,
|
||||
snapshots.empty() ? 0 : snapshots.back());
|
||||
snapshots.empty() ? 0 : snapshots.back(),
|
||||
snapshot_checker);
|
||||
|
||||
CompactionIterator c_iter(
|
||||
iter, internal_comparator.user_comparator(), &merge, kMaxSequenceNumber,
|
||||
|
||||
@@ -1388,23 +1388,24 @@ void rocksdb_writebatch_put_log_data(
|
||||
b->rep.PutLogData(Slice(blob, len));
|
||||
}
|
||||
|
||||
class H : public WriteBatch::Handler {
|
||||
public:
|
||||
void* state_;
|
||||
void (*put_)(void*, const char* k, size_t klen, const char* v, size_t vlen);
|
||||
void (*deleted_)(void*, const char* k, size_t klen);
|
||||
virtual void Put(const Slice& key, const Slice& value) override {
|
||||
(*put_)(state_, key.data(), key.size(), value.data(), value.size());
|
||||
}
|
||||
virtual void Delete(const Slice& key) override {
|
||||
(*deleted_)(state_, key.data(), key.size());
|
||||
}
|
||||
};
|
||||
|
||||
void rocksdb_writebatch_iterate(
|
||||
rocksdb_writebatch_t* b,
|
||||
void* state,
|
||||
void (*put)(void*, const char* k, size_t klen, const char* v, size_t vlen),
|
||||
void (*deleted)(void*, const char* k, size_t klen)) {
|
||||
class H : public WriteBatch::Handler {
|
||||
public:
|
||||
void* state_;
|
||||
void (*put_)(void*, const char* k, size_t klen, const char* v, size_t vlen);
|
||||
void (*deleted_)(void*, const char* k, size_t klen);
|
||||
virtual void Put(const Slice& key, const Slice& value) override {
|
||||
(*put_)(state_, key.data(), key.size(), value.data(), value.size());
|
||||
}
|
||||
virtual void Delete(const Slice& key) override {
|
||||
(*deleted_)(state_, key.data(), key.size());
|
||||
}
|
||||
};
|
||||
H handler;
|
||||
handler.state_ = state;
|
||||
handler.put_ = put;
|
||||
@@ -1649,18 +1650,6 @@ void rocksdb_writebatch_wi_iterate(
|
||||
void* state,
|
||||
void (*put)(void*, const char* k, size_t klen, const char* v, size_t vlen),
|
||||
void (*deleted)(void*, const char* k, size_t klen)) {
|
||||
class H : public WriteBatch::Handler {
|
||||
public:
|
||||
void* state_;
|
||||
void (*put_)(void*, const char* k, size_t klen, const char* v, size_t vlen);
|
||||
void (*deleted_)(void*, const char* k, size_t klen);
|
||||
virtual void Put(const Slice& key, const Slice& value) override {
|
||||
(*put_)(state_, key.data(), key.size(), value.data(), value.size());
|
||||
}
|
||||
virtual void Delete(const Slice& key) override {
|
||||
(*deleted_)(state_, key.data(), key.size());
|
||||
}
|
||||
};
|
||||
H handler;
|
||||
handler.state_ = state;
|
||||
handler.put_ = put;
|
||||
@@ -3109,20 +3098,21 @@ void rocksdb_slicetransform_destroy(rocksdb_slicetransform_t* st) {
|
||||
delete st;
|
||||
}
|
||||
|
||||
struct Wrapper : public rocksdb_slicetransform_t {
|
||||
const SliceTransform* rep_;
|
||||
~Wrapper() { delete rep_; }
|
||||
const char* Name() const override { return rep_->Name(); }
|
||||
Slice Transform(const Slice& src) const override {
|
||||
return rep_->Transform(src);
|
||||
}
|
||||
bool InDomain(const Slice& src) const override {
|
||||
return rep_->InDomain(src);
|
||||
}
|
||||
bool InRange(const Slice& src) const override { return rep_->InRange(src); }
|
||||
static void DoNothing(void*) { }
|
||||
};
|
||||
|
||||
rocksdb_slicetransform_t* rocksdb_slicetransform_create_fixed_prefix(size_t prefixLen) {
|
||||
struct Wrapper : public rocksdb_slicetransform_t {
|
||||
const SliceTransform* rep_;
|
||||
~Wrapper() { delete rep_; }
|
||||
const char* Name() const override { return rep_->Name(); }
|
||||
Slice Transform(const Slice& src) const override {
|
||||
return rep_->Transform(src);
|
||||
}
|
||||
bool InDomain(const Slice& src) const override {
|
||||
return rep_->InDomain(src);
|
||||
}
|
||||
bool InRange(const Slice& src) const override { return rep_->InRange(src); }
|
||||
static void DoNothing(void*) { }
|
||||
};
|
||||
Wrapper* wrapper = new Wrapper;
|
||||
wrapper->rep_ = rocksdb::NewFixedPrefixTransform(prefixLen);
|
||||
wrapper->state_ = nullptr;
|
||||
@@ -3131,19 +3121,6 @@ rocksdb_slicetransform_t* rocksdb_slicetransform_create_fixed_prefix(size_t pref
|
||||
}
|
||||
|
||||
rocksdb_slicetransform_t* rocksdb_slicetransform_create_noop() {
|
||||
struct Wrapper : public rocksdb_slicetransform_t {
|
||||
const SliceTransform* rep_;
|
||||
~Wrapper() { delete rep_; }
|
||||
const char* Name() const override { return rep_->Name(); }
|
||||
Slice Transform(const Slice& src) const override {
|
||||
return rep_->Transform(src);
|
||||
}
|
||||
bool InDomain(const Slice& src) const override {
|
||||
return rep_->InDomain(src);
|
||||
}
|
||||
bool InRange(const Slice& src) const override { return rep_->InRange(src); }
|
||||
static void DoNothing(void*) { }
|
||||
};
|
||||
Wrapper* wrapper = new Wrapper;
|
||||
wrapper->rep_ = rocksdb::NewNoopTransform();
|
||||
wrapper->state_ = nullptr;
|
||||
|
||||
+149
-47
@@ -31,6 +31,7 @@
|
||||
#include "monitoring/thread_status_util.h"
|
||||
#include "options/options_helper.h"
|
||||
#include "table/block_based_table_factory.h"
|
||||
#include "table/merging_iterator.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/compression.h"
|
||||
|
||||
@@ -344,12 +345,13 @@ void SuperVersionUnrefHandle(void* ptr) {
|
||||
// When latter happens, we are in ~ColumnFamilyData(), no get should happen as
|
||||
// well.
|
||||
SuperVersion* sv = static_cast<SuperVersion*>(ptr);
|
||||
if (sv->Unref()) {
|
||||
sv->db_mutex->Lock();
|
||||
sv->Cleanup();
|
||||
sv->db_mutex->Unlock();
|
||||
delete sv;
|
||||
}
|
||||
bool was_last_ref __attribute__((__unused__));
|
||||
was_last_ref = sv->Unref();
|
||||
// Thread-local SuperVersions can't outlive ColumnFamilyData::super_version_.
|
||||
// This is important because we can't do SuperVersion cleanup here.
|
||||
// That would require locking DB mutex, which would deadlock because
|
||||
// SuperVersionUnrefHandle is called with locked ThreadLocalPtr mutex.
|
||||
assert(!was_last_ref);
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
@@ -381,11 +383,13 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
next_(nullptr),
|
||||
prev_(nullptr),
|
||||
log_number_(0),
|
||||
flush_reason_(FlushReason::kUnknown),
|
||||
column_family_set_(column_family_set),
|
||||
pending_flush_(false),
|
||||
pending_compaction_(false),
|
||||
prev_compaction_needed_bytes_(0),
|
||||
allow_2pc_(db_options.allow_2pc) {
|
||||
allow_2pc_(db_options.allow_2pc),
|
||||
last_memtable_id_(0) {
|
||||
Ref();
|
||||
|
||||
// Convert user defined table properties collector factories to internal ones.
|
||||
@@ -468,7 +472,7 @@ ColumnFamilyData::~ColumnFamilyData() {
|
||||
local_sv_.reset();
|
||||
super_version_->db_mutex->Lock();
|
||||
|
||||
bool is_last_reference __attribute__((unused));
|
||||
bool is_last_reference __attribute__((__unused__));
|
||||
is_last_reference = super_version_->Unref();
|
||||
assert(is_last_reference);
|
||||
super_version_->Cleanup();
|
||||
@@ -479,7 +483,7 @@ ColumnFamilyData::~ColumnFamilyData() {
|
||||
if (dummy_versions_ != nullptr) {
|
||||
// List must be empty
|
||||
assert(dummy_versions_->TEST_Next() == dummy_versions_);
|
||||
bool deleted __attribute__((unused));
|
||||
bool deleted __attribute__((__unused__));
|
||||
deleted = dummy_versions_->Unref();
|
||||
assert(deleted);
|
||||
}
|
||||
@@ -630,6 +634,41 @@ int GetL0ThresholdSpeedupCompaction(int level0_file_num_compaction_trigger,
|
||||
}
|
||||
} // namespace
|
||||
|
||||
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) {
|
||||
if (num_unflushed_memtables >= mutable_cf_options.max_write_buffer_number) {
|
||||
return {WriteStallCondition::kStopped, WriteStallCause::kMemtableLimit};
|
||||
} else if (!mutable_cf_options.disable_auto_compactions &&
|
||||
num_l0_files >= mutable_cf_options.level0_stop_writes_trigger) {
|
||||
return {WriteStallCondition::kStopped, WriteStallCause::kL0FileCountLimit};
|
||||
} else if (!mutable_cf_options.disable_auto_compactions &&
|
||||
mutable_cf_options.hard_pending_compaction_bytes_limit > 0 &&
|
||||
num_compaction_needed_bytes >=
|
||||
mutable_cf_options.hard_pending_compaction_bytes_limit) {
|
||||
return {WriteStallCondition::kStopped,
|
||||
WriteStallCause::kPendingCompactionBytes};
|
||||
} else if (mutable_cf_options.max_write_buffer_number > 3 &&
|
||||
num_unflushed_memtables >=
|
||||
mutable_cf_options.max_write_buffer_number - 1) {
|
||||
return {WriteStallCondition::kDelayed, WriteStallCause::kMemtableLimit};
|
||||
} else if (!mutable_cf_options.disable_auto_compactions &&
|
||||
mutable_cf_options.level0_slowdown_writes_trigger >= 0 &&
|
||||
num_l0_files >=
|
||||
mutable_cf_options.level0_slowdown_writes_trigger) {
|
||||
return {WriteStallCondition::kDelayed, WriteStallCause::kL0FileCountLimit};
|
||||
} else if (!mutable_cf_options.disable_auto_compactions &&
|
||||
mutable_cf_options.soft_pending_compaction_bytes_limit > 0 &&
|
||||
num_compaction_needed_bytes >=
|
||||
mutable_cf_options.soft_pending_compaction_bytes_limit) {
|
||||
return {WriteStallCondition::kDelayed,
|
||||
WriteStallCause::kPendingCompactionBytes};
|
||||
}
|
||||
return {WriteStallCondition::kNormal, WriteStallCause::kNone};
|
||||
}
|
||||
|
||||
WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
const MutableCFOptions& mutable_cf_options) {
|
||||
auto write_stall_condition = WriteStallCondition::kNormal;
|
||||
@@ -639,25 +678,29 @@ WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
uint64_t compaction_needed_bytes =
|
||||
vstorage->estimated_compaction_needed_bytes();
|
||||
|
||||
auto write_stall_condition_and_cause = GetWriteStallConditionAndCause(
|
||||
imm()->NumNotFlushed(), vstorage->l0_delay_trigger_count(),
|
||||
vstorage->estimated_compaction_needed_bytes(), mutable_cf_options);
|
||||
write_stall_condition = write_stall_condition_and_cause.first;
|
||||
auto write_stall_cause = write_stall_condition_and_cause.second;
|
||||
|
||||
bool was_stopped = write_controller->IsStopped();
|
||||
bool needed_delay = write_controller->NeedsDelay();
|
||||
|
||||
if (imm()->NumNotFlushed() >= mutable_cf_options.max_write_buffer_number) {
|
||||
if (write_stall_condition == WriteStallCondition::kStopped &&
|
||||
write_stall_cause == WriteStallCause::kMemtableLimit) {
|
||||
write_controller_token_ = write_controller->GetStopToken();
|
||||
internal_stats_->AddCFStats(InternalStats::MEMTABLE_LIMIT_STOPS, 1);
|
||||
write_stall_condition = WriteStallCondition::kStopped;
|
||||
ROCKS_LOG_WARN(
|
||||
ioptions_.info_log,
|
||||
"[%s] Stopping writes because we have %d immutable memtables "
|
||||
"(waiting for flush), max_write_buffer_number is set to %d",
|
||||
name_.c_str(), imm()->NumNotFlushed(),
|
||||
mutable_cf_options.max_write_buffer_number);
|
||||
} else if (!mutable_cf_options.disable_auto_compactions &&
|
||||
vstorage->l0_delay_trigger_count() >=
|
||||
mutable_cf_options.level0_stop_writes_trigger) {
|
||||
} else if (write_stall_condition == WriteStallCondition::kStopped &&
|
||||
write_stall_cause == WriteStallCause::kL0FileCountLimit) {
|
||||
write_controller_token_ = write_controller->GetStopToken();
|
||||
internal_stats_->AddCFStats(InternalStats::L0_FILE_COUNT_LIMIT_STOPS, 1);
|
||||
write_stall_condition = WriteStallCondition::kStopped;
|
||||
if (compaction_picker_->IsLevel0CompactionInProgress()) {
|
||||
internal_stats_->AddCFStats(
|
||||
InternalStats::LOCKED_L0_FILE_COUNT_LIMIT_STOPS, 1);
|
||||
@@ -665,28 +708,23 @@ WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
ROCKS_LOG_WARN(ioptions_.info_log,
|
||||
"[%s] Stopping writes because we have %d level-0 files",
|
||||
name_.c_str(), vstorage->l0_delay_trigger_count());
|
||||
} else if (!mutable_cf_options.disable_auto_compactions &&
|
||||
mutable_cf_options.hard_pending_compaction_bytes_limit > 0 &&
|
||||
compaction_needed_bytes >=
|
||||
mutable_cf_options.hard_pending_compaction_bytes_limit) {
|
||||
} else if (write_stall_condition == WriteStallCondition::kStopped &&
|
||||
write_stall_cause == WriteStallCause::kPendingCompactionBytes) {
|
||||
write_controller_token_ = write_controller->GetStopToken();
|
||||
internal_stats_->AddCFStats(
|
||||
InternalStats::PENDING_COMPACTION_BYTES_LIMIT_STOPS, 1);
|
||||
write_stall_condition = WriteStallCondition::kStopped;
|
||||
ROCKS_LOG_WARN(
|
||||
ioptions_.info_log,
|
||||
"[%s] Stopping writes because of estimated pending compaction "
|
||||
"bytes %" PRIu64,
|
||||
name_.c_str(), compaction_needed_bytes);
|
||||
} else if (mutable_cf_options.max_write_buffer_number > 3 &&
|
||||
imm()->NumNotFlushed() >=
|
||||
mutable_cf_options.max_write_buffer_number - 1) {
|
||||
} else if (write_stall_condition == WriteStallCondition::kDelayed &&
|
||||
write_stall_cause == WriteStallCause::kMemtableLimit) {
|
||||
write_controller_token_ =
|
||||
SetupDelay(write_controller, compaction_needed_bytes,
|
||||
prev_compaction_needed_bytes_, was_stopped,
|
||||
mutable_cf_options.disable_auto_compactions);
|
||||
internal_stats_->AddCFStats(InternalStats::MEMTABLE_LIMIT_SLOWDOWNS, 1);
|
||||
write_stall_condition = WriteStallCondition::kDelayed;
|
||||
ROCKS_LOG_WARN(
|
||||
ioptions_.info_log,
|
||||
"[%s] Stalling writes because we have %d immutable memtables "
|
||||
@@ -695,10 +733,8 @@ WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
name_.c_str(), imm()->NumNotFlushed(),
|
||||
mutable_cf_options.max_write_buffer_number,
|
||||
write_controller->delayed_write_rate());
|
||||
} else if (!mutable_cf_options.disable_auto_compactions &&
|
||||
mutable_cf_options.level0_slowdown_writes_trigger >= 0 &&
|
||||
vstorage->l0_delay_trigger_count() >=
|
||||
mutable_cf_options.level0_slowdown_writes_trigger) {
|
||||
} else if (write_stall_condition == WriteStallCondition::kDelayed &&
|
||||
write_stall_cause == WriteStallCause::kL0FileCountLimit) {
|
||||
// L0 is the last two files from stopping.
|
||||
bool near_stop = vstorage->l0_delay_trigger_count() >=
|
||||
mutable_cf_options.level0_stop_writes_trigger - 2;
|
||||
@@ -708,7 +744,6 @@ WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
mutable_cf_options.disable_auto_compactions);
|
||||
internal_stats_->AddCFStats(InternalStats::L0_FILE_COUNT_LIMIT_SLOWDOWNS,
|
||||
1);
|
||||
write_stall_condition = WriteStallCondition::kDelayed;
|
||||
if (compaction_picker_->IsLevel0CompactionInProgress()) {
|
||||
internal_stats_->AddCFStats(
|
||||
InternalStats::LOCKED_L0_FILE_COUNT_LIMIT_SLOWDOWNS, 1);
|
||||
@@ -718,10 +753,8 @@ WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
"rate %" PRIu64,
|
||||
name_.c_str(), vstorage->l0_delay_trigger_count(),
|
||||
write_controller->delayed_write_rate());
|
||||
} else if (!mutable_cf_options.disable_auto_compactions &&
|
||||
mutable_cf_options.soft_pending_compaction_bytes_limit > 0 &&
|
||||
vstorage->estimated_compaction_needed_bytes() >=
|
||||
mutable_cf_options.soft_pending_compaction_bytes_limit) {
|
||||
} else if (write_stall_condition == WriteStallCondition::kDelayed &&
|
||||
write_stall_cause == WriteStallCause::kPendingCompactionBytes) {
|
||||
// If the distance to hard limit is less than 1/4 of the gap between soft
|
||||
// and
|
||||
// hard bytes limit, we think it is near stop and speed up the slowdown.
|
||||
@@ -739,7 +772,6 @@ WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
mutable_cf_options.disable_auto_compactions);
|
||||
internal_stats_->AddCFStats(
|
||||
InternalStats::PENDING_COMPACTION_BYTES_LIMIT_SLOWDOWNS, 1);
|
||||
write_stall_condition = WriteStallCondition::kDelayed;
|
||||
ROCKS_LOG_WARN(
|
||||
ioptions_.info_log,
|
||||
"[%s] Stalling writes because of estimated pending compaction "
|
||||
@@ -747,6 +779,7 @@ WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
name_.c_str(), vstorage->estimated_compaction_needed_bytes(),
|
||||
write_controller->delayed_write_rate());
|
||||
} else {
|
||||
assert(write_stall_condition == WriteStallCondition::kNormal);
|
||||
if (vstorage->l0_delay_trigger_count() >=
|
||||
GetL0ThresholdSpeedupCompaction(
|
||||
mutable_cf_options.level0_file_num_compaction_trigger,
|
||||
@@ -849,6 +882,68 @@ bool ColumnFamilyData::RangeOverlapWithCompaction(
|
||||
smallest_user_key, largest_user_key, level);
|
||||
}
|
||||
|
||||
Status ColumnFamilyData::RangesOverlapWithMemtables(
|
||||
const autovector<Range>& ranges, SuperVersion* super_version,
|
||||
bool* overlap) {
|
||||
assert(overlap != nullptr);
|
||||
*overlap = false;
|
||||
// Create an InternalIterator over all unflushed memtables
|
||||
Arena arena;
|
||||
ReadOptions read_opts;
|
||||
read_opts.total_order_seek = true;
|
||||
MergeIteratorBuilder merge_iter_builder(&internal_comparator_, &arena);
|
||||
merge_iter_builder.AddIterator(
|
||||
super_version->mem->NewIterator(read_opts, &arena));
|
||||
super_version->imm->AddIterators(read_opts, &merge_iter_builder);
|
||||
ScopedArenaIterator memtable_iter(merge_iter_builder.Finish());
|
||||
|
||||
std::vector<InternalIterator*> memtable_range_del_iters;
|
||||
auto* active_range_del_iter =
|
||||
super_version->mem->NewRangeTombstoneIterator(read_opts);
|
||||
if (active_range_del_iter != nullptr) {
|
||||
memtable_range_del_iters.push_back(active_range_del_iter);
|
||||
}
|
||||
super_version->imm->AddRangeTombstoneIterators(read_opts,
|
||||
&memtable_range_del_iters);
|
||||
RangeDelAggregator range_del_agg(internal_comparator_, {} /* snapshots */,
|
||||
false /* collapse_deletions */);
|
||||
Status status;
|
||||
{
|
||||
std::unique_ptr<InternalIterator> memtable_range_del_iter(
|
||||
NewMergingIterator(&internal_comparator_,
|
||||
memtable_range_del_iters.empty()
|
||||
? nullptr
|
||||
: &memtable_range_del_iters[0],
|
||||
static_cast<int>(memtable_range_del_iters.size())));
|
||||
status = range_del_agg.AddTombstones(std::move(memtable_range_del_iter));
|
||||
}
|
||||
for (size_t i = 0; i < ranges.size() && status.ok() && !*overlap; ++i) {
|
||||
auto* vstorage = super_version->current->storage_info();
|
||||
auto* ucmp = vstorage->InternalComparator()->user_comparator();
|
||||
InternalKey range_start(ranges[i].start, kMaxSequenceNumber,
|
||||
kValueTypeForSeek);
|
||||
memtable_iter->Seek(range_start.Encode());
|
||||
status = memtable_iter->status();
|
||||
ParsedInternalKey seek_result;
|
||||
if (status.ok()) {
|
||||
if (memtable_iter->Valid() &&
|
||||
!ParseInternalKey(memtable_iter->key(), &seek_result)) {
|
||||
status = Status::Corruption("DB have corrupted keys");
|
||||
}
|
||||
}
|
||||
if (status.ok()) {
|
||||
if (memtable_iter->Valid() &&
|
||||
ucmp->Compare(seek_result.user_key, ranges[i].limit) <= 0) {
|
||||
*overlap = true;
|
||||
} else if (range_del_agg.IsRangeOverlapped(ranges[i].start,
|
||||
ranges[i].limit)) {
|
||||
*overlap = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
const int ColumnFamilyData::kCompactAllLevels = -1;
|
||||
const int ColumnFamilyData::kCompactToBaseLevel = -2;
|
||||
|
||||
@@ -867,8 +962,7 @@ Compaction* ColumnFamilyData::CompactRange(
|
||||
|
||||
SuperVersion* ColumnFamilyData::GetReferencedSuperVersion(
|
||||
InstrumentedMutex* db_mutex) {
|
||||
SuperVersion* sv = nullptr;
|
||||
sv = GetThreadLocalSuperVersion(db_mutex);
|
||||
SuperVersion* sv = GetThreadLocalSuperVersion(db_mutex);
|
||||
sv->Ref();
|
||||
if (!ReturnThreadLocalSuperVersion(sv)) {
|
||||
// This Unref() corresponds to the Ref() in GetThreadLocalSuperVersion()
|
||||
@@ -882,7 +976,6 @@ SuperVersion* ColumnFamilyData::GetReferencedSuperVersion(
|
||||
|
||||
SuperVersion* ColumnFamilyData::GetThreadLocalSuperVersion(
|
||||
InstrumentedMutex* db_mutex) {
|
||||
SuperVersion* sv = nullptr;
|
||||
// The SuperVersion is cached in thread local storage to avoid acquiring
|
||||
// mutex when SuperVersion does not change since the last use. When a new
|
||||
// SuperVersion is installed, the compaction or flush thread cleans up
|
||||
@@ -901,7 +994,7 @@ SuperVersion* ColumnFamilyData::GetThreadLocalSuperVersion(
|
||||
// should only keep kSVInUse before ReturnThreadLocalSuperVersion call
|
||||
// (if no Scrape happens).
|
||||
assert(ptr != SuperVersion::kSVInUse);
|
||||
sv = static_cast<SuperVersion*>(ptr);
|
||||
SuperVersion* sv = static_cast<SuperVersion*>(ptr);
|
||||
if (sv == SuperVersion::kSVObsolete ||
|
||||
sv->version_number != super_version_number_.load()) {
|
||||
RecordTick(ioptions_.statistics, NUMBER_SUPERVERSION_ACQUIRES);
|
||||
@@ -965,6 +1058,12 @@ void ColumnFamilyData::InstallSuperVersion(
|
||||
RecalculateWriteStallConditions(mutable_cf_options);
|
||||
|
||||
if (old_superversion != nullptr) {
|
||||
// Reset SuperVersions cached in thread local storage.
|
||||
// This should be done before old_superversion->Unref(). That's to ensure
|
||||
// that local_sv_ never holds the last reference to SuperVersion, since
|
||||
// it has no means to safely do SuperVersion cleanup.
|
||||
ResetThreadLocalSuperVersions();
|
||||
|
||||
if (old_superversion->mutable_cf_options.write_buffer_size !=
|
||||
mutable_cf_options.write_buffer_size) {
|
||||
mem_->UpdateWriteBufferSize(mutable_cf_options.write_buffer_size);
|
||||
@@ -980,9 +1079,6 @@ void ColumnFamilyData::InstallSuperVersion(
|
||||
sv_context->superversions_to_free.push_back(old_superversion);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset SuperVersions cached in thread local storage
|
||||
ResetThreadLocalSuperVersions();
|
||||
}
|
||||
|
||||
void ColumnFamilyData::ResetThreadLocalSuperVersions() {
|
||||
@@ -994,10 +1090,12 @@ void ColumnFamilyData::ResetThreadLocalSuperVersions() {
|
||||
continue;
|
||||
}
|
||||
auto sv = static_cast<SuperVersion*>(ptr);
|
||||
if (sv->Unref()) {
|
||||
sv->Cleanup();
|
||||
delete sv;
|
||||
}
|
||||
bool was_last_ref __attribute__((__unused__));
|
||||
was_last_ref = sv->Unref();
|
||||
// sv couldn't have been the last reference because
|
||||
// ResetThreadLocalSuperVersions() is called before
|
||||
// unref'ing super_version_.
|
||||
assert(!was_last_ref);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1059,10 +1157,14 @@ ColumnFamilySet::~ColumnFamilySet() {
|
||||
while (column_family_data_.size() > 0) {
|
||||
// cfd destructor will delete itself from column_family_data_
|
||||
auto cfd = column_family_data_.begin()->second;
|
||||
cfd->Unref();
|
||||
bool last_ref __attribute__((__unused__));
|
||||
last_ref = cfd->Unref();
|
||||
assert(last_ref);
|
||||
delete cfd;
|
||||
}
|
||||
dummy_cfd_->Unref();
|
||||
bool dummy_last_ref __attribute__((__unused__));
|
||||
dummy_last_ref = dummy_cfd_->Unref();
|
||||
assert(dummy_last_ref);
|
||||
delete dummy_cfd_;
|
||||
}
|
||||
|
||||
|
||||
+36
-1
@@ -30,6 +30,7 @@ namespace rocksdb {
|
||||
|
||||
class Version;
|
||||
class VersionSet;
|
||||
class VersionStorageInfo;
|
||||
class MemTable;
|
||||
class MemTableListVersion;
|
||||
class CompactionPicker;
|
||||
@@ -202,6 +203,10 @@ class ColumnFamilyData {
|
||||
void SetLogNumber(uint64_t log_number) { log_number_ = log_number; }
|
||||
uint64_t GetLogNumber() const { return log_number_; }
|
||||
|
||||
void SetFlushReason(FlushReason flush_reason) {
|
||||
flush_reason_ = flush_reason;
|
||||
}
|
||||
FlushReason GetFlushReason() const { return flush_reason_; }
|
||||
// thread-safe
|
||||
const EnvOptions* soptions() const;
|
||||
const ImmutableCFOptions* ioptions() const { return &ioptions_; }
|
||||
@@ -239,7 +244,11 @@ class ColumnFamilyData {
|
||||
void SetCurrent(Version* _current);
|
||||
uint64_t GetNumLiveVersions() const; // REQUIRE: DB mutex held
|
||||
uint64_t GetTotalSstFilesSize() const; // REQUIRE: DB mutex held
|
||||
void SetMemtable(MemTable* new_mem) { mem_ = new_mem; }
|
||||
void SetMemtable(MemTable* new_mem) {
|
||||
uint64_t memtable_id = last_memtable_id_.fetch_add(1) + 1;
|
||||
new_mem->SetID(memtable_id);
|
||||
mem_ = new_mem;
|
||||
}
|
||||
|
||||
// calculate the oldest log needed for the durability of this column family
|
||||
uint64_t OldestLogToKeep();
|
||||
@@ -265,6 +274,16 @@ class ColumnFamilyData {
|
||||
const Slice& largest_user_key,
|
||||
int level) const;
|
||||
|
||||
// Check if the passed ranges overlap with any unflushed memtables
|
||||
// (immutable or mutable).
|
||||
//
|
||||
// @param super_version A referenced SuperVersion that will be held for the
|
||||
// duration of this function.
|
||||
//
|
||||
// Thread-safe
|
||||
Status RangesOverlapWithMemtables(const autovector<Range>& ranges,
|
||||
SuperVersion* super_version, bool* overlap);
|
||||
|
||||
// A flag to tell a manual compaction is to compact all levels together
|
||||
// instead of a specific level.
|
||||
static const int kCompactAllLevels;
|
||||
@@ -327,6 +346,17 @@ class ColumnFamilyData {
|
||||
bool pending_flush() { return pending_flush_; }
|
||||
bool pending_compaction() { return pending_compaction_; }
|
||||
|
||||
enum class WriteStallCause {
|
||||
kNone,
|
||||
kMemtableLimit,
|
||||
kL0FileCountLimit,
|
||||
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);
|
||||
|
||||
// Recalculate some small conditions, which are changed only during
|
||||
// compaction, adding new memtable and/or
|
||||
// recalculation of compaction score. These values are used in
|
||||
@@ -400,6 +430,8 @@ class ColumnFamilyData {
|
||||
// recovered from
|
||||
uint64_t log_number_;
|
||||
|
||||
std::atomic<FlushReason> flush_reason_;
|
||||
|
||||
// An object that keeps all the compaction stats
|
||||
// and picks the next compaction
|
||||
std::unique_ptr<CompactionPicker> compaction_picker_;
|
||||
@@ -419,6 +451,9 @@ class ColumnFamilyData {
|
||||
|
||||
// if the database was opened with 2pc enabled
|
||||
bool allow_2pc_;
|
||||
|
||||
// Memtable id to track flush.
|
||||
std::atomic<uint64_t> last_memtable_id_;
|
||||
};
|
||||
|
||||
// ColumnFamilySet has interesting thread-safety requirements
|
||||
|
||||
+7
-120
@@ -383,6 +383,9 @@ class ColumnFamilyTest : public testing::Test {
|
||||
void AssertFilesPerLevel(const std::string& value, int cf) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
ASSERT_EQ(value, FilesPerLevel(cf));
|
||||
#else
|
||||
(void) value;
|
||||
(void) cf;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -397,6 +400,8 @@ class ColumnFamilyTest : public testing::Test {
|
||||
void AssertCountLiveFiles(int expected_value) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
ASSERT_EQ(expected_value, CountLiveFiles());
|
||||
#else
|
||||
(void) expected_value;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -445,6 +450,8 @@ class ColumnFamilyTest : public testing::Test {
|
||||
void AssertCountLiveLogFiles(int value) {
|
||||
#ifndef ROCKSDB_LITE // GetSortedWalFiles is not supported
|
||||
ASSERT_EQ(value, CountLiveLogFiles());
|
||||
#else
|
||||
(void) value;
|
||||
#endif // !ROCKSDB_LITE
|
||||
}
|
||||
|
||||
@@ -1868,126 +1875,6 @@ TEST_F(ColumnFamilyTest, SameCFManualAutomaticCompactionsLevel) {
|
||||
}
|
||||
}
|
||||
|
||||
// This test checks for automatic getting a conflict if there is a
|
||||
// manual which has not yet been scheduled.
|
||||
// The manual compaction waits in NotScheduled
|
||||
// We generate more files and then trigger an automatic compaction
|
||||
// This will wait because there is an unscheduled manual compaction.
|
||||
// Once the conflict is hit, the manual compaction starts and ends
|
||||
// Then another automatic will start and end.
|
||||
TEST_F(ColumnFamilyTest, SameCFManualAutomaticConflict) {
|
||||
Open();
|
||||
CreateColumnFamilies({"one"});
|
||||
ColumnFamilyOptions default_cf, one;
|
||||
db_options_.max_open_files = 20; // only 10 files in file cache
|
||||
db_options_.max_background_compactions = 3;
|
||||
|
||||
default_cf.compaction_style = kCompactionStyleLevel;
|
||||
default_cf.num_levels = 3;
|
||||
default_cf.write_buffer_size = 64 << 10; // 64KB
|
||||
default_cf.target_file_size_base = 30 << 10;
|
||||
default_cf.max_compaction_bytes = default_cf.target_file_size_base * 1100;
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.no_block_cache = true;
|
||||
default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
one.compaction_style = kCompactionStyleUniversal;
|
||||
|
||||
one.num_levels = 1;
|
||||
// trigger compaction if there are >= 4 files
|
||||
one.level0_file_num_compaction_trigger = 4;
|
||||
one.write_buffer_size = 120000;
|
||||
|
||||
Reopen({default_cf, one});
|
||||
// make sure all background compaction jobs can be scheduled
|
||||
auto stop_token =
|
||||
dbfull()->TEST_write_controler().GetCompactionPressureToken();
|
||||
|
||||
// SETUP column family "one" -- universal style
|
||||
for (int i = 0; i < one.level0_file_num_compaction_trigger - 2; ++i) {
|
||||
PutRandomData(1, 10, 12000, true);
|
||||
PutRandomData(1, 1, 10, true);
|
||||
WaitForFlush(1);
|
||||
AssertFilesPerLevel(ToString(i + 1), 1);
|
||||
}
|
||||
bool cf_1_1 = true;
|
||||
bool cf_1_2 = true;
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::BackgroundCompaction()::Conflict",
|
||||
"ColumnFamilyTest::ManualAutoCon:7"},
|
||||
{"ColumnFamilyTest::ManualAutoCon:9",
|
||||
"ColumnFamilyTest::ManualAutoCon:8"},
|
||||
{"ColumnFamilyTest::ManualAutoCon:2",
|
||||
"ColumnFamilyTest::ManualAutoCon:6"},
|
||||
{"ColumnFamilyTest::ManualAutoCon:4",
|
||||
"ColumnFamilyTest::ManualAutoCon:5"},
|
||||
{"ColumnFamilyTest::ManualAutoCon:1",
|
||||
"ColumnFamilyTest::ManualAutoCon:2"},
|
||||
{"ColumnFamilyTest::ManualAutoCon:1",
|
||||
"ColumnFamilyTest::ManualAutoCon:3"}});
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* arg) {
|
||||
if (cf_1_1) {
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::ManualAutoCon:4");
|
||||
cf_1_1 = false;
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::ManualAutoCon:3");
|
||||
} else if (cf_1_2) {
|
||||
cf_1_2 = false;
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::ManualAutoCon:2");
|
||||
}
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::RunManualCompaction:NotScheduled", [&](void* arg) {
|
||||
InstrumentedMutex* mutex = static_cast<InstrumentedMutex*>(arg);
|
||||
mutex->Unlock();
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::ManualAutoCon:9");
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::ManualAutoCon:7");
|
||||
mutex->Lock();
|
||||
});
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
rocksdb::port::Thread threads([&] {
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.exclusive_manual_compaction = false;
|
||||
ASSERT_OK(
|
||||
db_->CompactRange(compact_options, handles_[1], nullptr, nullptr));
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::ManualAutoCon:6");
|
||||
});
|
||||
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::ManualAutoCon:8");
|
||||
WaitForFlush(1);
|
||||
|
||||
// Add more L0 files and force automatic compaction
|
||||
for (int i = 0; i < one.level0_file_num_compaction_trigger; ++i) {
|
||||
PutRandomData(1, 10, 12000, true);
|
||||
PutRandomData(1, 1, 10, true);
|
||||
WaitForFlush(1);
|
||||
AssertFilesPerLevel(ToString(one.level0_file_num_compaction_trigger + i),
|
||||
1);
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::ManualAutoCon:5");
|
||||
// Add more L0 files and force automatic compaction
|
||||
for (int i = 0; i < one.level0_file_num_compaction_trigger; ++i) {
|
||||
PutRandomData(1, 10, 12000, true);
|
||||
PutRandomData(1, 1, 10, true);
|
||||
WaitForFlush(1);
|
||||
}
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::ManualAutoCon:1");
|
||||
|
||||
threads.join();
|
||||
WaitForCompaction();
|
||||
// VERIFY compaction "one"
|
||||
ASSERT_LE(NumTableFilesAtLevel(0, 1), 3);
|
||||
|
||||
// Compare against saved keys
|
||||
std::set<std::string>::iterator key_iter = keys_.begin();
|
||||
while (key_iter != keys_.end()) {
|
||||
ASSERT_NE("NOT_FOUND", Get(1, *key_iter));
|
||||
key_iter++;
|
||||
}
|
||||
}
|
||||
|
||||
// In this test, we generate enough files to trigger automatic compactions.
|
||||
// The automatic compaction waits in NonTrivial:AfterRun
|
||||
// We generate more files and then trigger an automatic compaction
|
||||
|
||||
@@ -319,7 +319,7 @@ int main(int argc, char** argv) {
|
||||
#else
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
int main(int /*argc*/, char** /*argv*/) {
|
||||
fprintf(stderr,
|
||||
"SKIPPED as DBImpl::CompactFiles is not supported in ROCKSDB_LITE\n");
|
||||
return 0;
|
||||
|
||||
+13
-10
@@ -405,20 +405,23 @@ void Compaction::Summary(char* output, int len) {
|
||||
uint64_t Compaction::OutputFilePreallocationSize() const {
|
||||
uint64_t preallocation_size = 0;
|
||||
|
||||
if (max_output_file_size_ != port::kMaxUint64 &&
|
||||
(cfd_->ioptions()->compaction_style == kCompactionStyleLevel ||
|
||||
output_level() > 0)) {
|
||||
preallocation_size = max_output_file_size_;
|
||||
} else {
|
||||
for (const auto& level_files : inputs_) {
|
||||
for (const auto& file : level_files.files) {
|
||||
preallocation_size += file->fd.GetFileSize();
|
||||
}
|
||||
for (const auto& level_files : inputs_) {
|
||||
for (const auto& file : level_files.files) {
|
||||
preallocation_size += file->fd.GetFileSize();
|
||||
}
|
||||
}
|
||||
|
||||
if (max_output_file_size_ != port::kMaxUint64 &&
|
||||
(immutable_cf_options_.compaction_style == kCompactionStyleLevel ||
|
||||
output_level() > 0)) {
|
||||
preallocation_size = std::min(max_output_file_size_, preallocation_size);
|
||||
}
|
||||
|
||||
// Over-estimate slightly so we don't end up just barely crossing
|
||||
// the threshold
|
||||
return preallocation_size + (preallocation_size / 10);
|
||||
// No point to prellocate more than 1GB.
|
||||
return std::min(uint64_t{1073741824},
|
||||
preallocation_size + (preallocation_size / 10));
|
||||
}
|
||||
|
||||
std::unique_ptr<CompactionFilter> Compaction::CreateCompactionFilter() const {
|
||||
|
||||
@@ -537,6 +537,11 @@ void CompactionIterator::NextFromInput() {
|
||||
//
|
||||
// Note: Dropping this Delete will not affect TransactionDB
|
||||
// write-conflict checking since it is earlier than any snapshot.
|
||||
//
|
||||
// It seems that we can also drop deletion later than earliest snapshot
|
||||
// given that:
|
||||
// (1) The deletion is earlier than earliest_write_conflict_snapshot, and
|
||||
// (2) No value exist earlier than the deletion.
|
||||
++iter_stats_.num_record_drop_obsolete;
|
||||
if (!bottommost_level_) {
|
||||
++iter_stats_.num_optimized_del_drop_obsolete;
|
||||
@@ -554,10 +559,6 @@ void CompactionIterator::NextFromInput() {
|
||||
// have hit (A)
|
||||
// We encapsulate the merge related state machine in a different
|
||||
// object to minimize change to the existing flow.
|
||||
// In case snapshot_checker is present, we can probably merge further
|
||||
// beyond prev_snapshot, since there could be more keys with sequence
|
||||
// smaller than prev_snapshot, but reported by snapshot_checker as not
|
||||
// visible by prev_snapshot. But it will make the logic more complicated.
|
||||
Status s = merge_helper_->MergeUntil(input_, range_del_agg_,
|
||||
prev_snapshot, bottommost_level_);
|
||||
merge_out_iter_.SeekToFirst();
|
||||
@@ -621,9 +622,12 @@ void CompactionIterator::PrepareOutput() {
|
||||
// and the earliest snapshot is larger than this seqno
|
||||
// and the userkey differs from the last userkey in compaction
|
||||
// then we can squash the seqno to zero.
|
||||
|
||||
//
|
||||
// This is safe for TransactionDB write-conflict checking since transactions
|
||||
// only care about sequence number larger than any active snapshots.
|
||||
//
|
||||
// Can we do the same for levels above bottom level as long as
|
||||
// KeyNotExistsBeyondOutputLevel() return true?
|
||||
if ((compaction_ != nullptr &&
|
||||
!compaction_->allow_ingest_behind()) &&
|
||||
ikeyNotNeededForIncrementalSnapshot() &&
|
||||
|
||||
+419
-41
@@ -9,8 +9,10 @@
|
||||
#include <vector>
|
||||
|
||||
#include "port/port.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/testharness.h"
|
||||
#include "util/testutil.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -39,9 +41,9 @@ class NoMergingMergeOp : public MergeOperator {
|
||||
// Always returns Decition::kRemove.
|
||||
class StallingFilter : public CompactionFilter {
|
||||
public:
|
||||
virtual Decision FilterV2(int level, const Slice& key, ValueType t,
|
||||
const Slice& existing_value, std::string* new_value,
|
||||
std::string* skip_until) const override {
|
||||
Decision FilterV2(int /*level*/, const Slice& key, ValueType /*type*/,
|
||||
const Slice& /*existing_value*/, std::string* /*new_value*/,
|
||||
std::string* /*skip_until*/) const override {
|
||||
int k = std::atoi(key.ToString().c_str());
|
||||
last_seen.store(k);
|
||||
while (k >= stall_at.load()) {
|
||||
@@ -72,6 +74,18 @@ class StallingFilter : public CompactionFilter {
|
||||
mutable std::atomic<int> last_seen{0};
|
||||
};
|
||||
|
||||
// Compaction filter that filter out all keys.
|
||||
class FilterAllKeysCompactionFilter : public CompactionFilter {
|
||||
public:
|
||||
Decision FilterV2(int /*level*/, const Slice& /*key*/, ValueType /*type*/,
|
||||
const Slice& /*existing_value*/, std::string* /*new_value*/,
|
||||
std::string* /*skip_until*/) const override {
|
||||
return Decision::kRemove;
|
||||
}
|
||||
|
||||
const char* Name() const override { return "AllKeysCompactionFilter"; }
|
||||
};
|
||||
|
||||
class LoggingForwardVectorIterator : public InternalIterator {
|
||||
public:
|
||||
struct Action {
|
||||
@@ -144,76 +158,146 @@ class FakeCompaction : public CompactionIterator::CompactionProxy {
|
||||
public:
|
||||
FakeCompaction() = default;
|
||||
|
||||
virtual int level(size_t compaction_input_level) const { return 0; }
|
||||
virtual int level(size_t compaction_input_level) const override { return 0; }
|
||||
virtual bool KeyNotExistsBeyondOutputLevel(
|
||||
const Slice& user_key, std::vector<size_t>* level_ptrs) const {
|
||||
return key_not_exists_beyond_output_level;
|
||||
const Slice& user_key, std::vector<size_t>* level_ptrs) const override {
|
||||
return is_bottommost_level || key_not_exists_beyond_output_level;
|
||||
}
|
||||
virtual bool bottommost_level() const { return false; }
|
||||
virtual int number_levels() const { return 1; }
|
||||
virtual Slice GetLargestUserKey() const {
|
||||
virtual bool bottommost_level() const override { return is_bottommost_level; }
|
||||
virtual int number_levels() const override { return 1; }
|
||||
virtual Slice GetLargestUserKey() const override {
|
||||
return "\xff\xff\xff\xff\xff\xff\xff\xff\xff";
|
||||
}
|
||||
virtual bool allow_ingest_behind() const { return false; }
|
||||
virtual bool allow_ingest_behind() const override { return false; }
|
||||
|
||||
virtual bool preserve_deletes() const {return false; }
|
||||
virtual bool preserve_deletes() const override { return false; }
|
||||
|
||||
bool key_not_exists_beyond_output_level = false;
|
||||
|
||||
bool is_bottommost_level = false;
|
||||
};
|
||||
|
||||
class CompactionIteratorTest : public testing::Test {
|
||||
// A simplifed snapshot checker which assumes each snapshot has a global
|
||||
// last visible sequence.
|
||||
class TestSnapshotChecker : public SnapshotChecker {
|
||||
public:
|
||||
explicit TestSnapshotChecker(
|
||||
SequenceNumber last_committed_sequence,
|
||||
const std::unordered_map<SequenceNumber, SequenceNumber>& snapshots = {})
|
||||
: last_committed_sequence_(last_committed_sequence),
|
||||
snapshots_(snapshots) {}
|
||||
|
||||
bool IsInSnapshot(SequenceNumber seq,
|
||||
SequenceNumber snapshot_seq) const override {
|
||||
if (snapshot_seq == kMaxSequenceNumber) {
|
||||
return seq <= last_committed_sequence_;
|
||||
}
|
||||
assert(snapshots_.count(snapshot_seq) > 0);
|
||||
return seq <= snapshots_.at(snapshot_seq);
|
||||
}
|
||||
|
||||
private:
|
||||
SequenceNumber last_committed_sequence_;
|
||||
// A map of valid snapshot to last visible sequence to the snapshot.
|
||||
std::unordered_map<SequenceNumber, SequenceNumber> snapshots_;
|
||||
};
|
||||
|
||||
// Test param:
|
||||
// bool: whether to pass snapshot_checker to compaction iterator.
|
||||
class CompactionIteratorTest : public testing::TestWithParam<bool> {
|
||||
public:
|
||||
CompactionIteratorTest()
|
||||
: cmp_(BytewiseComparator()), icmp_(cmp_), snapshots_({}) {}
|
||||
|
||||
void InitIterators(const std::vector<std::string>& ks,
|
||||
const std::vector<std::string>& vs,
|
||||
const std::vector<std::string>& range_del_ks,
|
||||
const std::vector<std::string>& range_del_vs,
|
||||
SequenceNumber last_sequence,
|
||||
MergeOperator* merge_op = nullptr,
|
||||
CompactionFilter* filter = nullptr) {
|
||||
void InitIterators(
|
||||
const std::vector<std::string>& ks, const std::vector<std::string>& vs,
|
||||
const std::vector<std::string>& range_del_ks,
|
||||
const std::vector<std::string>& range_del_vs,
|
||||
SequenceNumber last_sequence,
|
||||
SequenceNumber last_committed_sequence = kMaxSequenceNumber,
|
||||
MergeOperator* merge_op = nullptr, CompactionFilter* filter = nullptr,
|
||||
bool bottommost_level = false,
|
||||
SequenceNumber earliest_write_conflict_snapshot = kMaxSequenceNumber) {
|
||||
std::unique_ptr<InternalIterator> range_del_iter(
|
||||
new test::VectorIterator(range_del_ks, range_del_vs));
|
||||
range_del_agg_.reset(new RangeDelAggregator(icmp_, snapshots_));
|
||||
ASSERT_OK(range_del_agg_->AddTombstones(std::move(range_del_iter)));
|
||||
|
||||
std::unique_ptr<CompactionIterator::CompactionProxy> compaction;
|
||||
if (filter) {
|
||||
if (filter || bottommost_level) {
|
||||
compaction_proxy_ = new FakeCompaction();
|
||||
compaction_proxy_->is_bottommost_level = bottommost_level;
|
||||
compaction.reset(compaction_proxy_);
|
||||
}
|
||||
// TODO(yiwu) add a mock snapshot checker and add test for it.
|
||||
SnapshotChecker* snapshot_checker = nullptr;
|
||||
bool use_snapshot_checker = UseSnapshotChecker() || GetParam();
|
||||
if (use_snapshot_checker || last_committed_sequence < kMaxSequenceNumber) {
|
||||
snapshot_checker_.reset(
|
||||
new TestSnapshotChecker(last_committed_sequence, snapshot_map_));
|
||||
}
|
||||
merge_helper_.reset(
|
||||
new MergeHelper(Env::Default(), cmp_, merge_op, filter, nullptr, false,
|
||||
0 /*latest_snapshot*/, snapshot_checker_.get(),
|
||||
0 /*level*/, nullptr /*statistics*/, &shutting_down_));
|
||||
|
||||
merge_helper_.reset(new MergeHelper(Env::Default(), cmp_, merge_op, filter,
|
||||
nullptr, false, 0, 0, nullptr,
|
||||
&shutting_down_));
|
||||
iter_.reset(new LoggingForwardVectorIterator(ks, vs));
|
||||
iter_->SeekToFirst();
|
||||
c_iter_.reset(new CompactionIterator(
|
||||
iter_.get(), cmp_, merge_helper_.get(), last_sequence, &snapshots_,
|
||||
kMaxSequenceNumber, snapshot_checker, Env::Default(), false,
|
||||
range_del_agg_.get(), std::move(compaction), filter, nullptr,
|
||||
&shutting_down_));
|
||||
earliest_write_conflict_snapshot, snapshot_checker_.get(),
|
||||
Env::Default(), false, range_del_agg_.get(), std::move(compaction),
|
||||
filter, nullptr, &shutting_down_));
|
||||
}
|
||||
|
||||
void AddSnapshot(SequenceNumber snapshot) { snapshots_.push_back(snapshot); }
|
||||
void AddSnapshot(SequenceNumber snapshot,
|
||||
SequenceNumber last_visible_seq = kMaxSequenceNumber) {
|
||||
snapshots_.push_back(snapshot);
|
||||
snapshot_map_[snapshot] = last_visible_seq;
|
||||
}
|
||||
|
||||
virtual bool UseSnapshotChecker() const { return false; }
|
||||
|
||||
void RunTest(
|
||||
const std::vector<std::string>& input_keys,
|
||||
const std::vector<std::string>& input_values,
|
||||
const std::vector<std::string>& expected_keys,
|
||||
const std::vector<std::string>& expected_values,
|
||||
SequenceNumber last_committed_seq = kMaxSequenceNumber,
|
||||
MergeOperator* merge_operator = nullptr,
|
||||
CompactionFilter* compaction_filter = nullptr,
|
||||
bool bottommost_level = false,
|
||||
SequenceNumber earliest_write_conflict_snapshot = kMaxSequenceNumber) {
|
||||
InitIterators(input_keys, input_values, {}, {}, kMaxSequenceNumber,
|
||||
last_committed_seq, merge_operator, compaction_filter,
|
||||
bottommost_level, earliest_write_conflict_snapshot);
|
||||
c_iter_->SeekToFirst();
|
||||
for (size_t i = 0; i < expected_keys.size(); i++) {
|
||||
std::string info = "i = " + ToString(i);
|
||||
ASSERT_TRUE(c_iter_->Valid()) << info;
|
||||
ASSERT_OK(c_iter_->status()) << info;
|
||||
ASSERT_EQ(expected_keys[i], c_iter_->key().ToString()) << info;
|
||||
ASSERT_EQ(expected_values[i], c_iter_->value().ToString()) << info;
|
||||
c_iter_->Next();
|
||||
}
|
||||
ASSERT_FALSE(c_iter_->Valid());
|
||||
}
|
||||
|
||||
const Comparator* cmp_;
|
||||
const InternalKeyComparator icmp_;
|
||||
std::vector<SequenceNumber> snapshots_;
|
||||
// A map of valid snapshot to last visible sequence to the snapshot.
|
||||
std::unordered_map<SequenceNumber, SequenceNumber> snapshot_map_;
|
||||
std::unique_ptr<MergeHelper> merge_helper_;
|
||||
std::unique_ptr<LoggingForwardVectorIterator> iter_;
|
||||
std::unique_ptr<CompactionIterator> c_iter_;
|
||||
std::unique_ptr<RangeDelAggregator> range_del_agg_;
|
||||
std::unique_ptr<SnapshotChecker> snapshot_checker_;
|
||||
std::atomic<bool> shutting_down_{false};
|
||||
FakeCompaction* compaction_proxy_;
|
||||
};
|
||||
|
||||
// It is possible that the output of the compaction iterator is empty even if
|
||||
// the input is not.
|
||||
TEST_F(CompactionIteratorTest, EmptyResult) {
|
||||
TEST_P(CompactionIteratorTest, EmptyResult) {
|
||||
InitIterators({test::KeyStr("a", 5, kTypeSingleDeletion),
|
||||
test::KeyStr("a", 3, kTypeValue)},
|
||||
{"", "val"}, {}, {}, 5);
|
||||
@@ -223,7 +307,7 @@ TEST_F(CompactionIteratorTest, EmptyResult) {
|
||||
|
||||
// If there is a corruption after a single deletion, the corrupted key should
|
||||
// be preserved.
|
||||
TEST_F(CompactionIteratorTest, CorruptionAfterSingleDeletion) {
|
||||
TEST_P(CompactionIteratorTest, CorruptionAfterSingleDeletion) {
|
||||
InitIterators({test::KeyStr("a", 5, kTypeSingleDeletion),
|
||||
test::KeyStr("a", 3, kTypeValue, true),
|
||||
test::KeyStr("b", 10, kTypeValue)},
|
||||
@@ -242,7 +326,7 @@ TEST_F(CompactionIteratorTest, CorruptionAfterSingleDeletion) {
|
||||
ASSERT_FALSE(c_iter_->Valid());
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorTest, SimpleRangeDeletion) {
|
||||
TEST_P(CompactionIteratorTest, SimpleRangeDeletion) {
|
||||
InitIterators({test::KeyStr("morning", 5, kTypeValue),
|
||||
test::KeyStr("morning", 2, kTypeValue),
|
||||
test::KeyStr("night", 3, kTypeValue)},
|
||||
@@ -258,7 +342,7 @@ TEST_F(CompactionIteratorTest, SimpleRangeDeletion) {
|
||||
ASSERT_FALSE(c_iter_->Valid());
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorTest, RangeDeletionWithSnapshots) {
|
||||
TEST_P(CompactionIteratorTest, RangeDeletionWithSnapshots) {
|
||||
AddSnapshot(10);
|
||||
std::vector<std::string> ks1;
|
||||
ks1.push_back(test::KeyStr("ma", 28, kTypeRangeDeletion));
|
||||
@@ -279,7 +363,7 @@ TEST_F(CompactionIteratorTest, RangeDeletionWithSnapshots) {
|
||||
ASSERT_FALSE(c_iter_->Valid());
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorTest, CompactionFilterSkipUntil) {
|
||||
TEST_P(CompactionIteratorTest, CompactionFilterSkipUntil) {
|
||||
class Filter : public CompactionFilter {
|
||||
virtual Decision FilterV2(int level, const Slice& key, ValueType t,
|
||||
const Slice& existing_value,
|
||||
@@ -354,7 +438,7 @@ TEST_F(CompactionIteratorTest, CompactionFilterSkipUntil) {
|
||||
test::KeyStr("j", 99, kTypeValue)},
|
||||
{"av50", "am45", "bv60", "bv40", "cv35", "dm70", "em71", "fm65", "fm30",
|
||||
"fv25", "gv90", "hv91", "im95", "jv99"},
|
||||
{}, {}, kMaxSequenceNumber, &merge_op, &filter);
|
||||
{}, {}, kMaxSequenceNumber, kMaxSequenceNumber, &merge_op, &filter);
|
||||
|
||||
// Compaction should output just "a", "e" and "h" keys.
|
||||
c_iter_->SeekToFirst();
|
||||
@@ -389,13 +473,14 @@ TEST_F(CompactionIteratorTest, CompactionFilterSkipUntil) {
|
||||
ASSERT_EQ(expected_actions, iter_->log);
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorTest, ShuttingDownInFilter) {
|
||||
TEST_P(CompactionIteratorTest, ShuttingDownInFilter) {
|
||||
NoMergingMergeOp merge_op;
|
||||
StallingFilter filter;
|
||||
InitIterators(
|
||||
{test::KeyStr("1", 1, kTypeValue), test::KeyStr("2", 2, kTypeValue),
|
||||
test::KeyStr("3", 3, kTypeValue), test::KeyStr("4", 4, kTypeValue)},
|
||||
{"v1", "v2", "v3", "v4"}, {}, {}, kMaxSequenceNumber, &merge_op, &filter);
|
||||
{"v1", "v2", "v3", "v4"}, {}, {}, kMaxSequenceNumber, kMaxSequenceNumber,
|
||||
&merge_op, &filter);
|
||||
// Don't leave tombstones (kTypeDeletion) for filtered keys.
|
||||
compaction_proxy_->key_not_exists_beyond_output_level = true;
|
||||
|
||||
@@ -426,13 +511,14 @@ TEST_F(CompactionIteratorTest, ShuttingDownInFilter) {
|
||||
|
||||
// Same as ShuttingDownInFilter, but shutdown happens during filter call for
|
||||
// a merge operand, not for a value.
|
||||
TEST_F(CompactionIteratorTest, ShuttingDownInMerge) {
|
||||
TEST_P(CompactionIteratorTest, ShuttingDownInMerge) {
|
||||
NoMergingMergeOp merge_op;
|
||||
StallingFilter filter;
|
||||
InitIterators(
|
||||
{test::KeyStr("1", 1, kTypeValue), test::KeyStr("2", 2, kTypeMerge),
|
||||
test::KeyStr("3", 3, kTypeMerge), test::KeyStr("4", 4, kTypeValue)},
|
||||
{"v1", "v2", "v3", "v4"}, {}, {}, kMaxSequenceNumber, &merge_op, &filter);
|
||||
{"v1", "v2", "v3", "v4"}, {}, {}, kMaxSequenceNumber, kMaxSequenceNumber,
|
||||
&merge_op, &filter);
|
||||
compaction_proxy_->key_not_exists_beyond_output_level = true;
|
||||
|
||||
std::atomic<bool> seek_done{false};
|
||||
@@ -460,7 +546,7 @@ TEST_F(CompactionIteratorTest, ShuttingDownInMerge) {
|
||||
EXPECT_EQ(2, filter.last_seen.load());
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorTest, SingleMergeOperand) {
|
||||
TEST_P(CompactionIteratorTest, SingleMergeOperand) {
|
||||
class Filter : public CompactionFilter {
|
||||
virtual Decision FilterV2(int level, const Slice& key, ValueType t,
|
||||
const Slice& existing_value,
|
||||
@@ -552,7 +638,7 @@ TEST_F(CompactionIteratorTest, SingleMergeOperand) {
|
||||
// c should invoke FullMerge due to kTypeValue at the beginning.
|
||||
test::KeyStr("c", 90, kTypeMerge), test::KeyStr("c", 80, kTypeValue)},
|
||||
{"av1", "bv2", "bv1", "cv2", "cv1"}, {}, {}, kMaxSequenceNumber,
|
||||
&merge_op, &filter);
|
||||
kMaxSequenceNumber, &merge_op, &filter);
|
||||
|
||||
c_iter_->SeekToFirst();
|
||||
ASSERT_TRUE(c_iter_->Valid());
|
||||
@@ -565,6 +651,298 @@ TEST_F(CompactionIteratorTest, SingleMergeOperand) {
|
||||
ASSERT_EQ("cv1cv2", c_iter_->value().ToString());
|
||||
}
|
||||
|
||||
// In bottommost level, values earlier than earliest snapshot can be output
|
||||
// with sequence = 0.
|
||||
TEST_P(CompactionIteratorTest, ZeroOutSequenceAtBottomLevel) {
|
||||
AddSnapshot(1);
|
||||
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*/,
|
||||
nullptr /*merge_operator*/, nullptr /*compaction_filter*/,
|
||||
true /*bottommost_level*/);
|
||||
}
|
||||
|
||||
// In bottommost level, deletions earlier than earliest snapshot can be removed
|
||||
// permanently.
|
||||
TEST_P(CompactionIteratorTest, RemoveDeletionAtBottomLevel) {
|
||||
AddSnapshot(1);
|
||||
RunTest({test::KeyStr("a", 1, kTypeDeletion),
|
||||
test::KeyStr("b", 2, kTypeDeletion)},
|
||||
{"", ""}, {test::KeyStr("b", 2, kTypeDeletion)}, {""},
|
||||
kMaxSequenceNumber /*last_commited_seq*/, nullptr /*merge_operator*/,
|
||||
nullptr /*compaction_filter*/, true /*bottommost_level*/);
|
||||
}
|
||||
|
||||
// In bottommost level, single deletions earlier than earliest snapshot can be
|
||||
// removed permanently.
|
||||
TEST_P(CompactionIteratorTest, RemoveSingleDeletionAtBottomLevel) {
|
||||
AddSnapshot(1);
|
||||
RunTest({test::KeyStr("a", 1, kTypeSingleDeletion),
|
||||
test::KeyStr("b", 2, kTypeSingleDeletion)},
|
||||
{"", ""}, {test::KeyStr("b", 2, kTypeSingleDeletion)}, {""},
|
||||
kMaxSequenceNumber /*last_commited_seq*/, nullptr /*merge_operator*/,
|
||||
nullptr /*compaction_filter*/, true /*bottommost_level*/);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(CompactionIteratorTestInstance, CompactionIteratorTest,
|
||||
testing::Values(true, false));
|
||||
|
||||
// Tests how CompactionIterator work together with SnapshotChecker.
|
||||
class CompactionIteratorWithSnapshotCheckerTest
|
||||
: public CompactionIteratorTest {
|
||||
public:
|
||||
bool UseSnapshotChecker() const override { return true; }
|
||||
};
|
||||
|
||||
// Uncommitted keys (keys with seq > last_committed_seq) should be output as-is
|
||||
// while committed version of these keys should get compacted as usual.
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
PreserveUncommittedKeys_Value) {
|
||||
RunTest(
|
||||
{test::KeyStr("foo", 3, kTypeValue), test::KeyStr("foo", 2, kTypeValue),
|
||||
test::KeyStr("foo", 1, kTypeValue)},
|
||||
{"v3", "v2", "v1"},
|
||||
{test::KeyStr("foo", 3, kTypeValue), test::KeyStr("foo", 2, kTypeValue)},
|
||||
{"v3", "v2"}, 2 /*last_committed_seq*/);
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
PreserveUncommittedKeys_Deletion) {
|
||||
RunTest({test::KeyStr("foo", 2, kTypeDeletion),
|
||||
test::KeyStr("foo", 1, kTypeValue)},
|
||||
{"", "v1"},
|
||||
{test::KeyStr("foo", 2, kTypeDeletion),
|
||||
test::KeyStr("foo", 1, kTypeValue)},
|
||||
{"", "v1"}, 1 /*last_committed_seq*/);
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
PreserveUncommittedKeys_Merge) {
|
||||
auto merge_op = MergeOperators::CreateStringAppendOperator();
|
||||
RunTest(
|
||||
{test::KeyStr("foo", 3, kTypeMerge), test::KeyStr("foo", 2, kTypeMerge),
|
||||
test::KeyStr("foo", 1, kTypeValue)},
|
||||
{"v3", "v2", "v1"},
|
||||
{test::KeyStr("foo", 3, kTypeMerge), test::KeyStr("foo", 2, kTypeValue)},
|
||||
{"v3", "v1,v2"}, 2 /*last_committed_seq*/, merge_op.get());
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
PreserveUncommittedKeys_SingleDelete) {
|
||||
RunTest({test::KeyStr("foo", 2, kTypeSingleDeletion),
|
||||
test::KeyStr("foo", 1, kTypeValue)},
|
||||
{"", "v1"},
|
||||
{test::KeyStr("foo", 2, kTypeSingleDeletion),
|
||||
test::KeyStr("foo", 1, kTypeValue)},
|
||||
{"", "v1"}, 1 /*last_committed_seq*/);
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
PreserveUncommittedKeys_BlobIndex) {
|
||||
RunTest({test::KeyStr("foo", 3, kTypeBlobIndex),
|
||||
test::KeyStr("foo", 2, kTypeBlobIndex),
|
||||
test::KeyStr("foo", 1, kTypeBlobIndex)},
|
||||
{"v3", "v2", "v1"},
|
||||
{test::KeyStr("foo", 3, kTypeBlobIndex),
|
||||
test::KeyStr("foo", 2, kTypeBlobIndex)},
|
||||
{"v3", "v2"}, 2 /*last_committed_seq*/);
|
||||
}
|
||||
|
||||
// Test compaction iterator dedup keys visible to the same snapshot.
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest, DedupSameSnapshot_Value) {
|
||||
AddSnapshot(2, 1);
|
||||
RunTest(
|
||||
{test::KeyStr("foo", 4, kTypeValue), test::KeyStr("foo", 3, kTypeValue),
|
||||
test::KeyStr("foo", 2, kTypeValue), test::KeyStr("foo", 1, kTypeValue)},
|
||||
{"v4", "v3", "v2", "v1"},
|
||||
{test::KeyStr("foo", 4, kTypeValue), test::KeyStr("foo", 3, kTypeValue),
|
||||
test::KeyStr("foo", 1, kTypeValue)},
|
||||
{"v4", "v3", "v1"}, 3 /*last_committed_seq*/);
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest, DedupSameSnapshot_Deletion) {
|
||||
AddSnapshot(2, 1);
|
||||
RunTest(
|
||||
{test::KeyStr("foo", 4, kTypeValue),
|
||||
test::KeyStr("foo", 3, kTypeDeletion),
|
||||
test::KeyStr("foo", 2, kTypeValue), test::KeyStr("foo", 1, kTypeValue)},
|
||||
{"v4", "", "v2", "v1"},
|
||||
{test::KeyStr("foo", 4, kTypeValue),
|
||||
test::KeyStr("foo", 3, kTypeDeletion),
|
||||
test::KeyStr("foo", 1, kTypeValue)},
|
||||
{"v4", "", "v1"}, 3 /*last_committed_seq*/);
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest, DedupSameSnapshot_Merge) {
|
||||
AddSnapshot(2, 1);
|
||||
AddSnapshot(4, 3);
|
||||
auto merge_op = MergeOperators::CreateStringAppendOperator();
|
||||
RunTest(
|
||||
{test::KeyStr("foo", 5, kTypeMerge), test::KeyStr("foo", 4, kTypeMerge),
|
||||
test::KeyStr("foo", 3, kTypeMerge), test::KeyStr("foo", 2, kTypeMerge),
|
||||
test::KeyStr("foo", 1, kTypeValue)},
|
||||
{"v5", "v4", "v3", "v2", "v1"},
|
||||
{test::KeyStr("foo", 5, kTypeMerge), test::KeyStr("foo", 4, kTypeMerge),
|
||||
test::KeyStr("foo", 3, kTypeMerge), test::KeyStr("foo", 1, kTypeValue)},
|
||||
{"v5", "v4", "v2,v3", "v1"}, 4 /*last_committed_seq*/, merge_op.get());
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
DedupSameSnapshot_SingleDeletion) {
|
||||
AddSnapshot(2, 1);
|
||||
RunTest(
|
||||
{test::KeyStr("foo", 4, kTypeValue),
|
||||
test::KeyStr("foo", 3, kTypeSingleDeletion),
|
||||
test::KeyStr("foo", 2, kTypeValue), test::KeyStr("foo", 1, kTypeValue)},
|
||||
{"v4", "", "v2", "v1"},
|
||||
{test::KeyStr("foo", 4, kTypeValue), test::KeyStr("foo", 1, kTypeValue)},
|
||||
{"v4", "v1"}, 3 /*last_committed_seq*/);
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest, DedupSameSnapshot_BlobIndex) {
|
||||
AddSnapshot(2, 1);
|
||||
RunTest({test::KeyStr("foo", 4, kTypeBlobIndex),
|
||||
test::KeyStr("foo", 3, kTypeBlobIndex),
|
||||
test::KeyStr("foo", 2, kTypeBlobIndex),
|
||||
test::KeyStr("foo", 1, kTypeBlobIndex)},
|
||||
{"v4", "v3", "v2", "v1"},
|
||||
{test::KeyStr("foo", 4, kTypeBlobIndex),
|
||||
test::KeyStr("foo", 3, kTypeBlobIndex),
|
||||
test::KeyStr("foo", 1, kTypeBlobIndex)},
|
||||
{"v4", "v3", "v1"}, 3 /*last_committed_seq*/);
|
||||
}
|
||||
|
||||
// At bottom level, sequence numbers can be zero out, and deletions can be
|
||||
// removed, but only when they are visible to earliest snapshot.
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
NotZeroOutSequenceIfNotVisibleToEarliestSnapshot) {
|
||||
AddSnapshot(2, 1);
|
||||
RunTest({test::KeyStr("a", 1, kTypeValue), test::KeyStr("b", 2, kTypeValue),
|
||||
test::KeyStr("c", 3, kTypeValue)},
|
||||
{"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*/,
|
||||
nullptr /*merge_operator*/, nullptr /*compaction_filter*/,
|
||||
true /*bottommost_level*/);
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
NotRemoveDeletionIfNotVisibleToEarliestSnapshot) {
|
||||
AddSnapshot(2, 1);
|
||||
RunTest(
|
||||
{test::KeyStr("a", 1, kTypeDeletion), test::KeyStr("b", 2, kTypeDeletion),
|
||||
test::KeyStr("c", 3, kTypeDeletion)},
|
||||
{"", "", ""},
|
||||
{test::KeyStr("b", 2, kTypeDeletion),
|
||||
test::KeyStr("c", 3, kTypeDeletion)},
|
||||
{"", ""}, kMaxSequenceNumber /*last_commited_seq*/,
|
||||
nullptr /*merge_operator*/, nullptr /*compaction_filter*/,
|
||||
true /*bottommost_level*/);
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
NotRemoveSingleDeletionIfNotVisibleToEarliestSnapshot) {
|
||||
AddSnapshot(2, 1);
|
||||
RunTest({test::KeyStr("a", 1, kTypeSingleDeletion),
|
||||
test::KeyStr("b", 2, kTypeSingleDeletion),
|
||||
test::KeyStr("c", 3, kTypeSingleDeletion)},
|
||||
{"", "", ""},
|
||||
{test::KeyStr("b", 2, kTypeSingleDeletion),
|
||||
test::KeyStr("c", 3, kTypeSingleDeletion)},
|
||||
{"", ""}, kMaxSequenceNumber /*last_commited_seq*/,
|
||||
nullptr /*merge_operator*/, nullptr /*compaction_filter*/,
|
||||
true /*bottommost_level*/);
|
||||
}
|
||||
|
||||
// Single delete should not cancel out values that not visible to the
|
||||
// same set of snapshots
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
SingleDeleteAcrossSnapshotBoundary) {
|
||||
AddSnapshot(2, 1);
|
||||
RunTest({test::KeyStr("a", 2, kTypeSingleDeletion),
|
||||
test::KeyStr("a", 1, kTypeValue)},
|
||||
{"", "v1"},
|
||||
{test::KeyStr("a", 2, kTypeSingleDeletion),
|
||||
test::KeyStr("a", 1, kTypeValue)},
|
||||
{"", "v1"}, 2 /*last_committed_seq*/);
|
||||
}
|
||||
|
||||
// Single delete should be kept in case it is not visible to the
|
||||
// earliest write conflict snapshot. If a single delete is kept for this reason,
|
||||
// corresponding value can be trimmed to save space.
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
KeepSingleDeletionForWriteConflictChecking) {
|
||||
AddSnapshot(2, 0);
|
||||
RunTest({test::KeyStr("a", 2, kTypeSingleDeletion),
|
||||
test::KeyStr("a", 1, kTypeValue)},
|
||||
{"", "v1"},
|
||||
{test::KeyStr("a", 2, kTypeSingleDeletion),
|
||||
test::KeyStr("a", 1, kTypeValue)},
|
||||
{"", ""}, 2 /*last_committed_seq*/, nullptr /*merge_operator*/,
|
||||
nullptr /*compaction_filter*/, false /*bottommost_level*/,
|
||||
2 /*earliest_write_conflict_snapshot*/);
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest, CompactionFilter_Value) {
|
||||
std::unique_ptr<CompactionFilter> compaction_filter(
|
||||
new FilterAllKeysCompactionFilter());
|
||||
RunTest(
|
||||
{test::KeyStr("a", 2, kTypeValue), test::KeyStr("a", 1, kTypeValue),
|
||||
test::KeyStr("b", 3, kTypeValue), test::KeyStr("c", 1, kTypeValue)},
|
||||
{"v2", "v1", "v3", "v4"},
|
||||
{test::KeyStr("a", 2, kTypeValue), test::KeyStr("a", 1, kTypeDeletion),
|
||||
test::KeyStr("b", 3, kTypeValue), test::KeyStr("c", 1, kTypeDeletion)},
|
||||
{"v2", "", "v3", ""}, 1 /*last_committed_seq*/,
|
||||
nullptr /*merge_operator*/, compaction_filter.get());
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest, CompactionFilter_Deletion) {
|
||||
std::unique_ptr<CompactionFilter> compaction_filter(
|
||||
new FilterAllKeysCompactionFilter());
|
||||
RunTest(
|
||||
{test::KeyStr("a", 2, kTypeDeletion), test::KeyStr("a", 1, kTypeValue)},
|
||||
{"", "v1"},
|
||||
{test::KeyStr("a", 2, kTypeDeletion),
|
||||
test::KeyStr("a", 1, kTypeDeletion)},
|
||||
{"", ""}, 1 /*last_committed_seq*/, nullptr /*merge_operator*/,
|
||||
compaction_filter.get());
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
CompactionFilter_PartialMerge) {
|
||||
std::shared_ptr<MergeOperator> merge_op =
|
||||
MergeOperators::CreateStringAppendOperator();
|
||||
std::unique_ptr<CompactionFilter> compaction_filter(
|
||||
new FilterAllKeysCompactionFilter());
|
||||
RunTest({test::KeyStr("a", 3, kTypeMerge), test::KeyStr("a", 2, kTypeMerge),
|
||||
test::KeyStr("a", 1, kTypeMerge)},
|
||||
{"v3", "v2", "v1"}, {test::KeyStr("a", 3, kTypeMerge)}, {"v3"},
|
||||
2 /*last_committed_seq*/, merge_op.get(), compaction_filter.get());
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest, CompactionFilter_FullMerge) {
|
||||
std::shared_ptr<MergeOperator> merge_op =
|
||||
MergeOperators::CreateStringAppendOperator();
|
||||
std::unique_ptr<CompactionFilter> compaction_filter(
|
||||
new FilterAllKeysCompactionFilter());
|
||||
RunTest(
|
||||
{test::KeyStr("a", 3, kTypeMerge), test::KeyStr("a", 2, kTypeMerge),
|
||||
test::KeyStr("a", 1, kTypeValue)},
|
||||
{"v3", "v2", "v1"},
|
||||
{test::KeyStr("a", 3, kTypeMerge), test::KeyStr("a", 1, kTypeDeletion)},
|
||||
{"v3", ""}, 2 /*last_committed_seq*/, merge_op.get(),
|
||||
compaction_filter.get());
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -747,8 +747,8 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
compaction_filter, db_options_.info_log.get(),
|
||||
false /* internal key corruption is expected */,
|
||||
existing_snapshots_.empty() ? 0 : existing_snapshots_.back(),
|
||||
compact_->compaction->level(), db_options_.statistics.get(),
|
||||
shutting_down_);
|
||||
snapshot_checker_, compact_->compaction->level(),
|
||||
db_options_.statistics.get(), shutting_down_);
|
||||
|
||||
TEST_SYNC_POINT("CompactionJob::Run():Inprogress");
|
||||
|
||||
|
||||
@@ -1034,7 +1034,7 @@ int main(int argc, char** argv) {
|
||||
#else
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
int main(int /*argc*/, char** /*argv*/) {
|
||||
fprintf(stderr, "SKIPPED, not supported in ROCKSDB_LITE\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -1043,5 +1043,5 @@ int main(int argc, char** argv) {
|
||||
|
||||
#else
|
||||
|
||||
int main(int argc, char** argv) { return 0; }
|
||||
int main(int /*argc*/, char** /*argv*/) { return 0; }
|
||||
#endif // !defined(IOS_CROSS_COMPILE)
|
||||
@@ -946,7 +946,7 @@ int main(int argc, char** argv) {
|
||||
#else
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
int main(int /*argc*/, char** /*argv*/) {
|
||||
fprintf(stderr,
|
||||
"SKIPPED as CompactionJobStats is not supported in ROCKSDB_LITE\n");
|
||||
return 0;
|
||||
|
||||
@@ -175,6 +175,8 @@ TEST_F(CompactionPickerTest, Level1Trigger) {
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, Level1Trigger2) {
|
||||
mutable_cf_options_.target_file_size_base = 10000000000;
|
||||
mutable_cf_options_.RefreshDerivedOptions(ioptions_);
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
Add(1, 66U, "150", "200", 1000000001U);
|
||||
Add(1, 88U, "201", "300", 1000000000U);
|
||||
@@ -191,13 +193,14 @@ TEST_F(CompactionPickerTest, Level1Trigger2) {
|
||||
ASSERT_EQ(66U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(6U, compaction->input(1, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(7U, compaction->input(1, 1)->fd.GetNumber());
|
||||
ASSERT_EQ(uint64_t{1073741824}, compaction->OutputFilePreallocationSize());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, LevelMaxScore) {
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
mutable_cf_options_.target_file_size_base = 10000000;
|
||||
mutable_cf_options_.target_file_size_multiplier = 10;
|
||||
mutable_cf_options_.max_bytes_for_level_base = 10 * 1024 * 1024;
|
||||
mutable_cf_options_.RefreshDerivedOptions(ioptions_);
|
||||
Add(0, 1U, "150", "200", 1000000U);
|
||||
// Level 1 score 1.2
|
||||
Add(1, 66U, "150", "200", 6000000U);
|
||||
@@ -218,6 +221,9 @@ TEST_F(CompactionPickerTest, LevelMaxScore) {
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(7U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(mutable_cf_options_.target_file_size_base +
|
||||
mutable_cf_options_.target_file_size_base / 10,
|
||||
compaction->OutputFilePreallocationSize());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, NeedsCompactionLevel) {
|
||||
@@ -521,9 +527,10 @@ TEST_F(CompactionPickerTest, NeedsCompactionFIFO) {
|
||||
TEST_F(CompactionPickerTest, CompactionPriMinOverlapping1) {
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
ioptions_.compaction_pri = kMinOverlappingRatio;
|
||||
mutable_cf_options_.target_file_size_base = 10000000;
|
||||
mutable_cf_options_.target_file_size_base = 100000000000;
|
||||
mutable_cf_options_.target_file_size_multiplier = 10;
|
||||
mutable_cf_options_.max_bytes_for_level_base = 10 * 1024 * 1024;
|
||||
mutable_cf_options_.RefreshDerivedOptions(ioptions_);
|
||||
|
||||
Add(2, 6U, "150", "179", 50000000U);
|
||||
Add(2, 7U, "180", "220", 50000000U);
|
||||
@@ -543,6 +550,8 @@ TEST_F(CompactionPickerTest, CompactionPriMinOverlapping1) {
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
// Pick file 8 because it overlaps with 0 files on level 3.
|
||||
ASSERT_EQ(8U, compaction->input(0, 0)->fd.GetNumber());
|
||||
// Compaction input size * 1.1
|
||||
ASSERT_GE(uint64_t{55000000}, compaction->OutputFilePreallocationSize());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, CompactionPriMinOverlapping2) {
|
||||
|
||||
@@ -603,9 +603,9 @@ Compaction* UniversalCompactionPicker::PickCompactionToReduceSortedRuns(
|
||||
|
||||
CompactionReason compaction_reason;
|
||||
if (max_number_of_files_to_compact == UINT_MAX) {
|
||||
compaction_reason = CompactionReason::kUniversalSortedRunNum;
|
||||
} else {
|
||||
compaction_reason = CompactionReason::kUniversalSizeRatio;
|
||||
} else {
|
||||
compaction_reason = CompactionReason::kUniversalSortedRunNum;
|
||||
}
|
||||
return new Compaction(
|
||||
vstorage, ioptions_, mutable_cf_options, std::move(inputs), output_level,
|
||||
|
||||
+10
-2
@@ -19,9 +19,17 @@ void CancelAllBackgroundWork(DB* db, bool wait) {
|
||||
}
|
||||
|
||||
Status DeleteFilesInRange(DB* db, ColumnFamilyHandle* column_family,
|
||||
const Slice* begin, const Slice* end) {
|
||||
const Slice* begin, const Slice* end,
|
||||
bool include_end) {
|
||||
RangePtr range(begin, end);
|
||||
return DeleteFilesInRanges(db, column_family, &range, 1, include_end);
|
||||
}
|
||||
|
||||
Status DeleteFilesInRanges(DB* db, ColumnFamilyHandle* column_family,
|
||||
const RangePtr* ranges, size_t n,
|
||||
bool include_end) {
|
||||
return (static_cast_with_check<DBImpl, DB>(db->GetRootDB()))
|
||||
->DeleteFilesInRange(column_family, begin, end);
|
||||
->DeleteFilesInRanges(column_family, ranges, n, include_end);
|
||||
}
|
||||
|
||||
Status VerifySstFileChecksum(const Options& options,
|
||||
|
||||
@@ -510,7 +510,7 @@ int main(int argc, char** argv) {
|
||||
#else
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
int main(int /*argc*/, char** /*argv*/) {
|
||||
fprintf(stderr, "SKIPPED as RepairDB() is not supported in ROCKSDB_LITE\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -333,7 +333,7 @@ int main(int argc, char** argv) {
|
||||
#else
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
int main(int /*argc*/, char** /*argv*/) {
|
||||
fprintf(stderr, "SKIPPED as Cuckoo table is not supported in ROCKSDB_LITE\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
+114
-6
@@ -9,6 +9,7 @@
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/perf_context.h"
|
||||
#include "util/fault_injection_test_env.h"
|
||||
#if !defined(ROCKSDB_LITE)
|
||||
#include "util/sync_point.h"
|
||||
#endif
|
||||
@@ -570,19 +571,19 @@ TEST_F(DBBasicTest, CompactBetweenSnapshots) {
|
||||
|
||||
TEST_F(DBBasicTest, DBOpen_Options) {
|
||||
Options options = CurrentOptions();
|
||||
std::string dbname = test::TmpDir(env_) + "/db_options_test";
|
||||
ASSERT_OK(DestroyDB(dbname, options));
|
||||
Close();
|
||||
Destroy(options);
|
||||
|
||||
// Does not exist, and create_if_missing == false: error
|
||||
DB* db = nullptr;
|
||||
options.create_if_missing = false;
|
||||
Status s = DB::Open(options, dbname, &db);
|
||||
Status s = DB::Open(options, dbname_, &db);
|
||||
ASSERT_TRUE(strstr(s.ToString().c_str(), "does not exist") != nullptr);
|
||||
ASSERT_TRUE(db == nullptr);
|
||||
|
||||
// Does not exist, and create_if_missing == true: OK
|
||||
options.create_if_missing = true;
|
||||
s = DB::Open(options, dbname, &db);
|
||||
s = DB::Open(options, dbname_, &db);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_TRUE(db != nullptr);
|
||||
|
||||
@@ -592,14 +593,14 @@ TEST_F(DBBasicTest, DBOpen_Options) {
|
||||
// Does exist, and error_if_exists == true: error
|
||||
options.create_if_missing = false;
|
||||
options.error_if_exists = true;
|
||||
s = DB::Open(options, dbname, &db);
|
||||
s = DB::Open(options, dbname_, &db);
|
||||
ASSERT_TRUE(strstr(s.ToString().c_str(), "exists") != nullptr);
|
||||
ASSERT_TRUE(db == nullptr);
|
||||
|
||||
// Does exist, and error_if_exists == false: OK
|
||||
options.create_if_missing = true;
|
||||
options.error_if_exists = false;
|
||||
s = DB::Open(options, dbname, &db);
|
||||
s = DB::Open(options, dbname_, &db);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_TRUE(db != nullptr);
|
||||
|
||||
@@ -847,6 +848,113 @@ TEST_F(DBBasicTest, MmapAndBufferOptions) {
|
||||
}
|
||||
#endif
|
||||
|
||||
class TestEnv : public EnvWrapper {
|
||||
public:
|
||||
explicit TestEnv() : EnvWrapper(Env::Default()),
|
||||
close_count(0) { }
|
||||
|
||||
class TestLogger : public Logger {
|
||||
public:
|
||||
using Logger::Logv;
|
||||
TestLogger(TestEnv *env_ptr) : Logger() { env = env_ptr; }
|
||||
~TestLogger() {
|
||||
if (!closed_) {
|
||||
CloseHelper();
|
||||
}
|
||||
}
|
||||
virtual void Logv(const char *format, va_list ap) override { };
|
||||
protected:
|
||||
virtual Status CloseImpl() override {
|
||||
return CloseHelper();
|
||||
}
|
||||
private:
|
||||
Status CloseHelper() {
|
||||
env->CloseCountInc();;
|
||||
return Status::IOError();
|
||||
}
|
||||
TestEnv *env;
|
||||
};
|
||||
|
||||
void CloseCountInc() { close_count++; }
|
||||
|
||||
int GetCloseCount() { return close_count; }
|
||||
|
||||
virtual Status NewLogger(const std::string& fname,
|
||||
shared_ptr<Logger>* result) {
|
||||
result->reset(new TestLogger(this));
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
private:
|
||||
int close_count;
|
||||
};
|
||||
|
||||
TEST_F(DBBasicTest, DBClose) {
|
||||
Options options = GetDefaultOptions();
|
||||
std::string dbname = test::TmpDir(env_) + "/db_close_test";
|
||||
ASSERT_OK(DestroyDB(dbname, options));
|
||||
|
||||
DB* db = nullptr;
|
||||
TestEnv *env = new TestEnv();
|
||||
options.create_if_missing = true;
|
||||
options.env = env;
|
||||
Status s = DB::Open(options, dbname, &db);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_TRUE(db != nullptr);
|
||||
|
||||
s = db->Close();
|
||||
ASSERT_EQ(env->GetCloseCount(), 1);
|
||||
ASSERT_EQ(s, Status::IOError());
|
||||
|
||||
delete db;
|
||||
ASSERT_EQ(env->GetCloseCount(), 1);
|
||||
|
||||
// Do not call DB::Close() and ensure our logger Close() still gets called
|
||||
s = DB::Open(options, dbname, &db);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_TRUE(db != nullptr);
|
||||
delete db;
|
||||
ASSERT_EQ(env->GetCloseCount(), 2);
|
||||
|
||||
// Provide our own logger and ensure DB::Close() does not close it
|
||||
options.info_log.reset(new TestEnv::TestLogger(env));
|
||||
options.create_if_missing = false;
|
||||
s = DB::Open(options, dbname, &db);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_TRUE(db != nullptr);
|
||||
|
||||
s = db->Close();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
delete db;
|
||||
ASSERT_EQ(env->GetCloseCount(), 2);
|
||||
options.info_log.reset();
|
||||
ASSERT_EQ(env->GetCloseCount(), 3);
|
||||
|
||||
delete options.env;
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, DBCloseFlushError) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_injection_env(
|
||||
new FaultInjectionTestEnv(Env::Default()));
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.manual_wal_flush = true;
|
||||
options.write_buffer_size=100;
|
||||
options.env = fault_injection_env.get();
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("key1", "value1"));
|
||||
ASSERT_OK(Put("key2", "value2"));
|
||||
ASSERT_OK(dbfull()->TEST_SwitchMemtable());
|
||||
ASSERT_OK(Put("key3", "value3"));
|
||||
fault_injection_env->SetFilesystemActive(false);
|
||||
Status s = dbfull()->Close();
|
||||
fault_injection_env->SetFilesystemActive(true);
|
||||
ASSERT_NE(s, Status::OK());
|
||||
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -111,6 +111,31 @@ class DBBlockCacheTest : public DBTestBase {
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(DBBlockCacheTest, IteratorBlockCacheUsage) {
|
||||
ReadOptions read_options;
|
||||
read_options.fill_cache = false;
|
||||
auto table_options = GetTableOptions();
|
||||
auto options = GetOptions(table_options);
|
||||
InitTable(options);
|
||||
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(0, 0, false);
|
||||
table_options.block_cache = cache;
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
Reopen(options);
|
||||
RecordCacheCounters(options);
|
||||
|
||||
std::vector<std::unique_ptr<Iterator>> iterators(kNumBlocks - 1);
|
||||
Iterator* iter = nullptr;
|
||||
|
||||
ASSERT_EQ(0, cache->GetUsage());
|
||||
iter = db_->NewIterator(read_options);
|
||||
iter->Seek(ToString(0));
|
||||
ASSERT_LT(0, cache->GetUsage());
|
||||
delete iter;
|
||||
iter = nullptr;
|
||||
ASSERT_EQ(0, cache->GetUsage());
|
||||
}
|
||||
|
||||
TEST_F(DBBlockCacheTest, TestWithoutCompressedBlockCache) {
|
||||
ReadOptions read_options;
|
||||
auto table_options = GetTableOptions();
|
||||
|
||||
@@ -1517,6 +1517,122 @@ TEST_F(DBCompactionTest, DeleteFileRange) {
|
||||
ASSERT_GT(old_num_files, new_num_files);
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, DeleteFilesInRanges) {
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 10 * 1024 * 1024;
|
||||
options.max_bytes_for_level_multiplier = 2;
|
||||
options.num_levels = 4;
|
||||
options.max_background_compactions = 3;
|
||||
options.disable_auto_compactions = true;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
int32_t value_size = 10 * 1024; // 10 KB
|
||||
|
||||
Random rnd(301);
|
||||
std::map<int32_t, std::string> values;
|
||||
|
||||
// file [0 => 100), [100 => 200), ... [900, 1000)
|
||||
for (auto i = 0; i < 10; i++) {
|
||||
for (auto j = 0; j < 100; j++) {
|
||||
auto k = i * 100 + j;
|
||||
values[k] = RandomString(&rnd, value_size);
|
||||
ASSERT_OK(Put(Key(k), values[k]));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_EQ("10", FilesPerLevel(0));
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 2;
|
||||
ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr));
|
||||
ASSERT_EQ("0,0,10", FilesPerLevel(0));
|
||||
|
||||
// file [0 => 100), [200 => 300), ... [800, 900)
|
||||
for (auto i = 0; i < 10; i+=2) {
|
||||
for (auto j = 0; j < 100; j++) {
|
||||
auto k = i * 100 + j;
|
||||
ASSERT_OK(Put(Key(k), values[k]));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_EQ("5,0,10", FilesPerLevel(0));
|
||||
ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr));
|
||||
ASSERT_EQ("0,5,10", FilesPerLevel(0));
|
||||
|
||||
// Delete files in range [0, 299] (inclusive)
|
||||
{
|
||||
auto begin_str1 = Key(0), end_str1 = Key(100);
|
||||
auto begin_str2 = Key(100), end_str2 = Key(200);
|
||||
auto begin_str3 = Key(200), end_str3 = Key(299);
|
||||
Slice begin1(begin_str1), end1(end_str1);
|
||||
Slice begin2(begin_str2), end2(end_str2);
|
||||
Slice begin3(begin_str3), end3(end_str3);
|
||||
std::vector<RangePtr> ranges;
|
||||
ranges.push_back(RangePtr(&begin1, &end1));
|
||||
ranges.push_back(RangePtr(&begin2, &end2));
|
||||
ranges.push_back(RangePtr(&begin3, &end3));
|
||||
ASSERT_OK(DeleteFilesInRanges(db_, db_->DefaultColumnFamily(),
|
||||
ranges.data(), ranges.size()));
|
||||
ASSERT_EQ("0,3,7", FilesPerLevel(0));
|
||||
|
||||
// Keys [0, 300) should not exist.
|
||||
for (auto i = 0; i < 300; i++) {
|
||||
ReadOptions ropts;
|
||||
std::string result;
|
||||
auto s = db_->Get(ropts, Key(i), &result);
|
||||
ASSERT_TRUE(s.IsNotFound());
|
||||
}
|
||||
for (auto i = 300; i < 1000; i++) {
|
||||
ASSERT_EQ(Get(Key(i)), values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete files in range [600, 999) (exclusive)
|
||||
{
|
||||
auto begin_str1 = Key(600), end_str1 = Key(800);
|
||||
auto begin_str2 = Key(700), end_str2 = Key(900);
|
||||
auto begin_str3 = Key(800), end_str3 = Key(999);
|
||||
Slice begin1(begin_str1), end1(end_str1);
|
||||
Slice begin2(begin_str2), end2(end_str2);
|
||||
Slice begin3(begin_str3), end3(end_str3);
|
||||
std::vector<RangePtr> ranges;
|
||||
ranges.push_back(RangePtr(&begin1, &end1));
|
||||
ranges.push_back(RangePtr(&begin2, &end2));
|
||||
ranges.push_back(RangePtr(&begin3, &end3));
|
||||
ASSERT_OK(DeleteFilesInRanges(db_, db_->DefaultColumnFamily(),
|
||||
ranges.data(), ranges.size(), false));
|
||||
ASSERT_EQ("0,1,4", FilesPerLevel(0));
|
||||
|
||||
// Keys [600, 900) should not exist.
|
||||
for (auto i = 600; i < 900; i++) {
|
||||
ReadOptions ropts;
|
||||
std::string result;
|
||||
auto s = db_->Get(ropts, Key(i), &result);
|
||||
ASSERT_TRUE(s.IsNotFound());
|
||||
}
|
||||
for (auto i = 300; i < 600; i++) {
|
||||
ASSERT_EQ(Get(Key(i)), values[i]);
|
||||
}
|
||||
for (auto i = 900; i < 1000; i++) {
|
||||
ASSERT_EQ(Get(Key(i)), values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete all files.
|
||||
{
|
||||
RangePtr range;
|
||||
ASSERT_OK(DeleteFilesInRanges(db_, db_->DefaultColumnFamily(), &range, 1));
|
||||
ASSERT_EQ("", FilesPerLevel(0));
|
||||
|
||||
for (auto i = 0; i < 1000; i++) {
|
||||
ReadOptions ropts;
|
||||
std::string result;
|
||||
auto s = db_->Get(ropts, Key(i), &result);
|
||||
ASSERT_TRUE(s.IsNotFound());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, DeleteFileRangeFileEndpointsOverlapBug) {
|
||||
// regression test for #2833: groups of files whose user-keys overlap at the
|
||||
// endpoints could be split by `DeleteFilesInRange`. This caused old data to
|
||||
@@ -2989,6 +3105,276 @@ TEST_F(DBCompactionTest, CompactBottomLevelFilesWithDeletions) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, CompactRangeDelayedByL0FileCount) {
|
||||
// Verify that, when `CompactRangeOptions::allow_write_stall == false`, manual
|
||||
// compaction only triggers flush after it's sure stall won't be triggered for
|
||||
// L0 file count going too high.
|
||||
const int kNumL0FilesTrigger = 4;
|
||||
const int kNumL0FilesLimit = 8;
|
||||
// i == 0: verifies normal case where stall is avoided by delay
|
||||
// i == 1: verifies no delay in edge case where stall trigger is same as
|
||||
// compaction trigger, so stall can't be avoided
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
Options options = CurrentOptions();
|
||||
options.level0_slowdown_writes_trigger = kNumL0FilesLimit;
|
||||
if (i == 0) {
|
||||
options.level0_file_num_compaction_trigger = kNumL0FilesTrigger;
|
||||
} else {
|
||||
options.level0_file_num_compaction_trigger = kNumL0FilesLimit;
|
||||
}
|
||||
Reopen(options);
|
||||
|
||||
if (i == 0) {
|
||||
// ensure the auto compaction doesn't finish until manual compaction has
|
||||
// had a chance to be delayed.
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::CompactRange:StallWait", "CompactionJob::Run():End"}});
|
||||
} else {
|
||||
// ensure the auto-compaction doesn't finish until manual compaction has
|
||||
// continued without delay.
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::CompactRange:StallWaitDone", "CompactionJob::Run():End"}});
|
||||
}
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Random rnd(301);
|
||||
for (int j = 0; j < kNumL0FilesLimit - 1; ++j) {
|
||||
for (int k = 0; k < 2; ++k) {
|
||||
ASSERT_OK(Put(Key(k), RandomString(&rnd, 1024)));
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
auto manual_compaction_thread = port::Thread([this]() {
|
||||
CompactRangeOptions cro;
|
||||
cro.allow_write_stall = false;
|
||||
db_->CompactRange(cro, nullptr, nullptr);
|
||||
});
|
||||
|
||||
manual_compaction_thread.join();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_GT(NumTableFilesAtLevel(1), 0);
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, CompactRangeDelayedByImmMemTableCount) {
|
||||
// Verify that, when `CompactRangeOptions::allow_write_stall == false`, manual
|
||||
// compaction only triggers flush after it's sure stall won't be triggered for
|
||||
// immutable memtable count going too high.
|
||||
const int kNumImmMemTableLimit = 8;
|
||||
// i == 0: verifies normal case where stall is avoided by delay
|
||||
// i == 1: verifies no delay in edge case where stall trigger is same as flush
|
||||
// trigger, so stall can't be avoided
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
// the delay limit is one less than the stop limit. This test focuses on
|
||||
// avoiding delay limit, but this option sets stop limit, so add one.
|
||||
options.max_write_buffer_number = kNumImmMemTableLimit + 1;
|
||||
if (i == 1) {
|
||||
options.min_write_buffer_number_to_merge = kNumImmMemTableLimit;
|
||||
}
|
||||
Reopen(options);
|
||||
|
||||
if (i == 0) {
|
||||
// ensure the flush doesn't finish until manual compaction has had a
|
||||
// chance to be delayed.
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::CompactRange:StallWait", "FlushJob::WriteLevel0Table"}});
|
||||
} else {
|
||||
// ensure the flush doesn't finish until manual compaction has continued
|
||||
// without delay.
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::CompactRange:StallWaitDone",
|
||||
"FlushJob::WriteLevel0Table"}});
|
||||
}
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Random rnd(301);
|
||||
for (int j = 0; j < kNumImmMemTableLimit - 1; ++j) {
|
||||
ASSERT_OK(Put(Key(0), RandomString(&rnd, 1024)));
|
||||
FlushOptions flush_opts;
|
||||
flush_opts.wait = false;
|
||||
dbfull()->Flush(flush_opts);
|
||||
}
|
||||
|
||||
auto manual_compaction_thread = port::Thread([this]() {
|
||||
CompactRangeOptions cro;
|
||||
cro.allow_write_stall = false;
|
||||
db_->CompactRange(cro, nullptr, nullptr);
|
||||
});
|
||||
|
||||
manual_compaction_thread.join();
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_GT(NumTableFilesAtLevel(1), 0);
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, CompactRangeShutdownWhileDelayed) {
|
||||
// Verify that, when `CompactRangeOptions::allow_write_stall == false`, delay
|
||||
// does not hang if CF is dropped or DB is closed
|
||||
const int kNumL0FilesTrigger = 4;
|
||||
const int kNumL0FilesLimit = 8;
|
||||
Options options = CurrentOptions();
|
||||
options.level0_file_num_compaction_trigger = kNumL0FilesTrigger;
|
||||
options.level0_slowdown_writes_trigger = kNumL0FilesLimit;
|
||||
// i == 0: DB::DropColumnFamily() on CompactRange's target CF unblocks it
|
||||
// i == 1: DB::CancelAllBackgroundWork() unblocks CompactRange. This is to
|
||||
// simulate what happens during Close as we can't call Close (it
|
||||
// blocks on the auto-compaction, making a cycle).
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
CreateAndReopenWithCF({"one"}, options);
|
||||
// The calls to close CF/DB wait until the manual compaction stalls.
|
||||
// The auto-compaction waits until the manual compaction finishes to ensure
|
||||
// the signal comes from closing CF/DB, not from compaction making progress.
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::CompactRange:StallWait",
|
||||
"DBCompactionTest::CompactRangeShutdownWhileDelayed:PreShutdown"},
|
||||
{"DBCompactionTest::CompactRangeShutdownWhileDelayed:PostManual",
|
||||
"CompactionJob::Run():End"}});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Random rnd(301);
|
||||
for (int j = 0; j < kNumL0FilesLimit - 1; ++j) {
|
||||
for (int k = 0; k < 2; ++k) {
|
||||
ASSERT_OK(Put(1, Key(k), RandomString(&rnd, 1024)));
|
||||
}
|
||||
Flush(1);
|
||||
}
|
||||
auto manual_compaction_thread = port::Thread([this]() {
|
||||
CompactRangeOptions cro;
|
||||
cro.allow_write_stall = false;
|
||||
ASSERT_TRUE(db_->CompactRange(cro, handles_[1], nullptr, nullptr)
|
||||
.IsShutdownInProgress());
|
||||
});
|
||||
|
||||
TEST_SYNC_POINT(
|
||||
"DBCompactionTest::CompactRangeShutdownWhileDelayed:PreShutdown");
|
||||
if (i == 0) {
|
||||
ASSERT_OK(db_->DropColumnFamily(handles_[1]));
|
||||
} else {
|
||||
dbfull()->CancelAllBackgroundWork(false /* wait */);
|
||||
}
|
||||
manual_compaction_thread.join();
|
||||
TEST_SYNC_POINT(
|
||||
"DBCompactionTest::CompactRangeShutdownWhileDelayed:PostManual");
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, CompactRangeSkipFlushAfterDelay) {
|
||||
// Verify that, when `CompactRangeOptions::allow_write_stall == false`,
|
||||
// CompactRange skips its flush if the delay is long enough that the memtables
|
||||
// existing at the beginning of the call have already been flushed.
|
||||
const int kNumL0FilesTrigger = 4;
|
||||
const int kNumL0FilesLimit = 8;
|
||||
Options options = CurrentOptions();
|
||||
options.level0_slowdown_writes_trigger = kNumL0FilesLimit;
|
||||
options.level0_file_num_compaction_trigger = kNumL0FilesTrigger;
|
||||
Reopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
// The manual flush includes the memtable that was active when CompactRange
|
||||
// began. So it unblocks CompactRange and precludes its flush. Throughout the
|
||||
// test, stall conditions are upheld via high L0 file count.
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::CompactRange:StallWait",
|
||||
"DBCompactionTest::CompactRangeSkipFlushAfterDelay:PreFlush"},
|
||||
{"DBCompactionTest::CompactRangeSkipFlushAfterDelay:PostFlush",
|
||||
"DBImpl::CompactRange:StallWaitDone"},
|
||||
{"DBImpl::CompactRange:StallWaitDone", "CompactionJob::Run():End"}});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
for (int i = 0; i < kNumL0FilesLimit - 1; ++i) {
|
||||
for (int j = 0; j < 2; ++j) {
|
||||
ASSERT_OK(Put(Key(j), RandomString(&rnd, 1024)));
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
auto manual_compaction_thread = port::Thread([this]() {
|
||||
CompactRangeOptions cro;
|
||||
cro.allow_write_stall = false;
|
||||
db_->CompactRange(cro, nullptr, nullptr);
|
||||
});
|
||||
|
||||
TEST_SYNC_POINT("DBCompactionTest::CompactRangeSkipFlushAfterDelay:PreFlush");
|
||||
Put(ToString(0), RandomString(&rnd, 1024));
|
||||
Flush();
|
||||
Put(ToString(0), RandomString(&rnd, 1024));
|
||||
TEST_SYNC_POINT("DBCompactionTest::CompactRangeSkipFlushAfterDelay:PostFlush");
|
||||
manual_compaction_thread.join();
|
||||
|
||||
// If CompactRange's flush was skipped, the final Put above will still be
|
||||
// in the active memtable.
|
||||
std::string num_keys_in_memtable;
|
||||
db_->GetProperty(DB::Properties::kNumEntriesActiveMemTable, &num_keys_in_memtable);
|
||||
ASSERT_EQ(ToString(1), num_keys_in_memtable);
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, CompactRangeFlushOverlappingMemtable) {
|
||||
// Verify memtable only gets flushed if it contains data overlapping the range
|
||||
// provided to `CompactRange`. Tests all kinds of overlap/non-overlap.
|
||||
const int kNumEndpointKeys = 5;
|
||||
std::string keys[kNumEndpointKeys] = {"a", "b", "c", "d", "e"};
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
Reopen(options);
|
||||
|
||||
// One extra iteration for nullptr, which means left side of interval is
|
||||
// unbounded.
|
||||
for (int i = 0; i <= kNumEndpointKeys; ++i) {
|
||||
Slice begin;
|
||||
Slice* begin_ptr;
|
||||
if (i == 0) {
|
||||
begin_ptr = nullptr;
|
||||
} else {
|
||||
begin = keys[i - 1];
|
||||
begin_ptr = &begin;
|
||||
}
|
||||
// Start at `i` so right endpoint comes after left endpoint. One extra
|
||||
// iteration for nullptr, which means right side of interval is unbounded.
|
||||
for (int j = std::max(0, i - 1); j <= kNumEndpointKeys; ++j) {
|
||||
Slice end;
|
||||
Slice* end_ptr;
|
||||
if (j == kNumEndpointKeys) {
|
||||
end_ptr = nullptr;
|
||||
} else {
|
||||
end = keys[j];
|
||||
end_ptr = &end;
|
||||
}
|
||||
ASSERT_OK(Put("b", "val"));
|
||||
ASSERT_OK(Put("d", "val"));
|
||||
CompactRangeOptions compact_range_opts;
|
||||
ASSERT_OK(db_->CompactRange(compact_range_opts, begin_ptr, end_ptr));
|
||||
|
||||
uint64_t get_prop_tmp, num_memtable_entries = 0;
|
||||
ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kNumEntriesImmMemTables,
|
||||
&get_prop_tmp));
|
||||
num_memtable_entries += get_prop_tmp;
|
||||
ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kNumEntriesActiveMemTable,
|
||||
&get_prop_tmp));
|
||||
num_memtable_entries += get_prop_tmp;
|
||||
if (begin_ptr == nullptr || end_ptr == nullptr ||
|
||||
(i <= 4 && j >= 1 && (begin != "c" || end != "c"))) {
|
||||
// In this case `CompactRange`'s range overlapped in some way with the
|
||||
// memtable's range, so flush should've happened. Then "b" and "d" won't
|
||||
// be in the memtable.
|
||||
ASSERT_EQ(0, num_memtable_entries);
|
||||
} else {
|
||||
ASSERT_EQ(2, num_memtable_entries);
|
||||
// flush anyways to prepare for next iteration
|
||||
db_->Flush(FlushOptions());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBCompactionTestWithParam, DBCompactionTestWithParam,
|
||||
::testing::Values(std::make_tuple(1, true),
|
||||
std::make_tuple(1, false),
|
||||
@@ -3095,6 +3481,8 @@ int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
#else
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
@@ -501,6 +501,8 @@ int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
#else
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
+14
-1
@@ -57,6 +57,7 @@ Status DBImpl::EnableFileDeletions(bool force) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "File Deletions Enabled");
|
||||
should_purge_files = true;
|
||||
FindObsoleteFiles(&job_context, true);
|
||||
bg_cv_.SignalAll();
|
||||
} else {
|
||||
ROCKS_LOG_WARN(
|
||||
immutable_db_options_.info_log,
|
||||
@@ -92,7 +93,7 @@ Status DBImpl::GetLiveFiles(std::vector<std::string>& ret,
|
||||
}
|
||||
cfd->Ref();
|
||||
mutex_.Unlock();
|
||||
status = FlushMemTable(cfd, FlushOptions());
|
||||
status = FlushMemTable(cfd, FlushOptions(), FlushReason::kGetLiveFiles);
|
||||
TEST_SYNC_POINT("DBImpl::GetLiveFiles:1");
|
||||
TEST_SYNC_POINT("DBImpl::GetLiveFiles:2");
|
||||
mutex_.Lock();
|
||||
@@ -141,6 +142,18 @@ Status DBImpl::GetLiveFiles(std::vector<std::string>& ret,
|
||||
}
|
||||
|
||||
Status DBImpl::GetSortedWalFiles(VectorLogPtr& files) {
|
||||
{
|
||||
// If caller disabled deletions, this function should return files that are
|
||||
// guaranteed not to be deleted until deletions are re-enabled. We need to
|
||||
// wait for pending purges to finish since WalManager doesn't know which
|
||||
// files are going to be purged. Additional purges won't be scheduled as
|
||||
// long as deletions are disabled (so the below loop must terminate).
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
while (disable_delete_obsolete_files_ > 0 &&
|
||||
pending_purge_obsolete_files_ > 0) {
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
}
|
||||
return wal_manager_.GetSortedWalFiles(files);
|
||||
}
|
||||
|
||||
|
||||
@@ -126,6 +126,41 @@ TEST_F(DBFlushTest, FlushInLowPriThreadPool) {
|
||||
ASSERT_EQ(1, num_compactions);
|
||||
}
|
||||
|
||||
TEST_F(DBFlushTest, ManualFlushWithMinWriteBufferNumberToMerge) {
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 100;
|
||||
options.max_write_buffer_number = 4;
|
||||
options.min_write_buffer_number_to_merge = 3;
|
||||
Reopen(options);
|
||||
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::BGWorkFlush",
|
||||
"DBFlushTest::ManualFlushWithMinWriteBufferNumberToMerge:1"},
|
||||
{"DBFlushTest::ManualFlushWithMinWriteBufferNumberToMerge:2",
|
||||
"FlushJob::WriteLevel0Table"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(Put("key1", "value1"));
|
||||
|
||||
port::Thread t([&]() {
|
||||
// The call wait for flush to finish, i.e. with flush_options.wait = true.
|
||||
ASSERT_OK(Flush());
|
||||
});
|
||||
|
||||
// Wait for flush start.
|
||||
TEST_SYNC_POINT("DBFlushTest::ManualFlushWithMinWriteBufferNumberToMerge:1");
|
||||
// Insert a second memtable before the manual flush finish.
|
||||
// At the end of the manual flush job, it will check if further flush
|
||||
// is needed, but it will not trigger flush of the second memtable because
|
||||
// min_write_buffer_number_to_merge is not reached.
|
||||
ASSERT_OK(Put("key2", "value2"));
|
||||
ASSERT_OK(dbfull()->TEST_SwitchMemtable());
|
||||
TEST_SYNC_POINT("DBFlushTest::ManualFlushWithMinWriteBufferNumberToMerge:2");
|
||||
|
||||
// Manual flush should return, without waiting for flush indefinitely.
|
||||
t.join();
|
||||
}
|
||||
|
||||
TEST_P(DBFlushDirectIOTest, DirectIO) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
@@ -150,6 +185,26 @@ TEST_P(DBFlushDirectIOTest, DirectIO) {
|
||||
delete options.env;
|
||||
}
|
||||
|
||||
TEST_F(DBFlushTest, FlushError) {
|
||||
Options options;
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_injection_env(
|
||||
new FaultInjectionTestEnv(env_));
|
||||
options.write_buffer_size = 100;
|
||||
options.max_write_buffer_number = 4;
|
||||
options.min_write_buffer_number_to_merge = 3;
|
||||
options.disable_auto_compactions = true;
|
||||
options.env = fault_injection_env.get();
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(Put("key1", "value1"));
|
||||
ASSERT_OK(Put("key2", "value2"));
|
||||
fault_injection_env->SetFilesystemActive(false);
|
||||
Status s = dbfull()->TEST_SwitchMemtable();
|
||||
fault_injection_env->SetFilesystemActive(true);
|
||||
Destroy(options);
|
||||
ASSERT_NE(s, Status::OK());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBFlushDirectIOTest, DBFlushDirectIOTest,
|
||||
testing::Bool());
|
||||
|
||||
|
||||
+108
-51
@@ -123,13 +123,13 @@ CompressionType GetCompressionFlush(
|
||||
namespace {
|
||||
void DumpSupportInfo(Logger* logger) {
|
||||
ROCKS_LOG_HEADER(logger, "Compression algorithms supported:");
|
||||
ROCKS_LOG_HEADER(logger, "\tSnappy supported: %d", Snappy_Supported());
|
||||
ROCKS_LOG_HEADER(logger, "\tZlib supported: %d", Zlib_Supported());
|
||||
ROCKS_LOG_HEADER(logger, "\tBzip supported: %d", BZip2_Supported());
|
||||
ROCKS_LOG_HEADER(logger, "\tLZ4 supported: %d", LZ4_Supported());
|
||||
ROCKS_LOG_HEADER(logger, "\tZSTDNotFinal supported: %d",
|
||||
ZSTDNotFinal_Supported());
|
||||
ROCKS_LOG_HEADER(logger, "\tZSTD supported: %d", ZSTD_Supported());
|
||||
for (auto& compression : OptionsHelper::compression_type_string_map) {
|
||||
if (compression.second != kNoCompression &&
|
||||
compression.second != kDisableCompressionOption) {
|
||||
ROCKS_LOG_HEADER(logger, "\t%s supported: %d", compression.first.c_str(),
|
||||
CompressionTypeSupported(compression.second));
|
||||
}
|
||||
}
|
||||
ROCKS_LOG_HEADER(logger, "Fast CRC32 supported: %s",
|
||||
crc32c::IsFastCrc32Supported().c_str());
|
||||
}
|
||||
@@ -141,6 +141,7 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
const bool seq_per_batch)
|
||||
: env_(options.env),
|
||||
dbname_(dbname),
|
||||
own_info_log_(options.info_log == nullptr),
|
||||
initial_db_options_(SanitizeOptions(dbname, options)),
|
||||
immutable_db_options_(initial_db_options_),
|
||||
mutable_db_options_(initial_db_options_),
|
||||
@@ -177,6 +178,7 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
num_running_flushes_(0),
|
||||
bg_purge_scheduled_(0),
|
||||
disable_delete_obsolete_files_(0),
|
||||
pending_purge_obsolete_files_(0),
|
||||
delete_obsolete_files_last_run_(env_->NowMicros()),
|
||||
last_stats_dump_time_microsec_(0),
|
||||
next_job_id_(1),
|
||||
@@ -212,7 +214,8 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
// requires a custom gc for compaction, we use that to set use_custom_gc_
|
||||
// as well.
|
||||
use_custom_gc_(seq_per_batch),
|
||||
preserve_deletes_(options.preserve_deletes) {
|
||||
preserve_deletes_(options.preserve_deletes),
|
||||
closed_(false) {
|
||||
env_->GetAbsolutePath(dbname, &db_absolute_path_);
|
||||
|
||||
// Reserve ten files or so for other uses and give the rest to TableCache.
|
||||
@@ -255,7 +258,7 @@ void DBImpl::CancelAllBackgroundWork(bool wait) {
|
||||
if (!cfd->IsDropped() && cfd->initialized() && !cfd->mem()->IsEmpty()) {
|
||||
cfd->Ref();
|
||||
mutex_.Unlock();
|
||||
FlushMemTable(cfd, FlushOptions());
|
||||
FlushMemTable(cfd, FlushOptions(), FlushReason::kShutDown);
|
||||
mutex_.Lock();
|
||||
cfd->Unref();
|
||||
}
|
||||
@@ -275,7 +278,7 @@ void DBImpl::CancelAllBackgroundWork(bool wait) {
|
||||
}
|
||||
}
|
||||
|
||||
DBImpl::~DBImpl() {
|
||||
Status DBImpl::CloseHelper() {
|
||||
// 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))
|
||||
@@ -284,6 +287,7 @@ DBImpl::~DBImpl() {
|
||||
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;
|
||||
mutex_.Lock();
|
||||
bg_bottom_compaction_scheduled_ -= bottom_compactions_unscheduled;
|
||||
bg_compaction_scheduled_ -= compactions_unscheduled;
|
||||
@@ -291,7 +295,8 @@ DBImpl::~DBImpl() {
|
||||
|
||||
// Wait for background work to finish
|
||||
while (bg_bottom_compaction_scheduled_ || bg_compaction_scheduled_ ||
|
||||
bg_flush_scheduled_ || bg_purge_scheduled_) {
|
||||
bg_flush_scheduled_ || bg_purge_scheduled_ ||
|
||||
pending_purge_obsolete_files_) {
|
||||
TEST_SYNC_POINT("DBImpl::~DBImpl:WaitJob");
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
@@ -345,7 +350,18 @@ DBImpl::~DBImpl() {
|
||||
delete l;
|
||||
}
|
||||
for (auto& log : logs_) {
|
||||
log.ClearWriter();
|
||||
uint64_t log_number = log.writer->get_log_number();
|
||||
Status s = log.ClearWriter();
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"Unable to Sync WAL file %s with error -- %s",
|
||||
LogFileName(immutable_db_options_.wal_dir, log_number).c_str(),
|
||||
s.ToString().c_str());
|
||||
// Retain the first error
|
||||
if (ret.ok()) {
|
||||
ret = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
logs_.clear();
|
||||
|
||||
@@ -378,6 +394,25 @@ DBImpl::~DBImpl() {
|
||||
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "Shutdown complete");
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
|
||||
if (immutable_db_options_.info_log && own_info_log_) {
|
||||
Status s = immutable_db_options_.info_log->Close();
|
||||
if (ret.ok()) {
|
||||
ret = s;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
Status DBImpl::CloseImpl() {
|
||||
return CloseHelper();
|
||||
}
|
||||
|
||||
DBImpl::~DBImpl() {
|
||||
if (!closed_) {
|
||||
closed_ = true;
|
||||
CloseHelper();
|
||||
}
|
||||
}
|
||||
|
||||
void DBImpl::MaybeIgnoreError(Status* s) const {
|
||||
@@ -523,6 +558,7 @@ Status DBImpl::SetOptions(ColumnFamilyHandle* column_family,
|
||||
|
||||
persist_options_status = WriteOptionsFile(
|
||||
false /*need_mutex_lock*/, true /*need_enter_write_thread*/);
|
||||
bg_cv_.SignalAll();
|
||||
}
|
||||
}
|
||||
sv_context.Clean();
|
||||
@@ -1407,6 +1443,7 @@ Status DBImpl::DropColumnFamilyImpl(ColumnFamilyHandle* column_family) {
|
||||
}
|
||||
is_snapshot_supported_ = new_is_snapshot_supported;
|
||||
}
|
||||
bg_cv_.SignalAll();
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
@@ -2047,8 +2084,7 @@ Status DBImpl::DeleteFile(std::string name) {
|
||||
name.c_str());
|
||||
return Status::NotSupported("Delete only supported for archived logs");
|
||||
}
|
||||
status =
|
||||
env_->DeleteFile(immutable_db_options_.wal_dir + "/" + name.c_str());
|
||||
status = wal_manager_.DeleteFile(name, number);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
|
||||
"DeleteFile %s failed -- %s.\n", name.c_str(),
|
||||
@@ -2127,52 +2163,63 @@ Status DBImpl::DeleteFile(std::string name) {
|
||||
return status;
|
||||
}
|
||||
|
||||
Status DBImpl::DeleteFilesInRange(ColumnFamilyHandle* column_family,
|
||||
const Slice* begin, const Slice* end) {
|
||||
Status DBImpl::DeleteFilesInRanges(ColumnFamilyHandle* column_family,
|
||||
const RangePtr* ranges, size_t n,
|
||||
bool include_end) {
|
||||
Status status;
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
ColumnFamilyData* cfd = cfh->cfd();
|
||||
VersionEdit edit;
|
||||
std::vector<FileMetaData*> deleted_files;
|
||||
std::set<FileMetaData*> deleted_files;
|
||||
JobContext job_context(next_job_id_.fetch_add(1), true);
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
Version* input_version = cfd->current();
|
||||
|
||||
auto* vstorage = input_version->storage_info();
|
||||
for (int i = 1; i < cfd->NumberLevels(); i++) {
|
||||
if (vstorage->LevelFiles(i).empty() ||
|
||||
!vstorage->OverlapInLevel(i, begin, end)) {
|
||||
continue;
|
||||
}
|
||||
std::vector<FileMetaData*> level_files;
|
||||
InternalKey begin_storage, end_storage, *begin_key, *end_key;
|
||||
if (begin == nullptr) {
|
||||
begin_key = nullptr;
|
||||
} else {
|
||||
begin_storage.SetMinPossibleForUserKey(*begin);
|
||||
begin_key = &begin_storage;
|
||||
}
|
||||
if (end == nullptr) {
|
||||
end_key = nullptr;
|
||||
} else {
|
||||
end_storage.SetMaxPossibleForUserKey(*end);
|
||||
end_key = &end_storage;
|
||||
}
|
||||
|
||||
vstorage->GetCleanInputsWithinInterval(i, begin_key, end_key,
|
||||
&level_files, -1 /* hint_index */,
|
||||
nullptr /* file_index */);
|
||||
FileMetaData* level_file;
|
||||
for (uint32_t j = 0; j < level_files.size(); j++) {
|
||||
level_file = level_files[j];
|
||||
if (level_file->being_compacted) {
|
||||
for (size_t r = 0; r < n; r++) {
|
||||
auto begin = ranges[r].start, end = ranges[r].limit;
|
||||
for (int i = 1; i < cfd->NumberLevels(); i++) {
|
||||
if (vstorage->LevelFiles(i).empty() ||
|
||||
!vstorage->OverlapInLevel(i, begin, end)) {
|
||||
continue;
|
||||
}
|
||||
edit.SetColumnFamily(cfd->GetID());
|
||||
edit.DeleteFile(i, level_file->fd.GetNumber());
|
||||
deleted_files.push_back(level_file);
|
||||
level_file->being_compacted = true;
|
||||
std::vector<FileMetaData*> level_files;
|
||||
InternalKey begin_storage, end_storage, *begin_key, *end_key;
|
||||
if (begin == nullptr) {
|
||||
begin_key = nullptr;
|
||||
} else {
|
||||
begin_storage.SetMinPossibleForUserKey(*begin);
|
||||
begin_key = &begin_storage;
|
||||
}
|
||||
if (end == nullptr) {
|
||||
end_key = nullptr;
|
||||
} else {
|
||||
end_storage.SetMaxPossibleForUserKey(*end);
|
||||
end_key = &end_storage;
|
||||
}
|
||||
|
||||
vstorage->GetCleanInputsWithinInterval(i, begin_key, end_key,
|
||||
&level_files, -1 /* hint_index */,
|
||||
nullptr /* file_index */);
|
||||
FileMetaData* level_file;
|
||||
for (uint32_t j = 0; j < level_files.size(); j++) {
|
||||
level_file = level_files[j];
|
||||
if (level_file->being_compacted) {
|
||||
continue;
|
||||
}
|
||||
if (deleted_files.find(level_file) != deleted_files.end()) {
|
||||
continue;
|
||||
}
|
||||
if (!include_end && end != nullptr &&
|
||||
cfd->user_comparator()->Compare(level_file->largest.user_key(), *end) == 0) {
|
||||
continue;
|
||||
}
|
||||
edit.SetColumnFamily(cfd->GetID());
|
||||
edit.DeleteFile(i, level_file->fd.GetNumber());
|
||||
deleted_files.insert(level_file);
|
||||
level_file->being_compacted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (edit.GetDeletedFiles().empty()) {
|
||||
@@ -2321,7 +2368,15 @@ Status DB::DestroyColumnFamilyHandle(ColumnFamilyHandle* column_family) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
DB::~DB() { }
|
||||
DB::~DB() {}
|
||||
|
||||
Status DBImpl::Close() {
|
||||
if (!closed_) {
|
||||
closed_ = true;
|
||||
return CloseImpl();
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DB::ListColumnFamilies(const DBOptions& db_options,
|
||||
const std::string& name,
|
||||
@@ -2777,12 +2832,14 @@ Status DBImpl::IngestExternalFile(
|
||||
// Figure out if we need to flush the memtable first
|
||||
if (status.ok()) {
|
||||
bool need_flush = false;
|
||||
status = ingestion_job.NeedsFlush(&need_flush);
|
||||
status = ingestion_job.NeedsFlush(&need_flush, cfd->GetSuperVersion());
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::IngestExternalFile:NeedFlush",
|
||||
&need_flush);
|
||||
if (status.ok() && need_flush) {
|
||||
mutex_.Unlock();
|
||||
status = FlushMemTable(cfd, FlushOptions(), true /* writes_stopped */);
|
||||
status = FlushMemTable(cfd, FlushOptions(),
|
||||
FlushReason::kExternalFileIngestion,
|
||||
true /* writes_stopped */);
|
||||
mutex_.Lock();
|
||||
}
|
||||
}
|
||||
|
||||
+46
-10
@@ -247,8 +247,9 @@ class DBImpl : public DB {
|
||||
const TransactionLogIterator::ReadOptions&
|
||||
read_options = TransactionLogIterator::ReadOptions()) override;
|
||||
virtual Status DeleteFile(std::string name) override;
|
||||
Status DeleteFilesInRange(ColumnFamilyHandle* column_family,
|
||||
const Slice* begin, const Slice* end);
|
||||
Status DeleteFilesInRanges(ColumnFamilyHandle* column_family,
|
||||
const RangePtr* ranges, size_t n,
|
||||
bool include_end = true);
|
||||
|
||||
virtual void GetLiveFilesMetaData(
|
||||
std::vector<LiveFileMetaData>* metadata) override;
|
||||
@@ -468,6 +469,8 @@ class DBImpl : public DB {
|
||||
// belong to live files are posibly removed. Also, removes all the
|
||||
// files in sst_delete_files and log_delete_files.
|
||||
// It is not necessary to hold the mutex when invoking this method.
|
||||
// If FindObsoleteFiles() was run, we need to also run
|
||||
// PurgeObsoleteFiles(), even if disable_delete_obsolete_files_ is true
|
||||
void PurgeObsoleteFiles(const JobContext& background_contet,
|
||||
bool schedule_only = false);
|
||||
|
||||
@@ -611,10 +614,14 @@ class DBImpl : public DB {
|
||||
std::vector<ColumnFamilyHandle*>* handles, DB** dbptr,
|
||||
const bool seq_per_batch);
|
||||
|
||||
virtual Status Close() override;
|
||||
|
||||
protected:
|
||||
Env* const env_;
|
||||
const std::string dbname_;
|
||||
unique_ptr<VersionSet> versions_;
|
||||
// Flag to check whether we allocated and own the info log file
|
||||
bool own_info_log_;
|
||||
const DBOptions initial_db_options_;
|
||||
const ImmutableDBOptions immutable_db_options_;
|
||||
MutableDBOptions mutable_db_options_;
|
||||
@@ -666,10 +673,14 @@ class DBImpl : public DB {
|
||||
// batch which will be written to memtable later during the commit, and in
|
||||
// WritePrepared it is guaranteed since it will be used only for WAL markers
|
||||
// which will never be written to memtable.
|
||||
// batch_cnt is expected to be non-zero in seq_per_batch mode and indicates
|
||||
// the number of sub-patches. A sub-patch is a subset of the write batch that
|
||||
// does not have duplicate keys.
|
||||
Status WriteImpl(const WriteOptions& options, WriteBatch* updates,
|
||||
WriteCallback* callback = nullptr,
|
||||
uint64_t* log_used = nullptr, uint64_t log_ref = 0,
|
||||
bool disable_memtable = false, uint64_t* seq_used = nullptr,
|
||||
size_t batch_cnt = 0,
|
||||
PreReleaseCallback* pre_release_callback = nullptr);
|
||||
|
||||
Status PipelinedWriteImpl(const WriteOptions& options, WriteBatch* updates,
|
||||
@@ -678,10 +689,13 @@ class DBImpl : public DB {
|
||||
bool disable_memtable = false,
|
||||
uint64_t* seq_used = nullptr);
|
||||
|
||||
// batch_cnt is expected to be non-zero in seq_per_batch mode and indicates
|
||||
// the number of sub-patches. A sub-patch is a subset of the write batch that
|
||||
// does not have duplicate keys.
|
||||
Status WriteImplWALOnly(const WriteOptions& options, WriteBatch* updates,
|
||||
WriteCallback* callback = nullptr,
|
||||
uint64_t* log_used = nullptr, uint64_t log_ref = 0,
|
||||
uint64_t* seq_used = nullptr,
|
||||
uint64_t* seq_used = nullptr, size_t batch_cnt = 0,
|
||||
PreReleaseCallback* pre_release_callback = nullptr);
|
||||
|
||||
uint64_t FindMinLogContainingOutstandingPrep();
|
||||
@@ -690,6 +704,9 @@ class DBImpl : public DB {
|
||||
// The writer must be the leader in write_thread_ and holding mutex_
|
||||
Status WriteRecoverableState();
|
||||
|
||||
// Actual implementation of Close()
|
||||
Status CloseImpl();
|
||||
|
||||
private:
|
||||
friend class DB;
|
||||
friend class InternalStats;
|
||||
@@ -805,10 +822,14 @@ class DBImpl : public DB {
|
||||
|
||||
// Force current memtable contents to be flushed.
|
||||
Status FlushMemTable(ColumnFamilyData* cfd, const FlushOptions& options,
|
||||
bool writes_stopped = false);
|
||||
FlushReason flush_reason, bool writes_stopped = false);
|
||||
|
||||
// Wait for memtable flushed
|
||||
Status WaitForFlushMemTable(ColumnFamilyData* cfd);
|
||||
// Wait for memtable flushed.
|
||||
// If flush_memtable_id is non-null, wait until the memtable with the ID
|
||||
// gets flush. Otherwise, wait until the column family don't have any
|
||||
// memtable pending flush.
|
||||
Status WaitForFlushMemTable(ColumnFamilyData* cfd,
|
||||
const uint64_t* flush_memtable_id = nullptr);
|
||||
|
||||
// REQUIRES: mutex locked
|
||||
Status SwitchWAL(WriteContext* write_context);
|
||||
@@ -863,7 +884,7 @@ class DBImpl : public DB {
|
||||
ColumnFamilyData* GetColumnFamilyDataByName(const std::string& cf_name);
|
||||
|
||||
void MaybeScheduleFlushOrCompaction();
|
||||
void SchedulePendingFlush(ColumnFamilyData* cfd);
|
||||
void SchedulePendingFlush(ColumnFamilyData* cfd, FlushReason flush_reason);
|
||||
void SchedulePendingCompaction(ColumnFamilyData* cfd);
|
||||
void SchedulePendingPurge(std::string fname, FileType type, uint64_t number,
|
||||
uint32_t path_id, int job_id);
|
||||
@@ -902,7 +923,7 @@ class DBImpl : public DB {
|
||||
// helper functions for adding and removing from flush & compaction queues
|
||||
void AddToCompactionQueue(ColumnFamilyData* cfd);
|
||||
ColumnFamilyData* PopFirstFromCompactionQueue();
|
||||
void AddToFlushQueue(ColumnFamilyData* cfd);
|
||||
void AddToFlushQueue(ColumnFamilyData* cfd, FlushReason flush_reason);
|
||||
ColumnFamilyData* PopFirstFromFlushQueue();
|
||||
|
||||
// helper function to call after some of the logs_ were synced
|
||||
@@ -912,6 +933,8 @@ class DBImpl : public DB {
|
||||
|
||||
uint64_t GetMaxTotalWalSize() const;
|
||||
|
||||
Status CloseHelper();
|
||||
|
||||
// table_cache_ provides its own synchronization
|
||||
std::shared_ptr<Cache> table_cache_;
|
||||
|
||||
@@ -940,6 +963,10 @@ class DBImpl : public DB {
|
||||
// (i.e. whenever a flush is done, even if it didn't make any progress)
|
||||
// * whenever there is an error in background purge, flush or compaction
|
||||
// * whenever num_running_ingest_file_ goes to 0.
|
||||
// * whenever pending_purge_obsolete_files_ goes to 0.
|
||||
// * whenever disable_delete_obsolete_files_ goes to 0.
|
||||
// * whenever SetOptions successfully updates options.
|
||||
// * whenever a column family is dropped.
|
||||
InstrumentedCondVar bg_cv_;
|
||||
// Writes are protected by locking both mutex_ and log_write_mutex_, and reads
|
||||
// must be under either mutex_ or log_write_mutex_. Since after ::Open,
|
||||
@@ -977,9 +1004,11 @@ class DBImpl : public DB {
|
||||
writer = nullptr;
|
||||
return w;
|
||||
}
|
||||
void ClearWriter() {
|
||||
Status ClearWriter() {
|
||||
Status s = writer->WriteBuffer();
|
||||
delete writer;
|
||||
writer = nullptr;
|
||||
return s;
|
||||
}
|
||||
|
||||
uint64_t number;
|
||||
@@ -1206,6 +1235,10 @@ class DBImpl : public DB {
|
||||
// without any synchronization
|
||||
int disable_delete_obsolete_files_;
|
||||
|
||||
// Number of times FindObsoleteFiles has found deletable files and the
|
||||
// corresponding call to PurgeObsoleteFiles has not yet finished.
|
||||
int pending_purge_obsolete_files_;
|
||||
|
||||
// last time when DeleteObsoleteFiles with full scan was executed. Originaly
|
||||
// initialized with startup time.
|
||||
uint64_t delete_obsolete_files_last_run_;
|
||||
@@ -1334,7 +1367,7 @@ class DBImpl : public DB {
|
||||
return Env::WLTH_SHORT;
|
||||
}
|
||||
|
||||
// When set, we use a seprate queue for writes that dont write to memtable. In
|
||||
// When set, we use a separate queue for writes that dont write to memtable. In
|
||||
// 2PC these are the writes at Prepare phase.
|
||||
const bool two_write_queues_;
|
||||
const bool manual_wal_flush_;
|
||||
@@ -1359,6 +1392,9 @@ class DBImpl : public DB {
|
||||
// is set to false.
|
||||
std::atomic<SequenceNumber> preserve_deletes_seqnum_;
|
||||
const bool preserve_deletes_;
|
||||
|
||||
// Flag to check whether Close() has been called on this DB
|
||||
bool closed_;
|
||||
};
|
||||
|
||||
extern Options SanitizeOptions(const std::string& db,
|
||||
|
||||
+107
-19
@@ -23,6 +23,7 @@
|
||||
#include "util/sync_point.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
Status DBImpl::SyncClosedLogs(JobContext* job_context) {
|
||||
TEST_SYNC_POINT("DBImpl::SyncClosedLogs:Start");
|
||||
mutex_.AssertHeld();
|
||||
@@ -222,6 +223,7 @@ void DBImpl::NotifyOnFlushBegin(ColumnFamilyData* cfd, FileMetaData* file_meta,
|
||||
info.smallest_seqno = file_meta->smallest_seqno;
|
||||
info.largest_seqno = file_meta->largest_seqno;
|
||||
info.table_properties = prop;
|
||||
info.flush_reason = cfd->GetFlushReason();
|
||||
for (auto listener : immutable_db_options_.listeners) {
|
||||
listener->OnFlushBegin(this, info);
|
||||
}
|
||||
@@ -266,6 +268,7 @@ void DBImpl::NotifyOnFlushCompleted(ColumnFamilyData* cfd,
|
||||
info.smallest_seqno = file_meta->smallest_seqno;
|
||||
info.largest_seqno = file_meta->largest_seqno;
|
||||
info.table_properties = prop;
|
||||
info.flush_reason = cfd->GetFlushReason();
|
||||
for (auto listener : immutable_db_options_.listeners) {
|
||||
listener->OnFlushCompleted(this, info);
|
||||
}
|
||||
@@ -287,10 +290,75 @@ Status DBImpl::CompactRange(const CompactRangeOptions& options,
|
||||
auto cfd = cfh->cfd();
|
||||
bool exclusive = options.exclusive_manual_compaction;
|
||||
|
||||
Status s = FlushMemTable(cfd, FlushOptions());
|
||||
if (!s.ok()) {
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
return s;
|
||||
bool flush_needed = true;
|
||||
if (begin != nullptr && end != nullptr) {
|
||||
// TODO(ajkr): We could also optimize away the flush in certain cases where
|
||||
// one/both sides of the interval are unbounded. But it requires more
|
||||
// changes to RangesOverlapWithMemtables.
|
||||
Range range(*begin, *end);
|
||||
SuperVersion* super_version = cfd->GetReferencedSuperVersion(&mutex_);
|
||||
cfd->RangesOverlapWithMemtables({range}, super_version, &flush_needed);
|
||||
CleanupSuperVersion(super_version);
|
||||
}
|
||||
|
||||
if (!options.allow_write_stall && flush_needed) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
uint64_t orig_active_memtable_id = cfd->mem()->GetID();
|
||||
WriteStallCondition write_stall_condition = WriteStallCondition::kNormal;
|
||||
do {
|
||||
if (write_stall_condition != WriteStallCondition::kNormal) {
|
||||
TEST_SYNC_POINT("DBImpl::CompactRange:StallWait");
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"[%s] CompactRange waiting on stall conditions to clear",
|
||||
cfd->GetName().c_str());
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
if (cfd->IsDropped() || shutting_down_.load(std::memory_order_acquire)) {
|
||||
return Status::ShutdownInProgress();
|
||||
}
|
||||
|
||||
uint64_t earliest_memtable_id =
|
||||
std::min(cfd->mem()->GetID(), cfd->imm()->GetEarliestMemTableID());
|
||||
if (earliest_memtable_id > orig_active_memtable_id) {
|
||||
// We waited so long that the memtable we were originally waiting on was
|
||||
// flushed.
|
||||
flush_needed = false;
|
||||
break;
|
||||
}
|
||||
|
||||
const auto& mutable_cf_options = *cfd->GetLatestMutableCFOptions();
|
||||
const auto* vstorage = cfd->current()->storage_info();
|
||||
|
||||
// Skip stalling check if we're below auto-flush and auto-compaction
|
||||
// triggers. If it stalled in these conditions, that'd mean the stall
|
||||
// triggers are so low that stalling is needed for any background work. In
|
||||
// that case we shouldn't wait since background work won't be scheduled.
|
||||
if (cfd->imm()->NumNotFlushed() <
|
||||
cfd->ioptions()->min_write_buffer_number_to_merge &&
|
||||
vstorage->l0_delay_trigger_count() <
|
||||
mutable_cf_options.level0_file_num_compaction_trigger) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 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;
|
||||
} while (write_stall_condition != WriteStallCondition::kNormal);
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::CompactRange:StallWaitDone");
|
||||
Status s;
|
||||
if (flush_needed) {
|
||||
s = FlushMemTable(cfd, FlushOptions(), FlushReason::kManualCompaction);
|
||||
if (!s.ok()) {
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
int max_level_with_files = 0;
|
||||
@@ -441,7 +509,8 @@ Status DBImpl::CompactFiles(
|
||||
} // release the mutex
|
||||
|
||||
// delete unnecessary files if any, this is done outside the mutex
|
||||
if (job_context.HaveSomethingToDelete() || !log_buffer.IsEmpty()) {
|
||||
if (job_context.HaveSomethingToClean() ||
|
||||
job_context.HaveSomethingToDelete() || !log_buffer.IsEmpty()) {
|
||||
// Have to flush the info logs before bg_compaction_scheduled_--
|
||||
// because if bg_flush_scheduled_ becomes 0 and the lock is
|
||||
// released, the deconstructor of DB can kick in and destroy all the
|
||||
@@ -809,7 +878,14 @@ int DBImpl::Level0StopWriteTrigger(ColumnFamilyHandle* column_family) {
|
||||
Status DBImpl::Flush(const FlushOptions& flush_options,
|
||||
ColumnFamilyHandle* column_family) {
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
return FlushMemTable(cfh->cfd(), flush_options);
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "[%s] Manual flush start.",
|
||||
cfh->GetName().c_str());
|
||||
Status s =
|
||||
FlushMemTable(cfh->cfd(), flush_options, FlushReason::kManualCompaction);
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"[%s] Manual flush finished, status: %s\n",
|
||||
cfh->GetName().c_str(), s.ToString().c_str());
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::RunManualCompaction(ColumnFamilyData* cfd, int input_level,
|
||||
@@ -942,8 +1018,9 @@ Status DBImpl::RunManualCompaction(ColumnFamilyData* cfd, int input_level,
|
||||
|
||||
Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
|
||||
const FlushOptions& flush_options,
|
||||
bool writes_stopped) {
|
||||
FlushReason flush_reason, bool writes_stopped) {
|
||||
Status s;
|
||||
uint64_t flush_memtable_id = 0;
|
||||
{
|
||||
WriteContext context;
|
||||
InstrumentedMutexLock guard_lock(&mutex_);
|
||||
@@ -961,6 +1038,7 @@ Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
|
||||
|
||||
// SwitchMemtable() will release and reacquire mutex during execution
|
||||
s = SwitchMemtable(cfd, &context);
|
||||
flush_memtable_id = cfd->imm()->GetLatestMemTableID();
|
||||
|
||||
if (!writes_stopped) {
|
||||
write_thread_.ExitUnbatched(&w);
|
||||
@@ -969,22 +1047,25 @@ Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
|
||||
cfd->imm()->FlushRequested();
|
||||
|
||||
// schedule flush
|
||||
SchedulePendingFlush(cfd);
|
||||
SchedulePendingFlush(cfd, flush_reason);
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
|
||||
if (s.ok() && flush_options.wait) {
|
||||
// Wait until the compaction completes
|
||||
s = WaitForFlushMemTable(cfd);
|
||||
s = WaitForFlushMemTable(cfd, &flush_memtable_id);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::WaitForFlushMemTable(ColumnFamilyData* cfd) {
|
||||
Status DBImpl::WaitForFlushMemTable(ColumnFamilyData* cfd,
|
||||
const uint64_t* flush_memtable_id) {
|
||||
Status s;
|
||||
// Wait until the compaction completes
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
while (cfd->imm()->NumNotFlushed() > 0 && bg_error_.ok()) {
|
||||
while (cfd->imm()->NumNotFlushed() > 0 && bg_error_.ok() &&
|
||||
(flush_memtable_id == nullptr ||
|
||||
cfd->imm()->GetEarliestMemTableID() <= *flush_memtable_id)) {
|
||||
if (shutting_down_.load(std::memory_order_acquire)) {
|
||||
return Status::ShutdownInProgress();
|
||||
}
|
||||
@@ -1059,6 +1140,7 @@ void DBImpl::MaybeScheduleFlushOrCompaction() {
|
||||
if (HasExclusiveManualCompaction()) {
|
||||
// only manual compactions are allowed to run. don't schedule automatic
|
||||
// compactions
|
||||
TEST_SYNC_POINT("DBImpl::MaybeScheduleFlushOrCompaction:Conflict");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1121,11 +1203,12 @@ ColumnFamilyData* DBImpl::PopFirstFromCompactionQueue() {
|
||||
return cfd;
|
||||
}
|
||||
|
||||
void DBImpl::AddToFlushQueue(ColumnFamilyData* cfd) {
|
||||
void DBImpl::AddToFlushQueue(ColumnFamilyData* cfd, FlushReason flush_reason) {
|
||||
assert(!cfd->pending_flush());
|
||||
cfd->Ref();
|
||||
flush_queue_.push_back(cfd);
|
||||
cfd->set_pending_flush(true);
|
||||
cfd->SetFlushReason(flush_reason);
|
||||
}
|
||||
|
||||
ColumnFamilyData* DBImpl::PopFirstFromFlushQueue() {
|
||||
@@ -1134,12 +1217,14 @@ ColumnFamilyData* DBImpl::PopFirstFromFlushQueue() {
|
||||
flush_queue_.pop_front();
|
||||
assert(cfd->pending_flush());
|
||||
cfd->set_pending_flush(false);
|
||||
// TODO: need to unset flush reason?
|
||||
return cfd;
|
||||
}
|
||||
|
||||
void DBImpl::SchedulePendingFlush(ColumnFamilyData* cfd) {
|
||||
void DBImpl::SchedulePendingFlush(ColumnFamilyData* cfd,
|
||||
FlushReason flush_reason) {
|
||||
if (!cfd->pending_flush() && cfd->imm()->IsFlushPending()) {
|
||||
AddToFlushQueue(cfd);
|
||||
AddToFlushQueue(cfd, flush_reason);
|
||||
++unscheduled_flushes_;
|
||||
}
|
||||
}
|
||||
@@ -1303,8 +1388,10 @@ void DBImpl::BackgroundCallFlush() {
|
||||
// created. Thus, we force full scan in FindObsoleteFiles()
|
||||
FindObsoleteFiles(&job_context, !s.ok() && !s.IsShutdownInProgress());
|
||||
// delete unnecessary files if any, this is done outside the mutex
|
||||
if (job_context.HaveSomethingToDelete() || !log_buffer.IsEmpty()) {
|
||||
if (job_context.HaveSomethingToClean() ||
|
||||
job_context.HaveSomethingToDelete() || !log_buffer.IsEmpty()) {
|
||||
mutex_.Unlock();
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCallFlush:FilesFound");
|
||||
// Have to flush the info logs before bg_flush_scheduled_--
|
||||
// because if bg_flush_scheduled_ becomes 0 and the lock is
|
||||
// released, the deconstructor of DB can kick in and destroy all the
|
||||
@@ -1384,7 +1471,8 @@ void DBImpl::BackgroundCallCompaction(PrepickedCompaction* prepicked_compaction,
|
||||
FindObsoleteFiles(&job_context, !s.ok() && !s.IsShutdownInProgress());
|
||||
|
||||
// delete unnecessary files if any, this is done outside the mutex
|
||||
if (job_context.HaveSomethingToDelete() || !log_buffer.IsEmpty()) {
|
||||
if (job_context.HaveSomethingToClean() ||
|
||||
job_context.HaveSomethingToDelete() || !log_buffer.IsEmpty()) {
|
||||
mutex_.Unlock();
|
||||
// Have to flush the info logs before bg_compaction_scheduled_--
|
||||
// because if bg_flush_scheduled_ becomes 0 and the lock is
|
||||
@@ -1503,7 +1591,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
: m->manual_end->DebugString().c_str()));
|
||||
}
|
||||
} else if (!is_prepicked && !compaction_queue_.empty()) {
|
||||
if (HaveManualCompaction(compaction_queue_.front())) {
|
||||
if (HasExclusiveManualCompaction()) {
|
||||
// Can't compact right now, but try again later
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCompaction()::Conflict");
|
||||
|
||||
@@ -1679,7 +1767,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
env_->Schedule(&DBImpl::BGWorkBottomCompaction, ca, Env::Priority::BOTTOM,
|
||||
this, &DBImpl::UnscheduleCallback);
|
||||
} else {
|
||||
int output_level __attribute__((unused));
|
||||
int output_level __attribute__((__unused__));
|
||||
output_level = c->output_level();
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:NonTrivial",
|
||||
&output_level);
|
||||
@@ -1913,7 +2001,7 @@ void DBImpl::InstallSuperVersionAndScheduleWork(
|
||||
|
||||
// Whenever we install new SuperVersion, we might need to issue new flushes or
|
||||
// compactions.
|
||||
SchedulePendingFlush(cfd);
|
||||
SchedulePendingFlush(cfd, FlushReason::kSuperVersionChange);
|
||||
SchedulePendingCompaction(cfd);
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
|
||||
|
||||
+1
-1
@@ -103,7 +103,7 @@ Status DBImpl::TEST_FlushMemTable(bool wait, ColumnFamilyHandle* cfh) {
|
||||
auto cfhi = reinterpret_cast<ColumnFamilyHandleImpl*>(cfh);
|
||||
cfd = cfhi->cfd();
|
||||
}
|
||||
return FlushMemTable(cfd, fo);
|
||||
return FlushMemTable(cfd, fo, FlushReason::kTest);
|
||||
}
|
||||
|
||||
Status DBImpl::TEST_WaitForFlushMemTable(ColumnFamilyHandle* column_family) {
|
||||
|
||||
+13
-6
@@ -285,6 +285,9 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
job_context->logs_to_free = logs_to_free_;
|
||||
job_context->log_recycle_files.assign(log_recycle_files_.begin(),
|
||||
log_recycle_files_.end());
|
||||
if (job_context->HaveSomethingToDelete()) {
|
||||
++pending_purge_obsolete_files_;
|
||||
}
|
||||
logs_to_free_.clear();
|
||||
}
|
||||
|
||||
@@ -342,15 +345,12 @@ void DBImpl::DeleteObsoleteFileImpl(int job_id, const std::string& fname,
|
||||
// files in sst_delete_files and log_delete_files.
|
||||
// It is not necessary to hold the mutex when invoking this method.
|
||||
void DBImpl::PurgeObsoleteFiles(const JobContext& state, bool schedule_only) {
|
||||
TEST_SYNC_POINT("DBImpl::PurgeObsoleteFiles:Begin");
|
||||
// we'd better have sth to delete
|
||||
assert(state.HaveSomethingToDelete());
|
||||
|
||||
// this checks if FindObsoleteFiles() was run before. If not, don't do
|
||||
// PurgeObsoleteFiles(). If FindObsoleteFiles() was run, we need to also
|
||||
// run PurgeObsoleteFiles(), even if disable_delete_obsolete_files_ is true
|
||||
if (state.manifest_file_number == 0) {
|
||||
return;
|
||||
}
|
||||
// FindObsoleteFiles() should've populated this so nonzero
|
||||
assert(state.manifest_file_number != 0);
|
||||
|
||||
// Now, convert live list to an unordered map, WITHOUT mutex held;
|
||||
// set is slow.
|
||||
@@ -533,6 +533,13 @@ void DBImpl::PurgeObsoleteFiles(const JobContext& state, bool schedule_only) {
|
||||
wal_manager_.PurgeObsoleteWALFiles();
|
||||
#endif // ROCKSDB_LITE
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
--pending_purge_obsolete_files_;
|
||||
assert(pending_purge_obsolete_files_ >= 0);
|
||||
if (pending_purge_obsolete_files_ == 0) {
|
||||
bg_cv_.SignalAll();
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::PurgeObsoleteFiles:End");
|
||||
}
|
||||
|
||||
void DBImpl::DeleteObsoleteFiles() {
|
||||
|
||||
@@ -356,6 +356,29 @@ Status DBImpl::Recover(
|
||||
assert(s.IsIOError());
|
||||
return s;
|
||||
}
|
||||
// Verify compatibility of env_options_ and filesystem
|
||||
{
|
||||
unique_ptr<RandomAccessFile> idfile;
|
||||
EnvOptions customized_env(env_options_);
|
||||
customized_env.use_direct_reads |=
|
||||
immutable_db_options_.use_direct_io_for_flush_and_compaction;
|
||||
s = env_->NewRandomAccessFile(IdentityFileName(dbname_), &idfile,
|
||||
customized_env);
|
||||
if (!s.ok()) {
|
||||
const char* error_msg = s.ToString().c_str();
|
||||
// Check if unsupported Direct I/O is the root cause
|
||||
customized_env.use_direct_reads = false;
|
||||
s = env_->NewRandomAccessFile(IdentityFileName(dbname_), &idfile,
|
||||
customized_env);
|
||||
if (s.ok()) {
|
||||
return Status::InvalidArgument(
|
||||
"Direct I/O is not supported by the specified DB.");
|
||||
} else {
|
||||
return Status::InvalidArgument(
|
||||
"Found options incompatible with filesystem", error_msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Status s = versions_->Recover(column_families, read_only);
|
||||
|
||||
+59
-29
@@ -64,7 +64,9 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
WriteBatch* my_batch, WriteCallback* callback,
|
||||
uint64_t* log_used, uint64_t log_ref,
|
||||
bool disable_memtable, uint64_t* seq_used,
|
||||
size_t batch_cnt,
|
||||
PreReleaseCallback* pre_release_callback) {
|
||||
assert(!seq_per_batch_ || batch_cnt != 0);
|
||||
if (my_batch == nullptr) {
|
||||
return Status::Corruption("Batch is nullptr!");
|
||||
}
|
||||
@@ -76,6 +78,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
"pipelined_writes is not compatible with concurrent prepares");
|
||||
}
|
||||
if (seq_per_batch_ && immutable_db_options_.enable_pipelined_write) {
|
||||
// TODO(yiwu): update pipeline write with seq_per_batch and batch_cnt
|
||||
return Status::NotSupported(
|
||||
"pipelined_writes is not compatible with seq_per_batch");
|
||||
}
|
||||
@@ -93,7 +96,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
|
||||
if (two_write_queues_ && disable_memtable) {
|
||||
return WriteImplWALOnly(write_options, my_batch, callback, log_used,
|
||||
log_ref, seq_used, pre_release_callback);
|
||||
log_ref, seq_used, batch_cnt, pre_release_callback);
|
||||
}
|
||||
|
||||
if (immutable_db_options_.enable_pipelined_write) {
|
||||
@@ -103,7 +106,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
|
||||
PERF_TIMER_GUARD(write_pre_and_post_process_time);
|
||||
WriteThread::Writer w(write_options, my_batch, callback, log_ref,
|
||||
disable_memtable, pre_release_callback);
|
||||
disable_memtable, batch_cnt, pre_release_callback);
|
||||
|
||||
if (!write_options.disableWAL) {
|
||||
RecordTick(stats_, WRITE_WITH_WAL);
|
||||
@@ -122,7 +125,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
w.status = WriteBatchInternal::InsertInto(
|
||||
&w, w.sequence, &column_family_memtables, &flush_scheduler_,
|
||||
write_options.ignore_missing_column_families, 0 /*log_number*/, this,
|
||||
true /*concurrent_memtable_writes*/, seq_per_batch_);
|
||||
true /*concurrent_memtable_writes*/, seq_per_batch_, w.batch_cnt);
|
||||
}
|
||||
|
||||
if (write_thread_.CompleteParallelMemTableWriter(&w)) {
|
||||
@@ -214,7 +217,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
uint64_t total_byte_size = 0;
|
||||
for (auto* writer : write_group) {
|
||||
if (writer->CheckCallback(this)) {
|
||||
valid_batches++;
|
||||
valid_batches += writer->batch_cnt;
|
||||
if (writer->ShouldWriteToMemtable()) {
|
||||
total_count += WriteBatchInternal::Count(writer->batch);
|
||||
parallel = parallel && !writer->batch->HasMerge();
|
||||
@@ -303,7 +306,8 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
}
|
||||
writer->sequence = next_sequence;
|
||||
if (seq_per_batch_) {
|
||||
next_sequence++;
|
||||
assert(writer->batch_cnt);
|
||||
next_sequence += writer->batch_cnt;
|
||||
} else if (writer->ShouldWriteToMemtable()) {
|
||||
next_sequence += WriteBatchInternal::Count(writer->batch);
|
||||
}
|
||||
@@ -323,7 +327,8 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
w.status = WriteBatchInternal::InsertInto(
|
||||
&w, w.sequence, &column_family_memtables, &flush_scheduler_,
|
||||
write_options.ignore_missing_column_families, 0 /*log_number*/,
|
||||
this, true /*concurrent_memtable_writes*/, seq_per_batch_);
|
||||
this, true /*concurrent_memtable_writes*/, seq_per_batch_,
|
||||
w.batch_cnt);
|
||||
}
|
||||
}
|
||||
if (seq_used != nullptr) {
|
||||
@@ -515,12 +520,13 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
Status DBImpl::WriteImplWALOnly(const WriteOptions& write_options,
|
||||
WriteBatch* my_batch, WriteCallback* callback,
|
||||
uint64_t* log_used, uint64_t log_ref,
|
||||
uint64_t* seq_used,
|
||||
uint64_t* seq_used, size_t batch_cnt,
|
||||
PreReleaseCallback* pre_release_callback) {
|
||||
Status status;
|
||||
PERF_TIMER_GUARD(write_pre_and_post_process_time);
|
||||
WriteThread::Writer w(write_options, my_batch, callback, log_ref,
|
||||
true /* disable_memtable */, pre_release_callback);
|
||||
true /* disable_memtable */, batch_cnt,
|
||||
pre_release_callback);
|
||||
RecordTick(stats_, WRITE_WITH_WAL);
|
||||
StopWatch write_sw(env_, immutable_db_options_.statistics.get(), DB_WRITE);
|
||||
|
||||
@@ -576,7 +582,15 @@ Status DBImpl::WriteImplWALOnly(const WriteOptions& write_options,
|
||||
PERF_TIMER_GUARD(write_wal_time);
|
||||
// LastAllocatedSequence is increased inside WriteToWAL under
|
||||
// wal_write_mutex_ to ensure ordered events in WAL
|
||||
size_t seq_inc = seq_per_batch_ ? write_group.size : 0 /*total_count*/;
|
||||
size_t seq_inc = 0 /* total_count */;
|
||||
if (seq_per_batch_) {
|
||||
size_t total_batch_cnt = 0;
|
||||
for (auto* writer : write_group) {
|
||||
assert(writer->batch_cnt);
|
||||
total_batch_cnt += writer->batch_cnt;
|
||||
}
|
||||
seq_inc = total_batch_cnt;
|
||||
}
|
||||
if (!write_options.disableWAL) {
|
||||
status =
|
||||
ConcurrentWriteToWAL(write_group, log_used, &last_sequence, seq_inc);
|
||||
@@ -591,7 +605,8 @@ Status DBImpl::WriteImplWALOnly(const WriteOptions& write_options,
|
||||
}
|
||||
writer->sequence = curr_seq;
|
||||
if (seq_per_batch_) {
|
||||
curr_seq++;
|
||||
assert(writer->batch_cnt);
|
||||
curr_seq += writer->batch_cnt;
|
||||
}
|
||||
// else seq advances only by memtable writes
|
||||
}
|
||||
@@ -1004,7 +1019,7 @@ Status DBImpl::SwitchWAL(WriteContext* write_context) {
|
||||
break;
|
||||
}
|
||||
cfd->imm()->FlushRequested();
|
||||
SchedulePendingFlush(cfd);
|
||||
SchedulePendingFlush(cfd, FlushReason::kWriteBufferManager);
|
||||
}
|
||||
}
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
@@ -1050,7 +1065,7 @@ Status DBImpl::HandleWriteBufferFull(WriteContext* write_context) {
|
||||
status = SwitchMemtable(cfd_picked, write_context);
|
||||
if (status.ok()) {
|
||||
cfd_picked->imm()->FlushRequested();
|
||||
SchedulePendingFlush(cfd_picked);
|
||||
SchedulePendingFlush(cfd_picked, FlushReason::kWriteBufferFull);
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
}
|
||||
@@ -1292,6 +1307,29 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
". Immutable memtables: %d.\n",
|
||||
cfd->GetName().c_str(), new_log_number, num_imm_unflushed);
|
||||
mutex_.Lock();
|
||||
if (s.ok() && creating_new_log) {
|
||||
log_write_mutex_.Lock();
|
||||
logfile_number_ = new_log_number;
|
||||
assert(new_log != nullptr);
|
||||
log_empty_ = true;
|
||||
log_dir_synced_ = false;
|
||||
if (!logs_.empty()) {
|
||||
// Alway flush the buffer of the last log before switching to a new one
|
||||
log::Writer* cur_log_writer = logs_.back().writer;
|
||||
s = cur_log_writer->WriteBuffer();
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"[%s] Failed to switch from #%" PRIu64 " to #%" PRIu64
|
||||
" WAL file -- %s\n",
|
||||
cfd->GetName().c_str(), cur_log_writer->get_log_number(),
|
||||
new_log_number);
|
||||
}
|
||||
}
|
||||
logs_.emplace_back(logfile_number_, new_log);
|
||||
alive_log_files_.push_back(LogFileNumberSize(logfile_number_));
|
||||
log_write_mutex_.Unlock();
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
// how do we fail if we're not creating new log?
|
||||
assert(creating_new_log);
|
||||
@@ -1302,21 +1340,7 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
}
|
||||
return s;
|
||||
}
|
||||
if (creating_new_log) {
|
||||
log_write_mutex_.Lock();
|
||||
logfile_number_ = new_log_number;
|
||||
assert(new_log != nullptr);
|
||||
log_empty_ = true;
|
||||
log_dir_synced_ = false;
|
||||
if (!logs_.empty()) {
|
||||
// Alway flush the buffer of the last log before switching to a new one
|
||||
log::Writer* cur_log_writer = logs_.back().writer;
|
||||
cur_log_writer->WriteBuffer();
|
||||
}
|
||||
logs_.emplace_back(logfile_number_, new_log);
|
||||
alive_log_files_.push_back(LogFileNumberSize(logfile_number_));
|
||||
log_write_mutex_.Unlock();
|
||||
}
|
||||
|
||||
for (auto loop_cfd : *versions_->GetColumnFamilySet()) {
|
||||
// all this is just optimization to delete logs that
|
||||
// are no longer needed -- if CF is empty, that means it
|
||||
@@ -1371,7 +1395,10 @@ Status DB::Put(const WriteOptions& opt, ColumnFamilyHandle* column_family,
|
||||
// 8 bytes are taken by header, 4 bytes for count, 1 byte for type,
|
||||
// and we allocate 11 extra bytes for key length, as well as value length.
|
||||
WriteBatch batch(key.size() + value.size() + 24);
|
||||
batch.Put(column_family, key, value);
|
||||
Status s = batch.Put(column_family, key, value);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
return Write(opt, &batch);
|
||||
}
|
||||
|
||||
@@ -1400,7 +1427,10 @@ Status DB::DeleteRange(const WriteOptions& opt,
|
||||
Status DB::Merge(const WriteOptions& opt, ColumnFamilyHandle* column_family,
|
||||
const Slice& key, const Slice& value) {
|
||||
WriteBatch batch;
|
||||
batch.Merge(column_family, key, value);
|
||||
Status s = batch.Merge(column_family, key, value);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
return Write(opt, &batch);
|
||||
}
|
||||
} // namespace rocksdb
|
||||
@@ -217,6 +217,9 @@ class DBIter final: public Iterator {
|
||||
*prop = "Iterator is not valid.";
|
||||
}
|
||||
return Status::OK();
|
||||
} else if (prop_name == "rocksdb.iterator.internal-key") {
|
||||
*prop = saved_key_.GetUserKey().ToString();
|
||||
return Status::OK();
|
||||
}
|
||||
return Status::InvalidArgument("Undentified property.");
|
||||
}
|
||||
@@ -611,10 +614,12 @@ void DBIter::MergeValuesNewToOld() {
|
||||
// Start the merge process by pushing the first operand
|
||||
merge_context_.PushOperand(iter_->value(),
|
||||
iter_->IsValuePinned() /* operand_pinned */);
|
||||
TEST_SYNC_POINT("DBIter::MergeValuesNewToOld:PushedFirstOperand");
|
||||
|
||||
ParsedInternalKey ikey;
|
||||
Status s;
|
||||
for (iter_->Next(); iter_->Valid(); iter_->Next()) {
|
||||
TEST_SYNC_POINT("DBIter::MergeValuesNewToOld:SteppedToNextOperand");
|
||||
if (!ParseKey(&ikey)) {
|
||||
// skip corrupted key
|
||||
continue;
|
||||
@@ -972,6 +977,17 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
// assume there is at least one parseable key for this user key
|
||||
ParsedInternalKey ikey;
|
||||
FindParseableKey(&ikey, kForward);
|
||||
assert(iter_->Valid());
|
||||
assert(user_comparator_->Equal(ikey.user_key, saved_key_.GetUserKey()));
|
||||
|
||||
// In case read_callback presents, the value we seek to may not be visible.
|
||||
// Seek for the next value that's visible.
|
||||
while (!IsVisible(ikey.sequence)) {
|
||||
iter_->Next();
|
||||
FindParseableKey(&ikey, kForward);
|
||||
assert(iter_->Valid());
|
||||
assert(user_comparator_->Equal(ikey.user_key, saved_key_.GetUserKey()));
|
||||
}
|
||||
|
||||
if (ikey.type == kTypeDeletion || ikey.type == kTypeSingleDeletion ||
|
||||
range_del_agg_.ShouldDelete(
|
||||
|
||||
+6
-4
@@ -36,7 +36,9 @@ class TestIterator : public InternalIterator {
|
||||
valid_(false),
|
||||
sequence_number_(0),
|
||||
iter_(0),
|
||||
cmp(comparator) {}
|
||||
cmp(comparator) {
|
||||
data_.reserve(16);
|
||||
}
|
||||
|
||||
void AddPut(std::string argkey, std::string argvalue) {
|
||||
Add(argkey, kTypeValue, argvalue);
|
||||
@@ -2549,7 +2551,7 @@ TEST_F(DBIterWithMergeIterTest, InnerMergeIteratorDataRace1) {
|
||||
// MergeIterator::Prev() realized the mem table iterator is at its end
|
||||
// and before an SeekToLast() is called.
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"MergeIterator::Prev:BeforeSeekToLast",
|
||||
"MergeIterator::Prev:BeforePrev",
|
||||
[&](void* arg) { internal_iter2_->Add("z", kTypeValue, "7", 12u); });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
@@ -2585,7 +2587,7 @@ TEST_F(DBIterWithMergeIterTest, InnerMergeIteratorDataRace2) {
|
||||
// mem table after MergeIterator::Prev() realized the mem tableiterator is at
|
||||
// its end and before an SeekToLast() is called.
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"MergeIterator::Prev:BeforeSeekToLast", [&](void* arg) {
|
||||
"MergeIterator::Prev:BeforePrev", [&](void* arg) {
|
||||
internal_iter2_->Add("z", kTypeValue, "7", 12u);
|
||||
internal_iter2_->Add("z", kTypeValue, "7", 11u);
|
||||
});
|
||||
@@ -2623,7 +2625,7 @@ TEST_F(DBIterWithMergeIterTest, InnerMergeIteratorDataRace3) {
|
||||
// mem table after MergeIterator::Prev() realized the mem table iterator is at
|
||||
// its end and before an SeekToLast() is called.
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"MergeIterator::Prev:BeforeSeekToLast", [&](void* arg) {
|
||||
"MergeIterator::Prev:BeforePrev", [&](void* arg) {
|
||||
internal_iter2_->Add("z", kTypeValue, "7", 16u, true);
|
||||
internal_iter2_->Add("z", kTypeValue, "7", 15u, true);
|
||||
internal_iter2_->Add("z", kTypeValue, "7", 14u, true);
|
||||
|
||||
+392
-98
File diff suppressed because it is too large.
Load diff
@@ -289,6 +289,8 @@ int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
#else
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
+80
-4
@@ -28,13 +28,11 @@ class MockMemTableRep : public MemTableRep {
|
||||
return rep_->Allocate(len, buf);
|
||||
}
|
||||
|
||||
virtual void Insert(KeyHandle handle) override {
|
||||
return rep_->Insert(handle);
|
||||
}
|
||||
virtual void Insert(KeyHandle handle) override { rep_->Insert(handle); }
|
||||
|
||||
virtual void InsertWithHint(KeyHandle handle, void** hint) override {
|
||||
num_insert_with_hint_++;
|
||||
ASSERT_NE(nullptr, hint);
|
||||
EXPECT_NE(nullptr, hint);
|
||||
last_hint_in_ = *hint;
|
||||
rep_->InsertWithHint(handle, hint);
|
||||
last_hint_out_ = *hint;
|
||||
@@ -129,6 +127,84 @@ class TestPrefixExtractor : public SliceTransform {
|
||||
}
|
||||
};
|
||||
|
||||
// Test that ::Add properly returns false when inserting duplicate keys
|
||||
TEST_F(DBMemTableTest, DuplicateSeq) {
|
||||
SequenceNumber seq = 123;
|
||||
std::string value;
|
||||
Status s;
|
||||
MergeContext merge_context;
|
||||
Options options;
|
||||
InternalKeyComparator ikey_cmp(options.comparator);
|
||||
RangeDelAggregator range_del_agg(ikey_cmp, {} /* snapshots */);
|
||||
|
||||
// Create a MemTable
|
||||
InternalKeyComparator cmp(BytewiseComparator());
|
||||
auto factory = std::make_shared<SkipListFactory>();
|
||||
options.memtable_factory = factory;
|
||||
ImmutableCFOptions ioptions(options);
|
||||
WriteBufferManager wb(options.db_write_buffer_size);
|
||||
MemTable* mem = new MemTable(cmp, ioptions, MutableCFOptions(options), &wb,
|
||||
kMaxSequenceNumber, 0 /* column_family_id */);
|
||||
|
||||
// Write some keys and make sure it returns false on duplicates
|
||||
bool res;
|
||||
res = mem->Add(seq, kTypeValue, "key", "value2");
|
||||
ASSERT_TRUE(res);
|
||||
res = mem->Add(seq, kTypeValue, "key", "value2");
|
||||
ASSERT_FALSE(res);
|
||||
// Changing the type should still cause the duplicatae key
|
||||
res = mem->Add(seq, kTypeMerge, "key", "value2");
|
||||
ASSERT_FALSE(res);
|
||||
// Changing the seq number will make the key fresh
|
||||
res = mem->Add(seq + 1, kTypeMerge, "key", "value2");
|
||||
ASSERT_TRUE(res);
|
||||
// Test with different types for duplicate keys
|
||||
res = mem->Add(seq, kTypeDeletion, "key", "");
|
||||
ASSERT_FALSE(res);
|
||||
res = mem->Add(seq, kTypeSingleDeletion, "key", "");
|
||||
ASSERT_FALSE(res);
|
||||
|
||||
// Test the duplicate keys under stress
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
bool insert_dup = i % 10 == 1;
|
||||
if (!insert_dup) {
|
||||
seq++;
|
||||
}
|
||||
res = mem->Add(seq, kTypeValue, "foo", "value" + ToString(seq));
|
||||
if (insert_dup) {
|
||||
ASSERT_FALSE(res);
|
||||
} else {
|
||||
ASSERT_TRUE(res);
|
||||
}
|
||||
}
|
||||
delete mem;
|
||||
|
||||
// Test with InsertWithHint
|
||||
options.memtable_insert_with_hint_prefix_extractor.reset(
|
||||
new TestPrefixExtractor()); // which uses _ to extract the prefix
|
||||
ioptions = ImmutableCFOptions(options);
|
||||
mem = new MemTable(cmp, ioptions, MutableCFOptions(options), &wb,
|
||||
kMaxSequenceNumber, 0 /* column_family_id */);
|
||||
// Insert a duplicate key with _ in it
|
||||
res = mem->Add(seq, kTypeValue, "key_1", "value");
|
||||
ASSERT_TRUE(res);
|
||||
res = mem->Add(seq, kTypeValue, "key_1", "value");
|
||||
ASSERT_FALSE(res);
|
||||
delete mem;
|
||||
|
||||
// Test when InsertConcurrently will be invoked
|
||||
options.allow_concurrent_memtable_write = true;
|
||||
ioptions = ImmutableCFOptions(options);
|
||||
mem = new MemTable(cmp, ioptions, MutableCFOptions(options), &wb,
|
||||
kMaxSequenceNumber, 0 /* column_family_id */);
|
||||
MemTablePostProcessInfo post_process_info;
|
||||
res = mem->Add(seq, kTypeValue, "key", "value", true, &post_process_info);
|
||||
ASSERT_TRUE(res);
|
||||
res = mem->Add(seq, kTypeValue, "key", "value", true, &post_process_info);
|
||||
ASSERT_FALSE(res);
|
||||
delete mem;
|
||||
}
|
||||
|
||||
TEST_F(DBMemTableTest, InsertWithHint) {
|
||||
Options options;
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
|
||||
@@ -14,10 +14,43 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class TestReadCallback : public ReadCallback {
|
||||
public:
|
||||
TestReadCallback(SnapshotChecker* snapshot_checker,
|
||||
SequenceNumber snapshot_seq)
|
||||
: snapshot_checker_(snapshot_checker), snapshot_seq_(snapshot_seq) {}
|
||||
|
||||
bool IsCommitted(SequenceNumber seq) override {
|
||||
return snapshot_checker_->IsInSnapshot(seq, snapshot_seq_);
|
||||
}
|
||||
|
||||
private:
|
||||
SnapshotChecker* snapshot_checker_;
|
||||
SequenceNumber snapshot_seq_;
|
||||
};
|
||||
|
||||
// Test merge operator functionality.
|
||||
class DBMergeOperatorTest : public DBTestBase {
|
||||
public:
|
||||
DBMergeOperatorTest() : DBTestBase("/db_merge_operator_test") {}
|
||||
|
||||
std::string GetWithReadCallback(SnapshotChecker* snapshot_checker,
|
||||
const Slice& key,
|
||||
const Snapshot* snapshot = nullptr) {
|
||||
SequenceNumber seq = snapshot == nullptr ? db_->GetLatestSequenceNumber()
|
||||
: snapshot->GetSequenceNumber();
|
||||
TestReadCallback read_callback(snapshot_checker, seq);
|
||||
ReadOptions read_opt;
|
||||
read_opt.snapshot = snapshot;
|
||||
PinnableSlice value;
|
||||
Status s =
|
||||
dbfull()->GetImpl(read_opt, db_->DefaultColumnFamily(), key, &value,
|
||||
nullptr /*value_found*/, &read_callback);
|
||||
if (!s.ok()) {
|
||||
return s.ToString();
|
||||
}
|
||||
return value.ToString();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(DBMergeOperatorTest, LimitMergeOperands) {
|
||||
@@ -449,8 +482,152 @@ TEST_P(MergeOperatorPinningTest, TailingIterator) {
|
||||
writer_thread.join();
|
||||
reader_thread.join();
|
||||
}
|
||||
|
||||
TEST_F(DBMergeOperatorTest, TailingIteratorMemtableUnrefedBySomeoneElse) {
|
||||
Options options = CurrentOptions();
|
||||
options.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Overview of the test:
|
||||
// * There are two merge operands for the same key: one in an sst file,
|
||||
// another in a memtable.
|
||||
// * Seek a tailing iterator to this key.
|
||||
// * As part of the seek, the iterator will:
|
||||
// (a) first visit the operand in the memtable and tell ForwardIterator
|
||||
// to pin this operand, then
|
||||
// (b) move on to the operand in the sst file, then pass both operands
|
||||
// to merge operator.
|
||||
// * The memtable may get flushed and unreferenced by another thread between
|
||||
// (a) and (b). The test simulates it by flushing the memtable inside a
|
||||
// SyncPoint callback located between (a) and (b).
|
||||
// * In this case it's ForwardIterator's responsibility to keep the memtable
|
||||
// pinned until (b) is complete. There used to be a bug causing
|
||||
// ForwardIterator to not pin it in some circumstances. This test
|
||||
// reproduces it.
|
||||
|
||||
db_->Merge(WriteOptions(), "key", "sst");
|
||||
db_->Flush(FlushOptions()); // Switch to SuperVersion A
|
||||
db_->Merge(WriteOptions(), "key", "memtable");
|
||||
|
||||
// Pin SuperVersion A
|
||||
std::unique_ptr<Iterator> someone_else(db_->NewIterator(ReadOptions()));
|
||||
|
||||
bool pushed_first_operand = false;
|
||||
bool stepped_to_next_operand = false;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBIter::MergeValuesNewToOld:PushedFirstOperand", [&](void*) {
|
||||
EXPECT_FALSE(pushed_first_operand);
|
||||
pushed_first_operand = true;
|
||||
db_->Flush(FlushOptions()); // Switch to SuperVersion B
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBIter::MergeValuesNewToOld:SteppedToNextOperand", [&](void*) {
|
||||
EXPECT_FALSE(stepped_to_next_operand);
|
||||
stepped_to_next_operand = true;
|
||||
someone_else.reset(); // Unpin SuperVersion A
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ReadOptions ro;
|
||||
ro.tailing = true;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(ro));
|
||||
iter->Seek("key");
|
||||
|
||||
ASSERT_TRUE(iter->status().ok());
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
EXPECT_EQ(std::string("sst,memtable"), iter->value().ToString());
|
||||
EXPECT_TRUE(pushed_first_operand);
|
||||
EXPECT_TRUE(stepped_to_next_operand);
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBMergeOperatorTest, SnapshotCheckerAndReadCallback) {
|
||||
Options options = CurrentOptions();
|
||||
options.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
class TestSnapshotChecker : public SnapshotChecker {
|
||||
bool IsInSnapshot(SequenceNumber seq,
|
||||
SequenceNumber snapshot_seq) const override {
|
||||
switch (snapshot_seq) {
|
||||
case 0:
|
||||
return seq == 0;
|
||||
case 1:
|
||||
return seq <= 1;
|
||||
case 2:
|
||||
// seq = 2 not visible to snapshot with seq = 2
|
||||
return seq <= 1;
|
||||
case 3:
|
||||
return seq <= 3;
|
||||
case 4:
|
||||
// seq = 4 not visible to snpahost with seq = 4
|
||||
return seq <= 3;
|
||||
default:
|
||||
// seq >=4 is uncommitted
|
||||
return seq <= 4;
|
||||
};
|
||||
}
|
||||
};
|
||||
TestSnapshotChecker* snapshot_checker = new TestSnapshotChecker();
|
||||
dbfull()->SetSnapshotChecker(snapshot_checker);
|
||||
|
||||
std::string value;
|
||||
ASSERT_OK(Merge("foo", "v1"));
|
||||
ASSERT_EQ(1, db_->GetLatestSequenceNumber());
|
||||
ASSERT_EQ("v1", GetWithReadCallback(snapshot_checker, "foo"));
|
||||
ASSERT_OK(Merge("foo", "v2"));
|
||||
ASSERT_EQ(2, db_->GetLatestSequenceNumber());
|
||||
// v2 is not visible to latest snapshot, which has seq = 2.
|
||||
ASSERT_EQ("v1", GetWithReadCallback(snapshot_checker, "foo"));
|
||||
// Take a snapshot with seq = 2.
|
||||
const Snapshot* snapshot1 = db_->GetSnapshot();
|
||||
ASSERT_EQ(2, snapshot1->GetSequenceNumber());
|
||||
// v2 is not visible to snapshot1, which has seq = 2
|
||||
ASSERT_EQ("v1", GetWithReadCallback(snapshot_checker, "foo", snapshot1));
|
||||
|
||||
// Verify flush doesn't alter the result.
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ("v1", GetWithReadCallback(snapshot_checker, "foo", snapshot1));
|
||||
ASSERT_EQ("v1", GetWithReadCallback(snapshot_checker, "foo"));
|
||||
|
||||
ASSERT_OK(Merge("foo", "v3"));
|
||||
ASSERT_EQ(3, db_->GetLatestSequenceNumber());
|
||||
ASSERT_EQ("v1,v2,v3", GetWithReadCallback(snapshot_checker, "foo"));
|
||||
ASSERT_OK(Merge("foo", "v4"));
|
||||
ASSERT_EQ(4, db_->GetLatestSequenceNumber());
|
||||
// v4 is not visible to latest snapshot, which has seq = 4.
|
||||
ASSERT_EQ("v1,v2,v3", GetWithReadCallback(snapshot_checker, "foo"));
|
||||
const Snapshot* snapshot2 = db_->GetSnapshot();
|
||||
ASSERT_EQ(4, snapshot2->GetSequenceNumber());
|
||||
// v4 is not visible to snapshot2, which has seq = 4.
|
||||
ASSERT_EQ("v1,v2,v3",
|
||||
GetWithReadCallback(snapshot_checker, "foo", snapshot2));
|
||||
|
||||
// Verify flush doesn't alter the result.
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ("v1", GetWithReadCallback(snapshot_checker, "foo", snapshot1));
|
||||
ASSERT_EQ("v1,v2,v3",
|
||||
GetWithReadCallback(snapshot_checker, "foo", snapshot2));
|
||||
ASSERT_EQ("v1,v2,v3", GetWithReadCallback(snapshot_checker, "foo"));
|
||||
|
||||
ASSERT_OK(Merge("foo", "v5"));
|
||||
ASSERT_EQ(5, db_->GetLatestSequenceNumber());
|
||||
// v5 is uncommitted
|
||||
ASSERT_EQ("v1,v2,v3,v4", GetWithReadCallback(snapshot_checker, "foo"));
|
||||
|
||||
// full manual compaction.
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
|
||||
// Verify compaction doesn't alter the result.
|
||||
ASSERT_EQ("v1", GetWithReadCallback(snapshot_checker, "foo", snapshot1));
|
||||
ASSERT_EQ("v1,v2,v3",
|
||||
GetWithReadCallback(snapshot_checker, "foo", snapshot2));
|
||||
ASSERT_EQ("v1,v2,v3,v4", GetWithReadCallback(snapshot_checker, "foo"));
|
||||
|
||||
db_->ReleaseSnapshot(snapshot1);
|
||||
db_->ReleaseSnapshot(snapshot2);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -518,8 +518,13 @@ static void assert_candidate_files_empty(DBImpl* dbfull, const bool empty) {
|
||||
JobContext job_context(0);
|
||||
dbfull->FindObsoleteFiles(&job_context, false);
|
||||
ASSERT_EQ(empty, job_context.full_scan_candidate_files.empty());
|
||||
job_context.Clean();
|
||||
dbfull->TEST_UnlockMutex();
|
||||
if (job_context.HaveSomethingToDelete()) {
|
||||
// fulfill the contract of FindObsoleteFiles by calling PurgeObsoleteFiles
|
||||
// afterwards; otherwise the test may hang on shutdown
|
||||
dbfull->PurgeObsoleteFiles(job_context);
|
||||
}
|
||||
job_context.Clean();
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, DeleteObsoleteFilesPeriodChange) {
|
||||
|
||||
@@ -809,6 +809,8 @@ int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
#else
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
+136
-26
@@ -480,6 +480,36 @@ TEST_F(DBTest, SingleDeletePutFlush) {
|
||||
kSkipUniversalCompaction | kSkipMergePut));
|
||||
}
|
||||
|
||||
// Disable because not all platform can run it.
|
||||
// It requires more than 9GB memory to run it, With single allocation
|
||||
// of more than 3GB.
|
||||
TEST_F(DBTest, DISABLED_SanitizeVeryVeryLargeValue) {
|
||||
const size_t kValueSize = 4 * size_t{1024 * 1024 * 1024}; // 4GB value
|
||||
std::string raw(kValueSize, 'v');
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.merge_operator = MergeOperators::CreatePutOperator();
|
||||
options.write_buffer_size = 100000; // Small write buffer
|
||||
options.paranoid_checks = true;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(Put("boo", "v1"));
|
||||
ASSERT_TRUE(Put("foo", raw).IsInvalidArgument());
|
||||
ASSERT_TRUE(Merge("foo", raw).IsInvalidArgument());
|
||||
|
||||
WriteBatch wb;
|
||||
ASSERT_TRUE(wb.Put("foo", raw).IsInvalidArgument());
|
||||
ASSERT_TRUE(wb.Merge("foo", raw).IsInvalidArgument());
|
||||
|
||||
Slice value_slice = raw;
|
||||
Slice key_slice = "foo";
|
||||
SliceParts sp_key(&key_slice, 1);
|
||||
SliceParts sp_value(&value_slice, 1);
|
||||
|
||||
ASSERT_TRUE(wb.Put(sp_key, sp_value).IsInvalidArgument());
|
||||
ASSERT_TRUE(wb.Merge(sp_key, sp_value).IsInvalidArgument());
|
||||
}
|
||||
|
||||
// Disable because not all platform can run it.
|
||||
// It requires more than 9GB memory to run it, With single allocation
|
||||
// of more than 3GB.
|
||||
@@ -503,7 +533,9 @@ TEST_F(DBTest, DISABLED_VeryLargeValue) {
|
||||
ASSERT_OK(Put(key2, raw));
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
std::string value;
|
||||
Status s = db_->Get(ReadOptions(), key1, &value);
|
||||
@@ -2191,6 +2223,8 @@ class ModelDB : public DB {
|
||||
batch.Put(cf, k, v);
|
||||
return Write(o, &batch);
|
||||
}
|
||||
using DB::Close;
|
||||
virtual Status Close() override { return Status::OK(); }
|
||||
using DB::Delete;
|
||||
virtual Status Delete(const WriteOptions& o, ColumnFamilyHandle* cf,
|
||||
const Slice& key) override {
|
||||
@@ -5054,55 +5088,84 @@ TEST_F(DBTest, PromoteL0Failure) {
|
||||
status = experimental::PromoteL0(db_, db_->DefaultColumnFamily());
|
||||
ASSERT_TRUE(status.IsInvalidArgument());
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// Github issue #596
|
||||
TEST_F(DBTest, HugeNumberOfLevels) {
|
||||
TEST_F(DBTest, CompactRangeWithEmptyBottomLevel) {
|
||||
const int kNumLevels = 2;
|
||||
const int kNumL0Files = 2;
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 2 * 1024 * 1024; // 2MB
|
||||
options.max_bytes_for_level_base = 2 * 1024 * 1024; // 2MB
|
||||
options.num_levels = 12;
|
||||
options.max_background_compactions = 10;
|
||||
options.max_bytes_for_level_multiplier = 2;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
options.disable_auto_compactions = true;
|
||||
options.num_levels = kNumLevels;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < 300000; ++i) {
|
||||
ASSERT_OK(Put(Key(i), RandomString(&rnd, 1024)));
|
||||
for (int i = 0; i < kNumL0Files; ++i) {
|
||||
ASSERT_OK(Put(Key(0), RandomString(&rnd, 1024)));
|
||||
Flush();
|
||||
}
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), kNumL0Files);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1), 0);
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1), kNumL0Files);
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBTest, AutomaticConflictsWithManualCompaction) {
|
||||
const int kNumL0Files = 50;
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 2 * 1024 * 1024; // 2MB
|
||||
options.max_bytes_for_level_base = 2 * 1024 * 1024; // 2MB
|
||||
options.num_levels = 12;
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
// never slowdown / stop
|
||||
options.level0_slowdown_writes_trigger = 999999;
|
||||
options.level0_stop_writes_trigger = 999999;
|
||||
options.max_background_compactions = 10;
|
||||
options.max_bytes_for_level_multiplier = 2;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < 300000; ++i) {
|
||||
ASSERT_OK(Put(Key(i), RandomString(&rnd, 1024)));
|
||||
}
|
||||
|
||||
// schedule automatic compactions after the manual one starts, but before it
|
||||
// finishes to ensure conflict.
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::BackgroundCompaction:Start",
|
||||
"DBTest::AutomaticConflictsWithManualCompaction:PrePuts"},
|
||||
{"DBTest::AutomaticConflictsWithManualCompaction:PostPuts",
|
||||
"DBImpl::BackgroundCompaction:NonTrivial:AfterRun"}});
|
||||
std::atomic<int> callback_count(0);
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction()::Conflict",
|
||||
"DBImpl::MaybeScheduleFlushOrCompaction:Conflict",
|
||||
[&](void* arg) { callback_count.fetch_add(1); });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
CompactRangeOptions croptions;
|
||||
croptions.exclusive_manual_compaction = false;
|
||||
ASSERT_OK(db_->CompactRange(croptions, nullptr, nullptr));
|
||||
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
// put two keys to ensure no trivial move
|
||||
for (int j = 0; j < 2; ++j) {
|
||||
ASSERT_OK(Put(Key(j), RandomString(&rnd, 1024)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
std::thread manual_compaction_thread([this]() {
|
||||
CompactRangeOptions croptions;
|
||||
croptions.exclusive_manual_compaction = true;
|
||||
ASSERT_OK(db_->CompactRange(croptions, nullptr, nullptr));
|
||||
});
|
||||
|
||||
TEST_SYNC_POINT("DBTest::AutomaticConflictsWithManualCompaction:PrePuts");
|
||||
for (int i = 0; i < kNumL0Files; ++i) {
|
||||
// put two keys to ensure no trivial move
|
||||
for (int j = 0; j < 2; ++j) {
|
||||
ASSERT_OK(Put(Key(j), RandomString(&rnd, 1024)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
TEST_SYNC_POINT("DBTest::AutomaticConflictsWithManualCompaction:PostPuts");
|
||||
|
||||
ASSERT_GE(callback_count.load(), 1);
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
for (int i = 0; i < 300000; ++i) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
ASSERT_NE("NOT_FOUND", Get(Key(i)));
|
||||
}
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
manual_compaction_thread.join();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
}
|
||||
|
||||
// Github issue #595
|
||||
@@ -5322,12 +5385,15 @@ class WriteStallListener : public EventListener {
|
||||
public:
|
||||
WriteStallListener() : condition_(WriteStallCondition::kNormal) {}
|
||||
void OnStallConditionsChanged(const WriteStallInfo& info) override {
|
||||
MutexLock l(&mutex_);
|
||||
condition_ = info.condition.cur;
|
||||
}
|
||||
bool CheckCondition(WriteStallCondition expected) {
|
||||
MutexLock l(&mutex_);
|
||||
return expected == condition_;
|
||||
}
|
||||
private:
|
||||
port::Mutex mutex_;
|
||||
WriteStallCondition condition_;
|
||||
};
|
||||
|
||||
@@ -5656,6 +5722,50 @@ TEST_F(DBTest, PauseBackgroundWorkTest) {
|
||||
// now it's done
|
||||
ASSERT_TRUE(done.load());
|
||||
}
|
||||
|
||||
// Keep spawning short-living threads that create an iterator and quit.
|
||||
// Meanwhile in another thread keep flushing memtables.
|
||||
// This used to cause a deadlock.
|
||||
TEST_F(DBTest, ThreadLocalPtrDeadlock) {
|
||||
std::atomic<int> flushes_done{0};
|
||||
std::atomic<int> threads_destroyed{0};
|
||||
auto done = [&] {
|
||||
return flushes_done.load() > 10;
|
||||
};
|
||||
|
||||
std::thread flushing_thread([&] {
|
||||
for (int i = 0; !done(); ++i) {
|
||||
ASSERT_OK(db_->Put(WriteOptions(), Slice("hi"),
|
||||
Slice(std::to_string(i).c_str())));
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
int cnt = ++flushes_done;
|
||||
fprintf(stderr, "Flushed %d times\n", cnt);
|
||||
}
|
||||
});
|
||||
|
||||
std::vector<std::thread> thread_spawning_threads(10);
|
||||
for (auto& t: thread_spawning_threads) {
|
||||
t = std::thread([&] {
|
||||
while (!done()) {
|
||||
{
|
||||
std::thread tmp_thread([&] {
|
||||
auto it = db_->NewIterator(ReadOptions());
|
||||
delete it;
|
||||
});
|
||||
tmp_thread.join();
|
||||
}
|
||||
++threads_destroyed;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (auto& t: thread_spawning_threads) {
|
||||
t.join();
|
||||
}
|
||||
flushing_thread.join();
|
||||
fprintf(stderr, "Done. Flushed %d times, destroyed %d threads\n",
|
||||
flushes_done.load(), threads_destroyed.load());
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+65
-5
@@ -1820,11 +1820,17 @@ TEST_F(DBTest2, ReadAmpBitmap) {
|
||||
|
||||
#ifndef OS_SOLARIS // GetUniqueIdFromFile is not implemented
|
||||
TEST_F(DBTest2, ReadAmpBitmapLiveInCacheAfterDBClose) {
|
||||
if (dbname_.find("dev/shm") != std::string::npos) {
|
||||
// /dev/shm dont support getting a unique file id, this mean that
|
||||
// running this test on /dev/shm will fail because lru_cache will load
|
||||
// the blocks again regardless of them being already in the cache
|
||||
return;
|
||||
{
|
||||
const int kIdBufLen = 100;
|
||||
char id_buf[kIdBufLen];
|
||||
std::unique_ptr<RandomAccessFile> file;
|
||||
env_->NewRandomAccessFile(dbname_, &file, EnvOptions());
|
||||
if (file->GetUniqueId(id_buf, kIdBufLen) == 0) {
|
||||
// fs holding db directory doesn't support getting a unique file id,
|
||||
// this means that running this test will fail because lru_cache will load
|
||||
// the blocks again regardless of them being already in the cache
|
||||
return;
|
||||
}
|
||||
}
|
||||
uint32_t bytes_per_bit[2] = {1, 16};
|
||||
for (size_t k = 0; k < 2; k++) {
|
||||
@@ -2430,6 +2436,60 @@ TEST_F(DBTest2, ReadCallbackTest) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBTest2, LiveFilesOmitObsoleteFiles) {
|
||||
// Regression test for race condition where an obsolete file is returned to
|
||||
// user as a "live file" but then deleted, all while file deletions are
|
||||
// disabled.
|
||||
//
|
||||
// It happened like this:
|
||||
//
|
||||
// 1. [flush thread] Log file "x.log" found by FindObsoleteFiles
|
||||
// 2. [user thread] DisableFileDeletions, GetSortedWalFiles are called and the
|
||||
// latter returned "x.log"
|
||||
// 3. [flush thread] PurgeObsoleteFiles deleted "x.log"
|
||||
// 4. [user thread] Reading "x.log" failed
|
||||
//
|
||||
// Unfortunately the only regression test I can come up with involves sleep.
|
||||
// We cannot set SyncPoints to repro since, once the fix is applied, the
|
||||
// SyncPoints would cause a deadlock as the repro's sequence of events is now
|
||||
// prohibited.
|
||||
//
|
||||
// Instead, if we sleep for a second between Find and Purge, and ensure the
|
||||
// read attempt happens after purge, then the sequence of events will almost
|
||||
// certainly happen on the old code.
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency({
|
||||
{"DBImpl::BackgroundCallFlush:FilesFound",
|
||||
"DBTest2::LiveFilesOmitObsoleteFiles:FlushTriggered"},
|
||||
{"DBImpl::PurgeObsoleteFiles:End",
|
||||
"DBTest2::LiveFilesOmitObsoleteFiles:LiveFilesCaptured"},
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::PurgeObsoleteFiles:Begin",
|
||||
[&](void* arg) { env_->SleepForMicroseconds(1000000); });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Put("key", "val");
|
||||
FlushOptions flush_opts;
|
||||
flush_opts.wait = false;
|
||||
db_->Flush(flush_opts);
|
||||
TEST_SYNC_POINT("DBTest2::LiveFilesOmitObsoleteFiles:FlushTriggered");
|
||||
|
||||
db_->DisableFileDeletions();
|
||||
VectorLogPtr log_files;
|
||||
db_->GetSortedWalFiles(log_files);
|
||||
TEST_SYNC_POINT("DBTest2::LiveFilesOmitObsoleteFiles:LiveFilesCaptured");
|
||||
for (const auto& log_file : log_files) {
|
||||
ASSERT_OK(env_->FileExists(LogFileName(dbname_, log_file->LogNumber())));
|
||||
}
|
||||
|
||||
db_->EnableFileDeletions();
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+3
-1
@@ -867,7 +867,6 @@ size_t DBTestBase::CountLiveFiles() {
|
||||
db_->GetLiveFilesMetaData(&metadata);
|
||||
return metadata.size();
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
int DBTestBase::NumTableFilesAtLevel(int level, int cf) {
|
||||
std::string property;
|
||||
@@ -928,6 +927,7 @@ std::string DBTestBase::FilesPerLevel(int cf) {
|
||||
result.resize(last_non_zero_offset);
|
||||
return result;
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
size_t DBTestBase::CountFiles() {
|
||||
std::vector<std::string> files;
|
||||
@@ -997,6 +997,7 @@ void DBTestBase::MoveFilesToLevel(int level, int cf) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
void DBTestBase::DumpFileCounts(const char* label) {
|
||||
fprintf(stderr, "---\n%s:\n", label);
|
||||
fprintf(stderr, "maxoverlap: %" PRIu64 "\n",
|
||||
@@ -1008,6 +1009,7 @@ void DBTestBase::DumpFileCounts(const char* label) {
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
std::string DBTestBase::DumpSSTableList() {
|
||||
std::string property;
|
||||
|
||||
+4
-2
@@ -137,8 +137,8 @@ class SpecialMemTableRep : public MemTableRep {
|
||||
// Insert key into the list.
|
||||
// REQUIRES: nothing that compares equal to key is currently in the list.
|
||||
virtual void Insert(KeyHandle handle) override {
|
||||
memtable_->Insert(handle);
|
||||
num_entries_++;
|
||||
memtable_->Insert(handle);
|
||||
}
|
||||
|
||||
// Returns true iff an entry that compares equal to key is in the list.
|
||||
@@ -863,13 +863,13 @@ class DBTestBase : public testing::Test {
|
||||
size_t TotalLiveFiles(int cf = 0);
|
||||
|
||||
size_t CountLiveFiles();
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
int NumTableFilesAtLevel(int level, int cf = 0);
|
||||
|
||||
double CompressionRatioAtLevel(int level, int cf = 0);
|
||||
|
||||
int TotalTableFiles(int cf = 0, int levels = -1);
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// Return spread of files per level
|
||||
std::string FilesPerLevel(int cf = 0);
|
||||
@@ -897,7 +897,9 @@ class DBTestBase : public testing::Test {
|
||||
|
||||
void MoveFilesToLevel(int level, int cf = 0);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
void DumpFileCounts(const char* label);
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
std::string DumpSSTableList();
|
||||
|
||||
|
||||
@@ -388,10 +388,16 @@ TEST_P(DBTestUniversalCompaction, DynamicUniversalCompactionSizeAmplification) {
|
||||
DestroyAndReopen(options);
|
||||
|
||||
int total_picked_compactions = 0;
|
||||
int total_size_amp_compactions = 0;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"UniversalCompactionPicker::PickCompaction:Return", [&](void* arg) {
|
||||
if (arg) {
|
||||
total_picked_compactions++;
|
||||
Compaction* c = static_cast<Compaction*>(arg);
|
||||
if (c->compaction_reason() ==
|
||||
CompactionReason::kUniversalSizeAmplification) {
|
||||
total_size_amp_compactions++;
|
||||
}
|
||||
}
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
@@ -445,6 +451,102 @@ TEST_P(DBTestUniversalCompaction, DynamicUniversalCompactionSizeAmplification) {
|
||||
// Verify that size amplification did happen
|
||||
ASSERT_EQ(NumSortedRuns(1), 1);
|
||||
ASSERT_EQ(total_picked_compactions, 1);
|
||||
ASSERT_EQ(total_size_amp_compactions, 1);
|
||||
}
|
||||
|
||||
TEST_P(DBTestUniversalCompaction, DynamicUniversalCompactionReadAmplification) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.num_levels = 1;
|
||||
options.write_buffer_size = 100 << 10; // 100KB
|
||||
options.target_file_size_base = 32 << 10; // 32KB
|
||||
options.level0_file_num_compaction_trigger = 3;
|
||||
// Initial setup of compaction_options_universal will prevent universal
|
||||
// compaction from happening
|
||||
options.compaction_options_universal.max_size_amplification_percent = 2000;
|
||||
options.compaction_options_universal.size_ratio = 0;
|
||||
options.compaction_options_universal.min_merge_width = 100;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
int total_picked_compactions = 0;
|
||||
int total_size_ratio_compactions = 0;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"UniversalCompactionPicker::PickCompaction:Return", [&](void* arg) {
|
||||
if (arg) {
|
||||
total_picked_compactions++;
|
||||
Compaction* c = static_cast<Compaction*>(arg);
|
||||
if (c->compaction_reason() == CompactionReason::kUniversalSizeRatio) {
|
||||
total_size_ratio_compactions++;
|
||||
}
|
||||
}
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
MutableCFOptions mutable_cf_options;
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
Random rnd(301);
|
||||
int key_idx = 0;
|
||||
|
||||
// Generate three files in Level 0. All files are approx the same size.
|
||||
for (int num = 0; num < options.level0_file_num_compaction_trigger; num++) {
|
||||
// Write 110KB (11 values, each 10K)
|
||||
for (int i = 0; i < 11; i++) {
|
||||
ASSERT_OK(Put(1, Key(key_idx), RandomString(&rnd, 10000)));
|
||||
key_idx++;
|
||||
}
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
||||
ASSERT_EQ(NumSortedRuns(1), num + 1);
|
||||
}
|
||||
ASSERT_EQ(NumSortedRuns(1), options.level0_file_num_compaction_trigger);
|
||||
|
||||
// Flush whatever is remaining in memtable. This is typically small, about
|
||||
// 30KB.
|
||||
ASSERT_OK(Flush(1));
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
// Verify compaction did not happen
|
||||
ASSERT_EQ(NumSortedRuns(1), options.level0_file_num_compaction_trigger + 1);
|
||||
ASSERT_EQ(total_picked_compactions, 0);
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
handles_[1],
|
||||
{{"compaction_options_universal",
|
||||
"{min_merge_width=2;max_merge_width=2;size_ratio=100;}"}}));
|
||||
ASSERT_EQ(dbfull()
|
||||
->GetOptions(handles_[1])
|
||||
.compaction_options_universal.min_merge_width,
|
||||
2);
|
||||
ASSERT_EQ(dbfull()
|
||||
->GetOptions(handles_[1])
|
||||
.compaction_options_universal.max_merge_width,
|
||||
2);
|
||||
ASSERT_EQ(
|
||||
dbfull()->GetOptions(handles_[1]).compaction_options_universal.size_ratio,
|
||||
100);
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_GetLatestMutableCFOptions(handles_[1],
|
||||
&mutable_cf_options));
|
||||
ASSERT_EQ(mutable_cf_options.compaction_options_universal.size_ratio, 100);
|
||||
ASSERT_EQ(mutable_cf_options.compaction_options_universal.min_merge_width, 2);
|
||||
ASSERT_EQ(mutable_cf_options.compaction_options_universal.max_merge_width, 2);
|
||||
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
|
||||
// Files in L0 are approx: 0.3 (30KB), 1, 1, 1.
|
||||
// On compaction: the files are below the size amp threshold, so we
|
||||
// fallthrough to checking read amp conditions. The configured size ratio is
|
||||
// not big enough to take 0.3 into consideration. So the next files 1 and 1
|
||||
// are compacted together first as they satisfy size ratio condition and
|
||||
// (min_merge_width, max_merge_width) condition, to give out a file size of 2.
|
||||
// Next, the newly generated 2 and the last file 1 are compacted together. So
|
||||
// at the end: #sortedRuns = 2, #picked_compactions = 2, and all the picked
|
||||
// ones are size ratio based compactions.
|
||||
ASSERT_EQ(NumSortedRuns(1), 2);
|
||||
// If max_merge_width had not been changed dynamically above, and if it
|
||||
// continued to be the default value of UINIT_MAX, total_picked_compactions
|
||||
// would have been 1.
|
||||
ASSERT_EQ(total_picked_compactions, 2);
|
||||
ASSERT_EQ(total_size_ratio_compactions, 2);
|
||||
}
|
||||
|
||||
TEST_P(DBTestUniversalCompaction, CompactFilesOnUniversalCompaction) {
|
||||
@@ -1657,6 +1759,8 @@ int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
#else
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
@@ -125,6 +125,26 @@ int InternalKeyComparator::Compare(const Slice& akey, const Slice& bkey) const {
|
||||
return r;
|
||||
}
|
||||
|
||||
int InternalKeyComparator::CompareKeySeq(const Slice& akey,
|
||||
const Slice& bkey) const {
|
||||
// Order by:
|
||||
// increasing user key (according to user-supplied comparator)
|
||||
// decreasing sequence number
|
||||
int r = user_comparator_->Compare(ExtractUserKey(akey), ExtractUserKey(bkey));
|
||||
PERF_COUNTER_ADD(user_key_comparison_count, 1);
|
||||
if (r == 0) {
|
||||
// Shift the number to exclude the last byte which contains the value type
|
||||
const uint64_t anum = DecodeFixed64(akey.data() + akey.size() - 8) >> 8;
|
||||
const uint64_t bnum = DecodeFixed64(bkey.data() + bkey.size() - 8) >> 8;
|
||||
if (anum > bnum) {
|
||||
r = -1;
|
||||
} else if (anum < bnum) {
|
||||
r = +1;
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
int InternalKeyComparator::Compare(const ParsedInternalKey& a,
|
||||
const ParsedInternalKey& b) const {
|
||||
// Order by:
|
||||
|
||||
@@ -162,6 +162,8 @@ class InternalKeyComparator
|
||||
|
||||
virtual const char* Name() const override;
|
||||
virtual int Compare(const Slice& a, const Slice& b) const override;
|
||||
// Same as Compare except that it excludes the value type from comparison
|
||||
virtual int CompareKeySeq(const Slice& a, const Slice& b) const;
|
||||
virtual void FindShortestSeparator(std::string* start,
|
||||
const Slice& limit) const override;
|
||||
virtual void FindShortSuccessor(std::string* key) const override;
|
||||
|
||||
@@ -500,7 +500,7 @@ int main(int argc, char** argv) {
|
||||
#else
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
int main(int /*argc*/, char** /*argv*/) {
|
||||
fprintf(stderr,
|
||||
"SKIPPED as DBImpl::DeleteFile is not supported in ROCKSDB_LITE\n");
|
||||
return 0;
|
||||
|
||||
@@ -132,11 +132,15 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
return status;
|
||||
}
|
||||
|
||||
Status ExternalSstFileIngestionJob::NeedsFlush(bool* flush_needed) {
|
||||
SuperVersion* super_version = cfd_->GetSuperVersion();
|
||||
Status ExternalSstFileIngestionJob::NeedsFlush(bool* flush_needed,
|
||||
SuperVersion* super_version) {
|
||||
autovector<Range> ranges;
|
||||
for (const IngestedFileInfo& file_to_ingest : files_to_ingest_) {
|
||||
ranges.emplace_back(file_to_ingest.smallest_user_key,
|
||||
file_to_ingest.largest_user_key);
|
||||
}
|
||||
Status status =
|
||||
IngestedFilesOverlapWithMemtables(super_version, flush_needed);
|
||||
|
||||
cfd_->RangesOverlapWithMemtables(ranges, super_version, flush_needed);
|
||||
if (status.ok() && *flush_needed &&
|
||||
!ingestion_options_.allow_blocking_flush) {
|
||||
status = Status::InvalidArgument("External file requires flush");
|
||||
@@ -148,11 +152,12 @@ Status ExternalSstFileIngestionJob::NeedsFlush(bool* flush_needed) {
|
||||
// nonmem_write_thread_
|
||||
Status ExternalSstFileIngestionJob::Run() {
|
||||
Status status;
|
||||
SuperVersion* super_version = cfd_->GetSuperVersion();
|
||||
#ifndef NDEBUG
|
||||
// We should never run the job with a memtable that is overlapping
|
||||
// with the files we are ingesting
|
||||
bool need_flush = false;
|
||||
status = NeedsFlush(&need_flush);
|
||||
status = NeedsFlush(&need_flush, super_version);
|
||||
assert(status.ok() && need_flush == false);
|
||||
#endif
|
||||
|
||||
@@ -167,7 +172,6 @@ Status ExternalSstFileIngestionJob::Run() {
|
||||
// It is safe to use this instead of LastAllocatedSequence since we are
|
||||
// the only active writer, and hence they are equal
|
||||
const SequenceNumber last_seqno = versions_->LastSequence();
|
||||
SuperVersion* super_version = cfd_->GetSuperVersion();
|
||||
edit_.SetColumnFamily(cfd_->GetID());
|
||||
// The levels that the files will be ingested into
|
||||
|
||||
@@ -375,54 +379,6 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
return status;
|
||||
}
|
||||
|
||||
Status ExternalSstFileIngestionJob::IngestedFilesOverlapWithMemtables(
|
||||
SuperVersion* sv, bool* overlap) {
|
||||
*overlap = false;
|
||||
// Create an InternalIterator over all memtables
|
||||
Arena arena;
|
||||
ReadOptions ro;
|
||||
ro.total_order_seek = true;
|
||||
MergeIteratorBuilder merge_iter_builder(&cfd_->internal_comparator(), &arena);
|
||||
merge_iter_builder.AddIterator(sv->mem->NewIterator(ro, &arena));
|
||||
sv->imm->AddIterators(ro, &merge_iter_builder);
|
||||
ScopedArenaIterator memtable_iter(merge_iter_builder.Finish());
|
||||
|
||||
std::vector<InternalIterator*> memtable_range_del_iters;
|
||||
auto* active_range_del_iter = sv->mem->NewRangeTombstoneIterator(ro);
|
||||
if (active_range_del_iter != nullptr) {
|
||||
memtable_range_del_iters.push_back(active_range_del_iter);
|
||||
}
|
||||
sv->imm->AddRangeTombstoneIterators(ro, &memtable_range_del_iters);
|
||||
RangeDelAggregator range_del_agg(cfd_->internal_comparator(),
|
||||
{} /* snapshots */,
|
||||
false /* collapse_deletions */);
|
||||
Status status;
|
||||
{
|
||||
std::unique_ptr<InternalIterator> memtable_range_del_iter(
|
||||
NewMergingIterator(&cfd_->internal_comparator(),
|
||||
memtable_range_del_iters.empty()
|
||||
? nullptr
|
||||
: &memtable_range_del_iters[0],
|
||||
static_cast<int>(memtable_range_del_iters.size())));
|
||||
status = range_del_agg.AddTombstones(std::move(memtable_range_del_iter));
|
||||
}
|
||||
if (status.ok()) {
|
||||
for (IngestedFileInfo& f : files_to_ingest_) {
|
||||
status = IngestedFileOverlapWithIteratorRange(&f, memtable_iter.get(),
|
||||
overlap);
|
||||
if (!status.ok() || *overlap == true) {
|
||||
break;
|
||||
}
|
||||
if (range_del_agg.IsRangeOverlapped(f.smallest_user_key,
|
||||
f.largest_user_key)) {
|
||||
*overlap = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile(
|
||||
SuperVersion* sv, bool force_global_seqno, CompactionStyle compaction_style,
|
||||
IngestedFileInfo* file_to_ingest, SequenceNumber* assigned_seqno) {
|
||||
@@ -473,7 +429,10 @@ Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile(
|
||||
return f1->largest_seqno < f2->largest_seqno;
|
||||
}))
|
||||
->largest_seqno;
|
||||
if (level_largest_seqno != 0) {
|
||||
// should only assign seqno to current level's largest seqno when
|
||||
// the file fits
|
||||
if (level_largest_seqno != 0 &&
|
||||
IngestedFileFitInLevel(file_to_ingest, lvl)) {
|
||||
*assigned_seqno = level_largest_seqno;
|
||||
} else {
|
||||
continue;
|
||||
@@ -550,6 +509,9 @@ Status ExternalSstFileIngestionJob::AssignGlobalSeqnoForIngestedFile(
|
||||
std::string seqno_val;
|
||||
PutFixed64(&seqno_val, seqno);
|
||||
status = rwfile->Write(file_to_ingest->global_seqno_offset, seqno_val);
|
||||
if (status.ok()) {
|
||||
status = rwfile->Fsync();
|
||||
}
|
||||
if (status.ok()) {
|
||||
file_to_ingest->assigned_seqno = seqno;
|
||||
}
|
||||
|
||||
@@ -84,8 +84,12 @@ class ExternalSstFileIngestionJob {
|
||||
// Check if we need to flush the memtable before running the ingestion job
|
||||
// This will be true if the files we are ingesting are overlapping with any
|
||||
// key range in the memtable.
|
||||
// REQUIRES: Mutex held
|
||||
Status NeedsFlush(bool* flush_needed);
|
||||
//
|
||||
// @param super_version A referenced SuperVersion that will be held for the
|
||||
// duration of this function.
|
||||
//
|
||||
// Thread-safe
|
||||
Status NeedsFlush(bool* flush_needed, SuperVersion* super_version);
|
||||
|
||||
// Will execute the ingestion job and prepare edit() to be applied.
|
||||
// REQUIRES: Mutex held
|
||||
@@ -110,10 +114,6 @@ class ExternalSstFileIngestionJob {
|
||||
Status GetIngestedFileInfo(const std::string& external_file,
|
||||
IngestedFileInfo* file_to_ingest);
|
||||
|
||||
// Check if the files we are ingesting overlap with any memtable.
|
||||
// REQUIRES: Mutex held
|
||||
Status IngestedFilesOverlapWithMemtables(SuperVersion* sv, bool* overlap);
|
||||
|
||||
// Assign `file_to_ingest` the appropriate sequence number and the lowest
|
||||
// possible level that it can be ingested to according to compaction_style.
|
||||
// REQUIRES: Mutex held
|
||||
|
||||
@@ -388,9 +388,10 @@ class SstFileWriterCollector : public TablePropertiesCollector {
|
||||
const char* Name() const override { return name_.c_str(); }
|
||||
|
||||
Status Finish(UserCollectedProperties* properties) override {
|
||||
std::string count = std::to_string(count_);
|
||||
*properties = UserCollectedProperties{
|
||||
{prefix_ + "_SstFileWriterCollector", "YES"},
|
||||
{prefix_ + "_Count", std::to_string(count_)},
|
||||
{prefix_ + "_Count", count},
|
||||
};
|
||||
return Status::OK();
|
||||
}
|
||||
@@ -1940,6 +1941,55 @@ TEST_F(ExternalSSTFileTest, IngestBehind) {
|
||||
size_t kcnt = 0;
|
||||
VerifyDBFromMap(true_data, &kcnt, false);
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileTest, SkipBloomFilter) {
|
||||
Options options = CurrentOptions();
|
||||
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(10));
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
|
||||
// Create external SST file and include bloom filters
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
DestroyAndReopen(options);
|
||||
{
|
||||
std::string file_path = sst_files_dir_ + "sst_with_bloom.sst";
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
ASSERT_OK(sst_file_writer.Open(file_path));
|
||||
ASSERT_OK(sst_file_writer.Put("Key1", "Value1"));
|
||||
ASSERT_OK(sst_file_writer.Finish());
|
||||
|
||||
ASSERT_OK(
|
||||
db_->IngestExternalFile({file_path}, IngestExternalFileOptions()));
|
||||
|
||||
ASSERT_EQ(Get("Key1"), "Value1");
|
||||
ASSERT_GE(
|
||||
options.statistics->getTickerCount(Tickers::BLOCK_CACHE_FILTER_ADD), 1);
|
||||
}
|
||||
|
||||
// Create external SST file but skip bloom filters
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
DestroyAndReopen(options);
|
||||
{
|
||||
std::string file_path = sst_files_dir_ + "sst_with_no_bloom.sst";
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options, nullptr, true,
|
||||
Env::IOPriority::IO_TOTAL,
|
||||
true /* skip_filters */);
|
||||
ASSERT_OK(sst_file_writer.Open(file_path));
|
||||
ASSERT_OK(sst_file_writer.Put("Key1", "Value1"));
|
||||
ASSERT_OK(sst_file_writer.Finish());
|
||||
|
||||
ASSERT_OK(
|
||||
db_->IngestExternalFile({file_path}, IngestExternalFileOptions()));
|
||||
|
||||
ASSERT_EQ(Get("Key1"), "Value1");
|
||||
ASSERT_EQ(
|
||||
options.statistics->getTickerCount(Tickers::BLOCK_CACHE_FILTER_ADD), 0);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
@@ -1951,7 +2001,7 @@ int main(int argc, char** argv) {
|
||||
#else
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
int main(int /*argc*/, char** /*argv*/) {
|
||||
fprintf(stderr,
|
||||
"SKIPPED as External SST File Writer and Ingestion are not supported "
|
||||
"in ROCKSDB_LITE\n");
|
||||
|
||||
@@ -228,16 +228,9 @@ class FaultInjectionTest : public testing::Test,
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
#ifdef ROCKSDB_UBSAN_RUN
|
||||
#if defined(__clang__)
|
||||
__attribute__((__no_sanitize__("shift"), no_sanitize("signed-integer-overflow")))
|
||||
#elif defined(__GNUC__)
|
||||
__attribute__((__no_sanitize_undefined__))
|
||||
#endif
|
||||
#endif
|
||||
// Return the ith key
|
||||
Slice Key(int i, std::string* storage) const {
|
||||
int num = i;
|
||||
unsigned long long num = i;
|
||||
if (!sequential_order_) {
|
||||
// random transfer
|
||||
const int m = 0x5bd1e995;
|
||||
@@ -245,7 +238,7 @@ __attribute__((__no_sanitize_undefined__))
|
||||
num ^= num << 24;
|
||||
}
|
||||
char buf[100];
|
||||
snprintf(buf, sizeof(buf), "%016d", num);
|
||||
snprintf(buf, sizeof(buf), "%016d", static_cast<int>(num));
|
||||
storage->assign(buf, strlen(buf));
|
||||
return Slice(*storage);
|
||||
}
|
||||
|
||||
+33
-6
@@ -54,6 +54,32 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
const char* GetFlushReasonString (FlushReason flush_reason) {
|
||||
switch (flush_reason) {
|
||||
case FlushReason::kUnknown:
|
||||
return "Unknown";
|
||||
case FlushReason::kGetLiveFiles:
|
||||
return "Get Live Files";
|
||||
case FlushReason::kShutDown:
|
||||
return "Shut down";
|
||||
case FlushReason::kExternalFileIngestion:
|
||||
return "External File Ingestion";
|
||||
case FlushReason::kManualCompaction:
|
||||
return "Manual Compaction";
|
||||
case FlushReason::kWriteBufferManager:
|
||||
return "Write Buffer Manager";
|
||||
case FlushReason::kWriteBufferFull:
|
||||
return "Write Buffer Full";
|
||||
case FlushReason::kTest:
|
||||
return "Test";
|
||||
case FlushReason::kSuperVersionChange:
|
||||
return "SuperVersion Change";
|
||||
default:
|
||||
return "Invalid";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
FlushJob::FlushJob(const std::string& dbname, ColumnFamilyData* cfd,
|
||||
const ImmutableDBOptions& db_options,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
@@ -278,12 +304,13 @@ Status FlushJob::WriteLevel0Table() {
|
||||
total_memory_usage += m->ApproximateMemoryUsage();
|
||||
}
|
||||
|
||||
event_logger_->Log() << "job" << job_context_->job_id << "event"
|
||||
<< "flush_started"
|
||||
<< "num_memtables" << mems_.size() << "num_entries"
|
||||
<< total_num_entries << "num_deletes"
|
||||
<< total_num_deletes << "memory_usage"
|
||||
<< total_memory_usage;
|
||||
event_logger_->Log()
|
||||
<< "job" << job_context_->job_id << "event"
|
||||
<< "flush_started"
|
||||
<< "num_memtables" << mems_.size() << "num_entries" << total_num_entries
|
||||
<< "num_deletes" << total_num_deletes << "memory_usage"
|
||||
<< total_memory_usage << "flush_reason"
|
||||
<< GetFlushReasonString(cfd_->GetFlushReason());
|
||||
|
||||
{
|
||||
ScopedArenaIterator iter(
|
||||
|
||||
+70
-50
@@ -25,15 +25,15 @@
|
||||
namespace rocksdb {
|
||||
|
||||
// Usage:
|
||||
// LevelIterator iter;
|
||||
// ForwardLevelIterator iter;
|
||||
// iter.SetFileIndex(file_index);
|
||||
// iter.Seek(target);
|
||||
// iter.Next()
|
||||
class LevelIterator : public InternalIterator {
|
||||
class ForwardLevelIterator : public InternalIterator {
|
||||
public:
|
||||
LevelIterator(const ColumnFamilyData* const cfd,
|
||||
const ReadOptions& read_options,
|
||||
const std::vector<FileMetaData*>& files)
|
||||
ForwardLevelIterator(const ColumnFamilyData* const cfd,
|
||||
const ReadOptions& read_options,
|
||||
const std::vector<FileMetaData*>& files)
|
||||
: cfd_(cfd),
|
||||
read_options_(read_options),
|
||||
files_(files),
|
||||
@@ -42,7 +42,7 @@ class LevelIterator : public InternalIterator {
|
||||
file_iter_(nullptr),
|
||||
pinned_iters_mgr_(nullptr) {}
|
||||
|
||||
~LevelIterator() {
|
||||
~ForwardLevelIterator() {
|
||||
// Reset current pointer
|
||||
if (pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled()) {
|
||||
pinned_iters_mgr_->PinIterator(file_iter_);
|
||||
@@ -84,11 +84,11 @@ class LevelIterator : public InternalIterator {
|
||||
}
|
||||
}
|
||||
void SeekToLast() override {
|
||||
status_ = Status::NotSupported("LevelIterator::SeekToLast()");
|
||||
status_ = Status::NotSupported("ForwardLevelIterator::SeekToLast()");
|
||||
valid_ = false;
|
||||
}
|
||||
void Prev() override {
|
||||
status_ = Status::NotSupported("LevelIterator::Prev()");
|
||||
status_ = Status::NotSupported("ForwardLevelIterator::Prev()");
|
||||
valid_ = false;
|
||||
}
|
||||
bool Valid() const override {
|
||||
@@ -105,7 +105,7 @@ class LevelIterator : public InternalIterator {
|
||||
valid_ = file_iter_->Valid();
|
||||
}
|
||||
void SeekForPrev(const Slice& internal_key) override {
|
||||
status_ = Status::NotSupported("LevelIterator::SeekForPrev()");
|
||||
status_ = Status::NotSupported("ForwardLevelIterator::SeekForPrev()");
|
||||
valid_ = false;
|
||||
}
|
||||
void Next() override {
|
||||
@@ -196,38 +196,60 @@ ForwardIterator::~ForwardIterator() {
|
||||
Cleanup(true);
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Used in PinnedIteratorsManager to release pinned SuperVersion
|
||||
static void ReleaseSuperVersionFunc(void* sv) {
|
||||
delete reinterpret_cast<SuperVersion*>(sv);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void ForwardIterator::SVCleanup() {
|
||||
if (sv_ != nullptr && sv_->Unref()) {
|
||||
void ForwardIterator::SVCleanup(DBImpl* db, SuperVersion* sv,
|
||||
bool background_purge_on_iterator_cleanup) {
|
||||
if (sv->Unref()) {
|
||||
// Job id == 0 means that this is not our background process, but rather
|
||||
// user thread
|
||||
JobContext job_context(0);
|
||||
db_->mutex_.Lock();
|
||||
sv_->Cleanup();
|
||||
db_->FindObsoleteFiles(&job_context, false, true);
|
||||
if (read_options_.background_purge_on_iterator_cleanup) {
|
||||
db_->ScheduleBgLogWriterClose(&job_context);
|
||||
}
|
||||
db_->mutex_.Unlock();
|
||||
if (pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled()) {
|
||||
pinned_iters_mgr_->PinPtr(sv_, &ReleaseSuperVersionFunc);
|
||||
} else {
|
||||
delete sv_;
|
||||
db->mutex_.Lock();
|
||||
sv->Cleanup();
|
||||
db->FindObsoleteFiles(&job_context, false, true);
|
||||
if (background_purge_on_iterator_cleanup) {
|
||||
db->ScheduleBgLogWriterClose(&job_context);
|
||||
}
|
||||
db->mutex_.Unlock();
|
||||
delete sv;
|
||||
if (job_context.HaveSomethingToDelete()) {
|
||||
db_->PurgeObsoleteFiles(
|
||||
job_context, read_options_.background_purge_on_iterator_cleanup);
|
||||
db->PurgeObsoleteFiles(job_context, background_purge_on_iterator_cleanup);
|
||||
}
|
||||
job_context.Clean();
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct SVCleanupParams {
|
||||
DBImpl* db;
|
||||
SuperVersion* sv;
|
||||
bool background_purge_on_iterator_cleanup;
|
||||
};
|
||||
}
|
||||
|
||||
// Used in PinnedIteratorsManager to release pinned SuperVersion
|
||||
void ForwardIterator::DeferredSVCleanup(void* arg) {
|
||||
auto d = reinterpret_cast<SVCleanupParams*>(arg);
|
||||
ForwardIterator::SVCleanup(
|
||||
d->db, d->sv, d->background_purge_on_iterator_cleanup);
|
||||
delete d;
|
||||
}
|
||||
|
||||
void ForwardIterator::SVCleanup() {
|
||||
if (sv_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled()) {
|
||||
// pinned_iters_mgr_ tells us to make sure that all visited key-value slices
|
||||
// are alive until pinned_iters_mgr_->ReleasePinnedData() is called.
|
||||
// The slices may point into some memtables owned by sv_, so we need to keep
|
||||
// sv_ referenced until pinned_iters_mgr_ unpins everything.
|
||||
auto p = new SVCleanupParams{
|
||||
db_, sv_, read_options_.background_purge_on_iterator_cleanup};
|
||||
pinned_iters_mgr_->PinPtr(p, &ForwardIterator::DeferredSVCleanup);
|
||||
} else {
|
||||
SVCleanup(db_, sv_, read_options_.background_purge_on_iterator_cleanup);
|
||||
}
|
||||
}
|
||||
|
||||
void ForwardIterator::Cleanup(bool release_sv) {
|
||||
if (mutable_iter_ != nullptr) {
|
||||
DeleteIterator(mutable_iter_, true /* is_arena */);
|
||||
@@ -361,14 +383,13 @@ void ForwardIterator::SeekInternal(const Slice& internal_key,
|
||||
|
||||
if (!l0_iters_[i]->status().ok()) {
|
||||
immutable_status_ = l0_iters_[i]->status();
|
||||
} else if (l0_iters_[i]->Valid()) {
|
||||
if (!IsOverUpperBound(l0_iters_[i]->key())) {
|
||||
immutable_min_heap_.push(l0_iters_[i]);
|
||||
} else {
|
||||
has_iter_trimmed_for_upper_bound_ = true;
|
||||
DeleteIterator(l0_iters_[i]);
|
||||
l0_iters_[i] = nullptr;
|
||||
}
|
||||
} else if (l0_iters_[i]->Valid() &&
|
||||
!IsOverUpperBound(l0_iters_[i]->key())) {
|
||||
immutable_min_heap_.push(l0_iters_[i]);
|
||||
} else {
|
||||
has_iter_trimmed_for_upper_bound_ = true;
|
||||
DeleteIterator(l0_iters_[i]);
|
||||
l0_iters_[i] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,15 +416,14 @@ void ForwardIterator::SeekInternal(const Slice& internal_key,
|
||||
|
||||
if (!level_iters_[level - 1]->status().ok()) {
|
||||
immutable_status_ = level_iters_[level - 1]->status();
|
||||
} else if (level_iters_[level - 1]->Valid()) {
|
||||
if (!IsOverUpperBound(level_iters_[level - 1]->key())) {
|
||||
immutable_min_heap_.push(level_iters_[level - 1]);
|
||||
} else {
|
||||
// Nothing in this level is interesting. Remove.
|
||||
has_iter_trimmed_for_upper_bound_ = true;
|
||||
DeleteIterator(level_iters_[level - 1]);
|
||||
level_iters_[level - 1] = nullptr;
|
||||
}
|
||||
} else if (level_iters_[level - 1]->Valid() &&
|
||||
!IsOverUpperBound(level_iters_[level - 1]->key())) {
|
||||
immutable_min_heap_.push(level_iters_[level - 1]);
|
||||
} else {
|
||||
// Nothing in this level is interesting. Remove.
|
||||
has_iter_trimmed_for_upper_bound_ = true;
|
||||
DeleteIterator(level_iters_[level - 1]);
|
||||
level_iters_[level - 1] = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -541,7 +561,7 @@ void ForwardIterator::UpdateChildrenPinnedItersMgr() {
|
||||
}
|
||||
|
||||
// Set PinnedIteratorsManager for L1+ levels iterators.
|
||||
for (LevelIterator* child_iter : level_iters_) {
|
||||
for (ForwardLevelIterator* child_iter : level_iters_) {
|
||||
if (child_iter) {
|
||||
child_iter->SetPinnedItersMgr(pinned_iters_mgr_);
|
||||
}
|
||||
@@ -703,7 +723,7 @@ void ForwardIterator::BuildLevelIterators(const VersionStorageInfo* vstorage) {
|
||||
}
|
||||
} else {
|
||||
level_iters_.push_back(
|
||||
new LevelIterator(cfd_, read_options_, level_files));
|
||||
new ForwardLevelIterator(cfd_, read_options_, level_files));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ class DBImpl;
|
||||
class Env;
|
||||
struct SuperVersion;
|
||||
class ColumnFamilyData;
|
||||
class LevelIterator;
|
||||
class ForwardLevelIterator;
|
||||
class VersionStorageInfo;
|
||||
struct FileMetaData;
|
||||
|
||||
@@ -85,7 +85,14 @@ class ForwardIterator : public InternalIterator {
|
||||
|
||||
private:
|
||||
void Cleanup(bool release_sv);
|
||||
// Unreference and, if needed, clean up the current SuperVersion. This is
|
||||
// either done immediately or deferred until this iterator is unpinned by
|
||||
// PinnedIteratorsManager.
|
||||
void SVCleanup();
|
||||
static void SVCleanup(
|
||||
DBImpl* db, SuperVersion* sv, bool background_purge_on_iterator_cleanup);
|
||||
static void DeferredSVCleanup(void* arg);
|
||||
|
||||
void RebuildIterators(bool refresh_sv);
|
||||
void RenewIterators();
|
||||
void BuildLevelIterators(const VersionStorageInfo* vstorage);
|
||||
@@ -119,7 +126,7 @@ class ForwardIterator : public InternalIterator {
|
||||
InternalIterator* mutable_iter_;
|
||||
std::vector<InternalIterator*> imm_iters_;
|
||||
std::vector<InternalIterator*> l0_iters_;
|
||||
std::vector<LevelIterator*> level_iters_;
|
||||
std::vector<ForwardLevelIterator*> level_iters_;
|
||||
InternalIterator* current_;
|
||||
bool valid_;
|
||||
|
||||
|
||||
+5
-2
@@ -81,8 +81,11 @@ struct SuperVersionContext {
|
||||
struct JobContext {
|
||||
inline bool HaveSomethingToDelete() const {
|
||||
return full_scan_candidate_files.size() || sst_delete_files.size() ||
|
||||
log_delete_files.size() || manifest_delete_files.size() ||
|
||||
memtables_to_free.size() > 0 || logs_to_free.size() > 0 ||
|
||||
log_delete_files.size() || manifest_delete_files.size();
|
||||
}
|
||||
|
||||
inline bool HaveSomethingToClean() const {
|
||||
return memtables_to_free.size() > 0 || logs_to_free.size() > 0 ||
|
||||
superversion_context.HaveSomethingToDelete();
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -528,7 +528,7 @@ TEST_F(EventListenerTest, CompactionReasonUniversal) {
|
||||
|
||||
ASSERT_GT(listener->compaction_reasons_.size(), 0);
|
||||
for (auto compaction_reason : listener->compaction_reasons_) {
|
||||
ASSERT_EQ(compaction_reason, CompactionReason::kUniversalSortedRunNum);
|
||||
ASSERT_EQ(compaction_reason, CompactionReason::kUniversalSizeRatio);
|
||||
}
|
||||
|
||||
options.level0_file_num_compaction_trigger = 8;
|
||||
|
||||
@@ -54,6 +54,7 @@ class Reader {
|
||||
// The Reader will start reading at the first record located at physical
|
||||
// position >= initial_offset within the file.
|
||||
Reader(std::shared_ptr<Logger> info_log,
|
||||
// @lint-ignore TXT2 T25377293 Grandfathered in
|
||||
unique_ptr<SequentialFileReader>&& file,
|
||||
Reporter* reporter, bool checksum, uint64_t initial_offset,
|
||||
uint64_t log_num);
|
||||
|
||||
+9
-2
@@ -16,7 +16,12 @@
|
||||
namespace rocksdb {
|
||||
|
||||
#ifdef ROCKSDB_JEMALLOC
|
||||
#ifdef __FreeBSD__
|
||||
#include <malloc_np.h>
|
||||
#define je_malloc_stats_print malloc_stats_print
|
||||
#else
|
||||
#include "jemalloc/jemalloc.h"
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
char* cur;
|
||||
@@ -36,8 +41,8 @@ static void GetJemallocStatus(void* mstat_arg, const char* status) {
|
||||
}
|
||||
#endif // ROCKSDB_JEMALLOC
|
||||
|
||||
void DumpMallocStats(std::string* stats) {
|
||||
#ifdef ROCKSDB_JEMALLOC
|
||||
void DumpMallocStats(std::string* stats) {
|
||||
MallocStatus mstat;
|
||||
const unsigned int kMallocStatusLen = 1000000;
|
||||
std::unique_ptr<char[]> buf{new char[kMallocStatusLen + 1]};
|
||||
@@ -45,8 +50,10 @@ void DumpMallocStats(std::string* stats) {
|
||||
mstat.end = buf.get() + kMallocStatusLen;
|
||||
je_malloc_stats_print(GetJemallocStatus, &mstat, "");
|
||||
stats->append(buf.get());
|
||||
#endif // ROCKSDB_JEMALLOC
|
||||
}
|
||||
#else
|
||||
void DumpMallocStats(std::string*) {}
|
||||
#endif // ROCKSDB_JEMALLOC
|
||||
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
+34
-12
@@ -225,7 +225,7 @@ int MemTable::KeyComparator::operator()(const char* prefix_len_key1,
|
||||
// Internal keys are encoded as length-prefixed strings.
|
||||
Slice k1 = GetLengthPrefixedSlice(prefix_len_key1);
|
||||
Slice k2 = GetLengthPrefixedSlice(prefix_len_key2);
|
||||
return comparator.Compare(k1, k2);
|
||||
return comparator.CompareKeySeq(k1, k2);
|
||||
}
|
||||
|
||||
int MemTable::KeyComparator::operator()(const char* prefix_len_key,
|
||||
@@ -233,7 +233,15 @@ int MemTable::KeyComparator::operator()(const char* prefix_len_key,
|
||||
const {
|
||||
// Internal keys are encoded as length-prefixed strings.
|
||||
Slice a = GetLengthPrefixedSlice(prefix_len_key);
|
||||
return comparator.Compare(a, key);
|
||||
return comparator.CompareKeySeq(a, key);
|
||||
}
|
||||
|
||||
void MemTableRep::InsertConcurrently(KeyHandle handle) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
throw std::runtime_error("concurrent insert not supported");
|
||||
#else
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
|
||||
Slice MemTableRep::UserKey(const char* key) const {
|
||||
@@ -281,8 +289,7 @@ class MemTableIterator : public InternalIterator {
|
||||
#ifndef NDEBUG
|
||||
// Assert that the MemTableIterator is never deleted while
|
||||
// Pinning is Enabled.
|
||||
assert(!pinned_iters_mgr_ ||
|
||||
(pinned_iters_mgr_ && !pinned_iters_mgr_->PinningEnabled()));
|
||||
assert(!pinned_iters_mgr_ || !pinned_iters_mgr_->PinningEnabled());
|
||||
#endif
|
||||
if (arena_mode_) {
|
||||
iter_->~Iterator();
|
||||
@@ -436,7 +443,7 @@ MemTable::MemTableStats MemTable::ApproximateStats(const Slice& start_ikey,
|
||||
return {entry_count * (data_size / n), entry_count};
|
||||
}
|
||||
|
||||
void MemTable::Add(SequenceNumber s, ValueType type,
|
||||
bool MemTable::Add(SequenceNumber s, ValueType type,
|
||||
const Slice& key, /* user key */
|
||||
const Slice& value, bool allow_concurrent,
|
||||
MemTablePostProcessInfo* post_process_info) {
|
||||
@@ -471,9 +478,15 @@ void MemTable::Add(SequenceNumber s, ValueType type,
|
||||
if (insert_with_hint_prefix_extractor_ != nullptr &&
|
||||
insert_with_hint_prefix_extractor_->InDomain(key_slice)) {
|
||||
Slice prefix = insert_with_hint_prefix_extractor_->Transform(key_slice);
|
||||
table->InsertWithHint(handle, &insert_hints_[prefix]);
|
||||
bool res = table->InsertKeyWithHint(handle, &insert_hints_[prefix]);
|
||||
if (!res) {
|
||||
return res;
|
||||
}
|
||||
} else {
|
||||
table->Insert(handle);
|
||||
bool res = table->InsertKey(handle);
|
||||
if (!res) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
// this is a bit ugly, but is the way to avoid locked instructions
|
||||
@@ -506,7 +519,10 @@ void MemTable::Add(SequenceNumber s, ValueType type,
|
||||
assert(post_process_info == nullptr);
|
||||
UpdateFlushState();
|
||||
} else {
|
||||
table->InsertConcurrently(handle);
|
||||
bool res = table->InsertKeyConcurrently(handle);
|
||||
if (!res) {
|
||||
return res;
|
||||
}
|
||||
|
||||
assert(post_process_info != nullptr);
|
||||
post_process_info->num_entries++;
|
||||
@@ -536,6 +552,7 @@ void MemTable::Add(SequenceNumber s, ValueType type,
|
||||
is_range_del_table_empty_ = false;
|
||||
}
|
||||
UpdateOldestKeyTime();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Callback from MemTable::Get()
|
||||
@@ -571,11 +588,12 @@ struct Saver {
|
||||
|
||||
static bool SaveValue(void* arg, const char* entry) {
|
||||
Saver* s = reinterpret_cast<Saver*>(arg);
|
||||
assert(s != nullptr);
|
||||
MergeContext* merge_context = s->merge_context;
|
||||
RangeDelAggregator* range_del_agg = s->range_del_agg;
|
||||
const MergeOperator* merge_operator = s->merge_operator;
|
||||
|
||||
assert(s != nullptr && merge_context != nullptr && range_del_agg != nullptr);
|
||||
assert(merge_context != nullptr && range_del_agg != nullptr);
|
||||
|
||||
// entry format is:
|
||||
// klength varint32
|
||||
@@ -791,8 +809,9 @@ void MemTable::Update(SequenceNumber seq,
|
||||
// Correct user key
|
||||
const uint64_t tag = DecodeFixed64(key_ptr + key_length - 8);
|
||||
ValueType type;
|
||||
SequenceNumber unused;
|
||||
UnPackSequenceAndType(tag, &unused, &type);
|
||||
SequenceNumber existing_seq;
|
||||
UnPackSequenceAndType(tag, &existing_seq, &type);
|
||||
assert(existing_seq != seq);
|
||||
if (type == kTypeValue) {
|
||||
Slice prev_value = GetLengthPrefixedSlice(key_ptr + key_length);
|
||||
uint32_t prev_size = static_cast<uint32_t>(prev_value.size());
|
||||
@@ -815,7 +834,10 @@ void MemTable::Update(SequenceNumber seq,
|
||||
}
|
||||
|
||||
// key doesn't exist
|
||||
Add(seq, kTypeValue, key, value);
|
||||
bool add_res __attribute__((__unused__));
|
||||
add_res = Add(seq, kTypeValue, key, value);
|
||||
// We already checked unused != seq above. In that case, Add should not fail.
|
||||
assert(add_res);
|
||||
}
|
||||
|
||||
bool MemTable::UpdateCallback(SequenceNumber seq,
|
||||
|
||||
+12
-1
@@ -167,7 +167,10 @@ class MemTable {
|
||||
//
|
||||
// REQUIRES: if allow_concurrent = false, external synchronization to prevent
|
||||
// simultaneous operations on the same MemTable.
|
||||
void Add(SequenceNumber seq, ValueType type, const Slice& key,
|
||||
//
|
||||
// Returns false if MemTableRepFactory::CanHandleDuplicatedKey() is true and
|
||||
// the <key, seq> already exists.
|
||||
bool Add(SequenceNumber seq, ValueType type, const Slice& key,
|
||||
const Slice& value, bool allow_concurrent = false,
|
||||
MemTablePostProcessInfo* post_process_info = nullptr);
|
||||
|
||||
@@ -368,6 +371,11 @@ class MemTable {
|
||||
return oldest_key_time_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// REQUIRES: db_mutex held.
|
||||
void SetID(uint64_t id) { id_ = id; }
|
||||
|
||||
uint64_t GetID() const { return id_; }
|
||||
|
||||
private:
|
||||
enum FlushStateEnum { FLUSH_NOT_REQUESTED, FLUSH_REQUESTED, FLUSH_SCHEDULED };
|
||||
|
||||
@@ -437,6 +445,9 @@ class MemTable {
|
||||
// Timestamp of oldest key
|
||||
std::atomic<uint64_t> oldest_key_time_;
|
||||
|
||||
// Memtable id to track flush.
|
||||
uint64_t id_ = 0;
|
||||
|
||||
// Returns a heuristic flush decision
|
||||
bool ShouldFlushNow() const;
|
||||
|
||||
|
||||
+21
-4
@@ -5,11 +5,12 @@
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <deque>
|
||||
#include <limits>
|
||||
#include <list>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "db/dbformat.h"
|
||||
#include "db/memtable.h"
|
||||
@@ -244,6 +245,22 @@ class MemTableList {
|
||||
|
||||
uint64_t GetMinLogContainingPrepSection();
|
||||
|
||||
uint64_t GetEarliestMemTableID() const {
|
||||
auto& memlist = current_->memlist_;
|
||||
if (memlist.empty()) {
|
||||
return std::numeric_limits<uint64_t>::max();
|
||||
}
|
||||
return memlist.back()->GetID();
|
||||
}
|
||||
|
||||
uint64_t GetLatestMemTableID() const {
|
||||
auto& memlist = current_->memlist_;
|
||||
if (memlist.empty()) {
|
||||
return 0;
|
||||
}
|
||||
return memlist.front()->GetID();
|
||||
}
|
||||
|
||||
private:
|
||||
// DB mutex held
|
||||
void InstallNewVersion();
|
||||
|
||||
+8
-3
@@ -5,12 +5,12 @@
|
||||
|
||||
#include "db/merge_helper.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
|
||||
#include "db/dbformat.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "port/likely.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/merge_operator.h"
|
||||
@@ -22,7 +22,8 @@ MergeHelper::MergeHelper(Env* env, const Comparator* user_comparator,
|
||||
const MergeOperator* user_merge_operator,
|
||||
const CompactionFilter* compaction_filter,
|
||||
Logger* logger, bool assert_valid_internal_key,
|
||||
SequenceNumber latest_snapshot, int level,
|
||||
SequenceNumber latest_snapshot,
|
||||
const SnapshotChecker* snapshot_checker, int level,
|
||||
Statistics* stats,
|
||||
const std::atomic<bool>* shutting_down)
|
||||
: env_(env),
|
||||
@@ -34,6 +35,7 @@ MergeHelper::MergeHelper(Env* env, const Comparator* user_comparator,
|
||||
assert_valid_internal_key_(assert_valid_internal_key),
|
||||
allow_single_operand_(false),
|
||||
latest_snapshot_(latest_snapshot),
|
||||
snapshot_checker_(snapshot_checker),
|
||||
level_(level),
|
||||
keys_(),
|
||||
filter_timer_(env_),
|
||||
@@ -158,7 +160,10 @@ Status MergeHelper::MergeUntil(InternalIterator* iter,
|
||||
// hit a different user key, stop right here
|
||||
hit_the_next_user_key = true;
|
||||
break;
|
||||
} else if (stop_before && ikey.sequence <= stop_before) {
|
||||
} else if (stop_before > 0 && ikey.sequence <= stop_before &&
|
||||
LIKELY(snapshot_checker_ == nullptr ||
|
||||
snapshot_checker_->IsInSnapshot(ikey.sequence,
|
||||
stop_before))) {
|
||||
// hit an entry that's visible by the previous snapshot, can't touch that
|
||||
break;
|
||||
}
|
||||
|
||||
+4
-1
@@ -13,6 +13,7 @@
|
||||
#include "db/dbformat.h"
|
||||
#include "db/merge_context.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "db/snapshot_checker.h"
|
||||
#include "rocksdb/compaction_filter.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/slice.h"
|
||||
@@ -33,7 +34,8 @@ class MergeHelper {
|
||||
const MergeOperator* user_merge_operator,
|
||||
const CompactionFilter* compaction_filter, Logger* logger,
|
||||
bool assert_valid_internal_key, SequenceNumber latest_snapshot,
|
||||
int level = 0, Statistics* stats = nullptr,
|
||||
const SnapshotChecker* snapshot_checker = nullptr, int level = 0,
|
||||
Statistics* stats = nullptr,
|
||||
const std::atomic<bool>* shutting_down = nullptr);
|
||||
|
||||
// Wrapper around MergeOperator::FullMergeV2() that records perf statistics.
|
||||
@@ -145,6 +147,7 @@ class MergeHelper {
|
||||
bool assert_valid_internal_key_; // enforce no internal key corruption?
|
||||
bool allow_single_operand_;
|
||||
SequenceNumber latest_snapshot_;
|
||||
const SnapshotChecker* const snapshot_checker_;
|
||||
int level_;
|
||||
|
||||
// the scratch area that holds the result of MergeUntil
|
||||
|
||||
@@ -112,7 +112,7 @@ int main(int argc, char** argv) {
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
int main(int /*argc*/, char** /*argv*/) {
|
||||
printf("Skipped as Options file is not supported in RocksDBLite.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1170,7 +1170,7 @@ int main(int argc, char** argv) {
|
||||
#else
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
int main(int /*argc*/, char** /*argv*/) {
|
||||
fprintf(stderr, "SKIPPED as plain table is not supported in ROCKSDB_LITE\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
+1
-1
@@ -887,7 +887,7 @@ int main(int argc, char** argv) {
|
||||
#else
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
int main(int /*argc*/, char** /*argv*/) {
|
||||
fprintf(stderr,
|
||||
"SKIPPED as HashSkipList and HashLinkList are not supported in "
|
||||
"ROCKSDB_LITE\n");
|
||||
|
||||
+1
-1
@@ -356,7 +356,7 @@ int main(int argc, char** argv) {
|
||||
#else
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
int main(int /*argc*/, char** /*argv*/) {
|
||||
fprintf(stderr, "SKIPPED as RepairDB is not supported in ROCKSDB_LITE\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,10 @@ class SnapshotList {
|
||||
list_.prev_ = &list_;
|
||||
list_.next_ = &list_;
|
||||
list_.number_ = 0xFFFFFFFFL; // placeholder marker, for debugging
|
||||
// Set all the variables to make UBSAN happy.
|
||||
list_.list_ = nullptr;
|
||||
list_.unix_time_ = 0;
|
||||
list_.is_write_conflict_boundary_ = false;
|
||||
count_ = 0;
|
||||
}
|
||||
|
||||
|
||||
+1
-2
@@ -272,9 +272,8 @@ InternalIterator* TableCache::NewRangeTombstoneIterator(
|
||||
const InternalKeyComparator& icomparator, const FileDescriptor& fd,
|
||||
HistogramImpl* file_read_hist, bool skip_filters, int level) {
|
||||
Status s;
|
||||
TableReader* table_reader = nullptr;
|
||||
Cache::Handle* handle = nullptr;
|
||||
table_reader = fd.table_reader;
|
||||
TableReader* table_reader = fd.table_reader;
|
||||
if (table_reader == nullptr) {
|
||||
s = FindTable(env_options, icomparator, fd, &handle,
|
||||
options.read_tier == kBlockCacheTier /* no_io */,
|
||||
|
||||
@@ -60,7 +60,7 @@ InternalKeyPropertiesCollector::GetReadableProperties() const {
|
||||
namespace {
|
||||
|
||||
uint64_t GetUint64Property(const UserCollectedProperties& props,
|
||||
const std::string property_name,
|
||||
const std::string& property_name,
|
||||
bool* property_present) {
|
||||
auto pos = props.find(property_name);
|
||||
if (pos == props.end()) {
|
||||
|
||||
+240
-118
@@ -424,129 +424,261 @@ bool SomeFileOverlapsRange(
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// An internal iterator. For a given version/level pair, yields
|
||||
// information about the files in the level. For a given entry, key()
|
||||
// is the largest key that occurs in the file, and value() is an
|
||||
// 16-byte value containing the file number and file size, both
|
||||
// encoded using EncodeFixed64.
|
||||
class LevelFileNumIterator : public InternalIterator {
|
||||
class LevelIterator final : public InternalIterator {
|
||||
public:
|
||||
LevelFileNumIterator(const InternalKeyComparator& icmp,
|
||||
const LevelFilesBrief* flevel, bool should_sample)
|
||||
: icmp_(icmp),
|
||||
flevel_(flevel),
|
||||
index_(static_cast<uint32_t>(flevel->num_files)),
|
||||
current_value_(0, 0, 0), // Marks as invalid
|
||||
should_sample_(should_sample) {}
|
||||
virtual bool Valid() const override { return index_ < flevel_->num_files; }
|
||||
virtual void Seek(const Slice& target) override {
|
||||
index_ = FindFile(icmp_, *flevel_, target);
|
||||
}
|
||||
virtual void SeekForPrev(const Slice& target) override {
|
||||
SeekForPrevImpl(target, &icmp_);
|
||||
}
|
||||
|
||||
virtual void SeekToFirst() override { index_ = 0; }
|
||||
virtual void SeekToLast() override {
|
||||
index_ = (flevel_->num_files == 0)
|
||||
? 0
|
||||
: static_cast<uint32_t>(flevel_->num_files) - 1;
|
||||
}
|
||||
virtual void Next() override {
|
||||
assert(Valid());
|
||||
index_++;
|
||||
}
|
||||
virtual void Prev() override {
|
||||
assert(Valid());
|
||||
if (index_ == 0) {
|
||||
index_ = static_cast<uint32_t>(flevel_->num_files); // Marks as invalid
|
||||
} else {
|
||||
index_--;
|
||||
}
|
||||
}
|
||||
Slice key() const override {
|
||||
assert(Valid());
|
||||
return flevel_->files[index_].largest_key;
|
||||
}
|
||||
Slice value() const override {
|
||||
assert(Valid());
|
||||
|
||||
auto file_meta = flevel_->files[index_];
|
||||
if (should_sample_) {
|
||||
sample_file_read_inc(file_meta.file_metadata);
|
||||
}
|
||||
current_value_ = file_meta.fd;
|
||||
return Slice(reinterpret_cast<const char*>(¤t_value_),
|
||||
sizeof(FileDescriptor));
|
||||
}
|
||||
virtual Status status() const override { return Status::OK(); }
|
||||
|
||||
private:
|
||||
const InternalKeyComparator icmp_;
|
||||
const LevelFilesBrief* flevel_;
|
||||
uint32_t index_;
|
||||
mutable FileDescriptor current_value_;
|
||||
bool should_sample_;
|
||||
};
|
||||
|
||||
class LevelFileIteratorState : public TwoLevelIteratorState {
|
||||
public:
|
||||
// @param skip_filters Disables loading/accessing the filter block
|
||||
LevelFileIteratorState(TableCache* table_cache,
|
||||
const ReadOptions& read_options,
|
||||
const EnvOptions& env_options,
|
||||
const InternalKeyComparator& icomparator,
|
||||
HistogramImpl* file_read_hist, bool for_compaction,
|
||||
bool prefix_enabled, bool skip_filters, int level,
|
||||
RangeDelAggregator* range_del_agg)
|
||||
: TwoLevelIteratorState(prefix_enabled),
|
||||
table_cache_(table_cache),
|
||||
LevelIterator(TableCache* table_cache, const ReadOptions& read_options,
|
||||
const EnvOptions& env_options,
|
||||
const InternalKeyComparator& icomparator,
|
||||
const LevelFilesBrief* flevel, bool should_sample,
|
||||
HistogramImpl* file_read_hist, bool for_compaction,
|
||||
bool skip_filters, int level, RangeDelAggregator* range_del_agg)
|
||||
: table_cache_(table_cache),
|
||||
read_options_(read_options),
|
||||
env_options_(env_options),
|
||||
icomparator_(icomparator),
|
||||
flevel_(flevel),
|
||||
file_read_hist_(file_read_hist),
|
||||
should_sample_(should_sample),
|
||||
for_compaction_(for_compaction),
|
||||
skip_filters_(skip_filters),
|
||||
file_index_(flevel_->num_files),
|
||||
level_(level),
|
||||
range_del_agg_(range_del_agg) {}
|
||||
range_del_agg_(range_del_agg),
|
||||
pinned_iters_mgr_(nullptr) {
|
||||
// Empty level is not supported.
|
||||
assert(flevel_ != nullptr && flevel_->num_files > 0);
|
||||
}
|
||||
|
||||
InternalIterator* NewSecondaryIterator(const Slice& meta_handle) override {
|
||||
if (meta_handle.size() != sizeof(FileDescriptor)) {
|
||||
return NewErrorInternalIterator(
|
||||
Status::Corruption("FileReader invoked with unexpected value"));
|
||||
virtual ~LevelIterator() { delete file_iter_.Set(nullptr); }
|
||||
|
||||
virtual void Seek(const Slice& target) override;
|
||||
virtual void SeekForPrev(const Slice& target) override;
|
||||
virtual void SeekToFirst() override;
|
||||
virtual void SeekToLast() override;
|
||||
virtual void Next() override;
|
||||
virtual void Prev() override;
|
||||
|
||||
virtual bool Valid() const override { return file_iter_.Valid(); }
|
||||
virtual Slice key() const override {
|
||||
assert(Valid());
|
||||
return file_iter_.key();
|
||||
}
|
||||
virtual Slice value() const override {
|
||||
assert(Valid());
|
||||
return file_iter_.value();
|
||||
}
|
||||
virtual Status status() const override {
|
||||
// It'd be nice if status() returned a const Status& instead of a Status
|
||||
if (!status_.ok()) {
|
||||
return status_;
|
||||
} else if (file_iter_.iter() != nullptr) {
|
||||
return file_iter_.status();
|
||||
}
|
||||
const FileDescriptor* fd =
|
||||
reinterpret_cast<const FileDescriptor*>(meta_handle.data());
|
||||
return table_cache_->NewIterator(
|
||||
read_options_, env_options_, icomparator_, *fd, range_del_agg_,
|
||||
nullptr /* don't need reference to table */, file_read_hist_,
|
||||
for_compaction_, nullptr /* arena */, skip_filters_, level_);
|
||||
return Status::OK();
|
||||
}
|
||||
virtual void SetPinnedItersMgr(
|
||||
PinnedIteratorsManager* pinned_iters_mgr) override {
|
||||
pinned_iters_mgr_ = pinned_iters_mgr;
|
||||
if (file_iter_.iter()) {
|
||||
file_iter_.SetPinnedItersMgr(pinned_iters_mgr);
|
||||
}
|
||||
}
|
||||
virtual bool IsKeyPinned() const override {
|
||||
return pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled() &&
|
||||
file_iter_.iter() && file_iter_.IsKeyPinned();
|
||||
}
|
||||
virtual bool IsValuePinned() const override {
|
||||
return pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled() &&
|
||||
file_iter_.iter() && file_iter_.IsValuePinned();
|
||||
}
|
||||
|
||||
bool PrefixMayMatch(const Slice& internal_key) override {
|
||||
return true;
|
||||
private:
|
||||
void SkipEmptyFileForward();
|
||||
void SkipEmptyFileBackward();
|
||||
void SetFileIterator(InternalIterator* iter);
|
||||
void InitFileIterator(size_t new_file_index);
|
||||
|
||||
const Slice& file_smallest_key(size_t file_index) {
|
||||
assert(file_index < flevel_->num_files);
|
||||
return flevel_->files[file_index].smallest_key;
|
||||
}
|
||||
|
||||
bool KeyReachedUpperBound(const Slice& internal_key) override {
|
||||
bool KeyReachedUpperBound(const Slice& internal_key) {
|
||||
return read_options_.iterate_upper_bound != nullptr &&
|
||||
icomparator_.user_comparator()->Compare(
|
||||
ExtractUserKey(internal_key),
|
||||
*read_options_.iterate_upper_bound) >= 0;
|
||||
}
|
||||
|
||||
private:
|
||||
InternalIterator* NewFileIterator() {
|
||||
assert(file_index_ < flevel_->num_files);
|
||||
auto file_meta = flevel_->files[file_index_];
|
||||
if (should_sample_) {
|
||||
sample_file_read_inc(file_meta.file_metadata);
|
||||
}
|
||||
|
||||
return table_cache_->NewIterator(
|
||||
read_options_, env_options_, icomparator_, file_meta.fd, range_del_agg_,
|
||||
nullptr /* don't need reference to table */, file_read_hist_,
|
||||
for_compaction_, nullptr /* arena */, skip_filters_, level_);
|
||||
}
|
||||
|
||||
TableCache* table_cache_;
|
||||
const ReadOptions read_options_;
|
||||
const EnvOptions& env_options_;
|
||||
const InternalKeyComparator& icomparator_;
|
||||
const LevelFilesBrief* flevel_;
|
||||
mutable FileDescriptor current_value_;
|
||||
|
||||
HistogramImpl* file_read_hist_;
|
||||
bool should_sample_;
|
||||
bool for_compaction_;
|
||||
bool skip_filters_;
|
||||
size_t file_index_;
|
||||
int level_;
|
||||
RangeDelAggregator* range_del_agg_;
|
||||
IteratorWrapper file_iter_; // May be nullptr
|
||||
PinnedIteratorsManager* pinned_iters_mgr_;
|
||||
Status status_;
|
||||
};
|
||||
|
||||
void LevelIterator::Seek(const Slice& target) {
|
||||
size_t new_file_index = FindFile(icomparator_, *flevel_, target);
|
||||
|
||||
InitFileIterator(new_file_index);
|
||||
if (file_iter_.iter() != nullptr) {
|
||||
file_iter_.Seek(target);
|
||||
}
|
||||
SkipEmptyFileForward();
|
||||
}
|
||||
|
||||
void LevelIterator::SeekForPrev(const Slice& target) {
|
||||
size_t new_file_index = FindFile(icomparator_, *flevel_, target);
|
||||
if (new_file_index >= flevel_->num_files) {
|
||||
new_file_index = flevel_->num_files - 1;
|
||||
}
|
||||
|
||||
InitFileIterator(new_file_index);
|
||||
if (file_iter_.iter() != nullptr) {
|
||||
file_iter_.SeekForPrev(target);
|
||||
SkipEmptyFileBackward();
|
||||
}
|
||||
}
|
||||
|
||||
void LevelIterator::SeekToFirst() {
|
||||
InitFileIterator(0);
|
||||
if (file_iter_.iter() != nullptr) {
|
||||
file_iter_.SeekToFirst();
|
||||
}
|
||||
SkipEmptyFileForward();
|
||||
}
|
||||
|
||||
void LevelIterator::SeekToLast() {
|
||||
InitFileIterator(flevel_->num_files - 1);
|
||||
if (file_iter_.iter() != nullptr) {
|
||||
file_iter_.SeekToLast();
|
||||
}
|
||||
SkipEmptyFileBackward();
|
||||
}
|
||||
|
||||
void LevelIterator::Next() {
|
||||
assert(Valid());
|
||||
file_iter_.Next();
|
||||
SkipEmptyFileForward();
|
||||
}
|
||||
|
||||
void LevelIterator::Prev() {
|
||||
assert(Valid());
|
||||
file_iter_.Prev();
|
||||
SkipEmptyFileBackward();
|
||||
}
|
||||
|
||||
void LevelIterator::SkipEmptyFileForward() {
|
||||
// For an error (IO error, checksum mismatch, etc), we skip the file
|
||||
// and move to the next one and continue reading data.
|
||||
// TODO this behavior is from LevelDB. We should revisit it.
|
||||
while (file_iter_.iter() == nullptr ||
|
||||
(!file_iter_.Valid() && !file_iter_.status().IsIncomplete())) {
|
||||
if (file_iter_.iter() != nullptr && !file_iter_.Valid() &&
|
||||
file_iter_.iter()->IsOutOfBound()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Move to next file
|
||||
if (file_index_ >= flevel_->num_files - 1) {
|
||||
// Already at the last file
|
||||
SetFileIterator(nullptr);
|
||||
return;
|
||||
}
|
||||
if (KeyReachedUpperBound(file_smallest_key(file_index_ + 1))) {
|
||||
SetFileIterator(nullptr);
|
||||
return;
|
||||
}
|
||||
InitFileIterator(file_index_ + 1);
|
||||
if (file_iter_.iter() != nullptr) {
|
||||
file_iter_.SeekToFirst();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LevelIterator::SkipEmptyFileBackward() {
|
||||
while (file_iter_.iter() == nullptr ||
|
||||
(!file_iter_.Valid() && !file_iter_.status().IsIncomplete())) {
|
||||
// Move to previous file
|
||||
if (file_index_ == 0) {
|
||||
// Already the first file
|
||||
SetFileIterator(nullptr);
|
||||
return;
|
||||
}
|
||||
InitFileIterator(file_index_ - 1);
|
||||
if (file_iter_.iter() != nullptr) {
|
||||
file_iter_.SeekToLast();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LevelIterator::SetFileIterator(InternalIterator* iter) {
|
||||
if (file_iter_.iter() != nullptr && status_.ok()) {
|
||||
// TODO right now we don't invalidate the iterator even if the status is
|
||||
// not OK. We should consider to do that so that it is harder for users to
|
||||
// skip errors.
|
||||
status_ = file_iter_.status();
|
||||
}
|
||||
|
||||
if (pinned_iters_mgr_ && iter) {
|
||||
iter->SetPinnedItersMgr(pinned_iters_mgr_);
|
||||
}
|
||||
|
||||
InternalIterator* old_iter = file_iter_.Set(iter);
|
||||
if (pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled()) {
|
||||
pinned_iters_mgr_->PinIterator(old_iter);
|
||||
} else {
|
||||
delete old_iter;
|
||||
}
|
||||
}
|
||||
|
||||
void LevelIterator::InitFileIterator(size_t new_file_index) {
|
||||
if (new_file_index >= flevel_->num_files) {
|
||||
file_index_ = new_file_index;
|
||||
SetFileIterator(nullptr);
|
||||
return;
|
||||
} else {
|
||||
// If the file iterator shows incomplete, we try it again if users seek
|
||||
// to the same file, as this time we may go to a different data block
|
||||
// which is cached in block cache.
|
||||
//
|
||||
if (file_iter_.iter() != nullptr && !file_iter_.status().IsIncomplete() &&
|
||||
new_file_index == file_index_) {
|
||||
// file_iter_ is already constructed with this iterator, so
|
||||
// no need to change anything
|
||||
} else {
|
||||
file_index_ = new_file_index;
|
||||
InternalIterator* iter = NewFileIterator();
|
||||
SetFileIterator(iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A wrapper of version builder which references the current version in
|
||||
// constructor and unref it in the destructor.
|
||||
// Both of the constructor and destructor need to be called inside DB Mutex.
|
||||
@@ -854,24 +986,18 @@ void Version::AddIteratorsForLevel(const ReadOptions& read_options,
|
||||
sample_file_read_inc(meta);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else if (storage_info_.LevelFilesBrief(level).num_files > 0) {
|
||||
// For levels > 0, we can use a concatenating iterator that sequentially
|
||||
// walks through the non-overlapping files in the level, opening them
|
||||
// lazily.
|
||||
auto* mem = arena->AllocateAligned(sizeof(LevelFileIteratorState));
|
||||
auto* state = new (mem)
|
||||
LevelFileIteratorState(cfd_->table_cache(), read_options, soptions,
|
||||
cfd_->internal_comparator(),
|
||||
cfd_->internal_stats()->GetFileReadHist(level),
|
||||
false /* for_compaction */,
|
||||
cfd_->ioptions()->prefix_extractor != nullptr,
|
||||
IsFilterSkipped(level), level, range_del_agg);
|
||||
mem = arena->AllocateAligned(sizeof(LevelFileNumIterator));
|
||||
auto* first_level_iter = new (mem) LevelFileNumIterator(
|
||||
auto* mem = arena->AllocateAligned(sizeof(LevelIterator));
|
||||
merge_iter_builder->AddIterator(new (mem) LevelIterator(
|
||||
cfd_->table_cache(), read_options, soptions,
|
||||
cfd_->internal_comparator(), &storage_info_.LevelFilesBrief(level),
|
||||
should_sample_file_read());
|
||||
merge_iter_builder->AddIterator(
|
||||
NewTwoLevelIterator(state, first_level_iter, arena, false));
|
||||
should_sample_file_read(),
|
||||
cfd_->internal_stats()->GetFileReadHist(level),
|
||||
false /* for_compaction */, IsFilterSkipped(level), level,
|
||||
range_del_agg));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1976,7 +2102,7 @@ void VersionStorageInfo::ExtendFileRangeOverlappingInterval(
|
||||
#endif
|
||||
*start_index = mid_index + 1;
|
||||
*end_index = mid_index;
|
||||
int count __attribute__((unused));
|
||||
int count __attribute__((__unused__));
|
||||
count = 0;
|
||||
|
||||
// check backwards from 'mid' to lower indices
|
||||
@@ -3732,17 +3858,13 @@ InternalIterator* VersionSet::MakeInputIterator(
|
||||
}
|
||||
} else {
|
||||
// Create concatenating iterator for the files from this level
|
||||
list[num++] = NewTwoLevelIterator(
|
||||
new LevelFileIteratorState(
|
||||
cfd->table_cache(), read_options, env_options_compactions,
|
||||
cfd->internal_comparator(),
|
||||
nullptr /* no per level latency histogram */,
|
||||
true /* for_compaction */, false /* prefix enabled */,
|
||||
false /* skip_filters */, (int)which /* level */,
|
||||
range_del_agg),
|
||||
new LevelFileNumIterator(cfd->internal_comparator(),
|
||||
c->input_levels(which),
|
||||
false /* don't sample compaction */));
|
||||
list[num++] = new LevelIterator(
|
||||
cfd->table_cache(), read_options, env_options_compactions,
|
||||
cfd->internal_comparator(), c->input_levels(which),
|
||||
false /* should_sample */,
|
||||
nullptr /* no per level latency histogram */,
|
||||
true /* for_compaction */, false /* skip_filters */,
|
||||
(int)which /* level */, range_del_agg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,15 @@ namespace rocksdb {
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
Status WalManager::DeleteFile(const std::string& fname, uint64_t number) {
|
||||
auto s = env_->DeleteFile(db_options_.wal_dir + "/" + fname);
|
||||
if (s.ok()) {
|
||||
MutexLock l(&read_first_record_cache_mutex_);
|
||||
read_first_record_cache_.erase(number);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status WalManager::GetSortedWalFiles(VectorLogPtr& files) {
|
||||
// First get sorted files in db dir, then get sorted files from archived
|
||||
// dir, to avoid a race condition where a log file is moved to archived
|
||||
|
||||
@@ -49,6 +49,8 @@ class WalManager {
|
||||
|
||||
void ArchiveWALFile(const std::string& fname, uint64_t number);
|
||||
|
||||
Status DeleteFile(const std::string& fname, uint64_t number);
|
||||
|
||||
Status TEST_ReadFirstRecord(const WalFileType type, const uint64_t number,
|
||||
SequenceNumber* sequence) {
|
||||
return ReadFirstRecord(type, number, sequence);
|
||||
|
||||
@@ -303,7 +303,7 @@ int main(int argc, char** argv) {
|
||||
#else
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
int main(int /*argc*/, char** /*argv*/) {
|
||||
fprintf(stderr, "SKIPPED as WalManager is not supported in ROCKSDB_LITE\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
+146
-32
@@ -15,10 +15,12 @@
|
||||
// kTypeValue varstring varstring
|
||||
// kTypeDeletion varstring
|
||||
// kTypeSingleDeletion varstring
|
||||
// kTypeRangeDeletion varstring varstring
|
||||
// kTypeMerge varstring varstring
|
||||
// kTypeColumnFamilyValue varint32 varstring varstring
|
||||
// kTypeColumnFamilyDeletion varint32 varstring varstring
|
||||
// kTypeColumnFamilySingleDeletion varint32 varstring varstring
|
||||
// kTypeColumnFamilyDeletion varint32 varstring
|
||||
// kTypeColumnFamilySingleDeletion varint32 varstring
|
||||
// kTypeColumnFamilyRangeDeletion varint32 varstring varstring
|
||||
// kTypeColumnFamilyMerge varint32 varstring varstring
|
||||
// kTypeBeginPrepareXID varstring
|
||||
// kTypeEndPrepareXID
|
||||
@@ -144,6 +146,12 @@ WriteBatch::WriteBatch(const std::string& rep)
|
||||
max_bytes_(0),
|
||||
rep_(rep) {}
|
||||
|
||||
WriteBatch::WriteBatch(std::string&& rep)
|
||||
: save_points_(nullptr),
|
||||
content_flags_(ContentFlags::DEFERRED),
|
||||
max_bytes_(0),
|
||||
rep_(std::move(rep)) {}
|
||||
|
||||
WriteBatch::WriteBatch(const WriteBatch& src)
|
||||
: save_points_(src.save_points_),
|
||||
wal_term_point_(src.wal_term_point_),
|
||||
@@ -394,14 +402,22 @@ Status WriteBatch::Iterate(Handler* handler) const {
|
||||
bool empty_batch = true;
|
||||
int found = 0;
|
||||
Status s;
|
||||
while (s.ok() && !input.empty() && handler->Continue()) {
|
||||
char tag = 0;
|
||||
uint32_t column_family = 0; // default
|
||||
char tag = 0;
|
||||
uint32_t column_family = 0; // default
|
||||
while ((s.ok() || UNLIKELY(s.IsTryAgain())) && !input.empty() &&
|
||||
handler->Continue()) {
|
||||
if (LIKELY(!s.IsTryAgain())) {
|
||||
tag = 0;
|
||||
column_family = 0; // default
|
||||
|
||||
s = ReadRecordFromWriteBatch(&input, &tag, &column_family, &key, &value,
|
||||
&blob, &xid);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
s = ReadRecordFromWriteBatch(&input, &tag, &column_family, &key, &value,
|
||||
&blob, &xid);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
} else {
|
||||
assert(s.IsTryAgain());
|
||||
s = Status::OK();
|
||||
}
|
||||
|
||||
switch (tag) {
|
||||
@@ -410,47 +426,59 @@ Status WriteBatch::Iterate(Handler* handler) const {
|
||||
assert(content_flags_.load(std::memory_order_relaxed) &
|
||||
(ContentFlags::DEFERRED | ContentFlags::HAS_PUT));
|
||||
s = handler->PutCF(column_family, key, value);
|
||||
empty_batch = false;
|
||||
found++;
|
||||
if (LIKELY(s.ok())) {
|
||||
empty_batch = false;
|
||||
found++;
|
||||
}
|
||||
break;
|
||||
case kTypeColumnFamilyDeletion:
|
||||
case kTypeDeletion:
|
||||
assert(content_flags_.load(std::memory_order_relaxed) &
|
||||
(ContentFlags::DEFERRED | ContentFlags::HAS_DELETE));
|
||||
s = handler->DeleteCF(column_family, key);
|
||||
empty_batch = false;
|
||||
found++;
|
||||
if (LIKELY(s.ok())) {
|
||||
empty_batch = false;
|
||||
found++;
|
||||
}
|
||||
break;
|
||||
case kTypeColumnFamilySingleDeletion:
|
||||
case kTypeSingleDeletion:
|
||||
assert(content_flags_.load(std::memory_order_relaxed) &
|
||||
(ContentFlags::DEFERRED | ContentFlags::HAS_SINGLE_DELETE));
|
||||
s = handler->SingleDeleteCF(column_family, key);
|
||||
empty_batch = false;
|
||||
found++;
|
||||
if (LIKELY(s.ok())) {
|
||||
empty_batch = false;
|
||||
found++;
|
||||
}
|
||||
break;
|
||||
case kTypeColumnFamilyRangeDeletion:
|
||||
case kTypeRangeDeletion:
|
||||
assert(content_flags_.load(std::memory_order_relaxed) &
|
||||
(ContentFlags::DEFERRED | ContentFlags::HAS_DELETE_RANGE));
|
||||
s = handler->DeleteRangeCF(column_family, key, value);
|
||||
empty_batch = false;
|
||||
found++;
|
||||
if (LIKELY(s.ok())) {
|
||||
empty_batch = false;
|
||||
found++;
|
||||
}
|
||||
break;
|
||||
case kTypeColumnFamilyMerge:
|
||||
case kTypeMerge:
|
||||
assert(content_flags_.load(std::memory_order_relaxed) &
|
||||
(ContentFlags::DEFERRED | ContentFlags::HAS_MERGE));
|
||||
s = handler->MergeCF(column_family, key, value);
|
||||
empty_batch = false;
|
||||
found++;
|
||||
if (LIKELY(s.ok())) {
|
||||
empty_batch = false;
|
||||
found++;
|
||||
}
|
||||
break;
|
||||
case kTypeColumnFamilyBlobIndex:
|
||||
case kTypeBlobIndex:
|
||||
assert(content_flags_.load(std::memory_order_relaxed) &
|
||||
(ContentFlags::DEFERRED | ContentFlags::HAS_BLOB_INDEX));
|
||||
s = handler->PutBlobIndexCF(column_family, key, value);
|
||||
found++;
|
||||
if (LIKELY(s.ok())) {
|
||||
found++;
|
||||
}
|
||||
break;
|
||||
case kTypeLogData:
|
||||
handler->LogData(blob);
|
||||
@@ -547,6 +575,13 @@ size_t WriteBatchInternal::GetFirstOffset(WriteBatch* b) {
|
||||
|
||||
Status WriteBatchInternal::Put(WriteBatch* b, uint32_t column_family_id,
|
||||
const Slice& key, const Slice& value) {
|
||||
if (key.size() > size_t{port::kMaxUint32}) {
|
||||
return Status::InvalidArgument("key is too large");
|
||||
}
|
||||
if (value.size() > size_t{port::kMaxUint32}) {
|
||||
return Status::InvalidArgument("value is too large");
|
||||
}
|
||||
|
||||
LocalSavePoint save(b);
|
||||
WriteBatchInternal::SetCount(b, WriteBatchInternal::Count(b) + 1);
|
||||
if (column_family_id == 0) {
|
||||
@@ -569,8 +604,33 @@ Status WriteBatch::Put(ColumnFamilyHandle* column_family, const Slice& key,
|
||||
value);
|
||||
}
|
||||
|
||||
Status WriteBatchInternal::CheckSlicePartsLength(const SliceParts& key,
|
||||
const SliceParts& value) {
|
||||
size_t total_key_bytes = 0;
|
||||
for (int i = 0; i < key.num_parts; ++i) {
|
||||
total_key_bytes += key.parts[i].size();
|
||||
}
|
||||
if (total_key_bytes >= size_t{port::kMaxUint32}) {
|
||||
return Status::InvalidArgument("key is too large");
|
||||
}
|
||||
|
||||
size_t total_value_bytes = 0;
|
||||
for (int i = 0; i < value.num_parts; ++i) {
|
||||
total_value_bytes += value.parts[i].size();
|
||||
}
|
||||
if (total_value_bytes >= size_t{port::kMaxUint32}) {
|
||||
return Status::InvalidArgument("value is too large");
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status WriteBatchInternal::Put(WriteBatch* b, uint32_t column_family_id,
|
||||
const SliceParts& key, const SliceParts& value) {
|
||||
Status s = CheckSlicePartsLength(key, value);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
LocalSavePoint save(b);
|
||||
WriteBatchInternal::SetCount(b, WriteBatchInternal::Count(b) + 1);
|
||||
if (column_family_id == 0) {
|
||||
@@ -787,6 +847,13 @@ Status WriteBatch::DeleteRange(ColumnFamilyHandle* column_family,
|
||||
|
||||
Status WriteBatchInternal::Merge(WriteBatch* b, uint32_t column_family_id,
|
||||
const Slice& key, const Slice& value) {
|
||||
if (key.size() > size_t{port::kMaxUint32}) {
|
||||
return Status::InvalidArgument("key is too large");
|
||||
}
|
||||
if (value.size() > size_t{port::kMaxUint32}) {
|
||||
return Status::InvalidArgument("value is too large");
|
||||
}
|
||||
|
||||
LocalSavePoint save(b);
|
||||
WriteBatchInternal::SetCount(b, WriteBatchInternal::Count(b) + 1);
|
||||
if (column_family_id == 0) {
|
||||
@@ -812,6 +879,11 @@ Status WriteBatch::Merge(ColumnFamilyHandle* column_family, const Slice& key,
|
||||
Status WriteBatchInternal::Merge(WriteBatch* b, uint32_t column_family_id,
|
||||
const SliceParts& key,
|
||||
const SliceParts& value) {
|
||||
Status s = CheckSlicePartsLength(key, value);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
LocalSavePoint save(b);
|
||||
WriteBatchInternal::SetCount(b, WriteBatchInternal::Count(b) + 1);
|
||||
if (column_family_id == 0) {
|
||||
@@ -1076,12 +1148,23 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
MaybeAdvanceSeq();
|
||||
return seek_status;
|
||||
}
|
||||
Status ret_status;
|
||||
|
||||
MemTable* mem = cf_mems_->GetMemTable();
|
||||
auto* moptions = mem->GetImmutableMemTableOptions();
|
||||
// inplace_update_support is inconsistent with snapshots, and therefore with
|
||||
// any kind of transactions including the ones that use seq_per_batch
|
||||
assert(!seq_per_batch_ || !moptions->inplace_update_support);
|
||||
if (!moptions->inplace_update_support) {
|
||||
mem->Add(sequence_, value_type, key, value, concurrent_memtable_writes_,
|
||||
get_post_process_info(mem));
|
||||
bool mem_res =
|
||||
mem->Add(sequence_, value_type, key, value,
|
||||
concurrent_memtable_writes_, get_post_process_info(mem));
|
||||
if (UNLIKELY(!mem_res)) {
|
||||
assert(seq_per_batch_);
|
||||
ret_status = Status::TryAgain("key+seq exists");
|
||||
const bool BATCH_BOUNDRY = true;
|
||||
MaybeAdvanceSeq(BATCH_BOUNDRY);
|
||||
}
|
||||
} else if (moptions->inplace_callback == nullptr) {
|
||||
assert(!concurrent_memtable_writes_);
|
||||
mem->Update(sequence_, key, value);
|
||||
@@ -1117,11 +1200,17 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
value, &merged_value);
|
||||
if (status == UpdateStatus::UPDATED_INPLACE) {
|
||||
// prev_value is updated in-place with final value.
|
||||
mem->Add(sequence_, value_type, key, Slice(prev_buffer, prev_size));
|
||||
bool mem_res __attribute__((__unused__));
|
||||
mem_res = mem->Add(
|
||||
sequence_, value_type, key, Slice(prev_buffer, prev_size));
|
||||
assert(mem_res);
|
||||
RecordTick(moptions->statistics, NUMBER_KEYS_WRITTEN);
|
||||
} else if (status == UpdateStatus::UPDATED) {
|
||||
// merged_value contains the final value.
|
||||
mem->Add(sequence_, value_type, key, Slice(merged_value));
|
||||
bool mem_res __attribute__((__unused__));
|
||||
mem_res =
|
||||
mem->Add(sequence_, value_type, key, Slice(merged_value));
|
||||
assert(mem_res);
|
||||
RecordTick(moptions->statistics, NUMBER_KEYS_WRITTEN);
|
||||
}
|
||||
}
|
||||
@@ -1131,7 +1220,7 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
// in memtable add/update.
|
||||
MaybeAdvanceSeq();
|
||||
CheckMemtableFull();
|
||||
return Status::OK();
|
||||
return ret_status;
|
||||
}
|
||||
|
||||
virtual Status PutCF(uint32_t column_family_id, const Slice& key,
|
||||
@@ -1141,12 +1230,20 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
|
||||
Status DeleteImpl(uint32_t column_family_id, const Slice& key,
|
||||
const Slice& value, ValueType delete_type) {
|
||||
Status ret_status;
|
||||
MemTable* mem = cf_mems_->GetMemTable();
|
||||
mem->Add(sequence_, delete_type, key, value, concurrent_memtable_writes_,
|
||||
get_post_process_info(mem));
|
||||
bool mem_res =
|
||||
mem->Add(sequence_, delete_type, key, value,
|
||||
concurrent_memtable_writes_, get_post_process_info(mem));
|
||||
if (UNLIKELY(!mem_res)) {
|
||||
assert(seq_per_batch_);
|
||||
ret_status = Status::TryAgain("key+seq exists");
|
||||
const bool BATCH_BOUNDRY = true;
|
||||
MaybeAdvanceSeq(BATCH_BOUNDRY);
|
||||
}
|
||||
MaybeAdvanceSeq();
|
||||
CheckMemtableFull();
|
||||
return Status::OK();
|
||||
return ret_status;
|
||||
}
|
||||
|
||||
virtual Status DeleteCF(uint32_t column_family_id,
|
||||
@@ -1238,6 +1335,7 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
return seek_status;
|
||||
}
|
||||
|
||||
Status ret_status;
|
||||
MemTable* mem = cf_mems_->GetMemTable();
|
||||
auto* moptions = mem->GetImmutableMemTableOptions();
|
||||
bool perform_merge = false;
|
||||
@@ -1293,18 +1391,30 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
perform_merge = false;
|
||||
} else {
|
||||
// 3) Add value to memtable
|
||||
mem->Add(sequence_, kTypeValue, key, new_value);
|
||||
bool mem_res = mem->Add(sequence_, kTypeValue, key, new_value);
|
||||
if (UNLIKELY(!mem_res)) {
|
||||
assert(seq_per_batch_);
|
||||
ret_status = Status::TryAgain("key+seq exists");
|
||||
const bool BATCH_BOUNDRY = true;
|
||||
MaybeAdvanceSeq(BATCH_BOUNDRY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!perform_merge) {
|
||||
// Add merge operator to memtable
|
||||
mem->Add(sequence_, kTypeMerge, key, value);
|
||||
bool mem_res = mem->Add(sequence_, kTypeMerge, key, value);
|
||||
if (UNLIKELY(!mem_res)) {
|
||||
assert(seq_per_batch_);
|
||||
ret_status = Status::TryAgain("key+seq exists");
|
||||
const bool BATCH_BOUNDRY = true;
|
||||
MaybeAdvanceSeq(BATCH_BOUNDRY);
|
||||
}
|
||||
}
|
||||
|
||||
MaybeAdvanceSeq();
|
||||
CheckMemtableFull();
|
||||
return Status::OK();
|
||||
return ret_status;
|
||||
}
|
||||
|
||||
virtual Status PutBlobIndexCF(uint32_t column_family_id, const Slice& key,
|
||||
@@ -1488,6 +1598,8 @@ Status WriteBatchInternal::InsertInto(
|
||||
if (!w->status.ok()) {
|
||||
return w->status;
|
||||
}
|
||||
assert(!seq_per_batch || w->batch_cnt != 0);
|
||||
assert(!seq_per_batch || inserter.sequence() - w->sequence == w->batch_cnt);
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
@@ -1496,7 +1608,7 @@ Status WriteBatchInternal::InsertInto(
|
||||
WriteThread::Writer* writer, SequenceNumber sequence,
|
||||
ColumnFamilyMemTables* memtables, FlushScheduler* flush_scheduler,
|
||||
bool ignore_missing_column_families, uint64_t log_number, DB* db,
|
||||
bool concurrent_memtable_writes, bool seq_per_batch) {
|
||||
bool concurrent_memtable_writes, bool seq_per_batch, size_t batch_cnt) {
|
||||
assert(writer->ShouldWriteToMemtable());
|
||||
MemTableInserter inserter(sequence, memtables, flush_scheduler,
|
||||
ignore_missing_column_families, log_number, db,
|
||||
@@ -1505,6 +1617,8 @@ Status WriteBatchInternal::InsertInto(
|
||||
SetSequence(writer->batch, sequence);
|
||||
inserter.set_log_number_ref(writer->log_ref);
|
||||
Status s = writer->batch->Iterate(&inserter);
|
||||
assert(!seq_per_batch || batch_cnt != 0);
|
||||
assert(!seq_per_batch || inserter.sequence() - sequence == batch_cnt);
|
||||
if (concurrent_memtable_writes) {
|
||||
inserter.PostProcess();
|
||||
}
|
||||
|
||||
@@ -138,6 +138,9 @@ class WriteBatchInternal {
|
||||
|
||||
static Status SetContents(WriteBatch* batch, const Slice& contents);
|
||||
|
||||
static Status CheckSlicePartsLength(const SliceParts& key,
|
||||
const SliceParts& value);
|
||||
|
||||
// Inserts batches[i] into memtable, for i in 0..num_batches-1 inclusive.
|
||||
//
|
||||
// If ignore_missing_column_families == true. WriteBatch
|
||||
@@ -182,7 +185,7 @@ class WriteBatchInternal {
|
||||
bool ignore_missing_column_families = false,
|
||||
uint64_t log_number = 0, DB* db = nullptr,
|
||||
bool concurrent_memtable_writes = false,
|
||||
bool seq_per_batch = false);
|
||||
bool seq_per_batch = false, size_t batch_cnt = 0);
|
||||
|
||||
static Status Append(WriteBatch* dst, const WriteBatch* src,
|
||||
const bool WAL_only = false);
|
||||
|
||||
@@ -291,7 +291,7 @@ TEST_F(WriteCallbackTest, WriteWithCallbackTest) {
|
||||
woptions.disableWAL = !enable_WAL;
|
||||
woptions.sync = enable_WAL;
|
||||
Status s;
|
||||
if (seq_per_batch && two_queues) {
|
||||
if (seq_per_batch) {
|
||||
class PublishSeqCallback : public PreReleaseCallback {
|
||||
public:
|
||||
PublishSeqCallback(DBImpl* db_impl_in)
|
||||
@@ -302,9 +302,13 @@ TEST_F(WriteCallbackTest, WriteWithCallbackTest) {
|
||||
}
|
||||
DBImpl* db_impl_;
|
||||
} publish_seq_callback(db_impl);
|
||||
s = db_impl->WriteImpl(woptions, &write_op.write_batch_,
|
||||
&write_op.callback_, nullptr, 0, false,
|
||||
nullptr, &publish_seq_callback);
|
||||
// seq_per_batch requires a natural batch separator or Noop
|
||||
WriteBatchInternal::InsertNoop(&write_op.write_batch_);
|
||||
const size_t ONE_BATCH = 1;
|
||||
s = db_impl->WriteImpl(
|
||||
woptions, &write_op.write_batch_, &write_op.callback_,
|
||||
nullptr, 0, false, nullptr, ONE_BATCH,
|
||||
two_queues ? &publish_seq_callback : nullptr);
|
||||
} else {
|
||||
s = db_impl->WriteWithCallback(
|
||||
woptions, &write_op.write_batch_, &write_op.callback_);
|
||||
@@ -429,7 +433,7 @@ int main(int argc, char** argv) {
|
||||
#else
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
int main(int /*argc*/, char** /*argv*/) {
|
||||
fprintf(stderr,
|
||||
"SKIPPED as WriteWithCallback is not supported in ROCKSDB_LITE\n");
|
||||
return 0;
|
||||
|
||||
@@ -118,6 +118,7 @@ class WriteThread {
|
||||
bool no_slowdown;
|
||||
bool disable_wal;
|
||||
bool disable_memtable;
|
||||
size_t batch_cnt; // if non-zero, number of sub-batches in the write batch
|
||||
PreReleaseCallback* pre_release_callback;
|
||||
uint64_t log_used; // log number that this batch was inserted into
|
||||
uint64_t log_ref; // log number that memtable insert should reference
|
||||
@@ -128,6 +129,7 @@ class WriteThread {
|
||||
SequenceNumber sequence; // the sequence number to use for the first key
|
||||
Status status; // status of memtable inserter
|
||||
Status callback_status; // status returned by callback->Callback()
|
||||
|
||||
std::aligned_storage<sizeof(std::mutex)>::type state_mutex_bytes;
|
||||
std::aligned_storage<sizeof(std::condition_variable)>::type state_cv_bytes;
|
||||
Writer* link_older; // read/write only before linking, or as leader
|
||||
@@ -139,6 +141,7 @@ class WriteThread {
|
||||
no_slowdown(false),
|
||||
disable_wal(false),
|
||||
disable_memtable(false),
|
||||
batch_cnt(0),
|
||||
pre_release_callback(nullptr),
|
||||
log_used(0),
|
||||
log_ref(0),
|
||||
@@ -152,12 +155,14 @@ class WriteThread {
|
||||
|
||||
Writer(const WriteOptions& write_options, WriteBatch* _batch,
|
||||
WriteCallback* _callback, uint64_t _log_ref, bool _disable_memtable,
|
||||
size_t _batch_cnt = 0,
|
||||
PreReleaseCallback* _pre_release_callback = nullptr)
|
||||
: batch(_batch),
|
||||
sync(write_options.sync),
|
||||
no_slowdown(write_options.no_slowdown),
|
||||
disable_wal(write_options.disableWAL),
|
||||
disable_memtable(_disable_memtable),
|
||||
batch_cnt(_batch_cnt),
|
||||
pre_release_callback(_pre_release_callback),
|
||||
log_used(0),
|
||||
log_ref(_log_ref),
|
||||
|
||||
Loaded 100 of 337 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user