mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-07 03:22:34 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
266ac245af | ||
|
|
64185c23ad | ||
|
|
e83d6a02e3 | ||
|
|
0980dc6c9a | ||
|
|
b767972313 | ||
|
|
c41744270a | ||
|
|
c21ea8f7a6 | ||
|
|
5444345588 | ||
|
|
2972a702a4 | ||
|
|
fbfa3e7a43 | ||
|
|
b01f426f56 | ||
|
|
503db684f7 | ||
|
|
3c840d1a6d | ||
|
|
92bfd6c507 | ||
|
|
7fdf735d5d | ||
|
|
7fbb9eccaf | ||
|
|
19cc66dc4f | ||
|
|
7eba54eb9b | ||
|
|
cd26af3476 | ||
|
|
a12479819d | ||
|
|
c10b391314 | ||
|
|
90177432e4 | ||
|
|
234f33a3f9 | ||
|
|
ccf7f833e3 | ||
|
|
1dfcdb15f9 | ||
|
|
39ef900551 | ||
|
|
78cb6b6112 | ||
|
|
867fe92e5e | ||
|
|
f004307e9b | ||
|
|
09ac6206ab | ||
|
|
5b68b114f1 | ||
|
|
4624ae52c9 | ||
|
|
8ace1f79b5 | ||
|
|
0d8e992b47 | ||
|
|
5358a80568 | ||
|
|
ed0a4c93ef | ||
|
|
1efc600ddf | ||
|
|
bddd5d3630 | ||
|
|
c1384a7076 | ||
|
|
29877ec7b4 | ||
|
|
8f2598ac9d | ||
|
|
9a44b4c32c | ||
|
|
ac8fb77afd | ||
|
|
23593171c4 | ||
|
|
af012c0f83 | ||
|
|
1c8dbe2aa2 | ||
|
|
eb6425303e | ||
|
|
132306fbf0 | ||
|
|
71598cdc75 | ||
|
|
3204a4f64b | ||
|
|
7aa96db7a2 | ||
|
|
dfa6c23c4b | ||
|
|
e367774d19 | ||
|
|
ad42d2fcbb | ||
|
|
279296f4d8 | ||
|
|
c5f0c6cc66 | ||
|
|
185ade4c0c | ||
|
|
5449c0990b | ||
|
|
a144a9782d |
No files matched your search
@@ -39,7 +39,6 @@ coverage/COVERAGE_REPORT
|
||||
.gdbhistory
|
||||
.gdb_history
|
||||
package/
|
||||
.phutil_module_cache
|
||||
unity.a
|
||||
tags
|
||||
etags
|
||||
|
||||
+203
-109
@@ -56,6 +56,7 @@ else()
|
||||
include_directories(${JEMALLOC_INCLUDE_DIR})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
option(WITH_SNAPPY "build with SNAPPY" OFF)
|
||||
if(WITH_SNAPPY)
|
||||
find_package(snappy REQUIRED)
|
||||
@@ -63,6 +64,38 @@ else()
|
||||
include_directories(${SNAPPY_INCLUDE_DIR})
|
||||
list(APPEND THIRDPARTY_LIBS ${SNAPPY_LIBRARIES})
|
||||
endif()
|
||||
|
||||
option(WITH_ZLIB "build with zlib" OFF)
|
||||
if(WITH_ZLIB)
|
||||
find_package(zlib REQUIRED)
|
||||
add_definitions(-DZLIB)
|
||||
include_directories(${ZLIB_INCLUDE_DIR})
|
||||
list(APPEND THIRDPARTY_LIBS ${ZLIB_LIBRARIES})
|
||||
endif()
|
||||
|
||||
option(WITH_BZ2 "build with bzip2" OFF)
|
||||
if(WITH_BZ2)
|
||||
find_package(bzip2 REQUIRED)
|
||||
add_definitions(-DBZIP2)
|
||||
include_directories(${BZIP2_INCLUDE_DIR})
|
||||
list(APPEND THIRDPARTY_LIBS ${BZIP2_LIBRARIES})
|
||||
endif()
|
||||
|
||||
option(WITH_LZ4 "build with lz4" OFF)
|
||||
if(WITH_LZ4)
|
||||
find_package(lz4 REQUIRED)
|
||||
add_definitions(-DLZ4)
|
||||
include_directories(${LZ4_INCLUDE_DIR})
|
||||
list(APPEND THIRDPARTY_LIBS ${LZ4_LIBRARIES})
|
||||
endif()
|
||||
|
||||
option(WITH_ZSTD "build with zstd" OFF)
|
||||
if(WITH_ZSTD)
|
||||
find_package(zstd REQUIRED)
|
||||
add_definitions(-DZSTD)
|
||||
include_directories(${ZSTD_INCLUDE_DIR})
|
||||
list(APPEND THIRDPARTY_LIBS ${ZSTD_LIBRARIES})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
@@ -115,6 +148,31 @@ if(WIN32 AND MSVC)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(BUILD_VERSION_CC ${CMAKE_BINARY_DIR}/build_version.cc)
|
||||
configure_file(util/build_version.cc.in ${BUILD_VERSION_CC} @ONLY)
|
||||
add_library(build_version OBJECT ${BUILD_VERSION_CC})
|
||||
target_include_directories(build_version PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/util)
|
||||
if(MSVC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi /nologo /EHsc /GS /Gd /GR /GF /fp:precise /Zc:wchar_t /Zc:forScope /errorReport:queue")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /FC /d2Zi+ /W3 /wd4127 /wd4800 /wd4996 /wd4351")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W -Wextra -Wall")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-compare -Wshadow -Wno-unused-parameter -Wno-unused-variable -Woverloaded-virtual -Wnon-virtual-dtor -Wno-missing-field-initializers")
|
||||
if(MINGW)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-format")
|
||||
endif()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
|
||||
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2 -fno-omit-frame-pointer")
|
||||
include(CheckCXXCompilerFlag)
|
||||
CHECK_CXX_COMPILER_FLAG("-momit-leaf-frame-pointer" HAVE_OMIT_LEAF_FRAME_POINTER)
|
||||
if(HAVE_OMIT_LEAF_FRAME_POINTER)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -momit-leaf-frame-pointer")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
option(PORTABLE "build a portable binary" OFF)
|
||||
option(FORCE_SSE42 "force building with SSE4.2, even when PORTABLE=ON" OFF)
|
||||
if(PORTABLE)
|
||||
@@ -158,31 +216,6 @@ if(HAVE_THREAD_LOCAL)
|
||||
add_definitions(-DROCKSDB_SUPPORT_THREAD_LOCAL)
|
||||
endif()
|
||||
|
||||
set(BUILD_VERSION_CC ${CMAKE_BINARY_DIR}/build_version.cc)
|
||||
configure_file(util/build_version.cc.in ${BUILD_VERSION_CC} @ONLY)
|
||||
add_library(build_version OBJECT ${BUILD_VERSION_CC})
|
||||
target_include_directories(build_version PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/util)
|
||||
if(MSVC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi /nologo /EHsc /GS /Gd /GR /GF /fp:precise /Zc:wchar_t /Zc:forScope /errorReport:queue")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /FC /d2Zi+ /W3 /wd4127 /wd4800 /wd4996 /wd4351")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W -Wextra -Wall")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-compare -Wshadow -Wno-unused-parameter -Wno-unused-variable -Woverloaded-virtual -Wnon-virtual-dtor -Wno-missing-field-initializers")
|
||||
if(MINGW)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-format")
|
||||
endif()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
|
||||
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2 -fno-omit-frame-pointer")
|
||||
include(CheckCXXCompilerFlag)
|
||||
CHECK_CXX_COMPILER_FLAG("-momit-leaf-frame-pointer" HAVE_OMIT_LEAF_FRAME_POINTER)
|
||||
if(HAVE_OMIT_LEAF_FRAME_POINTER)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -momit-leaf-frame-pointer")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
option(FAIL_ON_WARNINGS "Treat compile warnings as errors" ON)
|
||||
if(FAIL_ON_WARNINGS)
|
||||
if(MSVC)
|
||||
@@ -292,6 +325,7 @@ endif()
|
||||
option(WITH_FALLOCATE "build with fallocate" ON)
|
||||
|
||||
if(WITH_FALLOCATE)
|
||||
set(CMAKE_REQUIRED_FLAGS ${CMAKE_C_FLAGS})
|
||||
include(CheckCSourceCompiles)
|
||||
CHECK_C_SOURCE_COMPILES("
|
||||
#include <fcntl.h>
|
||||
@@ -601,7 +635,72 @@ else()
|
||||
message(STATUS "JNI library is disabled")
|
||||
endif()
|
||||
|
||||
set(TESTS
|
||||
# Installation and packaging
|
||||
if(WIN32)
|
||||
option(ROCKSDB_INSTALL_ON_WINDOWS "Enable install target on Windows" OFF)
|
||||
endif()
|
||||
if(NOT WIN32 OR ROCKSDB_INSTALL_ON_WINDOWS)
|
||||
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
# Change default installation prefix on Linux to /usr
|
||||
set(CMAKE_INSTALL_PREFIX /usr CACHE PATH "Install path prefix, prepended onto install directories." FORCE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
include(GNUInstallDirs)
|
||||
include(CMakePackageConfigHelpers)
|
||||
|
||||
set(package_config_destination ${CMAKE_INSTALL_LIBDIR}/cmake/rocksdb)
|
||||
|
||||
configure_package_config_file(
|
||||
${CMAKE_SOURCE_DIR}/cmake/RocksDBConfig.cmake.in RocksDBConfig.cmake
|
||||
INSTALL_DESTINATION ${package_config_destination}
|
||||
)
|
||||
|
||||
write_basic_package_version_file(
|
||||
RocksDBConfigVersion.cmake
|
||||
VERSION ${ROCKSDB_VERSION}
|
||||
COMPATIBILITY SameMajorVersion
|
||||
)
|
||||
|
||||
install(DIRECTORY include/rocksdb COMPONENT devel DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
|
||||
|
||||
install(
|
||||
TARGETS ${ROCKSDB_STATIC_LIB}
|
||||
EXPORT RocksDBTargets
|
||||
COMPONENT devel
|
||||
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
|
||||
INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
|
||||
)
|
||||
|
||||
install(
|
||||
TARGETS ${ROCKSDB_SHARED_LIB}
|
||||
EXPORT RocksDBTargets
|
||||
COMPONENT runtime
|
||||
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
|
||||
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
|
||||
INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
|
||||
)
|
||||
|
||||
install(
|
||||
EXPORT RocksDBTargets
|
||||
COMPONENT devel
|
||||
DESTINATION ${package_config_destination}
|
||||
NAMESPACE RocksDB::
|
||||
)
|
||||
|
||||
install(
|
||||
FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/RocksDBConfig.cmake
|
||||
${CMAKE_CURRENT_BINARY_DIR}/RocksDBConfigVersion.cmake
|
||||
COMPONENT devel
|
||||
DESTINATION ${package_config_destination}
|
||||
)
|
||||
endif()
|
||||
|
||||
option(WITH_TESTS "build with tests" ON)
|
||||
if(WITH_TESTS)
|
||||
set(TESTS
|
||||
cache/cache_test.cc
|
||||
cache/lru_cache_test.cc
|
||||
db/column_family_test.cc
|
||||
@@ -733,93 +832,88 @@ set(TESTS
|
||||
utilities/transactions/transaction_test.cc
|
||||
utilities/ttl/ttl_test.cc
|
||||
utilities/write_batch_with_index/write_batch_with_index_test.cc
|
||||
)
|
||||
if(WITH_LIBRADOS)
|
||||
list(APPEND TESTS utilities/env_librados_test.cc)
|
||||
endif()
|
||||
)
|
||||
if(WITH_LIBRADOS)
|
||||
list(APPEND TESTS utilities/env_librados_test.cc)
|
||||
endif()
|
||||
|
||||
set(BENCHMARKS
|
||||
cache/cache_bench.cc
|
||||
memtable/memtablerep_bench.cc
|
||||
tools/db_bench.cc
|
||||
table/table_reader_bench.cc
|
||||
utilities/column_aware_encoding_exp.cc
|
||||
utilities/persistent_cache/hash_table_bench.cc)
|
||||
add_library(testharness OBJECT util/testharness.cc)
|
||||
foreach(sourcefile ${BENCHMARKS})
|
||||
get_filename_component(exename ${sourcefile} NAME_WE)
|
||||
add_executable(${exename}${ARTIFACT_SUFFIX} ${sourcefile}
|
||||
$<TARGET_OBJECTS:testharness>)
|
||||
target_link_libraries(${exename}${ARTIFACT_SUFFIX} gtest ${LIBS})
|
||||
endforeach(sourcefile ${BENCHMARKS})
|
||||
|
||||
# For test util library that is build only in DEBUG mode
|
||||
# and linked to tests. Add test only code that is not #ifdefed for Release here.
|
||||
set(TESTUTIL_SOURCE
|
||||
db/db_test_util.cc
|
||||
monitoring/thread_status_updater_debug.cc
|
||||
table/mock_table.cc
|
||||
util/fault_injection_test_env.cc
|
||||
utilities/cassandra/test_utils.cc
|
||||
)
|
||||
# test utilities are only build in debug
|
||||
enable_testing()
|
||||
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND})
|
||||
set(TESTUTILLIB testutillib${ARTIFACT_SUFFIX})
|
||||
add_library(${TESTUTILLIB} STATIC ${TESTUTIL_SOURCE})
|
||||
if(MSVC)
|
||||
set_target_properties(${TESTUTILLIB} PROPERTIES COMPILE_FLAGS "/Fd${CMAKE_CFG_INTDIR}/testutillib${ARTIFACT_SUFFIX}.pdb")
|
||||
endif()
|
||||
set_target_properties(${TESTUTILLIB}
|
||||
PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD_RELEASE 1
|
||||
EXCLUDE_FROM_DEFAULT_BUILD_MINRELEASE 1
|
||||
EXCLUDE_FROM_DEFAULT_BUILD_RELWITHDEBINFO 1
|
||||
)
|
||||
|
||||
# Tests are excluded from Release builds
|
||||
set(TEST_EXES ${TESTS})
|
||||
|
||||
foreach(sourcefile ${TEST_EXES})
|
||||
set(BENCHMARKS
|
||||
cache/cache_bench.cc
|
||||
memtable/memtablerep_bench.cc
|
||||
tools/db_bench.cc
|
||||
table/table_reader_bench.cc
|
||||
utilities/column_aware_encoding_exp.cc
|
||||
utilities/persistent_cache/hash_table_bench.cc)
|
||||
add_library(testharness OBJECT util/testharness.cc)
|
||||
foreach(sourcefile ${BENCHMARKS})
|
||||
get_filename_component(exename ${sourcefile} NAME_WE)
|
||||
add_executable(${exename}${ARTIFACT_SUFFIX} ${sourcefile}
|
||||
$<TARGET_OBJECTS:testharness>)
|
||||
set_target_properties(${exename}${ARTIFACT_SUFFIX}
|
||||
PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD_RELEASE 1
|
||||
EXCLUDE_FROM_DEFAULT_BUILD_MINRELEASE 1
|
||||
EXCLUDE_FROM_DEFAULT_BUILD_RELWITHDEBINFO 1
|
||||
)
|
||||
target_link_libraries(${exename}${ARTIFACT_SUFFIX} testutillib${ARTIFACT_SUFFIX} gtest ${LIBS})
|
||||
if(NOT "${exename}" MATCHES "db_sanity_test")
|
||||
target_link_libraries(${exename}${ARTIFACT_SUFFIX} gtest ${LIBS})
|
||||
endforeach(sourcefile ${BENCHMARKS})
|
||||
|
||||
# For test util library that is build only in DEBUG mode
|
||||
# and linked to tests. Add test only code that is not #ifdefed for Release here.
|
||||
set(TESTUTIL_SOURCE
|
||||
db/db_test_util.cc
|
||||
monitoring/thread_status_updater_debug.cc
|
||||
table/mock_table.cc
|
||||
util/fault_injection_test_env.cc
|
||||
utilities/cassandra/test_utils.cc
|
||||
)
|
||||
# test utilities are only build in debug
|
||||
enable_testing()
|
||||
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND})
|
||||
set(TESTUTILLIB testutillib${ARTIFACT_SUFFIX})
|
||||
add_library(${TESTUTILLIB} STATIC ${TESTUTIL_SOURCE})
|
||||
if(MSVC)
|
||||
set_target_properties(${TESTUTILLIB} PROPERTIES COMPILE_FLAGS "/Fd${CMAKE_CFG_INTDIR}/testutillib${ARTIFACT_SUFFIX}.pdb")
|
||||
endif()
|
||||
set_target_properties(${TESTUTILLIB}
|
||||
PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD_RELEASE 1
|
||||
EXCLUDE_FROM_DEFAULT_BUILD_MINRELEASE 1
|
||||
EXCLUDE_FROM_DEFAULT_BUILD_RELWITHDEBINFO 1
|
||||
)
|
||||
|
||||
# Tests are excluded from Release builds
|
||||
set(TEST_EXES ${TESTS})
|
||||
|
||||
foreach(sourcefile ${TEST_EXES})
|
||||
get_filename_component(exename ${sourcefile} NAME_WE)
|
||||
add_executable(${exename}${ARTIFACT_SUFFIX} ${sourcefile}
|
||||
$<TARGET_OBJECTS:testharness>)
|
||||
set_target_properties(${exename}${ARTIFACT_SUFFIX}
|
||||
PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD_RELEASE 1
|
||||
EXCLUDE_FROM_DEFAULT_BUILD_MINRELEASE 1
|
||||
EXCLUDE_FROM_DEFAULT_BUILD_RELWITHDEBINFO 1
|
||||
)
|
||||
target_link_libraries(${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})
|
||||
endif()
|
||||
endforeach(sourcefile ${TEST_EXES})
|
||||
|
||||
# C executables must link to a shared object
|
||||
set(C_TESTS db/c_test.c)
|
||||
set(C_TEST_EXES ${C_TESTS})
|
||||
|
||||
foreach(sourcefile ${C_TEST_EXES})
|
||||
string(REPLACE ".c" "" exename ${sourcefile})
|
||||
string(REGEX REPLACE "^((.+)/)+" "" exename ${exename})
|
||||
add_executable(${exename}${ARTIFACT_SUFFIX} ${sourcefile})
|
||||
set_target_properties(${exename}${ARTIFACT_SUFFIX}
|
||||
PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD_RELEASE 1
|
||||
EXCLUDE_FROM_DEFAULT_BUILD_MINRELEASE 1
|
||||
EXCLUDE_FROM_DEFAULT_BUILD_RELWITHDEBINFO 1
|
||||
)
|
||||
target_link_libraries(${exename}${ARTIFACT_SUFFIX} ${ROCKSDB_IMPORT_LIB} testutillib${ARTIFACT_SUFFIX})
|
||||
add_test(NAME ${exename} COMMAND ${exename}${ARTIFACT_SUFFIX})
|
||||
add_dependencies(check ${exename}${ARTIFACT_SUFFIX})
|
||||
endif()
|
||||
endforeach(sourcefile ${TEST_EXES})
|
||||
|
||||
# C executables must link to a shared object
|
||||
set(C_TESTS db/c_test.c)
|
||||
set(C_TEST_EXES ${C_TESTS})
|
||||
|
||||
foreach(sourcefile ${C_TEST_EXES})
|
||||
string(REPLACE ".c" "" exename ${sourcefile})
|
||||
string(REGEX REPLACE "^((.+)/)+" "" exename ${exename})
|
||||
add_executable(${exename}${ARTIFACT_SUFFIX} ${sourcefile})
|
||||
set_target_properties(${exename}${ARTIFACT_SUFFIX}
|
||||
PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD_RELEASE 1
|
||||
EXCLUDE_FROM_DEFAULT_BUILD_MINRELEASE 1
|
||||
EXCLUDE_FROM_DEFAULT_BUILD_RELWITHDEBINFO 1
|
||||
)
|
||||
target_link_libraries(${exename}${ARTIFACT_SUFFIX} ${ROCKSDB_IMPORT_LIB} testutillib${ARTIFACT_SUFFIX})
|
||||
add_test(NAME ${exename} COMMAND ${exename}${ARTIFACT_SUFFIX})
|
||||
add_dependencies(check ${exename}${ARTIFACT_SUFFIX})
|
||||
endforeach(sourcefile ${C_TEST_EXES})
|
||||
add_subdirectory(tools)
|
||||
|
||||
# Installation and packaging for Linux
|
||||
if(NOT WIN32)
|
||||
install(TARGETS ${ROCKSDB_STATIC_LIB} COMPONENT devel ARCHIVE DESTINATION lib64)
|
||||
install(TARGETS ${ROCKSDB_SHARED_LIB} COMPONENT runtime DESTINATION lib64)
|
||||
install(DIRECTORY "${PROJECT_SOURCE_DIR}/include/rocksdb/"
|
||||
COMPONENT devel
|
||||
DESTINATION include/rocksdb)
|
||||
set(CMAKE_INSTALL_PREFIX /usr)
|
||||
endforeach(sourcefile ${C_TEST_EXES})
|
||||
endif()
|
||||
|
||||
option(WITH_TOOLS "build with tools" ON)
|
||||
if(WITH_TOOLS)
|
||||
add_subdirectory(tools)
|
||||
endif()
|
||||
+15
@@ -1,13 +1,28 @@
|
||||
# Rocksdb Change Log
|
||||
## Unreleased
|
||||
### Public API Change
|
||||
### New Features
|
||||
### Bug Fixes
|
||||
|
||||
## 5.8.0 (08/30/2017)
|
||||
### Public API Change
|
||||
* Users of `Statistics::getHistogramString()` will see fewer histogram buckets and different bucket endpoints.
|
||||
* `Slice::compare` and BytewiseComparator `Compare` no longer accept `Slice`s containing nullptr.
|
||||
* `Transaction::Get` and `Transaction::GetForUpdate` variants with `PinnableSlice` added.
|
||||
|
||||
### New Features
|
||||
* Add Iterator::Refresh(), which allows users to update the iterator state so that they can avoid some initialization costs of recreating iterators.
|
||||
* Replace dynamic_cast<> (except unit test) so people can choose to build with RTTI off. With make, release mode is by default built with -fno-rtti and debug mode is built without it. Users can override it by setting USE_RTTI=0 or 1.
|
||||
* Universal compactions including the bottom level can be executed in a dedicated thread pool. This alleviates head-of-line blocking in the compaction queue, which cause write stalling, particularly in multi-instance use cases. Users can enable this feature via `Env::SetBackgroundThreads(N, Env::Priority::BOTTOM)`, where `N > 0`.
|
||||
* Allow merge operator to be called even with a single merge operand during compactions, by appropriately overriding `MergeOperator::AllowSingleOperand`.
|
||||
* Add `DB::VerifyChecksum()`, which verifies the checksums in all SST files in a running DB.
|
||||
* Block-based table support for disabling checksums by setting `BlockBasedTableOptions::checksum = kNoChecksum`.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix wrong latencies in `rocksdb.db.get.micros`, `rocksdb.db.write.micros`, and `rocksdb.sst.read.micros`.
|
||||
* Fix incorrect dropping of deletions during intra-L0 compaction.
|
||||
* Fix transient reappearance of keys covered by range deletions when memtable prefix bloom filter is enabled.
|
||||
* Fix potentially wrong file smallest key when range deletions separated by snapshot are written together.
|
||||
|
||||
## 5.7.0 (07/13/2017)
|
||||
### Public API Change
|
||||
|
||||
@@ -9,5 +9,8 @@ This is the list of all known third-party language bindings for RocksDB. If some
|
||||
* Haskell - https://hackage.haskell.org/package/rocksdb-haskell
|
||||
* PHP - https://github.com/Photonios/rocksdb-php
|
||||
* C# - https://github.com/warrenfalk/rocksdb-sharp
|
||||
* Rust - https://github.com/spacejam/rust-rocksdb
|
||||
* Rust
|
||||
* https://github.com/spacejam/rust-rocksdb
|
||||
* https://github.com/bh1xuw/rust-rocks
|
||||
* D programming language - https://github.com/b1naryth1ef/rocksdb
|
||||
* Erlang - https://gitlab.com/barrel-db/erlang-rocksdb
|
||||
@@ -259,19 +259,6 @@ default: all
|
||||
WARNING_FLAGS = -W -Wextra -Wall -Wsign-compare -Wshadow \
|
||||
-Wno-unused-parameter
|
||||
|
||||
CCVERSION = $(shell $(CXX) -dumpversion)
|
||||
CCNAME = $(shell $(CXX) --version | awk 'NR==1' | cut -f1 -d " ")
|
||||
|
||||
ifeq ($(CCNAME), clang)
|
||||
ifeq ($(CCVERSION), 4*)
|
||||
CXXFLAGS += -faligned-new
|
||||
endif
|
||||
else
|
||||
ifeq ($(CCVERSION), 7)
|
||||
CXXFLAGS += -faligned-new
|
||||
endif
|
||||
endif
|
||||
|
||||
ifndef DISABLE_WARNING_AS_ERROR
|
||||
WARNING_FLAGS += -Werror
|
||||
endif
|
||||
|
||||
@@ -14,7 +14,6 @@ rocksdb_compiler_flags = [
|
||||
"-DROCKSDB_RANGESYNC_PRESENT",
|
||||
"-DROCKSDB_SCHED_GETCPU_PRESENT",
|
||||
"-DROCKSDB_SUPPORT_THREAD_LOCAL",
|
||||
"-DHAVE_SSE42",
|
||||
"-DOS_LINUX",
|
||||
"-DROCKSDB_UBSAN_RUN",
|
||||
# Flags to enable libs we include
|
||||
@@ -48,6 +47,10 @@ rocksdb_preprocessor_flags = [
|
||||
"-I" + REPO_PATH,
|
||||
]
|
||||
|
||||
rocksdb_arch_preprocessor_flags = {
|
||||
"x86_64": ["-DHAVE_SSE42"],
|
||||
}
|
||||
|
||||
cpp_library(
|
||||
name = "rocksdb_lib",
|
||||
headers = AutoHeaders.RECURSIVE_GLOB,
|
||||
@@ -263,6 +266,7 @@ cpp_library(
|
||||
],
|
||||
deps = [],
|
||||
preprocessor_flags = rocksdb_preprocessor_flags,
|
||||
arch_preprocessor_flags = rocksdb_arch_preprocessor_flags,
|
||||
compiler_flags = rocksdb_compiler_flags,
|
||||
external_deps = rocksdb_external_deps,
|
||||
)
|
||||
@@ -283,6 +287,7 @@ cpp_library(
|
||||
],
|
||||
deps = [":rocksdb_lib"],
|
||||
preprocessor_flags = rocksdb_preprocessor_flags,
|
||||
arch_preprocessor_flags = rocksdb_arch_preprocessor_flags,
|
||||
compiler_flags = rocksdb_compiler_flags,
|
||||
external_deps = rocksdb_external_deps,
|
||||
)
|
||||
@@ -296,6 +301,7 @@ cpp_library(
|
||||
],
|
||||
deps = [":rocksdb_lib"],
|
||||
preprocessor_flags = rocksdb_preprocessor_flags,
|
||||
arch_preprocessor_flags = rocksdb_arch_preprocessor_flags,
|
||||
compiler_flags = rocksdb_compiler_flags,
|
||||
external_deps = rocksdb_external_deps,
|
||||
)
|
||||
@@ -306,6 +312,7 @@ cpp_library(
|
||||
srcs = ["env/env_basic_test.cc"],
|
||||
deps = [":rocksdb_test_lib"],
|
||||
preprocessor_flags = rocksdb_preprocessor_flags,
|
||||
arch_preprocessor_flags = rocksdb_arch_preprocessor_flags,
|
||||
compiler_flags = rocksdb_compiler_flags,
|
||||
external_deps = rocksdb_external_deps,
|
||||
)
|
||||
@@ -501,6 +508,7 @@ for test_cfg in ROCKS_TESTS:
|
||||
srcs = [test_cc],
|
||||
deps = [":rocksdb_test_lib"],
|
||||
preprocessor_flags = rocksdb_preprocessor_flags,
|
||||
arch_preprocessor_flags = rocksdb_arch_preprocessor_flags,
|
||||
compiler_flags = rocksdb_compiler_flags,
|
||||
external_deps = rocksdb_external_deps,
|
||||
)
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
arcanist_util are only used internaly, If you want to change it please check
|
||||
<internal_rocksdb_repo>/arcanist_util
|
||||
@@ -6,7 +6,7 @@ rocksdb_target_header = """
|
||||
import os
|
||||
|
||||
TARGETS_PATH = os.path.dirname(__file__)
|
||||
REPO_PATH = TARGETS_PATH[(TARGETS_PATH.find('fbcode/') + len('fbcode/')):] + "/"
|
||||
REPO_PATH = "rocksdb/src/"
|
||||
BUCK_BINS = "buck-out/gen/" + REPO_PATH
|
||||
TEST_RUNNER = REPO_PATH + "buckifier/rocks_test_runner.sh"
|
||||
rocksdb_compiler_flags = [
|
||||
@@ -18,9 +18,7 @@ rocksdb_compiler_flags = [
|
||||
"-DROCKSDB_RANGESYNC_PRESENT",
|
||||
"-DROCKSDB_SCHED_GETCPU_PRESENT",
|
||||
"-DROCKSDB_SUPPORT_THREAD_LOCAL",
|
||||
"-DHAVE_SSE42",
|
||||
"-DOS_LINUX",
|
||||
"-DROCKSDB_UBSAN_RUN",
|
||||
# Flags to enable libs we include
|
||||
"-DSNAPPY",
|
||||
"-DZLIB",
|
||||
@@ -51,6 +49,10 @@ rocksdb_preprocessor_flags = [
|
||||
"-I" + REPO_PATH + "include/",
|
||||
"-I" + REPO_PATH,
|
||||
]
|
||||
|
||||
rocksdb_arch_preprocessor_flags = {
|
||||
"x86_64": ["-DHAVE_SSE42"],
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
@@ -61,6 +63,7 @@ cpp_library(
|
||||
srcs = [%s],
|
||||
deps = [%s],
|
||||
preprocessor_flags = rocksdb_preprocessor_flags,
|
||||
arch_preprocessor_flags = rocksdb_arch_preprocessor_flags,
|
||||
compiler_flags = rocksdb_compiler_flags,
|
||||
external_deps = rocksdb_external_deps,
|
||||
)
|
||||
@@ -72,6 +75,7 @@ cpp_binary(
|
||||
srcs = [%s],
|
||||
deps = [%s],
|
||||
preprocessor_flags = rocksdb_preprocessor_flags,
|
||||
arch_preprocessor_flags = rocksdb_arch_preprocessor_flags,
|
||||
compiler_flags = rocksdb_compiler_flags,
|
||||
external_deps = rocksdb_external_deps,
|
||||
)
|
||||
@@ -94,6 +98,7 @@ for test_cfg in ROCKS_TESTS:
|
||||
srcs = [test_cc],
|
||||
deps = [":rocksdb_test_lib"],
|
||||
preprocessor_flags = rocksdb_preprocessor_flags,
|
||||
arch_preprocessor_flags = rocksdb_arch_preprocessor_flags,
|
||||
compiler_flags = rocksdb_compiler_flags,
|
||||
external_deps = rocksdb_external_deps,
|
||||
)
|
||||
|
||||
@@ -21,11 +21,17 @@ function postURL($diffID, $url) {
|
||||
assert(is_numeric($diffID));
|
||||
assert(strlen($url) > 0);
|
||||
|
||||
$cmd = 'echo \'{"diff_id": ' . $diffID . ', '
|
||||
. '"name":"click here for sandcastle tests for D' . $diffID . '", '
|
||||
. '"link":"' . $url . '"}\' | '
|
||||
. 'arc call-conduit '
|
||||
. 'differential.updateunitresults';
|
||||
$cmd_args = array(
|
||||
'diff_id' => (int)$diffID,
|
||||
'name' => sprintf(
|
||||
'click here for sandcastle tests for D%d',
|
||||
(int)$diffID
|
||||
),
|
||||
'link' => $url
|
||||
);
|
||||
$cmd = 'echo ' . escapeshellarg(json_encode($cmd_args))
|
||||
. ' | arc call-conduit differential.updateunitresults';
|
||||
|
||||
shell_exec($cmd);
|
||||
}
|
||||
|
||||
@@ -35,11 +41,15 @@ function buildUpdateTestStatusCmd($diffID, $test, $status) {
|
||||
assert(strlen($test) > 0);
|
||||
assert(strlen($status) > 0);
|
||||
|
||||
$cmd = 'echo \'{"diff_id": ' . $diffID . ', '
|
||||
. '"name":"' . $test . '", '
|
||||
. '"result":"' . $status . '"}\' | '
|
||||
. 'arc call-conduit '
|
||||
. 'differential.updateunitresults';
|
||||
$cmd_args = array(
|
||||
'diff_id' => (int)$diffID,
|
||||
'name' => $test,
|
||||
'result' => $status
|
||||
);
|
||||
|
||||
$cmd = 'echo ' . escapeshellarg(json_encode($cmd_args))
|
||||
. ' | arc call-conduit differential.updateunitresults';
|
||||
|
||||
return $cmd;
|
||||
}
|
||||
|
||||
@@ -68,7 +78,7 @@ function getSteps($applyDiff, $diffID, $username, $test) {
|
||||
// and authenticate using that in Sandcastle.
|
||||
$setup = array(
|
||||
"name" => "Setup arcrc",
|
||||
"shell" => "echo " . $arcrc_content . " | base64 --decode"
|
||||
"shell" => "echo " . escapeshellarg($arcrc_content) . " | base64 --decode"
|
||||
. " | gzip -d > ~/.arcrc",
|
||||
"user" => "root"
|
||||
);
|
||||
@@ -114,7 +124,7 @@ function getSteps($applyDiff, $diffID, $username, $test) {
|
||||
$patch = array(
|
||||
"name" => "Patch " . $diffID,
|
||||
"shell" => "arc --arcrc-file ~/.arcrc "
|
||||
. "patch --nocommit --diff " . $diffID,
|
||||
. "patch --nocommit --diff " . escapeshellarg($diffID),
|
||||
"user" => "root"
|
||||
);
|
||||
|
||||
@@ -125,8 +135,8 @@ function getSteps($applyDiff, $diffID, $username, $test) {
|
||||
}
|
||||
|
||||
// Run the actual command.
|
||||
$cmd = $cmd . "J=$(nproc) ./build_tools/precommit_checker.py " . $test
|
||||
. "; exit_code=$?; ";
|
||||
$cmd = $cmd . "J=$(nproc) ./build_tools/precommit_checker.py " .
|
||||
escapeshellarg($test) . "; exit_code=$?; ";
|
||||
|
||||
if ($applyDiff) {
|
||||
$cmd = $cmd . "([[ \$exit_code -eq 0 ]] &&"
|
||||
@@ -159,7 +169,7 @@ function getSteps($applyDiff, $diffID, $username, $test) {
|
||||
"name" => "Run " . $test,
|
||||
"shell" => $cmd,
|
||||
"user" => "root",
|
||||
"parser" => "python build_tools/error_filter.py " . $test,
|
||||
"parser" => "python build_tools/error_filter.py " . escapeshellarg($test),
|
||||
);
|
||||
|
||||
$steps[] = $run_test;
|
||||
@@ -207,7 +217,7 @@ function getSandcastleConfig() {
|
||||
if (file_exists(PRIMARY_TOKEN_FILE)) {
|
||||
$cmd = 'cat ' . PRIMARY_TOKEN_FILE;
|
||||
} else {
|
||||
$cmd = 'cat ' . $cwd_token_file;
|
||||
$cmd = 'cat ' . escapeshellarg($cwd_token_file);
|
||||
}
|
||||
|
||||
assert(strlen($cmd) > 0);
|
||||
@@ -331,9 +341,11 @@ function getSandcastleConfig() {
|
||||
$app = $sandcastle_config[0];
|
||||
$token = $sandcastle_config[1];
|
||||
|
||||
$cmd = 'curl -s -k -F app=' . $app . ' '
|
||||
. '-F token=' . $token . ' -F job=\'' . json_encode($job)
|
||||
.'\' "' . $url . '"';
|
||||
$cmd = 'curl -s -k '
|
||||
. ' -F app=' . escapeshellarg($app)
|
||||
. ' -F token=' . escapeshellarg($token)
|
||||
. ' -F job=' . escapeshellarg(json_encode($job))
|
||||
.' ' . escapeshellarg($url);
|
||||
|
||||
$output = shell_exec($cmd);
|
||||
assert(strlen($output) > 0);
|
||||
|
||||
@@ -458,7 +458,7 @@ elif test -z "$PORTABLE"; then
|
||||
fi
|
||||
fi
|
||||
|
||||
$CXX $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <cstdint>
|
||||
#include <nmmintrin.h>
|
||||
int main() {
|
||||
|
||||
Vendored
+16
-3
@@ -234,11 +234,19 @@ void LRUCacheShard::EvictFromLRU(size_t charge,
|
||||
}
|
||||
|
||||
void* LRUCacheShard::operator new(size_t size) {
|
||||
return rocksdb::port::cacheline_aligned_alloc(size);
|
||||
return port::cacheline_aligned_alloc(size);
|
||||
}
|
||||
|
||||
void* LRUCacheShard::operator new[](size_t size) {
|
||||
return port::cacheline_aligned_alloc(size);
|
||||
}
|
||||
|
||||
void LRUCacheShard::operator delete(void *memblock) {
|
||||
rocksdb::port::cacheline_aligned_free(memblock);
|
||||
port::cacheline_aligned_free(memblock);
|
||||
}
|
||||
|
||||
void LRUCacheShard::operator delete[](void* memblock) {
|
||||
port::cacheline_aligned_free(memblock);
|
||||
}
|
||||
|
||||
void LRUCacheShard::SetCapacity(size_t capacity) {
|
||||
@@ -494,7 +502,12 @@ uint32_t LRUCache::GetHash(Handle* handle) const {
|
||||
return reinterpret_cast<const LRUHandle*>(handle)->hash;
|
||||
}
|
||||
|
||||
void LRUCache::DisownData() { shards_ = nullptr; }
|
||||
void LRUCache::DisownData() {
|
||||
// Do not drop data if compile with ASAN to suppress leak warning.
|
||||
#ifndef __SANITIZE_ADDRESS__
|
||||
shards_ = nullptr;
|
||||
#endif // !__SANITIZE_ADDRESS__
|
||||
}
|
||||
|
||||
size_t LRUCache::TEST_GetLRUSize() {
|
||||
size_t lru_size_of_all_shards = 0;
|
||||
|
||||
Vendored
+4
@@ -205,8 +205,12 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard : public CacheShard {
|
||||
// Overloading to aligned it to cache line size
|
||||
void* operator new(size_t);
|
||||
|
||||
void* operator new[](size_t);
|
||||
|
||||
void operator delete(void *);
|
||||
|
||||
void operator delete[](void*);
|
||||
|
||||
private:
|
||||
void LRU_Remove(LRUHandle* e);
|
||||
void LRU_Insert(LRUHandle* e);
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
@PACKAGE_INIT@
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/RocksDBTargets.cmake")
|
||||
check_required_components(RocksDB)
|
||||
@@ -0,0 +1,21 @@
|
||||
# - Find Bzip2
|
||||
# Find the bzip2 compression library and includes
|
||||
#
|
||||
# BZIP2_INCLUDE_DIR - where to find bzlib.h, etc.
|
||||
# BZIP2_LIBRARIES - List of libraries when using bzip2.
|
||||
# BZIP2_FOUND - True if bzip2 found.
|
||||
|
||||
find_path(BZIP2_INCLUDE_DIR
|
||||
NAMES bzlib.h
|
||||
HINTS ${BZIP2_ROOT_DIR}/include)
|
||||
|
||||
find_library(BZIP2_LIBRARIES
|
||||
NAMES bz2
|
||||
HINTS ${BZIP2_ROOT_DIR}/lib)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(bzip2 DEFAULT_MSG BZIP2_LIBRARIES BZIP2_INCLUDE_DIR)
|
||||
|
||||
mark_as_advanced(
|
||||
BZIP2_LIBRARIES
|
||||
BZIP2_INCLUDE_DIR)
|
||||
@@ -0,0 +1,21 @@
|
||||
# - Find Lz4
|
||||
# Find the lz4 compression library and includes
|
||||
#
|
||||
# LZ4_INCLUDE_DIR - where to find lz4.h, etc.
|
||||
# LZ4_LIBRARIES - List of libraries when using lz4.
|
||||
# LZ4_FOUND - True if lz4 found.
|
||||
|
||||
find_path(LZ4_INCLUDE_DIR
|
||||
NAMES lz4.h
|
||||
HINTS ${LZ4_ROOT_DIR}/include)
|
||||
|
||||
find_library(LZ4_LIBRARIES
|
||||
NAMES lz4
|
||||
HINTS ${LZ4_ROOT_DIR}/lib)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(lz4 DEFAULT_MSG LZ4_LIBRARIES LZ4_INCLUDE_DIR)
|
||||
|
||||
mark_as_advanced(
|
||||
LZ4_LIBRARIES
|
||||
LZ4_INCLUDE_DIR)
|
||||
@@ -0,0 +1,21 @@
|
||||
# - Find zlib
|
||||
# Find the zlib compression library and includes
|
||||
#
|
||||
# ZLIB_INCLUDE_DIR - where to find zlib.h, etc.
|
||||
# ZLIB_LIBRARIES - List of libraries when using zlib.
|
||||
# ZLIB_FOUND - True if zlib found.
|
||||
|
||||
find_path(ZLIB_INCLUDE_DIR
|
||||
NAMES zlib.h
|
||||
HINTS ${ZLIB_ROOT_DIR}/include)
|
||||
|
||||
find_library(ZLIB_LIBRARIES
|
||||
NAMES z
|
||||
HINTS ${ZLIB_ROOT_DIR}/lib)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(zlib DEFAULT_MSG ZLIB_LIBRARIES ZLIB_INCLUDE_DIR)
|
||||
|
||||
mark_as_advanced(
|
||||
ZLIB_LIBRARIES
|
||||
ZLIB_INCLUDE_DIR)
|
||||
@@ -0,0 +1,21 @@
|
||||
# - Find zstd
|
||||
# Find the zstd compression library and includes
|
||||
#
|
||||
# ZSTD_INCLUDE_DIR - where to find zstd.h, etc.
|
||||
# ZSTD_LIBRARIES - List of libraries when using zstd.
|
||||
# ZSTD_FOUND - True if zstd found.
|
||||
|
||||
find_path(ZSTD_INCLUDE_DIR
|
||||
NAMES zstd.h
|
||||
HINTS ${ZSTD_ROOT_DIR}/include)
|
||||
|
||||
find_library(ZSTD_LIBRARIES
|
||||
NAMES zstd
|
||||
HINTS ${ZSTD_ROOT_DIR}/lib)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(zstd DEFAULT_MSG ZSTD_LIBRARIES ZSTD_INCLUDE_DIR)
|
||||
|
||||
mark_as_advanced(
|
||||
ZSTD_LIBRARIES
|
||||
ZSTD_INCLUDE_DIR)
|
||||
@@ -36,6 +36,7 @@
|
||||
#include "utilities/merge_operators.h"
|
||||
#include "rocksdb/utilities/transaction.h"
|
||||
#include "rocksdb/utilities/transaction_db.h"
|
||||
#include "rocksdb/utilities/optimistic_transaction_db.h"
|
||||
#include "rocksdb/utilities/checkpoint.h"
|
||||
|
||||
using rocksdb::BytewiseComparator;
|
||||
@@ -95,6 +96,8 @@ using rocksdb::PinnableSlice;
|
||||
using rocksdb::TransactionDBOptions;
|
||||
using rocksdb::TransactionDB;
|
||||
using rocksdb::TransactionOptions;
|
||||
using rocksdb::OptimisticTransactionDB;
|
||||
using rocksdb::OptimisticTransactionOptions;
|
||||
using rocksdb::Transaction;
|
||||
using rocksdb::Checkpoint;
|
||||
|
||||
@@ -153,6 +156,12 @@ struct rocksdb_transaction_t {
|
||||
struct rocksdb_checkpoint_t {
|
||||
Checkpoint* rep;
|
||||
};
|
||||
struct rocksdb_optimistictransactiondb_t {
|
||||
OptimisticTransactionDB* rep;
|
||||
};
|
||||
struct rocksdb_optimistictransaction_options_t {
|
||||
OptimisticTransactionOptions rep;
|
||||
};
|
||||
|
||||
struct rocksdb_compactionfiltercontext_t {
|
||||
CompactionFilter::Context rep;
|
||||
@@ -3253,6 +3262,21 @@ void rocksdb_transaction_options_set_max_write_batch_size(
|
||||
opt->rep.max_write_batch_size = size;
|
||||
}
|
||||
|
||||
rocksdb_optimistictransaction_options_t*
|
||||
rocksdb_optimistictransaction_options_create() {
|
||||
return new rocksdb_optimistictransaction_options_t;
|
||||
}
|
||||
|
||||
void rocksdb_optimistictransaction_options_destroy(
|
||||
rocksdb_optimistictransaction_options_t* opt) {
|
||||
delete opt;
|
||||
}
|
||||
|
||||
void rocksdb_optimistictransaction_options_set_set_snapshot(
|
||||
rocksdb_optimistictransaction_options_t* opt, unsigned char v) {
|
||||
opt->rep.set_snapshot = v;
|
||||
}
|
||||
|
||||
rocksdb_column_family_handle_t* rocksdb_transactiondb_create_column_family(
|
||||
rocksdb_transactiondb_t* txn_db,
|
||||
const rocksdb_options_t* column_family_options,
|
||||
@@ -3320,7 +3344,14 @@ void rocksdb_transaction_destroy(rocksdb_transaction_t* txn) {
|
||||
delete txn;
|
||||
}
|
||||
|
||||
//Read a key inside a transaction
|
||||
const rocksdb_snapshot_t* rocksdb_transaction_get_snapshot(
|
||||
rocksdb_transaction_t* txn) {
|
||||
rocksdb_snapshot_t* result = new rocksdb_snapshot_t;
|
||||
result->rep = txn->rep->GetSnapshot();
|
||||
return result;
|
||||
}
|
||||
|
||||
// Read a key inside a transaction
|
||||
char* rocksdb_transaction_get(rocksdb_transaction_t* txn,
|
||||
const rocksdb_readoptions_t* options,
|
||||
const char* key, size_t klen, size_t* vlen,
|
||||
@@ -3361,6 +3392,28 @@ char* rocksdb_transaction_get_cf(rocksdb_transaction_t* txn,
|
||||
return result;
|
||||
}
|
||||
|
||||
// Read a key inside a transaction
|
||||
char* rocksdb_transaction_get_for_update(rocksdb_transaction_t* txn,
|
||||
const rocksdb_readoptions_t* options,
|
||||
const char* key, size_t klen,
|
||||
size_t* vlen, unsigned char exclusive,
|
||||
char** errptr) {
|
||||
char* result = nullptr;
|
||||
std::string tmp;
|
||||
Status s =
|
||||
txn->rep->GetForUpdate(options->rep, Slice(key, klen), &tmp, exclusive);
|
||||
if (s.ok()) {
|
||||
*vlen = tmp.size();
|
||||
result = CopyString(tmp);
|
||||
} else {
|
||||
*vlen = 0;
|
||||
if (!s.IsNotFound()) {
|
||||
SaveError(errptr, s);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Read a key outside a transaction
|
||||
char* rocksdb_transactiondb_get(
|
||||
rocksdb_transactiondb_t* txn_db,
|
||||
@@ -3418,13 +3471,13 @@ void rocksdb_transaction_put_cf(rocksdb_transaction_t* txn,
|
||||
Slice(val, vlen)));
|
||||
}
|
||||
|
||||
//Put a key outside a transaction
|
||||
// Put a key outside a transaction
|
||||
void rocksdb_transactiondb_put(rocksdb_transactiondb_t* txn_db,
|
||||
const rocksdb_writeoptions_t* options,
|
||||
const char* key, size_t klen, const char* val,
|
||||
size_t vlen, char** errptr) {
|
||||
SaveError(errptr,
|
||||
txn_db->rep->Put(options->rep, Slice(key, klen), Slice(val, vlen)));
|
||||
SaveError(errptr, txn_db->rep->Put(options->rep, Slice(key, klen),
|
||||
Slice(val, vlen)));
|
||||
}
|
||||
|
||||
void rocksdb_transactiondb_put_cf(rocksdb_transactiondb_t* txn_db,
|
||||
@@ -3437,7 +3490,7 @@ void rocksdb_transactiondb_put_cf(rocksdb_transactiondb_t* txn_db,
|
||||
Slice(key, keylen), Slice(val, vallen)));
|
||||
}
|
||||
|
||||
//Write batch into transaction db
|
||||
// Write batch into transaction db
|
||||
void rocksdb_transactiondb_write(
|
||||
rocksdb_transactiondb_t* db,
|
||||
const rocksdb_writeoptions_t* options,
|
||||
@@ -3446,6 +3499,22 @@ void rocksdb_transactiondb_write(
|
||||
SaveError(errptr, db->rep->Write(options->rep, &batch->rep));
|
||||
}
|
||||
|
||||
// Merge a key inside a transaction
|
||||
void rocksdb_transaction_merge(rocksdb_transaction_t* txn, const char* key,
|
||||
size_t klen, const char* val, size_t vlen,
|
||||
char** errptr) {
|
||||
SaveError(errptr, txn->rep->Merge(Slice(key, klen), Slice(val, vlen)));
|
||||
}
|
||||
|
||||
// Merge a key outside a transaction
|
||||
void rocksdb_transactiondb_merge(rocksdb_transactiondb_t* txn_db,
|
||||
const rocksdb_writeoptions_t* options,
|
||||
const char* key, size_t klen, const char* val,
|
||||
size_t vlen, char** errptr) {
|
||||
SaveError(errptr,
|
||||
txn_db->rep->Merge(options->rep, Slice(key, klen), Slice(val, vlen)));
|
||||
}
|
||||
|
||||
// Delete a key inside a transaction
|
||||
void rocksdb_transaction_delete(rocksdb_transaction_t* txn, const char* key,
|
||||
size_t klen, char** errptr) {
|
||||
@@ -3481,6 +3550,14 @@ rocksdb_iterator_t* rocksdb_transaction_create_iterator(
|
||||
return result;
|
||||
}
|
||||
|
||||
// Create an iterator outside a transaction
|
||||
rocksdb_iterator_t* rocksdb_transactiondb_create_iterator(
|
||||
rocksdb_transactiondb_t* txn_db, const rocksdb_readoptions_t* options) {
|
||||
rocksdb_iterator_t* result = new rocksdb_iterator_t;
|
||||
result->rep = txn_db->rep->NewIterator(options->rep);
|
||||
return result;
|
||||
}
|
||||
|
||||
void rocksdb_transactiondb_close(rocksdb_transactiondb_t* txn_db) {
|
||||
delete txn_db->rep;
|
||||
delete txn_db;
|
||||
@@ -3497,6 +3574,42 @@ rocksdb_checkpoint_t* rocksdb_transactiondb_checkpoint_object_create(
|
||||
return result;
|
||||
}
|
||||
|
||||
rocksdb_optimistictransactiondb_t* rocksdb_optimistictransactiondb_open(
|
||||
const rocksdb_options_t* options, const char* name,
|
||||
char** errptr) {
|
||||
OptimisticTransactionDB* otxn_db;
|
||||
if (SaveError(errptr, OptimisticTransactionDB::Open(
|
||||
options->rep, std::string(name), &otxn_db))) {
|
||||
return nullptr;
|
||||
}
|
||||
rocksdb_optimistictransactiondb_t* result =
|
||||
new rocksdb_optimistictransactiondb_t;
|
||||
result->rep = otxn_db;
|
||||
return result;
|
||||
}
|
||||
|
||||
rocksdb_transaction_t* rocksdb_optimistictransaction_begin(
|
||||
rocksdb_optimistictransactiondb_t* otxn_db,
|
||||
const rocksdb_writeoptions_t* write_options,
|
||||
const rocksdb_optimistictransaction_options_t* otxn_options,
|
||||
rocksdb_transaction_t* old_txn) {
|
||||
if (old_txn == nullptr) {
|
||||
rocksdb_transaction_t* result = new rocksdb_transaction_t;
|
||||
result->rep = otxn_db->rep->BeginTransaction(write_options->rep,
|
||||
otxn_options->rep, nullptr);
|
||||
return result;
|
||||
}
|
||||
old_txn->rep = otxn_db->rep->BeginTransaction(
|
||||
write_options->rep, otxn_options->rep, old_txn->rep);
|
||||
return old_txn;
|
||||
}
|
||||
|
||||
void rocksdb_optimistictransactiondb_close(
|
||||
rocksdb_optimistictransactiondb_t* otxn_db) {
|
||||
delete otxn_db->rep;
|
||||
delete otxn_db;
|
||||
}
|
||||
|
||||
void rocksdb_free(void* ptr) { free(ptr); }
|
||||
|
||||
rocksdb_pinnableslice_t* rocksdb_get_pinned(
|
||||
|
||||
+22
-22
@@ -284,32 +284,32 @@ bool Compaction::KeyNotExistsBeyondOutputLevel(
|
||||
assert(input_version_ != nullptr);
|
||||
assert(level_ptrs != nullptr);
|
||||
assert(level_ptrs->size() == static_cast<size_t>(number_levels_));
|
||||
assert(cfd_->ioptions()->compaction_style != kCompactionStyleFIFO);
|
||||
if (cfd_->ioptions()->compaction_style == kCompactionStyleUniversal) {
|
||||
return bottommost_level_;
|
||||
}
|
||||
if (cfd_->ioptions()->compaction_style == kCompactionStyleLevel &&
|
||||
output_level_ == 0) {
|
||||
return false;
|
||||
}
|
||||
// Maybe use binary search to find right entry instead of linear search?
|
||||
const Comparator* user_cmp = cfd_->user_comparator();
|
||||
for (int lvl = output_level_ + 1; lvl < number_levels_; lvl++) {
|
||||
const std::vector<FileMetaData*>& files = input_vstorage_->LevelFiles(lvl);
|
||||
for (; level_ptrs->at(lvl) < files.size(); level_ptrs->at(lvl)++) {
|
||||
auto* f = files[level_ptrs->at(lvl)];
|
||||
if (user_cmp->Compare(user_key, f->largest.user_key()) <= 0) {
|
||||
// We've advanced far enough
|
||||
if (user_cmp->Compare(user_key, f->smallest.user_key()) >= 0) {
|
||||
// Key falls in this file's range, so definitely
|
||||
// exists beyond output level
|
||||
return false;
|
||||
if (cfd_->ioptions()->compaction_style == kCompactionStyleLevel) {
|
||||
if (output_level_ == 0) {
|
||||
return false;
|
||||
}
|
||||
// Maybe use binary search to find right entry instead of linear search?
|
||||
const Comparator* user_cmp = cfd_->user_comparator();
|
||||
for (int lvl = output_level_ + 1; lvl < number_levels_; lvl++) {
|
||||
const std::vector<FileMetaData*>& files =
|
||||
input_vstorage_->LevelFiles(lvl);
|
||||
for (; level_ptrs->at(lvl) < files.size(); level_ptrs->at(lvl)++) {
|
||||
auto* f = files[level_ptrs->at(lvl)];
|
||||
if (user_cmp->Compare(user_key, f->largest.user_key()) <= 0) {
|
||||
// We've advanced far enough
|
||||
if (user_cmp->Compare(user_key, f->smallest.user_key()) >= 0) {
|
||||
// Key falls in this file's range, so definitely
|
||||
// exists beyond output level
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return bottommost_level_;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Mark (or clear) each file that is being compacted
|
||||
|
||||
@@ -16,6 +16,8 @@ struct CompactionIterationStats {
|
||||
int64_t num_record_drop_obsolete = 0;
|
||||
int64_t num_record_drop_range_del = 0;
|
||||
int64_t num_range_del_drop_obsolete = 0;
|
||||
// Deletions obsoleted before bottom level due to file gap optimization.
|
||||
int64_t num_optimized_del_drop_obsolete = 0;
|
||||
uint64_t total_filter_time = 0;
|
||||
|
||||
// Input statistics
|
||||
|
||||
@@ -111,6 +111,7 @@ void CompactionIterator::ResetRecordCounts() {
|
||||
iter_stats_.num_record_drop_obsolete = 0;
|
||||
iter_stats_.num_record_drop_range_del = 0;
|
||||
iter_stats_.num_range_del_drop_obsolete = 0;
|
||||
iter_stats_.num_optimized_del_drop_obsolete = 0;
|
||||
}
|
||||
|
||||
void CompactionIterator::SeekToFirst() {
|
||||
@@ -426,6 +427,9 @@ void CompactionIterator::NextFromInput() {
|
||||
// Can compact out this SingleDelete.
|
||||
++iter_stats_.num_record_drop_obsolete;
|
||||
++iter_stats_.num_single_del_fallthru;
|
||||
if (!bottommost_level_) {
|
||||
++iter_stats_.num_optimized_del_drop_obsolete;
|
||||
}
|
||||
} else {
|
||||
// Output SingleDelete
|
||||
valid_ = true;
|
||||
@@ -467,6 +471,9 @@ void CompactionIterator::NextFromInput() {
|
||||
// Note: Dropping this Delete will not affect TransactionDB
|
||||
// write-conflict checking since it is earlier than any snapshot.
|
||||
++iter_stats_.num_record_drop_obsolete;
|
||||
if (!bottommost_level_) {
|
||||
++iter_stats_.num_optimized_del_drop_obsolete;
|
||||
}
|
||||
input_->Next();
|
||||
} else if (ikey_.type == kTypeMerge) {
|
||||
if (!merge_helper_->HasOperator()) {
|
||||
|
||||
@@ -455,6 +455,111 @@ TEST_F(CompactionIteratorTest, ShuttingDownInMerge) {
|
||||
EXPECT_EQ(2, filter.last_seen.load());
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorTest, SingleMergeOperand) {
|
||||
class Filter : public CompactionFilter {
|
||||
virtual Decision FilterV2(int level, const Slice& key, ValueType t,
|
||||
const Slice& existing_value,
|
||||
std::string* new_value,
|
||||
std::string* skip_until) const override {
|
||||
std::string k = key.ToString();
|
||||
std::string v = existing_value.ToString();
|
||||
|
||||
// See InitIterators() call below for the sequence of keys and their
|
||||
// filtering decisions. Here we closely assert that compaction filter is
|
||||
// called with the expected keys and only them, and with the right values.
|
||||
if (k == "a") {
|
||||
EXPECT_EQ(ValueType::kMergeOperand, t);
|
||||
EXPECT_EQ("av1", v);
|
||||
return Decision::kKeep;
|
||||
} else if (k == "b") {
|
||||
EXPECT_EQ(ValueType::kMergeOperand, t);
|
||||
return Decision::kKeep;
|
||||
} else if (k == "c") {
|
||||
return Decision::kKeep;
|
||||
}
|
||||
|
||||
ADD_FAILURE();
|
||||
return Decision::kKeep;
|
||||
}
|
||||
|
||||
const char* Name() const override {
|
||||
return "CompactionIteratorTest.SingleMergeOperand::Filter";
|
||||
}
|
||||
};
|
||||
|
||||
class SingleMergeOp : public MergeOperator {
|
||||
public:
|
||||
bool FullMergeV2(const MergeOperationInput& merge_in,
|
||||
MergeOperationOutput* merge_out) const override {
|
||||
// See InitIterators() call below for why "c" is the only key for which
|
||||
// FullMergeV2 should be called.
|
||||
EXPECT_EQ("c", merge_in.key.ToString());
|
||||
|
||||
std::string temp_value;
|
||||
if (merge_in.existing_value != nullptr) {
|
||||
temp_value = merge_in.existing_value->ToString();
|
||||
}
|
||||
|
||||
for (auto& operand : merge_in.operand_list) {
|
||||
temp_value.append(operand.ToString());
|
||||
}
|
||||
merge_out->new_value = temp_value;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PartialMergeMulti(const Slice& key,
|
||||
const std::deque<Slice>& operand_list,
|
||||
std::string* new_value,
|
||||
Logger* logger) const override {
|
||||
std::string string_key = key.ToString();
|
||||
EXPECT_TRUE(string_key == "a" || string_key == "b");
|
||||
|
||||
if (string_key == "a") {
|
||||
EXPECT_EQ(1, operand_list.size());
|
||||
} else if (string_key == "b") {
|
||||
EXPECT_EQ(2, operand_list.size());
|
||||
}
|
||||
|
||||
std::string temp_value;
|
||||
for (auto& operand : operand_list) {
|
||||
temp_value.append(operand.ToString());
|
||||
}
|
||||
swap(temp_value, *new_value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* Name() const override {
|
||||
return "CompactionIteratorTest SingleMergeOp";
|
||||
}
|
||||
|
||||
bool AllowSingleOperand() const override { return true; }
|
||||
};
|
||||
|
||||
SingleMergeOp merge_op;
|
||||
Filter filter;
|
||||
InitIterators(
|
||||
// a should invoke PartialMergeMulti with a single merge operand.
|
||||
{test::KeyStr("a", 50, kTypeMerge),
|
||||
// b should invoke PartialMergeMulti with two operands.
|
||||
test::KeyStr("b", 70, kTypeMerge), test::KeyStr("b", 60, kTypeMerge),
|
||||
// c should invoke FullMerge due to kTypeValue at the beginning.
|
||||
test::KeyStr("c", 90, kTypeMerge), test::KeyStr("c", 80, kTypeValue)},
|
||||
{"av1", "bv2", "bv1", "cv2", "cv1"}, {}, {}, kMaxSequenceNumber,
|
||||
&merge_op, &filter);
|
||||
|
||||
c_iter_->SeekToFirst();
|
||||
ASSERT_TRUE(c_iter_->Valid());
|
||||
ASSERT_EQ(test::KeyStr("a", 50, kTypeMerge), c_iter_->key().ToString());
|
||||
ASSERT_EQ("av1", c_iter_->value().ToString());
|
||||
c_iter_->Next();
|
||||
ASSERT_TRUE(c_iter_->Valid());
|
||||
ASSERT_EQ("bv1bv2", c_iter_->value().ToString());
|
||||
c_iter_->Next();
|
||||
ASSERT_EQ("cv1cv2", c_iter_->value().ToString());
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -1014,6 +1014,10 @@ void CompactionJob::RecordDroppedKeys(
|
||||
RecordTick(stats_, COMPACTION_RANGE_DEL_DROP_OBSOLETE,
|
||||
c_iter_stats.num_range_del_drop_obsolete);
|
||||
}
|
||||
if (c_iter_stats.num_optimized_del_drop_obsolete > 0) {
|
||||
RecordTick(stats_, COMPACTION_OPTIMIZED_DEL_DROP_OBSOLETE,
|
||||
c_iter_stats.num_optimized_del_drop_obsolete);
|
||||
}
|
||||
}
|
||||
|
||||
Status CompactionJob::FinishCompactionOutputFile(
|
||||
|
||||
@@ -373,6 +373,7 @@ Compaction* UniversalCompactionPicker::PickCompaction(
|
||||
c->inputs(0)->size());
|
||||
|
||||
RegisterCompaction(c);
|
||||
vstorage->ComputeCompactionScore(ioptions_, mutable_cf_options);
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK("UniversalCompactionPicker::PickCompaction:Return",
|
||||
c);
|
||||
|
||||
+28
-29
@@ -360,7 +360,6 @@ TEST_F(DBBasicTest, FLUSH) {
|
||||
WriteOptions writeOpt = WriteOptions();
|
||||
writeOpt.disableWAL = true;
|
||||
SetPerfLevel(kEnableTime);
|
||||
;
|
||||
ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "foo", "v1"));
|
||||
// this will now also flush the last 2 writes
|
||||
ASSERT_OK(Flush(1));
|
||||
@@ -369,6 +368,7 @@ TEST_F(DBBasicTest, FLUSH) {
|
||||
get_perf_context()->Reset();
|
||||
Get(1, "foo");
|
||||
ASSERT_TRUE((int)get_perf_context()->get_from_output_files_time > 0);
|
||||
ASSERT_EQ(2, (int)get_perf_context()->get_read_bytes);
|
||||
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
||||
ASSERT_EQ("v1", Get(1, "foo"));
|
||||
@@ -725,6 +725,7 @@ TEST_F(DBBasicTest, FlushOneColumnFamily) {
|
||||
TEST_F(DBBasicTest, MultiGetSimple) {
|
||||
do {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
SetPerfLevel(kEnableCount);
|
||||
ASSERT_OK(Put(1, "k1", "v1"));
|
||||
ASSERT_OK(Put(1, "k2", "v2"));
|
||||
ASSERT_OK(Put(1, "k3", "v3"));
|
||||
@@ -738,12 +739,15 @@ TEST_F(DBBasicTest, MultiGetSimple) {
|
||||
std::vector<std::string> values(20, "Temporary data to be overwritten");
|
||||
std::vector<ColumnFamilyHandle*> cfs(keys.size(), handles_[1]);
|
||||
|
||||
get_perf_context()->Reset();
|
||||
std::vector<Status> s = db_->MultiGet(ReadOptions(), cfs, keys, &values);
|
||||
ASSERT_EQ(values.size(), keys.size());
|
||||
ASSERT_EQ(values[0], "v1");
|
||||
ASSERT_EQ(values[1], "v2");
|
||||
ASSERT_EQ(values[2], "v3");
|
||||
ASSERT_EQ(values[4], "v5");
|
||||
// four kv pairs * two bytes per value
|
||||
ASSERT_EQ(8, (int)get_perf_context()->multiget_read_bytes);
|
||||
|
||||
ASSERT_OK(s[0]);
|
||||
ASSERT_OK(s[1]);
|
||||
@@ -751,6 +755,7 @@ TEST_F(DBBasicTest, MultiGetSimple) {
|
||||
ASSERT_TRUE(s[3].IsNotFound());
|
||||
ASSERT_OK(s[4]);
|
||||
ASSERT_TRUE(s[5].IsNotFound());
|
||||
SetPerfLevel(kDisable);
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
@@ -787,36 +792,30 @@ TEST_F(DBBasicTest, MultiGetEmpty) {
|
||||
TEST_F(DBBasicTest, ChecksumTest) {
|
||||
BlockBasedTableOptions table_options;
|
||||
Options options = CurrentOptions();
|
||||
// change when new checksum type added
|
||||
int max_checksum = static_cast<int>(kxxHash);
|
||||
const int kNumPerFile = 2;
|
||||
|
||||
table_options.checksum = kCRC32c;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("a", "b"));
|
||||
ASSERT_OK(Put("c", "d"));
|
||||
ASSERT_OK(Flush()); // table with crc checksum
|
||||
// generate one table with each type of checksum
|
||||
for (int i = 0; i <= max_checksum; ++i) {
|
||||
table_options.checksum = static_cast<ChecksumType>(i);
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
Reopen(options);
|
||||
for (int j = 0; j < kNumPerFile; ++j) {
|
||||
ASSERT_OK(Put(Key(i * kNumPerFile + j), Key(i * kNumPerFile + j)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
table_options.checksum = kxxHash;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("e", "f"));
|
||||
ASSERT_OK(Put("g", "h"));
|
||||
ASSERT_OK(Flush()); // table with xxhash checksum
|
||||
|
||||
table_options.checksum = kCRC32c;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
Reopen(options);
|
||||
ASSERT_EQ("b", Get("a"));
|
||||
ASSERT_EQ("d", Get("c"));
|
||||
ASSERT_EQ("f", Get("e"));
|
||||
ASSERT_EQ("h", Get("g"));
|
||||
|
||||
table_options.checksum = kCRC32c;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
Reopen(options);
|
||||
ASSERT_EQ("b", Get("a"));
|
||||
ASSERT_EQ("d", Get("c"));
|
||||
ASSERT_EQ("f", Get("e"));
|
||||
ASSERT_EQ("h", Get("g"));
|
||||
// verify data with each type of checksum
|
||||
for (int i = 0; i <= kxxHash; ++i) {
|
||||
table_options.checksum = static_cast<ChecksumType>(i);
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
Reopen(options);
|
||||
for (int j = 0; j < (max_checksum + 1) * kNumPerFile; ++j) {
|
||||
ASSERT_EQ(Key(j), Get(Key(j)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// On Windows you can have either memory mapped file or a file
|
||||
|
||||
@@ -2684,6 +2684,46 @@ TEST_P(DBCompactionTestWithParam, IntraL0CompactionDoesNotObsoleteDeletions) {
|
||||
ASSERT_TRUE(db_->Get(roptions, Key(0), &result).IsNotFound());
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, OptimizedDeletionObsoleting) {
|
||||
// Deletions can be dropped when compacted to non-last level if they fall
|
||||
// outside the lower-level files' key-ranges.
|
||||
const int kNumL0Files = 4;
|
||||
Options options = CurrentOptions();
|
||||
options.level0_file_num_compaction_trigger = kNumL0Files;
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// put key 1 and 3 in separate L1, L2 files.
|
||||
// So key 0, 2, and 4+ fall outside these levels' key-ranges.
|
||||
for (int level = 2; level >= 1; --level) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
Put(Key(2 * i + 1), "val");
|
||||
Flush();
|
||||
}
|
||||
MoveFilesToLevel(level);
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(level));
|
||||
}
|
||||
|
||||
// Delete keys in range [1, 4]. These L0 files will be compacted with L1:
|
||||
// - Tombstones for keys 2 and 4 can be dropped early.
|
||||
// - Tombstones for keys 1 and 3 must be kept due to L2 files' key-ranges.
|
||||
for (int i = 0; i < kNumL0Files; ++i) {
|
||||
Put(Key(0), "val"); // sentinel to prevent trivial move
|
||||
Delete(Key(i + 1));
|
||||
Flush();
|
||||
}
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
|
||||
for (int i = 0; i < kNumL0Files; ++i) {
|
||||
std::string value;
|
||||
ASSERT_TRUE(db_->Get(ReadOptions(), Key(i + 1), &value).IsNotFound());
|
||||
}
|
||||
ASSERT_EQ(2, options.statistics->getTickerCount(
|
||||
COMPACTION_OPTIMIZED_DEL_DROP_OBSOLETE));
|
||||
ASSERT_EQ(2,
|
||||
options.statistics->getTickerCount(COMPACTION_KEY_DROP_OBSOLETE));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBCompactionTestWithParam, DBCompactionTestWithParam,
|
||||
::testing::Values(std::make_tuple(1, true),
|
||||
std::make_tuple(1, false),
|
||||
|
||||
+8
-4
@@ -990,6 +990,7 @@ Status DBImpl::GetImpl(const ReadOptions& read_options,
|
||||
size_t size = pinnable_val->size();
|
||||
RecordTick(stats_, BYTES_READ, size);
|
||||
MeasureTime(stats_, BYTES_PER_READ, size);
|
||||
PERF_COUNTER_ADD(get_read_bytes, size);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -1117,6 +1118,7 @@ std::vector<Status> DBImpl::MultiGet(
|
||||
RecordTick(stats_, NUMBER_MULTIGET_KEYS_READ, num_keys);
|
||||
RecordTick(stats_, NUMBER_MULTIGET_BYTES_READ, bytes_read);
|
||||
MeasureTime(stats_, BYTES_PER_MULTIGET, bytes_read);
|
||||
PERF_COUNTER_ADD(multiget_read_bytes, bytes_read);
|
||||
PERF_TIMER_STOP(get_post_process_time);
|
||||
|
||||
return stat_list;
|
||||
@@ -1322,6 +1324,11 @@ Status DBImpl::DropColumnFamilyImpl(ColumnFamilyHandle* column_family) {
|
||||
&edit, &mutex_);
|
||||
write_thread_.ExitUnbatched(&w);
|
||||
}
|
||||
if (s.ok()) {
|
||||
auto* mutable_cf_options = cfd->GetLatestMutableCFOptions();
|
||||
max_total_in_memory_state_ -= mutable_cf_options->write_buffer_size *
|
||||
mutable_cf_options->max_write_buffer_number;
|
||||
}
|
||||
|
||||
if (!cf_support_snapshot) {
|
||||
// Dropped Column Family doesn't support snapshot. Need to recalculate
|
||||
@@ -1343,9 +1350,6 @@ Status DBImpl::DropColumnFamilyImpl(ColumnFamilyHandle* column_family) {
|
||||
// later inside db_mutex.
|
||||
EraseThreadStatusCfInfo(cfd);
|
||||
assert(cfd->IsDropped());
|
||||
auto* mutable_cf_options = cfd->GetLatestMutableCFOptions();
|
||||
max_total_in_memory_state_ -= mutable_cf_options->write_buffer_size *
|
||||
mutable_cf_options->max_write_buffer_number;
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Dropped column family with id %u\n", cfd->GetID());
|
||||
} else {
|
||||
@@ -2768,7 +2772,7 @@ Status DBImpl::VerifyChecksum() {
|
||||
const auto& fd = vstorage->LevelFilesBrief(i).files[j].fd;
|
||||
std::string fname = TableFileName(immutable_db_options_.db_paths,
|
||||
fd.GetNumber(), fd.GetPathId());
|
||||
s = rocksdb::VerifySstFileChecksum(options, env_options, fname);
|
||||
s = rocksdb::VerifySstFileChecksum(options, env_options, fname);
|
||||
}
|
||||
}
|
||||
if (!s.ok()) {
|
||||
|
||||
+5
-3
@@ -616,16 +616,18 @@ class DBImpl : public DB {
|
||||
Status WriteImpl(const WriteOptions& options, WriteBatch* updates,
|
||||
WriteCallback* callback = nullptr,
|
||||
uint64_t* log_used = nullptr, uint64_t log_ref = 0,
|
||||
bool disable_memtable = false);
|
||||
bool disable_memtable = false, uint64_t* seq_used = nullptr);
|
||||
|
||||
Status PipelinedWriteImpl(const WriteOptions& options, WriteBatch* updates,
|
||||
WriteCallback* callback = nullptr,
|
||||
uint64_t* log_used = nullptr, uint64_t log_ref = 0,
|
||||
bool disable_memtable = false);
|
||||
bool disable_memtable = false,
|
||||
uint64_t* seq_used = nullptr);
|
||||
|
||||
Status WriteImplWALOnly(const WriteOptions& options, WriteBatch* updates,
|
||||
WriteCallback* callback = nullptr,
|
||||
uint64_t* log_used = nullptr, uint64_t log_ref = 0);
|
||||
uint64_t* log_used = nullptr, uint64_t log_ref = 0,
|
||||
uint64_t* seq_used = nullptr);
|
||||
|
||||
uint64_t FindMinLogContainingOutstandingPrep();
|
||||
uint64_t FindMinPrepLogReferencedByMemTable();
|
||||
|
||||
+29
-6
@@ -60,7 +60,7 @@ Status DBImpl::WriteWithCallback(const WriteOptions& write_options,
|
||||
Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
WriteBatch* my_batch, WriteCallback* callback,
|
||||
uint64_t* log_used, uint64_t log_ref,
|
||||
bool disable_memtable) {
|
||||
bool disable_memtable, uint64_t* seq_used) {
|
||||
if (my_batch == nullptr) {
|
||||
return Status::Corruption("Batch is nullptr!");
|
||||
}
|
||||
@@ -79,12 +79,12 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
|
||||
if (concurrent_prepare_ && disable_memtable) {
|
||||
return WriteImplWALOnly(write_options, my_batch, callback, log_used,
|
||||
log_ref);
|
||||
log_ref, seq_used);
|
||||
}
|
||||
|
||||
if (immutable_db_options_.enable_pipelined_write) {
|
||||
return PipelinedWriteImpl(write_options, my_batch, callback, log_used,
|
||||
log_ref, disable_memtable);
|
||||
log_ref, disable_memtable, seq_used);
|
||||
}
|
||||
|
||||
PERF_TIMER_GUARD(write_pre_and_post_process_time);
|
||||
@@ -127,6 +127,9 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
if (log_used != nullptr) {
|
||||
*log_used = w.log_used;
|
||||
}
|
||||
if (seq_used != nullptr) {
|
||||
*seq_used = w.sequence;
|
||||
}
|
||||
// write is complete and leader has updated sequence
|
||||
return w.FinalStatus();
|
||||
}
|
||||
@@ -278,6 +281,9 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
write_options.ignore_missing_column_families, 0 /*log_number*/,
|
||||
this, true /*concurrent_memtable_writes*/);
|
||||
}
|
||||
if (seq_used != nullptr) {
|
||||
*seq_used = w.sequence;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -325,7 +331,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
WriteBatch* my_batch, WriteCallback* callback,
|
||||
uint64_t* log_used, uint64_t log_ref,
|
||||
bool disable_memtable) {
|
||||
bool disable_memtable, uint64_t* seq_used) {
|
||||
PERF_TIMER_GUARD(write_pre_and_post_process_time);
|
||||
StopWatch write_sw(env_, immutable_db_options_.statistics.get(), DB_WRITE);
|
||||
|
||||
@@ -440,6 +446,9 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
write_thread_.ExitAsMemTableWriter(&w, *w.write_group);
|
||||
}
|
||||
}
|
||||
if (seq_used != nullptr) {
|
||||
*seq_used = w.sequence;
|
||||
}
|
||||
|
||||
assert(w.state == WriteThread::STATE_COMPLETED);
|
||||
return w.FinalStatus();
|
||||
@@ -447,7 +456,8 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
|
||||
Status DBImpl::WriteImplWALOnly(const WriteOptions& write_options,
|
||||
WriteBatch* my_batch, WriteCallback* callback,
|
||||
uint64_t* log_used, uint64_t log_ref) {
|
||||
uint64_t* log_used, uint64_t log_ref,
|
||||
uint64_t* seq_used) {
|
||||
Status status;
|
||||
PERF_TIMER_GUARD(write_pre_and_post_process_time);
|
||||
WriteThread::Writer w(write_options, my_batch, callback, log_ref,
|
||||
@@ -464,6 +474,9 @@ Status DBImpl::WriteImplWALOnly(const WriteOptions& write_options,
|
||||
if (log_used != nullptr) {
|
||||
*log_used = w.log_used;
|
||||
}
|
||||
if (seq_used != nullptr) {
|
||||
*seq_used = w.sequence;
|
||||
}
|
||||
return w.FinalStatus();
|
||||
}
|
||||
// else we are the leader of the write batch group
|
||||
@@ -509,6 +522,13 @@ Status DBImpl::WriteImplWALOnly(const WriteOptions& write_options,
|
||||
// wal_write_mutex_ to ensure ordered events in WAL
|
||||
status = ConcurrentWriteToWAL(write_group, log_used, &last_sequence,
|
||||
0 /*total_count*/);
|
||||
auto curr_seq = last_sequence + 1;
|
||||
for (auto* writer : write_group) {
|
||||
if (writer->CheckCallback(this)) {
|
||||
writer->sequence = curr_seq;
|
||||
curr_seq += WriteBatchInternal::Count(writer->batch);
|
||||
}
|
||||
}
|
||||
if (status.ok() && write_options.sync) {
|
||||
// Requesting sync with concurrent_prepare_ is expected to be very rare. We
|
||||
// hance provide a simple implementation that is not necessarily efficient.
|
||||
@@ -527,6 +547,9 @@ Status DBImpl::WriteImplWALOnly(const WriteOptions& write_options,
|
||||
if (status.ok()) {
|
||||
status = w.FinalStatus();
|
||||
}
|
||||
if (seq_used != nullptr) {
|
||||
*seq_used = w.sequence;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -1058,7 +1081,7 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
SuperVersion* new_superversion = nullptr;
|
||||
const MutableCFOptions mutable_cf_options = *cfd->GetLatestMutableCFOptions();
|
||||
|
||||
// Set current_memtble_info for memtable sealed callback
|
||||
// Set memtable_info for memtable sealed callback
|
||||
#ifndef ROCKSDB_LITE
|
||||
MemTableInfo memtable_info;
|
||||
memtable_info.cf_name = cfd->GetName();
|
||||
|
||||
@@ -86,6 +86,7 @@ class DBIter: public Iterator {
|
||||
RecordTick(global_statistics, NUMBER_DB_PREV, prev_count_);
|
||||
RecordTick(global_statistics, NUMBER_DB_PREV_FOUND, prev_found_count_);
|
||||
RecordTick(global_statistics, ITER_BYTES_READ, bytes_read_);
|
||||
PERF_COUNTER_ADD(iter_read_bytes, bytes_read_);
|
||||
ResetCounters();
|
||||
}
|
||||
|
||||
@@ -1014,6 +1015,7 @@ void DBIter::Seek(const Slice& target) {
|
||||
if (valid_) {
|
||||
RecordTick(statistics_, NUMBER_DB_SEEK_FOUND);
|
||||
RecordTick(statistics_, ITER_BYTES_READ, key().size() + value().size());
|
||||
PERF_COUNTER_ADD(iter_read_bytes, key().size() + value().size());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -1056,6 +1058,7 @@ void DBIter::SeekForPrev(const Slice& target) {
|
||||
if (valid_) {
|
||||
RecordTick(statistics_, NUMBER_DB_SEEK_FOUND);
|
||||
RecordTick(statistics_, ITER_BYTES_READ, key().size() + value().size());
|
||||
PERF_COUNTER_ADD(iter_read_bytes, key().size() + value().size());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -1094,6 +1097,7 @@ void DBIter::SeekToFirst() {
|
||||
if (valid_) {
|
||||
RecordTick(statistics_, NUMBER_DB_SEEK_FOUND);
|
||||
RecordTick(statistics_, ITER_BYTES_READ, key().size() + value().size());
|
||||
PERF_COUNTER_ADD(iter_read_bytes, key().size() + value().size());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -1141,6 +1145,7 @@ void DBIter::SeekToLast() {
|
||||
if (valid_) {
|
||||
RecordTick(statistics_, NUMBER_DB_SEEK_FOUND);
|
||||
RecordTick(statistics_, ITER_BYTES_READ, key().size() + value().size());
|
||||
PERF_COUNTER_ADD(iter_read_bytes, key().size() + value().size());
|
||||
}
|
||||
}
|
||||
if (valid_ && prefix_extractor_ && prefix_same_as_start_) {
|
||||
|
||||
+19
-8
@@ -1719,12 +1719,15 @@ TEST_F(DBIteratorTest, IteratorWithLocalStatistics) {
|
||||
|
||||
std::vector<port::Thread> threads;
|
||||
std::function<void()> reader_func_next = [&]() {
|
||||
SetPerfLevel(kEnableCount);
|
||||
get_perf_context()->Reset();
|
||||
Iterator* iter = db_->NewIterator(ReadOptions());
|
||||
|
||||
iter->SeekToFirst();
|
||||
// Seek will bump ITER_BYTES_READ
|
||||
total_bytes += iter->key().size();
|
||||
total_bytes += iter->value().size();
|
||||
uint64_t bytes = 0;
|
||||
bytes += iter->key().size();
|
||||
bytes += iter->value().size();
|
||||
while (true) {
|
||||
iter->Next();
|
||||
total_next++;
|
||||
@@ -1733,20 +1736,25 @@ TEST_F(DBIteratorTest, IteratorWithLocalStatistics) {
|
||||
break;
|
||||
}
|
||||
total_next_found++;
|
||||
total_bytes += iter->key().size();
|
||||
total_bytes += iter->value().size();
|
||||
bytes += iter->key().size();
|
||||
bytes += iter->value().size();
|
||||
}
|
||||
|
||||
delete iter;
|
||||
ASSERT_EQ(bytes, get_perf_context()->iter_read_bytes);
|
||||
SetPerfLevel(kDisable);
|
||||
total_bytes += bytes;
|
||||
};
|
||||
|
||||
std::function<void()> reader_func_prev = [&]() {
|
||||
SetPerfLevel(kEnableCount);
|
||||
Iterator* iter = db_->NewIterator(ReadOptions());
|
||||
|
||||
iter->SeekToLast();
|
||||
// Seek will bump ITER_BYTES_READ
|
||||
total_bytes += iter->key().size();
|
||||
total_bytes += iter->value().size();
|
||||
uint64_t bytes = 0;
|
||||
bytes += iter->key().size();
|
||||
bytes += iter->value().size();
|
||||
while (true) {
|
||||
iter->Prev();
|
||||
total_prev++;
|
||||
@@ -1755,11 +1763,14 @@ TEST_F(DBIteratorTest, IteratorWithLocalStatistics) {
|
||||
break;
|
||||
}
|
||||
total_prev_found++;
|
||||
total_bytes += iter->key().size();
|
||||
total_bytes += iter->value().size();
|
||||
bytes += iter->key().size();
|
||||
bytes += iter->value().size();
|
||||
}
|
||||
|
||||
delete iter;
|
||||
ASSERT_EQ(bytes, get_perf_context()->iter_read_bytes);
|
||||
SetPerfLevel(kDisable);
|
||||
total_bytes += bytes;
|
||||
};
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
|
||||
@@ -868,6 +868,134 @@ TEST_F(DBRangeDelTest, SubcompactionHasEmptyDedicatedRangeDelFile) {
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, MemtableBloomFilter) {
|
||||
// regression test for #2743. the range delete tombstones in memtable should
|
||||
// be added even when Get() skips searching due to its prefix bloom filter
|
||||
const int kMemtableSize = 1 << 20; // 1MB
|
||||
const int kMemtablePrefixFilterSize = 1 << 13; // 8KB
|
||||
const int kNumKeys = 1000;
|
||||
const int kPrefixLen = 8;
|
||||
Options options = CurrentOptions();
|
||||
options.memtable_prefix_bloom_size_ratio =
|
||||
static_cast<double>(kMemtablePrefixFilterSize) / kMemtableSize;
|
||||
options.prefix_extractor.reset(rocksdb::NewFixedPrefixTransform(kPrefixLen));
|
||||
options.write_buffer_size = kMemtableSize;
|
||||
Reopen(options);
|
||||
|
||||
for (int i = 0; i < kNumKeys; ++i) {
|
||||
ASSERT_OK(Put(Key(i), "val"));
|
||||
}
|
||||
Flush();
|
||||
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(0),
|
||||
Key(kNumKeys)));
|
||||
for (int i = 0; i < kNumKeys; ++i) {
|
||||
std::string value;
|
||||
ASSERT_TRUE(db_->Get(ReadOptions(), Key(i), &value).IsNotFound());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, CompactionTreatsSplitInputLevelDeletionAtomically) {
|
||||
// make sure compaction treats files containing a split range deletion in the
|
||||
// input level as an atomic unit. I.e., compacting any input-level file(s)
|
||||
// containing a portion of the range deletion causes all other input-level
|
||||
// files containing portions of that same range deletion to be included in the
|
||||
// compaction.
|
||||
const int kNumFilesPerLevel = 4, kValueBytes = 4 << 10;
|
||||
Options options = CurrentOptions();
|
||||
options.compression = kNoCompression;
|
||||
options.level0_file_num_compaction_trigger = kNumFilesPerLevel;
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(2 /* num_entries_flush */));
|
||||
options.target_file_size_base = kValueBytes;
|
||||
// i == 0: CompactFiles
|
||||
// i == 1: CompactRange
|
||||
// i == 2: automatic compaction
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(Put(Key(0), ""));
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
MoveFilesToLevel(2);
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(2));
|
||||
|
||||
// snapshot protects range tombstone from dropping due to becoming obsolete.
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(0),
|
||||
Key(2 * kNumFilesPerLevel));
|
||||
|
||||
Random rnd(301);
|
||||
std::string value = RandomString(&rnd, kValueBytes);
|
||||
for (int j = 0; j < kNumFilesPerLevel; ++j) {
|
||||
// give files overlapping key-ranges to prevent trivial move
|
||||
ASSERT_OK(Put(Key(j), value));
|
||||
ASSERT_OK(Put(Key(2 * kNumFilesPerLevel - 1 - j), value));
|
||||
if (j > 0) {
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
ASSERT_EQ(j, NumTableFilesAtLevel(0));
|
||||
}
|
||||
}
|
||||
// put extra key to trigger final flush
|
||||
ASSERT_OK(Put("", ""));
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ(kNumFilesPerLevel, NumTableFilesAtLevel(1));
|
||||
|
||||
ColumnFamilyMetaData meta;
|
||||
db_->GetColumnFamilyMetaData(&meta);
|
||||
if (i == 0) {
|
||||
ASSERT_OK(db_->CompactFiles(
|
||||
CompactionOptions(), {meta.levels[1].files[0].name}, 2 /* level */));
|
||||
} else if (i == 1) {
|
||||
auto begin_str = Key(0), end_str = Key(1);
|
||||
Slice begin = begin_str, end = end_str;
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &begin, &end));
|
||||
} else if (i == 2) {
|
||||
ASSERT_OK(db_->SetOptions(db_->DefaultColumnFamily(),
|
||||
{{"max_bytes_for_level_base", "10000"}}));
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
}
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(1));
|
||||
ASSERT_GT(NumTableFilesAtLevel(2), 0);
|
||||
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, UnorderedTombstones) {
|
||||
// Regression test for #2752. Range delete tombstones between
|
||||
// different snapshot stripes are not stored in order, so the first
|
||||
// tombstone of each snapshot stripe should be checked as a smallest
|
||||
// candidate.
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
auto cf = db_->DefaultColumnFamily();
|
||||
|
||||
ASSERT_OK(db_->Put(WriteOptions(), cf, "a", "a"));
|
||||
ASSERT_OK(db_->Flush(FlushOptions(), cf));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(1));
|
||||
|
||||
ASSERT_OK(db_->DeleteRange(WriteOptions(), cf, "b", "c"));
|
||||
// Hold a snapshot to separate these two delete ranges.
|
||||
auto snapshot = db_->GetSnapshot();
|
||||
ASSERT_OK(db_->DeleteRange(WriteOptions(), cf, "a", "b"));
|
||||
ASSERT_OK(db_->Flush(FlushOptions(), cf));
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
|
||||
std::vector<std::vector<FileMetaData>> files;
|
||||
dbfull()->TEST_GetFilesMetaData(cf, &files);
|
||||
ASSERT_EQ(1, files[0].size());
|
||||
ASSERT_EQ("a", files[0][0].smallest.user_key());
|
||||
ASSERT_EQ("c", files[0][0].largest.user_key());
|
||||
|
||||
std::string v;
|
||||
auto s = db_->Get(ReadOptions(), "a", &v);
|
||||
ASSERT_TRUE(s.IsNotFound());
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -2811,6 +2811,46 @@ TEST_F(DBTest, FIFOCompactionTestWithCompaction) {
|
||||
options.compaction_options_fifo.max_table_files_size);
|
||||
}
|
||||
|
||||
TEST_F(DBTest, FIFOCompactionStyleWithCompactionAndDelete) {
|
||||
Options options;
|
||||
options.compaction_style = kCompactionStyleFIFO;
|
||||
options.write_buffer_size = 20 << 10; // 20K
|
||||
options.arena_block_size = 4096;
|
||||
options.compaction_options_fifo.max_table_files_size = 1500 << 10; // 1MB
|
||||
options.compaction_options_fifo.allow_compaction = true;
|
||||
options.level0_file_num_compaction_trigger = 3;
|
||||
options.compression = kNoCompression;
|
||||
options.create_if_missing = true;
|
||||
options = CurrentOptions(options);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
// Each file contains a different key which will be dropped later.
|
||||
ASSERT_OK(Put("a" + ToString(i), RandomString(&rnd, 500)));
|
||||
ASSERT_OK(Put("key" + ToString(i), ""));
|
||||
ASSERT_OK(Put("z" + ToString(i), RandomString(&rnd, 500)));
|
||||
Flush();
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
}
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 1);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
ASSERT_EQ("", Get("key" + ToString(i)));
|
||||
}
|
||||
for (int i = 0; i < 3; i++) {
|
||||
// Each file contains a different key which will be dropped later.
|
||||
ASSERT_OK(Put("a" + ToString(i), RandomString(&rnd, 500)));
|
||||
ASSERT_OK(Delete("key" + ToString(i)));
|
||||
ASSERT_OK(Put("z" + ToString(i), RandomString(&rnd, 500)));
|
||||
Flush();
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
}
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 2);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
ASSERT_EQ("NOT_FOUND", Get("key" + ToString(i)));
|
||||
}
|
||||
}
|
||||
|
||||
// Check that FIFO-with-TTL is not supported with max_open_files != -1.
|
||||
TEST_F(DBTest, FIFOCompactionWithTTLAndMaxOpenFilesTest) {
|
||||
Options options;
|
||||
|
||||
@@ -2304,6 +2304,27 @@ TEST_F(DBTest2, RateLimitedCompactionReads) {
|
||||
}
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// Make sure DB can be reopen with reduced number of levels, given no file
|
||||
// is on levels higher than the new num_levels.
|
||||
TEST_F(DBTest2, ReduceLevel) {
|
||||
Options options;
|
||||
options.disable_auto_compactions = true;
|
||||
options.num_levels = 7;
|
||||
Reopen(options);
|
||||
Put("foo", "bar");
|
||||
Flush();
|
||||
MoveFilesToLevel(6);
|
||||
ASSERT_EQ("0,0,0,0,0,0,1", FilesPerLevel());
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 1;
|
||||
dbfull()->CompactRange(compact_options, nullptr, nullptr);
|
||||
ASSERT_EQ("0,1", FilesPerLevel());
|
||||
options.num_levels = 3;
|
||||
Reopen(options);
|
||||
ASSERT_EQ("0,1", FilesPerLevel());
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -696,17 +696,12 @@ TEST_P(DBTestUniversalCompactionParallel, PickByFileNumberBug) {
|
||||
num_keys -= 100;
|
||||
}
|
||||
|
||||
// Wait for the 2nd background compaction process to start
|
||||
TEST_SYNC_POINT("DBTestUniversalCompactionParallel::PickByFileNumberBug:0");
|
||||
TEST_SYNC_POINT("DBTestUniversalCompactionParallel::PickByFileNumberBug:1");
|
||||
|
||||
// Hold the 1st and 2nd compaction from finishing
|
||||
// Hold the 1st compaction from finishing
|
||||
TEST_SYNC_POINT("DBTestUniversalCompactionParallel::PickByFileNumberBug:2");
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
|
||||
// Although 2 compaction threads started, the second one did not compact
|
||||
// anything because the number of files not being compacted is less than
|
||||
// level0_file_num_compaction_trigger
|
||||
// There should only be one picked compaction as the score drops below one
|
||||
// after the first one is picked.
|
||||
EXPECT_EQ(total_picked_compactions, 1);
|
||||
EXPECT_EQ(TotalTableFiles(), 4);
|
||||
|
||||
@@ -1411,6 +1406,7 @@ TEST_P(DBTestUniversalCompaction, FullCompactionInBottomPriThreadPool) {
|
||||
ASSERT_EQ(NumSortedRuns(), 1);
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
Env::Default()->SetBackgroundThreads(0, Env::Priority::BOTTOM);
|
||||
}
|
||||
|
||||
TEST_P(DBTestUniversalCompaction, ConcurrentBottomPriLowPriCompactions) {
|
||||
@@ -1465,6 +1461,42 @@ TEST_P(DBTestUniversalCompaction, ConcurrentBottomPriLowPriCompactions) {
|
||||
ASSERT_GT(NumTableFilesAtLevel(0), 0);
|
||||
ASSERT_GT(NumTableFilesAtLevel(num_levels_ - 1), 0);
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
Env::Default()->SetBackgroundThreads(0, Env::Priority::BOTTOM);
|
||||
}
|
||||
|
||||
TEST_P(DBTestUniversalCompaction, RecalculateScoreAfterPicking) {
|
||||
// Regression test for extra compactions scheduled. Once enough compactions
|
||||
// have been scheduled to bring the score below one, we should stop
|
||||
// scheduling more; otherwise, other CFs/DBs may be delayed unnecessarily.
|
||||
const int kNumFilesTrigger = 8;
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_options_universal.max_merge_width = kNumFilesTrigger / 2;
|
||||
options.compaction_options_universal.max_size_amplification_percent =
|
||||
static_cast<unsigned int>(-1);
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.level0_file_num_compaction_trigger = kNumFilesTrigger;
|
||||
options.num_levels = num_levels_;
|
||||
options.write_buffer_size = 100 << 10; // 100KB
|
||||
Reopen(options);
|
||||
|
||||
std::atomic<int> num_compactions_attempted(0);
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction:Start", [&](void* arg) {
|
||||
++num_compactions_attempted;
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Random rnd(301);
|
||||
for (int num = 0; num < kNumFilesTrigger; num++) {
|
||||
ASSERT_EQ(NumSortedRuns(), num);
|
||||
int key_idx = 0;
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
}
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
// Compacting the first four files was enough to bring the score below one so
|
||||
// there's no need to schedule any more compactions.
|
||||
ASSERT_EQ(1, num_compactions_attempted);
|
||||
ASSERT_EQ(NumSortedRuns(), 5);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(UniversalCompactionNumLevels, DBTestUniversalCompaction,
|
||||
|
||||
+8
-7
@@ -643,6 +643,14 @@ bool MemTable::Get(const LookupKey& key, std::string* value, Status* s,
|
||||
}
|
||||
PERF_TIMER_GUARD(get_from_memtable_time);
|
||||
|
||||
std::unique_ptr<InternalIterator> range_del_iter(
|
||||
NewRangeTombstoneIterator(read_opts));
|
||||
Status status = range_del_agg->AddTombstones(std::move(range_del_iter));
|
||||
if (!status.ok()) {
|
||||
*s = status;
|
||||
return false;
|
||||
}
|
||||
|
||||
Slice user_key = key.user_key();
|
||||
bool found_final_value = false;
|
||||
bool merge_in_progress = s->IsMergeInProgress();
|
||||
@@ -658,13 +666,6 @@ bool MemTable::Get(const LookupKey& key, std::string* value, Status* s,
|
||||
if (prefix_bloom_) {
|
||||
PERF_COUNTER_ADD(bloom_memtable_hit_count, 1);
|
||||
}
|
||||
std::unique_ptr<InternalIterator> range_del_iter(
|
||||
NewRangeTombstoneIterator(read_opts));
|
||||
Status status = range_del_agg->AddTombstones(std::move(range_del_iter));
|
||||
if (!status.ok()) {
|
||||
*s = status;
|
||||
return false;
|
||||
}
|
||||
Saver saver;
|
||||
saver.status = s;
|
||||
saver.found_final_value = &found_final_value;
|
||||
|
||||
+29
-1
@@ -18,6 +18,33 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
MergeHelper::MergeHelper(Env* env, const Comparator* user_comparator,
|
||||
const MergeOperator* user_merge_operator,
|
||||
const CompactionFilter* compaction_filter,
|
||||
Logger* logger, bool assert_valid_internal_key,
|
||||
SequenceNumber latest_snapshot, int level,
|
||||
Statistics* stats,
|
||||
const std::atomic<bool>* shutting_down)
|
||||
: env_(env),
|
||||
user_comparator_(user_comparator),
|
||||
user_merge_operator_(user_merge_operator),
|
||||
compaction_filter_(compaction_filter),
|
||||
shutting_down_(shutting_down),
|
||||
logger_(logger),
|
||||
assert_valid_internal_key_(assert_valid_internal_key),
|
||||
allow_single_operand_(false),
|
||||
latest_snapshot_(latest_snapshot),
|
||||
level_(level),
|
||||
keys_(),
|
||||
filter_timer_(env_),
|
||||
total_filter_time_(0U),
|
||||
stats_(stats) {
|
||||
assert(user_comparator_ != nullptr);
|
||||
if (user_merge_operator_) {
|
||||
allow_single_operand_ = user_merge_operator_->AllowSingleOperand();
|
||||
}
|
||||
}
|
||||
|
||||
Status MergeHelper::TimedFullMerge(const MergeOperator* merge_operator,
|
||||
const Slice& key, const Slice* value,
|
||||
const std::vector<Slice>& operands,
|
||||
@@ -288,7 +315,8 @@ Status MergeHelper::MergeUntil(InternalIterator* iter,
|
||||
// Attempt to use the user's associative merge function to
|
||||
// merge the stacked merge operands into a single operand.
|
||||
s = Status::MergeInProgress();
|
||||
if (merge_context_.GetNumOperands() >= 2) {
|
||||
if (merge_context_.GetNumOperands() >= 2 ||
|
||||
(allow_single_operand_ && merge_context_.GetNumOperands() == 1)) {
|
||||
bool merge_success = false;
|
||||
std::string merge_result;
|
||||
{
|
||||
|
||||
+2
-16
@@ -34,22 +34,7 @@ class MergeHelper {
|
||||
const CompactionFilter* compaction_filter, Logger* logger,
|
||||
bool assert_valid_internal_key, SequenceNumber latest_snapshot,
|
||||
int level = 0, Statistics* stats = nullptr,
|
||||
const std::atomic<bool>* shutting_down = nullptr)
|
||||
: env_(env),
|
||||
user_comparator_(user_comparator),
|
||||
user_merge_operator_(user_merge_operator),
|
||||
compaction_filter_(compaction_filter),
|
||||
shutting_down_(shutting_down),
|
||||
logger_(logger),
|
||||
assert_valid_internal_key_(assert_valid_internal_key),
|
||||
latest_snapshot_(latest_snapshot),
|
||||
level_(level),
|
||||
keys_(),
|
||||
filter_timer_(env_),
|
||||
total_filter_time_(0U),
|
||||
stats_(stats) {
|
||||
assert(user_comparator_ != nullptr);
|
||||
}
|
||||
const std::atomic<bool>* shutting_down = nullptr);
|
||||
|
||||
// Wrapper around MergeOperator::FullMergeV2() that records perf statistics.
|
||||
// Result of merge will be written to result if status returned is OK.
|
||||
@@ -158,6 +143,7 @@ class MergeHelper {
|
||||
const std::atomic<bool>* shutting_down_;
|
||||
Logger* logger_;
|
||||
bool assert_valid_internal_key_; // enforce no internal key corruption?
|
||||
bool allow_single_operand_;
|
||||
SequenceNumber latest_snapshot_;
|
||||
int level_;
|
||||
|
||||
|
||||
@@ -357,7 +357,8 @@ Status RangeDelAggregator::AddTombstone(RangeTombstone tombstone) {
|
||||
++new_range_dels_iter;
|
||||
}
|
||||
} else {
|
||||
tombstone_map.emplace(tombstone.start_key_, std::move(tombstone));
|
||||
auto start_key = tombstone.start_key_;
|
||||
tombstone_map.emplace(start_key, std::move(tombstone));
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
@@ -412,8 +413,8 @@ void RangeDelAggregator::AddToBuilder(
|
||||
|
||||
// Note the order in which tombstones are stored is insignificant since we
|
||||
// insert them into a std::map on the read path.
|
||||
bool first_added = false;
|
||||
while (stripe_map_iter != rep_->stripe_map_.end()) {
|
||||
bool first_added = false;
|
||||
for (auto tombstone_map_iter = stripe_map_iter->second.raw_map.begin();
|
||||
tombstone_map_iter != stripe_map_iter->second.raw_map.end();
|
||||
++tombstone_map_iter) {
|
||||
@@ -452,7 +453,7 @@ void RangeDelAggregator::AddToBuilder(
|
||||
builder->Add(ikey_and_end_key.first.Encode(), ikey_and_end_key.second);
|
||||
if (!first_added) {
|
||||
first_added = true;
|
||||
InternalKey smallest_candidate = std::move(ikey_and_end_key.first);;
|
||||
InternalKey smallest_candidate = std::move(ikey_and_end_key.first);
|
||||
if (lower_bound != nullptr &&
|
||||
icmp_.user_comparator()->Compare(smallest_candidate.user_key(),
|
||||
*lower_bound) <= 0) {
|
||||
|
||||
+8
-3
@@ -74,9 +74,11 @@ class SnapshotList {
|
||||
count_--;
|
||||
}
|
||||
|
||||
// retrieve all snapshot numbers. They are sorted in ascending order.
|
||||
// retrieve all snapshot numbers up until max_seq. They are sorted in
|
||||
// ascending order.
|
||||
std::vector<SequenceNumber> GetAll(
|
||||
SequenceNumber* oldest_write_conflict_snapshot = nullptr) {
|
||||
SequenceNumber* oldest_write_conflict_snapshot = nullptr,
|
||||
const SequenceNumber& max_seq = kMaxSequenceNumber) const {
|
||||
std::vector<SequenceNumber> ret;
|
||||
|
||||
if (oldest_write_conflict_snapshot != nullptr) {
|
||||
@@ -86,8 +88,11 @@ class SnapshotList {
|
||||
if (empty()) {
|
||||
return ret;
|
||||
}
|
||||
SnapshotImpl* s = &list_;
|
||||
const SnapshotImpl* s = &list_;
|
||||
while (s->next_ != &list_) {
|
||||
if (s->next_->number_ > max_seq) {
|
||||
break;
|
||||
}
|
||||
ret.push_back(s->next_->number_);
|
||||
|
||||
if (oldest_write_conflict_snapshot != nullptr &&
|
||||
|
||||
+75
-20
@@ -17,6 +17,7 @@
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
@@ -87,7 +88,16 @@ class VersionBuilder::Rep {
|
||||
Logger* info_log_;
|
||||
TableCache* table_cache_;
|
||||
VersionStorageInfo* base_vstorage_;
|
||||
int num_levels_;
|
||||
LevelState* levels_;
|
||||
// Store states of levels larger than num_levels_. We do this instead of
|
||||
// storing them in levels_ to avoid regression in case there are no files
|
||||
// on invalid levels. The version is not consistent if in the end the files
|
||||
// on invalid levels don't cancel out.
|
||||
std::map<int, std::unordered_set<uint64_t>> invalid_levels_;
|
||||
// Whether there are invalid new files or invalid deletion on levels larger
|
||||
// than num_levels_.
|
||||
bool has_invalid_levels_;
|
||||
FileComparator level_zero_cmp_;
|
||||
FileComparator level_nonzero_cmp_;
|
||||
|
||||
@@ -97,8 +107,10 @@ class VersionBuilder::Rep {
|
||||
: env_options_(env_options),
|
||||
info_log_(info_log),
|
||||
table_cache_(table_cache),
|
||||
base_vstorage_(base_vstorage) {
|
||||
levels_ = new LevelState[base_vstorage_->num_levels()];
|
||||
base_vstorage_(base_vstorage),
|
||||
num_levels_(base_vstorage->num_levels()),
|
||||
has_invalid_levels_(false) {
|
||||
levels_ = new LevelState[num_levels_];
|
||||
level_zero_cmp_.sort_method = FileComparator::kLevel0;
|
||||
level_nonzero_cmp_.sort_method = FileComparator::kLevelNon0;
|
||||
level_nonzero_cmp_.internal_comparator =
|
||||
@@ -106,7 +118,7 @@ class VersionBuilder::Rep {
|
||||
}
|
||||
|
||||
~Rep() {
|
||||
for (int level = 0; level < base_vstorage_->num_levels(); level++) {
|
||||
for (int level = 0; level < num_levels_; level++) {
|
||||
const auto& added = levels_[level].added_files;
|
||||
for (auto& pair : added) {
|
||||
UnrefFile(pair.second);
|
||||
@@ -137,7 +149,7 @@ class VersionBuilder::Rep {
|
||||
}
|
||||
#endif
|
||||
// make sure the files are sorted correctly
|
||||
for (int level = 0; level < vstorage->num_levels(); level++) {
|
||||
for (int level = 0; level < num_levels_; level++) {
|
||||
auto& level_files = vstorage->LevelFiles(level);
|
||||
for (size_t i = 1; i < level_files.size(); i++) {
|
||||
auto f1 = level_files[i - 1];
|
||||
@@ -196,7 +208,7 @@ class VersionBuilder::Rep {
|
||||
#endif
|
||||
// a file to be deleted better exist in the previous version
|
||||
bool found = false;
|
||||
for (int l = 0; !found && l < base_vstorage_->num_levels(); l++) {
|
||||
for (int l = 0; !found && l < num_levels_; l++) {
|
||||
const std::vector<FileMetaData*>& base_files =
|
||||
base_vstorage_->LevelFiles(l);
|
||||
for (size_t i = 0; i < base_files.size(); i++) {
|
||||
@@ -210,7 +222,7 @@ class VersionBuilder::Rep {
|
||||
// if the file did not exist in the previous version, then it
|
||||
// is possibly moved from lower level to higher level in current
|
||||
// version
|
||||
for (int l = level + 1; !found && l < base_vstorage_->num_levels(); l++) {
|
||||
for (int l = level + 1; !found && l < num_levels_; l++) {
|
||||
auto& level_added = levels_[l].added_files;
|
||||
auto got = level_added.find(number);
|
||||
if (got != level_added.end()) {
|
||||
@@ -233,6 +245,19 @@ class VersionBuilder::Rep {
|
||||
}
|
||||
}
|
||||
|
||||
bool CheckConsistencyForNumLevels() {
|
||||
// Make sure there are no files on or beyond num_levels().
|
||||
if (has_invalid_levels_) {
|
||||
return false;
|
||||
}
|
||||
for (auto& level : invalid_levels_) {
|
||||
if (level.second.size() > 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Apply all of the edits in *edit to the current state.
|
||||
void Apply(VersionEdit* edit) {
|
||||
CheckConsistency(base_vstorage_);
|
||||
@@ -242,26 +267,45 @@ class VersionBuilder::Rep {
|
||||
for (const auto& del_file : del) {
|
||||
const auto level = del_file.first;
|
||||
const auto number = del_file.second;
|
||||
levels_[level].deleted_files.insert(number);
|
||||
CheckConsistencyForDeletes(edit, number, level);
|
||||
if (level < num_levels_) {
|
||||
levels_[level].deleted_files.insert(number);
|
||||
CheckConsistencyForDeletes(edit, number, level);
|
||||
|
||||
auto exising = levels_[level].added_files.find(number);
|
||||
if (exising != levels_[level].added_files.end()) {
|
||||
UnrefFile(exising->second);
|
||||
levels_[level].added_files.erase(number);
|
||||
auto exising = levels_[level].added_files.find(number);
|
||||
if (exising != levels_[level].added_files.end()) {
|
||||
UnrefFile(exising->second);
|
||||
levels_[level].added_files.erase(number);
|
||||
}
|
||||
} else {
|
||||
if (invalid_levels_[level].count(number) > 0) {
|
||||
invalid_levels_[level].erase(number);
|
||||
} else {
|
||||
// Deleting an non-existing file on invalid level.
|
||||
has_invalid_levels_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add new files
|
||||
for (const auto& new_file : edit->GetNewFiles()) {
|
||||
const int level = new_file.first;
|
||||
FileMetaData* f = new FileMetaData(new_file.second);
|
||||
f->refs = 1;
|
||||
if (level < num_levels_) {
|
||||
FileMetaData* f = new FileMetaData(new_file.second);
|
||||
f->refs = 1;
|
||||
|
||||
assert(levels_[level].added_files.find(f->fd.GetNumber()) ==
|
||||
levels_[level].added_files.end());
|
||||
levels_[level].deleted_files.erase(f->fd.GetNumber());
|
||||
levels_[level].added_files[f->fd.GetNumber()] = f;
|
||||
assert(levels_[level].added_files.find(f->fd.GetNumber()) ==
|
||||
levels_[level].added_files.end());
|
||||
levels_[level].deleted_files.erase(f->fd.GetNumber());
|
||||
levels_[level].added_files[f->fd.GetNumber()] = f;
|
||||
} else {
|
||||
uint64_t number = new_file.second.fd.GetNumber();
|
||||
if (invalid_levels_[level].count(number) == 0) {
|
||||
invalid_levels_[level].insert(number);
|
||||
} else {
|
||||
// Creating an already existing file on invalid level.
|
||||
has_invalid_levels_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,7 +314,7 @@ class VersionBuilder::Rep {
|
||||
CheckConsistency(base_vstorage_);
|
||||
CheckConsistency(vstorage);
|
||||
|
||||
for (int level = 0; level < base_vstorage_->num_levels(); level++) {
|
||||
for (int level = 0; level < num_levels_; level++) {
|
||||
const auto& cmp = (level == 0) ? level_zero_cmp_ : level_nonzero_cmp_;
|
||||
// Merge the set of added files with the set of pre-existing files.
|
||||
// Drop any deleted files. Store the result in *v.
|
||||
@@ -325,7 +369,7 @@ class VersionBuilder::Rep {
|
||||
assert(table_cache_ != nullptr);
|
||||
// <file metadata, level>
|
||||
std::vector<std::pair<FileMetaData*, int>> files_meta;
|
||||
for (int level = 0; level < base_vstorage_->num_levels(); level++) {
|
||||
for (int level = 0; level < num_levels_; level++) {
|
||||
for (auto& file_meta_pair : levels_[level].added_files) {
|
||||
auto* file_meta = file_meta_pair.second;
|
||||
assert(!file_meta->table_reader_handle);
|
||||
@@ -386,24 +430,35 @@ VersionBuilder::VersionBuilder(const EnvOptions& env_options,
|
||||
VersionStorageInfo* base_vstorage,
|
||||
Logger* info_log)
|
||||
: rep_(new Rep(env_options, info_log, table_cache, base_vstorage)) {}
|
||||
|
||||
VersionBuilder::~VersionBuilder() { delete rep_; }
|
||||
|
||||
void VersionBuilder::CheckConsistency(VersionStorageInfo* vstorage) {
|
||||
rep_->CheckConsistency(vstorage);
|
||||
}
|
||||
|
||||
void VersionBuilder::CheckConsistencyForDeletes(VersionEdit* edit,
|
||||
uint64_t number, int level) {
|
||||
rep_->CheckConsistencyForDeletes(edit, number, level);
|
||||
}
|
||||
|
||||
bool VersionBuilder::CheckConsistencyForNumLevels() {
|
||||
return rep_->CheckConsistencyForNumLevels();
|
||||
}
|
||||
|
||||
void VersionBuilder::Apply(VersionEdit* edit) { rep_->Apply(edit); }
|
||||
|
||||
void VersionBuilder::SaveTo(VersionStorageInfo* vstorage) {
|
||||
rep_->SaveTo(vstorage);
|
||||
}
|
||||
|
||||
void VersionBuilder::LoadTableHandlers(
|
||||
InternalStats* internal_stats, int max_threads,
|
||||
bool prefetch_index_and_filter_in_cache) {
|
||||
rep_->LoadTableHandlers(internal_stats, max_threads,
|
||||
prefetch_index_and_filter_in_cache);
|
||||
}
|
||||
|
||||
void VersionBuilder::MaybeAddFile(VersionStorageInfo* vstorage, int level,
|
||||
FileMetaData* f) {
|
||||
rep_->MaybeAddFile(vstorage, level, f);
|
||||
|
||||
@@ -29,6 +29,7 @@ class VersionBuilder {
|
||||
void CheckConsistency(VersionStorageInfo* vstorage);
|
||||
void CheckConsistencyForDeletes(VersionEdit* edit, uint64_t number,
|
||||
int level);
|
||||
bool CheckConsistencyForNumLevels();
|
||||
void Apply(VersionEdit* edit);
|
||||
void SaveTo(VersionStorageInfo* vstorage);
|
||||
void LoadTableHandlers(InternalStats* internal_stats, int max_threads,
|
||||
|
||||
+12
-5
@@ -2842,11 +2842,6 @@ Status VersionSet::Recover(
|
||||
cfd = column_family_set_->GetColumnFamily(edit.column_family_);
|
||||
// this should never happen since cf_in_builders is true
|
||||
assert(cfd != nullptr);
|
||||
if (edit.max_level_ >= cfd->current()->storage_info()->num_levels()) {
|
||||
s = Status::InvalidArgument(
|
||||
"db has more levels than options.num_levels");
|
||||
break;
|
||||
}
|
||||
|
||||
// if it is not column family add or column family drop,
|
||||
// then it's a file add/delete, which should be forwarded
|
||||
@@ -2930,6 +2925,18 @@ Status VersionSet::Recover(
|
||||
list_of_not_found);
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
for (auto cfd : *column_family_set_) {
|
||||
assert(builders.count(cfd->GetID()) > 0);
|
||||
auto* builder = builders[cfd->GetID()]->version_builder();
|
||||
if (!builder->CheckConsistencyForNumLevels()) {
|
||||
s = Status::InvalidArgument(
|
||||
"db has more levels than options.num_levels");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
for (auto cfd : *column_family_set_) {
|
||||
if (cfd->IsDropped()) {
|
||||
|
||||
@@ -1303,6 +1303,7 @@ Status WriteBatchInternal::InsertInto(WriteThread::WriteGroup& write_group,
|
||||
continue;
|
||||
}
|
||||
SetSequence(w->batch, inserter.sequence());
|
||||
w->sequence = inserter.sequence();
|
||||
inserter.set_log_number_ref(w->log_ref);
|
||||
w->status = w->batch->Iterate(&inserter);
|
||||
if (!w->status.ok()) {
|
||||
|
||||
@@ -31,4 +31,4 @@ In this example we have a DB of size 86G on HDD and emulate the small memory tha
|
||||
|
||||
In this example we have a DB of size 300G on SSD and emulate the small memory that would be available in presence of other DBs on the same node by by using direct IO (skipping OS file cache) and block cache of size 6G and 2G. Without partitioning the linkbench throughput drops from 38k tps to 23k when reducing block cache size from 6G to 2G. With partitioning the throughput drops from 38k to only 30k.
|
||||
|
||||
Learn more (here)[https://github.com/facebook/rocksdb/wiki/Partitioned-Index-Filters].
|
||||
Learn more [here](https://github.com/facebook/rocksdb/wiki/Partitioned-Index-Filters).
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
title: PinnableSlice; less memcpy with point lookups
|
||||
layout: post
|
||||
author: maysamyabandeh
|
||||
category: blog
|
||||
---
|
||||
|
||||
The classic API for [DB::Get](https://github.com/facebook/rocksdb/blob/9e583711144f580390ce21a49a8ceacca338fcd5/include/rocksdb/db.h#L310) receives a std::string as argument to which it will copy the value. The memcpy overhead could be non-trivial when the value is large. The [new API](https://github.com/facebook/rocksdb/blob/9e583711144f580390ce21a49a8ceacca338fcd5/include/rocksdb/db.h#L322) receives a PinnableSlice instead, which avoids memcpy in most of the cases.
|
||||
|
||||
### What is PinnableSlice?
|
||||
|
||||
Similarly to Slice, PinnableSlice refers to some in-memory data so it does not incur the memcpy cost. To ensure that the data will not be erased while it is being processed by the user, PinnableSlice, as its name suggests, has the data pinned in memory. The pinned data are released when PinnableSlice object is destructed or when ::Reset is invoked explicitly on it.
|
||||
|
||||
### How good is it?
|
||||
|
||||
Here are the improvements in throughput for an [in-memory benchmark](https://github.com/facebook/rocksdb/pull/1756#issuecomment-286201693):
|
||||
* value 1k byte: 14%
|
||||
* value 10k byte: 34%
|
||||
|
||||
### Any limitations?
|
||||
|
||||
PinnableSlice tries to avoid memcpy as much as possible. The primary gain is when reading large values from the block cache. There are however cases that it would still have to copy the data into its internal buffer. The reason is mainly the complexity of implementation and if there is enough motivation on the application side. the scope of PinnableSlice could be extended to such cases too. These include:
|
||||
* Merged values
|
||||
* Reads from memtables
|
||||
|
||||
### How to use it?
|
||||
|
||||
```cpp
|
||||
PinnableSlice pinnable_val;
|
||||
while (!stopped) {
|
||||
auto s = db->Get(opt, cf, key, &pinnable_val);
|
||||
// ... use it
|
||||
pinnable_val.Reset(); // then release it immediately
|
||||
}
|
||||
```
|
||||
|
||||
You can also [initialize the internal buffer](https://github.com/facebook/rocksdb/blob/9e583711144f580390ce21a49a8ceacca338fcd5/include/rocksdb/db.h#L314) of PinnableSlice by passing your own string in the constructor. [simple_example.cc](https://github.com/facebook/rocksdb/blob/master/examples/simple_example.cc) demonstrates that with more examples.
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
title: FlushWAL; less fwrite, faster writes
|
||||
layout: post
|
||||
author: maysamyabandeh
|
||||
category: blog
|
||||
---
|
||||
|
||||
When `DB::Put` is called, the data is written to both memtable (to be flushed to SST files later) and the WAL (write-ahead log) if it is enabled. In the case of a crash, RocksDB can recover as much as the memtable state that is reflected into the WAL. By default RocksDB automatically flushes the WAL from the application memory to the OS buffer after each `::Put`. It however can be configured to perform the flush manually after an explicit call to `::FlushWAL`. Not doing fwrite syscall after each `::Put` offers a tradeoff between reliability and write latency for the general case. As we explain below, some applications such as MyRocks benefit from this API to gain higher write throughput with however no compromise in reliability.
|
||||
|
||||
### How much is the gain?
|
||||
|
||||
Using `::FlushWAL` API along with setting `DBOptions.concurrent_prepare`, MyRocks achieves 40% higher throughput in Sysbench's [update-nonindex](https://github.com/akopytov/sysbench/blob/master/src/lua/oltp_update_non_index.lua) benchmark.
|
||||
|
||||
### Write, Flush, and Sync
|
||||
|
||||
The write to the WAL is first written to the application memory buffer. The buffer in the next step is "flushed" to OS buffer by calling fwrite syscall. The OS buffer is later "synced" to the persistent storage. The data in the OS buffer, although not persisted yet, will survive the application crash. By default, the flush occurs automatically upon each call to `DB::Put` or `DB::Write`. The user can additionally request sync after each write by setting `WriteOptions::sync`.
|
||||
|
||||
### FlushWAL API
|
||||
|
||||
The user can turn off the automatic flush of the WAL by setting `DBOptions::manual_wal_flush`. In that case, the WAL buffer is flushed when it is either full or `DB::FlushWAL` is called by the user. The API also accepts a boolean argument should we want to sync right after the flush: `::FlushWAL(true)`.
|
||||
|
||||
### Success story: MyRocks
|
||||
|
||||
Some applications that use RocksDB, already have other machinsims in place to provide reliability. MySQL for example uses 2PC (two-phase commit) to write to both binlog as well as the storage engine such as InnoDB and MyRocks. The group commit logic in MySQL allows the 1st phase (Prepare) to be run in parallel but after a commit group is formed performs the 2nd phase (Commit) in a serial manner. This makes low commit latency in the storage engine essential for acheiving high throughput. The commit in MyRocks includes writing to the RocksDB WAL, which as explaiend above, by default incures the latency of flushing the WAL new appends to the OS buffer.
|
||||
|
||||
Since binlog helps in recovering from some failure scenarios, MySQL can provide reliability without however needing a storage WAL flush after each individual commit. MyRocks benefits from this property, disables automatic WAL flush in RocksDB, and manually calls `::FlushWAL` when requested by MySQL.
|
||||
@@ -50,6 +50,33 @@ int main() {
|
||||
db->Get(ReadOptions(), "key2", &value);
|
||||
assert(value == "value");
|
||||
|
||||
{
|
||||
PinnableSlice pinnable_val;
|
||||
db->Get(ReadOptions(), db->DefaultColumnFamily(), "key2", &pinnable_val);
|
||||
assert(pinnable_val == "value");
|
||||
}
|
||||
|
||||
{
|
||||
std::string string_val;
|
||||
// If it cannot pin the value, it copies the value to its internal buffer.
|
||||
// The intenral buffer could be set during construction.
|
||||
PinnableSlice pinnable_val(&string_val);
|
||||
db->Get(ReadOptions(), db->DefaultColumnFamily(), "key2", &pinnable_val);
|
||||
assert(pinnable_val == "value");
|
||||
// If the value is not pinned, the internal buffer must have the value.
|
||||
assert(pinnable_val.IsPinned() || string_val == "value");
|
||||
}
|
||||
|
||||
PinnableSlice pinnable_val;
|
||||
db->Get(ReadOptions(), db->DefaultColumnFamily(), "key1", &pinnable_val);
|
||||
assert(s.IsNotFound());
|
||||
// Reset PinnableSlice after each use and before each reuse
|
||||
pinnable_val.Reset();
|
||||
db->Get(ReadOptions(), db->DefaultColumnFamily(), "key2", &pinnable_val);
|
||||
assert(pinnable_val == "value");
|
||||
pinnable_val.Reset();
|
||||
// The Slice pointed by pinnable_val is not valid after this point
|
||||
|
||||
delete db;
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -117,6 +117,8 @@ typedef struct rocksdb_pinnableslice_t rocksdb_pinnableslice_t;
|
||||
typedef struct rocksdb_transactiondb_options_t rocksdb_transactiondb_options_t;
|
||||
typedef struct rocksdb_transactiondb_t rocksdb_transactiondb_t;
|
||||
typedef struct rocksdb_transaction_options_t rocksdb_transaction_options_t;
|
||||
typedef struct rocksdb_optimistictransactiondb_t rocksdb_optimistictransactiondb_t;
|
||||
typedef struct rocksdb_optimistictransaction_options_t rocksdb_optimistictransaction_options_t;
|
||||
typedef struct rocksdb_transaction_t rocksdb_transaction_t;
|
||||
typedef struct rocksdb_checkpoint_t rocksdb_checkpoint_t;
|
||||
|
||||
@@ -1290,6 +1292,10 @@ extern ROCKSDB_LIBRARY_API void rocksdb_transaction_rollback(
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_transaction_destroy(
|
||||
rocksdb_transaction_t* txn);
|
||||
|
||||
// This snapshot should be freed using rocksdb_free
|
||||
extern ROCKSDB_LIBRARY_API const rocksdb_snapshot_t*
|
||||
rocksdb_transaction_get_snapshot(rocksdb_transaction_t* txn);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API char* rocksdb_transaction_get(
|
||||
rocksdb_transaction_t* txn, const rocksdb_readoptions_t* options,
|
||||
const char* key, size_t klen, size_t* vlen, char** errptr);
|
||||
@@ -1299,6 +1305,11 @@ extern ROCKSDB_LIBRARY_API char* rocksdb_transaction_get_cf(
|
||||
rocksdb_column_family_handle_t* column_family, const char* key, size_t klen,
|
||||
size_t* vlen, char** errptr);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API char* rocksdb_transaction_get_for_update(
|
||||
rocksdb_transaction_t* txn, const rocksdb_readoptions_t* options,
|
||||
const char* key, size_t klen, size_t* vlen, unsigned char exclusive,
|
||||
char** errptr);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API char* rocksdb_transactiondb_get(
|
||||
rocksdb_transactiondb_t* txn_db, const rocksdb_readoptions_t* options,
|
||||
const char* key, size_t klen, size_t* vlen, char** errptr);
|
||||
@@ -1329,6 +1340,14 @@ extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_write(
|
||||
rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options,
|
||||
rocksdb_writebatch_t *batch, char** errptr);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_transaction_merge(
|
||||
rocksdb_transaction_t* txn, const char* key, size_t klen, const char* val,
|
||||
size_t vlen, char** errptr);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_merge(
|
||||
rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options,
|
||||
const char* key, size_t klen, const char* val, size_t vlen, char** errptr);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_transaction_delete(
|
||||
rocksdb_transaction_t* txn, const char* key, size_t klen, char** errptr);
|
||||
|
||||
@@ -1349,6 +1368,10 @@ extern ROCKSDB_LIBRARY_API rocksdb_iterator_t*
|
||||
rocksdb_transaction_create_iterator(rocksdb_transaction_t* txn,
|
||||
const rocksdb_readoptions_t* options);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_iterator_t*
|
||||
rocksdb_transactiondb_create_iterator(rocksdb_transactiondb_t* txn_db,
|
||||
const rocksdb_readoptions_t* options);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_close(
|
||||
rocksdb_transactiondb_t* txn_db);
|
||||
|
||||
@@ -1356,6 +1379,20 @@ extern ROCKSDB_LIBRARY_API rocksdb_checkpoint_t*
|
||||
rocksdb_transactiondb_checkpoint_object_create(rocksdb_transactiondb_t* txn_db,
|
||||
char** errptr);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_optimistictransactiondb_t*
|
||||
rocksdb_optimistictransactiondb_open(const rocksdb_options_t* options,
|
||||
const char* name, char** errptr);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_transaction_t*
|
||||
rocksdb_optimistictransaction_begin(
|
||||
rocksdb_optimistictransactiondb_t* otxn_db,
|
||||
const rocksdb_writeoptions_t* write_options,
|
||||
const rocksdb_optimistictransaction_options_t* otxn_options,
|
||||
rocksdb_transaction_t* old_txn);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_optimistictransactiondb_close(
|
||||
rocksdb_optimistictransactiondb_t* otxn_db);
|
||||
|
||||
/* Transaction Options */
|
||||
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_transactiondb_options_t*
|
||||
@@ -1404,6 +1441,17 @@ extern ROCKSDB_LIBRARY_API void
|
||||
rocksdb_transaction_options_set_max_write_batch_size(
|
||||
rocksdb_transaction_options_t* opt, size_t size);
|
||||
|
||||
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_optimistictransaction_options_t*
|
||||
rocksdb_optimistictransaction_options_create();
|
||||
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_optimistictransaction_options_destroy(
|
||||
rocksdb_optimistictransaction_options_t* opt);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API void
|
||||
rocksdb_optimistictransaction_options_set_set_snapshot(
|
||||
rocksdb_optimistictransaction_options_t* opt, unsigned char v);
|
||||
|
||||
// referring to convention (3), this should be used by client
|
||||
// to free memory that was malloc()ed
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_free(void* ptr);
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
||||
@@ -183,6 +183,13 @@ class MergeOperator {
|
||||
// no checking is enforced. Client is responsible for providing
|
||||
// consistent MergeOperator between DB opens.
|
||||
virtual const char* Name() const = 0;
|
||||
|
||||
// Determines whether the MergeOperator can be called with just a single
|
||||
// merge operand.
|
||||
// Override and return true for allowing a single operand. FullMergeV2 and
|
||||
// PartialMerge/PartialMergeMulti should be implemented accordingly to handle
|
||||
// a single operand.
|
||||
virtual bool AllowSingleOperand() const { return false; }
|
||||
};
|
||||
|
||||
// The simpler, associative merge operator.
|
||||
|
||||
@@ -30,6 +30,11 @@ struct PerfContext {
|
||||
uint64_t block_read_time; // total nanos spent on block reads
|
||||
uint64_t block_checksum_time; // total nanos spent on block checksum
|
||||
uint64_t block_decompress_time; // total nanos spent on block decompression
|
||||
|
||||
uint64_t get_read_bytes; // bytes for vals returned by Get
|
||||
uint64_t multiget_read_bytes; // bytes for vals returned by MultiGet
|
||||
uint64_t iter_read_bytes; // bytes for keys/vals decoded by iterator
|
||||
|
||||
// total number of internal keys skipped over during iteration.
|
||||
// There are several reasons for it:
|
||||
// 1. when calling Next(), the iterator is in the position of the previous
|
||||
|
||||
+1
-10
@@ -213,18 +213,9 @@ inline bool operator!=(const Slice& x, const Slice& y) {
|
||||
return !(x == y);
|
||||
}
|
||||
|
||||
// UBSAN complain that we pass nullptr to memcmp that's fine since
|
||||
// we always do that for a string of len = 0
|
||||
#ifdef ROCKSDB_UBSAN_RUN
|
||||
#if defined(__clang__)
|
||||
__attribute__((__no_sanitize__("undefined")))
|
||||
#elif defined(__GNUC__)
|
||||
__attribute__((__no_sanitize_undefined__))
|
||||
#endif
|
||||
#endif
|
||||
inline int Slice::compare(const Slice& b) const {
|
||||
assert(data_ != nullptr && b.data_ != nullptr);
|
||||
const size_t min_len = (size_ < b.size_) ? size_ : b.size_;
|
||||
assert((data_ != nullptr && b.data_ != nullptr) || min_len == 0);
|
||||
int r = memcmp(data_, b.data_, min_len);
|
||||
if (r == 0) {
|
||||
if (size_ < b.size_) r = -1;
|
||||
|
||||
@@ -105,8 +105,9 @@ enum Tickers : uint32_t {
|
||||
COMPACTION_KEY_DROP_OBSOLETE, // The key is obsolete.
|
||||
COMPACTION_KEY_DROP_RANGE_DEL, // key was covered by a range tombstone.
|
||||
COMPACTION_KEY_DROP_USER, // user compaction function has dropped the key.
|
||||
|
||||
COMPACTION_RANGE_DEL_DROP_OBSOLETE, // all keys in range were deleted.
|
||||
// Deletions obsoleted before bottom level due to file gap optimization.
|
||||
COMPACTION_OPTIMIZED_DEL_DROP_OBSOLETE,
|
||||
|
||||
// Number of keys written to the database via the Put and Write call's
|
||||
NUMBER_KEYS_WRITTEN,
|
||||
@@ -264,7 +265,9 @@ const std::vector<std::pair<Tickers, std::string>> TickersNameMap = {
|
||||
{COMPACTION_KEY_DROP_RANGE_DEL, "rocksdb.compaction.key.drop.range_del"},
|
||||
{COMPACTION_KEY_DROP_USER, "rocksdb.compaction.key.drop.user"},
|
||||
{COMPACTION_RANGE_DEL_DROP_OBSOLETE,
|
||||
"rocksdb.compaction.range_del.drop.obsolete"},
|
||||
"rocksdb.compaction.range_del.drop.obsolete"},
|
||||
{COMPACTION_OPTIMIZED_DEL_DROP_OBSOLETE,
|
||||
"rocksdb.compaction.optimized.del.drop.obsolete"},
|
||||
{NUMBER_KEYS_WRITTEN, "rocksdb.number.keys.written"},
|
||||
{NUMBER_KEYS_READ, "rocksdb.number.keys.read"},
|
||||
{NUMBER_KEYS_UPDATED, "rocksdb.number.keys.updated"},
|
||||
|
||||
@@ -43,7 +43,7 @@ struct Options;
|
||||
using std::unique_ptr;
|
||||
|
||||
enum ChecksumType : char {
|
||||
kNoChecksum = 0x0, // not yet supported. Will fail
|
||||
kNoChecksum = 0x0,
|
||||
kCRC32c = 0x1,
|
||||
kxxHash = 0x2,
|
||||
};
|
||||
|
||||
@@ -169,8 +169,26 @@ class Transaction {
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
std::string* value) = 0;
|
||||
|
||||
// An overload of the the above method that receives a PinnableSlice
|
||||
// For backward compatiblity a default implementation is provided
|
||||
virtual Status Get(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* pinnable_val) {
|
||||
assert(pinnable_val != nullptr);
|
||||
auto s = Get(options, column_family, key, pinnable_val->GetSelf());
|
||||
pinnable_val->PinSelf();
|
||||
return s;
|
||||
}
|
||||
|
||||
virtual Status Get(const ReadOptions& options, const Slice& key,
|
||||
std::string* value) = 0;
|
||||
virtual Status Get(const ReadOptions& options, const Slice& key,
|
||||
PinnableSlice* pinnable_val) {
|
||||
assert(pinnable_val != nullptr);
|
||||
auto s = Get(options, key, pinnable_val->GetSelf());
|
||||
pinnable_val->PinSelf();
|
||||
return s;
|
||||
}
|
||||
|
||||
virtual std::vector<Status> MultiGet(
|
||||
const ReadOptions& options,
|
||||
@@ -212,6 +230,22 @@ class Transaction {
|
||||
const Slice& key, std::string* value,
|
||||
bool exclusive = true) = 0;
|
||||
|
||||
// An overload of the the above method that receives a PinnableSlice
|
||||
// For backward compatiblity a default implementation is provided
|
||||
virtual Status GetForUpdate(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice& key, PinnableSlice* pinnable_val,
|
||||
bool exclusive = true) {
|
||||
if (pinnable_val == nullptr) {
|
||||
std::string* null_str = nullptr;
|
||||
return GetForUpdate(options, key, null_str);
|
||||
} else {
|
||||
auto s = GetForUpdate(options, key, pinnable_val->GetSelf());
|
||||
pinnable_val->PinSelf();
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
virtual Status GetForUpdate(const ReadOptions& options, const Slice& key,
|
||||
std::string* value, bool exclusive = true) = 0;
|
||||
|
||||
|
||||
@@ -25,10 +25,14 @@ class TransactionDBMutexFactory;
|
||||
|
||||
enum TxnDBWritePolicy {
|
||||
WRITE_COMMITTED = 0, // write only the committed data
|
||||
WRITE_PREPARED, // write data after the prepare phase of 2pc
|
||||
WRITE_UNPREPARED // write data before the prepare phase of 2pc
|
||||
// TODO(myabandeh): Not implemented yet
|
||||
WRITE_PREPARED, // write data after the prepare phase of 2pc
|
||||
// TODO(myabandeh): Not implemented yet
|
||||
WRITE_UNPREPARED // write data before the prepare phase of 2pc
|
||||
};
|
||||
|
||||
const uint32_t kInitialMaxDeadlocks = 5;
|
||||
|
||||
struct TransactionDBOptions {
|
||||
// Specifies the maximum number of keys that can be locked at the same time
|
||||
// per column family.
|
||||
@@ -37,6 +41,9 @@ struct TransactionDBOptions {
|
||||
// If this value is not positive, no limit will be enforced.
|
||||
int64_t max_num_locks = -1;
|
||||
|
||||
// Stores the number of latest deadlocks to track
|
||||
uint32_t max_num_deadlocks = kInitialMaxDeadlocks;
|
||||
|
||||
// Increasing this value will increase the concurrency by dividing the lock
|
||||
// table (per column family) into more sub-tables, each with their own
|
||||
// separate
|
||||
@@ -123,6 +130,26 @@ struct KeyLockInfo {
|
||||
bool exclusive;
|
||||
};
|
||||
|
||||
struct DeadlockInfo {
|
||||
TransactionID m_txn_id;
|
||||
uint32_t m_cf_id;
|
||||
std::string m_waiting_key;
|
||||
bool m_exclusive;
|
||||
};
|
||||
|
||||
struct DeadlockPath {
|
||||
std::vector<DeadlockInfo> path;
|
||||
bool limit_exceeded;
|
||||
|
||||
explicit DeadlockPath(std::vector<DeadlockInfo> path_entry)
|
||||
: path(path_entry), limit_exceeded(false) {}
|
||||
|
||||
// empty path, limit exceeded constructor and default constructor
|
||||
explicit DeadlockPath(bool limit = false) : path(0), limit_exceeded(limit) {}
|
||||
|
||||
bool empty() { return path.empty() && !limit_exceeded; }
|
||||
};
|
||||
|
||||
class TransactionDB : public StackableDB {
|
||||
public:
|
||||
// Open a TransactionDB similar to DB::Open().
|
||||
@@ -181,6 +208,8 @@ class TransactionDB : public StackableDB {
|
||||
// The mapping is column family id -> KeyLockInfo
|
||||
virtual std::unordered_multimap<uint32_t, KeyLockInfo>
|
||||
GetLockStatusData() = 0;
|
||||
virtual std::vector<DeadlockPath> GetDeadlockInfoBuffer() = 0;
|
||||
virtual void SetDeadlockInfoBufferSize(uint32_t target_size) = 0;
|
||||
|
||||
protected:
|
||||
// To Create an TransactionDB, call Open()
|
||||
|
||||
@@ -186,10 +186,20 @@ class WriteBatchWithIndex : public WriteBatchBase {
|
||||
// regardless).
|
||||
Status GetFromBatchAndDB(DB* db, const ReadOptions& read_options,
|
||||
const Slice& key, std::string* value);
|
||||
|
||||
// An overload of the the above method that receives a PinnableSlice
|
||||
Status GetFromBatchAndDB(DB* db, const ReadOptions& read_options,
|
||||
const Slice& key, PinnableSlice* value);
|
||||
|
||||
Status GetFromBatchAndDB(DB* db, const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
std::string* value);
|
||||
|
||||
// An overload of the the above method that receives a PinnableSlice
|
||||
Status GetFromBatchAndDB(DB* db, const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value);
|
||||
|
||||
// Records the state of the batch for future calls to RollbackToSavePoint().
|
||||
// May be called multiple times to set multiple save points.
|
||||
void SetSavePoint() override;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#define ROCKSDB_MAJOR 5
|
||||
#define ROCKSDB_MINOR 7
|
||||
#define ROCKSDB_MINOR 8
|
||||
#define ROCKSDB_PATCH 0
|
||||
|
||||
// Do not use these. We made the mistake of declaring macros starting with
|
||||
|
||||
@@ -77,6 +77,7 @@ set(NATIVE_JAVA_CLASSES
|
||||
org.rocksdb.FlushOptions
|
||||
org.rocksdb.HashLinkedListMemTableConfig
|
||||
org.rocksdb.HashSkipListMemTableConfig
|
||||
org.rocksdb.IngestExternalFileOptions
|
||||
org.rocksdb.Logger
|
||||
org.rocksdb.LRUCache
|
||||
org.rocksdb.MemTableConfig
|
||||
|
||||
@@ -77,9 +77,9 @@ void Java_org_rocksdb_SstFileWriter_open(JNIEnv *env, jobject jobj,
|
||||
* Method: put
|
||||
* Signature: (JJJ)V
|
||||
*/
|
||||
void Java_org_rocksdb_SstFileWriter_put(JNIEnv *env, jobject jobj,
|
||||
jlong jhandle, jlong jkey_handle,
|
||||
jlong jvalue_handle) {
|
||||
void Java_org_rocksdb_SstFileWriter_put__JJJ(JNIEnv *env, jobject jobj,
|
||||
jlong jhandle, jlong jkey_handle,
|
||||
jlong jvalue_handle) {
|
||||
auto *key_slice = reinterpret_cast<rocksdb::Slice *>(jkey_handle);
|
||||
auto *value_slice = reinterpret_cast<rocksdb::Slice *>(jvalue_handle);
|
||||
rocksdb::Status s =
|
||||
@@ -90,14 +90,51 @@ void Java_org_rocksdb_SstFileWriter_put(JNIEnv *env, jobject jobj,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_SstFileWriter
|
||||
* Method: put
|
||||
* Signature: (JJJ)V
|
||||
*/
|
||||
void Java_org_rocksdb_SstFileWriter_put__J_3B_3B(JNIEnv *env, jobject jobj,
|
||||
jlong jhandle, jbyteArray jkey,
|
||||
jbyteArray jval) {
|
||||
jbyte* key = env->GetByteArrayElements(jkey, nullptr);
|
||||
if(key == nullptr) {
|
||||
// exception thrown: OutOfMemoryError
|
||||
return;
|
||||
}
|
||||
rocksdb::Slice key_slice(
|
||||
reinterpret_cast<char*>(key), env->GetArrayLength(jkey));
|
||||
|
||||
jbyte* value = env->GetByteArrayElements(jval, nullptr);
|
||||
if(value == nullptr) {
|
||||
// exception thrown: OutOfMemoryError
|
||||
env->ReleaseByteArrayElements(jkey, key, JNI_ABORT);
|
||||
return;
|
||||
}
|
||||
rocksdb::Slice value_slice(
|
||||
reinterpret_cast<char*>(value), env->GetArrayLength(jval));
|
||||
|
||||
rocksdb::Status s =
|
||||
reinterpret_cast<rocksdb::SstFileWriter *>(jhandle)->Put(key_slice,
|
||||
value_slice);
|
||||
|
||||
env->ReleaseByteArrayElements(jkey, key, JNI_ABORT);
|
||||
env->ReleaseByteArrayElements(jval, value, JNI_ABORT);
|
||||
|
||||
if (!s.ok()) {
|
||||
rocksdb::RocksDBExceptionJni::ThrowNew(env, s);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_SstFileWriter
|
||||
* Method: merge
|
||||
* Signature: (JJJ)V
|
||||
*/
|
||||
void Java_org_rocksdb_SstFileWriter_merge(JNIEnv *env, jobject jobj,
|
||||
jlong jhandle, jlong jkey_handle,
|
||||
jlong jvalue_handle) {
|
||||
void Java_org_rocksdb_SstFileWriter_merge__JJJ(JNIEnv *env, jobject jobj,
|
||||
jlong jhandle, jlong jkey_handle,
|
||||
jlong jvalue_handle) {
|
||||
auto *key_slice = reinterpret_cast<rocksdb::Slice *>(jkey_handle);
|
||||
auto *value_slice = reinterpret_cast<rocksdb::Slice *>(jvalue_handle);
|
||||
rocksdb::Status s =
|
||||
@@ -108,13 +145,76 @@ void Java_org_rocksdb_SstFileWriter_merge(JNIEnv *env, jobject jobj,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_SstFileWriter
|
||||
* Method: merge
|
||||
* Signature: (J[B[B)V
|
||||
*/
|
||||
void Java_org_rocksdb_SstFileWriter_merge__J_3B_3B(JNIEnv *env, jobject jobj,
|
||||
jlong jhandle, jbyteArray jkey,
|
||||
jbyteArray jval) {
|
||||
|
||||
jbyte* key = env->GetByteArrayElements(jkey, nullptr);
|
||||
if(key == nullptr) {
|
||||
// exception thrown: OutOfMemoryError
|
||||
return;
|
||||
}
|
||||
rocksdb::Slice key_slice(
|
||||
reinterpret_cast<char*>(key), env->GetArrayLength(jkey));
|
||||
|
||||
jbyte* value = env->GetByteArrayElements(jval, nullptr);
|
||||
if(value == nullptr) {
|
||||
// exception thrown: OutOfMemoryError
|
||||
env->ReleaseByteArrayElements(jkey, key, JNI_ABORT);
|
||||
return;
|
||||
}
|
||||
rocksdb::Slice value_slice(
|
||||
reinterpret_cast<char*>(value), env->GetArrayLength(jval));
|
||||
|
||||
rocksdb::Status s =
|
||||
reinterpret_cast<rocksdb::SstFileWriter *>(jhandle)->Merge(key_slice,
|
||||
value_slice);
|
||||
|
||||
env->ReleaseByteArrayElements(jkey, key, JNI_ABORT);
|
||||
env->ReleaseByteArrayElements(jval, value, JNI_ABORT);
|
||||
|
||||
if (!s.ok()) {
|
||||
rocksdb::RocksDBExceptionJni::ThrowNew(env, s);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_SstFileWriter
|
||||
* Method: delete
|
||||
* Signature: (JJJ)V
|
||||
*/
|
||||
void Java_org_rocksdb_SstFileWriter_delete(JNIEnv *env, jobject jobj,
|
||||
jlong jhandle, jlong jkey_handle) {
|
||||
void Java_org_rocksdb_SstFileWriter_delete__J_3B(JNIEnv *env, jobject jobj,
|
||||
jlong jhandle, jbyteArray jkey) {
|
||||
jbyte* key = env->GetByteArrayElements(jkey, nullptr);
|
||||
if(key == nullptr) {
|
||||
// exception thrown: OutOfMemoryError
|
||||
return;
|
||||
}
|
||||
rocksdb::Slice key_slice(
|
||||
reinterpret_cast<char*>(key), env->GetArrayLength(jkey));
|
||||
|
||||
rocksdb::Status s =
|
||||
reinterpret_cast<rocksdb::SstFileWriter *>(jhandle)->Delete(key_slice);
|
||||
|
||||
env->ReleaseByteArrayElements(jkey, key, JNI_ABORT);
|
||||
|
||||
if (!s.ok()) {
|
||||
rocksdb::RocksDBExceptionJni::ThrowNew(env, s);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_SstFileWriter
|
||||
* Method: delete
|
||||
* Signature: (JJJ)V
|
||||
*/
|
||||
void Java_org_rocksdb_SstFileWriter_delete__JJ(JNIEnv *env, jobject jobj,
|
||||
jlong jhandle, jlong jkey_handle) {
|
||||
auto *key_slice = reinterpret_cast<rocksdb::Slice *>(jkey_handle);
|
||||
rocksdb::Status s =
|
||||
reinterpret_cast<rocksdb::SstFileWriter *>(jhandle)->Delete(*key_slice);
|
||||
|
||||
@@ -117,6 +117,20 @@ public class SstFileWriter extends RocksObject {
|
||||
put(nativeHandle_, key.getNativeHandle(), value.getNativeHandle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a Put key with value to currently opened file.
|
||||
*
|
||||
* @param key the specified key to be inserted.
|
||||
* @param value the value associated with the specified key.
|
||||
*
|
||||
* @throws RocksDBException thrown if error happens in underlying
|
||||
* native library.
|
||||
*/
|
||||
public void put(final byte[] key, final byte[] value)
|
||||
throws RocksDBException {
|
||||
put(nativeHandle_, key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a Merge key with value to currently opened file.
|
||||
*
|
||||
@@ -132,6 +146,21 @@ public class SstFileWriter extends RocksObject {
|
||||
merge(nativeHandle_, key.getNativeHandle(), value.getNativeHandle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a Merge key with value to currently opened file.
|
||||
*
|
||||
* @param key the specified key to be merged.
|
||||
* @param value the value to be merged with the current value for
|
||||
* the specified key.
|
||||
*
|
||||
* @throws RocksDBException thrown if error happens in underlying
|
||||
* native library.
|
||||
*/
|
||||
public void merge(final byte[] key, final byte[] value)
|
||||
throws RocksDBException {
|
||||
merge(nativeHandle_, key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a Merge key with value to currently opened file.
|
||||
*
|
||||
@@ -171,6 +200,18 @@ public class SstFileWriter extends RocksObject {
|
||||
delete(nativeHandle_, key.getNativeHandle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a deletion key to currently opened file.
|
||||
*
|
||||
* @param key the specified key to be deleted.
|
||||
*
|
||||
* @throws RocksDBException thrown if error happens in underlying
|
||||
* native library.
|
||||
*/
|
||||
public void delete(final byte[] key) throws RocksDBException {
|
||||
delete(nativeHandle_, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish the process and close the sst file.
|
||||
*
|
||||
@@ -193,13 +234,22 @@ public class SstFileWriter extends RocksObject {
|
||||
|
||||
private native void put(final long handle, final long keyHandle,
|
||||
final long valueHandle) throws RocksDBException;
|
||||
|
||||
private native void put(final long handle, final byte[] key,
|
||||
final byte[] value) throws RocksDBException;
|
||||
|
||||
private native void merge(final long handle, final long keyHandle,
|
||||
final long valueHandle) throws RocksDBException;
|
||||
|
||||
private native void merge(final long handle, final byte[] key,
|
||||
final byte[] value) throws RocksDBException;
|
||||
|
||||
private native void delete(final long handle, final long keyHandle)
|
||||
throws RocksDBException;
|
||||
|
||||
private native void delete(final long handle, final byte[] key)
|
||||
throws RocksDBException;
|
||||
|
||||
private native void finish(final long handle) throws RocksDBException;
|
||||
|
||||
@Override protected final native void disposeInternal(final long handle);
|
||||
|
||||
@@ -30,7 +30,7 @@ public class SstFileWriterTest {
|
||||
|
||||
@Rule public TemporaryFolder parentFolder = new TemporaryFolder();
|
||||
|
||||
enum OpType { PUT, MERGE, DELETE }
|
||||
enum OpType { PUT, PUT_BYTES, MERGE, MERGE_BYTES, DELETE, DELETE_BYTES}
|
||||
|
||||
class KeyValueWithOp {
|
||||
KeyValueWithOp(String key, String value, OpType opType) {
|
||||
@@ -79,16 +79,27 @@ public class SstFileWriterTest {
|
||||
for (KeyValueWithOp keyValue : keyValues) {
|
||||
Slice keySlice = new Slice(keyValue.getKey());
|
||||
Slice valueSlice = new Slice(keyValue.getValue());
|
||||
byte[] keyBytes = keyValue.getKey().getBytes();
|
||||
byte[] valueBytes = keyValue.getValue().getBytes();
|
||||
switch (keyValue.getOpType()) {
|
||||
case PUT:
|
||||
sstFileWriter.put(keySlice, valueSlice);
|
||||
break;
|
||||
case PUT_BYTES:
|
||||
sstFileWriter.put(keyBytes, valueBytes);
|
||||
break;
|
||||
case MERGE:
|
||||
sstFileWriter.merge(keySlice, valueSlice);
|
||||
break;
|
||||
case MERGE_BYTES:
|
||||
sstFileWriter.merge(keyBytes, valueBytes);
|
||||
break;
|
||||
case DELETE:
|
||||
sstFileWriter.delete(keySlice);
|
||||
break;
|
||||
case DELETE_BYTES:
|
||||
sstFileWriter.delete(keyBytes);
|
||||
break;
|
||||
default:
|
||||
fail("Unsupported op type");
|
||||
}
|
||||
@@ -142,8 +153,12 @@ public class SstFileWriterTest {
|
||||
final List<KeyValueWithOp> keyValues = new ArrayList<>();
|
||||
keyValues.add(new KeyValueWithOp("key1", "value1", OpType.PUT));
|
||||
keyValues.add(new KeyValueWithOp("key2", "value2", OpType.PUT));
|
||||
keyValues.add(new KeyValueWithOp("key3", "value3", OpType.MERGE));
|
||||
keyValues.add(new KeyValueWithOp("key4", "", OpType.DELETE));
|
||||
keyValues.add(new KeyValueWithOp("key3", "value3", OpType.PUT_BYTES));
|
||||
keyValues.add(new KeyValueWithOp("key4", "value4", OpType.MERGE));
|
||||
keyValues.add(new KeyValueWithOp("key5", "value5", OpType.MERGE_BYTES));
|
||||
keyValues.add(new KeyValueWithOp("key6", "", OpType.DELETE));
|
||||
keyValues.add(new KeyValueWithOp("key7", "", OpType.DELETE));
|
||||
|
||||
|
||||
final File sstFile = newSstFile(keyValues, false);
|
||||
final File dbFolder = parentFolder.newFolder(DB_DIRECTORY_NAME);
|
||||
@@ -161,7 +176,10 @@ public class SstFileWriterTest {
|
||||
assertThat(db.get("key1".getBytes())).isEqualTo("value1".getBytes());
|
||||
assertThat(db.get("key2".getBytes())).isEqualTo("value2".getBytes());
|
||||
assertThat(db.get("key3".getBytes())).isEqualTo("value3".getBytes());
|
||||
assertThat(db.get("key4".getBytes())).isEqualTo(null);
|
||||
assertThat(db.get("key4".getBytes())).isEqualTo("value4".getBytes());
|
||||
assertThat(db.get("key5".getBytes())).isEqualTo("value5".getBytes());
|
||||
assertThat(db.get("key6".getBytes())).isEqualTo(null);
|
||||
assertThat(db.get("key7".getBytes())).isEqualTo(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+19
-34
@@ -23,41 +23,26 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
HistogramBucketMapper::HistogramBucketMapper()
|
||||
:
|
||||
// Add newer bucket index here.
|
||||
// Should be always added in sorted order.
|
||||
// If you change this, you also need to change
|
||||
// size of array buckets_ in HistogramImpl
|
||||
bucketValues_(
|
||||
{1, 2, 3, 4, 5, 6,
|
||||
7, 8, 9, 10, 12, 14,
|
||||
16, 18, 20, 25, 30, 35,
|
||||
40, 45, 50, 60, 70, 80,
|
||||
90, 100, 120, 140, 160, 180,
|
||||
200, 250, 300, 350, 400, 450,
|
||||
500, 600, 700, 800, 900, 1000,
|
||||
1200, 1400, 1600, 1800, 2000, 2500,
|
||||
3000, 3500, 4000, 4500, 5000, 6000,
|
||||
7000, 8000, 9000, 10000, 12000, 14000,
|
||||
16000, 18000, 20000, 25000, 30000, 35000,
|
||||
40000, 45000, 50000, 60000, 70000, 80000,
|
||||
90000, 100000, 120000, 140000, 160000, 180000,
|
||||
200000, 250000, 300000, 350000, 400000, 450000,
|
||||
500000, 600000, 700000, 800000, 900000, 1000000,
|
||||
1200000, 1400000, 1600000, 1800000, 2000000, 2500000,
|
||||
3000000, 3500000, 4000000, 4500000, 5000000, 6000000,
|
||||
7000000, 8000000, 9000000, 10000000, 12000000, 14000000,
|
||||
16000000, 18000000, 20000000, 25000000, 30000000, 35000000,
|
||||
40000000, 45000000, 50000000, 60000000, 70000000, 80000000,
|
||||
90000000, 100000000, 120000000, 140000000, 160000000, 180000000,
|
||||
200000000, 250000000, 300000000, 350000000, 400000000, 450000000,
|
||||
500000000, 600000000, 700000000, 800000000, 900000000, 1000000000}),
|
||||
maxBucketValue_(bucketValues_.back()),
|
||||
minBucketValue_(bucketValues_.front()) {
|
||||
for (size_t i =0; i < bucketValues_.size(); ++i) {
|
||||
valueIndexMap_[bucketValues_[i]] = i;
|
||||
HistogramBucketMapper::HistogramBucketMapper() {
|
||||
// If you change this, you also need to change
|
||||
// size of array buckets_ in HistogramImpl
|
||||
bucketValues_ = {1, 2};
|
||||
valueIndexMap_ = {{1, 0}, {2, 1}};
|
||||
double bucket_val = static_cast<double>(bucketValues_.back());
|
||||
while ((bucket_val = 1.5 * bucket_val) <= static_cast<double>(port::kMaxUint64)) {
|
||||
bucketValues_.push_back(static_cast<uint64_t>(bucket_val));
|
||||
// Extracts two most significant digits to make histogram buckets more
|
||||
// human-readable. E.g., 172 becomes 170.
|
||||
uint64_t pow_of_ten = 1;
|
||||
while (bucketValues_.back() / 10 > 10) {
|
||||
bucketValues_.back() /= 10;
|
||||
pow_of_ten *= 10;
|
||||
}
|
||||
bucketValues_.back() *= pow_of_ten;
|
||||
valueIndexMap_[bucketValues_.back()] = bucketValues_.size() - 1;
|
||||
}
|
||||
maxBucketValue_ = bucketValues_.back();
|
||||
minBucketValue_ = bucketValues_.front();
|
||||
}
|
||||
|
||||
size_t HistogramBucketMapper::IndexForValue(const uint64_t value) const {
|
||||
|
||||
@@ -45,9 +45,9 @@ class HistogramBucketMapper {
|
||||
}
|
||||
|
||||
private:
|
||||
const std::vector<uint64_t> bucketValues_;
|
||||
const uint64_t maxBucketValue_;
|
||||
const uint64_t minBucketValue_;
|
||||
std::vector<uint64_t> bucketValues_;
|
||||
uint64_t maxBucketValue_;
|
||||
uint64_t minBucketValue_;
|
||||
std::map<uint64_t, uint64_t> valueIndexMap_;
|
||||
};
|
||||
|
||||
@@ -89,7 +89,7 @@ struct HistogramStat {
|
||||
std::atomic_uint_fast64_t num_;
|
||||
std::atomic_uint_fast64_t sum_;
|
||||
std::atomic_uint_fast64_t sum_squares_;
|
||||
std::atomic_uint_fast64_t buckets_[138]; // 138==BucketMapper::BucketCount()
|
||||
std::atomic_uint_fast64_t buckets_[109]; // 109==BucketMapper::BucketCount()
|
||||
const uint64_t num_buckets_;
|
||||
};
|
||||
|
||||
@@ -146,4 +146,4 @@ class HistogramImpl : public Histogram {
|
||||
std::mutex mutex_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
} // namespace rocksdb
|
||||
@@ -29,33 +29,31 @@ void PopulateHistogram(Histogram& histogram,
|
||||
}
|
||||
|
||||
void BasicOperation(Histogram& histogram) {
|
||||
PopulateHistogram(histogram, 1, 100, 10);
|
||||
PopulateHistogram(histogram, 1, 110, 10); // fill up to bucket [70, 110)
|
||||
|
||||
HistogramData data;
|
||||
histogram.Data(&data);
|
||||
|
||||
ASSERT_LE(fabs(histogram.Percentile(100.0) - 100.0), kIota);
|
||||
ASSERT_LE(fabs(data.percentile99 - 99.0), kIota);
|
||||
ASSERT_LE(fabs(data.percentile95 - 95.0), kIota);
|
||||
ASSERT_LE(fabs(data.median - 50.0), kIota);
|
||||
ASSERT_EQ(data.average, 50.5); // avg is acurately calculated.
|
||||
ASSERT_LT(fabs(data.standard_deviation- 28.86), kIota); //sd is ~= 28.86
|
||||
ASSERT_LE(fabs(histogram.Percentile(100.0) - 110.0), kIota);
|
||||
ASSERT_LE(fabs(data.percentile99 - 108.9), kIota); // 99 * 110 / 100
|
||||
ASSERT_LE(fabs(data.percentile95 - 104.5), kIota); // 95 * 110 / 100
|
||||
ASSERT_LE(fabs(data.median - 55.0), kIota); // 50 * 110 / 100
|
||||
ASSERT_EQ(data.average, 55.5); // (1 + 110) / 2
|
||||
}
|
||||
|
||||
void MergeHistogram(Histogram& histogram, Histogram& other) {
|
||||
PopulateHistogram(histogram, 1, 100);
|
||||
PopulateHistogram(other, 101, 200);
|
||||
PopulateHistogram(other, 101, 250);
|
||||
histogram.Merge(other);
|
||||
|
||||
HistogramData data;
|
||||
histogram.Data(&data);
|
||||
|
||||
ASSERT_LE(fabs(histogram.Percentile(100.0) - 200.0), kIota);
|
||||
ASSERT_LE(fabs(data.percentile99 - 198.0), kIota);
|
||||
ASSERT_LE(fabs(data.percentile95 - 190.0), kIota);
|
||||
ASSERT_LE(fabs(data.median - 100.0), kIota);
|
||||
ASSERT_EQ(data.average, 100.5); // avg is acurately calculated.
|
||||
ASSERT_LT(fabs(data.standard_deviation - 57.73), kIota); //sd is ~= 57.73
|
||||
ASSERT_LE(fabs(histogram.Percentile(100.0) - 250.0), kIota);
|
||||
ASSERT_LE(fabs(data.percentile99 - 247.5), kIota); // 99 * 250 / 100
|
||||
ASSERT_LE(fabs(data.percentile95 - 237.5), kIota); // 95 * 250 / 100
|
||||
ASSERT_LE(fabs(data.median - 125.0), kIota); // 50 * 250 / 100
|
||||
ASSERT_EQ(data.average, 125.5); // (1 + 250) / 2
|
||||
}
|
||||
|
||||
void EmptyHistogram(Histogram& histogram) {
|
||||
|
||||
@@ -40,6 +40,9 @@ void PerfContext::Reset() {
|
||||
block_read_time = 0;
|
||||
block_checksum_time = 0;
|
||||
block_decompress_time = 0;
|
||||
get_read_bytes = 0;
|
||||
multiget_read_bytes = 0;
|
||||
iter_read_bytes = 0;
|
||||
internal_key_skipped_count = 0;
|
||||
internal_delete_skipped_count = 0;
|
||||
internal_recent_skipped_count = 0;
|
||||
@@ -117,6 +120,9 @@ std::string PerfContext::ToString(bool exclude_zero_counters) const {
|
||||
PERF_CONTEXT_OUTPUT(block_read_time);
|
||||
PERF_CONTEXT_OUTPUT(block_checksum_time);
|
||||
PERF_CONTEXT_OUTPUT(block_decompress_time);
|
||||
PERF_CONTEXT_OUTPUT(get_read_bytes);
|
||||
PERF_CONTEXT_OUTPUT(multiget_read_bytes);
|
||||
PERF_CONTEXT_OUTPUT(iter_read_bytes);
|
||||
PERF_CONTEXT_OUTPUT(internal_key_skipped_count);
|
||||
PERF_CONTEXT_OUTPUT(internal_delete_skipped_count);
|
||||
PERF_CONTEXT_OUTPUT(internal_recent_skipped_count);
|
||||
|
||||
@@ -83,7 +83,7 @@ struct ImmutableCFOptions {
|
||||
bool advise_random_on_open;
|
||||
|
||||
// This options is required by PlainTableReader. May need to move it
|
||||
// to PlainTalbeOptions just like bloom_bits_per_key
|
||||
// to PlainTableOptions just like bloom_bits_per_key
|
||||
uint32_t bloom_locality;
|
||||
|
||||
bool purge_redundant_kvs_while_flush;
|
||||
|
||||
+3
-1
@@ -185,7 +185,9 @@ int GetMaxOpenFiles() {
|
||||
}
|
||||
|
||||
void *cacheline_aligned_alloc(size_t size) {
|
||||
#if defined (_ISOC11_SOURCE)
|
||||
#if __GNUC__ < 5 && defined(__SANITIZE_ADDRESS__)
|
||||
return malloc(size);
|
||||
#elif defined(_ISOC11_SOURCE)
|
||||
return aligned_alloc(CACHE_LINE_SIZE, size);
|
||||
#elif ( _POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600 || defined(__APPLE__))
|
||||
void *m;
|
||||
|
||||
@@ -551,9 +551,8 @@ void BlockBasedTableBuilder::WriteRawBlock(const Slice& block_contents,
|
||||
char* trailer_without_type = trailer + 1;
|
||||
switch (r->table_options.checksum) {
|
||||
case kNoChecksum:
|
||||
// we don't support no checksum yet
|
||||
assert(false);
|
||||
// intentional fallthrough
|
||||
EncodeFixed32(trailer_without_type, 0);
|
||||
break;
|
||||
case kCRC32c: {
|
||||
auto crc = crc32c::Value(block_contents.data(), block_contents.size());
|
||||
crc = crc32c::Extend(crc, trailer, 1); // Extend to cover block type
|
||||
|
||||
@@ -186,23 +186,88 @@ class PartitionIndexReader : public IndexReader, public Cleanable {
|
||||
// Filters are already checked before seeking the index
|
||||
const bool skip_filters = true;
|
||||
const bool is_index = true;
|
||||
Cleanable* block_cache_cleaner = nullptr;
|
||||
const bool pin_cached_indexes =
|
||||
level_ == 0 &&
|
||||
table_->rep_->table_options.pin_l0_filter_and_index_blocks_in_cache;
|
||||
if (pin_cached_indexes) {
|
||||
// Keep partition indexes into the cache as long as the partition index
|
||||
// reader object is alive
|
||||
block_cache_cleaner = this;
|
||||
}
|
||||
return NewTwoLevelIterator(
|
||||
new BlockBasedTable::BlockEntryIteratorState(
|
||||
table_, ReadOptions(), icomparator_, skip_filters, is_index,
|
||||
block_cache_cleaner),
|
||||
partition_map_.size() ? &partition_map_ : nullptr),
|
||||
index_block_->NewIterator(icomparator_, nullptr, true));
|
||||
// TODO(myabandeh): Update TwoLevelIterator to be able to make use of
|
||||
// on-stack
|
||||
// BlockIter while the state is on heap
|
||||
// on-stack BlockIter while the state is on heap. Currentlly it assumes
|
||||
// the first level iter is always on heap and will attempt to delete it
|
||||
// in its destructor.
|
||||
}
|
||||
|
||||
virtual void CacheDependencies(bool pin) override {
|
||||
// Before read partitions, prefetch them to avoid lots of IOs
|
||||
auto rep = table_->rep_;
|
||||
BlockIter biter;
|
||||
BlockHandle handle;
|
||||
index_block_->NewIterator(icomparator_, &biter, true);
|
||||
// Index partitions are assumed to be consecuitive. Prefetch them all.
|
||||
// Read the first block offset
|
||||
biter.SeekToFirst();
|
||||
Slice input = biter.value();
|
||||
Status s = handle.DecodeFrom(&input);
|
||||
assert(s.ok());
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(rep->ioptions.info_log,
|
||||
"Could not read first index partition");
|
||||
return;
|
||||
}
|
||||
uint64_t prefetch_off = handle.offset();
|
||||
|
||||
// Read the last block's offset
|
||||
biter.SeekToLast();
|
||||
input = biter.value();
|
||||
s = handle.DecodeFrom(&input);
|
||||
assert(s.ok());
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(rep->ioptions.info_log,
|
||||
"Could not read last index partition");
|
||||
return;
|
||||
}
|
||||
uint64_t last_off = handle.offset() + handle.size() + kBlockTrailerSize;
|
||||
uint64_t prefetch_len = last_off - prefetch_off;
|
||||
std::unique_ptr<FilePrefetchBuffer> prefetch_buffer;
|
||||
auto& file = table_->rep_->file;
|
||||
prefetch_buffer.reset(new FilePrefetchBuffer());
|
||||
s = prefetch_buffer->Prefetch(file.get(), prefetch_off, prefetch_len);
|
||||
|
||||
// After prefetch, read the partitions one by one
|
||||
biter.SeekToFirst();
|
||||
auto ro = ReadOptions();
|
||||
Cache* block_cache = rep->table_options.block_cache.get();
|
||||
for (; biter.Valid(); biter.Next()) {
|
||||
input = biter.value();
|
||||
s = handle.DecodeFrom(&input);
|
||||
assert(s.ok());
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(rep->ioptions.info_log,
|
||||
"Could not read index partition");
|
||||
continue;
|
||||
}
|
||||
|
||||
BlockBasedTable::CachableEntry<Block> block;
|
||||
Slice compression_dict;
|
||||
if (rep->compression_dict_block) {
|
||||
compression_dict = rep->compression_dict_block->data;
|
||||
}
|
||||
const bool is_index = true;
|
||||
s = table_->MaybeLoadDataBlockToCache(prefetch_buffer.get(), rep, ro,
|
||||
handle, compression_dict, &block,
|
||||
is_index);
|
||||
|
||||
assert(s.ok() || block.value == nullptr);
|
||||
if (s.ok() && block.value != nullptr) {
|
||||
assert(block.cache_handle != nullptr);
|
||||
if (pin) {
|
||||
partition_map_[handle.offset()] = block;
|
||||
RegisterCleanup(&ReleaseCachedEntry, block_cache, block.cache_handle);
|
||||
} else {
|
||||
block_cache->Release(block.cache_handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual size_t size() const override { return index_block_->size(); }
|
||||
@@ -222,13 +287,13 @@ class PartitionIndexReader : public IndexReader, public Cleanable {
|
||||
const int level)
|
||||
: IndexReader(icomparator, stats),
|
||||
table_(table),
|
||||
index_block_(std::move(index_block)),
|
||||
level_(level) {
|
||||
index_block_(std::move(index_block)) {
|
||||
assert(index_block_ != nullptr);
|
||||
}
|
||||
BlockBasedTable* table_;
|
||||
std::unique_ptr<Block> index_block_;
|
||||
int level_;
|
||||
std::unordered_map<uint64_t, BlockBasedTable::CachableEntry<Block>>
|
||||
partition_map_;
|
||||
};
|
||||
|
||||
// Index that allows binary search lookup for the first key of each block.
|
||||
@@ -708,10 +773,9 @@ Status BlockBasedTable::Open(const ImmutableCFOptions& ioptions,
|
||||
} else {
|
||||
if (found_range_del_block && !rep->range_del_handle.IsNull()) {
|
||||
ReadOptions read_options;
|
||||
// TODO: try to use prefetched buffer too.
|
||||
s = MaybeLoadDataBlockToCache(rep, read_options, rep->range_del_handle,
|
||||
Slice() /* compression_dict */,
|
||||
&rep->range_del_entry);
|
||||
s = MaybeLoadDataBlockToCache(
|
||||
prefetch_buffer.get(), rep, read_options, rep->range_del_handle,
|
||||
Slice() /* compression_dict */, &rep->range_del_entry);
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(
|
||||
rep->ioptions.info_log,
|
||||
@@ -735,7 +799,9 @@ Status BlockBasedTable::Open(const ImmutableCFOptions& ioptions,
|
||||
rep->ioptions.info_log);
|
||||
}
|
||||
|
||||
// pre-fetching of blocks is turned on
|
||||
const bool pin =
|
||||
rep->table_options.pin_l0_filter_and_index_blocks_in_cache && level == 0;
|
||||
// pre-fetching of blocks is turned on
|
||||
// Will use block cache for index/filter blocks access
|
||||
// Always prefetch index and filter for level 0
|
||||
if (table_options.cache_index_and_filter_blocks) {
|
||||
@@ -744,32 +810,29 @@ Status BlockBasedTable::Open(const ImmutableCFOptions& ioptions,
|
||||
// Hack: Call NewIndexIterator() to implicitly add index to the
|
||||
// block_cache
|
||||
|
||||
// if pin_l0_filter_and_index_blocks_in_cache is true and this is
|
||||
// a level0 file, then we will pass in this pointer to rep->index
|
||||
// to NewIndexIterator(), which will save the index block in there
|
||||
// else it's a nullptr and nothing special happens
|
||||
CachableEntry<IndexReader>* index_entry = nullptr;
|
||||
if (rep->table_options.pin_l0_filter_and_index_blocks_in_cache &&
|
||||
level == 0) {
|
||||
index_entry = &rep->index_entry;
|
||||
}
|
||||
CachableEntry<IndexReader> index_entry;
|
||||
unique_ptr<InternalIterator> iter(
|
||||
new_table->NewIndexIterator(ReadOptions(), nullptr, index_entry));
|
||||
new_table->NewIndexIterator(ReadOptions(), nullptr, &index_entry));
|
||||
index_entry.value->CacheDependencies(pin);
|
||||
if (pin) {
|
||||
rep->index_entry = std::move(index_entry);
|
||||
} else {
|
||||
index_entry.Release(table_options.block_cache.get());
|
||||
}
|
||||
s = iter->status();
|
||||
|
||||
if (s.ok()) {
|
||||
// Hack: Call GetFilter() to implicitly add filter to the block_cache
|
||||
auto filter_entry = new_table->GetFilter();
|
||||
if (filter_entry.value != nullptr) {
|
||||
filter_entry.value->CacheDependencies(pin);
|
||||
}
|
||||
// if pin_l0_filter_and_index_blocks_in_cache is true, and this is
|
||||
// a level0 file, then save it in rep_->filter_entry; it will be
|
||||
// released in the destructor only, hence it will be pinned in the
|
||||
// cache while this reader is alive
|
||||
if (rep->table_options.pin_l0_filter_and_index_blocks_in_cache &&
|
||||
level == 0) {
|
||||
if (pin) {
|
||||
rep->filter_entry = filter_entry;
|
||||
if (rep->filter_entry.value != nullptr) {
|
||||
rep->filter_entry.value->SetLevel(level);
|
||||
}
|
||||
} else {
|
||||
filter_entry.Release(table_options.block_cache.get());
|
||||
}
|
||||
@@ -782,17 +845,25 @@ Status BlockBasedTable::Open(const ImmutableCFOptions& ioptions,
|
||||
IndexReader* index_reader = nullptr;
|
||||
s = new_table->CreateIndexReader(prefetch_buffer.get(), &index_reader,
|
||||
meta_iter.get(), level);
|
||||
|
||||
if (s.ok()) {
|
||||
rep->index_reader.reset(index_reader);
|
||||
// The partitions of partitioned index are always stored in cache. They
|
||||
// are hence follow the configuration for pin and prefetch regardless of
|
||||
// the value of cache_index_and_filter_blocks
|
||||
if (prefetch_index_and_filter_in_cache || level == 0) {
|
||||
rep->index_reader->CacheDependencies(pin);
|
||||
}
|
||||
|
||||
// Set filter block
|
||||
if (rep->filter_policy) {
|
||||
const bool is_a_filter_partition = true;
|
||||
rep->filter.reset(new_table->ReadFilter(
|
||||
prefetch_buffer.get(), rep->filter_handle, !is_a_filter_partition));
|
||||
if (rep->filter.get()) {
|
||||
rep->filter->SetLevel(level);
|
||||
auto filter = new_table->ReadFilter(
|
||||
prefetch_buffer.get(), rep->filter_handle, !is_a_filter_partition);
|
||||
rep->filter.reset(filter);
|
||||
// Refer to the comment above about paritioned indexes always being
|
||||
// cached
|
||||
if (filter && (prefetch_index_and_filter_in_cache || level == 0)) {
|
||||
filter->CacheDependencies(pin);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -1109,15 +1180,16 @@ FilterBlockReader* BlockBasedTable::ReadFilter(
|
||||
}
|
||||
|
||||
BlockBasedTable::CachableEntry<FilterBlockReader> BlockBasedTable::GetFilter(
|
||||
bool no_io) const {
|
||||
FilePrefetchBuffer* prefetch_buffer, bool no_io) const {
|
||||
const BlockHandle& filter_blk_handle = rep_->filter_handle;
|
||||
const bool is_a_filter_partition = true;
|
||||
return GetFilter(filter_blk_handle, !is_a_filter_partition, no_io);
|
||||
return GetFilter(prefetch_buffer, filter_blk_handle, !is_a_filter_partition,
|
||||
no_io);
|
||||
}
|
||||
|
||||
BlockBasedTable::CachableEntry<FilterBlockReader> BlockBasedTable::GetFilter(
|
||||
const BlockHandle& filter_blk_handle, const bool is_a_filter_partition,
|
||||
bool no_io) const {
|
||||
FilePrefetchBuffer* prefetch_buffer, const BlockHandle& filter_blk_handle,
|
||||
const bool is_a_filter_partition, bool no_io) const {
|
||||
// If cache_index_and_filter_blocks is false, filter should be pre-populated.
|
||||
// We will return rep_->filter anyway. rep_->filter can be nullptr if filter
|
||||
// read fails at Open() time. We don't want to reload again since it will
|
||||
@@ -1157,8 +1229,8 @@ BlockBasedTable::CachableEntry<FilterBlockReader> BlockBasedTable::GetFilter(
|
||||
// Do not invoke any io.
|
||||
return CachableEntry<FilterBlockReader>();
|
||||
} else {
|
||||
filter = ReadFilter(nullptr /* prefetch_buffer */, filter_blk_handle,
|
||||
is_a_filter_partition);
|
||||
filter =
|
||||
ReadFilter(prefetch_buffer, filter_blk_handle, is_a_filter_partition);
|
||||
if (filter != nullptr) {
|
||||
assert(filter->size() > 0);
|
||||
Status s = block_cache->Insert(
|
||||
@@ -1305,8 +1377,8 @@ InternalIterator* BlockBasedTable::NewDataBlockIterator(
|
||||
if (rep->compression_dict_block) {
|
||||
compression_dict = rep->compression_dict_block->data;
|
||||
}
|
||||
s = MaybeLoadDataBlockToCache(rep, ro, handle, compression_dict, &block,
|
||||
is_index);
|
||||
s = MaybeLoadDataBlockToCache(nullptr /*prefetch_buffer*/, rep, ro, handle,
|
||||
compression_dict, &block, is_index);
|
||||
}
|
||||
|
||||
// Didn't get any data from block caches.
|
||||
@@ -1355,8 +1427,10 @@ InternalIterator* BlockBasedTable::NewDataBlockIterator(
|
||||
}
|
||||
|
||||
Status BlockBasedTable::MaybeLoadDataBlockToCache(
|
||||
Rep* rep, const ReadOptions& ro, const BlockHandle& handle,
|
||||
Slice compression_dict, CachableEntry<Block>* block_entry, bool is_index) {
|
||||
FilePrefetchBuffer* prefetch_buffer, Rep* rep, const ReadOptions& ro,
|
||||
const BlockHandle& handle, Slice compression_dict,
|
||||
CachableEntry<Block>* block_entry, bool is_index) {
|
||||
assert(block_entry != nullptr);
|
||||
const bool no_io = (ro.read_tier == kBlockCacheTier);
|
||||
Cache* block_cache = rep->table_options.block_cache.get();
|
||||
Cache* block_cache_compressed =
|
||||
@@ -1392,12 +1466,11 @@ Status BlockBasedTable::MaybeLoadDataBlockToCache(
|
||||
std::unique_ptr<Block> raw_block;
|
||||
{
|
||||
StopWatch sw(rep->ioptions.env, statistics, READ_BLOCK_GET_MICROS);
|
||||
s = ReadBlockFromFile(rep->file.get(), nullptr /* prefetch_buffer*/,
|
||||
rep->footer, ro, handle, &raw_block,
|
||||
rep->ioptions, block_cache_compressed == nullptr,
|
||||
compression_dict, rep->persistent_cache_options,
|
||||
rep->global_seqno,
|
||||
rep->table_options.read_amp_bytes_per_bit);
|
||||
s = ReadBlockFromFile(
|
||||
rep->file.get(), prefetch_buffer, rep->footer, ro, handle,
|
||||
&raw_block, rep->ioptions, block_cache_compressed == nullptr,
|
||||
compression_dict, rep->persistent_cache_options, rep->global_seqno,
|
||||
rep->table_options.read_amp_bytes_per_bit);
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
@@ -1414,20 +1487,21 @@ Status BlockBasedTable::MaybeLoadDataBlockToCache(
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(s.ok() || block_entry->value == nullptr);
|
||||
return s;
|
||||
}
|
||||
|
||||
BlockBasedTable::BlockEntryIteratorState::BlockEntryIteratorState(
|
||||
BlockBasedTable* table, const ReadOptions& read_options,
|
||||
const InternalKeyComparator* icomparator, bool skip_filters, bool is_index,
|
||||
Cleanable* block_cache_cleaner)
|
||||
std::unordered_map<uint64_t, CachableEntry<Block>>* block_map)
|
||||
: TwoLevelIteratorState(table->rep_->ioptions.prefix_extractor != nullptr),
|
||||
table_(table),
|
||||
read_options_(read_options),
|
||||
icomparator_(icomparator),
|
||||
skip_filters_(skip_filters),
|
||||
is_index_(is_index),
|
||||
block_cache_cleaner_(block_cache_cleaner) {}
|
||||
block_map_(block_map) {}
|
||||
|
||||
InternalIterator*
|
||||
BlockBasedTable::BlockEntryIteratorState::NewSecondaryIterator(
|
||||
@@ -1436,23 +1510,25 @@ BlockBasedTable::BlockEntryIteratorState::NewSecondaryIterator(
|
||||
BlockHandle handle;
|
||||
Slice input = index_value;
|
||||
Status s = handle.DecodeFrom(&input);
|
||||
auto iter = NewDataBlockIterator(table_->rep_, read_options_, handle, nullptr,
|
||||
is_index_, s);
|
||||
if (block_cache_cleaner_) {
|
||||
uint64_t offset = handle.offset();
|
||||
{
|
||||
ReadLock rl(&cleaner_mu);
|
||||
if (cleaner_set.find(offset) != cleaner_set.end()) {
|
||||
// already have a reference to the block cache objects
|
||||
return iter;
|
||||
}
|
||||
auto rep = table_->rep_;
|
||||
if (block_map_) {
|
||||
auto block = block_map_->find(handle.offset());
|
||||
// This is a possible scenario since block cache might not have had space
|
||||
// for the partition
|
||||
if (block != block_map_->end()) {
|
||||
PERF_COUNTER_ADD(block_cache_hit_count, 1);
|
||||
RecordTick(rep->ioptions.statistics, BLOCK_CACHE_INDEX_HIT);
|
||||
RecordTick(rep->ioptions.statistics, BLOCK_CACHE_HIT);
|
||||
Cache* block_cache = rep->table_options.block_cache.get();
|
||||
assert(block_cache);
|
||||
RecordTick(rep->ioptions.statistics, BLOCK_CACHE_BYTES_READ,
|
||||
block_cache->GetUsage(block->second.cache_handle));
|
||||
return block->second.value->NewIterator(
|
||||
&rep->internal_comparator, nullptr, true, rep->ioptions.statistics);
|
||||
}
|
||||
WriteLock wl(&cleaner_mu);
|
||||
cleaner_set.insert(offset);
|
||||
// Keep the data into cache until the cleaner cleansup
|
||||
iter->DelegateCleanupsTo(block_cache_cleaner_);
|
||||
}
|
||||
return iter;
|
||||
return NewDataBlockIterator(rep, read_options_, handle, nullptr, is_index_,
|
||||
s);
|
||||
}
|
||||
|
||||
bool BlockBasedTable::BlockEntryIteratorState::PrefixMayMatch(
|
||||
@@ -1646,7 +1722,8 @@ Status BlockBasedTable::Get(const ReadOptions& read_options, const Slice& key,
|
||||
const bool no_io = read_options.read_tier == kBlockCacheTier;
|
||||
CachableEntry<FilterBlockReader> filter_entry;
|
||||
if (!skip_filters) {
|
||||
filter_entry = GetFilter(read_options.read_tier == kBlockCacheTier);
|
||||
filter_entry = GetFilter(/*prefetch_buffer*/ nullptr,
|
||||
read_options.read_tier == kBlockCacheTier);
|
||||
}
|
||||
FilterBlockReader* filter = filter_entry.value;
|
||||
|
||||
|
||||
@@ -181,6 +181,11 @@ class BlockBasedTable : public TableReader {
|
||||
// that was allocated in block cache.
|
||||
virtual size_t ApproximateMemoryUsage() const = 0;
|
||||
|
||||
virtual void CacheDependencies(bool /* unused */) {}
|
||||
|
||||
// Prefetch all the blocks referenced by this index to the buffer
|
||||
void PrefetchBlocks(FilePrefetchBuffer* buf);
|
||||
|
||||
protected:
|
||||
const InternalKeyComparator* icomparator_;
|
||||
|
||||
@@ -208,6 +213,7 @@ class BlockBasedTable : public TableReader {
|
||||
explicit BlockBasedTable(Rep* rep) : rep_(rep) {}
|
||||
|
||||
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,
|
||||
@@ -227,7 +233,8 @@ class BlockBasedTable : public TableReader {
|
||||
// @param block_entry value is set to the uncompressed block if found. If
|
||||
// in uncompressed block cache, also sets cache_handle to reference that
|
||||
// block.
|
||||
static Status MaybeLoadDataBlockToCache(Rep* rep, const ReadOptions& ro,
|
||||
static Status MaybeLoadDataBlockToCache(FilePrefetchBuffer* prefetch_buffer,
|
||||
Rep* rep, const ReadOptions& ro,
|
||||
const BlockHandle& handle,
|
||||
Slice compression_dict,
|
||||
CachableEntry<Block>* block_entry,
|
||||
@@ -236,10 +243,11 @@ class BlockBasedTable : public TableReader {
|
||||
// For the following two functions:
|
||||
// if `no_io == true`, we will not try to read filter/index from sst file
|
||||
// were they not present in cache yet.
|
||||
CachableEntry<FilterBlockReader> GetFilter(bool no_io = false) const;
|
||||
CachableEntry<FilterBlockReader> GetFilter(
|
||||
FilePrefetchBuffer* prefetch_buffer = nullptr, bool no_io = false) const;
|
||||
virtual CachableEntry<FilterBlockReader> GetFilter(
|
||||
const BlockHandle& filter_blk_handle, const bool is_a_filter_partition,
|
||||
bool no_io) const;
|
||||
FilePrefetchBuffer* prefetch_buffer, const BlockHandle& filter_blk_handle,
|
||||
const bool is_a_filter_partition, bool no_io) const;
|
||||
|
||||
// Get the iterator from the index reader.
|
||||
// If input_iter is not set, return new Iterator
|
||||
@@ -345,11 +353,11 @@ class BlockBasedTable : public TableReader {
|
||||
// Maitaning state of a two-level iteration on a partitioned index structure
|
||||
class BlockBasedTable::BlockEntryIteratorState : public TwoLevelIteratorState {
|
||||
public:
|
||||
BlockEntryIteratorState(BlockBasedTable* table,
|
||||
const ReadOptions& read_options,
|
||||
const InternalKeyComparator* icomparator,
|
||||
bool skip_filters, bool is_index = false,
|
||||
Cleanable* block_cache_cleaner = nullptr);
|
||||
BlockEntryIteratorState(
|
||||
BlockBasedTable* table, const ReadOptions& read_options,
|
||||
const InternalKeyComparator* icomparator, bool skip_filters,
|
||||
bool is_index = false,
|
||||
std::unordered_map<uint64_t, CachableEntry<Block>>* block_map = nullptr);
|
||||
InternalIterator* NewSecondaryIterator(const Slice& index_value) override;
|
||||
bool PrefixMayMatch(const Slice& internal_key) override;
|
||||
bool KeyReachedUpperBound(const Slice& internal_key) override;
|
||||
@@ -362,8 +370,7 @@ class BlockBasedTable::BlockEntryIteratorState : public TwoLevelIteratorState {
|
||||
bool skip_filters_;
|
||||
// true if the 2nd level iterator is on indexes instead of on user data.
|
||||
bool is_index_;
|
||||
Cleanable* block_cache_cleaner_;
|
||||
std::set<uint64_t> cleaner_set;
|
||||
std::unordered_map<uint64_t, CachableEntry<Block>>* block_map_;
|
||||
port::RWMutex cleaner_mu;
|
||||
};
|
||||
|
||||
|
||||
@@ -109,7 +109,11 @@ class CuckooBuilderTest : public testing::Test {
|
||||
expected_locations.begin();
|
||||
if (key_idx == keys.size()) {
|
||||
// i is not one of the expected locations. Empty bucket.
|
||||
ASSERT_EQ(read_slice.compare(expected_unused_bucket), 0);
|
||||
if (read_slice.data() == nullptr) {
|
||||
ASSERT_EQ(0, expected_unused_bucket.size());
|
||||
} else {
|
||||
ASSERT_EQ(read_slice.compare(expected_unused_bucket), 0);
|
||||
}
|
||||
} else {
|
||||
keys_found[key_idx] = true;
|
||||
ASSERT_EQ(read_slice.compare(keys[key_idx] + values[key_idx]), 0);
|
||||
|
||||
@@ -108,15 +108,14 @@ class FilterBlockReader {
|
||||
|
||||
bool whole_key_filtering() const { return whole_key_filtering_; }
|
||||
|
||||
int GetLevel() const { return level_; }
|
||||
void SetLevel(int level) { level_ = level; }
|
||||
|
||||
// convert this object to a human readable form
|
||||
virtual std::string ToString() const {
|
||||
std::string error_msg("Unsupported filter \n");
|
||||
return error_msg;
|
||||
}
|
||||
|
||||
virtual void CacheDependencies(bool pin) {}
|
||||
|
||||
protected:
|
||||
bool whole_key_filtering_;
|
||||
|
||||
|
||||
+3
-1
@@ -102,7 +102,7 @@ inline uint64_t UpconvertLegacyFooterFormat(uint64_t magic_number) {
|
||||
// <padding> to make the total size 2 * BlockHandle::kMaxEncodedLength
|
||||
// table_magic_number (8 bytes)
|
||||
// new footer format:
|
||||
// checksum (char, 1 byte)
|
||||
// checksum type (char, 1 byte)
|
||||
// metaindex handle (varint64 offset, varint64 size)
|
||||
// index handle (varint64 offset, varint64 size)
|
||||
// <padding> to make the total size 2 * BlockHandle::kMaxEncodedLength + 1
|
||||
@@ -278,6 +278,8 @@ Status CheckBlockChecksum(const ReadOptions& options, const Footer& footer,
|
||||
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);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/filter_policy.h"
|
||||
#include "table/block.h"
|
||||
@@ -100,19 +101,29 @@ PartitionedFilterBlockReader::PartitionedFilterBlockReader(
|
||||
}
|
||||
|
||||
PartitionedFilterBlockReader::~PartitionedFilterBlockReader() {
|
||||
{
|
||||
ReadLock rl(&mu_);
|
||||
for (auto it = handle_list_.begin(); it != handle_list_.end(); ++it) {
|
||||
table_->rep_->table_options.block_cache.get()->Release(*it);
|
||||
}
|
||||
// TODO(myabandeh): if instead of filter object we store only the blocks in
|
||||
// block cache, then we don't have to manually earse them from block cache
|
||||
// here.
|
||||
auto block_cache = table_->rep_->table_options.block_cache.get();
|
||||
if (UNLIKELY(block_cache == nullptr)) {
|
||||
return;
|
||||
}
|
||||
char cache_key[BlockBasedTable::kMaxCacheKeyPrefixSize + kMaxVarint64Length];
|
||||
for (auto it = filter_block_set_.begin(); it != filter_block_set_.end();
|
||||
++it) {
|
||||
BlockIter biter;
|
||||
BlockHandle handle;
|
||||
idx_on_fltr_blk_->NewIterator(&comparator_, &biter, true);
|
||||
biter.SeekToFirst();
|
||||
for (; biter.Valid(); biter.Next()) {
|
||||
auto input = biter.value();
|
||||
auto s = handle.DecodeFrom(&input);
|
||||
assert(s.ok());
|
||||
if (!s.ok()) {
|
||||
continue;
|
||||
}
|
||||
auto key = BlockBasedTable::GetCacheKey(table_->rep_->cache_key_prefix,
|
||||
table_->rep_->cache_key_prefix_size,
|
||||
*it, cache_key);
|
||||
table_->rep_->table_options.block_cache.get()->Erase(key);
|
||||
handle, cache_key);
|
||||
block_cache->Erase(key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,34 +216,22 @@ PartitionedFilterBlockReader::GetFilterPartition(
|
||||
const bool is_a_filter_partition = true;
|
||||
auto block_cache = table_->rep_->table_options.block_cache.get();
|
||||
if (LIKELY(block_cache != nullptr)) {
|
||||
bool pin_cached_filters =
|
||||
GetLevel() == 0 &&
|
||||
table_->rep_->table_options.pin_l0_filter_and_index_blocks_in_cache;
|
||||
if (pin_cached_filters) {
|
||||
ReadLock rl(&mu_);
|
||||
auto iter = filter_cache_.find(fltr_blk_handle.offset());
|
||||
if (iter != filter_cache_.end()) {
|
||||
if (filter_map_.size() != 0) {
|
||||
auto iter = filter_map_.find(fltr_blk_handle.offset());
|
||||
// This is a possible scenario since block cache might not have had space
|
||||
// for the partition
|
||||
if (iter != filter_map_.end()) {
|
||||
PERF_COUNTER_ADD(block_cache_hit_count, 1);
|
||||
RecordTick(statistics(), BLOCK_CACHE_FILTER_HIT);
|
||||
RecordTick(statistics(), BLOCK_CACHE_HIT);
|
||||
RecordTick(statistics(), BLOCK_CACHE_BYTES_READ,
|
||||
block_cache->GetUsage(iter->second.cache_handle));
|
||||
*cached = true;
|
||||
return {iter->second, nullptr};
|
||||
return iter->second;
|
||||
}
|
||||
}
|
||||
auto filter =
|
||||
table_->GetFilter(fltr_blk_handle, is_a_filter_partition, no_io);
|
||||
if (filter.IsSet()) {
|
||||
WriteLock wl(&mu_);
|
||||
filter_block_set_.insert(fltr_blk_handle);
|
||||
if (pin_cached_filters) {
|
||||
std::pair<uint64_t, FilterBlockReader*> pair(fltr_blk_handle.offset(),
|
||||
filter.value);
|
||||
auto succ = filter_cache_.insert(pair).second;
|
||||
if (succ) {
|
||||
handle_list_.push_back(filter.cache_handle);
|
||||
} // Otherwise it is already inserted by a concurrent thread
|
||||
*cached = true;
|
||||
}
|
||||
}
|
||||
return filter;
|
||||
return table_->GetFilter(/*prefetch_buffer*/ nullptr, fltr_blk_handle,
|
||||
is_a_filter_partition, no_io);
|
||||
} else {
|
||||
auto filter = table_->ReadFilter(prefetch_buffer, fltr_blk_handle,
|
||||
is_a_filter_partition);
|
||||
@@ -244,4 +243,69 @@ size_t PartitionedFilterBlockReader::ApproximateMemoryUsage() const {
|
||||
return idx_on_fltr_blk_->size();
|
||||
}
|
||||
|
||||
// TODO(myabandeh): merge this with the same function in IndexReader
|
||||
void PartitionedFilterBlockReader::CacheDependencies(bool pin) {
|
||||
// Before read partitions, prefetch them to avoid lots of IOs
|
||||
auto rep = table_->rep_;
|
||||
BlockIter biter;
|
||||
BlockHandle handle;
|
||||
idx_on_fltr_blk_->NewIterator(&comparator_, &biter, true);
|
||||
// Index partitions are assumed to be consecuitive. Prefetch them all.
|
||||
// Read the first block offset
|
||||
biter.SeekToFirst();
|
||||
Slice input = biter.value();
|
||||
Status s = handle.DecodeFrom(&input);
|
||||
assert(s.ok());
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(rep->ioptions.info_log,
|
||||
"Could not read first index partition");
|
||||
return;
|
||||
}
|
||||
uint64_t prefetch_off = handle.offset();
|
||||
|
||||
// Read the last block's offset
|
||||
biter.SeekToLast();
|
||||
input = biter.value();
|
||||
s = handle.DecodeFrom(&input);
|
||||
assert(s.ok());
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(rep->ioptions.info_log,
|
||||
"Could not read last index partition");
|
||||
return;
|
||||
}
|
||||
uint64_t last_off = handle.offset() + handle.size() + kBlockTrailerSize;
|
||||
uint64_t prefetch_len = last_off - prefetch_off;
|
||||
std::unique_ptr<FilePrefetchBuffer> prefetch_buffer;
|
||||
auto& file = table_->rep_->file;
|
||||
prefetch_buffer.reset(new FilePrefetchBuffer());
|
||||
s = prefetch_buffer->Prefetch(file.get(), prefetch_off, prefetch_len);
|
||||
|
||||
// After prefetch, read the partitions one by one
|
||||
biter.SeekToFirst();
|
||||
Cache* block_cache = rep->table_options.block_cache.get();
|
||||
for (; biter.Valid(); biter.Next()) {
|
||||
input = biter.value();
|
||||
s = handle.DecodeFrom(&input);
|
||||
assert(s.ok());
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(rep->ioptions.info_log, "Could not read index partition");
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool no_io = true;
|
||||
const bool is_a_filter_partition = true;
|
||||
auto filter = table_->GetFilter(prefetch_buffer.get(), handle,
|
||||
is_a_filter_partition, !no_io);
|
||||
if (LIKELY(filter.IsSet())) {
|
||||
if (pin) {
|
||||
filter_map_[handle.offset()] = std::move(filter);
|
||||
} else {
|
||||
block_cache->Release(filter.cache_handle);
|
||||
}
|
||||
} else {
|
||||
delete filter.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -88,20 +88,15 @@ class PartitionedFilterBlockReader : public FilterBlockReader {
|
||||
BlockBasedTable::CachableEntry<FilterBlockReader> GetFilterPartition(
|
||||
FilePrefetchBuffer* prefetch_buffer, Slice* handle, const bool no_io,
|
||||
bool* cached);
|
||||
virtual void CacheDependencies(bool pin) override;
|
||||
|
||||
const SliceTransform* prefix_extractor_;
|
||||
std::unique_ptr<Block> idx_on_fltr_blk_;
|
||||
const Comparator& comparator_;
|
||||
const BlockBasedTable* table_;
|
||||
std::unordered_map<uint64_t, FilterBlockReader*> filter_cache_;
|
||||
autovector<Cache::Handle*> handle_list_;
|
||||
struct BlockHandleCmp {
|
||||
bool operator()(const BlockHandle& lhs, const BlockHandle& rhs) const {
|
||||
return lhs.offset() < rhs.offset();
|
||||
}
|
||||
};
|
||||
std::set<BlockHandle, BlockHandleCmp> filter_block_set_;
|
||||
port::RWMutex mu_;
|
||||
std::unordered_map<uint64_t,
|
||||
BlockBasedTable::CachableEntry<FilterBlockReader>>
|
||||
filter_map_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -22,11 +22,14 @@ std::map<uint64_t, Slice> slices;
|
||||
|
||||
class MockedBlockBasedTable : public BlockBasedTable {
|
||||
public:
|
||||
explicit MockedBlockBasedTable(Rep* rep) : BlockBasedTable(rep) {}
|
||||
explicit MockedBlockBasedTable(Rep* rep) : BlockBasedTable(rep) {
|
||||
// Initialize what Open normally does as much as necessary for the test
|
||||
rep->cache_key_prefix_size = 10;
|
||||
}
|
||||
|
||||
virtual CachableEntry<FilterBlockReader> GetFilter(
|
||||
const BlockHandle& filter_blk_handle, const bool is_a_filter_partition,
|
||||
bool no_io) const override {
|
||||
FilePrefetchBuffer*, const BlockHandle& filter_blk_handle,
|
||||
const bool /* unused */, bool /* unused */) const override {
|
||||
Slice slice = slices[filter_blk_handle.offset()];
|
||||
auto obj = new FullFilterBlockReader(
|
||||
nullptr, true, BlockContents(slice, false, kNoCompression),
|
||||
|
||||
+1
-1
@@ -2174,7 +2174,7 @@ std::map<std::string, size_t> MockCache::marked_data_in_cache_;
|
||||
|
||||
// Block cache can contain raw data blocks as well as general objects. If an
|
||||
// object depends on the table to be live, it then must be destructed before the
|
||||
// table is closed. This test makese sure that the only items remains in the
|
||||
// table is closed. This test makes sure that the only items remains in the
|
||||
// cache after the table is closed are raw data blocks.
|
||||
TEST_F(BlockBasedTableTest, NoObjectInCacheAfterTableClose) {
|
||||
for (auto index_type :
|
||||
|
||||
+81
-10
@@ -44,6 +44,7 @@ int main() {
|
||||
#include "db/version_set.h"
|
||||
#include "hdfs/env_hdfs.h"
|
||||
#include "monitoring/histogram.h"
|
||||
#include "options/options_helper.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/env.h"
|
||||
@@ -72,6 +73,8 @@ using GFLAGS::RegisterFlagValidator;
|
||||
using GFLAGS::SetUsageMessage;
|
||||
|
||||
static const long KB = 1024;
|
||||
static const int kRandomValueMaxFactor = 3;
|
||||
static const int kValueMaxLen = 100;
|
||||
|
||||
static bool ValidateUint32Range(const char* flagname, uint64_t value) {
|
||||
if (value > std::numeric_limits<uint32_t>::max()) {
|
||||
@@ -93,6 +96,13 @@ DEFINE_int64(max_key, 1 * KB* KB,
|
||||
|
||||
DEFINE_int32(column_families, 10, "Number of column families");
|
||||
|
||||
DEFINE_int64(
|
||||
active_width, 0,
|
||||
"Number of keys in active span of the key-range at any given time. The "
|
||||
"span begins with its left endpoint at key 0, gradually moves rightwards, "
|
||||
"and ends with its right endpoint at max_key. If set to 0, active_width "
|
||||
"will be sanitized to be equal to max_key.");
|
||||
|
||||
// TODO(noetzli) Add support for single deletes
|
||||
DEFINE_bool(test_batches_snapshots, false,
|
||||
"If set, the test uses MultiGet(), MultiPut() and MultiDelete()"
|
||||
@@ -167,6 +177,11 @@ DEFINE_int32(max_write_buffer_number_to_maintain,
|
||||
"after they are flushed. If this value is set to -1, "
|
||||
"'max_write_buffer_number' will be used.");
|
||||
|
||||
DEFINE_double(memtable_prefix_bloom_size_ratio,
|
||||
rocksdb::Options().memtable_prefix_bloom_size_ratio,
|
||||
"creates prefix blooms for memtables, each with size "
|
||||
"`write_buffer_size * memtable_prefix_bloom_size_ratio`.");
|
||||
|
||||
DEFINE_int32(open_files, rocksdb::Options().max_open_files,
|
||||
"Maximum number of files to keep open at the same time "
|
||||
"(use default if == 0)");
|
||||
@@ -198,6 +213,10 @@ DEFINE_int32(max_background_compactions,
|
||||
"The maximum number of concurrent background compactions "
|
||||
"that can occur in parallel.");
|
||||
|
||||
DEFINE_int32(num_bottom_pri_threads, 0,
|
||||
"The number of threads in the bottom-priority thread pool (used "
|
||||
"by universal compaction only).");
|
||||
|
||||
DEFINE_int32(compaction_thread_pool_adjust_interval, 0,
|
||||
"The interval (in milliseconds) to adjust compaction thread pool "
|
||||
"size. Don't change it periodically if the value is 0.");
|
||||
@@ -310,13 +329,15 @@ extern std::vector<std::string> rocksdb_kill_prefix_blacklist;
|
||||
|
||||
DEFINE_bool(disable_wal, false, "If true, do not write WAL for write.");
|
||||
|
||||
DEFINE_int32(target_file_size_base, 64 * KB,
|
||||
DEFINE_int64(target_file_size_base, rocksdb::Options().target_file_size_base,
|
||||
"Target level-1 file size for compaction");
|
||||
|
||||
DEFINE_int32(target_file_size_multiplier, 1,
|
||||
"A multiplier to compute target level-N file size (N >= 2)");
|
||||
|
||||
DEFINE_uint64(max_bytes_for_level_base, 256 * KB, "Max bytes for level-1");
|
||||
DEFINE_uint64(max_bytes_for_level_base,
|
||||
rocksdb::Options().max_bytes_for_level_base,
|
||||
"Max bytes for level-1");
|
||||
|
||||
DEFINE_double(max_bytes_for_level_multiplier, 2,
|
||||
"A multiplier to compute max bytes for level-N (N >= 2)");
|
||||
@@ -406,10 +427,30 @@ enum rocksdb::CompressionType StringToCompressionType(const char* ctype) {
|
||||
else if (!strcasecmp(ctype, "zstd"))
|
||||
return rocksdb::kZSTD;
|
||||
|
||||
fprintf(stdout, "Cannot parse compression type '%s'\n", ctype);
|
||||
fprintf(stderr, "Cannot parse compression type '%s'\n", ctype);
|
||||
return rocksdb::kSnappyCompression; //default value
|
||||
}
|
||||
|
||||
enum rocksdb::ChecksumType StringToChecksumType(const char* ctype) {
|
||||
assert(ctype);
|
||||
auto iter = rocksdb::checksum_type_string_map.find(ctype);
|
||||
if (iter != rocksdb::checksum_type_string_map.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
fprintf(stderr, "Cannot parse checksum type '%s'\n", ctype);
|
||||
return rocksdb::kCRC32c;
|
||||
}
|
||||
|
||||
std::string ChecksumTypeToString(rocksdb::ChecksumType ctype) {
|
||||
auto iter = std::find_if(
|
||||
rocksdb::checksum_type_string_map.begin(),
|
||||
rocksdb::checksum_type_string_map.end(),
|
||||
[&](const std::pair<std::string, rocksdb::ChecksumType>&
|
||||
name_and_enum_val) { return name_and_enum_val.second == ctype; });
|
||||
assert(iter != rocksdb::checksum_type_string_map.end());
|
||||
return iter->first;
|
||||
}
|
||||
|
||||
std::vector<std::string> SplitString(std::string src) {
|
||||
std::vector<std::string> ret;
|
||||
if (src.empty()) {
|
||||
@@ -431,6 +472,9 @@ DEFINE_string(compression_type, "snappy",
|
||||
static enum rocksdb::CompressionType FLAGS_compression_type_e =
|
||||
rocksdb::kSnappyCompression;
|
||||
|
||||
DEFINE_string(checksum_type, "kCRC32c", "Algorithm to use to checksum blocks");
|
||||
static enum rocksdb::ChecksumType FLAGS_checksum_type_e = rocksdb::kCRC32c;
|
||||
|
||||
DEFINE_string(hdfs, "", "Name of hdfs environment");
|
||||
// posix or hdfs environment
|
||||
static rocksdb::Env* FLAGS_env = rocksdb::Env::Default();
|
||||
@@ -1114,8 +1158,6 @@ class StressTest {
|
||||
ToString(FLAGS_write_buffer_size / 4),
|
||||
ToString(FLAGS_write_buffer_size / 8),
|
||||
}},
|
||||
{"memtable_prefix_bloom_bits", {"0", "8", "10"}},
|
||||
{"memtable_prefix_bloom_probes", {"4", "5", "6"}},
|
||||
{"memtable_huge_page_size", {"0", ToString(2 * 1024 * 1024)}},
|
||||
{"max_successive_merges", {"0", "2", "4"}},
|
||||
{"inplace_update_num_locks", {"100", "200", "300"}},
|
||||
@@ -1243,7 +1285,7 @@ class StressTest {
|
||||
threads[i] = nullptr;
|
||||
}
|
||||
auto now = FLAGS_env->NowMicros();
|
||||
if (!FLAGS_test_batches_snapshots) {
|
||||
if (!FLAGS_test_batches_snapshots && !shared.HasVerificationFailedYet()) {
|
||||
fprintf(stdout, "%s Verification successful\n",
|
||||
FLAGS_env->TimeToString(now/1000000).c_str());
|
||||
}
|
||||
@@ -1727,7 +1769,11 @@ class StressTest {
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
long rand_key = thread->rand.Next() % max_key;
|
||||
const double completed_ratio =
|
||||
static_cast<double>(i) / FLAGS_ops_per_thread;
|
||||
const int64_t base_key = static_cast<int64_t>(
|
||||
completed_ratio * (FLAGS_max_key - FLAGS_active_width));
|
||||
long rand_key = base_key + thread->rand.Next() % FLAGS_active_width;
|
||||
int rand_column_family = thread->rand.Next() % FLAGS_column_families;
|
||||
std::string keystr = Key(rand_key);
|
||||
Slice key = keystr;
|
||||
@@ -2010,7 +2056,7 @@ class StressTest {
|
||||
return false;
|
||||
}
|
||||
// compare value_from_db with the value in the shared state
|
||||
char value[100];
|
||||
char value[kValueMaxLen];
|
||||
uint32_t value_base = shared->Get(cf, key);
|
||||
if (value_base == SharedState::SENTINEL && !strict) {
|
||||
return true;
|
||||
@@ -2053,7 +2099,8 @@ class StressTest {
|
||||
}
|
||||
|
||||
static size_t GenerateValue(uint32_t rand, char *v, size_t max_sz) {
|
||||
size_t value_sz = ((rand % 3) + 1) * FLAGS_value_size_mult;
|
||||
size_t value_sz =
|
||||
((rand % kRandomValueMaxFactor) + 1) * FLAGS_value_size_mult;
|
||||
assert(value_sz <= max_sz && value_sz >= sizeof(uint32_t));
|
||||
*((uint32_t*)v) = rand;
|
||||
for (size_t i=sizeof(uint32_t); i < value_sz; i++) {
|
||||
@@ -2105,6 +2152,8 @@ class StressTest {
|
||||
1 << FLAGS_log2_keys_per_lock);
|
||||
std::string compression = CompressionTypeToString(FLAGS_compression_type_e);
|
||||
fprintf(stdout, "Compression : %s\n", compression.c_str());
|
||||
std::string checksum = ChecksumTypeToString(FLAGS_checksum_type_e);
|
||||
fprintf(stdout, "Checksum type : %s\n", checksum.c_str());
|
||||
fprintf(stdout, "Max subcompactions : %" PRIu64 "\n",
|
||||
FLAGS_subcompactions);
|
||||
|
||||
@@ -2139,6 +2188,7 @@ class StressTest {
|
||||
BlockBasedTableOptions block_based_options;
|
||||
block_based_options.block_cache = cache_;
|
||||
block_based_options.block_cache_compressed = compressed_cache_;
|
||||
block_based_options.checksum = FLAGS_checksum_type_e;
|
||||
block_based_options.block_size = FLAGS_block_size;
|
||||
block_based_options.format_version = 2;
|
||||
block_based_options.filter_policy = filter_policy_;
|
||||
@@ -2151,6 +2201,8 @@ class StressTest {
|
||||
FLAGS_min_write_buffer_number_to_merge;
|
||||
options_.max_write_buffer_number_to_maintain =
|
||||
FLAGS_max_write_buffer_number_to_maintain;
|
||||
options_.memtable_prefix_bloom_size_ratio =
|
||||
FLAGS_memtable_prefix_bloom_size_ratio;
|
||||
options_.max_background_compactions = FLAGS_max_background_compactions;
|
||||
options_.max_background_flushes = FLAGS_max_background_flushes;
|
||||
options_.compaction_style =
|
||||
@@ -2387,6 +2439,7 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
FLAGS_compression_type_e =
|
||||
StringToCompressionType(FLAGS_compression_type.c_str());
|
||||
FLAGS_checksum_type_e = StringToChecksumType(FLAGS_checksum_type.c_str());
|
||||
if (!FLAGS_hdfs.empty()) {
|
||||
FLAGS_env = new rocksdb::HdfsEnv(FLAGS_hdfs);
|
||||
}
|
||||
@@ -2395,7 +2448,8 @@ int main(int argc, char** argv) {
|
||||
// The number of background threads should be at least as much the
|
||||
// max number of concurrent compactions.
|
||||
FLAGS_env->SetBackgroundThreads(FLAGS_max_background_compactions);
|
||||
|
||||
FLAGS_env->SetBackgroundThreads(FLAGS_num_bottom_pri_threads,
|
||||
rocksdb::Env::Priority::BOTTOM);
|
||||
if (FLAGS_prefixpercent > 0 && FLAGS_prefix_size <= 0) {
|
||||
fprintf(stderr,
|
||||
"Error: prefixpercent is non-zero while prefix_size is "
|
||||
@@ -2408,6 +2462,12 @@ int main(int argc, char** argv) {
|
||||
"test_batches_snapshots test!\n");
|
||||
exit(1);
|
||||
}
|
||||
if (FLAGS_memtable_prefix_bloom_size_ratio > 0.0 && FLAGS_prefix_size <= 0) {
|
||||
fprintf(stderr,
|
||||
"Error: please specify positive prefix_size in order to use "
|
||||
"memtable_prefix_bloom_size_ratio\n");
|
||||
exit(1);
|
||||
}
|
||||
if ((FLAGS_readpercent + FLAGS_prefixpercent +
|
||||
FLAGS_writepercent + FLAGS_delpercent + FLAGS_delrangepercent +
|
||||
FLAGS_iterpercent) != 100) {
|
||||
@@ -2433,6 +2493,17 @@ int main(int argc, char** argv) {
|
||||
"test_batches_snapshots mode\n");
|
||||
exit(1);
|
||||
}
|
||||
if (FLAGS_active_width > FLAGS_max_key) {
|
||||
fprintf(stderr, "Error: active_width can be at most max_key\n");
|
||||
exit(1);
|
||||
} else if (FLAGS_active_width == 0) {
|
||||
FLAGS_active_width = FLAGS_max_key;
|
||||
}
|
||||
if (FLAGS_value_size_mult * kRandomValueMaxFactor > kValueMaxLen) {
|
||||
fprintf(stderr, "Error: value_size_mult can be at most %d\n",
|
||||
kValueMaxLen / kRandomValueMaxFactor);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Choose a location for the test database if none given with --db=<path>
|
||||
if (FLAGS_db.empty()) {
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
#include "util/random.h"
|
||||
#include "util/sync_point.h"
|
||||
#include "util/timer_queue.h"
|
||||
#include "utilities/transactions/optimistic_transaction_db_impl.h"
|
||||
#include "utilities/transactions/optimistic_transaction.h"
|
||||
#include "utilities/transactions/optimistic_transaction_db_impl.h"
|
||||
|
||||
namespace {
|
||||
int kBlockBasedTableVersionFormat = 2;
|
||||
@@ -305,8 +305,8 @@ void BlobDBImpl::StartBackgroundTasks() {
|
||||
kDeleteCheckPeriodMillisecs,
|
||||
std::bind(&BlobDBImpl::EvictCompacted, this, std::placeholders::_1));
|
||||
tqueue_.add(
|
||||
kDeleteObsoletedFilesPeriodMillisecs,
|
||||
std::bind(&BlobDBImpl::DeleteObsFiles, this, std::placeholders::_1));
|
||||
kDeleteObsoleteFilesPeriodMillisecs,
|
||||
std::bind(&BlobDBImpl::DeleteObsoleteFiles, this, std::placeholders::_1));
|
||||
tqueue_.add(kSanityCheckPeriodMillisecs,
|
||||
std::bind(&BlobDBImpl::SanityCheck, this, std::placeholders::_1));
|
||||
tqueue_.add(kWriteAmplificationStatsPeriodMillisecs,
|
||||
@@ -891,7 +891,10 @@ Status BlobDBImpl::Write(const WriteOptions& opts, WriteBatch* updates) {
|
||||
}
|
||||
|
||||
if (blob_inserter.has_put()) {
|
||||
CloseIf(blob_inserter.last_file());
|
||||
s = CloseBlobFileIfNeeded(blob_inserter.last_file());
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
// add deleted key to list of keys that have been deleted for book-keeping
|
||||
@@ -1022,7 +1025,9 @@ Status BlobDBImpl::PutUntil(const WriteOptions& options,
|
||||
extendTTL(&(bfile->ttl_range_), expiration);
|
||||
}
|
||||
|
||||
CloseIf(bfile);
|
||||
if (s.ok()) {
|
||||
s = CloseBlobFileIfNeeded(bfile);
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT("BlobDBImpl::PutUntil:Finish");
|
||||
return s;
|
||||
@@ -1117,15 +1122,25 @@ Status BlobDBImpl::AppendSN(const std::shared_ptr<BlobFile>& bfile,
|
||||
}
|
||||
|
||||
std::vector<Status> BlobDBImpl::MultiGet(
|
||||
const ReadOptions& options,
|
||||
const ReadOptions& read_options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_family,
|
||||
const std::vector<Slice>& keys, std::vector<std::string>* values) {
|
||||
// Get a snapshot to avoid blob file get deleted between we
|
||||
// fetch and index entry and reading from the file.
|
||||
ReadOptions ro(read_options);
|
||||
bool snapshot_created = SetSnapshotIfNeeded(&ro);
|
||||
std::vector<std::string> values_lsm;
|
||||
values_lsm.resize(keys.size());
|
||||
auto statuses = db_->MultiGet(options, column_family, keys, &values_lsm);
|
||||
auto statuses = db_->MultiGet(ro, column_family, keys, &values_lsm);
|
||||
TEST_SYNC_POINT("BlobDBImpl::MultiGet:AfterIndexEntryGet:1");
|
||||
TEST_SYNC_POINT("BlobDBImpl::MultiGet:AfterIndexEntryGet:2");
|
||||
|
||||
values->resize(keys.size());
|
||||
assert(statuses.size() == keys.size());
|
||||
for (size_t i = 0; i < keys.size(); ++i) {
|
||||
if (!statuses[i].ok()) continue;
|
||||
if (!statuses[i].ok()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family[i]);
|
||||
auto cfd = cfh->cfd();
|
||||
@@ -1133,9 +1148,21 @@ std::vector<Status> BlobDBImpl::MultiGet(
|
||||
Status s = CommonGet(cfd, keys[i], values_lsm[i], &((*values)[i]));
|
||||
statuses[i] = s;
|
||||
}
|
||||
if (snapshot_created) {
|
||||
db_->ReleaseSnapshot(ro.snapshot);
|
||||
}
|
||||
return statuses;
|
||||
}
|
||||
|
||||
bool BlobDBImpl::SetSnapshotIfNeeded(ReadOptions* read_options) {
|
||||
assert(read_options != nullptr);
|
||||
if (read_options->snapshot != nullptr) {
|
||||
return false;
|
||||
}
|
||||
read_options->snapshot = db_->GetSnapshot();
|
||||
return true;
|
||||
}
|
||||
|
||||
Status BlobDBImpl::CommonGet(const ColumnFamilyData* cfd, const Slice& key,
|
||||
const std::string& index_entry, std::string* value,
|
||||
SequenceNumber* sequence) {
|
||||
@@ -1172,11 +1199,6 @@ Status BlobDBImpl::CommonGet(const ColumnFamilyData* cfd, const Slice& key,
|
||||
bfile = hitr->second;
|
||||
}
|
||||
|
||||
if (bfile->Obsolete()) {
|
||||
return Status::NotFound(
|
||||
"Blob Not Found as blob file was garbage collected");
|
||||
}
|
||||
|
||||
// 0 - size
|
||||
if (!handle.size() && value != nullptr) {
|
||||
value->clear();
|
||||
@@ -1274,25 +1296,30 @@ Status BlobDBImpl::CommonGet(const ColumnFamilyData* cfd, const Slice& key,
|
||||
return s;
|
||||
}
|
||||
|
||||
Status BlobDBImpl::Get(const ReadOptions& options,
|
||||
Status BlobDBImpl::Get(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value) {
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
auto cfd = cfh->cfd();
|
||||
|
||||
// Get a snapshot to avoid blob file get deleted between we
|
||||
// fetch and index entry and reading from the file.
|
||||
// TODO(yiwu): For Get() retry if file not found would be a simpler strategy.
|
||||
ReadOptions ro(read_options);
|
||||
bool snapshot_created = SetSnapshotIfNeeded(&ro);
|
||||
|
||||
Status s;
|
||||
std::string index_entry;
|
||||
s = db_->Get(options, column_family, key, &index_entry);
|
||||
if (!s.ok()) {
|
||||
if (debug_level_ >= 3)
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Get Failed on LSM KEY: %s status: '%s'",
|
||||
key.ToString().c_str(), s.ToString().c_str());
|
||||
return s;
|
||||
s = db_->Get(ro, column_family, key, &index_entry);
|
||||
TEST_SYNC_POINT("BlobDBImpl::Get:AfterIndexEntryGet:1");
|
||||
TEST_SYNC_POINT("BlobDBImpl::Get:AfterIndexEntryGet:2");
|
||||
if (s.ok()) {
|
||||
s = CommonGet(cfd, key, index_entry, value->GetSelf());
|
||||
value->PinSelf();
|
||||
}
|
||||
if (snapshot_created) {
|
||||
db_->ReleaseSnapshot(ro.snapshot);
|
||||
}
|
||||
|
||||
s = CommonGet(cfd, key, index_entry, value->GetSelf());
|
||||
value->PinSelf();
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -1302,6 +1329,8 @@ Slice BlobDBIterator::value() const {
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(cfh_);
|
||||
auto cfd = cfh->cfd();
|
||||
|
||||
TEST_SYNC_POINT("BlobDBIterator::value:BeforeGetBlob:1");
|
||||
TEST_SYNC_POINT("BlobDBIterator::value:BeforeGetBlob:2");
|
||||
Status s = db_impl_->CommonGet(cfd, iter_->key(), index_entry.ToString(false),
|
||||
&vpart_);
|
||||
return Slice(vpart_);
|
||||
@@ -1338,58 +1367,44 @@ std::pair<bool, int64_t> BlobDBImpl::SanityCheck(bool aborted) {
|
||||
return std::make_pair(true, -1);
|
||||
}
|
||||
|
||||
std::pair<bool, int64_t> BlobDBImpl::CloseSeqWrite(
|
||||
std::shared_ptr<BlobFile> bfile, bool aborted) {
|
||||
Status BlobDBImpl::CloseBlobFile(std::shared_ptr<BlobFile> bfile) {
|
||||
Status s;
|
||||
ROCKS_LOG_INFO(db_options_.info_log, "Close blob file %" PRIu64,
|
||||
bfile->BlobFileNumber());
|
||||
{
|
||||
WriteLock wl(&mutex_);
|
||||
|
||||
// this prevents others from picking up this file
|
||||
open_blob_files_.erase(bfile);
|
||||
|
||||
auto findit =
|
||||
std::find(open_simple_files_.begin(), open_simple_files_.end(), bfile);
|
||||
if (findit != open_simple_files_.end()) open_simple_files_.erase(findit);
|
||||
if (bfile->HasTTL()) {
|
||||
size_t erased __attribute__((__unused__)) = open_blob_files_.erase(bfile);
|
||||
assert(erased == 1);
|
||||
} else {
|
||||
auto iter = std::find(open_simple_files_.begin(),
|
||||
open_simple_files_.end(), bfile);
|
||||
assert(iter != open_simple_files_.end());
|
||||
open_simple_files_.erase(iter);
|
||||
}
|
||||
}
|
||||
|
||||
if (!bfile->closed_.load()) {
|
||||
WriteLock lockbfile_w(&bfile->mutex_);
|
||||
bfile->WriteFooterAndCloseLocked();
|
||||
s = bfile->WriteFooterAndCloseLocked();
|
||||
}
|
||||
|
||||
return std::make_pair(false, -1);
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_ERROR(db_options_.info_log,
|
||||
"Failed to close blob file %" PRIu64 "with error: %s",
|
||||
bfile->BlobFileNumber(), s.ToString().c_str());
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
void BlobDBImpl::CloseIf(const std::shared_ptr<BlobFile>& bfile) {
|
||||
Status BlobDBImpl::CloseBlobFileIfNeeded(std::shared_ptr<BlobFile>& bfile) {
|
||||
// atomic read
|
||||
bool close = bfile->GetFileSize() > bdb_options_.blob_file_size;
|
||||
if (!close) return;
|
||||
|
||||
if (debug_level_ >= 2) {
|
||||
ROCKS_LOG_DEBUG(db_options_.info_log,
|
||||
"Scheduling file for close %s fsize: %" PRIu64
|
||||
" limit: %" PRIu64,
|
||||
bfile->PathName().c_str(), bfile->GetFileSize(),
|
||||
bdb_options_.blob_file_size);
|
||||
if (bfile->GetFileSize() < bdb_options_.blob_file_size) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
{
|
||||
WriteLock wl(&mutex_);
|
||||
|
||||
open_blob_files_.erase(bfile);
|
||||
auto findit =
|
||||
std::find(open_simple_files_.begin(), open_simple_files_.end(), bfile);
|
||||
if (findit != open_simple_files_.end()) {
|
||||
open_simple_files_.erase(findit);
|
||||
} else {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"File not found while closing %s fsize: %" PRIu64
|
||||
" Multithreaded Writes?",
|
||||
bfile->PathName().c_str(), bfile->GetFileSize());
|
||||
}
|
||||
}
|
||||
|
||||
tqueue_.add(0, std::bind(&BlobDBImpl::CloseSeqWrite, this, bfile,
|
||||
std::placeholders::_1));
|
||||
return CloseBlobFile(bfile);
|
||||
}
|
||||
|
||||
bool BlobDBImpl::FileDeleteOk_SnapshotCheckLocked(
|
||||
@@ -1560,7 +1575,9 @@ std::pair<bool, int64_t> BlobDBImpl::CheckSeqFiles(bool aborted) {
|
||||
}
|
||||
}
|
||||
|
||||
for (auto bfile : process_files) CloseSeqWrite(bfile, false);
|
||||
for (auto bfile : process_files) {
|
||||
CloseBlobFile(bfile);
|
||||
}
|
||||
|
||||
return std::make_pair(true, -1);
|
||||
}
|
||||
@@ -1615,12 +1632,13 @@ std::pair<bool, int64_t> BlobDBImpl::ReclaimOpenFiles(bool aborted) {
|
||||
return std::make_pair(true, -1);
|
||||
}
|
||||
|
||||
// TODO(yiwu): correct the stats and expose it.
|
||||
std::pair<bool, int64_t> BlobDBImpl::WaStats(bool aborted) {
|
||||
if (aborted) return std::make_pair(false, -1);
|
||||
|
||||
WriteLock wl(&mutex_);
|
||||
|
||||
if (all_periods_write_.size() < kWriteAmplificationStatsPeriods) {
|
||||
if (all_periods_write_.size() >= kWriteAmplificationStatsPeriods) {
|
||||
total_periods_write_ -= (*all_periods_write_.begin());
|
||||
total_periods_ampl_ = (*all_periods_ampl_.begin());
|
||||
|
||||
@@ -1889,7 +1907,8 @@ Status BlobDBImpl::GCFileAndUpdateLSM(const std::shared_ptr<BlobFile>& bfptr,
|
||||
delete transaction;
|
||||
}
|
||||
ROCKS_LOG_INFO(
|
||||
db_options_.info_log, "%s blob file %" PRIu64 ".",
|
||||
db_options_.info_log,
|
||||
"%s blob file %" PRIu64
|
||||
". Total blob records: %" PRIu64 ", Deletes: %" PRIu64 "/%" PRIu64
|
||||
" succeeded, Relocates: %" PRIu64 "/%" PRIu64 " succeeded.",
|
||||
s.ok() ? "Successfully garbage collected" : "Failed to garbage collect",
|
||||
@@ -1908,7 +1927,8 @@ Status BlobDBImpl::GCFileAndUpdateLSM(const std::shared_ptr<BlobFile>& bfptr,
|
||||
// but under the asusmption that this is only called when a
|
||||
// file is Immutable, we can reduce the critical section
|
||||
bool BlobDBImpl::ShouldGCFile(std::shared_ptr<BlobFile> bfile, uint64_t now,
|
||||
uint64_t last_id, std::string* reason) {
|
||||
bool is_oldest_simple_blob_file,
|
||||
std::string* reason) {
|
||||
if (bfile->HasTTL()) {
|
||||
ttlrange_t ttl_range = bfile->GetTTLRange();
|
||||
if (now > ttl_range.second) {
|
||||
@@ -1965,16 +1985,15 @@ bool BlobDBImpl::ShouldGCFile(std::shared_ptr<BlobFile> bfile, uint64_t now,
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ret = bfile->BlobFileNumber() == last_id;
|
||||
if (ret) {
|
||||
*reason = "eligible last simple blob file";
|
||||
} else {
|
||||
*reason = "not eligible since not last simple blob file";
|
||||
if (is_oldest_simple_blob_file) {
|
||||
*reason = "out of space and is the oldest simple blob file";
|
||||
return true;
|
||||
}
|
||||
return ret;
|
||||
*reason = "out of space but is not the oldest simple blob file";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::pair<bool, int64_t> BlobDBImpl::DeleteObsFiles(bool aborted) {
|
||||
std::pair<bool, int64_t> BlobDBImpl::DeleteObsoleteFiles(bool aborted) {
|
||||
if (aborted) return std::make_pair(false, -1);
|
||||
|
||||
{
|
||||
@@ -1999,6 +2018,7 @@ std::pair<bool, int64_t> BlobDBImpl::DeleteObsFiles(bool aborted) {
|
||||
}
|
||||
}
|
||||
|
||||
blob_files_.erase(bfile->BlobFileNumber());
|
||||
Status s = env_->DeleteFile(bfile->PathName());
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_ERROR(db_options_.info_log,
|
||||
@@ -2023,7 +2043,9 @@ std::pair<bool, int64_t> BlobDBImpl::DeleteObsFiles(bool aborted) {
|
||||
// put files back into obsolete if for some reason, delete failed
|
||||
if (!tobsolete.empty()) {
|
||||
WriteLock wl(&mutex_);
|
||||
for (auto bfile : tobsolete) obsolete_files_.push_front(bfile);
|
||||
for (auto bfile : tobsolete) {
|
||||
obsolete_files_.push_front(bfile);
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_pair(!aborted, -1);
|
||||
@@ -2095,31 +2117,27 @@ std::pair<bool, int64_t> BlobDBImpl::CallbackEvicts(
|
||||
}
|
||||
|
||||
void BlobDBImpl::CopyBlobFiles(
|
||||
std::vector<std::shared_ptr<BlobFile>>* bfiles_copy, uint64_t* last_id) {
|
||||
std::vector<std::shared_ptr<BlobFile>>* bfiles_copy) {
|
||||
ReadLock rl(&mutex_);
|
||||
|
||||
// take a copy
|
||||
bfiles_copy->reserve(blob_files_.size());
|
||||
for (auto const& ent : blob_files_) {
|
||||
bfiles_copy->push_back(ent.second);
|
||||
|
||||
// A. has ttl is immutable, once set, hence no locks required
|
||||
// B. blob files are sorted based on number(i.e. index of creation )
|
||||
// so we will return the last blob file
|
||||
if (!ent.second->HasTTL()) *last_id = ent.second->BlobFileNumber();
|
||||
for (auto const& p : blob_files_) {
|
||||
bfiles_copy->push_back(p.second);
|
||||
}
|
||||
}
|
||||
|
||||
void BlobDBImpl::FilterSubsetOfFiles(
|
||||
const std::vector<std::shared_ptr<BlobFile>>& blob_files,
|
||||
std::vector<std::shared_ptr<BlobFile>>* to_process, uint64_t epoch,
|
||||
uint64_t last_id, size_t files_to_collect) {
|
||||
size_t files_to_collect) {
|
||||
// 100.0 / 15.0 = 7
|
||||
uint64_t next_epoch_increment = static_cast<uint64_t>(
|
||||
std::ceil(100 / static_cast<double>(kGCFilePercentage)));
|
||||
uint64_t now = EpochNow();
|
||||
|
||||
size_t files_processed = 0;
|
||||
bool simple_blob_file_found = false;
|
||||
for (auto bfile : blob_files) {
|
||||
if (files_processed >= files_to_collect) break;
|
||||
// if this is the first time processing the file
|
||||
@@ -2139,8 +2157,15 @@ void BlobDBImpl::FilterSubsetOfFiles(
|
||||
// then it should not be GC'd
|
||||
if (bfile->Obsolete() || !bfile->Immutable()) continue;
|
||||
|
||||
bool is_oldest_simple_blob_file = false;
|
||||
if (!simple_blob_file_found && !bfile->HasTTL()) {
|
||||
is_oldest_simple_blob_file = true;
|
||||
simple_blob_file_found = true;
|
||||
}
|
||||
|
||||
std::string reason;
|
||||
bool shouldgc = ShouldGCFile(bfile, now, last_id, &reason);
|
||||
bool shouldgc =
|
||||
ShouldGCFile(bfile, now, is_oldest_simple_blob_file, &reason);
|
||||
if (!shouldgc) {
|
||||
ROCKS_LOG_DEBUG(db_options_.info_log,
|
||||
"File has been skipped for GC ttl %s %" PRIu64 " %" PRIu64
|
||||
@@ -2164,11 +2189,8 @@ std::pair<bool, int64_t> BlobDBImpl::RunGC(bool aborted) {
|
||||
|
||||
current_epoch_++;
|
||||
|
||||
// collect the ID of the last regular file, in case we need to GC it.
|
||||
uint64_t last_id = std::numeric_limits<uint64_t>::max();
|
||||
|
||||
std::vector<std::shared_ptr<BlobFile>> blob_files;
|
||||
CopyBlobFiles(&blob_files, &last_id);
|
||||
CopyBlobFiles(&blob_files);
|
||||
|
||||
if (!blob_files.size()) return std::make_pair(true, -1);
|
||||
|
||||
@@ -2177,7 +2199,7 @@ std::pair<bool, int64_t> BlobDBImpl::RunGC(bool aborted) {
|
||||
size_t files_to_collect = (kGCFilePercentage * blob_files.size()) / 100;
|
||||
|
||||
std::vector<std::shared_ptr<BlobFile>> to_process;
|
||||
FilterSubsetOfFiles(blob_files, &to_process, current_epoch_, last_id,
|
||||
FilterSubsetOfFiles(blob_files, &to_process, current_epoch_,
|
||||
files_to_collect);
|
||||
|
||||
// in this collect the set of files, which became obsolete
|
||||
@@ -2209,8 +2231,6 @@ std::pair<bool, int64_t> BlobDBImpl::RunGC(bool aborted) {
|
||||
WriteLock wl(&mutex_);
|
||||
for (auto bfile : obsoletes) {
|
||||
bool last_file = (bfile == obsoletes.back());
|
||||
// remove from global list so writers
|
||||
blob_files_.erase(bfile->BlobFileNumber());
|
||||
|
||||
if (!evict_cb) {
|
||||
bfile->SetCanBeDeleted();
|
||||
@@ -2228,10 +2248,14 @@ std::pair<bool, int64_t> BlobDBImpl::RunGC(bool aborted) {
|
||||
return std::make_pair(true, -1);
|
||||
}
|
||||
|
||||
Iterator* BlobDBImpl::NewIterator(const ReadOptions& opts,
|
||||
Iterator* BlobDBImpl::NewIterator(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family) {
|
||||
return new BlobDBIterator(db_->NewIterator(opts, column_family),
|
||||
column_family, this);
|
||||
// Get a snapshot to avoid blob file get deleted between we
|
||||
// fetch and index entry and reading from the file.
|
||||
ReadOptions ro(read_options);
|
||||
bool snapshot_created = SetSnapshotIfNeeded(&ro);
|
||||
return new BlobDBIterator(db_->NewIterator(ro, column_family), column_family,
|
||||
this, snapshot_created, ro.snapshot);
|
||||
}
|
||||
|
||||
Status DestroyBlobDB(const std::string& dbname, const Options& options,
|
||||
@@ -2280,6 +2304,7 @@ Status BlobDBImpl::TEST_GetSequenceNumber(const Slice& key,
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<BlobFile>> BlobDBImpl::TEST_GetBlobFiles() const {
|
||||
ReadLock l(&mutex_);
|
||||
std::vector<std::shared_ptr<BlobFile>> blob_files;
|
||||
for (auto& p : blob_files_) {
|
||||
blob_files.emplace_back(p.second);
|
||||
@@ -2287,14 +2312,40 @@ std::vector<std::shared_ptr<BlobFile>> BlobDBImpl::TEST_GetBlobFiles() const {
|
||||
return blob_files;
|
||||
}
|
||||
|
||||
void BlobDBImpl::TEST_CloseBlobFile(std::shared_ptr<BlobFile>& bfile) {
|
||||
CloseSeqWrite(bfile, false /*abort*/);
|
||||
std::vector<std::shared_ptr<BlobFile>> BlobDBImpl::TEST_GetObsoleteFiles()
|
||||
const {
|
||||
ReadLock l(&mutex_);
|
||||
std::vector<std::shared_ptr<BlobFile>> obsolete_files;
|
||||
for (auto& bfile : obsolete_files_) {
|
||||
obsolete_files.emplace_back(bfile);
|
||||
}
|
||||
return obsolete_files;
|
||||
}
|
||||
|
||||
void BlobDBImpl::TEST_DeleteObsoleteFiles() {
|
||||
DeleteObsoleteFiles(false /*abort*/);
|
||||
}
|
||||
|
||||
Status BlobDBImpl::TEST_CloseBlobFile(std::shared_ptr<BlobFile>& bfile) {
|
||||
return CloseBlobFile(bfile);
|
||||
}
|
||||
|
||||
Status BlobDBImpl::TEST_GCFileAndUpdateLSM(std::shared_ptr<BlobFile>& bfile,
|
||||
GCStats* gc_stats) {
|
||||
return GCFileAndUpdateLSM(bfile, gc_stats);
|
||||
}
|
||||
|
||||
void BlobDBImpl::TEST_RunGC() { RunGC(false /*abort*/); }
|
||||
|
||||
void BlobDBImpl::TEST_ObsoleteFile(std::shared_ptr<BlobFile>& bfile) {
|
||||
uint64_t number = bfile->BlobFileNumber();
|
||||
assert(blob_files_.count(number) > 0);
|
||||
bfile->SetCanBeDeleted();
|
||||
{
|
||||
WriteLock l(&mutex_);
|
||||
obsolete_files_.push_back(bfile);
|
||||
}
|
||||
}
|
||||
#endif // !NDEBUG
|
||||
|
||||
} // namespace blob_db
|
||||
|
||||
@@ -200,7 +200,7 @@ class BlobDBImpl : public BlobDB {
|
||||
static constexpr uint32_t kReclaimOpenFilesPeriodMillisecs = 1 * 1000;
|
||||
|
||||
// how often to schedule delete obs files periods
|
||||
static constexpr uint32_t kDeleteObsoletedFilesPeriodMillisecs = 10 * 1000;
|
||||
static constexpr uint32_t kDeleteObsoleteFilesPeriodMillisecs = 10 * 1000;
|
||||
|
||||
// how often to schedule check seq files period
|
||||
static constexpr uint32_t kCheckSeqFilesPeriodMillisecs = 10 * 1000;
|
||||
@@ -219,16 +219,16 @@ class BlobDBImpl : public BlobDB {
|
||||
const Slice& key) override;
|
||||
|
||||
using rocksdb::StackableDB::Get;
|
||||
Status Get(const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
Status Get(const ReadOptions& read_options, ColumnFamilyHandle* column_family,
|
||||
const Slice& key, PinnableSlice* value) override;
|
||||
|
||||
using rocksdb::StackableDB::NewIterator;
|
||||
virtual Iterator* NewIterator(const ReadOptions& opts,
|
||||
virtual Iterator* NewIterator(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family) override;
|
||||
|
||||
using rocksdb::StackableDB::MultiGet;
|
||||
virtual std::vector<Status> MultiGet(
|
||||
const ReadOptions& options,
|
||||
const ReadOptions& read_options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_family,
|
||||
const std::vector<Slice>& keys,
|
||||
std::vector<std::string>* values) override;
|
||||
@@ -261,15 +261,27 @@ class BlobDBImpl : public BlobDB {
|
||||
|
||||
std::vector<std::shared_ptr<BlobFile>> TEST_GetBlobFiles() const;
|
||||
|
||||
void TEST_CloseBlobFile(std::shared_ptr<BlobFile>& bfile);
|
||||
std::vector<std::shared_ptr<BlobFile>> TEST_GetObsoleteFiles() const;
|
||||
|
||||
Status TEST_CloseBlobFile(std::shared_ptr<BlobFile>& bfile);
|
||||
|
||||
Status TEST_GCFileAndUpdateLSM(std::shared_ptr<BlobFile>& bfile,
|
||||
GCStats* gc_stats);
|
||||
|
||||
void TEST_RunGC();
|
||||
|
||||
void TEST_ObsoleteFile(std::shared_ptr<BlobFile>& bfile);
|
||||
|
||||
void TEST_DeleteObsoleteFiles();
|
||||
#endif // !NDEBUG
|
||||
|
||||
private:
|
||||
Status OpenPhase1();
|
||||
|
||||
// Create a snapshot if there isn't one in read options.
|
||||
// Return true if a snapshot is created.
|
||||
bool SetSnapshotIfNeeded(ReadOptions* read_options);
|
||||
|
||||
Status CommonGet(const ColumnFamilyData* cfd, const Slice& key,
|
||||
const std::string& index_entry, std::string* value,
|
||||
SequenceNumber* sequence = nullptr);
|
||||
@@ -281,23 +293,21 @@ class BlobDBImpl : public BlobDB {
|
||||
// this handler is called.
|
||||
void OnFlushBeginHandler(DB* db, const FlushJobInfo& info);
|
||||
|
||||
// timer queue callback to close a file by appending a footer
|
||||
// removes file from open files list
|
||||
std::pair<bool, int64_t> CloseSeqWrite(std::shared_ptr<BlobFile> bfile,
|
||||
bool aborted);
|
||||
|
||||
// is this file ready for Garbage collection. if the TTL of the file
|
||||
// has expired or if threshold of the file has been evicted
|
||||
// tt - current time
|
||||
// last_id - the id of the non-TTL file to evict
|
||||
bool ShouldGCFile(std::shared_ptr<BlobFile> bfile, uint64_t now,
|
||||
uint64_t last_id, std::string* reason);
|
||||
bool is_oldest_simple_blob_file, std::string* reason);
|
||||
|
||||
// collect all the blob log files from the blob directory
|
||||
Status GetAllLogFiles(std::set<std::pair<uint64_t, std::string>>* file_nums);
|
||||
|
||||
// appends a task into timer queue to close the file
|
||||
void CloseIf(const std::shared_ptr<BlobFile>& bfile);
|
||||
// Close a file by appending a footer, and removes file from open files list.
|
||||
Status CloseBlobFile(std::shared_ptr<BlobFile> bfile);
|
||||
|
||||
// Close a file if its size exceeds blob_file_size
|
||||
Status CloseBlobFileIfNeeded(std::shared_ptr<BlobFile>& bfile);
|
||||
|
||||
uint64_t ExtractExpiration(const Slice& key, const Slice& value,
|
||||
Slice* value_slice, std::string* new_value);
|
||||
@@ -328,7 +338,7 @@ class BlobDBImpl : public BlobDB {
|
||||
// delete files which have been garbage collected and marked
|
||||
// obsolete. Check whether any snapshots exist which refer to
|
||||
// the same
|
||||
std::pair<bool, int64_t> DeleteObsFiles(bool aborted);
|
||||
std::pair<bool, int64_t> DeleteObsoleteFiles(bool aborted);
|
||||
|
||||
// Major task to garbage collect expired and deleted blobs
|
||||
std::pair<bool, int64_t> RunGC(bool aborted);
|
||||
@@ -403,13 +413,12 @@ class BlobDBImpl : public BlobDB {
|
||||
bool FindFileAndEvictABlob(uint64_t file_number, uint64_t key_size,
|
||||
uint64_t blob_offset, uint64_t blob_size);
|
||||
|
||||
void CopyBlobFiles(std::vector<std::shared_ptr<BlobFile>>* bfiles_copy,
|
||||
uint64_t* last_id);
|
||||
void CopyBlobFiles(std::vector<std::shared_ptr<BlobFile>>* bfiles_copy);
|
||||
|
||||
void FilterSubsetOfFiles(
|
||||
const std::vector<std::shared_ptr<BlobFile>>& blob_files,
|
||||
std::vector<std::shared_ptr<BlobFile>>* to_process, uint64_t epoch,
|
||||
uint64_t last_id, size_t files_to_collect);
|
||||
size_t files_to_collect);
|
||||
|
||||
uint64_t EpochNow() { return env_->NowMicros() / 1000000; }
|
||||
|
||||
@@ -445,7 +454,7 @@ class BlobDBImpl : public BlobDB {
|
||||
|
||||
// Read Write Mutex, which protects all the data structures
|
||||
// HEAVILY TRAFFICKED
|
||||
port::RWMutex mutex_;
|
||||
mutable port::RWMutex mutex_;
|
||||
|
||||
// Writers has to hold write_mutex_ before writing.
|
||||
mutable port::Mutex write_mutex_;
|
||||
@@ -454,12 +463,12 @@ class BlobDBImpl : public BlobDB {
|
||||
std::atomic<uint64_t> next_file_number_;
|
||||
|
||||
// entire metadata of all the BLOB files memory
|
||||
std::unordered_map<uint64_t, std::shared_ptr<BlobFile>> blob_files_;
|
||||
std::map<uint64_t, std::shared_ptr<BlobFile>> blob_files_;
|
||||
|
||||
// epoch or version of the open files.
|
||||
std::atomic<uint64_t> epoch_of_;
|
||||
|
||||
// typically we keep 4 open blob files (simple i.e. no TTL)
|
||||
// All opened non-TTL blob files.
|
||||
std::vector<std::shared_ptr<BlobFile>> open_simple_files_;
|
||||
|
||||
// all the blob files which are currently being appended to based
|
||||
@@ -590,7 +599,7 @@ class BlobFile {
|
||||
|
||||
// This Read-Write mutex is per file specific and protects
|
||||
// all the datastructures
|
||||
port::RWMutex mutex_;
|
||||
mutable port::RWMutex mutex_;
|
||||
|
||||
// time when the random access reader was last created.
|
||||
std::atomic<std::time_t> last_access_;
|
||||
@@ -697,12 +706,23 @@ class BlobFile {
|
||||
class BlobDBIterator : public Iterator {
|
||||
public:
|
||||
explicit BlobDBIterator(Iterator* iter, ColumnFamilyHandle* column_family,
|
||||
BlobDBImpl* impl)
|
||||
: iter_(iter), cfh_(column_family), db_impl_(impl) {
|
||||
assert(iter_);
|
||||
BlobDBImpl* impl, bool own_snapshot,
|
||||
const Snapshot* snapshot)
|
||||
: iter_(iter),
|
||||
cfh_(column_family),
|
||||
db_impl_(impl),
|
||||
own_snapshot_(own_snapshot),
|
||||
snapshot_(snapshot) {
|
||||
assert(iter != nullptr);
|
||||
assert(snapshot != nullptr);
|
||||
}
|
||||
|
||||
~BlobDBIterator() { delete iter_; }
|
||||
~BlobDBIterator() {
|
||||
if (own_snapshot_) {
|
||||
db_impl_->ReleaseSnapshot(snapshot_);
|
||||
}
|
||||
delete iter_;
|
||||
}
|
||||
|
||||
bool Valid() const override { return iter_->Valid(); }
|
||||
|
||||
@@ -724,10 +744,14 @@ class BlobDBIterator : public Iterator {
|
||||
|
||||
Status status() const override { return iter_->status(); }
|
||||
|
||||
// Iterator::Refresh() not supported.
|
||||
|
||||
private:
|
||||
Iterator* iter_;
|
||||
ColumnFamilyHandle* cfh_;
|
||||
BlobDBImpl* db_impl_;
|
||||
bool own_snapshot_;
|
||||
const Snapshot* snapshot_;
|
||||
mutable std::string vpart_;
|
||||
};
|
||||
|
||||
|
||||
@@ -185,7 +185,7 @@ TEST_F(BlobDBTest, PutWithTTL) {
|
||||
auto blob_files = bdb_impl->TEST_GetBlobFiles();
|
||||
ASSERT_EQ(1, blob_files.size());
|
||||
ASSERT_TRUE(blob_files[0]->HasTTL());
|
||||
bdb_impl->TEST_CloseBlobFile(blob_files[0]);
|
||||
ASSERT_OK(bdb_impl->TEST_CloseBlobFile(blob_files[0]));
|
||||
GCStats gc_stats;
|
||||
ASSERT_OK(bdb_impl->TEST_GCFileAndUpdateLSM(blob_files[0], &gc_stats));
|
||||
ASSERT_EQ(100 - data.size(), gc_stats.num_deletes);
|
||||
@@ -214,7 +214,7 @@ TEST_F(BlobDBTest, PutUntil) {
|
||||
auto blob_files = bdb_impl->TEST_GetBlobFiles();
|
||||
ASSERT_EQ(1, blob_files.size());
|
||||
ASSERT_TRUE(blob_files[0]->HasTTL());
|
||||
bdb_impl->TEST_CloseBlobFile(blob_files[0]);
|
||||
ASSERT_OK(bdb_impl->TEST_CloseBlobFile(blob_files[0]));
|
||||
GCStats gc_stats;
|
||||
ASSERT_OK(bdb_impl->TEST_GCFileAndUpdateLSM(blob_files[0], &gc_stats));
|
||||
ASSERT_EQ(100 - data.size(), gc_stats.num_deletes);
|
||||
@@ -246,7 +246,7 @@ TEST_F(BlobDBTest, TTLExtrator_NoTTL) {
|
||||
auto blob_files = bdb_impl->TEST_GetBlobFiles();
|
||||
ASSERT_EQ(1, blob_files.size());
|
||||
ASSERT_FALSE(blob_files[0]->HasTTL());
|
||||
bdb_impl->TEST_CloseBlobFile(blob_files[0]);
|
||||
ASSERT_OK(bdb_impl->TEST_CloseBlobFile(blob_files[0]));
|
||||
GCStats gc_stats;
|
||||
ASSERT_OK(bdb_impl->TEST_GCFileAndUpdateLSM(blob_files[0], &gc_stats));
|
||||
ASSERT_EQ(0, gc_stats.num_deletes);
|
||||
@@ -291,7 +291,7 @@ TEST_F(BlobDBTest, TTLExtractor_ExtractTTL) {
|
||||
auto blob_files = bdb_impl->TEST_GetBlobFiles();
|
||||
ASSERT_EQ(1, blob_files.size());
|
||||
ASSERT_TRUE(blob_files[0]->HasTTL());
|
||||
bdb_impl->TEST_CloseBlobFile(blob_files[0]);
|
||||
ASSERT_OK(bdb_impl->TEST_CloseBlobFile(blob_files[0]));
|
||||
GCStats gc_stats;
|
||||
ASSERT_OK(bdb_impl->TEST_GCFileAndUpdateLSM(blob_files[0], &gc_stats));
|
||||
auto &data = static_cast<TestTTLExtractor *>(ttl_extractor_.get())->data;
|
||||
@@ -338,7 +338,7 @@ TEST_F(BlobDBTest, TTLExtractor_ExtractExpiration) {
|
||||
auto blob_files = bdb_impl->TEST_GetBlobFiles();
|
||||
ASSERT_EQ(1, blob_files.size());
|
||||
ASSERT_TRUE(blob_files[0]->HasTTL());
|
||||
bdb_impl->TEST_CloseBlobFile(blob_files[0]);
|
||||
ASSERT_OK(bdb_impl->TEST_CloseBlobFile(blob_files[0]));
|
||||
GCStats gc_stats;
|
||||
ASSERT_OK(bdb_impl->TEST_GCFileAndUpdateLSM(blob_files[0], &gc_stats));
|
||||
auto &data = static_cast<TestTTLExtractor *>(ttl_extractor_.get())->data;
|
||||
@@ -395,7 +395,7 @@ TEST_F(BlobDBTest, TTLExtractor_ChangeValue) {
|
||||
auto blob_files = bdb_impl->TEST_GetBlobFiles();
|
||||
ASSERT_EQ(1, blob_files.size());
|
||||
ASSERT_TRUE(blob_files[0]->HasTTL());
|
||||
bdb_impl->TEST_CloseBlobFile(blob_files[0]);
|
||||
ASSERT_OK(bdb_impl->TEST_CloseBlobFile(blob_files[0]));
|
||||
GCStats gc_stats;
|
||||
ASSERT_OK(bdb_impl->TEST_GCFileAndUpdateLSM(blob_files[0], &gc_stats));
|
||||
ASSERT_EQ(100 - data.size(), gc_stats.num_deletes);
|
||||
@@ -592,7 +592,7 @@ TEST_F(BlobDBTest, GCAfterOverwriteKeys) {
|
||||
}
|
||||
auto blob_files = blob_db_impl->TEST_GetBlobFiles();
|
||||
ASSERT_EQ(1, blob_files.size());
|
||||
blob_db_impl->TEST_CloseBlobFile(blob_files[0]);
|
||||
ASSERT_OK(blob_db_impl->TEST_CloseBlobFile(blob_files[0]));
|
||||
// Test for data in SST
|
||||
size_t new_keys = 0;
|
||||
for (int i = 0; i < 100; i++) {
|
||||
@@ -617,7 +617,7 @@ TEST_F(BlobDBTest, GCAfterOverwriteKeys) {
|
||||
VerifyDB(data);
|
||||
}
|
||||
|
||||
TEST_F(BlobDBTest, GCRelocateKeyWhileOverwritting) {
|
||||
TEST_F(BlobDBTest, GCRelocateKeyWhileOverwriting) {
|
||||
Random rnd(301);
|
||||
BlobDBOptions bdb_options;
|
||||
bdb_options.disable_background_tasks = true;
|
||||
@@ -627,7 +627,7 @@ TEST_F(BlobDBTest, GCRelocateKeyWhileOverwritting) {
|
||||
static_cast_with_check<BlobDBImpl, BlobDB>(blob_db_);
|
||||
auto blob_files = blob_db_impl->TEST_GetBlobFiles();
|
||||
ASSERT_EQ(1, blob_files.size());
|
||||
blob_db_impl->TEST_CloseBlobFile(blob_files[0]);
|
||||
ASSERT_OK(blob_db_impl->TEST_CloseBlobFile(blob_files[0]));
|
||||
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"BlobDBImpl::GCFileAndUpdateLSM:AfterGetForUpdate",
|
||||
@@ -650,7 +650,7 @@ TEST_F(BlobDBTest, GCRelocateKeyWhileOverwritting) {
|
||||
VerifyDB({{"foo", "v2"}});
|
||||
}
|
||||
|
||||
TEST_F(BlobDBTest, GCExpiredKeyWhileOverwritting) {
|
||||
TEST_F(BlobDBTest, GCExpiredKeyWhileOverwriting) {
|
||||
Random rnd(301);
|
||||
Options options;
|
||||
options.env = mock_env_.get();
|
||||
@@ -663,7 +663,7 @@ TEST_F(BlobDBTest, GCExpiredKeyWhileOverwritting) {
|
||||
static_cast_with_check<BlobDBImpl, BlobDB>(blob_db_);
|
||||
auto blob_files = blob_db_impl->TEST_GetBlobFiles();
|
||||
ASSERT_EQ(1, blob_files.size());
|
||||
blob_db_impl->TEST_CloseBlobFile(blob_files[0]);
|
||||
ASSERT_OK(blob_db_impl->TEST_CloseBlobFile(blob_files[0]));
|
||||
mock_env_->set_now_micros(300 * 1000000);
|
||||
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
@@ -688,6 +688,132 @@ TEST_F(BlobDBTest, GCExpiredKeyWhileOverwritting) {
|
||||
VerifyDB({{"foo", "v2"}});
|
||||
}
|
||||
|
||||
TEST_F(BlobDBTest, GCOldestSimpleBlobFileWhenOutOfSpace) {
|
||||
// Use mock env to stop wall clock.
|
||||
Options options;
|
||||
options.env = mock_env_.get();
|
||||
BlobDBOptions bdb_options;
|
||||
bdb_options.blob_dir_size = 100;
|
||||
bdb_options.blob_file_size = 100;
|
||||
bdb_options.disable_background_tasks = true;
|
||||
Open(bdb_options);
|
||||
std::string value(100, 'v');
|
||||
ASSERT_OK(blob_db_->PutWithTTL(WriteOptions(), "key_with_ttl", value, 60));
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ASSERT_OK(blob_db_->Put(WriteOptions(), "key" + ToString(i), value));
|
||||
}
|
||||
BlobDBImpl *blob_db_impl =
|
||||
static_cast_with_check<BlobDBImpl, BlobDB>(blob_db_);
|
||||
auto blob_files = blob_db_impl->TEST_GetBlobFiles();
|
||||
ASSERT_EQ(11, blob_files.size());
|
||||
ASSERT_TRUE(blob_files[0]->HasTTL());
|
||||
ASSERT_TRUE(blob_files[0]->Immutable());
|
||||
for (int i = 1; i <= 10; i++) {
|
||||
ASSERT_FALSE(blob_files[i]->HasTTL());
|
||||
if (i < 10) {
|
||||
ASSERT_TRUE(blob_files[i]->Immutable());
|
||||
}
|
||||
}
|
||||
blob_db_impl->TEST_RunGC();
|
||||
// The oldest simple blob file (i.e. blob_files[1]) has been selected for GC.
|
||||
auto obsolete_files = blob_db_impl->TEST_GetObsoleteFiles();
|
||||
ASSERT_EQ(1, obsolete_files.size());
|
||||
ASSERT_EQ(blob_files[1]->BlobFileNumber(),
|
||||
obsolete_files[0]->BlobFileNumber());
|
||||
}
|
||||
|
||||
TEST_F(BlobDBTest, ReadWhileGC) {
|
||||
// run the same test for Get(), MultiGet() and Iterator each.
|
||||
for (int i = 0; i < 3; i++) {
|
||||
BlobDBOptions bdb_options;
|
||||
bdb_options.disable_background_tasks = true;
|
||||
Open(bdb_options);
|
||||
blob_db_->Put(WriteOptions(), "foo", "bar");
|
||||
BlobDBImpl *blob_db_impl =
|
||||
static_cast_with_check<BlobDBImpl, BlobDB>(blob_db_);
|
||||
auto blob_files = blob_db_impl->TEST_GetBlobFiles();
|
||||
ASSERT_EQ(1, blob_files.size());
|
||||
std::shared_ptr<BlobFile> bfile = blob_files[0];
|
||||
uint64_t bfile_number = bfile->BlobFileNumber();
|
||||
ASSERT_OK(blob_db_impl->TEST_CloseBlobFile(bfile));
|
||||
|
||||
switch (i) {
|
||||
case 0:
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"BlobDBImpl::Get:AfterIndexEntryGet:1",
|
||||
"BlobDBTest::ReadWhileGC:1"},
|
||||
{"BlobDBTest::ReadWhileGC:2",
|
||||
"BlobDBImpl::Get:AfterIndexEntryGet:2"}});
|
||||
break;
|
||||
case 1:
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"BlobDBImpl::MultiGet:AfterIndexEntryGet:1",
|
||||
"BlobDBTest::ReadWhileGC:1"},
|
||||
{"BlobDBTest::ReadWhileGC:2",
|
||||
"BlobDBImpl::MultiGet:AfterIndexEntryGet:2"}});
|
||||
break;
|
||||
case 2:
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"BlobDBIterator::value:BeforeGetBlob:1",
|
||||
"BlobDBTest::ReadWhileGC:1"},
|
||||
{"BlobDBTest::ReadWhileGC:2",
|
||||
"BlobDBIterator::value:BeforeGetBlob:2"}});
|
||||
break;
|
||||
}
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
auto reader = port::Thread([this, i]() {
|
||||
std::string value;
|
||||
std::vector<std::string> values;
|
||||
std::vector<Status> statuses;
|
||||
switch (i) {
|
||||
case 0:
|
||||
ASSERT_OK(blob_db_->Get(ReadOptions(), "foo", &value));
|
||||
ASSERT_EQ("bar", value);
|
||||
break;
|
||||
case 1:
|
||||
statuses = blob_db_->MultiGet(ReadOptions(), {"foo"}, &values);
|
||||
ASSERT_EQ(1, statuses.size());
|
||||
ASSERT_EQ(1, values.size());
|
||||
ASSERT_EQ("bar", values[0]);
|
||||
break;
|
||||
case 2:
|
||||
// VerifyDB use iterator to scan the DB.
|
||||
VerifyDB({{"foo", "bar"}});
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
TEST_SYNC_POINT("BlobDBTest::ReadWhileGC:1");
|
||||
GCStats gc_stats;
|
||||
ASSERT_OK(blob_db_impl->TEST_GCFileAndUpdateLSM(bfile, &gc_stats));
|
||||
ASSERT_EQ(1, gc_stats.blob_count);
|
||||
ASSERT_EQ(1, gc_stats.num_relocate);
|
||||
ASSERT_EQ(1, gc_stats.relocate_succeeded);
|
||||
blob_db_impl->TEST_ObsoleteFile(blob_files[0]);
|
||||
blob_db_impl->TEST_DeleteObsoleteFiles();
|
||||
// The file shouln't be deleted
|
||||
blob_files = blob_db_impl->TEST_GetBlobFiles();
|
||||
ASSERT_EQ(2, blob_files.size());
|
||||
ASSERT_EQ(bfile_number, blob_files[0]->BlobFileNumber());
|
||||
auto obsolete_files = blob_db_impl->TEST_GetObsoleteFiles();
|
||||
ASSERT_EQ(1, obsolete_files.size());
|
||||
ASSERT_EQ(bfile_number, obsolete_files[0]->BlobFileNumber());
|
||||
TEST_SYNC_POINT("BlobDBTest::ReadWhileGC:2");
|
||||
reader.join();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
// The file is deleted this time
|
||||
blob_db_impl->TEST_DeleteObsoleteFiles();
|
||||
blob_files = blob_db_impl->TEST_GetBlobFiles();
|
||||
ASSERT_EQ(1, blob_files.size());
|
||||
ASSERT_NE(bfile_number, blob_files[0]->BlobFileNumber());
|
||||
ASSERT_EQ(0, blob_db_impl->TEST_GetObsoleteFiles().size());
|
||||
VerifyDB({{"foo", "bar"}});
|
||||
Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blob_db
|
||||
} // namespace rocksdb
|
||||
|
||||
|
||||
@@ -47,27 +47,6 @@ bool CassandraValueMergeOperator::FullMergeV2(
|
||||
return true;
|
||||
}
|
||||
|
||||
// Implementation for the merge operation (merges two Cassandra values)
|
||||
bool CassandraValueMergeOperator::PartialMerge(const Slice& key,
|
||||
const Slice& left_operand,
|
||||
const Slice& right_operand,
|
||||
std::string* new_value,
|
||||
Logger* logger) const {
|
||||
// Clear the *new_value for writing.
|
||||
assert(new_value);
|
||||
new_value->clear();
|
||||
|
||||
std::vector<RowValue> row_values;
|
||||
row_values.push_back(RowValue::Deserialize(left_operand.data(),
|
||||
left_operand.size()));
|
||||
row_values.push_back(RowValue::Deserialize(right_operand.data(),
|
||||
right_operand.size()));
|
||||
RowValue merged = RowValue::Merge(std::move(row_values));
|
||||
new_value->reserve(merged.Size());
|
||||
merged.Serialize(new_value);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CassandraValueMergeOperator::PartialMergeMulti(
|
||||
const Slice& key,
|
||||
const std::deque<Slice>& operand_list,
|
||||
|
||||
@@ -20,18 +20,14 @@ public:
|
||||
virtual bool FullMergeV2(const MergeOperationInput& merge_in,
|
||||
MergeOperationOutput* merge_out) const override;
|
||||
|
||||
virtual bool PartialMerge(const Slice& key,
|
||||
const Slice& left_operand,
|
||||
const Slice& right_operand,
|
||||
std::string* new_value,
|
||||
Logger* logger) const override;
|
||||
|
||||
virtual bool PartialMergeMulti(const Slice& key,
|
||||
const std::deque<Slice>& operand_list,
|
||||
std::string* new_value,
|
||||
Logger* logger) const override;
|
||||
|
||||
virtual const char* Name() const override;
|
||||
|
||||
virtual bool AllowSingleOperand() const override { return true; }
|
||||
};
|
||||
} // namespace cassandra
|
||||
} // namespace rocksdb
|
||||
@@ -25,6 +25,8 @@ class MaxOperator : public MergeOperator {
|
||||
if (merge_in.existing_value) {
|
||||
max = Slice(merge_in.existing_value->data(),
|
||||
merge_in.existing_value->size());
|
||||
} else if (max.data() == nullptr) {
|
||||
max = Slice();
|
||||
}
|
||||
|
||||
for (const auto& op : merge_in.operand_list) {
|
||||
|
||||
@@ -44,12 +44,9 @@ void OptimisticTransaction::Reinitialize(
|
||||
Initialize(txn_options);
|
||||
}
|
||||
|
||||
OptimisticTransaction::~OptimisticTransaction() {
|
||||
}
|
||||
OptimisticTransaction::~OptimisticTransaction() {}
|
||||
|
||||
void OptimisticTransaction::Clear() {
|
||||
TransactionBaseImpl::Clear();
|
||||
}
|
||||
void OptimisticTransaction::Clear() { TransactionBaseImpl::Clear(); }
|
||||
|
||||
Status OptimisticTransaction::Prepare() {
|
||||
return Status::InvalidArgument(
|
||||
@@ -82,8 +79,8 @@ Status OptimisticTransaction::Rollback() {
|
||||
//
|
||||
// 'exclusive' is unused for OptimisticTransaction.
|
||||
Status OptimisticTransaction::TryLock(ColumnFamilyHandle* column_family,
|
||||
const Slice& key, bool read_only,
|
||||
bool exclusive, bool untracked) {
|
||||
const Slice& key, bool read_only,
|
||||
bool exclusive, bool untracked) {
|
||||
if (untracked) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@ namespace rocksdb {
|
||||
class OptimisticTransaction : public TransactionBaseImpl {
|
||||
public:
|
||||
OptimisticTransaction(OptimisticTransactionDB* db,
|
||||
const WriteOptions& write_options,
|
||||
const OptimisticTransactionOptions& txn_options);
|
||||
const WriteOptions& write_options,
|
||||
const OptimisticTransactionOptions& txn_options);
|
||||
|
||||
virtual ~OptimisticTransaction();
|
||||
|
||||
|
||||
@@ -35,9 +35,9 @@ TransactionID PessimisticTransaction::GenTxnID() {
|
||||
return txn_id_counter_.fetch_add(1);
|
||||
}
|
||||
|
||||
PessimisticTransaction::PessimisticTransaction(TransactionDB* txn_db,
|
||||
const WriteOptions& write_options,
|
||||
const TransactionOptions& txn_options)
|
||||
PessimisticTransaction::PessimisticTransaction(
|
||||
TransactionDB* txn_db, const WriteOptions& write_options,
|
||||
const TransactionOptions& txn_options)
|
||||
: TransactionBaseImpl(txn_db->GetRootDB(), write_options),
|
||||
txn_db_impl_(nullptr),
|
||||
expiration_time_(0),
|
||||
@@ -99,9 +99,9 @@ void PessimisticTransaction::Clear() {
|
||||
TransactionBaseImpl::Clear();
|
||||
}
|
||||
|
||||
void PessimisticTransaction::Reinitialize(TransactionDB* txn_db,
|
||||
const WriteOptions& write_options,
|
||||
const TransactionOptions& txn_options) {
|
||||
void PessimisticTransaction::Reinitialize(
|
||||
TransactionDB* txn_db, const WriteOptions& write_options,
|
||||
const TransactionOptions& txn_options) {
|
||||
if (!name_.empty() && txn_state_ != COMMITED) {
|
||||
txn_db_impl_->UnregisterTransaction(this);
|
||||
}
|
||||
@@ -120,9 +120,9 @@ bool PessimisticTransaction::IsExpired() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
WriteCommittedTxn::WriteCommittedTxn(
|
||||
TransactionDB* txn_db, const WriteOptions& write_options,
|
||||
const TransactionOptions& txn_options)
|
||||
WriteCommittedTxn::WriteCommittedTxn(TransactionDB* txn_db,
|
||||
const WriteOptions& write_options,
|
||||
const TransactionOptions& txn_options)
|
||||
: PessimisticTransaction(txn_db, write_options, txn_options){};
|
||||
|
||||
Status WriteCommittedTxn::CommitBatch(WriteBatch* batch) {
|
||||
@@ -370,7 +370,7 @@ Status PessimisticTransaction::RollbackToSavePoint() {
|
||||
// Lock all keys in this batch.
|
||||
// On success, caller should unlock keys_to_unlock
|
||||
Status PessimisticTransaction::LockBatch(WriteBatch* batch,
|
||||
TransactionKeyMap* keys_to_unlock) {
|
||||
TransactionKeyMap* keys_to_unlock) {
|
||||
class Handler : public WriteBatch::Handler {
|
||||
public:
|
||||
// Sorted map of column_family_id to sorted set of keys.
|
||||
@@ -448,8 +448,8 @@ Status PessimisticTransaction::LockBatch(WriteBatch* batch,
|
||||
// this key will only be locked if there have been no writes to this key since
|
||||
// the snapshot time.
|
||||
Status PessimisticTransaction::TryLock(ColumnFamilyHandle* column_family,
|
||||
const Slice& key, bool read_only, bool exclusive,
|
||||
bool untracked) {
|
||||
const Slice& key, bool read_only,
|
||||
bool exclusive, bool untracked) {
|
||||
uint32_t cfh_id = GetColumnFamilyID(column_family);
|
||||
std::string key_str = key.ToString();
|
||||
bool previously_locked;
|
||||
@@ -535,10 +535,9 @@ Status PessimisticTransaction::TryLock(ColumnFamilyHandle* column_family,
|
||||
|
||||
// Return OK() if this key has not been modified more recently than the
|
||||
// transaction snapshot_.
|
||||
Status PessimisticTransaction::ValidateSnapshot(ColumnFamilyHandle* column_family,
|
||||
const Slice& key,
|
||||
SequenceNumber prev_seqno,
|
||||
SequenceNumber* new_seqno) {
|
||||
Status PessimisticTransaction::ValidateSnapshot(
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
SequenceNumber prev_seqno, SequenceNumber* new_seqno) {
|
||||
assert(snapshot_);
|
||||
|
||||
SequenceNumber seq = snapshot_->GetSequenceNumber();
|
||||
@@ -566,8 +565,8 @@ bool PessimisticTransaction::TryStealingLocks() {
|
||||
LOCKS_STOLEN);
|
||||
}
|
||||
|
||||
void PessimisticTransaction::UnlockGetForUpdate(ColumnFamilyHandle* column_family,
|
||||
const Slice& key) {
|
||||
void PessimisticTransaction::UnlockGetForUpdate(
|
||||
ColumnFamilyHandle* column_family, const Slice& key) {
|
||||
txn_db_impl_->UnLock(this, GetColumnFamilyID(column_family), key.ToString());
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ class PessimisticTransactionDB;
|
||||
class PessimisticTransaction : public TransactionBaseImpl {
|
||||
public:
|
||||
PessimisticTransaction(TransactionDB* db, const WriteOptions& write_options,
|
||||
const TransactionOptions& txn_options);
|
||||
const TransactionOptions& txn_options);
|
||||
|
||||
virtual ~PessimisticTransaction();
|
||||
|
||||
@@ -182,7 +182,7 @@ class PessimisticTransaction : public TransactionBaseImpl {
|
||||
class WriteCommittedTxn : public PessimisticTransaction {
|
||||
public:
|
||||
WriteCommittedTxn(TransactionDB* db, const WriteOptions& write_options,
|
||||
const TransactionOptions& txn_options);
|
||||
const TransactionOptions& txn_options);
|
||||
|
||||
virtual ~WriteCommittedTxn() {}
|
||||
|
||||
|
||||
@@ -5,8 +5,13 @@
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#ifndef __STDC_FORMAT_MACROS
|
||||
#define __STDC_FORMAT_MACROS
|
||||
#endif
|
||||
|
||||
#include "utilities/transactions/pessimistic_transaction_db.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
@@ -16,8 +21,9 @@
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/utilities/transaction_db.h"
|
||||
#include "util/cast_util.h"
|
||||
#include "utilities/transactions/transaction_db_mutex_impl.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "utilities/transactions/pessimistic_transaction.h"
|
||||
#include "utilities/transactions/transaction_db_mutex_impl.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -27,11 +33,13 @@ PessimisticTransactionDB::PessimisticTransactionDB(
|
||||
db_impl_(static_cast_with_check<DBImpl, DB>(db)),
|
||||
txn_db_options_(txn_db_options),
|
||||
lock_mgr_(this, txn_db_options_.num_stripes, txn_db_options.max_num_locks,
|
||||
txn_db_options_.max_num_deadlocks,
|
||||
txn_db_options_.custom_mutex_factory
|
||||
? txn_db_options_.custom_mutex_factory
|
||||
: std::shared_ptr<TransactionDBMutexFactory>(
|
||||
new TransactionDBMutexFactoryImpl())) {
|
||||
assert(db_impl_ != nullptr);
|
||||
info_log_ = db_impl_->GetDBOptions().info_log;
|
||||
}
|
||||
|
||||
// Support initiliazing PessimisticTransactionDB from a stackable db
|
||||
@@ -56,6 +64,7 @@ PessimisticTransactionDB::PessimisticTransactionDB(
|
||||
db_impl_(static_cast_with_check<DBImpl, DB>(db->GetRootDB())),
|
||||
txn_db_options_(txn_db_options),
|
||||
lock_mgr_(this, txn_db_options_.num_stripes, txn_db_options.max_num_locks,
|
||||
txn_db_options_.max_num_deadlocks,
|
||||
txn_db_options_.custom_mutex_factory
|
||||
? txn_db_options_.custom_mutex_factory
|
||||
: std::shared_ptr<TransactionDBMutexFactory>(
|
||||
@@ -301,7 +310,8 @@ Status PessimisticTransactionDB::DropColumnFamily(
|
||||
return s;
|
||||
}
|
||||
|
||||
Status PessimisticTransactionDB::TryLock(PessimisticTransaction* txn, uint32_t cfh_id,
|
||||
Status PessimisticTransactionDB::TryLock(PessimisticTransaction* txn,
|
||||
uint32_t cfh_id,
|
||||
const std::string& key,
|
||||
bool exclusive) {
|
||||
return lock_mgr_.TryLock(txn, cfh_id, key, GetEnv(), exclusive);
|
||||
@@ -312,8 +322,8 @@ void PessimisticTransactionDB::UnLock(PessimisticTransaction* txn,
|
||||
lock_mgr_.UnLock(txn, keys, GetEnv());
|
||||
}
|
||||
|
||||
void PessimisticTransactionDB::UnLock(PessimisticTransaction* txn, uint32_t cfh_id,
|
||||
const std::string& key) {
|
||||
void PessimisticTransactionDB::UnLock(PessimisticTransaction* txn,
|
||||
uint32_t cfh_id, const std::string& key) {
|
||||
lock_mgr_.UnLock(txn, cfh_id, key, GetEnv());
|
||||
}
|
||||
|
||||
@@ -330,7 +340,7 @@ Transaction* PessimisticTransactionDB::BeginInternalTransaction(
|
||||
|
||||
// All user Put, Merge, Delete, and Write requests must be intercepted to make
|
||||
// sure that they lock all keys that they are writing to avoid causing conflicts
|
||||
// with any concurent transactions. The easiest way to do this is to wrap all
|
||||
// with any concurrent transactions. The easiest way to do this is to wrap all
|
||||
// write operations in a transaction.
|
||||
//
|
||||
// Put(), Merge(), and Delete() only lock a single key per call. Write() will
|
||||
@@ -409,7 +419,8 @@ Status PessimisticTransactionDB::Write(const WriteOptions& opts,
|
||||
Transaction* txn = BeginInternalTransaction(opts);
|
||||
txn->DisableIndexing();
|
||||
|
||||
auto txn_impl = static_cast_with_check<PessimisticTransaction, Transaction>(txn);
|
||||
auto txn_impl =
|
||||
static_cast_with_check<PessimisticTransaction, Transaction>(txn);
|
||||
|
||||
// Since commitBatch sorts the keys before locking, concurrent Write()
|
||||
// operations will not cause a deadlock.
|
||||
@@ -422,8 +433,8 @@ Status PessimisticTransactionDB::Write(const WriteOptions& opts,
|
||||
return s;
|
||||
}
|
||||
|
||||
void PessimisticTransactionDB::InsertExpirableTransaction(TransactionID tx_id,
|
||||
PessimisticTransaction* tx) {
|
||||
void PessimisticTransactionDB::InsertExpirableTransaction(
|
||||
TransactionID tx_id, PessimisticTransaction* tx) {
|
||||
assert(tx->GetExpirationTime() > 0);
|
||||
std::lock_guard<std::mutex> lock(map_mutex_);
|
||||
expirable_transactions_map_.insert({tx_id, tx});
|
||||
@@ -449,7 +460,8 @@ bool PessimisticTransactionDB::TryStealingExpiredTransactionLocks(
|
||||
void PessimisticTransactionDB::ReinitializeTransaction(
|
||||
Transaction* txn, const WriteOptions& write_options,
|
||||
const TransactionOptions& txn_options) {
|
||||
auto txn_impl = static_cast_with_check<PessimisticTransaction, Transaction>(txn);
|
||||
auto txn_impl =
|
||||
static_cast_with_check<PessimisticTransaction, Transaction>(txn);
|
||||
|
||||
txn_impl->Reinitialize(this, write_options, txn_options);
|
||||
}
|
||||
@@ -482,6 +494,14 @@ PessimisticTransactionDB::GetLockStatusData() {
|
||||
return lock_mgr_.GetLockStatusData();
|
||||
}
|
||||
|
||||
std::vector<DeadlockPath> PessimisticTransactionDB::GetDeadlockInfoBuffer() {
|
||||
return lock_mgr_.GetDeadlockInfoBuffer();
|
||||
}
|
||||
|
||||
void PessimisticTransactionDB::SetDeadlockInfoBufferSize(uint32_t target_size) {
|
||||
lock_mgr_.Resize(target_size);
|
||||
}
|
||||
|
||||
void PessimisticTransactionDB::RegisterTransaction(Transaction* txn) {
|
||||
assert(txn);
|
||||
assert(txn->GetName().length() > 0);
|
||||
@@ -499,5 +519,288 @@ void PessimisticTransactionDB::UnregisterTransaction(Transaction* txn) {
|
||||
transactions_.erase(it);
|
||||
}
|
||||
|
||||
// Returns true if commit_seq <= snapshot_seq
|
||||
bool WritePreparedTxnDB::IsInSnapshot(uint64_t prep_seq,
|
||||
uint64_t snapshot_seq) {
|
||||
// Here we try to infer the return value without looking into prepare list.
|
||||
// This would help avoiding synchronization over a shared map.
|
||||
// TODO(myabandeh): read your own writes
|
||||
// TODO(myabandeh): optimize this. This sequence of checks must be correct but
|
||||
// not necessary efficient
|
||||
if (snapshot_seq < prep_seq) {
|
||||
// snapshot_seq < prep_seq <= commit_seq => snapshot_seq < commit_seq
|
||||
return false;
|
||||
}
|
||||
if (!delayed_prepared_empty_.load(std::memory_order_acquire)) {
|
||||
// We should not normally reach here
|
||||
ReadLock rl(&prepared_mutex_);
|
||||
if (delayed_prepared_.find(prep_seq) != delayed_prepared_.end()) {
|
||||
// Then it is not committed yet
|
||||
return false;
|
||||
}
|
||||
}
|
||||
auto indexed_seq = prep_seq % COMMIT_CACHE_SIZE;
|
||||
CommitEntry cached;
|
||||
bool exist = GetCommitEntry(indexed_seq, &cached);
|
||||
if (!exist) {
|
||||
// It is not committed, so it must be still prepared
|
||||
return false;
|
||||
}
|
||||
if (prep_seq == cached.prep_seq) {
|
||||
// It is committed and also not evicted from commit cache
|
||||
return cached.commit_seq <= snapshot_seq;
|
||||
}
|
||||
// At this point we dont know if it was committed or it is still prepared
|
||||
auto max_evicted_seq = max_evicted_seq_.load(std::memory_order_acquire);
|
||||
if (max_evicted_seq < prep_seq) {
|
||||
// Not evicted from cache and also not present, so must be still prepared
|
||||
return false;
|
||||
}
|
||||
// When advancing max_evicted_seq_, we move older entires from prepared to
|
||||
// delayed_prepared_. Also we move evicted entries from commit cache to
|
||||
// old_commit_map_ if it overlaps with any snapshot. Since prep_seq <=
|
||||
// max_evicted_seq_, we have three cases: i) in delayed_prepared_, ii) in
|
||||
// old_commit_map_, iii) committed with no conflict with any snapshot (i)
|
||||
// delayed_prepared_ is checked above
|
||||
if (max_evicted_seq < snapshot_seq) { // then (ii) cannot be the case
|
||||
// only (iii) is the case: committed
|
||||
// commit_seq <= max_evicted_seq_ < snapshot_seq => commit_seq <
|
||||
// snapshot_seq
|
||||
return true;
|
||||
}
|
||||
// else (ii) might be the case: check the commit data saved for this snapshot.
|
||||
// If there was no overlapping commit entry, then it is committed with a
|
||||
// commit_seq lower than any live snapshot, including snapshot_seq.
|
||||
if (old_commit_map_empty_.load(std::memory_order_acquire)) {
|
||||
return true;
|
||||
}
|
||||
{
|
||||
// We should not normally reach here
|
||||
ReadLock rl(&old_commit_map_mutex_);
|
||||
auto old_commit_entry = old_commit_map_.find(prep_seq);
|
||||
if (old_commit_entry == old_commit_map_.end() ||
|
||||
old_commit_entry->second <= snapshot_seq) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// (ii) it the case: it is committed but after the snapshot_seq
|
||||
return false;
|
||||
}
|
||||
|
||||
void WritePreparedTxnDB::AddPrepared(uint64_t seq) {
|
||||
ROCKS_LOG_DEBUG(info_log_, "Txn %" PRIu64 " Prepareing", seq);
|
||||
WriteLock wl(&prepared_mutex_);
|
||||
prepared_txns_.push(seq);
|
||||
}
|
||||
|
||||
void WritePreparedTxnDB::AddCommitted(uint64_t prepare_seq,
|
||||
uint64_t commit_seq) {
|
||||
ROCKS_LOG_DEBUG(info_log_, "Txn %" PRIu64 " Committing with %" PRIu64,
|
||||
prepare_seq, commit_seq);
|
||||
auto indexed_seq = prepare_seq % COMMIT_CACHE_SIZE;
|
||||
CommitEntry evicted;
|
||||
bool to_be_evicted = GetCommitEntry(indexed_seq, &evicted);
|
||||
if (to_be_evicted) {
|
||||
auto prev_max = max_evicted_seq_.load(std::memory_order_acquire);
|
||||
if (prev_max < evicted.commit_seq) {
|
||||
// TODO(myabandeh) inc max in larger steps to avoid frequent updates
|
||||
auto max_evicted_seq = evicted.commit_seq;
|
||||
// When max_evicted_seq_ advances, move older entries from prepared_txns_
|
||||
// to delayed_prepared_. This guarantees that if a seq is lower than max,
|
||||
// then it is not in prepared_txns_ ans save an expensive, synchronized
|
||||
// lookup from a shared set. delayed_prepared_ is expected to be empty in
|
||||
// normal cases.
|
||||
{
|
||||
WriteLock wl(&prepared_mutex_);
|
||||
while (!prepared_txns_.empty() &&
|
||||
prepared_txns_.top() <= max_evicted_seq) {
|
||||
auto to_be_popped = prepared_txns_.top();
|
||||
delayed_prepared_.insert(to_be_popped);
|
||||
prepared_txns_.pop();
|
||||
delayed_prepared_empty_.store(false, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
|
||||
// With each change to max_evicted_seq_ fetch the live snapshots behind it
|
||||
SequenceNumber curr_seq;
|
||||
std::vector<SequenceNumber> all_snapshots;
|
||||
bool update_snapshots = false;
|
||||
{
|
||||
InstrumentedMutex(db_impl_->mutex());
|
||||
// We use this to identify how fresh are the snapshot list. Since this
|
||||
// is done atomically with obtaining the snapshot list, the one with
|
||||
// the larger seq is more fresh. If the seq is equal the full snapshot
|
||||
// list could be different since taking snapshots does not increase
|
||||
// the db seq. However since we only care about snapshots before the
|
||||
// new max, such recent snapshots would not be included the in the
|
||||
// list anyway.
|
||||
curr_seq = db_impl_->GetLatestSequenceNumber();
|
||||
if (curr_seq > snapshots_version_) {
|
||||
// This is to avoid updating the snapshots_ if it already updated
|
||||
// with a more recent vesion by a concrrent thread
|
||||
update_snapshots = true;
|
||||
// We only care about snapshots lower then max
|
||||
all_snapshots =
|
||||
db_impl_->snapshots().GetAll(nullptr, max_evicted_seq);
|
||||
}
|
||||
}
|
||||
if (update_snapshots) {
|
||||
WriteLock wl(&snapshots_mutex_);
|
||||
snapshots_version_ = curr_seq;
|
||||
// We update the list concurrently with the readers.
|
||||
// Both new and old lists are sorted and the new list is subset of the
|
||||
// previous list plus some new items. Thus if a snapshot repeats in
|
||||
// both new and old lists, it will appear upper in the new list. So if
|
||||
// we simply insert the new snapshots in order, if an overwritten item
|
||||
// is still valid in the new list is either written to the same place in
|
||||
// the array or it is written in a higher palce before it gets
|
||||
// overwritten by another item. This guarantess a reader that reads the
|
||||
// list bottom-up will eventaully see a snapshot that repeats in the
|
||||
// update, either before it gets overwritten by the writer or
|
||||
// afterwards.
|
||||
size_t i = 0;
|
||||
auto it = all_snapshots.begin();
|
||||
for (; it != all_snapshots.end() && i < SNAPSHOT_CACHE_SIZE;
|
||||
it++, i++) {
|
||||
snapshot_cache_[i].store(*it, std::memory_order_release);
|
||||
}
|
||||
snapshots_.clear();
|
||||
for (; it != all_snapshots.end(); it++) {
|
||||
// Insert them to a vector that is less efficient to access
|
||||
// concurrently
|
||||
snapshots_.push_back(*it);
|
||||
}
|
||||
// Update the size at the end. Otherwise a parallel reader might read
|
||||
// items that are not set yet.
|
||||
snapshots_total_.store(all_snapshots.size(), std::memory_order_release);
|
||||
}
|
||||
while (prev_max < max_evicted_seq &&
|
||||
!max_evicted_seq_.compare_exchange_weak(
|
||||
prev_max, max_evicted_seq, std::memory_order_release,
|
||||
std::memory_order_acquire)) {
|
||||
};
|
||||
}
|
||||
// After each eviction from commit cache, check if the commit entry should
|
||||
// be kept around because it overlaps with a live snapshot.
|
||||
// First check the snapshot cache that is efficient for concurrent access
|
||||
auto cnt = snapshots_total_.load(std::memory_order_acquire);
|
||||
// The list might get updated concurrently as we are reading from it. The
|
||||
// reader should be able to read all the snapshots that are still valid
|
||||
// after the update. Since the survived snapshots are written in a higher
|
||||
// place before gets overwritten the reader that reads bottom-up will
|
||||
// eventully see it.
|
||||
const bool next_is_larger = true;
|
||||
SequenceNumber snapshot_seq = kMaxSequenceNumber;
|
||||
size_t ip1 = std::min(cnt, SNAPSHOT_CACHE_SIZE);
|
||||
for (; 0 < ip1; ip1--) {
|
||||
snapshot_seq = snapshot_cache_[ip1 - 1].load(std::memory_order_acquire);
|
||||
if (!MaybeUpdateOldCommitMap(evicted.prep_seq, evicted.commit_seq,
|
||||
snapshot_seq, !next_is_larger)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (UNLIKELY(SNAPSHOT_CACHE_SIZE < cnt && ip1 == SNAPSHOT_CACHE_SIZE &&
|
||||
snapshot_seq < evicted.prep_seq)) {
|
||||
// Then access the less efficient list of snapshots_
|
||||
ReadLock rl(&snapshots_mutex_);
|
||||
// Items could have moved from the snapshots_ to snapshot_cache_ before
|
||||
// accquiring the lock. To make sure that we do not miss a valid snapshot,
|
||||
// read snapshot_cache_ again while holding the lock.
|
||||
for (size_t i = 0; i < SNAPSHOT_CACHE_SIZE; i++) {
|
||||
snapshot_seq = snapshot_cache_[i].load(std::memory_order_acquire);
|
||||
if (!MaybeUpdateOldCommitMap(evicted.prep_seq, evicted.commit_seq,
|
||||
snapshot_seq, next_is_larger)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (auto snapshot_seq_2 : snapshots_) {
|
||||
if (!MaybeUpdateOldCommitMap(evicted.prep_seq, evicted.commit_seq,
|
||||
snapshot_seq_2, next_is_larger)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
bool succ =
|
||||
ExchangeCommitEntry(indexed_seq, evicted, {prepare_seq, commit_seq});
|
||||
if (!succ) {
|
||||
// A very rare event, in which the commit entry is updated before we do.
|
||||
// Here we apply a very simple solution of retrying.
|
||||
// TODO(myabandeh): do precautions to detect bugs that cause infinite loops
|
||||
AddCommitted(prepare_seq, commit_seq);
|
||||
return;
|
||||
}
|
||||
{
|
||||
WriteLock wl(&prepared_mutex_);
|
||||
prepared_txns_.erase(prepare_seq);
|
||||
bool was_empty = delayed_prepared_.empty();
|
||||
if (!was_empty) {
|
||||
delayed_prepared_.erase(prepare_seq);
|
||||
bool is_empty = delayed_prepared_.empty();
|
||||
if (was_empty != is_empty) {
|
||||
delayed_prepared_empty_.store(is_empty, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool WritePreparedTxnDB::GetCommitEntry(uint64_t indexed_seq,
|
||||
CommitEntry* entry) {
|
||||
// TODO(myabandeh): implement lock-free commit_cache_
|
||||
ReadLock rl(&commit_cache_mutex_);
|
||||
*entry = commit_cache_[indexed_seq];
|
||||
return (entry->commit_seq != 0); // initialized
|
||||
}
|
||||
|
||||
bool WritePreparedTxnDB::AddCommitEntry(uint64_t indexed_seq,
|
||||
CommitEntry& new_entry,
|
||||
CommitEntry* evicted_entry) {
|
||||
// TODO(myabandeh): implement lock-free commit_cache_
|
||||
WriteLock wl(&commit_cache_mutex_);
|
||||
*evicted_entry = commit_cache_[indexed_seq];
|
||||
commit_cache_[indexed_seq] = new_entry;
|
||||
return (evicted_entry->commit_seq != 0); // initialized
|
||||
}
|
||||
|
||||
bool WritePreparedTxnDB::ExchangeCommitEntry(uint64_t indexed_seq,
|
||||
CommitEntry& expected_entry,
|
||||
CommitEntry new_entry) {
|
||||
// TODO(myabandeh): implement lock-free commit_cache_
|
||||
WriteLock wl(&commit_cache_mutex_);
|
||||
auto& evicted_entry = commit_cache_[indexed_seq];
|
||||
if (evicted_entry.prep_seq != expected_entry.prep_seq) {
|
||||
return false;
|
||||
}
|
||||
commit_cache_[indexed_seq] = new_entry;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 10m entry, 80MB size
|
||||
size_t WritePreparedTxnDB::DEF_COMMIT_CACHE_SIZE = static_cast<size_t>(1 << 21);
|
||||
size_t WritePreparedTxnDB::DEF_SNAPSHOT_CACHE_SIZE =
|
||||
static_cast<size_t>(1 << 7);
|
||||
|
||||
bool WritePreparedTxnDB::MaybeUpdateOldCommitMap(
|
||||
const uint64_t& prep_seq, const uint64_t& commit_seq,
|
||||
const uint64_t& snapshot_seq, const bool next_is_larger = true) {
|
||||
// If we do not store an entry in old_commit_map we assume it is committed in
|
||||
// all snapshots. if commit_seq <= snapshot_seq, it is considered already in
|
||||
// the snapshot so we need not to keep the entry around for this snapshot.
|
||||
if (commit_seq <= snapshot_seq) {
|
||||
// continue the search if the next snapshot could be smaller than commit_seq
|
||||
return !next_is_larger;
|
||||
}
|
||||
// then snapshot_seq < commit_seq
|
||||
if (prep_seq <= snapshot_seq) { // overlapping range
|
||||
WriteLock wl(&old_commit_map_mutex_);
|
||||
old_commit_map_empty_.store(false, std::memory_order_release);
|
||||
old_commit_map_[prep_seq] = commit_seq;
|
||||
// Storing once is enough. No need to check it for other snapshots.
|
||||
return false;
|
||||
}
|
||||
// continue the search if the next snapshot could be larger than prep_seq
|
||||
return next_is_larger;
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
@@ -64,11 +65,12 @@ class PessimisticTransactionDB : public TransactionDB {
|
||||
using StackableDB::DropColumnFamily;
|
||||
virtual Status DropColumnFamily(ColumnFamilyHandle* column_family) override;
|
||||
|
||||
Status TryLock(PessimisticTransaction* txn, uint32_t cfh_id, const std::string& key,
|
||||
bool exclusive);
|
||||
Status TryLock(PessimisticTransaction* txn, uint32_t cfh_id,
|
||||
const std::string& key, bool exclusive);
|
||||
|
||||
void UnLock(PessimisticTransaction* txn, const TransactionKeyMap* keys);
|
||||
void UnLock(PessimisticTransaction* txn, uint32_t cfh_id, const std::string& key);
|
||||
void UnLock(PessimisticTransaction* txn, uint32_t cfh_id,
|
||||
const std::string& key);
|
||||
|
||||
void AddColumnFamily(const ColumnFamilyHandle* handle);
|
||||
|
||||
@@ -79,7 +81,8 @@ class PessimisticTransactionDB : public TransactionDB {
|
||||
return txn_db_options_;
|
||||
}
|
||||
|
||||
void InsertExpirableTransaction(TransactionID tx_id, PessimisticTransaction* tx);
|
||||
void InsertExpirableTransaction(TransactionID tx_id,
|
||||
PessimisticTransaction* tx);
|
||||
void RemoveExpirableTransaction(TransactionID tx_id);
|
||||
|
||||
// If transaction is no longer available, locks can be stolen
|
||||
@@ -98,13 +101,25 @@ class PessimisticTransactionDB : public TransactionDB {
|
||||
|
||||
TransactionLockMgr::LockStatusData GetLockStatusData() override;
|
||||
|
||||
std::vector<DeadlockPath> GetDeadlockInfoBuffer() override;
|
||||
void SetDeadlockInfoBufferSize(uint32_t target_size) override;
|
||||
|
||||
struct CommitEntry {
|
||||
uint64_t prep_seq;
|
||||
uint64_t commit_seq;
|
||||
CommitEntry() : prep_seq(0), commit_seq(0) {}
|
||||
CommitEntry(uint64_t ps, uint64_t cs) : prep_seq(ps), commit_seq(cs) {}
|
||||
};
|
||||
|
||||
protected:
|
||||
void ReinitializeTransaction(
|
||||
Transaction* txn, const WriteOptions& write_options,
|
||||
const TransactionOptions& txn_options = TransactionOptions());
|
||||
DBImpl* db_impl_;
|
||||
std::shared_ptr<Logger> info_log_;
|
||||
|
||||
private:
|
||||
DBImpl* db_impl_;
|
||||
friend class WritePreparedTxnDB;
|
||||
const TransactionDBOptions txn_db_options_;
|
||||
TransactionLockMgr lock_mgr_;
|
||||
|
||||
@@ -150,17 +165,151 @@ class WritePreparedTxnDB : public PessimisticTransactionDB {
|
||||
public:
|
||||
explicit WritePreparedTxnDB(DB* db,
|
||||
const TransactionDBOptions& txn_db_options)
|
||||
: PessimisticTransactionDB(db, txn_db_options) {}
|
||||
: PessimisticTransactionDB(db, txn_db_options),
|
||||
SNAPSHOT_CACHE_SIZE(DEF_SNAPSHOT_CACHE_SIZE),
|
||||
COMMIT_CACHE_SIZE(DEF_COMMIT_CACHE_SIZE) {
|
||||
init(txn_db_options);
|
||||
}
|
||||
|
||||
explicit WritePreparedTxnDB(StackableDB* db,
|
||||
const TransactionDBOptions& txn_db_options)
|
||||
: PessimisticTransactionDB(db, txn_db_options) {}
|
||||
: PessimisticTransactionDB(db, txn_db_options),
|
||||
SNAPSHOT_CACHE_SIZE(DEF_SNAPSHOT_CACHE_SIZE),
|
||||
COMMIT_CACHE_SIZE(DEF_COMMIT_CACHE_SIZE) {
|
||||
init(txn_db_options);
|
||||
}
|
||||
|
||||
virtual ~WritePreparedTxnDB() {}
|
||||
|
||||
Transaction* BeginTransaction(const WriteOptions& write_options,
|
||||
const TransactionOptions& txn_options,
|
||||
Transaction* old_txn) override;
|
||||
|
||||
// Check whether the transaction that wrote the value with seqeunce number seq
|
||||
// is visible to the snapshot with sequence number snapshot_seq
|
||||
bool IsInSnapshot(uint64_t seq, uint64_t snapshot_seq);
|
||||
// Add the trasnaction with prepare sequence seq to the prepared list
|
||||
void AddPrepared(uint64_t seq);
|
||||
// Add the transaction with prepare sequence prepare_seq and commit sequence
|
||||
// commit_seq to the commit map
|
||||
void AddCommitted(uint64_t prepare_seq, uint64_t commit_seq);
|
||||
|
||||
private:
|
||||
friend class WritePreparedTransactionTest_IsInSnapshotTest_Test;
|
||||
|
||||
void init(const TransactionDBOptions& /* unused */) {
|
||||
snapshot_cache_ = unique_ptr<std::atomic<SequenceNumber>[]>(
|
||||
new std::atomic<SequenceNumber>[SNAPSHOT_CACHE_SIZE] {});
|
||||
commit_cache_ =
|
||||
unique_ptr<CommitEntry[]>(new CommitEntry[COMMIT_CACHE_SIZE]{});
|
||||
}
|
||||
|
||||
// A heap with the amortized O(1) complexity for erase. It uses one extra heap
|
||||
// to keep track of erased entries that are not yet on top of the main heap.
|
||||
class PreparedHeap {
|
||||
std::priority_queue<uint64_t, std::vector<uint64_t>, std::greater<uint64_t>>
|
||||
heap_;
|
||||
std::priority_queue<uint64_t, std::vector<uint64_t>, std::greater<uint64_t>>
|
||||
erased_heap_;
|
||||
|
||||
public:
|
||||
bool empty() { return heap_.empty(); }
|
||||
uint64_t top() { return heap_.top(); }
|
||||
void push(uint64_t v) { heap_.push(v); }
|
||||
void pop() {
|
||||
heap_.pop();
|
||||
while (!heap_.empty() && !erased_heap_.empty() &&
|
||||
heap_.top() == erased_heap_.top()) {
|
||||
heap_.pop();
|
||||
erased_heap_.pop();
|
||||
}
|
||||
}
|
||||
void erase(uint64_t seq) {
|
||||
if (!heap_.empty()) {
|
||||
if (seq < heap_.top()) {
|
||||
// Already popped, ignore it.
|
||||
} else if (heap_.top() == seq) {
|
||||
heap_.pop();
|
||||
} else { // (heap_.top() > seq)
|
||||
// Down the heap, remember to pop it later
|
||||
erased_heap_.push(seq);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Get the commit entry with index indexed_seq from the commit table. It
|
||||
// returns true if such entry exists.
|
||||
bool GetCommitEntry(uint64_t indexed_seq, CommitEntry* entry);
|
||||
// Rewrite the entry with the index indexed_seq in the commit table with the
|
||||
// commit entry <prep_seq, commit_seq>. If the rewrite results into eviction,
|
||||
// sets the evicted_entry and returns true.
|
||||
bool AddCommitEntry(uint64_t indexed_seq, CommitEntry& new_entry,
|
||||
CommitEntry* evicted_entry);
|
||||
// Rewrite the entry with the index indexed_seq in the commit table with the
|
||||
// commit entry new_entry only if the existing entry matches the
|
||||
// expected_entry. Returns false otherwise.
|
||||
bool ExchangeCommitEntry(uint64_t indexed_seq, CommitEntry& expected_entry,
|
||||
CommitEntry new_entry);
|
||||
|
||||
// Add a new entry to old_commit_map_ if prep_seq <= snapshot_seq <
|
||||
// commit_seq. Return false if checking the next snapshot(s) is not needed.
|
||||
// This is the case if the entry already added to old_commit_map_ or none of
|
||||
// the next snapshots could satisfy the condition. next_is_larger: the next
|
||||
// snapshot will be a larger value
|
||||
bool MaybeUpdateOldCommitMap(const uint64_t& prep_seq,
|
||||
const uint64_t& commit_seq,
|
||||
const uint64_t& snapshot_seq,
|
||||
const bool next_is_larger);
|
||||
|
||||
// The list of live snapshots at the last time that max_evicted_seq_ advanced.
|
||||
// The list stored into two data structures: in snapshot_cache_ that is
|
||||
// efficient for concurrent reads, and in snapshots_ if the data does not fit
|
||||
// into snapshot_cache_. The total number of snapshots in the two lists
|
||||
std::atomic<size_t> snapshots_total_ = {};
|
||||
// The list sorted in ascending order. Thread-safety for writes is provided
|
||||
// with snapshots_mutex_ and concurrent reads are safe due to std::atomic for
|
||||
// each entry. In x86_64 architecture such reads are compiled to simple read
|
||||
// instructions. 128 entries
|
||||
// TODO(myabandeh): avoid non-const static variables
|
||||
static size_t DEF_SNAPSHOT_CACHE_SIZE;
|
||||
const size_t SNAPSHOT_CACHE_SIZE;
|
||||
unique_ptr<std::atomic<SequenceNumber>[]> snapshot_cache_;
|
||||
// 2nd list for storing snapshots. The list sorted in ascending order.
|
||||
// Thread-safety is provided with snapshots_mutex_.
|
||||
std::vector<SequenceNumber> snapshots_;
|
||||
// The version of the latest list of snapshots. This can be used to avoid
|
||||
// rewrittiing a list that is concurrently updated with a more recent version.
|
||||
SequenceNumber snapshots_version_ = 0;
|
||||
|
||||
// A heap of prepared transactions. Thread-safety is provided with
|
||||
// prepared_mutex_.
|
||||
PreparedHeap prepared_txns_;
|
||||
// TODO(myabandeh): avoid non-const static variables
|
||||
static size_t DEF_COMMIT_CACHE_SIZE;
|
||||
const size_t COMMIT_CACHE_SIZE;
|
||||
// commit_cache_ must be initialized to zero to tell apart an empty index from
|
||||
// a filled one. Thread-safety is provided with commit_cache_mutex_.
|
||||
unique_ptr<CommitEntry[]> commit_cache_;
|
||||
// The largest evicted *commit* sequence number from the commit_cache_
|
||||
std::atomic<uint64_t> max_evicted_seq_ = {};
|
||||
// A map of the evicted entries from commit_cache_ that has to be kept around
|
||||
// to service the old snapshots. This is expected to be empty normally.
|
||||
// Thread-safety is provided with old_commit_map_mutex_.
|
||||
std::map<uint64_t, uint64_t> old_commit_map_;
|
||||
// A set of long-running prepared transactions that are not finished by the
|
||||
// time max_evicted_seq_ advances their sequence number. This is expected to
|
||||
// be empty normally. Thread-safety is provided with prepared_mutex_.
|
||||
std::set<uint64_t> delayed_prepared_;
|
||||
// Update when delayed_prepared_.empty() changes. Expected to be true
|
||||
// normally.
|
||||
std::atomic<bool> delayed_prepared_empty_ = {true};
|
||||
// Update when old_commit_map_.empty() changes. Expected to be true normally.
|
||||
std::atomic<bool> old_commit_map_empty_ = {true};
|
||||
port::RWMutex prepared_mutex_;
|
||||
port::RWMutex old_commit_map_mutex_;
|
||||
port::RWMutex commit_cache_mutex_;
|
||||
port::RWMutex snapshots_mutex_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -181,8 +181,21 @@ Status TransactionBaseImpl::RollbackToSavePoint() {
|
||||
Status TransactionBaseImpl::Get(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice& key, std::string* value) {
|
||||
assert(value != nullptr);
|
||||
PinnableSlice pinnable_val(value);
|
||||
assert(!pinnable_val.IsPinned());
|
||||
auto s = Get(read_options, column_family, key, &pinnable_val);
|
||||
if (s.ok() && pinnable_val.IsPinned()) {
|
||||
value->assign(pinnable_val.data(), pinnable_val.size());
|
||||
} // else value is already assigned
|
||||
return s;
|
||||
}
|
||||
|
||||
Status TransactionBaseImpl::Get(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice& key, PinnableSlice* pinnable_val) {
|
||||
return write_batch_.GetFromBatchAndDB(db_, read_options, column_family, key,
|
||||
value);
|
||||
pinnable_val);
|
||||
}
|
||||
|
||||
Status TransactionBaseImpl::GetForUpdate(const ReadOptions& read_options,
|
||||
@@ -192,7 +205,26 @@ Status TransactionBaseImpl::GetForUpdate(const ReadOptions& read_options,
|
||||
Status s = TryLock(column_family, key, true /* read_only */, exclusive);
|
||||
|
||||
if (s.ok() && value != nullptr) {
|
||||
s = Get(read_options, column_family, key, value);
|
||||
assert(value != nullptr);
|
||||
PinnableSlice pinnable_val(value);
|
||||
assert(!pinnable_val.IsPinned());
|
||||
s = Get(read_options, column_family, key, &pinnable_val);
|
||||
if (s.ok() && pinnable_val.IsPinned()) {
|
||||
value->assign(pinnable_val.data(), pinnable_val.size());
|
||||
} // else value is already assigned
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status TransactionBaseImpl::GetForUpdate(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice& key,
|
||||
PinnableSlice* pinnable_val,
|
||||
bool exclusive) {
|
||||
Status s = TryLock(column_family, key, true /* read_only */, exclusive);
|
||||
|
||||
if (s.ok() && pinnable_val != nullptr) {
|
||||
s = Get(read_options, column_family, key, pinnable_val);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -46,18 +46,27 @@ class TransactionBaseImpl : public Transaction {
|
||||
|
||||
Status RollbackToSavePoint() override;
|
||||
|
||||
using Transaction::Get;
|
||||
Status Get(const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
const Slice& key, std::string* value) override;
|
||||
|
||||
Status Get(const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
const Slice& key, PinnableSlice* value) override;
|
||||
|
||||
Status Get(const ReadOptions& options, const Slice& key,
|
||||
std::string* value) override {
|
||||
return Get(options, db_->DefaultColumnFamily(), key, value);
|
||||
}
|
||||
|
||||
using Transaction::GetForUpdate;
|
||||
Status GetForUpdate(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
std::string* value, bool exclusive) override;
|
||||
|
||||
Status GetForUpdate(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* pinnable_val, bool exclusive) override;
|
||||
|
||||
Status GetForUpdate(const ReadOptions& options, const Slice& key,
|
||||
std::string* value, bool exclusive) override {
|
||||
return GetForUpdate(options, db_->DefaultColumnFamily(), key, value,
|
||||
|
||||
@@ -96,6 +96,64 @@ struct LockMap {
|
||||
size_t GetStripe(const std::string& key) const;
|
||||
};
|
||||
|
||||
void DeadlockInfoBuffer::AddNewPath(DeadlockPath path) {
|
||||
std::lock_guard<std::mutex> lock(paths_buffer_mutex_);
|
||||
|
||||
if (paths_buffer_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
paths_buffer_[buffer_idx_] = path;
|
||||
buffer_idx_ = (buffer_idx_ + 1) % paths_buffer_.size();
|
||||
}
|
||||
|
||||
void DeadlockInfoBuffer::Resize(uint32_t target_size) {
|
||||
std::lock_guard<std::mutex> lock(paths_buffer_mutex_);
|
||||
|
||||
paths_buffer_ = Normalize();
|
||||
|
||||
// Drop the deadlocks that will no longer be needed ater the normalize
|
||||
if (target_size < paths_buffer_.size()) {
|
||||
paths_buffer_.erase(
|
||||
paths_buffer_.begin(),
|
||||
paths_buffer_.begin() + (paths_buffer_.size() - target_size));
|
||||
buffer_idx_ = 0;
|
||||
}
|
||||
// Resize the buffer to the target size and restore the buffer's idx
|
||||
else {
|
||||
auto prev_size = paths_buffer_.size();
|
||||
paths_buffer_.resize(target_size);
|
||||
buffer_idx_ = (uint32_t)prev_size;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<DeadlockPath> DeadlockInfoBuffer::Normalize() {
|
||||
auto working = paths_buffer_;
|
||||
|
||||
if (working.empty()) {
|
||||
return working;
|
||||
}
|
||||
|
||||
// Next write occurs at a nonexistent path's slot
|
||||
if (paths_buffer_[buffer_idx_].empty()) {
|
||||
working.resize(buffer_idx_);
|
||||
} else {
|
||||
std::rotate(working.begin(), working.begin() + buffer_idx_, working.end());
|
||||
}
|
||||
|
||||
return working;
|
||||
}
|
||||
|
||||
std::vector<DeadlockPath> DeadlockInfoBuffer::PrepareBuffer() {
|
||||
std::lock_guard<std::mutex> lock(paths_buffer_mutex_);
|
||||
|
||||
// Reversing the normalized vector returns the latest deadlocks first
|
||||
auto working = Normalize();
|
||||
std::reverse(working.begin(), working.end());
|
||||
|
||||
return working;
|
||||
}
|
||||
|
||||
namespace {
|
||||
void UnrefLockMapsCache(void* ptr) {
|
||||
// Called when a thread exits or a ThreadLocalPtr gets destroyed.
|
||||
@@ -107,11 +165,13 @@ void UnrefLockMapsCache(void* ptr) {
|
||||
|
||||
TransactionLockMgr::TransactionLockMgr(
|
||||
TransactionDB* txn_db, size_t default_num_stripes, int64_t max_num_locks,
|
||||
uint32_t max_num_deadlocks,
|
||||
std::shared_ptr<TransactionDBMutexFactory> mutex_factory)
|
||||
: txn_db_impl_(nullptr),
|
||||
default_num_stripes_(default_num_stripes),
|
||||
max_num_locks_(max_num_locks),
|
||||
lock_maps_cache_(new ThreadLocalPtr(&UnrefLockMapsCache)),
|
||||
dlock_buffer_(max_num_deadlocks),
|
||||
mutex_factory_(mutex_factory) {
|
||||
assert(txn_db);
|
||||
txn_db_impl_ =
|
||||
@@ -309,7 +369,8 @@ Status TransactionLockMgr::AcquireWithTimeout(
|
||||
// detection.
|
||||
if (wait_ids.size() != 0) {
|
||||
if (txn->IsDeadlockDetect()) {
|
||||
if (IncrementWaiters(txn, wait_ids)) {
|
||||
if (IncrementWaiters(txn, wait_ids, key, column_family_id,
|
||||
lock_info.exclusive)) {
|
||||
result = Status::Busy(Status::SubCode::kDeadlock);
|
||||
stripe->stripe_mutex->UnLock();
|
||||
return result;
|
||||
@@ -357,13 +418,15 @@ Status TransactionLockMgr::AcquireWithTimeout(
|
||||
}
|
||||
|
||||
void TransactionLockMgr::DecrementWaiters(
|
||||
const PessimisticTransaction* txn, const autovector<TransactionID>& wait_ids) {
|
||||
const PessimisticTransaction* txn,
|
||||
const autovector<TransactionID>& wait_ids) {
|
||||
std::lock_guard<std::mutex> lock(wait_txn_map_mutex_);
|
||||
DecrementWaitersImpl(txn, wait_ids);
|
||||
}
|
||||
|
||||
void TransactionLockMgr::DecrementWaitersImpl(
|
||||
const PessimisticTransaction* txn, const autovector<TransactionID>& wait_ids) {
|
||||
const PessimisticTransaction* txn,
|
||||
const autovector<TransactionID>& wait_ids) {
|
||||
auto id = txn->GetID();
|
||||
assert(wait_txn_map_.Contains(id));
|
||||
wait_txn_map_.Delete(id);
|
||||
@@ -377,12 +440,16 @@ void TransactionLockMgr::DecrementWaitersImpl(
|
||||
}
|
||||
|
||||
bool TransactionLockMgr::IncrementWaiters(
|
||||
const PessimisticTransaction* txn, const autovector<TransactionID>& wait_ids) {
|
||||
const PessimisticTransaction* txn,
|
||||
const autovector<TransactionID>& wait_ids, const std::string& key,
|
||||
const uint32_t& cf_id, const bool& exclusive) {
|
||||
auto id = txn->GetID();
|
||||
std::vector<TransactionID> queue(txn->GetDeadlockDetectDepth());
|
||||
std::vector<int> queue_parents(txn->GetDeadlockDetectDepth());
|
||||
std::vector<TransactionID> queue_values(txn->GetDeadlockDetectDepth());
|
||||
std::lock_guard<std::mutex> lock(wait_txn_map_mutex_);
|
||||
assert(!wait_txn_map_.Contains(id));
|
||||
wait_txn_map_.Insert(id, wait_ids);
|
||||
|
||||
wait_txn_map_.Insert(id, {wait_ids, cf_id, key, exclusive});
|
||||
|
||||
for (auto wait_id : wait_ids) {
|
||||
if (rev_wait_txn_map_.Contains(wait_id)) {
|
||||
@@ -398,13 +465,15 @@ bool TransactionLockMgr::IncrementWaiters(
|
||||
}
|
||||
|
||||
const auto* next_ids = &wait_ids;
|
||||
int parent = -1;
|
||||
for (int tail = 0, head = 0; head < txn->GetDeadlockDetectDepth(); head++) {
|
||||
int i = 0;
|
||||
if (next_ids) {
|
||||
for (; i < static_cast<int>(next_ids->size()) &&
|
||||
tail + i < txn->GetDeadlockDetectDepth();
|
||||
i++) {
|
||||
queue[tail + i] = (*next_ids)[i];
|
||||
queue_values[tail + i] = (*next_ids)[i];
|
||||
queue_parents[tail + i] = parent;
|
||||
}
|
||||
tail += i;
|
||||
}
|
||||
@@ -414,19 +483,33 @@ bool TransactionLockMgr::IncrementWaiters(
|
||||
return false;
|
||||
}
|
||||
|
||||
auto next = queue[head];
|
||||
auto next = queue_values[head];
|
||||
if (next == id) {
|
||||
std::vector<DeadlockInfo> path;
|
||||
while (head != -1) {
|
||||
assert(wait_txn_map_.Contains(queue_values[head]));
|
||||
|
||||
auto extracted_info = wait_txn_map_.Get(queue_values[head]);
|
||||
path.push_back({queue_values[head], extracted_info.m_cf_id,
|
||||
extracted_info.m_waiting_key,
|
||||
extracted_info.m_exclusive});
|
||||
head = queue_parents[head];
|
||||
}
|
||||
std::reverse(path.begin(), path.end());
|
||||
dlock_buffer_.AddNewPath(DeadlockPath(path));
|
||||
DecrementWaitersImpl(txn, wait_ids);
|
||||
return true;
|
||||
} else if (!wait_txn_map_.Contains(next)) {
|
||||
next_ids = nullptr;
|
||||
continue;
|
||||
} else {
|
||||
next_ids = &wait_txn_map_.Get(next);
|
||||
parent = head;
|
||||
next_ids = &(wait_txn_map_.Get(next).m_neighbors);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait cycle too big, just assume deadlock.
|
||||
dlock_buffer_.AddNewPath(DeadlockPath(true));
|
||||
DecrementWaitersImpl(txn, wait_ids);
|
||||
return true;
|
||||
}
|
||||
@@ -537,7 +620,8 @@ void TransactionLockMgr::UnLockKey(const PessimisticTransaction* txn,
|
||||
}
|
||||
}
|
||||
|
||||
void TransactionLockMgr::UnLock(PessimisticTransaction* txn, uint32_t column_family_id,
|
||||
void TransactionLockMgr::UnLock(PessimisticTransaction* txn,
|
||||
uint32_t column_family_id,
|
||||
const std::string& key, Env* env) {
|
||||
std::shared_ptr<LockMap> lock_map_ptr = GetLockMap(column_family_id);
|
||||
LockMap* lock_map = lock_map_ptr.get();
|
||||
@@ -646,6 +730,13 @@ TransactionLockMgr::LockStatusData TransactionLockMgr::GetLockStatusData() {
|
||||
|
||||
return data;
|
||||
}
|
||||
std::vector<DeadlockPath> TransactionLockMgr::GetDeadlockInfoBuffer() {
|
||||
return dlock_buffer_.PrepareBuffer();
|
||||
}
|
||||
|
||||
void TransactionLockMgr::Resize(uint32_t target_size) {
|
||||
dlock_buffer_.Resize(target_size);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -26,13 +26,35 @@ struct LockInfo;
|
||||
struct LockMap;
|
||||
struct LockMapStripe;
|
||||
|
||||
struct DeadlockInfoBuffer {
|
||||
private:
|
||||
std::vector<DeadlockPath> paths_buffer_;
|
||||
uint32_t buffer_idx_;
|
||||
std::mutex paths_buffer_mutex_;
|
||||
std::vector<DeadlockPath> Normalize();
|
||||
|
||||
public:
|
||||
explicit DeadlockInfoBuffer(uint32_t n_latest_dlocks)
|
||||
: paths_buffer_(n_latest_dlocks), buffer_idx_(0) {}
|
||||
void AddNewPath(DeadlockPath path);
|
||||
void Resize(uint32_t target_size);
|
||||
std::vector<DeadlockPath> PrepareBuffer();
|
||||
};
|
||||
|
||||
struct TrackedTrxInfo {
|
||||
autovector<TransactionID> m_neighbors;
|
||||
uint32_t m_cf_id;
|
||||
std::string m_waiting_key;
|
||||
bool m_exclusive;
|
||||
};
|
||||
|
||||
class Slice;
|
||||
class PessimisticTransactionDB;
|
||||
|
||||
class TransactionLockMgr {
|
||||
public:
|
||||
TransactionLockMgr(TransactionDB* txn_db, size_t default_num_stripes,
|
||||
int64_t max_num_locks,
|
||||
int64_t max_num_locks, uint32_t max_num_deadlocks,
|
||||
std::shared_ptr<TransactionDBMutexFactory> factory);
|
||||
|
||||
~TransactionLockMgr();
|
||||
@@ -59,6 +81,8 @@ class TransactionLockMgr {
|
||||
|
||||
using LockStatusData = std::unordered_multimap<uint32_t, KeyLockInfo>;
|
||||
LockStatusData GetLockStatusData();
|
||||
std::vector<DeadlockPath> GetDeadlockInfoBuffer();
|
||||
void Resize(uint32_t);
|
||||
|
||||
private:
|
||||
PessimisticTransactionDB* txn_db_impl_;
|
||||
@@ -92,7 +116,8 @@ class TransactionLockMgr {
|
||||
// Maps from waitee -> number of waiters.
|
||||
HashMap<TransactionID, int> rev_wait_txn_map_;
|
||||
// Maps from waiter -> waitee.
|
||||
HashMap<TransactionID, autovector<TransactionID>> wait_txn_map_;
|
||||
HashMap<TransactionID, TrackedTrxInfo> wait_txn_map_;
|
||||
DeadlockInfoBuffer dlock_buffer_;
|
||||
|
||||
// Used to allocate mutexes/condvars to use when locking keys
|
||||
std::shared_ptr<TransactionDBMutexFactory> mutex_factory_;
|
||||
@@ -116,7 +141,9 @@ class TransactionLockMgr {
|
||||
LockMapStripe* stripe, LockMap* lock_map, Env* env);
|
||||
|
||||
bool IncrementWaiters(const PessimisticTransaction* txn,
|
||||
const autovector<TransactionID>& wait_ids);
|
||||
const autovector<TransactionID>& wait_ids,
|
||||
const std::string& key, const uint32_t& cf_id,
|
||||
const bool& exclusive);
|
||||
void DecrementWaiters(const PessimisticTransaction* txn,
|
||||
const autovector<TransactionID>& wait_ids);
|
||||
void DecrementWaitersImpl(const PessimisticTransaction* txn,
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#ifndef __STDC_FORMAT_MACROS
|
||||
#define __STDC_FORMAT_MACROS
|
||||
#endif
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
@@ -17,7 +22,6 @@
|
||||
#include "rocksdb/utilities/transaction_db.h"
|
||||
#include "table/mock_table.h"
|
||||
#include "util/fault_injection_test_env.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/random.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/sync_point.h"
|
||||
@@ -26,6 +30,7 @@
|
||||
#include "util/transaction_test_util.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
#include "utilities/merge_operators/string_append/stringappend.h"
|
||||
#include "utilities/transactions/pessimistic_transaction_db.h"
|
||||
|
||||
#include "port/port.h"
|
||||
|
||||
@@ -33,8 +38,8 @@ using std::string;
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class TransactionTest
|
||||
: public ::testing::TestWithParam<std::tuple<bool, bool>> {
|
||||
class TransactionTest : public ::testing::TestWithParam<
|
||||
std::tuple<bool, bool, TxnDBWritePolicy>> {
|
||||
public:
|
||||
TransactionDB* db;
|
||||
FaultInjectionTestEnv* env;
|
||||
@@ -57,6 +62,7 @@ class TransactionTest
|
||||
DestroyDB(dbname, options);
|
||||
txn_db_options.transaction_lock_timeout = 0;
|
||||
txn_db_options.default_lock_timeout = 0;
|
||||
txn_db_options.write_policy = std::get<2>(GetParam());
|
||||
Status s;
|
||||
if (std::get<0>(GetParam()) == false) {
|
||||
s = TransactionDB::Open(options, txn_db_options, dbname, &db);
|
||||
@@ -123,16 +129,23 @@ class TransactionTest
|
||||
};
|
||||
|
||||
class MySQLStyleTransactionTest : public TransactionTest {};
|
||||
class WritePreparedTransactionTest : public TransactionTest {};
|
||||
|
||||
static const TxnDBWritePolicy wc = WRITE_COMMITTED;
|
||||
static const TxnDBWritePolicy wp = WRITE_PREPARED;
|
||||
// TODO(myabandeh): Instantiate the tests with other write policies
|
||||
INSTANTIATE_TEST_CASE_P(DBAsBaseDB, TransactionTest,
|
||||
::testing::Values(std::make_tuple(false, false)));
|
||||
::testing::Values(std::make_tuple(false, false, wc)));
|
||||
INSTANTIATE_TEST_CASE_P(StackableDBAsBaseDB, TransactionTest,
|
||||
::testing::Values(std::make_tuple(true, false)));
|
||||
::testing::Values(std::make_tuple(true, false, wc)));
|
||||
INSTANTIATE_TEST_CASE_P(MySQLStyleTransactionTest, MySQLStyleTransactionTest,
|
||||
::testing::Values(std::make_tuple(false, false),
|
||||
std::make_tuple(false, true),
|
||||
std::make_tuple(true, false),
|
||||
std::make_tuple(true, true)));
|
||||
::testing::Values(std::make_tuple(false, false, wc),
|
||||
std::make_tuple(false, true, wc),
|
||||
std::make_tuple(true, false, wc),
|
||||
std::make_tuple(true, true, wc)));
|
||||
INSTANTIATE_TEST_CASE_P(WritePreparedTransactionTest,
|
||||
WritePreparedTransactionTest,
|
||||
::testing::Values(std::make_tuple(false, true, wp)));
|
||||
|
||||
TEST_P(TransactionTest, DoubleEmptyWrite) {
|
||||
WriteOptions write_options;
|
||||
@@ -462,6 +475,37 @@ TEST_P(TransactionTest, DeadlockCycleShared) {
|
||||
auto s =
|
||||
txns[i]->GetForUpdate(read_options, "0", nullptr, true /* exclusive */);
|
||||
ASSERT_TRUE(s.IsDeadlock());
|
||||
|
||||
// Calculate next buffer len, plateau at 5 when 5 records are inserted.
|
||||
const uint32_t curr_dlock_buffer_len_ =
|
||||
(i - 14 > kInitialMaxDeadlocks) ? kInitialMaxDeadlocks : (i - 14);
|
||||
|
||||
auto dlock_buffer = db->GetDeadlockInfoBuffer();
|
||||
ASSERT_EQ(dlock_buffer.size(), curr_dlock_buffer_len_);
|
||||
auto dlock_entry = dlock_buffer[0].path;
|
||||
ASSERT_EQ(dlock_entry.size(), kInitialMaxDeadlocks);
|
||||
|
||||
int64_t curr_waiting_key = 0;
|
||||
|
||||
// Offset of each txn id from the root of the shared dlock tree's txn id.
|
||||
int64_t offset_root = dlock_entry[0].m_txn_id - 1;
|
||||
// Offset of the final entry in the dlock path from the root's txn id.
|
||||
TransactionID leaf_id =
|
||||
dlock_entry[dlock_entry.size() - 1].m_txn_id - offset_root;
|
||||
|
||||
for (auto it = dlock_entry.rbegin(); it != dlock_entry.rend(); it++) {
|
||||
auto dl_node = *it;
|
||||
ASSERT_EQ(dl_node.m_txn_id, offset_root + leaf_id);
|
||||
ASSERT_EQ(dl_node.m_cf_id, 0);
|
||||
ASSERT_EQ(dl_node.m_waiting_key, ToString(curr_waiting_key));
|
||||
ASSERT_EQ(dl_node.m_exclusive, true);
|
||||
|
||||
if (curr_waiting_key == 0) {
|
||||
curr_waiting_key = leaf_id;
|
||||
}
|
||||
curr_waiting_key /= 2;
|
||||
leaf_id /= 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Rollback the leaf transaction.
|
||||
@@ -473,6 +517,102 @@ TEST_P(TransactionTest, DeadlockCycleShared) {
|
||||
for (auto& t : threads) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
// Downsize the buffer and verify the 3 latest deadlocks are preserved.
|
||||
auto dlock_buffer_before_resize = db->GetDeadlockInfoBuffer();
|
||||
db->SetDeadlockInfoBufferSize(3);
|
||||
auto dlock_buffer_after_resize = db->GetDeadlockInfoBuffer();
|
||||
ASSERT_EQ(dlock_buffer_after_resize.size(), 3);
|
||||
|
||||
for (uint32_t i = 0; i < dlock_buffer_after_resize.size(); i++) {
|
||||
for (uint32_t j = 0; j < dlock_buffer_after_resize[i].path.size(); j++) {
|
||||
ASSERT_EQ(dlock_buffer_after_resize[i].path[j].m_txn_id,
|
||||
dlock_buffer_before_resize[i].path[j].m_txn_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Upsize the buffer and verify the 3 latest dealocks are preserved.
|
||||
dlock_buffer_before_resize = db->GetDeadlockInfoBuffer();
|
||||
db->SetDeadlockInfoBufferSize(5);
|
||||
dlock_buffer_after_resize = db->GetDeadlockInfoBuffer();
|
||||
ASSERT_EQ(dlock_buffer_after_resize.size(), 3);
|
||||
|
||||
for (uint32_t i = 0; i < dlock_buffer_before_resize.size(); i++) {
|
||||
for (uint32_t j = 0; j < dlock_buffer_before_resize[i].path.size(); j++) {
|
||||
ASSERT_EQ(dlock_buffer_after_resize[i].path[j].m_txn_id,
|
||||
dlock_buffer_before_resize[i].path[j].m_txn_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Downsize to 0 and verify the size is consistent.
|
||||
dlock_buffer_before_resize = db->GetDeadlockInfoBuffer();
|
||||
db->SetDeadlockInfoBufferSize(0);
|
||||
dlock_buffer_after_resize = db->GetDeadlockInfoBuffer();
|
||||
ASSERT_EQ(dlock_buffer_after_resize.size(), 0);
|
||||
|
||||
// Upsize from 0 to verify the size is persistent.
|
||||
dlock_buffer_before_resize = db->GetDeadlockInfoBuffer();
|
||||
db->SetDeadlockInfoBufferSize(3);
|
||||
dlock_buffer_after_resize = db->GetDeadlockInfoBuffer();
|
||||
ASSERT_EQ(dlock_buffer_after_resize.size(), 0);
|
||||
|
||||
// Contrived case of shared lock of cycle size 2 to verify that a shared
|
||||
// lock causing a deadlock is correctly reported as "shared" in the buffer.
|
||||
std::vector<Transaction*> txns_shared(2);
|
||||
|
||||
// Create a cycle of size 2.
|
||||
for (uint32_t i = 0; i < 2; i++) {
|
||||
txns_shared[i] = db->BeginTransaction(write_options, txn_options);
|
||||
ASSERT_TRUE(txns_shared[i]);
|
||||
auto s = txns_shared[i]->GetForUpdate(read_options, ToString(i), nullptr);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
std::atomic<uint32_t> checkpoints_shared(0);
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"TransactionLockMgr::AcquireWithTimeout:WaitingTxn",
|
||||
[&](void* arg) { checkpoints_shared.fetch_add(1); });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::vector<port::Thread> threads_shared;
|
||||
for (uint32_t i = 0; i < 1; i++) {
|
||||
std::function<void()> blocking_thread = [&, i] {
|
||||
auto s =
|
||||
txns_shared[i]->GetForUpdate(read_options, ToString(i + 1), nullptr);
|
||||
ASSERT_OK(s);
|
||||
txns_shared[i]->Rollback();
|
||||
delete txns_shared[i];
|
||||
};
|
||||
threads_shared.emplace_back(blocking_thread);
|
||||
}
|
||||
|
||||
// Wait until all threads are waiting on each other.
|
||||
while (checkpoints_shared.load() != 1) {
|
||||
/* sleep override */
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
// Complete the cycle T2 -> T1 with a shared lock.
|
||||
auto s = txns_shared[1]->GetForUpdate(read_options, "0", nullptr, false);
|
||||
ASSERT_TRUE(s.IsDeadlock());
|
||||
|
||||
auto dlock_buffer = db->GetDeadlockInfoBuffer();
|
||||
|
||||
// Verify the size of the buffer and the single path.
|
||||
ASSERT_EQ(dlock_buffer.size(), 1);
|
||||
ASSERT_EQ(dlock_buffer[0].path.size(), 2);
|
||||
|
||||
// Verify the exclusivity field of the transactions in the deadlock path.
|
||||
ASSERT_TRUE(dlock_buffer[0].path[0].m_exclusive);
|
||||
ASSERT_FALSE(dlock_buffer[0].path[1].m_exclusive);
|
||||
txns_shared[1]->Rollback();
|
||||
delete txns_shared[1];
|
||||
|
||||
for (auto& t : threads_shared) {
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(TransactionTest, DeadlockCycle) {
|
||||
@@ -480,7 +620,8 @@ TEST_P(TransactionTest, DeadlockCycle) {
|
||||
ReadOptions read_options;
|
||||
TransactionOptions txn_options;
|
||||
|
||||
const uint32_t kMaxCycleLength = 50;
|
||||
// offset by 2 from the max depth to test edge case
|
||||
const uint32_t kMaxCycleLength = 52;
|
||||
|
||||
txn_options.lock_timeout = 1000000;
|
||||
txn_options.deadlock_detect = true;
|
||||
@@ -489,6 +630,7 @@ TEST_P(TransactionTest, DeadlockCycle) {
|
||||
// Set up a long wait for chain like this:
|
||||
//
|
||||
// T1 -> T2 -> T3 -> ... -> Tlen
|
||||
|
||||
std::vector<Transaction*> txns(len);
|
||||
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
@@ -509,8 +651,7 @@ TEST_P(TransactionTest, DeadlockCycle) {
|
||||
std::vector<port::Thread> threads;
|
||||
for (uint32_t i = 0; i < len - 1; i++) {
|
||||
std::function<void()> blocking_thread = [&, i] {
|
||||
auto s =
|
||||
txns[i]->GetForUpdate(read_options, ToString(i + 1), nullptr);
|
||||
auto s = txns[i]->GetForUpdate(read_options, ToString(i + 1), nullptr);
|
||||
ASSERT_OK(s);
|
||||
txns[i]->Rollback();
|
||||
delete txns[i];
|
||||
@@ -530,6 +671,39 @@ TEST_P(TransactionTest, DeadlockCycle) {
|
||||
auto s = txns[len - 1]->GetForUpdate(read_options, "0", nullptr);
|
||||
ASSERT_TRUE(s.IsDeadlock());
|
||||
|
||||
const uint32_t dlock_buffer_size_ = (len - 1 > 5) ? 5 : (len - 1);
|
||||
uint32_t curr_waiting_key = 0;
|
||||
TransactionID curr_txn_id = txns[0]->GetID();
|
||||
|
||||
auto dlock_buffer = db->GetDeadlockInfoBuffer();
|
||||
ASSERT_EQ(dlock_buffer.size(), dlock_buffer_size_);
|
||||
uint32_t check_len = len;
|
||||
bool check_limit_flag = false;
|
||||
|
||||
// Special case for a deadlock path that exceeds the maximum depth.
|
||||
if (len > 50) {
|
||||
check_len = 0;
|
||||
check_limit_flag = true;
|
||||
}
|
||||
auto dlock_entry = dlock_buffer[0].path;
|
||||
ASSERT_EQ(dlock_entry.size(), check_len);
|
||||
ASSERT_EQ(dlock_buffer[0].limit_exceeded, check_limit_flag);
|
||||
|
||||
// Iterates backwards over path verifying decreasing txn_ids.
|
||||
for (auto it = dlock_entry.rbegin(); it != dlock_entry.rend(); it++) {
|
||||
auto dl_node = *it;
|
||||
ASSERT_EQ(dl_node.m_txn_id, len + curr_txn_id - 1);
|
||||
ASSERT_EQ(dl_node.m_cf_id, 0);
|
||||
ASSERT_EQ(dl_node.m_waiting_key, ToString(curr_waiting_key));
|
||||
ASSERT_EQ(dl_node.m_exclusive, true);
|
||||
|
||||
curr_txn_id--;
|
||||
if (curr_waiting_key == 0) {
|
||||
curr_waiting_key = len;
|
||||
}
|
||||
curr_waiting_key--;
|
||||
}
|
||||
|
||||
// Rollback the last transaction.
|
||||
txns[len - 1]->Rollback();
|
||||
delete txns[len - 1];
|
||||
@@ -4559,6 +4733,138 @@ TEST_P(TransactionTest, MemoryLimitTest) {
|
||||
delete txn;
|
||||
}
|
||||
|
||||
// Test WritePreparedTxnDB's IsInSnapshot against different ordering of
|
||||
// snapshot, max_committed_seq_, prepared, and commit entries.
|
||||
TEST_P(WritePreparedTransactionTest, IsInSnapshotTest) {
|
||||
WriteOptions wo;
|
||||
// Use small commit cache to trigger lots of eviction and fast advance of
|
||||
// max_evicted_seq_
|
||||
// will take effect after ReOpen
|
||||
WritePreparedTxnDB::DEF_COMMIT_CACHE_SIZE = 8;
|
||||
// Same for snapshot cache size
|
||||
WritePreparedTxnDB::DEF_SNAPSHOT_CACHE_SIZE = 5;
|
||||
|
||||
// Take some preliminary snapshots first. This is to stress the data structure
|
||||
// that holds the old snapshots as it will be designed to be efficient when
|
||||
// only a few snapshots are below the max_evicted_seq_.
|
||||
for (int max_snapshots = 1; max_snapshots < 20; max_snapshots++) {
|
||||
// Leave some gap between the preliminary snapshots and the final snapshot
|
||||
// that we check. This should test for also different overlapping scnearios
|
||||
// between the last snapshot and the commits.
|
||||
for (int max_gap = 1; max_gap < 10; max_gap++) {
|
||||
// Since we do not actually write to db, we mock the seq as it would be
|
||||
// increaased by the db. The only exception is that we need db seq to
|
||||
// advance for our snapshots. for which we apply a dummy put each time we
|
||||
// increase our mock of seq.
|
||||
uint64_t seq = 0;
|
||||
// At each step we prepare a txn and then we commit it in the next txn.
|
||||
// This emulates the consecuitive transactions that write to the same key
|
||||
uint64_t cur_txn = 0;
|
||||
// Number of snapshots taken so far
|
||||
int num_snapshots = 0;
|
||||
std::vector<const Snapshot*> to_be_released;
|
||||
// Number of gaps applied so far
|
||||
int gap_cnt = 0;
|
||||
// The final snapshot that we will inspect
|
||||
uint64_t snapshot = 0;
|
||||
bool found_committed = false;
|
||||
// To stress the data structure that maintain prepared txns, at each cycle
|
||||
// we add a new prepare txn. These do not mean to be committed for
|
||||
// snapshot inspection.
|
||||
std::set<uint64_t> prepared;
|
||||
// We keep the list of txns comitted before we take the last snaphot.
|
||||
// These should be the only seq numbers that will be found in the snapshot
|
||||
std::set<uint64_t> committed_before;
|
||||
ReOpen(); // to restart the db
|
||||
WritePreparedTxnDB* wp_db = dynamic_cast<WritePreparedTxnDB*>(db);
|
||||
assert(wp_db);
|
||||
assert(wp_db->db_impl_);
|
||||
// We continue until max advances a bit beyond the snapshot.
|
||||
while (!snapshot || wp_db->max_evicted_seq_ < snapshot + 100) {
|
||||
// do prepare for a transaction
|
||||
wp_db->db_impl_->Put(wo, "key", "value"); // dummy put to inc db seq
|
||||
seq++;
|
||||
ASSERT_EQ(wp_db->db_impl_->GetLatestSequenceNumber(), seq);
|
||||
wp_db->AddPrepared(seq);
|
||||
prepared.insert(seq);
|
||||
|
||||
// If cur_txn is not started, do prepare for it.
|
||||
if (!cur_txn) {
|
||||
wp_db->db_impl_->Put(wo, "key", "value"); // dummy put to inc db seq
|
||||
seq++;
|
||||
ASSERT_EQ(wp_db->db_impl_->GetLatestSequenceNumber(), seq);
|
||||
cur_txn = seq;
|
||||
wp_db->AddPrepared(cur_txn);
|
||||
} else { // else commit it
|
||||
wp_db->db_impl_->Put(wo, "key", "value"); // dummy put to inc db seq
|
||||
seq++;
|
||||
ASSERT_EQ(wp_db->db_impl_->GetLatestSequenceNumber(), seq);
|
||||
wp_db->AddCommitted(cur_txn, seq);
|
||||
if (!snapshot) {
|
||||
committed_before.insert(cur_txn);
|
||||
}
|
||||
cur_txn = 0;
|
||||
}
|
||||
|
||||
if (num_snapshots < max_snapshots - 1) {
|
||||
// Take preliminary snapshots
|
||||
auto tmp_snapshot = db->GetSnapshot();
|
||||
to_be_released.push_back(tmp_snapshot);
|
||||
num_snapshots++;
|
||||
} else if (gap_cnt < max_gap) {
|
||||
// Wait for some gap before taking the final snapshot
|
||||
gap_cnt++;
|
||||
} else if (!snapshot) {
|
||||
// Take the final snapshot if it is not already taken
|
||||
auto tmp_snapshot = db->GetSnapshot();
|
||||
to_be_released.push_back(tmp_snapshot);
|
||||
snapshot = tmp_snapshot->GetSequenceNumber();
|
||||
// We increase the db seq artificailly by a dummy Put. Check that this
|
||||
// technique is effective and db seq is that same as ours.
|
||||
ASSERT_EQ(snapshot, seq);
|
||||
num_snapshots++;
|
||||
}
|
||||
|
||||
// If the snapshot is taken, verify seq numbers visible to it. We redo
|
||||
// it at each cycle to test that the system is still sound when
|
||||
// max_evicted_seq_ advances.
|
||||
if (snapshot) {
|
||||
for (uint64_t s = 0; s <= seq; s++) {
|
||||
bool was_committed =
|
||||
(committed_before.find(s) != committed_before.end());
|
||||
bool is_in_snapshot = wp_db->IsInSnapshot(s, snapshot);
|
||||
if (was_committed != is_in_snapshot) {
|
||||
printf("max_snapshots %d max_gap %d seq %" PRIu64 " max %" PRIu64
|
||||
" snapshot %" PRIu64
|
||||
" gap_cnt %d num_snapshots %d s %" PRIu64 "\n",
|
||||
max_snapshots, max_gap, seq,
|
||||
wp_db->max_evicted_seq_.load(), snapshot, gap_cnt,
|
||||
num_snapshots, s);
|
||||
}
|
||||
ASSERT_EQ(was_committed, is_in_snapshot);
|
||||
found_committed = found_committed || is_in_snapshot;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Safety check to make sure the test actually ran
|
||||
ASSERT_TRUE(found_committed);
|
||||
// As an extra check, check if prepared set will be properly empty after
|
||||
// they are committed.
|
||||
if (cur_txn) {
|
||||
wp_db->AddCommitted(cur_txn, seq);
|
||||
}
|
||||
for (auto p : prepared) {
|
||||
wp_db->AddCommitted(p, seq);
|
||||
}
|
||||
ASSERT_TRUE(wp_db->delayed_prepared_.empty());
|
||||
ASSERT_TRUE(wp_db->prepared_txns_.empty());
|
||||
for (auto s : to_be_released) {
|
||||
db->ReleaseSnapshot(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -14,17 +14,18 @@
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/utilities/transaction_db.h"
|
||||
#include "utilities/transactions/pessimistic_transaction_db.h"
|
||||
#include "utilities/transactions/pessimistic_transaction.h"
|
||||
#include "utilities/transactions/pessimistic_transaction_db.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
struct WriteOptions;
|
||||
|
||||
WritePreparedTxn::WritePreparedTxn(
|
||||
TransactionDB* txn_db, const WriteOptions& write_options,
|
||||
const TransactionOptions& txn_options)
|
||||
: PessimisticTransaction(txn_db, write_options, txn_options) {
|
||||
WritePreparedTxn::WritePreparedTxn(WritePreparedTxnDB* txn_db,
|
||||
const WriteOptions& write_options,
|
||||
const TransactionOptions& txn_options)
|
||||
: PessimisticTransaction(txn_db, write_options, txn_options),
|
||||
wpt_db_(txn_db) {
|
||||
PessimisticTransaction::Initialize(txn_options);
|
||||
}
|
||||
|
||||
@@ -35,9 +36,18 @@ Status WritePreparedTxn::CommitBatch(WriteBatch* /* unused */) {
|
||||
}
|
||||
|
||||
Status WritePreparedTxn::PrepareInternal() {
|
||||
// TODO(myabandeh) Implement this
|
||||
throw std::runtime_error("Prepare not Implemented");
|
||||
return Status::OK();
|
||||
WriteOptions write_options = write_options_;
|
||||
write_options.disableWAL = false;
|
||||
WriteBatchInternal::MarkEndPrepare(GetWriteBatch()->GetWriteBatch(), name_);
|
||||
const bool disable_memtable = true;
|
||||
uint64_t seq_used;
|
||||
Status s =
|
||||
db_impl_->WriteImpl(write_options, GetWriteBatch()->GetWriteBatch(),
|
||||
/*callback*/ nullptr, &log_number_, /*log ref*/ 0,
|
||||
!disable_memtable, &seq_used);
|
||||
prepare_seq_ = seq_used;
|
||||
wpt_db_->AddPrepared(prepare_seq_);
|
||||
return s;
|
||||
}
|
||||
|
||||
Status WritePreparedTxn::CommitWithoutPrepareInternal() {
|
||||
@@ -47,9 +57,24 @@ Status WritePreparedTxn::CommitWithoutPrepareInternal() {
|
||||
}
|
||||
|
||||
Status WritePreparedTxn::CommitInternal() {
|
||||
// TODO(myabandeh) Implement this
|
||||
throw std::runtime_error("Commit not Implemented");
|
||||
return Status::OK();
|
||||
// We take the commit-time batch and append the Commit marker.
|
||||
// The Memtable will ignore the Commit marker in non-recovery mode
|
||||
WriteBatch* working_batch = GetCommitTimeWriteBatch();
|
||||
// TODO(myabandeh): prevent the users from writing to txn after the prepare
|
||||
// phase
|
||||
assert(working_batch->Count() == 0);
|
||||
WriteBatchInternal::MarkCommit(working_batch, name_);
|
||||
|
||||
// any operations appended to this working_batch will be ignored from WAL
|
||||
working_batch->MarkWalTerminationPoint();
|
||||
|
||||
const bool disable_memtable = true;
|
||||
uint64_t seq_used;
|
||||
auto s = db_impl_->WriteImpl(write_options_, working_batch, nullptr, nullptr,
|
||||
log_number_, disable_memtable, &seq_used);
|
||||
uint64_t& commit_seq = seq_used;
|
||||
wpt_db_->AddCommitted(prepare_seq_, commit_seq);
|
||||
return s;
|
||||
}
|
||||
|
||||
Status WritePreparedTxn::Rollback() {
|
||||
|
||||
@@ -25,13 +25,14 @@
|
||||
#include "rocksdb/utilities/transaction_db.h"
|
||||
#include "rocksdb/utilities/write_batch_with_index.h"
|
||||
#include "util/autovector.h"
|
||||
#include "utilities/transactions/transaction_base.h"
|
||||
#include "utilities/transactions/pessimistic_transaction.h"
|
||||
#include "utilities/transactions/pessimistic_transaction_db.h"
|
||||
#include "utilities/transactions/transaction_base.h"
|
||||
#include "utilities/transactions/transaction_util.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class TransactionDBImpl;
|
||||
class WritePreparedTxnDB;
|
||||
|
||||
// This impl could write to DB also uncomitted data and then later tell apart
|
||||
// committed data from uncomitted data. Uncommitted data could be after the
|
||||
@@ -39,8 +40,8 @@ class TransactionDBImpl;
|
||||
// (WriteUnpreparedTxnImpl).
|
||||
class WritePreparedTxn : public PessimisticTransaction {
|
||||
public:
|
||||
WritePreparedTxn(TransactionDB* db, const WriteOptions& write_options,
|
||||
const TransactionOptions& txn_options);
|
||||
WritePreparedTxn(WritePreparedTxnDB* db, const WriteOptions& write_options,
|
||||
const TransactionOptions& txn_options);
|
||||
|
||||
virtual ~WritePreparedTxn() {}
|
||||
|
||||
@@ -65,6 +66,9 @@ class WritePreparedTxn : public PessimisticTransaction {
|
||||
// No copying allowed
|
||||
WritePreparedTxn(const WritePreparedTxn&);
|
||||
void operator=(const WritePreparedTxn&);
|
||||
|
||||
WritePreparedTxnDB* wpt_db_;
|
||||
uint64_t prepare_seq_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -385,8 +385,8 @@ class WBWIIteratorImpl : public WBWIIterator {
|
||||
};
|
||||
|
||||
struct WriteBatchWithIndex::Rep {
|
||||
Rep(const Comparator* index_comparator, size_t reserved_bytes = 0,
|
||||
size_t max_bytes = 0, bool _overwrite_key = false)
|
||||
explicit Rep(const Comparator* index_comparator, size_t reserved_bytes = 0,
|
||||
size_t max_bytes = 0, bool _overwrite_key = false)
|
||||
: write_batch(reserved_bytes, max_bytes),
|
||||
comparator(index_comparator, &write_batch),
|
||||
skip_list(comparator, &arena),
|
||||
@@ -743,8 +743,23 @@ Status WriteBatchWithIndex::GetFromBatchAndDB(DB* db,
|
||||
const ReadOptions& read_options,
|
||||
const Slice& key,
|
||||
std::string* value) {
|
||||
assert(value != nullptr);
|
||||
PinnableSlice pinnable_val(value);
|
||||
assert(!pinnable_val.IsPinned());
|
||||
auto s = GetFromBatchAndDB(db, read_options, db->DefaultColumnFamily(), key,
|
||||
&pinnable_val);
|
||||
if (s.ok() && pinnable_val.IsPinned()) {
|
||||
value->assign(pinnable_val.data(), pinnable_val.size());
|
||||
} // else value is already assigned
|
||||
return s;
|
||||
}
|
||||
|
||||
Status WriteBatchWithIndex::GetFromBatchAndDB(DB* db,
|
||||
const ReadOptions& read_options,
|
||||
const Slice& key,
|
||||
PinnableSlice* pinnable_val) {
|
||||
return GetFromBatchAndDB(db, read_options, db->DefaultColumnFamily(), key,
|
||||
value);
|
||||
pinnable_val);
|
||||
}
|
||||
|
||||
Status WriteBatchWithIndex::GetFromBatchAndDB(DB* db,
|
||||
@@ -752,19 +767,38 @@ Status WriteBatchWithIndex::GetFromBatchAndDB(DB* db,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice& key,
|
||||
std::string* value) {
|
||||
assert(value != nullptr);
|
||||
PinnableSlice pinnable_val(value);
|
||||
assert(!pinnable_val.IsPinned());
|
||||
auto s =
|
||||
GetFromBatchAndDB(db, read_options, column_family, key, &pinnable_val);
|
||||
if (s.ok() && pinnable_val.IsPinned()) {
|
||||
value->assign(pinnable_val.data(), pinnable_val.size());
|
||||
} // else value is already assigned
|
||||
return s;
|
||||
}
|
||||
|
||||
Status WriteBatchWithIndex::GetFromBatchAndDB(DB* db,
|
||||
const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice& key,
|
||||
PinnableSlice* pinnable_val) {
|
||||
Status s;
|
||||
MergeContext merge_context;
|
||||
const ImmutableDBOptions& immuable_db_options =
|
||||
reinterpret_cast<DBImpl*>(db)->immutable_db_options();
|
||||
|
||||
std::string batch_value;
|
||||
// Since the lifetime of the WriteBatch is the same as that of the transaction
|
||||
// we cannot pin it as otherwise the returned value will not be available
|
||||
// after the transaction finishes.
|
||||
std::string& batch_value = *pinnable_val->GetSelf();
|
||||
WriteBatchWithIndexInternal::Result result =
|
||||
WriteBatchWithIndexInternal::GetFromBatch(
|
||||
immuable_db_options, this, column_family, key, &merge_context,
|
||||
&rep->comparator, &batch_value, rep->overwrite_key, &s);
|
||||
|
||||
if (result == WriteBatchWithIndexInternal::Result::kFound) {
|
||||
value->assign(batch_value.data(), batch_value.size());
|
||||
pinnable_val->PinSelf();
|
||||
return s;
|
||||
}
|
||||
if (result == WriteBatchWithIndexInternal::Result::kDeleted) {
|
||||
@@ -785,7 +819,7 @@ Status WriteBatchWithIndex::GetFromBatchAndDB(DB* db,
|
||||
result == WriteBatchWithIndexInternal::Result::kNotFound);
|
||||
|
||||
// Did not find key in batch OR could not resolve Merges. Try DB.
|
||||
s = db->Get(read_options, column_family, key, value);
|
||||
s = db->Get(read_options, column_family, key, pinnable_val);
|
||||
|
||||
if (s.ok() || s.IsNotFound()) { // DB Get Succeeded
|
||||
if (result == WriteBatchWithIndexInternal::Result::kMergeInProgress) {
|
||||
@@ -797,18 +831,18 @@ Status WriteBatchWithIndex::GetFromBatchAndDB(DB* db,
|
||||
Env* env = immuable_db_options.env;
|
||||
Logger* logger = immuable_db_options.info_log.get();
|
||||
|
||||
Slice db_slice(*value);
|
||||
Slice* merge_data;
|
||||
if (s.ok()) {
|
||||
merge_data = &db_slice;
|
||||
merge_data = pinnable_val;
|
||||
} else { // Key not present in db (s.IsNotFound())
|
||||
merge_data = nullptr;
|
||||
}
|
||||
|
||||
if (merge_operator) {
|
||||
s = MergeHelper::TimedFullMerge(merge_operator, key, merge_data,
|
||||
merge_context.GetOperands(), value,
|
||||
logger, statistics, env);
|
||||
s = MergeHelper::TimedFullMerge(
|
||||
merge_operator, key, merge_data, merge_context.GetOperands(),
|
||||
pinnable_val->GetSelf(), logger, statistics, env);
|
||||
pinnable_val->PinSelf();
|
||||
} else {
|
||||
s = Status::InvalidArgument("Options::merge_operator must be set");
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user