mirror of
https://github.com/facebook/rocksdb.git
synced 2026-07-07 22:55:23 +08:00
Compare commits
91 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 849dca7221 | |||
| 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 | |||
| 677f249d6d | |||
| 6aa95f4d0f | |||
| 0f0d2ab95a | |||
| 46ec52499e | |||
| 00b33c2474 | |||
| 24e2c1640d | |||
| 398d72fa61 | |||
| 30a017feca | |||
| 3e955fad09 | |||
| 84ddbd186a | |||
| 90c1d81975 | |||
| 1c9ada59cc | |||
| ea8ccd2267 | |||
| 00e5e1ef7f | |||
| ccc095a016 | |||
| b5c99cc908 | |||
| f00e176c5b | |||
| 02a2c11732 | |||
| 0ef3fdd732 | |||
| 58b841b356 | |||
| f54d7f5fea | |||
| e763e1b623 | |||
| 48cf8da2bb | |||
| 06149429d9 | |||
| 1563801bce | |||
| 2190e96727 | |||
| 0faa026db6 | |||
| 78c2eedb4f | |||
| aa6509d8e4 | |||
| a6d3c762df | |||
| fccc12f386 | |||
| 95583e1532 | |||
| 237b292515 | |||
| a79c7c05e8 | |||
| 5a7e08468a | |||
| b4d88d7128 | |||
| def6a00740 | |||
| 6b77c07379 | |||
| cd2e5cae7f | |||
| 546a63272f | |||
| 35dfbd58dd | |||
| 51c2ea0feb | |||
| 9089373a01 | |||
| 7393ef779c | |||
| 4bcb7fb148 | |||
| e3a06f12d2 | |||
| a9c8d4ef15 | |||
| bb5ed4b1d1 | |||
| 0d5692e02b | |||
| 2f1a3a4d74 |
+64
-32
@@ -14,7 +14,7 @@
|
||||
# cd build
|
||||
# 3. Run cmake to generate project files for Windows, add more options to enable required third-party libraries.
|
||||
# See thirdparty.inc for more information.
|
||||
# sample command: cmake -G "Visual Studio 14 Win64" -DGFLAGS=1 -DSNAPPY=1 -DJEMALLOC=1 -DJNI=1 ..
|
||||
# sample command: cmake -G "Visual Studio 15 Win64" -DWITH_GFLAGS=1 -DWITH_SNAPPY=1 -DWITH_JEMALLOC=1 -DWITH_JNI=1 ..
|
||||
# 4. Then build the project in debug mode (you may want to add /m[:<N>] flag to run msbuild in <N> parallel threads
|
||||
# or simply /m ot use all avail cores)
|
||||
# msbuild rocksdb.sln
|
||||
@@ -34,15 +34,25 @@
|
||||
|
||||
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)
|
||||
option(WITH_LZ4 "build with lz4" OFF)
|
||||
option(WITH_ZLIB "build with zlib" OFF)
|
||||
if(MSVC)
|
||||
# Defaults currently different for GFLAGS.
|
||||
# We will address find_package work a little later
|
||||
option(WITH_GFLAGS "build with GFlags" OFF)
|
||||
option(WITH_XPRESS "build with windows built in compression" OFF)
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/thirdparty.inc)
|
||||
else()
|
||||
if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
|
||||
@@ -56,8 +66,18 @@ else()
|
||||
include_directories(${JEMALLOC_INCLUDE_DIR})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# No config file for this
|
||||
option(WITH_GFLAGS "build with GFlags" ON)
|
||||
if(WITH_GFLAGS)
|
||||
find_package(gflags)
|
||||
if(gflags_FOUND)
|
||||
add_definitions(-DGFLAGS=1)
|
||||
include_directories(${gflags_INCLUDE_DIR})
|
||||
list(APPEND THIRDPARTY_LIBS ${gflags_LIBRARIES})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
option(WITH_SNAPPY "build with SNAPPY" OFF)
|
||||
if(WITH_SNAPPY)
|
||||
find_package(snappy REQUIRED)
|
||||
add_definitions(-DSNAPPY)
|
||||
@@ -65,7 +85,6 @@ else()
|
||||
list(APPEND THIRDPARTY_LIBS ${SNAPPY_LIBRARIES})
|
||||
endif()
|
||||
|
||||
option(WITH_ZLIB "build with zlib" OFF)
|
||||
if(WITH_ZLIB)
|
||||
find_package(zlib REQUIRED)
|
||||
add_definitions(-DZLIB)
|
||||
@@ -81,7 +100,6 @@ else()
|
||||
list(APPEND THIRDPARTY_LIBS ${BZIP2_LIBRARIES})
|
||||
endif()
|
||||
|
||||
option(WITH_LZ4 "build with lz4" OFF)
|
||||
if(WITH_LZ4)
|
||||
find_package(lz4 REQUIRED)
|
||||
add_definitions(-DLZ4)
|
||||
@@ -114,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)
|
||||
@@ -163,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)
|
||||
@@ -175,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()
|
||||
|
||||
@@ -254,13 +285,6 @@ if(WITH_UBSAN)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
find_package(gflags)
|
||||
if(gflags_FOUND)
|
||||
add_definitions(-DGFLAGS=1)
|
||||
include_directories(${gflags_INCLUDE_DIR})
|
||||
list(APPEND THIRDPARTY_LIBS ${gflags_LIBRARIES})
|
||||
endif()
|
||||
|
||||
find_package(NUMA)
|
||||
if(NUMA_FOUND)
|
||||
add_definitions(-DNUMA)
|
||||
@@ -490,6 +514,7 @@ set(SOURCES
|
||||
table/block_based_table_factory.cc
|
||||
table/block_based_table_reader.cc
|
||||
table/block_builder.cc
|
||||
table/block_fetcher.cc
|
||||
table/block_prefix_index.cc
|
||||
table/bloom_block.cc
|
||||
table/cuckoo_table_builder.cc
|
||||
@@ -617,6 +642,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
|
||||
@@ -711,7 +742,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}
|
||||
)
|
||||
|
||||
@@ -941,17 +972,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})
|
||||
|
||||
|
||||
+6
-1
@@ -1,12 +1,16 @@
|
||||
# Rocksdb Change Log
|
||||
## Unreleased
|
||||
### 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.
|
||||
### Bug Fixes
|
||||
* Fix `DisableFileDeletions()` followed by `GetSortedWalFiles()` to not return obsolete WAL files that `PurgeObsoleteFiles()` is going to delete.
|
||||
* Fix DB::Flush() keep waiting after flush finish under certain condition.
|
||||
|
||||
## 5.10.0 (12/11/2017)
|
||||
### Public API Change
|
||||
* When running `make` with environment variable `USE_SSE` set and `PORTABLE` unset, will use all machine features available locally. Previously this combination only compiled SSE-related features.
|
||||
|
||||
### New Features
|
||||
* CRC32C is now using the 3-way pipelined SSE algorithm `crc32c_3way` on supported platforms to improve performance. The system will choose to use this algorithm on supported platforms automatically whenever possible. If PCLMULQDQ is not supported it will fall back to the old Fast_CRC32 algorithm.
|
||||
* Provide lifetime hints when writing files on Linux. This reduces hardware write-amp on storage devices supporting multiple streams.
|
||||
* Add a DB stat, `NUMBER_ITER_SKIP`, which returns how many internal keys were skipped during iterations (e.g., due to being tombstones or duplicate versions of a key).
|
||||
* Add PerfContext counters, `key_lock_wait_count` and `key_lock_wait_time`, which measure the number of times transactions wait on key locks and total amount of time waiting.
|
||||
@@ -28,6 +32,7 @@
|
||||
* Return an error on write if write_options.sync = true and write_options.disableWAL = true to warn user of inconsistent options. Previously we will not write to WAL and not respecting the sync options in this case.
|
||||
|
||||
### New Features
|
||||
* CRC32C is now using the 3-way pipelined SSE algorithm `crc32c_3way` on supported platforms to improve performance. The system will choose to use this algorithm on supported platforms automatically whenever possible. If PCLMULQDQ is not supported it will fall back to the old Fast_CRC32 algorithm.
|
||||
* `DBOptions::writable_file_max_buffer_size` can now be changed dynamically.
|
||||
* `DBOptions::bytes_per_sync`, `DBOptions::compaction_readahead_size`, and `DBOptions::wal_bytes_per_sync` can now be changed dynamically, `DBOptions::wal_bytes_per_sync` will flush all memtables and switch to a new WAL file.
|
||||
* Support dynamic adjustment of rate limit according to demand for background I/O. It can be enabled by passing `true` to the `auto_tuned` parameter in `NewGenericRateLimiter()`. The value passed as `rate_bytes_per_sec` will still be respected as an upper-bound.
|
||||
|
||||
+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`.
|
||||
|
||||
|
||||
@@ -344,6 +344,8 @@ ifeq ($(HAVE_POWER8),1)
|
||||
LIB_CC_OBJECTS = $(LIB_SOURCES:.cc=.o)
|
||||
LIBOBJECTS += $(LIB_SOURCES_C:.c=.o)
|
||||
LIBOBJECTS += $(LIB_SOURCES_ASM:.S=.o)
|
||||
else
|
||||
LIB_CC_OBJECTS = $(LIB_SOURCES:.cc=.o)
|
||||
endif
|
||||
|
||||
LIBOBJECTS += $(TOOL_LIB_SOURCES:.cc=.o)
|
||||
@@ -507,6 +509,7 @@ PARALLEL_TEST = \
|
||||
backupable_db_test \
|
||||
db_compaction_filter_test \
|
||||
db_compaction_test \
|
||||
db_merge_operator_test \
|
||||
db_sst_test \
|
||||
db_test \
|
||||
db_universal_compaction_test \
|
||||
@@ -518,7 +521,7 @@ PARALLEL_TEST = \
|
||||
persistent_cache_test \
|
||||
table_test \
|
||||
transaction_test \
|
||||
write_prepared_transaction_test
|
||||
write_prepared_transaction_test \
|
||||
|
||||
SUBSET := $(TESTS)
|
||||
ifdef ROCKSDBTESTS_START
|
||||
@@ -1553,11 +1556,11 @@ BZIP2_DOWNLOAD_BASE ?= http://www.bzip.org
|
||||
SNAPPY_VER ?= 1.1.4
|
||||
SNAPPY_SHA256 ?= 134bfe122fd25599bb807bb8130e7ba6d9bdb851e0b16efcb83ac4f5d0b70057
|
||||
SNAPPY_DOWNLOAD_BASE ?= https://github.com/google/snappy/releases/download
|
||||
LZ4_VER ?= 1.7.5
|
||||
LZ4_SHA256 ?= 0190cacd63022ccb86f44fa5041dc6c3804407ad61550ca21c382827319e7e7e
|
||||
LZ4_VER ?= 1.8.0
|
||||
LZ4_SHA256 ?= 2ca482ea7a9bb103603108b5a7510b7592b90158c151ff50a28f1ca8389fccf6
|
||||
LZ4_DOWNLOAD_BASE ?= https://github.com/lz4/lz4/archive
|
||||
ZSTD_VER ?= 1.2.0
|
||||
ZSTD_SHA256 ?= 4a7e4593a3638276ca7f2a09dc4f38e674d8317bbea51626393ca73fc047cbfb
|
||||
ZSTD_VER ?= 1.3.3
|
||||
ZSTD_SHA256 ?= a77c47153ee7de02626c5b2a097005786b71688be61e9fb81806a011f90b297b
|
||||
ZSTD_DOWNLOAD_BASE ?= https://github.com/facebook/zstd/archive
|
||||
|
||||
ifeq ($(PLATFORM), OS_MACOSX)
|
||||
@@ -1571,7 +1574,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
|
||||
@@ -1597,7 +1600,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:
|
||||
@@ -1609,7 +1612,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:
|
||||
@@ -1622,7 +1625,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:
|
||||
@@ -1635,7 +1638,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:
|
||||
@@ -1648,29 +1651,45 @@ libzstd.a:
|
||||
exit 1; \
|
||||
fi
|
||||
tar xvzf zstd-$(ZSTD_VER).tar.gz
|
||||
cd zstd-$(ZSTD_VER)/lib && make CFLAGS='-fPIC -O2 ${EXTRA_CFLAGS}' all
|
||||
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
|
||||
java_static_libobjects = $(patsubst %,jls/%,$(LIBOBJECTS))
|
||||
java_static_libobjects = $(patsubst %,jls/%,$(LIB_CC_OBJECTS))
|
||||
CLEAN_FILES += jls
|
||||
java_static_all_libobjects = $(java_static_libobjects)
|
||||
|
||||
ifneq ($(ROCKSDB_JAVA_NO_COMPRESSION), 1)
|
||||
JAVA_COMPRESSIONS = libz.a libbz2.a libsnappy.a liblz4.a libzstd.a
|
||||
endif
|
||||
|
||||
JAVA_STATIC_FLAGS = -DZLIB -DBZIP2 -DSNAPPY -DLZ4 -DZSTD
|
||||
JAVA_STATIC_INCLUDES = -I./zlib-$(ZLIB_VER) -I./bzip2-$(BZIP2_VER) -I./snappy-$(SNAPPY_VER) -I./lz4-$(LZ4_VER)/lib -I./zstd-$(ZSTD_VER)/lib
|
||||
JAVA_STATIC_INCLUDES = -I./zlib-$(ZLIB_VER) -I./bzip2-$(BZIP2_VER) -I./snappy-$(SNAPPY_VER) -I./lz4-$(LZ4_VER)/lib -I./zstd-$(ZSTD_VER)/lib/include
|
||||
|
||||
ifeq ($(HAVE_POWER8),1)
|
||||
JAVA_STATIC_C_LIBOBJECTS = $(patsubst %.c.o,jls/%.c.o,$(LIB_SOURCES_C:.c=.o))
|
||||
JAVA_STATIC_ASM_LIBOBJECTS = $(patsubst %.S.o,jls/%.S.o,$(LIB_SOURCES_ASM:.S=.o))
|
||||
|
||||
java_static_ppc_libobjects = $(JAVA_STATIC_C_LIBOBJECTS) $(JAVA_STATIC_ASM_LIBOBJECTS)
|
||||
|
||||
jls/util/crc32c_ppc.o: util/crc32c_ppc.c
|
||||
$(AM_V_CC)$(CC) $(CFLAGS) $(JAVA_STATIC_FLAGS) $(JAVA_STATIC_INCLUDES) -c $< -o $@
|
||||
|
||||
jls/util/crc32c_ppc_asm.o: util/crc32c_ppc_asm.S
|
||||
$(AM_V_CC)$(CC) $(CFLAGS) $(JAVA_STATIC_FLAGS) $(JAVA_STATIC_INCLUDES) -c $< -o $@
|
||||
|
||||
java_static_all_libobjects += $(java_static_ppc_libobjects)
|
||||
endif
|
||||
|
||||
$(java_static_libobjects): jls/%.o: %.cc $(JAVA_COMPRESSIONS)
|
||||
$(AM_V_CC)mkdir -p $(@D) && $(CXX) $(CXXFLAGS) $(JAVA_STATIC_FLAGS) $(JAVA_STATIC_INCLUDES) -fPIC -c $< -o $@ $(COVERAGEFLAGS)
|
||||
|
||||
rocksdbjavastatic: $(java_static_libobjects)
|
||||
rocksdbjavastatic: $(java_static_all_libobjects)
|
||||
cd java;$(MAKE) javalib;
|
||||
rm -f ./java/target/$(ROCKSDBJNILIB)
|
||||
$(CXX) $(CXXFLAGS) -I./java/. $(JAVA_INCLUDE) -shared -fPIC \
|
||||
-o ./java/target/$(ROCKSDBJNILIB) $(JNI_NATIVE_SOURCES) \
|
||||
$(java_static_libobjects) $(COVERAGEFLAGS) \
|
||||
$(java_static_all_libobjects) $(COVERAGEFLAGS) \
|
||||
$(JAVA_COMPRESSIONS) $(JAVA_STATIC_LDFLAGS)
|
||||
cd java/target;strip $(STRIPFLAGS) $(ROCKSDBJNILIB)
|
||||
cd java;jar -cf target/$(ROCKSDB_JAR) HISTORY*.md
|
||||
@@ -1731,7 +1750,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.
|
||||
|
||||
@@ -157,6 +157,7 @@ cpp_library(
|
||||
"table/block_based_table_factory.cc",
|
||||
"table/block_based_table_reader.cc",
|
||||
"table/block_builder.cc",
|
||||
"table/block_fetcher.cc",
|
||||
"table/block_prefix_index.cc",
|
||||
"table/bloom_block.cc",
|
||||
"table/cuckoo_table_builder.cc",
|
||||
@@ -566,7 +567,7 @@ ROCKS_TESTS = [
|
||||
[
|
||||
"db_merge_operator_test",
|
||||
"db/db_merge_operator_test.cc",
|
||||
"serial",
|
||||
"parallel",
|
||||
],
|
||||
[
|
||||
"db_options_test",
|
||||
@@ -1016,7 +1017,7 @@ ROCKS_TESTS = [
|
||||
[
|
||||
"write_prepared_transaction_test",
|
||||
"utilities/transactions/write_prepared_transaction_test.cc",
|
||||
"serial",
|
||||
"parallel",
|
||||
],
|
||||
]
|
||||
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ image: Visual Studio 2015
|
||||
before_build:
|
||||
- md %APPVEYOR_BUILD_FOLDER%\build
|
||||
- cd %APPVEYOR_BUILD_FOLDER%\build
|
||||
- cmake -G "Visual Studio 14 2015 Win64" -DOPTDBG=1 -DXPRESS=1 -DPORTABLE=1 ..
|
||||
- cmake -G "Visual Studio 14 Win64" -DOPTDBG=1 -DWITH_XPRESS=1 -DPORTABLE=1 ..
|
||||
- cd ..
|
||||
build:
|
||||
project: build\rocksdb.sln
|
||||
|
||||
@@ -141,6 +141,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 +482,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
|
||||
|
||||
@@ -1999,6 +1999,11 @@ void rocksdb_options_optimize_universal_style_compaction(
|
||||
opt->rep.OptimizeUniversalStyleCompaction(memtable_memory_budget);
|
||||
}
|
||||
|
||||
void rocksdb_options_set_allow_ingest_behind(
|
||||
rocksdb_options_t* opt, unsigned char v) {
|
||||
opt->rep.allow_ingest_behind = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_compaction_filter(
|
||||
rocksdb_options_t* opt,
|
||||
rocksdb_compactionfilter_t* filter) {
|
||||
|
||||
+6
-3
@@ -385,7 +385,8 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
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.
|
||||
@@ -1059,10 +1060,12 @@ 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__)) = cfd->Unref();
|
||||
assert(last_ref);
|
||||
delete cfd;
|
||||
}
|
||||
dummy_cfd_->Unref();
|
||||
bool dummy_last_ref __attribute__((__unused__)) = dummy_cfd_->Unref();
|
||||
assert(dummy_last_ref);
|
||||
delete dummy_cfd_;
|
||||
}
|
||||
|
||||
|
||||
+8
-1
@@ -239,7 +239,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();
|
||||
@@ -419,6 +423,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
|
||||
|
||||
@@ -1868,126 +1868,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
|
||||
|
||||
+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 {
|
||||
|
||||
@@ -184,8 +184,11 @@ void CompactionIterator::InvokeFilterIfNeeded(bool* need_skip,
|
||||
Slice* skip_until) {
|
||||
if (compaction_filter_ != nullptr &&
|
||||
(ikey_.type == kTypeValue || ikey_.type == kTypeBlobIndex) &&
|
||||
(visible_at_tip_ || ikey_.sequence > latest_snapshot_ ||
|
||||
ignore_snapshots_)) {
|
||||
(visible_at_tip_ || ignore_snapshots_ ||
|
||||
ikey_.sequence > latest_snapshot_ ||
|
||||
(snapshot_checker_ != nullptr &&
|
||||
UNLIKELY(!snapshot_checker_->IsInSnapshot(ikey_.sequence,
|
||||
latest_snapshot_))))) {
|
||||
// If the user has specified a compaction filter and the sequence
|
||||
// number is greater than any external snapshot, then invoke the
|
||||
// filter. If the return value of the compaction filter is true,
|
||||
@@ -411,7 +414,10 @@ void CompactionIterator::NextFromInput() {
|
||||
cmp_->Equal(ikey_.user_key, next_ikey.user_key)) {
|
||||
// Check whether the next key belongs to the same snapshot as the
|
||||
// SingleDelete.
|
||||
if (prev_snapshot == 0 || next_ikey.sequence > prev_snapshot) {
|
||||
if (prev_snapshot == 0 || next_ikey.sequence > prev_snapshot ||
|
||||
(snapshot_checker_ != nullptr &&
|
||||
UNLIKELY(!snapshot_checker_->IsInSnapshot(next_ikey.sequence,
|
||||
prev_snapshot)))) {
|
||||
if (next_ikey.type == kTypeSingleDeletion) {
|
||||
// We encountered two SingleDeletes in a row. This could be due to
|
||||
// unexpected user input.
|
||||
@@ -422,8 +428,12 @@ void CompactionIterator::NextFromInput() {
|
||||
// input_->Next().
|
||||
++iter_stats_.num_record_drop_obsolete;
|
||||
++iter_stats_.num_single_del_mismatch;
|
||||
} else if ((ikey_.sequence <= earliest_write_conflict_snapshot_) ||
|
||||
has_outputted_key_) {
|
||||
} else if (has_outputted_key_ ||
|
||||
(ikey_.sequence <= earliest_write_conflict_snapshot_ &&
|
||||
(snapshot_checker_ == nullptr ||
|
||||
LIKELY(snapshot_checker_->IsInSnapshot(
|
||||
ikey_.sequence,
|
||||
earliest_write_conflict_snapshot_))))) {
|
||||
// Found a matching value, we can drop the single delete and the
|
||||
// value. It is safe to drop both records since we've already
|
||||
// outputted a key in this snapshot, or there is no earlier
|
||||
@@ -471,6 +481,9 @@ void CompactionIterator::NextFromInput() {
|
||||
// comparison, so the value of has_current_user_key does not matter.
|
||||
has_current_user_key_ = false;
|
||||
if (compaction_ != nullptr && ikey_.sequence <= earliest_snapshot_ &&
|
||||
(snapshot_checker_ == nullptr ||
|
||||
LIKELY(snapshot_checker_->IsInSnapshot(ikey_.sequence,
|
||||
earliest_snapshot_))) &&
|
||||
compaction_->KeyNotExistsBeyondOutputLevel(ikey_.user_key,
|
||||
&level_ptrs_)) {
|
||||
// Key doesn't exist outside of this range.
|
||||
@@ -504,6 +517,9 @@ void CompactionIterator::NextFromInput() {
|
||||
input_->Next();
|
||||
} else if (compaction_ != nullptr && ikey_.type == kTypeDeletion &&
|
||||
ikey_.sequence <= earliest_snapshot_ &&
|
||||
(snapshot_checker_ == nullptr ||
|
||||
LIKELY(snapshot_checker_->IsInSnapshot(ikey_.sequence,
|
||||
earliest_snapshot_))) &&
|
||||
ikeyNotNeededForIncrementalSnapshot() &&
|
||||
compaction_->KeyNotExistsBeyondOutputLevel(ikey_.user_key,
|
||||
&level_ptrs_)) {
|
||||
@@ -538,6 +554,10 @@ 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();
|
||||
|
||||
@@ -625,8 +625,8 @@ Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options) {
|
||||
"[%s] compacted to: %s, MB/sec: %.1f rd, %.1f wr, level %d, "
|
||||
"files in(%d, %d) out(%d) "
|
||||
"MB in(%.1f, %.1f) out(%.1f), read-write-amplify(%.1f) "
|
||||
"write-amplify(%.1f) %s, records in: %d, records dropped: %d "
|
||||
"output_compression: %s\n",
|
||||
"write-amplify(%.1f) %s, records in: %" PRIu64
|
||||
", records dropped: %" PRIu64 " output_compression: %s\n",
|
||||
cfd->GetName().c_str(), vstorage->LevelSummary(&tmp), bytes_read_per_sec,
|
||||
bytes_written_per_sec, compact_->compaction->output_level(),
|
||||
stats.num_input_files_in_non_output_levels,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+57
-6
@@ -570,19 +570,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 +592,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 +847,57 @@ TEST_F(DBBasicTest, MmapAndBufferOptions) {
|
||||
}
|
||||
#endif
|
||||
|
||||
class TestEnv : public EnvWrapper {
|
||||
public:
|
||||
explicit TestEnv(Env* base) : EnvWrapper(base) { };
|
||||
|
||||
class TestLogger : public Logger {
|
||||
public:
|
||||
using Logger::Logv;
|
||||
virtual void Logv(const char *format, va_list ap) override { };
|
||||
private:
|
||||
virtual Status CloseImpl() override {
|
||||
return Status::NotSupported();
|
||||
}
|
||||
};
|
||||
|
||||
virtual Status NewLogger(const std::string& fname,
|
||||
shared_ptr<Logger>* result) {
|
||||
result->reset(new TestLogger());
|
||||
return Status::OK();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(DBBasicTest, DBClose) {
|
||||
Options options = GetDefaultOptions();
|
||||
std::string dbname = test::TmpDir(env_) + "/db_close_test";
|
||||
ASSERT_OK(DestroyDB(dbname, options));
|
||||
|
||||
DB* db = nullptr;
|
||||
options.create_if_missing = true;
|
||||
options.env = new TestEnv(Env::Default());
|
||||
Status s = DB::Open(options, dbname, &db);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_TRUE(db != nullptr);
|
||||
|
||||
s = db->Close();
|
||||
ASSERT_EQ(s, Status::NotSupported());
|
||||
|
||||
delete db;
|
||||
|
||||
// Provide our own logger and ensure DB::Close() does not close it
|
||||
options.info_log.reset(new TestEnv::TestLogger());
|
||||
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;
|
||||
delete options.env;
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -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,
|
||||
@@ -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",
|
||||
"DBImpl::FlushMemTableToOutputFile:BeforeInstallSV"}});
|
||||
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;
|
||||
|
||||
+36
-15
@@ -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.
|
||||
@@ -275,7 +278,7 @@ void DBImpl::CancelAllBackgroundWork(bool wait) {
|
||||
}
|
||||
}
|
||||
|
||||
DBImpl::~DBImpl() {
|
||||
Status DBImpl::CloseImpl() {
|
||||
// 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))
|
||||
@@ -291,7 +294,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();
|
||||
}
|
||||
@@ -378,8 +382,16 @@ DBImpl::~DBImpl() {
|
||||
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "Shutdown complete");
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
|
||||
Status s = Status::OK();
|
||||
if (immutable_db_options_.info_log && own_info_log_) {
|
||||
s = immutable_db_options_.info_log->Close();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
DBImpl::~DBImpl() { Close(); }
|
||||
|
||||
void DBImpl::MaybeIgnoreError(Status* s) const {
|
||||
if (s->ok() || immutable_db_options_.paranoid_checks) {
|
||||
// No change needed
|
||||
@@ -1510,7 +1522,8 @@ ArenaWrappedDBIter* DBImpl::NewIteratorImpl(const ReadOptions& read_options,
|
||||
ColumnFamilyData* cfd,
|
||||
SequenceNumber snapshot,
|
||||
ReadCallback* read_callback,
|
||||
bool allow_blob) {
|
||||
bool allow_blob,
|
||||
bool allow_refresh) {
|
||||
SuperVersion* sv = cfd->GetReferencedSuperVersion(&mutex_);
|
||||
|
||||
// Try to generate a DB iterator tree in continuous memory area to be
|
||||
@@ -1559,7 +1572,8 @@ ArenaWrappedDBIter* DBImpl::NewIteratorImpl(const ReadOptions& read_options,
|
||||
env_, read_options, *cfd->ioptions(), snapshot,
|
||||
sv->mutable_cf_options.max_sequential_skip_in_iterations,
|
||||
sv->version_number, read_callback,
|
||||
((read_options.snapshot != nullptr) ? nullptr : this), cfd, allow_blob);
|
||||
((read_options.snapshot != nullptr) ? nullptr : this), cfd, allow_blob,
|
||||
allow_refresh);
|
||||
|
||||
InternalIterator* internal_iter =
|
||||
NewInternalIterator(read_options, cfd, sv, db_iter->GetArena(),
|
||||
@@ -2045,8 +2059,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(),
|
||||
@@ -2319,7 +2332,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,
|
||||
|
||||
+27
-4
@@ -148,7 +148,8 @@ class DBImpl : public DB {
|
||||
ColumnFamilyData* cfd,
|
||||
SequenceNumber snapshot,
|
||||
ReadCallback* read_callback,
|
||||
bool allow_blob = false);
|
||||
bool allow_blob = false,
|
||||
bool allow_refresh = true);
|
||||
|
||||
virtual const Snapshot* GetSnapshot() override;
|
||||
virtual void ReleaseSnapshot(const Snapshot* snapshot) override;
|
||||
@@ -467,6 +468,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);
|
||||
|
||||
@@ -610,10 +613,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_;
|
||||
@@ -806,8 +813,12 @@ class DBImpl : public DB {
|
||||
Status FlushMemTable(ColumnFamilyData* cfd, const FlushOptions& options,
|
||||
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);
|
||||
@@ -911,6 +922,9 @@ class DBImpl : public DB {
|
||||
|
||||
uint64_t GetMaxTotalWalSize() const;
|
||||
|
||||
// Actual implementation of Close()
|
||||
virtual Status CloseImpl();
|
||||
|
||||
// table_cache_ provides its own synchronization
|
||||
std::shared_ptr<Cache> table_cache_;
|
||||
|
||||
@@ -939,6 +953,8 @@ 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.
|
||||
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,
|
||||
@@ -946,7 +962,7 @@ class DBImpl : public DB {
|
||||
// from the same write_thread_ without any locks.
|
||||
uint64_t logfile_number_;
|
||||
std::deque<uint64_t>
|
||||
log_recycle_files; // a list of log files that we can recycle
|
||||
log_recycle_files_; // a list of log files that we can recycle
|
||||
bool log_dir_synced_;
|
||||
// Without two_write_queues, read and writes to log_empty_ are protected by
|
||||
// mutex_. Since it is currently updated/read only in write_thread_, it can be
|
||||
@@ -1205,6 +1221,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_;
|
||||
@@ -1358,6 +1378,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,
|
||||
|
||||
@@ -134,6 +134,7 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
TEST_SYNC_POINT("DBImpl::FlushMemTableToOutputFile:BeforeInstallSV");
|
||||
InstallSuperVersionAndScheduleWork(cfd, &job_context->superversion_context,
|
||||
mutable_cf_options);
|
||||
if (made_progress) {
|
||||
@@ -441,7 +442,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 +811,13 @@ 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);
|
||||
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,
|
||||
@@ -944,6 +952,7 @@ Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
|
||||
const FlushOptions& flush_options,
|
||||
bool writes_stopped) {
|
||||
Status s;
|
||||
uint64_t flush_memtable_id = 0;
|
||||
{
|
||||
WriteContext context;
|
||||
InstrumentedMutexLock guard_lock(&mutex_);
|
||||
@@ -961,6 +970,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);
|
||||
@@ -975,16 +985,19 @@ Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
|
||||
|
||||
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 +1072,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;
|
||||
}
|
||||
|
||||
@@ -1303,8 +1317,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 +1400,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 +1520,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");
|
||||
|
||||
|
||||
+17
-10
@@ -238,11 +238,11 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
while (alive_log_files_.begin()->number < min_log_number) {
|
||||
auto& earliest = *alive_log_files_.begin();
|
||||
if (immutable_db_options_.recycle_log_file_num >
|
||||
log_recycle_files.size()) {
|
||||
log_recycle_files_.size()) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"adding log %" PRIu64 " to recycle list\n",
|
||||
earliest.number);
|
||||
log_recycle_files.push_back(earliest.number);
|
||||
log_recycle_files_.push_back(earliest.number);
|
||||
} else {
|
||||
job_context->log_delete_files.push_back(earliest.number);
|
||||
}
|
||||
@@ -283,8 +283,11 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
// We're just cleaning up for DB::Write().
|
||||
assert(job_context->logs_to_free.empty());
|
||||
job_context->logs_to_free = logs_to_free_;
|
||||
job_context->log_recycle_files.assign(log_recycle_files.begin(),
|
||||
log_recycle_files.end());
|
||||
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() {
|
||||
|
||||
+12
-8
@@ -521,9 +521,6 @@ Status DBImpl::WriteImplWALOnly(const WriteOptions& write_options,
|
||||
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);
|
||||
if (write_options.disableWAL) {
|
||||
return status;
|
||||
}
|
||||
RecordTick(stats_, WRITE_WITH_WAL);
|
||||
StopWatch write_sw(env_, immutable_db_options_.statistics.get(), DB_WRITE);
|
||||
|
||||
@@ -580,7 +577,13 @@ Status DBImpl::WriteImplWALOnly(const WriteOptions& write_options,
|
||||
// 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*/;
|
||||
status = ConcurrentWriteToWAL(write_group, log_used, &last_sequence, seq_inc);
|
||||
if (!write_options.disableWAL) {
|
||||
status =
|
||||
ConcurrentWriteToWAL(write_group, log_used, &last_sequence, seq_inc);
|
||||
} else {
|
||||
// Otherwise we inc seq number to do solely the seq allocation
|
||||
last_sequence = versions_->FetchAddLastAllocatedSequence(seq_inc);
|
||||
}
|
||||
auto curr_seq = last_sequence + 1;
|
||||
for (auto* writer : write_group) {
|
||||
if (writer->CallbackFailed()) {
|
||||
@@ -593,6 +596,7 @@ Status DBImpl::WriteImplWALOnly(const WriteOptions& write_options,
|
||||
// else seq advances only by memtable writes
|
||||
}
|
||||
if (status.ok() && write_options.sync) {
|
||||
assert(!write_options.disableWAL);
|
||||
// Requesting sync with two_write_queues_ is expected to be very rare. We
|
||||
// hance provide a simple implementation that is not necessarily efficient.
|
||||
if (manual_wal_flush_) {
|
||||
@@ -693,7 +697,7 @@ Status DBImpl::PreprocessWrite(const WriteOptions& write_options,
|
||||
}
|
||||
|
||||
if (UNLIKELY(status.ok() && !bg_error_.ok())) {
|
||||
return bg_error_;
|
||||
status = bg_error_;
|
||||
}
|
||||
|
||||
if (UNLIKELY(status.ok() && !flush_scheduler_.Empty())) {
|
||||
@@ -1211,9 +1215,9 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
}
|
||||
uint64_t recycle_log_number = 0;
|
||||
if (creating_new_log && immutable_db_options_.recycle_log_file_num &&
|
||||
!log_recycle_files.empty()) {
|
||||
recycle_log_number = log_recycle_files.front();
|
||||
log_recycle_files.pop_front();
|
||||
!log_recycle_files_.empty()) {
|
||||
recycle_log_number = log_recycle_files_.front();
|
||||
log_recycle_files_.pop_front();
|
||||
}
|
||||
uint64_t new_log_number =
|
||||
creating_new_log ? versions_->NewFileNumber() : logfile_number_;
|
||||
|
||||
+19
-6
@@ -972,6 +972,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(
|
||||
@@ -1378,17 +1389,19 @@ void ArenaWrappedDBIter::Init(Env* env, const ReadOptions& read_options,
|
||||
const SequenceNumber& sequence,
|
||||
uint64_t max_sequential_skip_in_iteration,
|
||||
uint64_t version_number,
|
||||
ReadCallback* read_callback, bool allow_blob) {
|
||||
ReadCallback* read_callback, bool allow_blob,
|
||||
bool allow_refresh) {
|
||||
auto mem = arena_.AllocateAligned(sizeof(DBIter));
|
||||
db_iter_ = new (mem)
|
||||
DBIter(env, read_options, cf_options, cf_options.user_comparator, nullptr,
|
||||
sequence, true, max_sequential_skip_in_iteration, read_callback,
|
||||
allow_blob);
|
||||
sv_number_ = version_number;
|
||||
allow_refresh_ = allow_refresh;
|
||||
}
|
||||
|
||||
Status ArenaWrappedDBIter::Refresh() {
|
||||
if (cfd_ == nullptr || db_impl_ == nullptr) {
|
||||
if (cfd_ == nullptr || db_impl_ == nullptr || !allow_refresh_) {
|
||||
return Status::NotSupported("Creating renew iterator is not allowed.");
|
||||
}
|
||||
assert(db_iter_ != nullptr);
|
||||
@@ -1406,7 +1419,7 @@ Status ArenaWrappedDBIter::Refresh() {
|
||||
SuperVersion* sv = cfd_->GetReferencedSuperVersion(db_impl_->mutex());
|
||||
Init(env, read_options_, *(cfd_->ioptions()), latest_seq,
|
||||
sv->mutable_cf_options.max_sequential_skip_in_iterations,
|
||||
cur_sv_number, read_callback_, allow_blob_);
|
||||
cur_sv_number, read_callback_, allow_blob_, allow_refresh_);
|
||||
|
||||
InternalIterator* internal_iter = db_impl_->NewInternalIterator(
|
||||
read_options_, cfd_, sv, &arena_, db_iter_->GetRangeDelAggregator());
|
||||
@@ -1423,12 +1436,12 @@ ArenaWrappedDBIter* NewArenaWrappedDbIterator(
|
||||
const ImmutableCFOptions& cf_options, const SequenceNumber& sequence,
|
||||
uint64_t max_sequential_skip_in_iterations, uint64_t version_number,
|
||||
ReadCallback* read_callback, DBImpl* db_impl, ColumnFamilyData* cfd,
|
||||
bool allow_blob) {
|
||||
bool allow_blob, bool allow_refresh) {
|
||||
ArenaWrappedDBIter* iter = new ArenaWrappedDBIter();
|
||||
iter->Init(env, read_options, cf_options, sequence,
|
||||
max_sequential_skip_in_iterations, version_number, read_callback,
|
||||
allow_blob);
|
||||
if (db_impl != nullptr && cfd != nullptr) {
|
||||
allow_blob, allow_refresh);
|
||||
if (db_impl != nullptr && cfd != nullptr && allow_refresh) {
|
||||
iter->StoreRefreshInfo(read_options, db_impl, cfd, read_callback,
|
||||
allow_blob);
|
||||
}
|
||||
|
||||
+4
-3
@@ -73,7 +73,7 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
const ImmutableCFOptions& cf_options,
|
||||
const SequenceNumber& sequence,
|
||||
uint64_t max_sequential_skip_in_iterations, uint64_t version_number,
|
||||
ReadCallback* read_callback, bool allow_blob);
|
||||
ReadCallback* read_callback, bool allow_blob, bool allow_refresh);
|
||||
|
||||
void StoreRefreshInfo(const ReadOptions& read_options, DBImpl* db_impl,
|
||||
ColumnFamilyData* cfd, ReadCallback* read_callback,
|
||||
@@ -94,6 +94,7 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
ReadOptions read_options_;
|
||||
ReadCallback* read_callback_;
|
||||
bool allow_blob_ = false;
|
||||
bool allow_refresh_ = true;
|
||||
};
|
||||
|
||||
// Generate the arena wrapped iterator class.
|
||||
@@ -104,6 +105,6 @@ extern ArenaWrappedDBIter* NewArenaWrappedDbIterator(
|
||||
const ImmutableCFOptions& cf_options, const SequenceNumber& sequence,
|
||||
uint64_t max_sequential_skip_in_iterations, uint64_t version_number,
|
||||
ReadCallback* read_callback, DBImpl* db_impl = nullptr,
|
||||
ColumnFamilyData* cfd = nullptr, bool allow_blob = false);
|
||||
|
||||
ColumnFamilyData* cfd = nullptr, bool allow_blob = false,
|
||||
bool allow_refresh = true);
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "db/db_iter.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/port.h"
|
||||
#include "port/stack_trace.h"
|
||||
@@ -2157,6 +2158,141 @@ TEST_F(DBIteratorTest, SkipStatistics) {
|
||||
ASSERT_EQ(skip_count, TestGetTickerCount(options, NUMBER_ITER_SKIP));
|
||||
}
|
||||
|
||||
TEST_F(DBIteratorTest, ReadCallback) {
|
||||
class TestReadCallback : public ReadCallback {
|
||||
public:
|
||||
explicit TestReadCallback(SequenceNumber last_visible_seq)
|
||||
: last_visible_seq_(last_visible_seq) {}
|
||||
|
||||
bool IsCommitted(SequenceNumber seq) override {
|
||||
return seq <= last_visible_seq_;
|
||||
}
|
||||
|
||||
private:
|
||||
SequenceNumber last_visible_seq_;
|
||||
};
|
||||
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
ASSERT_OK(Put("foo", "v2"));
|
||||
ASSERT_OK(Put("foo", "v3"));
|
||||
ASSERT_OK(Put("a", "va"));
|
||||
ASSERT_OK(Put("z", "vz"));
|
||||
SequenceNumber seq1 = db_->GetLatestSequenceNumber();
|
||||
TestReadCallback callback1(seq1);
|
||||
ASSERT_OK(Put("foo", "v4"));
|
||||
ASSERT_OK(Put("foo", "v5"));
|
||||
ASSERT_OK(Put("bar", "v7"));
|
||||
|
||||
SequenceNumber seq2 = db_->GetLatestSequenceNumber();
|
||||
auto* cfd =
|
||||
reinterpret_cast<ColumnFamilyHandleImpl*>(db_->DefaultColumnFamily())
|
||||
->cfd();
|
||||
// The iterator are suppose to see data before seq1.
|
||||
Iterator* iter =
|
||||
dbfull()->NewIteratorImpl(ReadOptions(), cfd, seq2, &callback1);
|
||||
|
||||
// Seek
|
||||
// The latest value of "foo" before seq1 is "v3"
|
||||
iter->Seek("foo");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ("foo", iter->key());
|
||||
ASSERT_EQ("v3", iter->value());
|
||||
// "bar" is not visible to the iterator. It will move on to the next key
|
||||
// "foo".
|
||||
iter->Seek("bar");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ("foo", iter->key());
|
||||
ASSERT_EQ("v3", iter->value());
|
||||
|
||||
// Next
|
||||
// Seek to "a"
|
||||
iter->Seek("a");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ("va", iter->value());
|
||||
// "bar" is not visible to the iterator. It will move on to the next key
|
||||
// "foo".
|
||||
iter->Next();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ("foo", iter->key());
|
||||
ASSERT_EQ("v3", iter->value());
|
||||
|
||||
// Prev
|
||||
// Seek to "z"
|
||||
iter->Seek("z");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ("vz", iter->value());
|
||||
// The previous key is "foo", which is visible to the iterator.
|
||||
iter->Prev();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ("foo", iter->key());
|
||||
ASSERT_EQ("v3", iter->value());
|
||||
// "bar" is not visible to the iterator. It will move on to the next key "a".
|
||||
iter->Prev(); // skipping "bar"
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ("a", iter->key());
|
||||
ASSERT_EQ("va", iter->value());
|
||||
|
||||
// SeekForPrev
|
||||
// The previous key is "foo", which is visible to the iterator.
|
||||
iter->SeekForPrev("y");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ("foo", iter->key());
|
||||
ASSERT_EQ("v3", iter->value());
|
||||
// "bar" is not visible to the iterator. It will move on to the next key "a".
|
||||
iter->SeekForPrev("bar");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ("a", iter->key());
|
||||
ASSERT_EQ("va", iter->value());
|
||||
|
||||
delete iter;
|
||||
|
||||
// Prev beyond max_sequential_skip_in_iterations
|
||||
uint64_t num_versions =
|
||||
CurrentOptions().max_sequential_skip_in_iterations + 10;
|
||||
for (uint64_t i = 0; i < num_versions; i++) {
|
||||
ASSERT_OK(Put("bar", ToString(i)));
|
||||
}
|
||||
SequenceNumber seq3 = db_->GetLatestSequenceNumber();
|
||||
TestReadCallback callback2(seq3);
|
||||
ASSERT_OK(Put("bar", "v8"));
|
||||
SequenceNumber seq4 = db_->GetLatestSequenceNumber();
|
||||
|
||||
// The iterator is suppose to see data before seq3.
|
||||
iter = dbfull()->NewIteratorImpl(ReadOptions(), cfd, seq4, &callback2);
|
||||
// Seek to "z", which is visible.
|
||||
iter->Seek("z");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ("vz", iter->value());
|
||||
// Previous key is "foo" and the last value "v5" is visible.
|
||||
iter->Prev();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ("foo", iter->key());
|
||||
ASSERT_EQ("v5", iter->value());
|
||||
// Since the number of values of "bar" is more than
|
||||
// max_sequential_skip_in_iterations, Prev() will ultimately fallback to
|
||||
// seek in forward direction. Here we test the fallback seek is correct.
|
||||
// The last visible value should be (num_versions - 1), as "v8" is not
|
||||
// visible.
|
||||
iter->Prev();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ("bar", iter->key());
|
||||
ASSERT_EQ(ToString(num_versions - 1), iter->value());
|
||||
|
||||
delete iter;
|
||||
}
|
||||
|
||||
} // 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) {
|
||||
|
||||
@@ -376,6 +376,30 @@ TEST_F(DBSSTTest, RateLimitedDelete) {
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, OpenDBWithExistingTrash) {
|
||||
Options options = CurrentOptions();
|
||||
|
||||
options.sst_file_manager.reset(
|
||||
NewSstFileManager(env_, nullptr, "", 1024 * 1024 /* 1 MB/sec */));
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(options.sst_file_manager.get());
|
||||
|
||||
Destroy(last_options_);
|
||||
|
||||
// Add some trash files to the db directory so the DB can clean them up
|
||||
env_->CreateDirIfMissing(dbname_);
|
||||
ASSERT_OK(WriteStringToFile(env_, "abc", dbname_ + "/" + "001.sst.trash"));
|
||||
ASSERT_OK(WriteStringToFile(env_, "abc", dbname_ + "/" + "002.sst.trash"));
|
||||
ASSERT_OK(WriteStringToFile(env_, "abc", dbname_ + "/" + "003.sst.trash"));
|
||||
|
||||
// Reopen the DB and verify that it deletes existing trash files
|
||||
ASSERT_OK(TryReopen(options));
|
||||
sfm->WaitForEmptyTrash();
|
||||
ASSERT_NOK(env_->FileExists(dbname_ + "/" + "001.sst.trash"));
|
||||
ASSERT_NOK(env_->FileExists(dbname_ + "/" + "002.sst.trash"));
|
||||
ASSERT_NOK(env_->FileExists(dbname_ + "/" + "003.sst.trash"));
|
||||
}
|
||||
|
||||
|
||||
// Create a DB with 2 db_paths, and generate multiple files in the 2
|
||||
// db_paths using CompactRangeOptions, make sure that files that were
|
||||
// deleted from first db_path were deleted using DeleteScheduler and
|
||||
|
||||
+73
-32
@@ -503,7 +503,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 +2193,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 {
|
||||
@@ -3488,12 +3492,16 @@ TEST_F(DBTest, GetThreadStatus) {
|
||||
const int kTestCount = 3;
|
||||
const unsigned int kHighPriCounts[kTestCount] = {3, 2, 5};
|
||||
const unsigned int kLowPriCounts[kTestCount] = {10, 15, 3};
|
||||
const unsigned int kBottomPriCounts[kTestCount] = {2, 1, 4};
|
||||
for (int test = 0; test < kTestCount; ++test) {
|
||||
// Change the number of threads in high / low priority pool.
|
||||
env_->SetBackgroundThreads(kHighPriCounts[test], Env::HIGH);
|
||||
env_->SetBackgroundThreads(kLowPriCounts[test], Env::LOW);
|
||||
env_->SetBackgroundThreads(kBottomPriCounts[test], Env::BOTTOM);
|
||||
// Wait to ensure the all threads has been registered
|
||||
unsigned int thread_type_counts[ThreadStatus::NUM_THREAD_TYPES];
|
||||
// TODO(ajkr): it'd be better if SetBackgroundThreads returned only after
|
||||
// all threads have been registered.
|
||||
// Try up to 60 seconds.
|
||||
for (int num_try = 0; num_try < 60000; num_try++) {
|
||||
env_->SleepForMicroseconds(1000);
|
||||
@@ -3508,20 +3516,21 @@ TEST_F(DBTest, GetThreadStatus) {
|
||||
if (thread_type_counts[ThreadStatus::HIGH_PRIORITY] ==
|
||||
kHighPriCounts[test] &&
|
||||
thread_type_counts[ThreadStatus::LOW_PRIORITY] ==
|
||||
kLowPriCounts[test]) {
|
||||
kLowPriCounts[test] &&
|
||||
thread_type_counts[ThreadStatus::BOTTOM_PRIORITY] ==
|
||||
kBottomPriCounts[test]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Verify the total number of threades
|
||||
ASSERT_EQ(thread_type_counts[ThreadStatus::HIGH_PRIORITY] +
|
||||
thread_type_counts[ThreadStatus::LOW_PRIORITY],
|
||||
kHighPriCounts[test] + kLowPriCounts[test]);
|
||||
// Verify the number of high-priority threads
|
||||
ASSERT_EQ(thread_type_counts[ThreadStatus::HIGH_PRIORITY],
|
||||
kHighPriCounts[test]);
|
||||
// Verify the number of low-priority threads
|
||||
ASSERT_EQ(thread_type_counts[ThreadStatus::LOW_PRIORITY],
|
||||
kLowPriCounts[test]);
|
||||
// Verify the number of bottom-priority threads
|
||||
ASSERT_EQ(thread_type_counts[ThreadStatus::BOTTOM_PRIORITY],
|
||||
kBottomPriCounts[test]);
|
||||
}
|
||||
if (i == 0) {
|
||||
// repeat the test with multiple column families
|
||||
@@ -4375,7 +4384,6 @@ TEST_F(DBTest, DynamicFIFOCompactionOptions) {
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction,
|
||||
true);
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBTest, DynamicUniversalCompactionOptions) {
|
||||
Options options;
|
||||
@@ -4444,6 +4452,7 @@ TEST_F(DBTest, DynamicUniversalCompactionOptions) {
|
||||
dbfull()->GetOptions().compaction_options_universal.allow_trivial_move,
|
||||
false);
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBTest, FileCreationRandomFailure) {
|
||||
Options options;
|
||||
@@ -5049,55 +5058,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
|
||||
@@ -5317,12 +5355,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_;
|
||||
};
|
||||
|
||||
|
||||
@@ -2430,6 +2430,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) {
|
||||
|
||||
+6
-2
@@ -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;
|
||||
@@ -1157,11 +1159,13 @@ UpdateStatus DBTestBase::updateInPlaceNoAction(char* prevValue,
|
||||
|
||||
// Utility method to test InplaceUpdate
|
||||
void DBTestBase::validateNumberOfEntries(int numValues, int cf) {
|
||||
ScopedArenaIterator iter;
|
||||
Arena arena;
|
||||
auto options = CurrentOptions();
|
||||
InternalKeyComparator icmp(options.comparator);
|
||||
RangeDelAggregator range_del_agg(icmp, {} /* snapshots */);
|
||||
// This should be defined after range_del_agg so that it destructs the
|
||||
// assigned iterator before it range_del_agg is already destructed.
|
||||
ScopedArenaIterator iter;
|
||||
if (cf != 0) {
|
||||
iter.set(
|
||||
dbfull()->NewInternalIterator(&arena, &range_del_agg, handles_[cf]));
|
||||
|
||||
+3
-1
@@ -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();
|
||||
|
||||
|
||||
@@ -1940,6 +1940,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) {
|
||||
|
||||
@@ -364,6 +364,7 @@ Status FlushJob::WriteLevel0Table() {
|
||||
InternalStats::CompactionStats stats(1);
|
||||
stats.micros = db_options_.env->NowMicros() - start_micros;
|
||||
stats.bytes_written = meta_.fd.GetFileSize();
|
||||
MeasureTime(stats_, FLUSH_TIME, stats.micros);
|
||||
cfd_->internal_stats()->AddCompactionStats(0 /* level */, stats);
|
||||
cfd_->internal_stats()->AddCFStats(InternalStats::BYTES_FLUSHED,
|
||||
meta_.fd.GetFileSize());
|
||||
|
||||
+16
-6
@@ -40,6 +40,7 @@ class FlushJobTest : public testing::Test {
|
||||
EXPECT_OK(env_->CreateDirIfMissing(dbname_));
|
||||
db_options_.db_paths.emplace_back(dbname_,
|
||||
std::numeric_limits<uint64_t>::max());
|
||||
db_options_.statistics = rocksdb::CreateDBStatistics();
|
||||
// TODO(icanadi) Remove this once we mock out VersionSet
|
||||
NewDB();
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
@@ -138,16 +139,22 @@ TEST_F(FlushJobTest, NonEmpty) {
|
||||
|
||||
EventLogger event_logger(db_options_.info_log.get());
|
||||
SnapshotChecker* snapshot_checker = nullptr; // not relavant
|
||||
FlushJob flush_job(
|
||||
dbname_, versions_->GetColumnFamilySet()->GetDefault(), db_options_,
|
||||
*cfd->GetLatestMutableCFOptions(), env_options_, versions_.get(), &mutex_,
|
||||
&shutting_down_, {}, kMaxSequenceNumber, snapshot_checker, &job_context,
|
||||
nullptr, nullptr, nullptr, kNoCompression, nullptr, &event_logger, true);
|
||||
FlushJob flush_job(dbname_, versions_->GetColumnFamilySet()->GetDefault(),
|
||||
db_options_, *cfd->GetLatestMutableCFOptions(),
|
||||
env_options_, versions_.get(), &mutex_, &shutting_down_,
|
||||
{}, kMaxSequenceNumber, snapshot_checker, &job_context,
|
||||
nullptr, nullptr, nullptr, kNoCompression,
|
||||
db_options_.statistics.get(), &event_logger, true);
|
||||
|
||||
HistogramData hist;
|
||||
FileMetaData fd;
|
||||
mutex_.Lock();
|
||||
flush_job.PickMemTable();
|
||||
ASSERT_OK(flush_job.Run(&fd));
|
||||
mutex_.Unlock();
|
||||
db_options_.statistics->histogramData(FLUSH_TIME, &hist);
|
||||
ASSERT_GT(hist.average, 0.0);
|
||||
|
||||
ASSERT_EQ(ToString(0), fd.smallest.user_key().ToString());
|
||||
ASSERT_EQ("9999a",
|
||||
fd.largest.user_key().ToString()); // range tombstone end key
|
||||
@@ -210,12 +217,15 @@ TEST_F(FlushJobTest, Snapshots) {
|
||||
env_options_, versions_.get(), &mutex_, &shutting_down_,
|
||||
snapshots, kMaxSequenceNumber, snapshot_checker,
|
||||
&job_context, nullptr, nullptr, nullptr, kNoCompression,
|
||||
nullptr, &event_logger, true);
|
||||
db_options_.statistics.get(), &event_logger, true);
|
||||
mutex_.Lock();
|
||||
flush_job.PickMemTable();
|
||||
ASSERT_OK(flush_job.Run());
|
||||
mutex_.Unlock();
|
||||
mock_table_factory_->AssertSingleFile(inserted_keys);
|
||||
HistogramData hist;
|
||||
db_options_.statistics->histogramData(FLUSH_TIME, &hist);
|
||||
ASSERT_GT(hist.average, 0.0);
|
||||
job_context.Clean();
|
||||
}
|
||||
|
||||
|
||||
@@ -566,7 +566,7 @@ void ForwardIterator::RebuildIterators(bool refresh_sv) {
|
||||
sv_ = cfd_->GetReferencedSuperVersion(&(db_->mutex_));
|
||||
}
|
||||
RangeDelAggregator range_del_agg(
|
||||
InternalKeyComparator(cfd_->internal_comparator()), {} /* snapshots */);
|
||||
cfd_->internal_comparator(), {} /* snapshots */);
|
||||
mutable_iter_ = sv_->mem->NewIterator(read_options_, &arena_);
|
||||
sv_->imm->AddIterators(read_options_, &imm_iters_, &arena_);
|
||||
if (!read_options_.ignore_range_deletions) {
|
||||
@@ -621,7 +621,7 @@ void ForwardIterator::RenewIterators() {
|
||||
mutable_iter_ = svnew->mem->NewIterator(read_options_, &arena_);
|
||||
svnew->imm->AddIterators(read_options_, &imm_iters_, &arena_);
|
||||
RangeDelAggregator range_del_agg(
|
||||
InternalKeyComparator(cfd_->internal_comparator()), {} /* snapshots */);
|
||||
cfd_->internal_comparator(), {} /* snapshots */);
|
||||
if (!read_options_.ignore_range_deletions) {
|
||||
std::unique_ptr<InternalIterator> range_del_iter(
|
||||
svnew->mem->NewRangeTombstoneIterator(read_options_));
|
||||
|
||||
+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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -236,6 +236,14 @@ int MemTable::KeyComparator::operator()(const char* prefix_len_key,
|
||||
return comparator.Compare(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 {
|
||||
Slice slice = GetLengthPrefixedSlice(key);
|
||||
return Slice(slice.data(), slice.size() - 8);
|
||||
|
||||
@@ -368,6 +368,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 +442,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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
+10
-2
@@ -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_),
|
||||
|
||||
+3
-3
@@ -105,13 +105,13 @@ GEM
|
||||
rb-fsevent (>= 0.9.3)
|
||||
rb-inotify (>= 0.9.7)
|
||||
mercenary (0.3.6)
|
||||
mini_portile2 (2.1.0)
|
||||
mini_portile2 (2.3.0)
|
||||
minima (2.0.0)
|
||||
minitest (5.9.1)
|
||||
multipart-post (2.0.0)
|
||||
net-dns (0.8.0)
|
||||
nokogiri (1.6.8.1)
|
||||
mini_portile2 (~> 2.1.0)
|
||||
nokogiri (1.8.1)
|
||||
mini_portile2 (~> 2.3.0)
|
||||
octokit (4.4.1)
|
||||
sawyer (~> 0.7.0, >= 0.5.3)
|
||||
pathutil (0.14.0)
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
title: Auto-tuned Rate Limiter
|
||||
layout: post
|
||||
author: ajkr
|
||||
category: blog
|
||||
---
|
||||
|
||||
### Introduction
|
||||
|
||||
Our rate limiter has been hard to configure since users need to pick a value that is low enough to prevent background I/O spikes, which can impact user-visible read/write latencies. Meanwhile, picking too low a value can cause memtables and L0 files to pile up, eventually leading to writes stalling. Tuning the rate limiter has been especially difficult for users whose DB instances have different workloads, or have workloads that vary over time, or commonly both.
|
||||
|
||||
To address this, in RocksDB 5.9 we released a dynamic rate limiter that adjusts itself over time according to demand for background I/O. It can be enabled simply by passing `auto_tuned=true` in the `NewGenericRateLimiter()` call. In this case `rate_bytes_per_sec` will indicate the upper-bound of the window within which a rate limit will be picked dynamically. The chosen rate limit will be much lower unless absolutely necessary, so setting this to the device's maximum throughput is a reasonable choice on dedicated hosts.
|
||||
|
||||
### Algorithm
|
||||
|
||||
We use a simple multiplicative-increase, multiplicative-decrease algorithm. We measure demand for background I/O as the ratio of intervals where the rate limiter is drained. There are low and high watermarks for this ratio, which will trigger a change in rate limit when breached. The rate limit can move within a window bounded by the user-specified upper-bound, and a lower-bound that we derive internally. Users can expect this lower bound to be 1-2 orders of magnitude less than the provided upper-bound (so don't provide INT64_MAX as your upper-bound), although it's subject to change.
|
||||
|
||||
### Benchmark Results
|
||||
|
||||
Data is ingested at 10MB/s and the rate limiter was created with 1000MB/s as its upper bound. The dynamically chosen rate limit hovers around 125MB/s. The other clustering of points at 50MB/s is due to number of compaction threads being reduced to one when there's no compaction pressure.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
The following graph summarizes the above two time series graphs in CDF form. In particular, notice the p90 - p100 for background write rate are significantly lower with auto-tuned rate limiter enabled.
|
||||
|
||||

|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
title: WritePrepared Transactions
|
||||
layout: post
|
||||
author: maysamyabandeh
|
||||
category: blog
|
||||
---
|
||||
|
||||
RocksDB supports both optimistic and pessimistic concurrency controls. The pessimistic transactions make use of locks to provide isolation between the transactions. The default write policy in pessimistic transactions is _WriteCommitted_, which means that the data is written to the DB, i.e., the memtable, only after the transaction is committed. This policy simplified the implementation but came with some limitations in throughput, transaction size, and variety in supported isolation levels. In the below, we explain these in detail and present the other write policies, _WritePrepared_ and _WriteUnprepared_. We then dive into the design of _WritePrepared_ transactions.
|
||||
|
||||
> _WritePrepared_ are to be announced as production-ready soon.
|
||||
|
||||
### WriteCommitted, Pros and Cons
|
||||
|
||||
With _WriteCommitted_ write policy, the data is written to the memtable only after the transaction commits. This greatly simplifies the read path as any data that is read by other transactions can be assumed to be committed. This write policy, however, implies that the writes are buffered in memory in the meanwhile. This makes memory a bottleneck for large transactions. The delay of the commit phase in 2PC (two-phase commit) also becomes noticeable since most of the work, i.e., writing to memtable, is done at the commit phase. When the commit of multiple transactions are done in a serial fashion, such as in 2PC implementation of MySQL, the lengthy commit latency becomes a major contributor to lower throughput. Moreover this write policy cannot provide weaker isolation levels, such as READ UNCOMMITTED, that could potentially provide higher throughput for some applications.
|
||||
|
||||
### Alternatives: _WritePrepared_ and _WriteUnprepared_
|
||||
|
||||
To tackle the lengthy commit issue, we should do memtable writes at earlier phases of 2PC so that the commit phase become lightweight and fast. 2PC is composed of Write stage, where the transaction `::Put` is invoked, the prepare phase, where `::Prepare` is invoked (upon which the DB promises to commit the transaction if later is requested), and commit phase, where `::Commit` is invoked and the transaction writes become visible to all readers. To make the commit phase lightweight, the memtable write could be done at either `::Prepare` or `::Put` stages, resulting into _WritePrepared_ and _WriteUnprepared_ write policies respectively. The downside is that when another transaction is reading data, it would need a way to tell apart which data is committed, and if they are, whether they are committed before the transaction's start, i.e., in the read snapshot of the transaction. _WritePrepared_ would still have the issue of buffering the data, which makes the memory the bottleneck for large transactions. It however provides a good milestone for transitioning from _WriteCommitted_ to _WriteUnprepared_ write policy. Here we explain the design of _WritePrepared_ policy. We will cover the changes that make the design to also supported _WriteUnprepared_ in an upcoming post.
|
||||
|
||||
### _WritePrepared_ in a nutshell
|
||||
|
||||
These are the primary design questions that needs to be addressed:
|
||||
1) How do we identify the key/values in the DB with transactions that wrote them?
|
||||
2) How do we figure if a key/value written by transaction Txn_w is in the read snapshot of the reading transaction Txn_r?
|
||||
3) How do we rollback the data written by aborted transactions?
|
||||
|
||||
With _WritePrepared_, a transaction still buffers the writes in a write batch object in memory. When 2PC `::Prepare` is called, it writes the in-memory write batch to the WAL (write-ahead log) as well as to the memtable(s) (one memtable per column family); We reuse the existing notion of sequence numbers in RocksDB to tag all the key/values in the same write batch with the same sequence number, `prepare_seq`, which is also used as the identifier for the transaction. At commit time, it writes a commit marker to the WAL, whose sequence number, `commit_seq`, will be used as the commit timestamp of the transaction. Before releasing the commit sequence number to the readers, it stores a mapping from `prepare_seq` to `commit_seq` in an in-memory data structure that we call _CommitCache_. When a transaction reading values from the DB (tagged with `prepare_seq`) it makes use of the _CommitCache_ to figure if `commit_seq` of the value is in its read snapshot. To rollback an aborted transaction, we apply the status before the transaction by making another write that cancels out the writes of the aborted transaction.
|
||||
|
||||
The _CommitCache_ is a lock-free data structure that caches the recent commit entries. Looking up the entries in the cache must be enough for almost all th transactions that commit in a timely manner. When evicting the older entries from the cache, it still maintains some other data structures to cover the corner cases for transactions that takes abnormally too long to finish. We will cover them in the design details below.
|
||||
|
||||
### Preliminary Results
|
||||
The full experimental results are to be reported soon. Here we present the improvement in tps observed in some preliminary experiments with MyRocks:
|
||||
* sysbench update-noindex: 25%
|
||||
* sysbench read-write: 7.6%
|
||||
* linkbench: 3.7%
|
||||
|
||||
Learn more [here](https://github.com/facebook/rocksdb/wiki/WritePrepared-Transactions).
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 172 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 79 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 303 KiB |
Vendored
+10
-1
@@ -73,9 +73,18 @@ RandomAccessFile::~RandomAccessFile() {
|
||||
WritableFile::~WritableFile() {
|
||||
}
|
||||
|
||||
Logger::~Logger() {
|
||||
Logger::~Logger() { Close(); }
|
||||
|
||||
Status Logger::Close() {
|
||||
if (!closed_) {
|
||||
closed_ = true;
|
||||
return CloseImpl();
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status Logger::CloseImpl() { return Status::OK(); }
|
||||
|
||||
FileLock::~FileLock() {
|
||||
}
|
||||
|
||||
|
||||
Vendored
+10
-6
@@ -277,6 +277,16 @@ class HdfsLogger : public Logger {
|
||||
HdfsWritableFile* file_;
|
||||
uint64_t (*gettid_)(); // Return the thread id for the current thread
|
||||
|
||||
virtual Status CloseImpl() {
|
||||
ROCKS_LOG_DEBUG(mylog, "[hdfs] HdfsLogger closed %s\n",
|
||||
file_->getName().c_str());
|
||||
Status s = file_->Close();
|
||||
if (mylog != nullptr && mylog == this) {
|
||||
mylog = nullptr;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
public:
|
||||
HdfsLogger(HdfsWritableFile* f, uint64_t (*gettid)())
|
||||
: file_(f), gettid_(gettid) {
|
||||
@@ -285,12 +295,6 @@ class HdfsLogger : public Logger {
|
||||
}
|
||||
|
||||
virtual ~HdfsLogger() {
|
||||
ROCKS_LOG_DEBUG(mylog, "[hdfs] HdfsLogger closed %s\n",
|
||||
file_->getName().c_str());
|
||||
delete file_;
|
||||
if (mylog != nullptr && mylog == this) {
|
||||
mylog = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
virtual void Logv(const char* format, va_list ap) {
|
||||
|
||||
Vendored
+11
-3
@@ -24,6 +24,7 @@
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include "env/io_posix.h"
|
||||
#include "monitoring/iostats_context_imp.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "util/sync_point.h"
|
||||
@@ -32,6 +33,15 @@ namespace rocksdb {
|
||||
|
||||
class PosixLogger : public Logger {
|
||||
private:
|
||||
virtual Status CloseImpl() override {
|
||||
int ret;
|
||||
|
||||
ret = fclose(file_);
|
||||
if (ret) {
|
||||
return IOError("Unable to close log file", "", ret);
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
FILE* file_;
|
||||
uint64_t (*gettid_)(); // Return the thread id for the current thread
|
||||
std::atomic_size_t log_size_;
|
||||
@@ -51,9 +61,7 @@ class PosixLogger : public Logger {
|
||||
last_flush_micros_(0),
|
||||
env_(env),
|
||||
flush_pending_(false) {}
|
||||
virtual ~PosixLogger() {
|
||||
fclose(file_);
|
||||
}
|
||||
virtual ~PosixLogger() { Close(); }
|
||||
virtual void Flush() override {
|
||||
TEST_SYNC_POINT("PosixLogger::Flush:Begin1");
|
||||
TEST_SYNC_POINT("PosixLogger::Flush:Begin2");
|
||||
|
||||
@@ -710,6 +710,9 @@ extern ROCKSDB_LIBRARY_API void rocksdb_options_optimize_level_style_compaction(
|
||||
extern ROCKSDB_LIBRARY_API void
|
||||
rocksdb_options_optimize_universal_style_compaction(
|
||||
rocksdb_options_t* opt, uint64_t memtable_memory_budget);
|
||||
extern ROCKSDB_LIBRARY_API void
|
||||
rocksdb_options_set_allow_ingest_behind(rocksdb_options_t*,
|
||||
unsigned char);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_compaction_filter(
|
||||
rocksdb_options_t*, rocksdb_compactionfilter_t*);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_compaction_filter_factory(
|
||||
|
||||
@@ -94,10 +94,6 @@ class CompactionFilter {
|
||||
// be used by a single thread that is doing the compaction run, and this
|
||||
// call does not need to be thread-safe. However, multiple filters may be
|
||||
// in existence and operating concurrently.
|
||||
//
|
||||
// The last paragraph is not true if you set max_subcompactions to more than
|
||||
// 1. In that case, subcompaction from multiple threads may call a single
|
||||
// CompactionFilter concurrently.
|
||||
virtual bool Filter(int level, const Slice& key, const Slice& existing_value,
|
||||
std::string* new_value, bool* value_changed) const {
|
||||
return false;
|
||||
|
||||
@@ -163,6 +163,12 @@ class DB {
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
std::vector<ColumnFamilyHandle*>* handles, DB** dbptr);
|
||||
|
||||
// Close the DB by releasing resources, closing files etc. This should be
|
||||
// called before calling the desctructor so that the caller can get back a
|
||||
// status in case there are any errors. Regardless of the return status, the
|
||||
// DB must be freed
|
||||
virtual Status Close() { return Status::OK(); }
|
||||
|
||||
// ListColumnFamilies will open the DB specified by argument name
|
||||
// and return the list of all column families in that DB
|
||||
// through column_families argument. The ordering of
|
||||
@@ -973,7 +979,7 @@ class DB {
|
||||
// the file can fit in, and ingest the file into this level (2). A file that
|
||||
// have a key range that overlap with the memtable key range will require us
|
||||
// to Flush the memtable first before ingesting the file.
|
||||
// In the second mode we will always ingest in the bottom mode level (see
|
||||
// In the second mode we will always ingest in the bottom most level (see
|
||||
// docs to IngestExternalFileOptions::ingest_behind).
|
||||
//
|
||||
// (1) External SST files can be created using SstFileWriter
|
||||
|
||||
@@ -819,9 +819,12 @@ class Logger {
|
||||
size_t kDoNotSupportGetLogFileSize = (std::numeric_limits<size_t>::max)();
|
||||
|
||||
explicit Logger(const InfoLogLevel log_level = InfoLogLevel::INFO_LEVEL)
|
||||
: log_level_(log_level) {}
|
||||
: closed_(false), log_level_(log_level) {}
|
||||
virtual ~Logger();
|
||||
|
||||
// Close the log file. Must be called before destructor
|
||||
virtual Status Close();
|
||||
|
||||
// Write a header to the log file with the specified format
|
||||
// It is recommended that you log all header information at the start of the
|
||||
// application. But it is not enforced.
|
||||
@@ -852,6 +855,8 @@ class Logger {
|
||||
// No copying allowed
|
||||
Logger(const Logger&);
|
||||
void operator=(const Logger&);
|
||||
virtual Status CloseImpl();
|
||||
bool closed_;
|
||||
InfoLogLevel log_level_;
|
||||
};
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ class Iterator : public Cleanable {
|
||||
// Position at the last key in the source that at or before target
|
||||
// The iterator is Valid() after this call iff the source contains
|
||||
// an entry that comes at or before target.
|
||||
virtual void SeekForPrev(const Slice& target) {}
|
||||
virtual void SeekForPrev(const Slice& target) = 0;
|
||||
|
||||
// Moves to the next entry in the source. After this call, Valid() is
|
||||
// true iff the iterator was not positioned at the last entry in the source.
|
||||
|
||||
@@ -96,13 +96,7 @@ class MemTableRep {
|
||||
|
||||
// Like Insert(handle), but may be called concurrent with other calls
|
||||
// to InsertConcurrently for other handles
|
||||
virtual void InsertConcurrently(KeyHandle handle) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
throw std::runtime_error("concurrent insert not supported");
|
||||
#else
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
virtual void InsertConcurrently(KeyHandle handle);
|
||||
|
||||
// Returns true iff an entry that compares equal to key is in the collection.
|
||||
virtual bool Contains(const char* key) const = 0;
|
||||
|
||||
@@ -72,16 +72,18 @@ class SstFileWriter {
|
||||
SstFileWriter(const EnvOptions& env_options, const Options& options,
|
||||
ColumnFamilyHandle* column_family = nullptr,
|
||||
bool invalidate_page_cache = true,
|
||||
Env::IOPriority io_priority = Env::IOPriority::IO_TOTAL)
|
||||
Env::IOPriority io_priority = Env::IOPriority::IO_TOTAL,
|
||||
bool skip_filters = false)
|
||||
: SstFileWriter(env_options, options, options.comparator, column_family,
|
||||
invalidate_page_cache, io_priority) {}
|
||||
invalidate_page_cache, io_priority, skip_filters) {}
|
||||
|
||||
// Deprecated API
|
||||
SstFileWriter(const EnvOptions& env_options, const Options& options,
|
||||
const Comparator* user_comparator,
|
||||
ColumnFamilyHandle* column_family = nullptr,
|
||||
bool invalidate_page_cache = true,
|
||||
Env::IOPriority io_priority = Env::IOPriority::IO_TOTAL);
|
||||
Env::IOPriority io_priority = Env::IOPriority::IO_TOTAL,
|
||||
bool skip_filters = false);
|
||||
|
||||
~SstFileWriter();
|
||||
|
||||
|
||||
@@ -415,7 +415,7 @@ const std::vector<std::pair<Tickers, std::string>> TickersNameMap = {
|
||||
{BLOB_DB_WRITE_BLOB, "rocksdb.blobdb.write.blob"},
|
||||
{BLOB_DB_WRITE_BLOB_TTL, "rocksdb.blobdb.write.blob.ttl"},
|
||||
{BLOB_DB_BLOB_FILE_BYTES_WRITTEN, "rocksdb.blobdb.blob.file.bytes.written"},
|
||||
{BLOB_DB_BLOB_FILE_BYTES_READ, "rocksdb.blobdb.blob.file.bytes.read"},
|
||||
{BLOB_DB_BLOB_FILE_BYTES_READ, "rocksdb.blobdb.blob.file,bytes.read"},
|
||||
{BLOB_DB_BLOB_FILE_SYNCED, "rocksdb.blobdb.blob.file.synced"},
|
||||
{BLOB_DB_BLOB_INDEX_EXPIRED, "rocksdb.blobdb.blob.index.expired"},
|
||||
{BLOB_DB_GC_NUM_FILES, "rocksdb.blobdb.gc.num.files"},
|
||||
@@ -510,6 +510,8 @@ enum Histograms : uint32_t {
|
||||
BLOB_DB_COMPRESSION_MICROS,
|
||||
// BlobDB decompression time.
|
||||
BLOB_DB_DECOMPRESSION_MICROS,
|
||||
// Time spent flushing memtable to disk
|
||||
FLUSH_TIME,
|
||||
|
||||
HISTOGRAM_ENUM_MAX, // TODO(ldemailly): enforce HistogramsNameMap match
|
||||
};
|
||||
@@ -560,6 +562,7 @@ const std::vector<std::pair<Histograms, std::string>> HistogramsNameMap = {
|
||||
{BLOB_DB_GC_MICROS, "rocksdb.blobdb.gc.micros"},
|
||||
{BLOB_DB_COMPRESSION_MICROS, "rocksdb.blobdb.compression.micros"},
|
||||
{BLOB_DB_DECOMPRESSION_MICROS, "rocksdb.blobdb.decompression.micros"},
|
||||
{FLUSH_TIME, "rocksdb.db.flush.micros"},
|
||||
};
|
||||
|
||||
struct HistogramData {
|
||||
|
||||
@@ -217,6 +217,11 @@ struct BlockBasedTableOptions {
|
||||
// This option only affects newly written tables. When reading exising tables,
|
||||
// the information about version is read from the footer.
|
||||
uint32_t format_version = 2;
|
||||
|
||||
// Store index blocks on disk in compressed format. Changing this option to
|
||||
// false will avoid the overhead of decompression if index blocks are evicted
|
||||
// and read back
|
||||
bool enable_index_compression = true;
|
||||
};
|
||||
|
||||
// Table Properties that are specific to block-based table properties.
|
||||
|
||||
@@ -45,6 +45,7 @@ struct ThreadStatus {
|
||||
HIGH_PRIORITY = 0, // RocksDB BG thread in high-pri thread pool
|
||||
LOW_PRIORITY, // RocksDB BG thread in low-pri thread pool
|
||||
USER, // User thread (Non-RocksDB BG thread)
|
||||
BOTTOM_PRIORITY, // RocksDB BG thread in bottom-pri thread pool
|
||||
NUM_THREAD_TYPES
|
||||
};
|
||||
|
||||
@@ -163,7 +164,7 @@ struct ThreadStatus {
|
||||
// The followings are a set of utility functions for interpreting
|
||||
// the information of ThreadStatus
|
||||
|
||||
static const std::string& GetThreadTypeName(ThreadType thread_type);
|
||||
static std::string GetThreadTypeName(ThreadType thread_type);
|
||||
|
||||
// Obtain the name of an operation given its type.
|
||||
static const std::string& GetOperationName(OperationType op_type);
|
||||
|
||||
@@ -25,7 +25,7 @@ enum EntryType {
|
||||
kEntryOther,
|
||||
};
|
||||
|
||||
// <user key, seqeence number and entry type> tuple.
|
||||
// <user key, sequence number, and entry type> tuple.
|
||||
struct FullKey {
|
||||
Slice user_key;
|
||||
SequenceNumber sequence;
|
||||
|
||||
@@ -60,6 +60,10 @@ class DBWithTTL : public StackableDB {
|
||||
DBWithTTL** dbptr, std::vector<int32_t> ttls,
|
||||
bool read_only = false);
|
||||
|
||||
virtual void SetTtl(int32_t ttl) = 0;
|
||||
|
||||
virtual void SetTtl(ColumnFamilyHandle *h, int32_t ttl) = 0;
|
||||
|
||||
protected:
|
||||
explicit DBWithTTL(DB* db) : StackableDB(db) {}
|
||||
};
|
||||
|
||||
@@ -23,18 +23,19 @@ class StackableDB : public DB {
|
||||
explicit StackableDB(DB* db) : db_(db) {}
|
||||
|
||||
// StackableDB take shared ownership of the underlying db.
|
||||
explicit StackableDB(std::shared_ptr<DB> db)
|
||||
: db_(db.get()), shared_db_ptr_(db) {}
|
||||
explicit StackableDB(std::shared_ptr<DB>& db)
|
||||
: db_(db.get()), shared_db_(db) {}
|
||||
|
||||
~StackableDB() {
|
||||
if (shared_db_ptr_ == nullptr) {
|
||||
if (shared_db_ == nullptr) {
|
||||
delete db_;
|
||||
} else {
|
||||
assert(shared_db_ptr_.get() == db_);
|
||||
assert(shared_db_.get() == db_);
|
||||
}
|
||||
db_ = nullptr;
|
||||
}
|
||||
|
||||
virtual Status Close() override { return db_->Close(); }
|
||||
|
||||
virtual DB* GetBaseDB() {
|
||||
return db_;
|
||||
}
|
||||
@@ -383,7 +384,7 @@ class StackableDB : public DB {
|
||||
|
||||
protected:
|
||||
DB* db_;
|
||||
std::shared_ptr<DB> shared_db_ptr_;
|
||||
std::shared_ptr<DB> shared_db_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -315,6 +315,7 @@ class WriteBatch : public WriteBatchBase {
|
||||
|
||||
// Constructor with a serialized string object
|
||||
explicit WriteBatch(const std::string& rep);
|
||||
explicit WriteBatch(std::string&& rep);
|
||||
|
||||
WriteBatch(const WriteBatch& src);
|
||||
WriteBatch(WriteBatch&& src) noexcept;
|
||||
|
||||
@@ -7,6 +7,8 @@ set(JNI_NATIVE_SOURCES
|
||||
rocksjni/clock_cache.cc
|
||||
rocksjni/columnfamilyhandle.cc
|
||||
rocksjni/compaction_filter.cc
|
||||
rocksjni/compaction_filter_factory.cc
|
||||
rocksjni/compaction_filter_factory_jnicallback.cc
|
||||
rocksjni/compaction_options_fifo.cc
|
||||
rocksjni/compaction_options_universal.cc
|
||||
rocksjni/comparator.cc
|
||||
@@ -17,6 +19,7 @@ set(JNI_NATIVE_SOURCES
|
||||
rocksjni/filter.cc
|
||||
rocksjni/ingest_external_file_options.cc
|
||||
rocksjni/iterator.cc
|
||||
rocksjni/jnicallback.cc
|
||||
rocksjni/loggerjnicallback.cc
|
||||
rocksjni/lru_cache.cc
|
||||
rocksjni/memtablejni.cc
|
||||
@@ -25,6 +28,7 @@ set(JNI_NATIVE_SOURCES
|
||||
rocksjni/options_util.cc
|
||||
rocksjni/ratelimiterjni.cc
|
||||
rocksjni/remove_emptyvalue_compactionfilterjni.cc
|
||||
rocksjni/rocks_callback_object.cc
|
||||
rocksjni/cassandra_compactionfilterjni.cc
|
||||
rocksjni/cassandra_value_operator.cc
|
||||
rocksjni/restorejni.cc
|
||||
@@ -204,6 +208,7 @@ add_jar(
|
||||
src/main/java/org/rocksdb/OptionsUtil.java
|
||||
src/main/java/org/rocksdb/PlainTableConfig.java
|
||||
src/main/java/org/rocksdb/RateLimiter.java
|
||||
src/main/java/org/rocksdb/RateLimiterMode.java
|
||||
src/main/java/org/rocksdb/ReadOptions.java
|
||||
src/main/java/org/rocksdb/ReadTier.java
|
||||
src/main/java/org/rocksdb/RemoveEmptyValueCompactionFilter.java
|
||||
|
||||
+45
-1
@@ -21,6 +21,7 @@
|
||||
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/filter_policy.h"
|
||||
#include "rocksdb/rate_limiter.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/utilities/backupable_db.h"
|
||||
#include "rocksdb/utilities/write_batch_with_index.h"
|
||||
@@ -2806,8 +2807,10 @@ class HistogramTypeJni {
|
||||
return 0x1D;
|
||||
case rocksdb::Histograms::READ_NUM_MERGE_OPERANDS:
|
||||
return 0x1E;
|
||||
case rocksdb::Histograms::HISTOGRAM_ENUM_MAX:
|
||||
case rocksdb::Histograms::FLUSH_TIME:
|
||||
return 0x1F;
|
||||
case rocksdb::Histograms::HISTOGRAM_ENUM_MAX:
|
||||
return 0x20;
|
||||
|
||||
default:
|
||||
// undefined/default
|
||||
@@ -2882,6 +2885,8 @@ class HistogramTypeJni {
|
||||
case 0x1E:
|
||||
return rocksdb::Histograms::READ_NUM_MERGE_OPERANDS;
|
||||
case 0x1F:
|
||||
return rocksdb::Histograms::FLUSH_TIME;
|
||||
case 0x20:
|
||||
return rocksdb::Histograms::HISTOGRAM_ENUM_MAX;
|
||||
|
||||
default:
|
||||
@@ -2930,6 +2935,45 @@ class StatsLevelJni {
|
||||
}
|
||||
};
|
||||
|
||||
// The portal class for org.rocksdb.RateLimiterMode
|
||||
class RateLimiterModeJni {
|
||||
public:
|
||||
// Returns the equivalent org.rocksdb.RateLimiterMode for the provided
|
||||
// C++ rocksdb::RateLimiter::Mode enum
|
||||
static jbyte toJavaRateLimiterMode(
|
||||
const rocksdb::RateLimiter::Mode& rate_limiter_mode) {
|
||||
switch(rate_limiter_mode) {
|
||||
case rocksdb::RateLimiter::Mode::kReadsOnly:
|
||||
return 0x0;
|
||||
case rocksdb::RateLimiter::Mode::kWritesOnly:
|
||||
return 0x1;
|
||||
case rocksdb::RateLimiter::Mode::kAllIo:
|
||||
return 0x2;
|
||||
|
||||
default:
|
||||
// undefined/default
|
||||
return 0x1;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the equivalent C++ rocksdb::RateLimiter::Mode enum for the
|
||||
// provided Java org.rocksdb.RateLimiterMode
|
||||
static rocksdb::RateLimiter::Mode toCppRateLimiterMode(jbyte jrate_limiter_mode) {
|
||||
switch(jrate_limiter_mode) {
|
||||
case 0x0:
|
||||
return rocksdb::RateLimiter::Mode::kReadsOnly;
|
||||
case 0x1:
|
||||
return rocksdb::RateLimiter::Mode::kWritesOnly;
|
||||
case 0x2:
|
||||
return rocksdb::RateLimiter::Mode::kAllIo;
|
||||
|
||||
default:
|
||||
// undefined/default
|
||||
return rocksdb::RateLimiter::Mode::kWritesOnly;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// various utility functions for working with RocksDB and JNI
|
||||
class JniUtil {
|
||||
public:
|
||||
|
||||
@@ -12,16 +12,21 @@
|
||||
/*
|
||||
* Class: org_rocksdb_RateLimiter
|
||||
* Method: newRateLimiterHandle
|
||||
* Signature: (JJI)J
|
||||
* Signature: (JJIBZ)J
|
||||
*/
|
||||
jlong Java_org_rocksdb_RateLimiter_newRateLimiterHandle(
|
||||
JNIEnv* env, jclass jclazz, jlong jrate_bytes_per_second,
|
||||
jlong jrefill_period_micros, jint jfairness) {
|
||||
jlong jrefill_period_micros, jint jfairness, jbyte jrate_limiter_mode,
|
||||
jboolean jauto_tune) {
|
||||
auto rate_limiter_mode = rocksdb::RateLimiterModeJni::toCppRateLimiterMode(
|
||||
jrate_limiter_mode);
|
||||
auto * sptr_rate_limiter =
|
||||
new std::shared_ptr<rocksdb::RateLimiter>(rocksdb::NewGenericRateLimiter(
|
||||
static_cast<int64_t>(jrate_bytes_per_second),
|
||||
static_cast<int64_t>(jrefill_period_micros),
|
||||
static_cast<int32_t>(jfairness)));
|
||||
static_cast<int32_t>(jfairness),
|
||||
rate_limiter_mode,
|
||||
jauto_tune));
|
||||
|
||||
return reinterpret_cast<jlong>(sptr_rate_limiter);
|
||||
}
|
||||
@@ -50,6 +55,17 @@ void Java_org_rocksdb_RateLimiter_setBytesPerSecond(
|
||||
SetBytesPerSecond(jbytes_per_second);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_RateLimiter
|
||||
* Method: getBytesPerSecond
|
||||
* Signature: (J)J
|
||||
*/
|
||||
jlong Java_org_rocksdb_RateLimiter_getBytesPerSecond(
|
||||
JNIEnv* env, jobject jobj, jlong handle) {
|
||||
return reinterpret_cast<std::shared_ptr<rocksdb::RateLimiter> *>(handle)->get()->
|
||||
GetBytesPerSecond();
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_RateLimiter
|
||||
* Method: request
|
||||
|
||||
@@ -12,8 +12,11 @@ package org.rocksdb;
|
||||
* @since 3.10.0
|
||||
*/
|
||||
public class RateLimiter extends RocksObject {
|
||||
private static final long DEFAULT_REFILL_PERIOD_MICROS = (100 * 1000);
|
||||
private static final int DEFAULT_FAIRNESS = 10;
|
||||
public static final long DEFAULT_REFILL_PERIOD_MICROS = 100 * 1000;
|
||||
public static final int DEFAULT_FAIRNESS = 10;
|
||||
public static final RateLimiterMode DEFAULT_MODE =
|
||||
RateLimiterMode.WRITES_ONLY;
|
||||
public static final boolean DEFAULT_AUTOTUNE = false;
|
||||
|
||||
/**
|
||||
* RateLimiter constructor
|
||||
@@ -21,24 +24,12 @@ public class RateLimiter extends RocksObject {
|
||||
* @param rateBytesPerSecond this is the only parameter you want to set
|
||||
* most of the time. It controls the total write rate of compaction
|
||||
* and flush in bytes per second. Currently, RocksDB does not enforce
|
||||
* rate limit for anything other than flush and compaction, e.g. write to WAL.
|
||||
* @param refillPeriodMicros this controls how often tokens are refilled. For example,
|
||||
* when rate_bytes_per_sec is set to 10MB/s and refill_period_us is set to
|
||||
* 100ms, then 1MB is refilled every 100ms internally. Larger value can lead to
|
||||
* burstier writes while smaller value introduces more CPU overhead.
|
||||
* The default should work for most cases.
|
||||
* @param fairness RateLimiter accepts high-pri requests and low-pri requests.
|
||||
* A low-pri request is usually blocked in favor of hi-pri request. Currently,
|
||||
* RocksDB assigns low-pri to request from compaction and high-pri to request
|
||||
* from flush. Low-pri requests can get blocked if flush requests come in
|
||||
* continuously. This fairness parameter grants low-pri requests permission by
|
||||
* fairness chance even though high-pri requests exist to avoid starvation.
|
||||
* You should be good by leaving it at default 10.
|
||||
* rate limit for anything other than flush and compaction, e.g. write to
|
||||
* WAL.
|
||||
*/
|
||||
public RateLimiter(final long rateBytesPerSecond,
|
||||
final long refillPeriodMicros, final int fairness) {
|
||||
super(newRateLimiterHandle(rateBytesPerSecond,
|
||||
refillPeriodMicros, fairness));
|
||||
public RateLimiter(final long rateBytesPerSecond) {
|
||||
this(rateBytesPerSecond, DEFAULT_REFILL_PERIOD_MICROS, DEFAULT_FAIRNESS,
|
||||
DEFAULT_MODE, DEFAULT_AUTOTUNE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,10 +38,115 @@ public class RateLimiter extends RocksObject {
|
||||
* @param rateBytesPerSecond this is the only parameter you want to set
|
||||
* most of the time. It controls the total write rate of compaction
|
||||
* and flush in bytes per second. Currently, RocksDB does not enforce
|
||||
* rate limit for anything other than flush and compaction, e.g. write to WAL.
|
||||
* rate limit for anything other than flush and compaction, e.g. write to
|
||||
* WAL.
|
||||
* @param refillPeriodMicros this controls how often tokens are refilled. For
|
||||
* example,
|
||||
* when rate_bytes_per_sec is set to 10MB/s and refill_period_us is set to
|
||||
* 100ms, then 1MB is refilled every 100ms internally. Larger value can
|
||||
* lead to burstier writes while smaller value introduces more CPU
|
||||
* overhead. The default of 100,000ms should work for most cases.
|
||||
*/
|
||||
public RateLimiter(final long rateBytesPerSecond) {
|
||||
this(rateBytesPerSecond, DEFAULT_REFILL_PERIOD_MICROS, DEFAULT_FAIRNESS);
|
||||
public RateLimiter(final long rateBytesPerSecond,
|
||||
final long refillPeriodMicros) {
|
||||
this(rateBytesPerSecond, refillPeriodMicros, DEFAULT_FAIRNESS, DEFAULT_MODE,
|
||||
DEFAULT_AUTOTUNE);
|
||||
}
|
||||
|
||||
/**
|
||||
* RateLimiter constructor
|
||||
*
|
||||
* @param rateBytesPerSecond this is the only parameter you want to set
|
||||
* most of the time. It controls the total write rate of compaction
|
||||
* and flush in bytes per second. Currently, RocksDB does not enforce
|
||||
* rate limit for anything other than flush and compaction, e.g. write to
|
||||
* WAL.
|
||||
* @param refillPeriodMicros this controls how often tokens are refilled. For
|
||||
* example,
|
||||
* when rate_bytes_per_sec is set to 10MB/s and refill_period_us is set to
|
||||
* 100ms, then 1MB is refilled every 100ms internally. Larger value can
|
||||
* lead to burstier writes while smaller value introduces more CPU
|
||||
* overhead. The default of 100,000ms should work for most cases.
|
||||
* @param fairness RateLimiter accepts high-pri requests and low-pri requests.
|
||||
* A low-pri request is usually blocked in favor of hi-pri request.
|
||||
* Currently, RocksDB assigns low-pri to request from compaction and
|
||||
* high-pri to request from flush. Low-pri requests can get blocked if
|
||||
* flush requests come in continuously. This fairness parameter grants
|
||||
* low-pri requests permission by fairness chance even though high-pri
|
||||
* requests exist to avoid starvation.
|
||||
* You should be good by leaving it at default 10.
|
||||
*/
|
||||
public RateLimiter(final long rateBytesPerSecond,
|
||||
final long refillPeriodMicros, final int fairness) {
|
||||
this(rateBytesPerSecond, refillPeriodMicros, fairness, DEFAULT_MODE,
|
||||
DEFAULT_AUTOTUNE);
|
||||
}
|
||||
|
||||
/**
|
||||
* RateLimiter constructor
|
||||
*
|
||||
* @param rateBytesPerSecond this is the only parameter you want to set
|
||||
* most of the time. It controls the total write rate of compaction
|
||||
* and flush in bytes per second. Currently, RocksDB does not enforce
|
||||
* rate limit for anything other than flush and compaction, e.g. write to
|
||||
* WAL.
|
||||
* @param refillPeriodMicros this controls how often tokens are refilled. For
|
||||
* example,
|
||||
* when rate_bytes_per_sec is set to 10MB/s and refill_period_us is set to
|
||||
* 100ms, then 1MB is refilled every 100ms internally. Larger value can
|
||||
* lead to burstier writes while smaller value introduces more CPU
|
||||
* overhead. The default of 100,000ms should work for most cases.
|
||||
* @param fairness RateLimiter accepts high-pri requests and low-pri requests.
|
||||
* A low-pri request is usually blocked in favor of hi-pri request.
|
||||
* Currently, RocksDB assigns low-pri to request from compaction and
|
||||
* high-pri to request from flush. Low-pri requests can get blocked if
|
||||
* flush requests come in continuously. This fairness parameter grants
|
||||
* low-pri requests permission by fairness chance even though high-pri
|
||||
* requests exist to avoid starvation.
|
||||
* You should be good by leaving it at default 10.
|
||||
* @param rateLimiterMode indicates which types of operations count against
|
||||
* the limit.
|
||||
*/
|
||||
public RateLimiter(final long rateBytesPerSecond,
|
||||
final long refillPeriodMicros, final int fairness,
|
||||
final RateLimiterMode rateLimiterMode) {
|
||||
this(rateBytesPerSecond, refillPeriodMicros, fairness, rateLimiterMode,
|
||||
DEFAULT_AUTOTUNE);
|
||||
}
|
||||
|
||||
/**
|
||||
* RateLimiter constructor
|
||||
*
|
||||
* @param rateBytesPerSecond this is the only parameter you want to set
|
||||
* most of the time. It controls the total write rate of compaction
|
||||
* and flush in bytes per second. Currently, RocksDB does not enforce
|
||||
* rate limit for anything other than flush and compaction, e.g. write to
|
||||
* WAL.
|
||||
* @param refillPeriodMicros this controls how often tokens are refilled. For
|
||||
* example,
|
||||
* when rate_bytes_per_sec is set to 10MB/s and refill_period_us is set to
|
||||
* 100ms, then 1MB is refilled every 100ms internally. Larger value can
|
||||
* lead to burstier writes while smaller value introduces more CPU
|
||||
* overhead. The default of 100,000ms should work for most cases.
|
||||
* @param fairness RateLimiter accepts high-pri requests and low-pri requests.
|
||||
* A low-pri request is usually blocked in favor of hi-pri request.
|
||||
* Currently, RocksDB assigns low-pri to request from compaction and
|
||||
* high-pri to request from flush. Low-pri requests can get blocked if
|
||||
* flush requests come in continuously. This fairness parameter grants
|
||||
* low-pri requests permission by fairness chance even though high-pri
|
||||
* requests exist to avoid starvation.
|
||||
* You should be good by leaving it at default 10.
|
||||
* @param rateLimiterMode indicates which types of operations count against
|
||||
* the limit.
|
||||
* @param autoTune Enables dynamic adjustment of rate limit within the range
|
||||
* {@code [rate_bytes_per_sec / 20, rate_bytes_per_sec]}, according to
|
||||
* the recent demand for background I/O.
|
||||
*/
|
||||
public RateLimiter(final long rateBytesPerSecond,
|
||||
final long refillPeriodMicros, final int fairness,
|
||||
final RateLimiterMode rateLimiterMode, final boolean autoTune) {
|
||||
super(newRateLimiterHandle(rateBytesPerSecond,
|
||||
refillPeriodMicros, fairness, rateLimiterMode.getValue(), autoTune));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,6 +160,16 @@ public class RateLimiter extends RocksObject {
|
||||
setBytesPerSecond(nativeHandle_, bytesPerSecond);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bytes per second.
|
||||
*
|
||||
* @return bytes per second.
|
||||
*/
|
||||
public long getBytesPerSecond() {
|
||||
assert(isOwningHandle());
|
||||
return getBytesPerSecond(nativeHandle_);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Request for token to write bytes. If this request can not be satisfied,
|
||||
* the call is blocked. Caller is responsible to make sure
|
||||
@@ -107,11 +213,13 @@ public class RateLimiter extends RocksObject {
|
||||
}
|
||||
|
||||
private static native long newRateLimiterHandle(final long rateBytesPerSecond,
|
||||
final long refillPeriodMicros, final int fairness);
|
||||
final long refillPeriodMicros, final int fairness,
|
||||
final byte rateLimiterMode, final boolean autoTune);
|
||||
@Override protected final native void disposeInternal(final long handle);
|
||||
|
||||
private native void setBytesPerSecond(final long handle,
|
||||
final long bytesPerSecond);
|
||||
private native long getBytesPerSecond(final long handle);
|
||||
private native void request(final long handle, final long bytes);
|
||||
private native long getSingleBurstBytes(final long handle);
|
||||
private native long getTotalBytesThrough(final long handle);
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
package org.rocksdb;
|
||||
|
||||
/**
|
||||
* Mode for {@link RateLimiter#RateLimiter(long, long, int, RateLimiterMode)}.
|
||||
*/
|
||||
public enum RateLimiterMode {
|
||||
READS_ONLY((byte)0x0),
|
||||
WRITES_ONLY((byte)0x1),
|
||||
ALL_IO((byte)0x2);
|
||||
|
||||
private final byte value;
|
||||
|
||||
RateLimiterMode(final byte value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the byte value of the enumerations value.</p>
|
||||
*
|
||||
* @return byte representation
|
||||
*/
|
||||
public byte getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Get the RateLimiterMode enumeration value by
|
||||
* passing the byte identifier to this method.</p>
|
||||
*
|
||||
* @param byteIdentifier of RateLimiterMode.
|
||||
*
|
||||
* @return AccessHint instance.
|
||||
*
|
||||
* @throws IllegalArgumentException if the access hint for the byteIdentifier
|
||||
* cannot be found
|
||||
*/
|
||||
public static RateLimiterMode getRateLimiterMode(final byte byteIdentifier) {
|
||||
for (final RateLimiterMode rateLimiterMode : RateLimiterMode.values()) {
|
||||
if (rateLimiterMode.getValue() == byteIdentifier) {
|
||||
return rateLimiterMode;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(
|
||||
"Illegal value provided for RateLimiterMode.");
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,10 @@ public class Environment {
|
||||
return (OS.contains("win"));
|
||||
}
|
||||
|
||||
public static boolean isFreeBSD() {
|
||||
return (OS.contains("freebsd"));
|
||||
}
|
||||
|
||||
public static boolean isMac() {
|
||||
return (OS.contains("mac"));
|
||||
}
|
||||
@@ -54,6 +58,8 @@ public class Environment {
|
||||
}
|
||||
} else if (isMac()) {
|
||||
return String.format("%sjni-osx", name);
|
||||
} else if (isFreeBSD()) {
|
||||
return String.format("%sjni-freebsd%s", name, is64Bit() ? "64" : "32");
|
||||
} else if (isAix() && is64Bit()) {
|
||||
return String.format("%sjni-aix64", name);
|
||||
} else if (isSolaris()) {
|
||||
@@ -71,7 +77,7 @@ public class Environment {
|
||||
}
|
||||
|
||||
private static String appendLibOsSuffix(final String libraryFileName, final boolean shared) {
|
||||
if (isUnix() || isAix() || isSolaris()) {
|
||||
if (isUnix() || isAix() || isSolaris() || isFreeBSD()) {
|
||||
return libraryFileName + ".so";
|
||||
} else if (isMac()) {
|
||||
return libraryFileName + (shared ? ".dylib" : ".jnilib");
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.rocksdb.RateLimiter.*;
|
||||
|
||||
public class RateLimiterTest {
|
||||
|
||||
@@ -16,17 +17,21 @@ public class RateLimiterTest {
|
||||
new RocksMemoryResource();
|
||||
|
||||
@Test
|
||||
public void setBytesPerSecond() {
|
||||
public void bytesPerSecond() {
|
||||
try(final RateLimiter rateLimiter =
|
||||
new RateLimiter(1000, 100 * 1000, 1)) {
|
||||
new RateLimiter(1000, DEFAULT_REFILL_PERIOD_MICROS,
|
||||
DEFAULT_FAIRNESS, DEFAULT_MODE, DEFAULT_AUTOTUNE)) {
|
||||
assertThat(rateLimiter.getBytesPerSecond()).isGreaterThan(0);
|
||||
rateLimiter.setBytesPerSecond(2000);
|
||||
assertThat(rateLimiter.getBytesPerSecond()).isGreaterThan(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSingleBurstBytes() {
|
||||
try(final RateLimiter rateLimiter =
|
||||
new RateLimiter(1000, 100 * 1000, 1)) {
|
||||
new RateLimiter(1000, DEFAULT_REFILL_PERIOD_MICROS,
|
||||
DEFAULT_FAIRNESS, DEFAULT_MODE, DEFAULT_AUTOTUNE)) {
|
||||
assertThat(rateLimiter.getSingleBurstBytes()).isEqualTo(100);
|
||||
}
|
||||
}
|
||||
@@ -34,7 +39,8 @@ public class RateLimiterTest {
|
||||
@Test
|
||||
public void getTotalBytesThrough() {
|
||||
try(final RateLimiter rateLimiter =
|
||||
new RateLimiter(1000, 100 * 1000, 1)) {
|
||||
new RateLimiter(1000, DEFAULT_REFILL_PERIOD_MICROS,
|
||||
DEFAULT_FAIRNESS, DEFAULT_MODE, DEFAULT_AUTOTUNE)) {
|
||||
assertThat(rateLimiter.getTotalBytesThrough()).isEqualTo(0);
|
||||
}
|
||||
}
|
||||
@@ -42,8 +48,18 @@ public class RateLimiterTest {
|
||||
@Test
|
||||
public void getTotalRequests() {
|
||||
try(final RateLimiter rateLimiter =
|
||||
new RateLimiter(1000, 100 * 1000, 1)) {
|
||||
new RateLimiter(1000, DEFAULT_REFILL_PERIOD_MICROS,
|
||||
DEFAULT_FAIRNESS, DEFAULT_MODE, DEFAULT_AUTOTUNE)) {
|
||||
assertThat(rateLimiter.getTotalRequests()).isEqualTo(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void autoTune() {
|
||||
try(final RateLimiter rateLimiter =
|
||||
new RateLimiter(1000, DEFAULT_REFILL_PERIOD_MICROS,
|
||||
DEFAULT_FAIRNESS, DEFAULT_MODE, true)) {
|
||||
assertThat(rateLimiter.getBytesPerSecond()).isGreaterThan(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,14 +14,21 @@
|
||||
namespace rocksdb {
|
||||
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
const std::string& ThreadStatus::GetThreadTypeName(
|
||||
std::string ThreadStatus::GetThreadTypeName(
|
||||
ThreadStatus::ThreadType thread_type) {
|
||||
static std::string thread_type_names[NUM_THREAD_TYPES + 1] = {
|
||||
"High Pri", "Low Pri", "User", "Unknown"};
|
||||
if (thread_type < 0 || thread_type >= NUM_THREAD_TYPES) {
|
||||
return thread_type_names[NUM_THREAD_TYPES]; // "Unknown"
|
||||
switch (thread_type) {
|
||||
case ThreadStatus::ThreadType::HIGH_PRIORITY:
|
||||
return "High Pri";
|
||||
case ThreadStatus::ThreadType::LOW_PRIORITY:
|
||||
return "Low Pri";
|
||||
case ThreadStatus::ThreadType::USER:
|
||||
return "User";
|
||||
case ThreadStatus::ThreadType::BOTTOM_PRIORITY:
|
||||
return "Bottom Pri";
|
||||
case ThreadStatus::ThreadType::NUM_THREAD_TYPES:
|
||||
assert(false);
|
||||
}
|
||||
return thread_type_names[thread_type];
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
const std::string& ThreadStatus::GetOperationName(
|
||||
@@ -120,7 +127,7 @@ std::map<std::string, uint64_t>
|
||||
|
||||
#else
|
||||
|
||||
const std::string& ThreadStatus::GetThreadTypeName(
|
||||
std::string ThreadStatus::GetThreadTypeName(
|
||||
ThreadStatus::ThreadType thread_type) {
|
||||
static std::string dummy_str = "";
|
||||
return dummy_str;
|
||||
|
||||
+12
-15
@@ -208,6 +208,18 @@ std::unordered_map<std::string, ChecksumType>
|
||||
{"kCRC32c", kCRC32c},
|
||||
{"kxxHash", kxxHash}};
|
||||
|
||||
std::unordered_map<std::string, CompressionType>
|
||||
OptionsHelper::compression_type_string_map = {
|
||||
{"kNoCompression", kNoCompression},
|
||||
{"kSnappyCompression", kSnappyCompression},
|
||||
{"kZlibCompression", kZlibCompression},
|
||||
{"kBZip2Compression", kBZip2Compression},
|
||||
{"kLZ4Compression", kLZ4Compression},
|
||||
{"kLZ4HCCompression", kLZ4HCCompression},
|
||||
{"kXpressCompression", kXpressCompression},
|
||||
{"kZSTD", kZSTD},
|
||||
{"kZSTDNotFinalCompression", kZSTDNotFinalCompression},
|
||||
{"kDisableCompressionOption", kDisableCompressionOption}};
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
template <typename T>
|
||||
@@ -1476,19 +1488,6 @@ std::unordered_map<std::string, OptionTypeInfo>
|
||||
{0, OptionType::kBoolean, OptionVerificationType::kDeprecated, false,
|
||||
0}}};
|
||||
|
||||
std::unordered_map<std::string, CompressionType>
|
||||
OptionsHelper::compression_type_string_map = {
|
||||
{"kNoCompression", kNoCompression},
|
||||
{"kSnappyCompression", kSnappyCompression},
|
||||
{"kZlibCompression", kZlibCompression},
|
||||
{"kBZip2Compression", kBZip2Compression},
|
||||
{"kLZ4Compression", kLZ4Compression},
|
||||
{"kLZ4HCCompression", kLZ4HCCompression},
|
||||
{"kXpressCompression", kXpressCompression},
|
||||
{"kZSTD", kZSTD},
|
||||
{"kZSTDNotFinalCompression", kZSTDNotFinalCompression},
|
||||
{"kDisableCompressionOption", kDisableCompressionOption}};
|
||||
|
||||
std::unordered_map<std::string, BlockBasedTableOptions::IndexType>
|
||||
OptionsHelper::block_base_table_index_type_string_map = {
|
||||
{"kBinarySearch", BlockBasedTableOptions::IndexType::kBinarySearch},
|
||||
@@ -1582,8 +1581,6 @@ int offset_of(T1 CompactionOptionsUniversal::*member) {
|
||||
std::unordered_map<std::string, OptionTypeInfo>
|
||||
OptionsHelper::cf_options_type_info = {
|
||||
/* not yet supported
|
||||
CompactionOptionsFIFO compaction_options_fifo;
|
||||
CompactionOptionsUniversal compaction_options_universal;
|
||||
CompressionOptions compression_opts;
|
||||
TablePropertiesCollectorFactories table_properties_collector_factories;
|
||||
typedef std::vector<std::shared_ptr<TablePropertiesCollectorFactory>>
|
||||
|
||||
@@ -144,6 +144,8 @@ struct OptionsHelper {
|
||||
static std::map<CompactionStopStyle, std::string>
|
||||
compaction_stop_style_to_string;
|
||||
static std::unordered_map<std::string, ChecksumType> checksum_type_string_map;
|
||||
static std::unordered_map<std::string, CompressionType>
|
||||
compression_type_string_map;
|
||||
#ifndef ROCKSDB_LITE
|
||||
static std::unordered_map<std::string, OptionTypeInfo> cf_options_type_info;
|
||||
static std::unordered_map<std::string, OptionTypeInfo>
|
||||
@@ -155,8 +157,6 @@ struct OptionsHelper {
|
||||
static std::unordered_map<std::string, OptionTypeInfo> db_options_type_info;
|
||||
static std::unordered_map<std::string, OptionTypeInfo>
|
||||
lru_cache_options_type_info;
|
||||
static std::unordered_map<std::string, CompressionType>
|
||||
compression_type_string_map;
|
||||
static std::unordered_map<std::string, BlockBasedTableOptions::IndexType>
|
||||
block_base_table_index_type_string_map;
|
||||
static std::unordered_map<std::string, EncodingType> encoding_type_string_map;
|
||||
|
||||
@@ -199,7 +199,7 @@ Status RocksDBOptionsParser::ParseStatement(std::string* name,
|
||||
namespace {
|
||||
bool ReadOneLine(std::istringstream* iss, SequentialFile* seq_file,
|
||||
std::string* output, bool* has_data, Status* result) {
|
||||
const int kBufferSize = 4096;
|
||||
const int kBufferSize = 8192;
|
||||
char buffer[kBufferSize + 1];
|
||||
Slice input_slice;
|
||||
|
||||
|
||||
@@ -150,7 +150,8 @@ TEST_F(OptionsSettableTest, BlockBasedTableOptionsAllFieldsSettable) {
|
||||
"filter_policy=bloomfilter:4:true;whole_key_filtering=1;"
|
||||
"format_version=1;"
|
||||
"hash_index_allow_collision=false;"
|
||||
"verify_compression=true;read_amp_bytes_per_bit=0",
|
||||
"verify_compression=true;read_amp_bytes_per_bit=0;"
|
||||
"enable_index_compression=false",
|
||||
new_bbto));
|
||||
|
||||
ASSERT_EQ(unset_bytes_base,
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ namespace port {
|
||||
|
||||
namespace {
|
||||
|
||||
#ifdef OS_LINUX
|
||||
#if defined(OS_LINUX) || defined(OS_FREEBSD)
|
||||
const char* GetExecutableName() {
|
||||
static char name[1024];
|
||||
|
||||
|
||||
+1
-1
@@ -253,7 +253,7 @@ Status WinEnvIO::OpenWritableFile(const std::string& fname,
|
||||
DWORD fileFlags = FILE_ATTRIBUTE_NORMAL;
|
||||
|
||||
if (local_options.use_direct_writes && !local_options.use_mmap_writes) {
|
||||
fileFlags = FILE_FLAG_NO_BUFFERING;
|
||||
fileFlags = FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH;
|
||||
}
|
||||
|
||||
// Desired access. We are want to write only here but if we want to memory
|
||||
|
||||
+3
-1
@@ -880,7 +880,7 @@ inline
|
||||
Status WinWritableImpl::SyncImpl() {
|
||||
Status s;
|
||||
// Calls flush buffers
|
||||
if (fsync(file_data_->GetFileHandle()) < 0) {
|
||||
if (!file_data_->use_direct_io() && fsync(file_data_->GetFileHandle()) < 0) {
|
||||
auto lastError = GetLastError();
|
||||
s = IOErrorFromWindowsError(
|
||||
"fsync failed at Sync() for: " + file_data_->GetName(), lastError);
|
||||
@@ -963,6 +963,8 @@ Status WinWritableFile::Sync() {
|
||||
|
||||
Status WinWritableFile::Fsync() { return SyncImpl(); }
|
||||
|
||||
bool WinWritableFile::IsSyncThreadSafe() const { return true; }
|
||||
|
||||
uint64_t WinWritableFile::GetFileSize() {
|
||||
return GetFileNextWriteOffset();
|
||||
}
|
||||
|
||||
@@ -368,6 +368,8 @@ class WinWritableFile : private WinFileData,
|
||||
|
||||
virtual Status Fsync() override;
|
||||
|
||||
virtual bool IsSyncThreadSafe() const override;
|
||||
|
||||
// Indicates if the class makes use of direct I/O
|
||||
// Use PositionedAppend
|
||||
virtual bool use_direct_io() const override;
|
||||
|
||||
@@ -96,6 +96,7 @@ LIB_SOURCES = \
|
||||
table/block_based_table_factory.cc \
|
||||
table/block_based_table_reader.cc \
|
||||
table/block_builder.cc \
|
||||
table/block_fetcher.cc \
|
||||
table/block_prefix_index.cc \
|
||||
table/bloom_block.cc \
|
||||
table/cuckoo_table_builder.cc \
|
||||
|
||||
+16
-24
@@ -421,36 +421,28 @@ Block::Block(BlockContents&& contents, SequenceNumber _global_seqno,
|
||||
}
|
||||
}
|
||||
|
||||
InternalIterator* Block::NewIterator(const Comparator* cmp, BlockIter* iter,
|
||||
bool total_order_seek, Statistics* stats) {
|
||||
BlockIter* Block::NewIterator(const Comparator* cmp, BlockIter* iter,
|
||||
bool total_order_seek, Statistics* stats) {
|
||||
BlockIter* ret_iter;
|
||||
if (iter != nullptr) {
|
||||
ret_iter = iter;
|
||||
} else {
|
||||
ret_iter = new BlockIter;
|
||||
}
|
||||
if (size_ < 2*sizeof(uint32_t)) {
|
||||
if (iter != nullptr) {
|
||||
iter->SetStatus(Status::Corruption("bad block contents"));
|
||||
return iter;
|
||||
} else {
|
||||
return NewErrorInternalIterator(Status::Corruption("bad block contents"));
|
||||
}
|
||||
ret_iter->SetStatus(Status::Corruption("bad block contents"));
|
||||
return ret_iter;
|
||||
}
|
||||
const uint32_t num_restarts = NumRestarts();
|
||||
if (num_restarts == 0) {
|
||||
if (iter != nullptr) {
|
||||
iter->SetStatus(Status::OK());
|
||||
return iter;
|
||||
} else {
|
||||
return NewEmptyInternalIterator();
|
||||
}
|
||||
ret_iter->SetStatus(Status::OK());
|
||||
return ret_iter;
|
||||
} else {
|
||||
BlockPrefixIndex* prefix_index_ptr =
|
||||
total_order_seek ? nullptr : prefix_index_.get();
|
||||
|
||||
if (iter != nullptr) {
|
||||
iter->Initialize(cmp, data_, restart_offset_, num_restarts,
|
||||
prefix_index_ptr, global_seqno_, read_amp_bitmap_.get());
|
||||
} else {
|
||||
iter = new BlockIter(cmp, data_, restart_offset_, num_restarts,
|
||||
prefix_index_ptr, global_seqno_,
|
||||
read_amp_bitmap_.get());
|
||||
}
|
||||
ret_iter->Initialize(cmp, data_, restart_offset_, num_restarts,
|
||||
prefix_index_ptr, global_seqno_,
|
||||
read_amp_bitmap_.get());
|
||||
|
||||
if (read_amp_bitmap_) {
|
||||
if (read_amp_bitmap_->GetStatistics() != stats) {
|
||||
@@ -460,7 +452,7 @@ InternalIterator* Block::NewIterator(const Comparator* cmp, BlockIter* iter,
|
||||
}
|
||||
}
|
||||
|
||||
return iter;
|
||||
return ret_iter;
|
||||
}
|
||||
|
||||
void Block::SetBlockPrefixIndex(BlockPrefixIndex* prefix_index) {
|
||||
|
||||
+9
-6
@@ -168,10 +168,10 @@ class Block {
|
||||
// If total_order_seek is true, hash_index_ and prefix_index_ are ignored.
|
||||
// This option only applies for index block. For data block, hash_index_
|
||||
// and prefix_index_ are null, so this option does not matter.
|
||||
InternalIterator* NewIterator(const Comparator* comparator,
|
||||
BlockIter* iter = nullptr,
|
||||
bool total_order_seek = true,
|
||||
Statistics* stats = nullptr);
|
||||
BlockIter* NewIterator(const Comparator* comparator,
|
||||
BlockIter* iter = nullptr,
|
||||
bool total_order_seek = true,
|
||||
Statistics* stats = nullptr);
|
||||
void SetBlockPrefixIndex(BlockPrefixIndex* prefix_index);
|
||||
|
||||
// Report an approximation of how much memory has been used.
|
||||
@@ -191,12 +191,15 @@ class Block {
|
||||
const SequenceNumber global_seqno_;
|
||||
|
||||
// No copying allowed
|
||||
Block(const Block&);
|
||||
void operator=(const Block&);
|
||||
Block(const Block&) = delete;
|
||||
void operator=(const Block&) = delete;
|
||||
};
|
||||
|
||||
class BlockIter : public InternalIterator {
|
||||
public:
|
||||
// Object created using this constructor will behave like an iterator
|
||||
// against an empty block. The state after the creation: Valid()=false
|
||||
// and status() is OK.
|
||||
BlockIter()
|
||||
: comparator_(nullptr),
|
||||
data_(nullptr),
|
||||
|
||||
@@ -783,9 +783,12 @@ Status BlockBasedTableBuilder::Finish() {
|
||||
WriteRawBlock(meta_index_builder.Finish(), kNoCompression,
|
||||
&metaindex_block_handle);
|
||||
|
||||
const bool is_data_block = true;
|
||||
WriteBlock(index_blocks.index_block_contents, &index_block_handle,
|
||||
!is_data_block);
|
||||
if (r->table_options.enable_index_compression) {
|
||||
WriteBlock(index_blocks.index_block_contents, &index_block_handle, false);
|
||||
} else {
|
||||
WriteRawBlock(index_blocks.index_block_contents, kNoCompression,
|
||||
&index_block_handle);
|
||||
}
|
||||
// If there are more index partitions, finish them and write them out
|
||||
Status& s = index_builder_status;
|
||||
while (s.IsIncomplete()) {
|
||||
@@ -793,8 +796,13 @@ Status BlockBasedTableBuilder::Finish() {
|
||||
if (!s.ok() && !s.IsIncomplete()) {
|
||||
return s;
|
||||
}
|
||||
WriteBlock(index_blocks.index_block_contents, &index_block_handle,
|
||||
!is_data_block);
|
||||
if (r->table_options.enable_index_compression) {
|
||||
WriteBlock(index_blocks.index_block_contents, &index_block_handle,
|
||||
false);
|
||||
} else {
|
||||
WriteRawBlock(index_blocks.index_block_contents, kNoCompression,
|
||||
&index_block_handle);
|
||||
}
|
||||
// The last index_block_handle will be for the partition index block
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,6 +223,9 @@ std::string BlockBasedTableFactory::GetPrintableTableOptions() const {
|
||||
snprintf(buffer, kBufferSize, " format_version: %d\n",
|
||||
table_options_.format_version);
|
||||
ret.append(buffer);
|
||||
snprintf(buffer, kBufferSize, " enable_index_compression: %d\n",
|
||||
table_options_.enable_index_compression);
|
||||
ret.append(buffer);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@@ -152,6 +152,9 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}},
|
||||
{"read_amp_bytes_per_bit",
|
||||
{offsetof(struct BlockBasedTableOptions, read_amp_bytes_per_bit),
|
||||
OptionType::kSizeT, OptionVerificationType::kNormal, false, 0}}};
|
||||
OptionType::kSizeT, OptionVerificationType::kNormal, false, 0}},
|
||||
{"enable_index_compression",
|
||||
{offsetof(struct BlockBasedTableOptions, enable_index_compression),
|
||||
OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}};
|
||||
#endif // !ROCKSDB_LITE
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "table/block.h"
|
||||
#include "table/block_based_filter_block.h"
|
||||
#include "table/block_based_table_factory.h"
|
||||
#include "table/block_fetcher.h"
|
||||
#include "table/block_prefix_index.h"
|
||||
#include "table/filter_block.h"
|
||||
#include "table/format.h"
|
||||
@@ -78,9 +79,10 @@ Status ReadBlockFromFile(
|
||||
const PersistentCacheOptions& cache_options, SequenceNumber global_seqno,
|
||||
size_t read_amp_bytes_per_bit) {
|
||||
BlockContents contents;
|
||||
Status s = ReadBlockContents(file, prefetch_buffer, footer, options, handle,
|
||||
&contents, ioptions, do_uncompress,
|
||||
compression_dict, cache_options);
|
||||
BlockFetcher block_fetcher(file, prefetch_buffer, footer, options, handle,
|
||||
&contents, ioptions, do_uncompress,
|
||||
compression_dict, cache_options);
|
||||
Status s = block_fetcher.ReadBlockContents();
|
||||
if (s.ok()) {
|
||||
result->reset(new Block(std::move(contents), global_seqno,
|
||||
read_amp_bytes_per_bit, ioptions.statistics));
|
||||
@@ -425,20 +427,23 @@ class HashIndexReader : public IndexReader {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Slice dummy_comp_dict;
|
||||
// Read contents for the blocks
|
||||
BlockContents prefixes_contents;
|
||||
s = ReadBlockContents(file, prefetch_buffer, footer, ReadOptions(),
|
||||
prefixes_handle, &prefixes_contents, ioptions,
|
||||
true /* decompress */, Slice() /*compression dict*/,
|
||||
cache_options);
|
||||
BlockFetcher prefixes_block_fetcher(
|
||||
file, prefetch_buffer, footer, ReadOptions(), prefixes_handle,
|
||||
&prefixes_contents, ioptions, true /* decompress */,
|
||||
dummy_comp_dict /*compression dict*/, cache_options);
|
||||
s = prefixes_block_fetcher.ReadBlockContents();
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
BlockContents prefixes_meta_contents;
|
||||
s = ReadBlockContents(file, prefetch_buffer, footer, ReadOptions(),
|
||||
prefixes_meta_handle, &prefixes_meta_contents,
|
||||
ioptions, true /* decompress */,
|
||||
Slice() /*compression dict*/, cache_options);
|
||||
BlockFetcher prefixes_meta_block_fetcher(
|
||||
file, prefetch_buffer, footer, ReadOptions(), prefixes_meta_handle,
|
||||
&prefixes_meta_contents, ioptions, true /* decompress */,
|
||||
dummy_comp_dict /*compression dict*/, cache_options);
|
||||
prefixes_meta_block_fetcher.ReadBlockContents();
|
||||
if (!s.ok()) {
|
||||
// TODO: log error
|
||||
return Status::OK();
|
||||
@@ -754,23 +759,27 @@ Status BlockBasedTable::Open(const ImmutableCFOptions& ioptions,
|
||||
|
||||
// Read the compression dictionary meta block
|
||||
bool found_compression_dict;
|
||||
s = SeekToCompressionDictBlock(meta_iter.get(), &found_compression_dict);
|
||||
BlockHandle compression_dict_handle;
|
||||
s = SeekToCompressionDictBlock(meta_iter.get(), &found_compression_dict,
|
||||
&compression_dict_handle);
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(
|
||||
rep->ioptions.info_log,
|
||||
"Error when seeking to compression dictionary block from file: %s",
|
||||
s.ToString().c_str());
|
||||
} else if (found_compression_dict) {
|
||||
} else if (found_compression_dict && !compression_dict_handle.IsNull()) {
|
||||
// TODO(andrewkr): Add to block cache if cache_index_and_filter_blocks is
|
||||
// true.
|
||||
unique_ptr<BlockContents> compression_dict_block{new BlockContents()};
|
||||
// TODO(andrewkr): ReadMetaBlock repeats SeekToCompressionDictBlock().
|
||||
// maybe decode a handle from meta_iter
|
||||
// and do ReadBlockContents(handle) instead
|
||||
s = rocksdb::ReadMetaBlock(rep->file.get(), prefetch_buffer.get(),
|
||||
file_size, kBlockBasedTableMagicNumber,
|
||||
rep->ioptions, rocksdb::kCompressionDictBlock,
|
||||
compression_dict_block.get());
|
||||
std::unique_ptr<BlockContents> compression_dict_cont{new BlockContents()};
|
||||
PersistentCacheOptions cache_options;
|
||||
ReadOptions read_options;
|
||||
read_options.verify_checksums = false;
|
||||
BlockFetcher compression_block_fetcher(
|
||||
rep->file.get(), prefetch_buffer.get(), rep->footer, read_options,
|
||||
compression_dict_handle, compression_dict_cont.get(), rep->ioptions, false /* decompress */,
|
||||
Slice() /*compression dict*/, cache_options);
|
||||
s = compression_block_fetcher.ReadBlockContents();
|
||||
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(
|
||||
rep->ioptions.info_log,
|
||||
@@ -778,7 +787,7 @@ Status BlockBasedTable::Open(const ImmutableCFOptions& ioptions,
|
||||
"block %s",
|
||||
s.ToString().c_str());
|
||||
} else {
|
||||
rep->compression_dict_block = std::move(compression_dict_block);
|
||||
rep->compression_dict_block = std::move(compression_dict_cont);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1193,11 +1202,16 @@ FilterBlockReader* BlockBasedTable::ReadFilter(
|
||||
return nullptr;
|
||||
}
|
||||
BlockContents block;
|
||||
if (!ReadBlockContents(rep->file.get(), prefetch_buffer, rep->footer,
|
||||
ReadOptions(), filter_handle, &block, rep->ioptions,
|
||||
false /* decompress */, Slice() /*compression dict*/,
|
||||
rep->persistent_cache_options)
|
||||
.ok()) {
|
||||
|
||||
Slice dummy_comp_dict;
|
||||
|
||||
BlockFetcher block_fetcher(rep->file.get(), prefetch_buffer, rep->footer,
|
||||
ReadOptions(), filter_handle, &block,
|
||||
rep->ioptions, false /* decompress */,
|
||||
dummy_comp_dict, rep->persistent_cache_options);
|
||||
Status s = block_fetcher.ReadBlockContents();
|
||||
|
||||
if (!s.ok()) {
|
||||
// Error reading the block
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1428,7 +1442,7 @@ InternalIterator* BlockBasedTable::NewIndexIterator(
|
||||
return iter;
|
||||
}
|
||||
|
||||
InternalIterator* BlockBasedTable::NewDataBlockIterator(
|
||||
BlockIter* BlockBasedTable::NewDataBlockIterator(
|
||||
Rep* rep, const ReadOptions& ro, const Slice& index_value,
|
||||
BlockIter* input_iter, bool is_index, GetContext* get_context) {
|
||||
BlockHandle handle;
|
||||
@@ -1444,7 +1458,7 @@ InternalIterator* BlockBasedTable::NewDataBlockIterator(
|
||||
// into an iterator over the contents of the corresponding block.
|
||||
// If input_iter is null, new a iterator
|
||||
// If input_iter is not null, update this iter and return it
|
||||
InternalIterator* BlockBasedTable::NewDataBlockIterator(
|
||||
BlockIter* BlockBasedTable::NewDataBlockIterator(
|
||||
Rep* rep, const ReadOptions& ro, const BlockHandle& handle,
|
||||
BlockIter* input_iter, bool is_index, GetContext* get_context, Status s) {
|
||||
PERF_TIMER_GUARD(new_table_block_iter_nanos);
|
||||
@@ -1462,16 +1476,18 @@ InternalIterator* BlockBasedTable::NewDataBlockIterator(
|
||||
get_context);
|
||||
}
|
||||
|
||||
BlockIter* iter;
|
||||
if (input_iter != nullptr) {
|
||||
iter = input_iter;
|
||||
} else {
|
||||
iter = new BlockIter;
|
||||
}
|
||||
// Didn't get any data from block caches.
|
||||
if (s.ok() && block.value == nullptr) {
|
||||
if (no_io) {
|
||||
// Could not read from block_cache and can't do IO
|
||||
if (input_iter != nullptr) {
|
||||
input_iter->SetStatus(Status::Incomplete("no blocking io"));
|
||||
return input_iter;
|
||||
} else {
|
||||
return NewErrorInternalIterator(Status::Incomplete("no blocking io"));
|
||||
}
|
||||
iter->SetStatus(Status::Incomplete("no blocking io"));
|
||||
return iter;
|
||||
}
|
||||
std::unique_ptr<Block> block_value;
|
||||
s = ReadBlockFromFile(rep->file.get(), nullptr /* prefetch_buffer */,
|
||||
@@ -1484,10 +1500,9 @@ InternalIterator* BlockBasedTable::NewDataBlockIterator(
|
||||
}
|
||||
}
|
||||
|
||||
InternalIterator* iter;
|
||||
if (s.ok()) {
|
||||
assert(block.value != nullptr);
|
||||
iter = block.value->NewIterator(&rep->internal_comparator, input_iter, true,
|
||||
iter = block.value->NewIterator(&rep->internal_comparator, iter, true,
|
||||
rep->ioptions.statistics);
|
||||
if (block.cache_handle != nullptr) {
|
||||
iter->RegisterCleanup(&ReleaseCachedEntry, block_cache,
|
||||
@@ -1497,12 +1512,7 @@ InternalIterator* BlockBasedTable::NewDataBlockIterator(
|
||||
}
|
||||
} else {
|
||||
assert(block.value == nullptr);
|
||||
if (input_iter != nullptr) {
|
||||
input_iter->SetStatus(s);
|
||||
iter = input_iter;
|
||||
} else {
|
||||
iter = NewErrorInternalIterator(s);
|
||||
}
|
||||
iter->SetStatus(s);
|
||||
}
|
||||
return iter;
|
||||
}
|
||||
@@ -1983,11 +1993,13 @@ Status BlockBasedTable::VerifyChecksumInBlocks(InternalIterator* index_iter) {
|
||||
break;
|
||||
}
|
||||
BlockContents contents;
|
||||
s = ReadBlockContents(rep_->file.get(), nullptr /* prefetch buffer */,
|
||||
rep_->footer, ReadOptions(), handle, &contents,
|
||||
rep_->ioptions, false /* decompress */,
|
||||
Slice() /*compression dict*/,
|
||||
rep_->persistent_cache_options);
|
||||
Slice dummy_comp_dict;
|
||||
BlockFetcher block_fetcher(rep_->file.get(), nullptr /* prefetch buffer */,
|
||||
rep_->footer, ReadOptions(), handle, &contents,
|
||||
rep_->ioptions, false /* decompress */,
|
||||
dummy_comp_dict /*compression dict*/,
|
||||
rep_->persistent_cache_options);
|
||||
s = block_fetcher.ReadBlockContents();
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
@@ -2272,12 +2284,14 @@ Status BlockBasedTable::DumpTable(WritableFile* out_file) {
|
||||
BlockHandle handle;
|
||||
if (FindMetaBlock(meta_iter.get(), filter_block_key, &handle).ok()) {
|
||||
BlockContents block;
|
||||
if (ReadBlockContents(rep_->file.get(), nullptr /* prefetch_buffer */,
|
||||
rep_->footer, ReadOptions(), handle, &block,
|
||||
rep_->ioptions, false /*decompress*/,
|
||||
Slice() /*compression dict*/,
|
||||
rep_->persistent_cache_options)
|
||||
.ok()) {
|
||||
Slice dummy_comp_dict;
|
||||
BlockFetcher block_fetcher(
|
||||
rep_->file.get(), nullptr /* prefetch_buffer */, rep_->footer,
|
||||
ReadOptions(), handle, &block, rep_->ioptions, false /*decompress*/,
|
||||
dummy_comp_dict /*compression dict*/,
|
||||
rep_->persistent_cache_options);
|
||||
s = block_fetcher.ReadBlockContents();
|
||||
if (!s.ok()) {
|
||||
rep_->filter.reset(new BlockBasedFilterBlockReader(
|
||||
rep_->ioptions.prefix_extractor, table_options,
|
||||
table_options.whole_key_filtering, std::move(block),
|
||||
|
||||
@@ -215,14 +215,17 @@ class BlockBasedTable : public TableReader {
|
||||
private:
|
||||
friend class MockedBlockBasedTable;
|
||||
// input_iter: if it is not null, update this one and return it as Iterator
|
||||
static InternalIterator* NewDataBlockIterator(
|
||||
Rep* rep, const ReadOptions& ro, const Slice& index_value,
|
||||
BlockIter* input_iter = nullptr, bool is_index = false,
|
||||
GetContext* get_context = nullptr);
|
||||
static InternalIterator* NewDataBlockIterator(
|
||||
Rep* rep, const ReadOptions& ro, const BlockHandle& block_hanlde,
|
||||
BlockIter* input_iter = nullptr, bool is_index = false,
|
||||
GetContext* get_context = nullptr, Status s = Status());
|
||||
static BlockIter* NewDataBlockIterator(Rep* rep, const ReadOptions& ro,
|
||||
const Slice& index_value,
|
||||
BlockIter* input_iter = nullptr,
|
||||
bool is_index = false,
|
||||
GetContext* get_context = nullptr);
|
||||
static BlockIter* NewDataBlockIterator(Rep* rep, const ReadOptions& ro,
|
||||
const BlockHandle& block_hanlde,
|
||||
BlockIter* input_iter = nullptr,
|
||||
bool is_index = false,
|
||||
GetContext* get_context = nullptr,
|
||||
Status s = Status());
|
||||
// If block cache enabled (compressed or uncompressed), looks for the block
|
||||
// identified by handle in (1) uncompressed cache, (2) compressed cache, and
|
||||
// then (3) file. If found, inserts into the cache(s) that were searched
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "table/block_fetcher.h"
|
||||
|
||||
#include <string>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "table/block.h"
|
||||
#include "table/block_based_table_reader.h"
|
||||
#include "table/persistent_cache_helper.h"
|
||||
#include "table/format.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/compression.h"
|
||||
#include "util/crc32c.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/stop_watch.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/xxhash.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
void BlockFetcher::CheckBlockChecksum() {
|
||||
// Check the crc of the type and the block contents
|
||||
if (read_options_.verify_checksums) {
|
||||
const char* data = slice_.data(); // Pointer to where Read put the data
|
||||
PERF_TIMER_GUARD(block_checksum_time);
|
||||
uint32_t value = DecodeFixed32(data + block_size_ + 1);
|
||||
uint32_t actual = 0;
|
||||
switch (footer_.checksum()) {
|
||||
case kNoChecksum:
|
||||
break;
|
||||
case kCRC32c:
|
||||
value = crc32c::Unmask(value);
|
||||
actual = crc32c::Value(data, block_size_ + 1);
|
||||
break;
|
||||
case kxxHash:
|
||||
actual = XXH32(data, static_cast<int>(block_size_) + 1, 0);
|
||||
break;
|
||||
default:
|
||||
status_ = Status::Corruption(
|
||||
"unknown checksum type " + ToString(footer_.checksum()) + " in " +
|
||||
file_->file_name() + " offset " + ToString(handle_.offset()) +
|
||||
" size " + ToString(block_size_));
|
||||
}
|
||||
if (status_.ok() && actual != value) {
|
||||
status_ = Status::Corruption(
|
||||
"block checksum mismatch: expected " + ToString(actual) + ", got " +
|
||||
ToString(value) + " in " + file_->file_name() + " offset " +
|
||||
ToString(handle_.offset()) + " size " + ToString(block_size_));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool BlockFetcher::TryGetUncompressBlockFromPersistentCache() {
|
||||
if (cache_options_.persistent_cache &&
|
||||
!cache_options_.persistent_cache->IsCompressed()) {
|
||||
Status status = PersistentCacheHelper::LookupUncompressedPage(
|
||||
cache_options_, handle_, contents_);
|
||||
if (status.ok()) {
|
||||
// uncompressed page is found for the block handle
|
||||
return true;
|
||||
} else {
|
||||
// uncompressed page is not found
|
||||
if (ioptions_.info_log && !status.IsNotFound()) {
|
||||
assert(!status.ok());
|
||||
ROCKS_LOG_INFO(ioptions_.info_log,
|
||||
"Error reading from persistent cache. %s",
|
||||
status.ToString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BlockFetcher::TryGetFromPrefetchBuffer() {
|
||||
if (prefetch_buffer_ != nullptr &&
|
||||
prefetch_buffer_->TryReadFromCache(
|
||||
handle_.offset(),
|
||||
static_cast<size_t>(handle_.size()) + kBlockTrailerSize, &slice_)) {
|
||||
block_size_ = static_cast<size_t>(handle_.size());
|
||||
CheckBlockChecksum();
|
||||
if (!status_.ok()) {
|
||||
return true;
|
||||
}
|
||||
got_from_prefetch_buffer_ = true;
|
||||
used_buf_ = const_cast<char*>(slice_.data());
|
||||
}
|
||||
return got_from_prefetch_buffer_;
|
||||
}
|
||||
|
||||
bool BlockFetcher::TryGetCompressedBlockFromPersistentCache() {
|
||||
if (cache_options_.persistent_cache &&
|
||||
cache_options_.persistent_cache->IsCompressed()) {
|
||||
// lookup uncompressed cache mode p-cache
|
||||
status_ = PersistentCacheHelper::LookupRawPage(
|
||||
cache_options_, handle_, &heap_buf_, block_size_ + kBlockTrailerSize);
|
||||
if (status_.ok()) {
|
||||
used_buf_ = heap_buf_.get();
|
||||
slice_ = Slice(heap_buf_.get(), block_size_);
|
||||
return true;
|
||||
} else if (!status_.IsNotFound() && ioptions_.info_log) {
|
||||
assert(!status_.ok());
|
||||
ROCKS_LOG_INFO(ioptions_.info_log,
|
||||
"Error reading from persistent cache. %s",
|
||||
status_.ToString().c_str());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void BlockFetcher::PrepareBufferForBlockFromFile() {
|
||||
// cache miss read from device
|
||||
if (do_uncompress_ &&
|
||||
block_size_ + kBlockTrailerSize < kDefaultStackBufferSize) {
|
||||
// If we've got a small enough hunk of data, read it in to the
|
||||
// trivially allocated stack buffer instead of needing a full malloc()
|
||||
used_buf_ = &stack_buf_[0];
|
||||
} else {
|
||||
heap_buf_ =
|
||||
std::unique_ptr<char[]>(new char[block_size_ + kBlockTrailerSize]);
|
||||
used_buf_ = heap_buf_.get();
|
||||
}
|
||||
}
|
||||
|
||||
void BlockFetcher::InsertCompressedBlockToPersistentCacheIfNeeded() {
|
||||
if (status_.ok() && read_options_.fill_cache &&
|
||||
cache_options_.persistent_cache &&
|
||||
cache_options_.persistent_cache->IsCompressed()) {
|
||||
// insert to raw cache
|
||||
PersistentCacheHelper::InsertRawPage(cache_options_, handle_, used_buf_,
|
||||
block_size_ + kBlockTrailerSize);
|
||||
}
|
||||
}
|
||||
|
||||
void BlockFetcher::InsertUncompressedBlockToPersistentCacheIfNeeded() {
|
||||
if (status_.ok() && !got_from_prefetch_buffer_ && read_options_.fill_cache &&
|
||||
cache_options_.persistent_cache &&
|
||||
!cache_options_.persistent_cache->IsCompressed()) {
|
||||
// insert to uncompressed cache
|
||||
PersistentCacheHelper::InsertUncompressedPage(cache_options_, handle_,
|
||||
*contents_);
|
||||
}
|
||||
}
|
||||
|
||||
void BlockFetcher::GetBlockContents() {
|
||||
if (slice_.data() != used_buf_) {
|
||||
// the slice content is not the buffer provided
|
||||
*contents_ = BlockContents(Slice(slice_.data(), block_size_), false,
|
||||
compression_type);
|
||||
} else {
|
||||
// page is uncompressed, the buffer either stack or heap provided
|
||||
if (got_from_prefetch_buffer_ || used_buf_ == &stack_buf_[0]) {
|
||||
heap_buf_ = std::unique_ptr<char[]>(new char[block_size_]);
|
||||
memcpy(heap_buf_.get(), used_buf_, block_size_);
|
||||
}
|
||||
*contents_ = BlockContents(std::move(heap_buf_), block_size_, true,
|
||||
compression_type);
|
||||
}
|
||||
}
|
||||
|
||||
Status BlockFetcher::ReadBlockContents() {
|
||||
block_size_ = static_cast<size_t>(handle_.size());
|
||||
|
||||
if (TryGetUncompressBlockFromPersistentCache()) {
|
||||
return Status::OK();
|
||||
}
|
||||
if (TryGetFromPrefetchBuffer()) {
|
||||
if (!status_.ok()) {
|
||||
return status_;
|
||||
}
|
||||
} else if (!TryGetCompressedBlockFromPersistentCache()) {
|
||||
PrepareBufferForBlockFromFile();
|
||||
Status s;
|
||||
|
||||
{
|
||||
PERF_TIMER_GUARD(block_read_time);
|
||||
// Actual file read
|
||||
status_ = file_->Read(handle_.offset(), block_size_ + kBlockTrailerSize,
|
||||
&slice_, used_buf_);
|
||||
}
|
||||
PERF_COUNTER_ADD(block_read_count, 1);
|
||||
PERF_COUNTER_ADD(block_read_byte, block_size_ + kBlockTrailerSize);
|
||||
if (!status_.ok()) {
|
||||
return status_;
|
||||
}
|
||||
|
||||
if (slice_.size() != block_size_ + kBlockTrailerSize) {
|
||||
return Status::Corruption("truncated block read from " +
|
||||
file_->file_name() + " offset " +
|
||||
ToString(handle_.offset()) + ", expected " +
|
||||
ToString(block_size_ + kBlockTrailerSize) +
|
||||
" bytes, got " + ToString(slice_.size()));
|
||||
}
|
||||
|
||||
CheckBlockChecksum();
|
||||
if (status_.ok()) {
|
||||
InsertCompressedBlockToPersistentCacheIfNeeded();
|
||||
} else {
|
||||
return status_;
|
||||
}
|
||||
}
|
||||
|
||||
PERF_TIMER_GUARD(block_decompress_time);
|
||||
|
||||
compression_type =
|
||||
static_cast<rocksdb::CompressionType>(slice_.data()[block_size_]);
|
||||
|
||||
if (do_uncompress_ && compression_type != kNoCompression) {
|
||||
// compressed page, uncompress, update cache
|
||||
status_ = UncompressBlockContents(slice_.data(), block_size_, contents_,
|
||||
footer_.version(), compression_dict_,
|
||||
ioptions_);
|
||||
} else {
|
||||
GetBlockContents();
|
||||
}
|
||||
|
||||
InsertUncompressedBlockToPersistentCacheIfNeeded();
|
||||
|
||||
return status_;
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#pragma once
|
||||
#include "table/block.h"
|
||||
#include "table/format.h"
|
||||
|
||||
namespace rocksdb {
|
||||
class BlockFetcher {
|
||||
public:
|
||||
// Read the block identified by "handle" from "file".
|
||||
// The only relevant option is options.verify_checksums for now.
|
||||
// On failure return non-OK.
|
||||
// On success fill *result and return OK - caller owns *result
|
||||
// @param compression_dict Data for presetting the compression library's
|
||||
// dictionary.
|
||||
BlockFetcher(RandomAccessFileReader* file,
|
||||
FilePrefetchBuffer* prefetch_buffer, const Footer& footer,
|
||||
const ReadOptions& read_options, const BlockHandle& handle,
|
||||
BlockContents* contents,
|
||||
const ImmutableCFOptions& ioptions,
|
||||
bool do_uncompress, const Slice& compression_dict,
|
||||
const PersistentCacheOptions& cache_options)
|
||||
: file_(file),
|
||||
prefetch_buffer_(prefetch_buffer),
|
||||
footer_(footer),
|
||||
read_options_(read_options),
|
||||
handle_(handle),
|
||||
contents_(contents),
|
||||
ioptions_(ioptions),
|
||||
do_uncompress_(do_uncompress),
|
||||
compression_dict_(compression_dict),
|
||||
cache_options_(cache_options) {}
|
||||
Status ReadBlockContents();
|
||||
|
||||
private:
|
||||
static const uint32_t kDefaultStackBufferSize = 5000;
|
||||
|
||||
RandomAccessFileReader* file_;
|
||||
FilePrefetchBuffer* prefetch_buffer_;
|
||||
const Footer& footer_;
|
||||
const ReadOptions read_options_;
|
||||
const BlockHandle& handle_;
|
||||
BlockContents* contents_;
|
||||
const ImmutableCFOptions& ioptions_;
|
||||
bool do_uncompress_;
|
||||
const Slice& compression_dict_;
|
||||
const PersistentCacheOptions& cache_options_;
|
||||
Status status_;
|
||||
Slice slice_;
|
||||
char* used_buf_ = nullptr;
|
||||
size_t block_size_;
|
||||
std::unique_ptr<char[]> heap_buf_;
|
||||
char stack_buf_[kDefaultStackBufferSize];
|
||||
bool got_from_prefetch_buffer_ = false;
|
||||
rocksdb::CompressionType compression_type;
|
||||
|
||||
// return true if found
|
||||
bool TryGetUncompressBlockFromPersistentCache();
|
||||
// return true if found
|
||||
bool TryGetFromPrefetchBuffer();
|
||||
bool TryGetCompressedBlockFromPersistentCache();
|
||||
void PrepareBufferForBlockFromFile();
|
||||
void GetBlockContents();
|
||||
void InsertCompressedBlockToPersistentCacheIfNeeded();
|
||||
void InsertUncompressedBlockToPersistentCacheIfNeeded();
|
||||
void CheckBlockChecksum();
|
||||
};
|
||||
} // namespace rocksdb
|
||||
+43
-19
@@ -229,18 +229,47 @@ class BlockReadAmpBitmapSlowAndAccurate {
|
||||
marked_ranges_.emplace(end_offset, start_offset);
|
||||
}
|
||||
|
||||
void ResetCheckSequence() { iter_valid_ = false; }
|
||||
|
||||
// Return true if any byte in this range was Marked
|
||||
// This does linear search from the previous position. When calling
|
||||
// multiple times, `offset` needs to be incremental to get correct results.
|
||||
// Call ResetCheckSequence() to reset it.
|
||||
bool IsPinMarked(size_t offset) {
|
||||
auto it = marked_ranges_.lower_bound(
|
||||
if (iter_valid_) {
|
||||
// Has existing iterator, try linear search from
|
||||
// the iterator.
|
||||
for (int i = 0; i < 64; i++) {
|
||||
if (offset < iter_->second) {
|
||||
return false;
|
||||
}
|
||||
if (offset <= iter_->first) {
|
||||
return true;
|
||||
}
|
||||
|
||||
iter_++;
|
||||
if (iter_ == marked_ranges_.end()) {
|
||||
iter_valid_ = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Initial call or have linear searched too many times.
|
||||
// Do binary search.
|
||||
iter_ = marked_ranges_.lower_bound(
|
||||
std::make_pair(offset, static_cast<size_t>(0)));
|
||||
if (it == marked_ranges_.end()) {
|
||||
if (iter_ == marked_ranges_.end()) {
|
||||
iter_valid_ = false;
|
||||
return false;
|
||||
}
|
||||
return offset <= it->first && offset >= it->second;
|
||||
iter_valid_ = true;
|
||||
return offset <= iter_->first && offset >= iter_->second;
|
||||
}
|
||||
|
||||
private:
|
||||
std::set<std::pair<size_t, size_t>> marked_ranges_;
|
||||
std::set<std::pair<size_t, size_t>>::iterator iter_;
|
||||
bool iter_valid_ = false;
|
||||
};
|
||||
|
||||
TEST_F(BlockTest, BlockReadAmpBitmap) {
|
||||
@@ -251,18 +280,16 @@ TEST_F(BlockTest, BlockReadAmpBitmap) {
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
std::vector<size_t> block_sizes = {
|
||||
1, // 1 byte
|
||||
32, // 32 bytes
|
||||
61, // 61 bytes
|
||||
64, // 64 bytes
|
||||
512, // 0.5 KB
|
||||
1024, // 1 KB
|
||||
1024 * 4, // 4 KB
|
||||
1024 * 10, // 10 KB
|
||||
1024 * 50, // 50 KB
|
||||
1024 * 1024, // 1 MB
|
||||
1024 * 1024 * 4, // 4 MB
|
||||
1024 * 1024 * 50, // 10 MB
|
||||
1, // 1 byte
|
||||
32, // 32 bytes
|
||||
61, // 61 bytes
|
||||
64, // 64 bytes
|
||||
512, // 0.5 KB
|
||||
1024, // 1 KB
|
||||
1024 * 4, // 4 KB
|
||||
1024 * 10, // 10 KB
|
||||
1024 * 50, // 50 KB
|
||||
1024 * 1024 * 4, // 5 MB
|
||||
777,
|
||||
124653,
|
||||
};
|
||||
@@ -278,10 +305,6 @@ TEST_F(BlockTest, BlockReadAmpBitmap) {
|
||||
if (block_size % kBytesPerBit != 0) {
|
||||
needed_bits++;
|
||||
}
|
||||
size_t bitmap_size = needed_bits / 32;
|
||||
if (needed_bits % 32 != 0) {
|
||||
bitmap_size++;
|
||||
}
|
||||
|
||||
ASSERT_EQ(stats->getTickerCount(READ_AMP_TOTAL_READ_BYTES), block_size);
|
||||
|
||||
@@ -309,6 +332,7 @@ TEST_F(BlockTest, BlockReadAmpBitmap) {
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < random_entries.size(); i++) {
|
||||
read_amp_slow_and_accurate.ResetCheckSequence();
|
||||
auto ¤t_entry = random_entries[rnd.Next() % random_entries.size()];
|
||||
|
||||
read_amp_bitmap.Mark(static_cast<uint32_t>(current_entry.first),
|
||||
|
||||
+1
-200
@@ -17,6 +17,7 @@
|
||||
#include "rocksdb/env.h"
|
||||
#include "table/block.h"
|
||||
#include "table/block_based_table_reader.h"
|
||||
#include "table/block_fetcher.h"
|
||||
#include "table/persistent_cache_helper.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/compression.h"
|
||||
@@ -40,7 +41,6 @@ extern const uint64_t kPlainTableMagicNumber;
|
||||
const uint64_t kLegacyPlainTableMagicNumber = 0;
|
||||
const uint64_t kPlainTableMagicNumber = 0;
|
||||
#endif
|
||||
const uint32_t DefaultStackBufferSize = 5000;
|
||||
|
||||
bool ShouldReportDetailedTime(Env* env, Statistics* stats) {
|
||||
return env != nullptr && stats != nullptr &&
|
||||
@@ -264,205 +264,6 @@ Status ReadFooterFromFile(RandomAccessFileReader* file,
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Without anonymous namespace here, we fail the warning -Wmissing-prototypes
|
||||
namespace {
|
||||
Status CheckBlockChecksum(const ReadOptions& options, const Footer& footer,
|
||||
const Slice& contents, size_t block_size,
|
||||
RandomAccessFileReader* file,
|
||||
const BlockHandle& handle) {
|
||||
Status s;
|
||||
// Check the crc of the type and the block contents
|
||||
if (options.verify_checksums) {
|
||||
const char* data = contents.data(); // Pointer to where Read put the data
|
||||
PERF_TIMER_GUARD(block_checksum_time);
|
||||
uint32_t value = DecodeFixed32(data + block_size + 1);
|
||||
uint32_t actual = 0;
|
||||
switch (footer.checksum()) {
|
||||
case kNoChecksum:
|
||||
break;
|
||||
case kCRC32c:
|
||||
value = crc32c::Unmask(value);
|
||||
actual = crc32c::Value(data, block_size + 1);
|
||||
break;
|
||||
case kxxHash:
|
||||
actual = XXH32(data, static_cast<int>(block_size) + 1, 0);
|
||||
break;
|
||||
default:
|
||||
s = Status::Corruption(
|
||||
"unknown checksum type " + ToString(footer.checksum()) + " in " +
|
||||
file->file_name() + " offset " + ToString(handle.offset()) +
|
||||
" size " + ToString(block_size));
|
||||
}
|
||||
if (s.ok() && actual != value) {
|
||||
s = Status::Corruption(
|
||||
"block checksum mismatch: expected " + ToString(actual) + ", got " +
|
||||
ToString(value) + " in " + file->file_name() + " offset " +
|
||||
ToString(handle.offset()) + " size " + ToString(block_size));
|
||||
}
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// Read a block and check its CRC
|
||||
// contents is the result of reading.
|
||||
// According to the implementation of file->Read, contents may not point to buf
|
||||
Status ReadBlock(RandomAccessFileReader* file, const Footer& footer,
|
||||
const ReadOptions& options, const BlockHandle& handle,
|
||||
Slice* contents, /* result of reading */ char* buf) {
|
||||
size_t n = static_cast<size_t>(handle.size());
|
||||
Status s;
|
||||
|
||||
{
|
||||
PERF_TIMER_GUARD(block_read_time);
|
||||
s = file->Read(handle.offset(), n + kBlockTrailerSize, contents, buf);
|
||||
}
|
||||
|
||||
PERF_COUNTER_ADD(block_read_count, 1);
|
||||
PERF_COUNTER_ADD(block_read_byte, n + kBlockTrailerSize);
|
||||
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
if (contents->size() != n + kBlockTrailerSize) {
|
||||
return Status::Corruption("truncated block read from " + file->file_name() +
|
||||
" offset " + ToString(handle.offset()) +
|
||||
", expected " + ToString(n + kBlockTrailerSize) +
|
||||
" bytes, got " + ToString(contents->size()));
|
||||
}
|
||||
return CheckBlockChecksum(options, footer, *contents, n, file, handle);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Status ReadBlockContents(RandomAccessFileReader* file,
|
||||
FilePrefetchBuffer* prefetch_buffer,
|
||||
const Footer& footer, const ReadOptions& read_options,
|
||||
const BlockHandle& handle, BlockContents* contents,
|
||||
const ImmutableCFOptions& ioptions,
|
||||
bool decompression_requested,
|
||||
const Slice& compression_dict,
|
||||
const PersistentCacheOptions& cache_options) {
|
||||
Status status;
|
||||
Slice slice;
|
||||
size_t n = static_cast<size_t>(handle.size());
|
||||
std::unique_ptr<char[]> heap_buf;
|
||||
char stack_buf[DefaultStackBufferSize];
|
||||
char* used_buf = nullptr;
|
||||
rocksdb::CompressionType compression_type;
|
||||
|
||||
if (cache_options.persistent_cache &&
|
||||
!cache_options.persistent_cache->IsCompressed()) {
|
||||
status = PersistentCacheHelper::LookupUncompressedPage(cache_options,
|
||||
handle, contents);
|
||||
if (status.ok()) {
|
||||
// uncompressed page is found for the block handle
|
||||
return status;
|
||||
} else {
|
||||
// uncompressed page is not found
|
||||
if (ioptions.info_log && !status.IsNotFound()) {
|
||||
assert(!status.ok());
|
||||
ROCKS_LOG_INFO(ioptions.info_log,
|
||||
"Error reading from persistent cache. %s",
|
||||
status.ToString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool got_from_prefetch_buffer = false;
|
||||
if (prefetch_buffer != nullptr &&
|
||||
prefetch_buffer->TryReadFromCache(
|
||||
handle.offset(),
|
||||
static_cast<size_t>(handle.size()) + kBlockTrailerSize, &slice)) {
|
||||
status =
|
||||
CheckBlockChecksum(read_options, footer, slice,
|
||||
static_cast<size_t>(handle.size()), file, handle);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
got_from_prefetch_buffer = true;
|
||||
used_buf = const_cast<char*>(slice.data());
|
||||
} else if (cache_options.persistent_cache &&
|
||||
cache_options.persistent_cache->IsCompressed()) {
|
||||
// lookup uncompressed cache mode p-cache
|
||||
status = PersistentCacheHelper::LookupRawPage(
|
||||
cache_options, handle, &heap_buf, n + kBlockTrailerSize);
|
||||
} else {
|
||||
status = Status::NotFound();
|
||||
}
|
||||
|
||||
if (!got_from_prefetch_buffer) {
|
||||
if (status.ok()) {
|
||||
// cache hit
|
||||
used_buf = heap_buf.get();
|
||||
slice = Slice(heap_buf.get(), n);
|
||||
} else {
|
||||
if (ioptions.info_log && !status.IsNotFound()) {
|
||||
assert(!status.ok());
|
||||
ROCKS_LOG_INFO(ioptions.info_log,
|
||||
"Error reading from persistent cache. %s",
|
||||
status.ToString().c_str());
|
||||
}
|
||||
// cache miss read from device
|
||||
if (decompression_requested &&
|
||||
n + kBlockTrailerSize < DefaultStackBufferSize) {
|
||||
// If we've got a small enough hunk of data, read it in to the
|
||||
// trivially allocated stack buffer instead of needing a full malloc()
|
||||
used_buf = &stack_buf[0];
|
||||
} else {
|
||||
heap_buf = std::unique_ptr<char[]>(new char[n + kBlockTrailerSize]);
|
||||
used_buf = heap_buf.get();
|
||||
}
|
||||
|
||||
status = ReadBlock(file, footer, read_options, handle, &slice, used_buf);
|
||||
if (status.ok() && read_options.fill_cache &&
|
||||
cache_options.persistent_cache &&
|
||||
cache_options.persistent_cache->IsCompressed()) {
|
||||
// insert to raw cache
|
||||
PersistentCacheHelper::InsertRawPage(cache_options, handle, used_buf,
|
||||
n + kBlockTrailerSize);
|
||||
}
|
||||
}
|
||||
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
PERF_TIMER_GUARD(block_decompress_time);
|
||||
|
||||
compression_type = static_cast<rocksdb::CompressionType>(slice.data()[n]);
|
||||
|
||||
if (decompression_requested && compression_type != kNoCompression) {
|
||||
// compressed page, uncompress, update cache
|
||||
status = UncompressBlockContents(slice.data(), n, contents,
|
||||
footer.version(), compression_dict,
|
||||
ioptions);
|
||||
} else if (slice.data() != used_buf) {
|
||||
// the slice content is not the buffer provided
|
||||
*contents = BlockContents(Slice(slice.data(), n), false, compression_type);
|
||||
} else {
|
||||
// page is uncompressed, the buffer either stack or heap provided
|
||||
if (got_from_prefetch_buffer || used_buf == &stack_buf[0]) {
|
||||
heap_buf = std::unique_ptr<char[]>(new char[n]);
|
||||
memcpy(heap_buf.get(), used_buf, n);
|
||||
}
|
||||
*contents = BlockContents(std::move(heap_buf), n, true, compression_type);
|
||||
}
|
||||
|
||||
if (status.ok() && !got_from_prefetch_buffer && read_options.fill_cache &&
|
||||
cache_options.persistent_cache &&
|
||||
!cache_options.persistent_cache->IsCompressed()) {
|
||||
// insert to uncompressed cache
|
||||
PersistentCacheHelper::InsertUncompressedPage(cache_options, handle,
|
||||
*contents);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
Status UncompressBlockContentsForCompressionType(
|
||||
const char* data, size_t n, BlockContents* contents,
|
||||
uint32_t format_version, const Slice& compression_dict,
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class Block;
|
||||
class RandomAccessFile;
|
||||
struct ReadOptions;
|
||||
|
||||
|
||||
+34
-13
@@ -11,6 +11,7 @@
|
||||
#include "rocksdb/table.h"
|
||||
#include "rocksdb/table_properties.h"
|
||||
#include "table/block.h"
|
||||
#include "table/block_fetcher.h"
|
||||
#include "table/format.h"
|
||||
#include "table/internal_iterator.h"
|
||||
#include "table/persistent_cache_helper.h"
|
||||
@@ -176,8 +177,13 @@ Status ReadProperties(const Slice& handle_value, RandomAccessFileReader* file,
|
||||
ReadOptions read_options;
|
||||
read_options.verify_checksums = false;
|
||||
Status s;
|
||||
s = ReadBlockContents(file, prefetch_buffer, footer, read_options, handle,
|
||||
&block_contents, ioptions, false /* decompress */);
|
||||
Slice compression_dict;
|
||||
PersistentCacheOptions cache_options;
|
||||
|
||||
BlockFetcher block_fetcher(
|
||||
file, prefetch_buffer, footer, read_options, handle, &block_contents,
|
||||
ioptions, false /* decompress */, compression_dict, cache_options);
|
||||
s = block_fetcher.ReadBlockContents();
|
||||
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -292,9 +298,14 @@ Status ReadTableProperties(RandomAccessFileReader* file, uint64_t file_size,
|
||||
BlockContents metaindex_contents;
|
||||
ReadOptions read_options;
|
||||
read_options.verify_checksums = false;
|
||||
s = ReadBlockContents(file, nullptr /* prefetch_buffer */, footer,
|
||||
read_options, metaindex_handle, &metaindex_contents,
|
||||
ioptions, false /* decompress */);
|
||||
Slice compression_dict;
|
||||
PersistentCacheOptions cache_options;
|
||||
|
||||
BlockFetcher block_fetcher(
|
||||
file, nullptr /* prefetch_buffer */, footer, read_options,
|
||||
metaindex_handle, &metaindex_contents, ioptions, false /* decompress */,
|
||||
compression_dict, cache_options);
|
||||
s = block_fetcher.ReadBlockContents();
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -350,9 +361,13 @@ Status FindMetaBlock(RandomAccessFileReader* file, uint64_t file_size,
|
||||
BlockContents metaindex_contents;
|
||||
ReadOptions read_options;
|
||||
read_options.verify_checksums = false;
|
||||
s = ReadBlockContents(file, nullptr /* prefetch_buffer */, footer,
|
||||
read_options, metaindex_handle, &metaindex_contents,
|
||||
ioptions, false /* do decompression */);
|
||||
Slice compression_dict;
|
||||
PersistentCacheOptions cache_options;
|
||||
BlockFetcher block_fetcher(
|
||||
file, nullptr /* prefetch_buffer */, footer, read_options,
|
||||
metaindex_handle, &metaindex_contents, ioptions,
|
||||
false /* do decompression */, compression_dict, cache_options);
|
||||
s = block_fetcher.ReadBlockContents();
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -384,9 +399,14 @@ Status ReadMetaBlock(RandomAccessFileReader* file,
|
||||
BlockContents metaindex_contents;
|
||||
ReadOptions read_options;
|
||||
read_options.verify_checksums = false;
|
||||
status = ReadBlockContents(file, prefetch_buffer, footer, read_options,
|
||||
Slice compression_dict;
|
||||
PersistentCacheOptions cache_options;
|
||||
|
||||
BlockFetcher block_fetcher(file, prefetch_buffer, footer, read_options,
|
||||
metaindex_handle, &metaindex_contents, ioptions,
|
||||
false /* decompress */);
|
||||
false /* decompress */, compression_dict,
|
||||
cache_options);
|
||||
status = block_fetcher.ReadBlockContents();
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
@@ -406,9 +426,10 @@ Status ReadMetaBlock(RandomAccessFileReader* file,
|
||||
}
|
||||
|
||||
// Reading metablock
|
||||
return ReadBlockContents(file, prefetch_buffer, footer, read_options,
|
||||
block_handle, contents, ioptions,
|
||||
false /* decompress */);
|
||||
BlockFetcher block_fetcher2(
|
||||
file, prefetch_buffer, footer, read_options, block_handle, contents,
|
||||
ioptions, false /* decompress */, compression_dict, cache_options);
|
||||
return block_fetcher2.ReadBlockContents();
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -27,7 +27,7 @@ const size_t kFadviseTrigger = 1024 * 1024; // 1MB
|
||||
struct SstFileWriter::Rep {
|
||||
Rep(const EnvOptions& _env_options, const Options& options,
|
||||
Env::IOPriority _io_priority, const Comparator* _user_comparator,
|
||||
ColumnFamilyHandle* _cfh, bool _invalidate_page_cache)
|
||||
ColumnFamilyHandle* _cfh, bool _invalidate_page_cache, bool _skip_filters)
|
||||
: env_options(_env_options),
|
||||
ioptions(options),
|
||||
mutable_cf_options(options),
|
||||
@@ -35,7 +35,8 @@ struct SstFileWriter::Rep {
|
||||
internal_comparator(_user_comparator),
|
||||
cfh(_cfh),
|
||||
invalidate_page_cache(_invalidate_page_cache),
|
||||
last_fadvise_size(0) {}
|
||||
last_fadvise_size(0),
|
||||
skip_filters(_skip_filters) {}
|
||||
|
||||
std::unique_ptr<WritableFileWriter> file_writer;
|
||||
std::unique_ptr<TableBuilder> builder;
|
||||
@@ -54,6 +55,7 @@ struct SstFileWriter::Rep {
|
||||
// The size of the file during the last time we called Fadvise to remove
|
||||
// cached pages from page cache.
|
||||
uint64_t last_fadvise_size;
|
||||
bool skip_filters;
|
||||
Status Add(const Slice& user_key, const Slice& value,
|
||||
const ValueType value_type) {
|
||||
if (!builder) {
|
||||
@@ -122,9 +124,9 @@ SstFileWriter::SstFileWriter(const EnvOptions& env_options,
|
||||
const Comparator* user_comparator,
|
||||
ColumnFamilyHandle* column_family,
|
||||
bool invalidate_page_cache,
|
||||
Env::IOPriority io_priority)
|
||||
Env::IOPriority io_priority, bool skip_filters)
|
||||
: rep_(new Rep(env_options, options, io_priority, user_comparator,
|
||||
column_family, invalidate_page_cache)) {
|
||||
column_family, invalidate_page_cache, skip_filters)) {
|
||||
rep_->file_info.file_size = 0;
|
||||
}
|
||||
|
||||
@@ -189,8 +191,8 @@ Status SstFileWriter::Open(const std::string& file_path) {
|
||||
TableBuilderOptions table_builder_options(
|
||||
r->ioptions, r->internal_comparator, &int_tbl_prop_collector_factories,
|
||||
compression_type, r->ioptions.compression_opts,
|
||||
nullptr /* compression_dict */, false /* skip_filters */,
|
||||
r->column_family_name, unknown_level);
|
||||
nullptr /* compression_dict */, r->skip_filters, r->column_family_name,
|
||||
unknown_level);
|
||||
r->file_writer.reset(
|
||||
new WritableFileWriter(std::move(sst_file), r->env_options));
|
||||
|
||||
|
||||
@@ -215,8 +215,9 @@ Status SeekToPropertiesBlock(InternalIterator* meta_iter, bool* is_found) {
|
||||
|
||||
// Seek to the compression dictionary block.
|
||||
// Return true if it successfully seeks to that block.
|
||||
Status SeekToCompressionDictBlock(InternalIterator* meta_iter, bool* is_found) {
|
||||
return SeekToMetaBlock(meta_iter, kCompressionDictBlock, is_found);
|
||||
Status SeekToCompressionDictBlock(InternalIterator* meta_iter, bool* is_found,
|
||||
BlockHandle* block_handle) {
|
||||
return SeekToMetaBlock(meta_iter, kCompressionDictBlock, is_found, block_handle);
|
||||
}
|
||||
|
||||
Status SeekToRangeDelBlock(InternalIterator* meta_iter, bool* is_found,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user