mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 11:02:36 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe55e3e18f | ||
|
|
3d267c9941 | ||
|
|
315d3afcde | ||
|
|
dae099db27 | ||
|
|
eb22eb7dfe | ||
|
|
b26658ca6b | ||
|
|
57f8dd3dc1 | ||
|
|
61c915ac50 | ||
|
|
63cbcd25d1 | ||
|
|
b2892047fd | ||
|
|
beb5daeeac | ||
|
|
a00f9bc498 | ||
|
|
7d8218912d | ||
|
|
a5163cfa60 | ||
|
|
6bca22522b | ||
|
|
2c4981cada | ||
|
|
9891486ddd | ||
|
|
849efe3863 | ||
|
|
e14eaa31fd | ||
|
|
8e5a257bfe | ||
|
|
a0cdf54bd3 | ||
|
|
935ce6d386 | ||
|
|
c93f7848c6 | ||
|
|
882e706400 | ||
|
|
f04765f7cf | ||
|
|
7768975517 | ||
|
|
314828c973 | ||
|
|
8a6d7a349a | ||
|
|
1991ad0409 | ||
|
|
2be7301d42 | ||
|
|
edde954e7b | ||
|
|
56281f3a97 | ||
|
|
4a21b1402c | ||
|
|
e39d080871 | ||
|
|
3f407b065c | ||
|
|
b77007df8b | ||
|
|
247d0979aa | ||
|
|
96fcefbf1d | ||
|
|
00197cff39 | ||
|
|
5b219eccb5 | ||
|
|
aad1191765 | ||
|
|
e333528991 | ||
|
|
7784980fcd | ||
|
|
3c6b49ed66 | ||
|
|
b3b875657f | ||
|
|
4f6e89b1d0 | ||
|
|
a2bf265a39 | ||
|
|
52fd1ff2c2 | ||
|
|
1886c435b9 | ||
|
|
63c30de80d | ||
|
|
13e66a8f51 | ||
|
|
01eabf7375 | ||
|
|
7ffb10fc1a | ||
|
|
236d4c67e9 | ||
|
|
cd7c4143d7 | ||
|
|
dfb6fe6755 | ||
|
|
3068870cce | ||
|
|
734e4acafb | ||
|
|
182b940e70 | ||
|
|
4118e13330 | ||
|
|
f2a8f92a15 | ||
|
|
4444256ab7 | ||
|
|
548d7fb261 | ||
|
|
fd43ee09da | ||
|
|
23a18ca5ad | ||
|
|
481856ac46 | ||
|
|
a0deec960f | ||
|
|
fe349db57b | ||
|
|
e63350e726 | ||
|
|
a13bde39ee | ||
|
|
73843aa636 | ||
|
|
9d60151b04 | ||
|
|
3f62215210 | ||
|
|
41e77b8390 | ||
|
|
c1038d2837 | ||
|
|
635a7bd1ad | ||
|
|
f39452e81f | ||
|
|
a4eb7387b2 | ||
|
|
36e4762ce0 | ||
|
|
86eb2b9ad9 | ||
|
|
0765babe15 | ||
|
|
647eafdc21 | ||
|
|
760ef68a69 | ||
|
|
327085b7b2 | ||
|
|
715591bba0 | ||
|
|
972e3ff295 | ||
|
|
420bdb42e7 | ||
|
|
1543d5d92e | ||
|
|
018bb2ebf5 | ||
|
|
dc51bd716b | ||
|
|
48e8baebc0 | ||
|
|
4b0aa3c4c8 | ||
|
|
53b693f5fe | ||
|
|
661e4c9267 | ||
|
|
489d142808 | ||
|
|
eba99c28e4 | ||
|
|
2ef92fea51 | ||
|
|
91300d01f6 | ||
|
|
ec2f64794b | ||
|
|
800e51553e | ||
|
|
b952c898b6 | ||
|
|
3b192f6186 | ||
|
|
6c57952002 | ||
|
|
425210cc48 | ||
|
|
1ea79a78c9 | ||
|
|
df5eeb85ca | ||
|
|
5ed650857d | ||
|
|
adb665e0bf | ||
|
|
307a4e80c8 | ||
|
|
361010d447 | ||
|
|
672300f47f | ||
|
|
b39b2ee12f | ||
|
|
1ca5f6d132 | ||
|
|
e095d0cbc7 | ||
|
|
14c0380e78 | ||
|
|
c90fef88b1 | ||
|
|
4e20c5da20 | ||
|
|
a9fb346e4a | ||
|
|
d133b08f68 | ||
|
|
144cdb8f16 | ||
|
|
9bd191d2f4 | ||
|
|
a9fae0a9d1 | ||
|
|
193221e0a1 | ||
|
|
e48f3f8b9e | ||
|
|
85bd8f518b | ||
|
|
a7875272d7 | ||
|
|
0f17f9279b | ||
|
|
cf19f559dd | ||
|
|
2dc019e095 | ||
|
|
f1aedda068 | ||
|
|
c54cdc3784 | ||
|
|
2bcaf8246b | ||
|
|
24bceb0963 | ||
|
|
815f54afad | ||
|
|
9e7cf3469b | ||
|
|
5c5d01ae74 | ||
|
|
f998c9790f | ||
|
|
879f366366 | ||
|
|
557034f362 | ||
|
|
437942e481 | ||
|
|
2b16d664cb | ||
|
|
16fb04434f | ||
|
|
ce22ea99a0 | ||
|
|
196af035c1 | ||
|
|
40a2e406f8 | ||
|
|
d5555d95a3 | ||
|
|
da61f348d3 | ||
|
|
f9eb56791a | ||
|
|
6a4faee5cd | ||
|
|
c90c48d3c8 | ||
|
|
1b295ac8ae | ||
|
|
25f5742f0b | ||
|
|
b50a81a2bb | ||
|
|
04751d5345 | ||
|
|
2946cadc46 | ||
|
|
0a9fd05c2a | ||
|
|
fcd1e0bf6c | ||
|
|
0aab5e55f0 | ||
|
|
9c0bb7f172 | ||
|
|
eeb27e1bbd | ||
|
|
c6168d13ab | ||
|
|
bc429de490 | ||
|
|
b9bc7a2aa4 | ||
|
|
60a2bbba94 | ||
|
|
9ee84067f6 | ||
|
|
f41df3045c | ||
|
|
2e8004e608 | ||
|
|
9de2f75216 | ||
|
|
24495186da | ||
|
|
1168cb810a | ||
|
|
99c052a34f | ||
|
|
f83cd64c0b | ||
|
|
0e926b84fd | ||
|
|
4dfaa6610a | ||
|
|
59a7c0337b | ||
|
|
869ae5d786 | ||
|
|
1d9dbef64e |
No files matched your search
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"project_id" : "rocksdb",
|
||||
"conduit_uri" : "https://reviews.facebook.net/",
|
||||
"conduit_uri" : "https://phabricator.fb.com/api/",
|
||||
"copyright_holder" : "Facebook",
|
||||
"load" : [
|
||||
"arcanist_util"
|
||||
|
||||
@@ -37,6 +37,7 @@ util/build_version.cc
|
||||
build_tools/VALGRIND_LOGS/
|
||||
coverage/COVERAGE_REPORT
|
||||
.gdbhistory
|
||||
.gdb_history
|
||||
package/
|
||||
.phutil_module_cache
|
||||
unity.a
|
||||
@@ -69,3 +70,4 @@ LOG
|
||||
db_logs/
|
||||
tp2/
|
||||
fbcode/
|
||||
fbcode
|
||||
@@ -5,6 +5,8 @@ os:
|
||||
- osx
|
||||
compiler:
|
||||
- clang
|
||||
jdk:
|
||||
- oraclejdk7
|
||||
|
||||
addons:
|
||||
apt:
|
||||
|
||||
+24
-11
@@ -43,6 +43,11 @@ else()
|
||||
add_definitions(-DROCKSDB_JEMALLOC)
|
||||
include_directories(${JEMALLOC_INCLUDE_DIR})
|
||||
endif()
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
|
||||
# FreeBSD has jemaloc as default malloc
|
||||
add_definitions(-DROCKSDB_JEMALLOC)
|
||||
set(WITH_JEMALLOC ON)
|
||||
endif()
|
||||
option(WITH_SNAPPY "build with SNAPPY" OFF)
|
||||
if(WITH_SNAPPY)
|
||||
find_package(snappy REQUIRED)
|
||||
@@ -79,24 +84,28 @@ endif()
|
||||
string(REGEX REPLACE "[^0-9a-f]+" "" GIT_SHA "${GIT_SHA}")
|
||||
|
||||
set(BUILD_VERSION_CC ${CMAKE_BINARY_DIR}/build_version.cc)
|
||||
file(GENERATE OUTPUT ${BUILD_VERSION_CC} CONTENT
|
||||
"#include \"build_version.h\"
|
||||
const char* rocksdb_build_git_sha = \"rocksdb_build_git_sha:${GIT_SHA}\";
|
||||
const char* rocksdb_build_git_datetime = \"rocksdb_build_git_datetime:${GIT_DATE_TIME}\";
|
||||
const char* rocksdb_build_compile_date = __DATE__;
|
||||
")
|
||||
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(WIN32)
|
||||
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 /WX /wd4127 /wd4800 /wd4996 /wd4351")
|
||||
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 -Werror")
|
||||
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")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native -fno-omit-frame-pointer -momit-leaf-frame-pointer")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer -momit-leaf-frame-pointer")
|
||||
endif()
|
||||
|
||||
option(FAIL_ON_WARNINGS "Treat compile warnings as errors" ON)
|
||||
if(FAIL_ON_WARNINGS)
|
||||
if(MSVC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /WX")
|
||||
else() # assume GCC
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
option(WITH_ASAN "build with ASAN" OFF)
|
||||
@@ -252,11 +261,11 @@ set(SOURCES
|
||||
db/db_impl.cc
|
||||
db/db_impl_debug.cc
|
||||
db/db_impl_experimental.cc
|
||||
db/db_impl_add_file.cc
|
||||
db/db_impl_readonly.cc
|
||||
db/db_info_dumper.cc
|
||||
db/db_iter.cc
|
||||
db/event_helpers.cc
|
||||
db/external_sst_file_ingestion_job.cc
|
||||
db/experimental.cc
|
||||
db/filename.cc
|
||||
db/file_indexer.cc
|
||||
@@ -391,9 +400,9 @@ set(SOURCES
|
||||
utilities/document/json_document_builder.cc
|
||||
utilities/env_mirror.cc
|
||||
utilities/env_registry.cc
|
||||
utilities/flashcache/flashcache.cc
|
||||
utilities/geodb/geodb_impl.cc
|
||||
utilities/leveldb_options/leveldb_options.cc
|
||||
utilities/lua/rocks_lua_compaction_filter.cc
|
||||
utilities/memory/memory_util.cc
|
||||
utilities/merge_operators/string_append/stringappend.cc
|
||||
utilities/merge_operators/string_append/stringappend2.cc
|
||||
@@ -492,6 +501,8 @@ set(TESTS
|
||||
db/db_inplace_update_test.cc
|
||||
db/db_iter_test.cc
|
||||
db/db_log_iter_test.cc
|
||||
db/db_memtable_test.cc
|
||||
db/db_merge_operator_test.cc
|
||||
db/db_options_test.cc
|
||||
db/db_properties_test.cc
|
||||
db/db_table_properties_test.cc
|
||||
@@ -502,6 +513,7 @@ set(TESTS
|
||||
db/db_bloom_filter_test.cc
|
||||
db/db_flush_test.cc
|
||||
db/db_iterator_test.cc
|
||||
db/db_range_del_test.cc
|
||||
db/db_sst_test.cc
|
||||
db/external_sst_file_test.cc
|
||||
db/db_universal_compaction_test.cc
|
||||
@@ -591,6 +603,7 @@ set(TESTS
|
||||
utilities/ttl/ttl_test.cc
|
||||
utilities/write_batch_with_index/write_batch_with_index_test.cc
|
||||
utilities/column_aware_encoding_test.cc
|
||||
utilities/lua/rocks_lua_test.cc
|
||||
)
|
||||
if(WITH_LIBRADOS)
|
||||
list(APPEND TESTS utilities/env_librados_test.cc)
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
# RocksDB default options change log
|
||||
## 5.0 (11/17/2016)
|
||||
* Options::allow_concurrent_memtable_write and Options::enable_write_thread_adaptive_yield are now true by default
|
||||
* Options.level0_stop_writes_trigger default value changes from 24 to 32.
|
||||
|
||||
## 4.8.0 (5/2/2016)
|
||||
* options.max_open_files changes from 5000 to -1. It improves performance, but users need to set file descriptor limit to be large enough and watch memory usage for index and bloom filters.
|
||||
* options.base_background_compactions changes from max_background_compactions to 1. When users set higher max_background_compactions but the write throughput is not high, the writes are less spiky to disks.
|
||||
|
||||
+27
@@ -1,4 +1,31 @@
|
||||
# Rocksdb Change Log
|
||||
## Unreleased
|
||||
### Bug fixes
|
||||
* Fix the data corruption bug in the case that concurrent memtable write is enabled and 2PC is used.
|
||||
* OptimizeForPointLookup() doesn't work with the default DB setting of allow_concurrent_memtable_write=true. Fix it.
|
||||
* Fix a 2PC related bug where WAL files size grow too large.
|
||||
* Fix the bug that if 2PC is enabled, checkpoints may loss some recent transactions.
|
||||
* When file copying is needed when creating checkpoints or bulk loading files, fsync the file after the file copying.
|
||||
|
||||
## 5.0.0 (11/17/2016)
|
||||
### Public API Change
|
||||
* Options::max_bytes_for_level_multiplier is now a double along with all getters and setters.
|
||||
* Support dynamically change `delayed_write_rate` and `max_total_wal_size` options via SetDBOptions().
|
||||
* Introduce DB::DeleteRange for optimized deletion of large ranges of contiguous keys.
|
||||
* Support dynamically change `delayed_write_rate` option via SetDBOptions().
|
||||
* Options::allow_concurrent_memtable_write and Options::enable_write_thread_adaptive_yield are now true by default.
|
||||
* Remove Tickers::SEQUENCE_NUMBER to avoid confusion if statistics object is shared among RocksDB instance. Alternatively DB::GetLatestSequenceNumber() can be used to get the same value.
|
||||
* Options.level0_stop_writes_trigger default value changes from 24 to 32.
|
||||
* New compaction filter API: CompactionFilter::FilterV2(). Allows to drop ranges of keys.
|
||||
* Removed flashcache support.
|
||||
* DB::AddFile() is deprecated and is replaced with DB::IngestExternalFile(). DB::IngestExternalFile() remove all the restrictions that existed for DB::AddFile.
|
||||
|
||||
### New Features
|
||||
* Add avoid_flush_during_shutdown option, which speeds up DB shutdown by not flushing unpersisted data (i.e. with disableWAL = true). Unpersisted data will be lost. The options is dynamically changeable via SetDBOptions().
|
||||
* Add memtable_insert_with_hint_prefix_extractor option. The option is mean to reduce CPU usage for inserting keys into memtable, if keys can be group by prefix and insert for each prefix are sequential or almost sequential. See include/rocksdb/options.h for more details.
|
||||
* Add LuaCompactionFilter in utilities. This allows developers to write compaction filters in Lua. To use this feature, LUA_PATH needs to be set to the root directory of Lua.
|
||||
* No longer populate "LATEST_BACKUP" file in backup directory, which formerly contained the number of the latest backup. The latest backup can be determined by finding the highest numbered file in the "meta/" subdirectory.
|
||||
|
||||
## 4.13.0 (10/18/2016)
|
||||
### Public API Change
|
||||
* DB::GetOptions() reflect dynamic changed options (i.e. through DB::SetOptions()) and return copy of options instead of reference.
|
||||
|
||||
+4
-4
@@ -40,7 +40,7 @@ your make commands, like this: `PORTABLE=1 make static_lib`
|
||||
## Supported platforms
|
||||
|
||||
* **Linux - Ubuntu**
|
||||
* Upgrade your gcc to version at least 4.7 to get C++11 support.
|
||||
* Upgrade your gcc to version at least 4.8 to get C++11 support.
|
||||
* Install gflags. First, try: `sudo apt-get install libgflags-dev`
|
||||
If this doesn't work and you're using Ubuntu, here's a nice tutorial:
|
||||
(http://askubuntu.com/questions/312173/installing-gflags-12-04)
|
||||
@@ -49,8 +49,8 @@ your make commands, like this: `PORTABLE=1 make static_lib`
|
||||
* Install zlib. Try: `sudo apt-get install zlib1g-dev`.
|
||||
* Install bzip2: `sudo apt-get install libbz2-dev`.
|
||||
* **Linux - CentOS**
|
||||
* Upgrade your gcc to version at least 4.7 to get C++11 support:
|
||||
`yum install gcc47-c++`
|
||||
* Upgrade your gcc to version at least 4.8 to get C++11 support:
|
||||
`yum install gcc48-c++`
|
||||
* Install gflags:
|
||||
|
||||
wget https://gflags.googlecode.com/files/gflags-2.0-no-svn-files.tar.gz
|
||||
@@ -80,7 +80,7 @@ your make commands, like this: `PORTABLE=1 make static_lib`
|
||||
* Update XCode: run `xcode-select --install` (or install it from XCode App's settting).
|
||||
* Install via [homebrew](http://brew.sh/).
|
||||
* If you're first time developer in MacOS, you still need to run: `xcode-select --install` in your command line.
|
||||
* run `brew tap homebrew/versions; brew install gcc47 --use-llvm` to install gcc 4.7 (or higher).
|
||||
* run `brew tap homebrew/versions; brew install gcc48 --use-llvm` to install gcc 4.8 (or higher).
|
||||
* run `brew install rocksdb`
|
||||
|
||||
* **iOS**:
|
||||
|
||||
@@ -166,6 +166,8 @@ ifneq ($(filter -DROCKSDB_LITE,$(OPT)),)
|
||||
# found
|
||||
CFLAGS += -fno-exceptions
|
||||
CXXFLAGS += -fno-exceptions
|
||||
# LUA is not supported under ROCKSDB_LITE
|
||||
LUA_PATH =
|
||||
endif
|
||||
|
||||
# ASAN doesn't work well with jemalloc. If we're compiling with ASAN, we should use regular malloc.
|
||||
@@ -185,6 +187,8 @@ ifdef COMPILE_WITH_TSAN
|
||||
# Turn off -pg when enabling TSAN testing, because that induces
|
||||
# a link failure. TODO: find the root cause
|
||||
PROFILING_FLAGS =
|
||||
# LUA is not supported under TSAN
|
||||
LUA_PATH =
|
||||
endif
|
||||
|
||||
# USAN doesn't work well with jemalloc. If we're compiling with USAN, we should use regular malloc.
|
||||
@@ -216,10 +220,34 @@ default: all
|
||||
WARNING_FLAGS = -W -Wextra -Wall -Wsign-compare -Wshadow \
|
||||
-Wno-unused-parameter
|
||||
|
||||
ifndef DISABLE_WARNING_AS_ERROR
|
||||
WARNING_FLAGS += -Werror
|
||||
|
||||
ifdef LUA_PATH
|
||||
|
||||
ifndef LUA_INCLUDE
|
||||
LUA_INCLUDE=$(LUA_PATH)/include
|
||||
endif
|
||||
|
||||
LUA_INCLUDE_FILE=$(LUA_INCLUDE)/lualib.h
|
||||
|
||||
ifeq ("$(wildcard $(LUA_INCLUDE_FILE))", "")
|
||||
# LUA_INCLUDE_FILE does not exist
|
||||
$(error Cannot find lualib.h under $(LUA_INCLUDE). Try to specify both LUA_PATH and LUA_INCLUDE manually)
|
||||
endif
|
||||
LUA_FLAGS = -I$(LUA_INCLUDE) -DLUA -DLUA_COMPAT_ALL
|
||||
CFLAGS += $(LUA_FLAGS)
|
||||
CXXFLAGS += $(LUA_FLAGS)
|
||||
|
||||
ifndef LUA_LIB
|
||||
LUA_LIB = $(LUA_PATH)/lib/liblua.a
|
||||
endif
|
||||
ifeq ("$(wildcard $(LUA_LIB))", "") # LUA_LIB does not exist
|
||||
$(error $(LUA_LIB) does not exist. Try to specify both LUA_PATH and LUA_LIB manually)
|
||||
endif
|
||||
LDFLAGS += $(LUA_LIB)
|
||||
|
||||
endif
|
||||
|
||||
|
||||
CFLAGS += $(WARNING_FLAGS) -I. -I./include $(PLATFORM_CCFLAGS) $(OPT)
|
||||
CXXFLAGS += $(WARNING_FLAGS) -I. -I./include $(PLATFORM_CXXFLAGS) $(OPT) -Woverloaded-virtual -Wnon-virtual-dtor -Wno-missing-field-initializers
|
||||
|
||||
@@ -231,14 +259,7 @@ ifdef FORCE_GIT_SHA
|
||||
else
|
||||
git_sha := $(shell git rev-parse HEAD 2>/dev/null)
|
||||
endif
|
||||
gen_build_version = \
|
||||
printf '%s\n' \
|
||||
'\#include "build_version.h"' \
|
||||
'const char* rocksdb_build_git_sha = \
|
||||
"rocksdb_build_git_sha:$(git_sha)";' \
|
||||
'const char* rocksdb_build_git_date = \
|
||||
"rocksdb_build_git_date:$(date)";' \
|
||||
'const char* rocksdb_build_compile_date = __DATE__;'
|
||||
gen_build_version = sed -e s/@@GIT_SHA@@/$(git_sha)/ -e s/@@GIT_DATE_TIME@@/$(date)/ util/build_version.cc.in
|
||||
|
||||
# Record the version of the source that we are compiling.
|
||||
# We keep a record of the git revision in this file. It is then built
|
||||
@@ -283,7 +304,10 @@ TESTS = \
|
||||
db_flush_test \
|
||||
db_inplace_update_test \
|
||||
db_iterator_test \
|
||||
db_memtable_test \
|
||||
db_merge_operator_test \
|
||||
db_options_test \
|
||||
db_range_del_test \
|
||||
db_sst_test \
|
||||
external_sst_file_test \
|
||||
db_tailing_iter_test \
|
||||
@@ -386,6 +410,7 @@ TESTS = \
|
||||
iostats_context_test \
|
||||
persistent_cache_test \
|
||||
statistics_test \
|
||||
lua_test \
|
||||
lru_cache_test \
|
||||
|
||||
PARALLEL_TEST = \
|
||||
@@ -400,7 +425,9 @@ PARALLEL_TEST = \
|
||||
fault_injection_test \
|
||||
inlineskiplist_test \
|
||||
manual_compaction_test \
|
||||
table_test
|
||||
persistent_cache_test \
|
||||
table_test \
|
||||
transaction_test
|
||||
|
||||
SUBSET := $(TESTS)
|
||||
ifdef ROCKSDBTESTS_START
|
||||
@@ -680,7 +707,8 @@ ifeq ($(filter -DROCKSDB_LITE,$(OPT)),)
|
||||
sh tools/rocksdb_dump_test.sh
|
||||
endif
|
||||
|
||||
check_some: $(SUBSET) ldb_tests
|
||||
# TODO add ldb_tests
|
||||
check_some: $(SUBSET)
|
||||
for t in $(SUBSET); do echo "===== Running $$t"; ./$$t || exit 1; done
|
||||
|
||||
.PHONY: ldb_tests
|
||||
@@ -723,6 +751,9 @@ ubsan_crash_test:
|
||||
COMPILE_WITH_UBSAN=1 $(MAKE) crash_test
|
||||
$(MAKE) clean
|
||||
|
||||
valgrind_test:
|
||||
DISABLE_JEMALLOC=1 $(MAKE) valgrind_check
|
||||
|
||||
valgrind_check: $(TESTS)
|
||||
$(MAKE) DRIVER="$(VALGRIND_VER) $(VALGRIND_OPTS)" gen_parallel_tests
|
||||
$(AM_V_GEN)if test "$(J)" != 1 \
|
||||
@@ -959,9 +990,18 @@ db_inplace_update_test: db/db_inplace_update_test.o db/db_test_util.o $(LIBOBJEC
|
||||
db_iterator_test: db/db_iterator_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_memtable_test: db/db_memtable_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_merge_operator_test: db/db_merge_operator_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_options_test: db/db_options_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_range_del_test: db/db_range_del_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_sst_test: db/db_sst_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1258,6 +1298,8 @@ statistics_test: util/statistics_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
lru_cache_test: util/lru_cache_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
lua_test: utilities/lua/rocks_lua_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
#-------------------------------------------------
|
||||
# make install related stuff
|
||||
@@ -1321,6 +1363,11 @@ else
|
||||
JAVA_INCLUDE = -I/System/Library/Frameworks/JavaVM.framework/Headers/
|
||||
endif
|
||||
endif
|
||||
ifeq ($(PLATFORM), OS_FREEBSD)
|
||||
JAVA_INCLUDE += -I$(JAVA_HOME)/include/freebsd
|
||||
ROCKSDBJNILIB = librocksdbjni-freebsd$(ARCH).so
|
||||
ROCKSDB_JAR = rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH)-freebsd$(ARCH).jar
|
||||
endif
|
||||
ifeq ($(PLATFORM), OS_SOLARIS)
|
||||
ROCKSDBJNILIB = librocksdbjni-solaris$(ARCH).so
|
||||
ROCKSDB_JAR = rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH)-solaris$(ARCH).jar
|
||||
|
||||
Vendored
+1
@@ -1,3 +1,4 @@
|
||||
# Vagrant file
|
||||
Vagrant.configure("2") do |config|
|
||||
|
||||
config.vm.provider "virtualbox" do |v|
|
||||
|
||||
@@ -12,7 +12,6 @@ define("ENV_HTTPS_APP_VALUE", "HTTPS_APP_VALUE");
|
||||
define("ENV_HTTPS_TOKEN_VALUE", "HTTPS_TOKEN_VALUE");
|
||||
|
||||
define("PRIMARY_TOKEN_FILE", '/home/krad/.sandcastle');
|
||||
define("SECONDARY_TOKEN_FILE", '$HOME/.sandcastle');
|
||||
define("CONT_RUN_ALIAS", "leveldb");
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
@@ -25,9 +24,7 @@ function postURL($diffID, $url) {
|
||||
$cmd = 'echo \'{"diff_id": "' . $diffID . '", '
|
||||
. '"name":"click here for sandcastle tests for D' . $diffID . '", '
|
||||
. '"link":"' . $url . '"}\' | '
|
||||
. 'no_proxy=facebook.com,tfbnw.net,fb.com '
|
||||
. 'http_proxy=fwdproxy.any.facebook.com:8080 '
|
||||
. 'https_proxy=fwdproxy.any.facebook.com:8080 arc call-conduit '
|
||||
. 'arc call-conduit '
|
||||
. 'differential.updateunitresults';
|
||||
shell_exec($cmd);
|
||||
}
|
||||
@@ -41,9 +38,7 @@ function buildUpdateTestStatusCmd($diffID, $test, $status) {
|
||||
$cmd = 'echo \'{"diff_id": "' . $diffID . '", '
|
||||
. '"name":"' . $test . '", '
|
||||
. '"result":"' . $status . '"}\' | '
|
||||
. 'no_proxy=facebook.com,tfbnw.net,fb.com '
|
||||
. 'http_proxy=fwdproxy.any.facebook.com:8080 '
|
||||
. 'https_proxy=fwdproxy.any.facebook.com:8080 arc call-conduit '
|
||||
. 'arc call-conduit '
|
||||
. 'differential.updateunitresults';
|
||||
return $cmd;
|
||||
}
|
||||
@@ -108,8 +103,7 @@ function getSteps($applyDiff, $diffID, $username, $test) {
|
||||
// Patch the code (keep your fingures crossed).
|
||||
$patch = array(
|
||||
"name" => "Patch " . $diffID,
|
||||
"shell" => "no_proxy=facebook.com,tfbnw.net,fb.com "
|
||||
."HTTPS_PROXY=fwdproxy:8080 arc --arcrc-file ~/.arcrc "
|
||||
"shell" => "arc --arcrc-file ~/.arcrc "
|
||||
. "patch --nocommit --diff " . $diffID,
|
||||
"user" => "root"
|
||||
);
|
||||
@@ -178,6 +172,8 @@ function getSteps($applyDiff, $diffID, $username, $test) {
|
||||
function getSandcastleConfig() {
|
||||
$sandcastle_config = array();
|
||||
|
||||
$cwd = getcwd();
|
||||
$cwd_token_file = "{$cwd}/.sandcastle";
|
||||
// This is a case when we're executed from a continuous run. Fetch the values
|
||||
// from the environment.
|
||||
if (getenv(ENV_POST_RECEIVE_HOOK)) {
|
||||
@@ -188,20 +184,20 @@ function getSandcastleConfig() {
|
||||
// configuration files.
|
||||
for ($i = 0; $i < 50; $i++) {
|
||||
if (file_exists(PRIMARY_TOKEN_FILE) ||
|
||||
file_exists(SECONDARY_TOKEN_FILE)) {
|
||||
file_exists($cwd_token_file)) {
|
||||
break;
|
||||
}
|
||||
// If we failed to fetch the tokens, sleep for 0.2 second and try again
|
||||
usleep(200000);
|
||||
}
|
||||
assert(file_exists(PRIMARY_TOKEN_FILE) ||
|
||||
file_exists(SECONDARY_TOKEN_FILE));
|
||||
file_exists($cwd_token_file));
|
||||
|
||||
// Try the primary location first, followed by a secondary.
|
||||
if (file_exists(PRIMARY_TOKEN_FILE)) {
|
||||
$cmd = 'cat ' . PRIMARY_TOKEN_FILE;
|
||||
} else {
|
||||
$cmd = 'cat ' . SECONDARY_TOKEN_FILE;
|
||||
$cmd = 'cat ' . $cwd_token_file;
|
||||
}
|
||||
|
||||
assert(strlen($cmd) > 0);
|
||||
@@ -303,11 +299,21 @@ function getSandcastleConfig() {
|
||||
);
|
||||
|
||||
// Submit to Sandcastle.
|
||||
$url = 'https://interngraph.intern.facebook.com/sandcastle/generate?'
|
||||
.'command=SandcastleUniversalCommand'
|
||||
.'&vcs=rocksdb-git&revision=origin%2Fmaster&type=lego'
|
||||
.'&user=' . $username . '&alias=rocksdb-precommit'
|
||||
.'&command-args=' . urlencode(json_encode($command));
|
||||
$url = 'https://interngraph.intern.facebook.com/sandcastle/create';
|
||||
|
||||
$job = array(
|
||||
'command' => 'SandcastleUniversalCommand',
|
||||
'args' => $command,
|
||||
'capabilities' => array(
|
||||
'vcs' => 'rocksdb-int-git',
|
||||
'type' => 'lego',
|
||||
),
|
||||
'hash' => 'origin/master',
|
||||
'user' => $username,
|
||||
'alias' => 'rocksdb-precommit',
|
||||
'tags' => array('rocksdb'),
|
||||
'description' => 'Rocksdb precommit job',
|
||||
);
|
||||
|
||||
// Fetch the configuration necessary to submit a successful HTTPS request.
|
||||
$sandcastle_config = getSandcastleConfig();
|
||||
@@ -315,8 +321,9 @@ function getSandcastleConfig() {
|
||||
$app = $sandcastle_config[0];
|
||||
$token = $sandcastle_config[1];
|
||||
|
||||
$cmd = 'https_proxy= HTTPS_PROXY= curl -s -k -F app=' . $app . ' '
|
||||
. '-F token=' . $token . ' "' . $url . '"';
|
||||
$cmd = 'curl -s -k -F app=' . $app . ' '
|
||||
. '-F token=' . $token . ' -F job=\'' . json_encode($job)
|
||||
.'\' "' . $url . '"';
|
||||
|
||||
$output = shell_exec($cmd);
|
||||
assert(strlen($output) > 0);
|
||||
|
||||
@@ -51,12 +51,7 @@ if [ -z "$ROCKSDB_NO_FBCODE" -a -d /mnt/gvfs/third-party ]; then
|
||||
FBCODE_BUILD="true"
|
||||
# If we're compiling with TSAN we need pic build
|
||||
PIC_BUILD=$COMPILE_WITH_TSAN
|
||||
if [ -z "$ROCKSDB_FBCODE_BUILD_WITH_481" ]; then
|
||||
source "$PWD/build_tools/fbcode_config.sh"
|
||||
else
|
||||
# we need this to build with MySQL. Don't use for other purposes.
|
||||
source "$PWD/build_tools/fbcode_config4.8.1.sh"
|
||||
fi
|
||||
source "$PWD/build_tools/fbcode_config.sh"
|
||||
fi
|
||||
|
||||
# Delete existing output, if it exists
|
||||
@@ -466,3 +461,4 @@ echo "PROFILING_FLAGS=$PROFILING_FLAGS" >> "$OUTPUT"
|
||||
if test -n "$JEMALLOC"; then
|
||||
echo "JEMALLOC=1" >> "$OUTPUT"
|
||||
fi
|
||||
echo "LUA_PATH=$LUA_PATH" >> "$OUTPUT"
|
||||
+19
-17
@@ -1,17 +1,19 @@
|
||||
GCC_BASE=/mnt/gvfs/third-party2/gcc/cf7d14c625ce30bae1a4661c2319c5a283e4dd22/4.9.x/centos6-native/108cf83
|
||||
CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/8598c375b0e94e1448182eb3df034704144a838d/stable/centos6-native/3f16ddd
|
||||
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/d6e0a7da6faba45f5e5b1638f9edd7afc2f34e7d/4.9.x/gcc-4.9-glibc-2.20/024dbc3
|
||||
GLIBC_BASE=/mnt/gvfs/third-party2/glibc/d282e6e8f3d20f4e40a516834847bdc038e07973/2.20/gcc-4.9-glibc-2.20/500e281
|
||||
SNAPPY_BASE=/mnt/gvfs/third-party2/snappy/8c38a4c1e52b4c2cc8a9cdc31b9c947ed7dbfcb4/1.1.3/gcc-4.9-glibc-2.20/e9936bf
|
||||
ZLIB_BASE=/mnt/gvfs/third-party2/zlib/0882df3713c7a84f15abe368dc004581f20b39d7/1.2.8/gcc-5-glibc-2.23/9bc6787
|
||||
BZIP2_BASE=/mnt/gvfs/third-party2/bzip2/740325875f6729f42d28deaa2147b0854f3a347e/1.0.6/gcc-5-glibc-2.23/9bc6787
|
||||
LZ4_BASE=/mnt/gvfs/third-party2/lz4/0e790b441e2d9acd68d51e1d2e028f88c6a79ddf/r131/gcc-5-glibc-2.23/9bc6787
|
||||
ZSTD_BASE=/mnt/gvfs/third-party2/zstd/9455f75ff7f4831dc9fda02a6a0f8c68922fad8f/1.0.0/gcc-5-glibc-2.23/9bc6787
|
||||
GFLAGS_BASE=/mnt/gvfs/third-party2/gflags/f001a51b2854957676d07306ef3abf67186b5c8b/2.1.1/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/fc8a13ca1fffa4d0765c716c5a0b49f0c107518f/master/gcc-5-glibc-2.23/1c32b4b
|
||||
NUMA_BASE=/mnt/gvfs/third-party2/numa/17c514c4d102a25ca15f4558be564eeed76f4b6a/2.0.8/gcc-5-glibc-2.23/9bc6787
|
||||
LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/ad576de2a1ea560c4d3434304f0fc4e079bede42/trunk/gcc-5-glibc-2.23/b1847cb
|
||||
TBB_BASE=/mnt/gvfs/third-party2/tbb/9d9a554877d0c5bef330fe818ab7178806dd316a/4.0_update2/gcc-4.9-glibc-2.20/e9936bf
|
||||
KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/7c111ff27e0c466235163f00f280a9d617c3d2ec/4.0.9-36_fbk5_2933_gd092e3f/gcc-5-glibc-2.23/da39a3e
|
||||
BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/b7fd454c4b10c6a81015d4524ed06cdeab558490/2.26/centos6-native/da39a3e
|
||||
VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/d7f4d4d86674a57668e3a96f76f0e17dd0eb8765/3.10.0/gcc-4.9-glibc-2.20/e9936bf
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
GCC_BASE=/mnt/gvfs/third-party2/gcc/7331085db891a2ef4a88a48a751d834e8d68f4cb/7.x/centos7-native/b2ef2b6
|
||||
CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/963d9aeda70cc4779885b1277484fe7544a04e3e/9.0.0/platform007/9e92d53/
|
||||
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/6ace84e956873d53638c738b6f65f3f469cca74c/7.x/platform007/5620abc
|
||||
GLIBC_BASE=/mnt/gvfs/third-party2/glibc/192b0f42d63dcf6210d6ceae387b49af049e6e0c/2.26/platform007/f259413
|
||||
SNAPPY_BASE=/mnt/gvfs/third-party2/snappy/7f9bdaada18f59bc27ec2b0871eb8a6144343aef/1.1.3/platform007/ca4da3d
|
||||
ZLIB_BASE=/mnt/gvfs/third-party2/zlib/2d9f0b9a4274cc21f61272a9e89bdb859bce8f1f/1.2.8/platform007/ca4da3d
|
||||
BZIP2_BASE=/mnt/gvfs/third-party2/bzip2/dc49a21c5fceec6456a7a28a94dcd16690af1337/1.0.6/platform007/ca4da3d
|
||||
LZ4_BASE=/mnt/gvfs/third-party2/lz4/0f607f8fc442ea7d6b876931b1898bb573d5e5da/1.9.1/platform007/ca4da3d
|
||||
ZSTD_BASE=/mnt/gvfs/third-party2/zstd/ca22bc441a4eb709e9e0b1f9fec9750fed7b31c5/1.4.x/platform007/15a3614
|
||||
GFLAGS_BASE=/mnt/gvfs/third-party2/gflags/0b9929d2588991c65a57168bf88aff2db87c5d48/2.2.0/platform007/ca4da3d
|
||||
JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/c26f08f47ac35fc31da2633b7da92d6b863246eb/master/platform007/c26c002
|
||||
NUMA_BASE=/mnt/gvfs/third-party2/numa/3f3fb57a5ccc5fd21c66416c0b83e0aa76a05376/2.0.11/platform007/ca4da3d
|
||||
LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/40c73d874898b386a71847f1b99115d93822d11f/1.4/platform007/6f3e0a9
|
||||
TBB_BASE=/mnt/gvfs/third-party2/tbb/4ce8e8dba77cdbd81b75d6f0c32fd7a1b76a11ec/2018_U5/platform007/ca4da3d
|
||||
KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/fb251ecd2f5ae16f8671f7014c246e52a748fe0b/fb/platform007/da39a3e
|
||||
BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/ab9f09bba370e7066cafd4eb59752db93f2e8312/2.29.1/platform007/15a3614
|
||||
VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/d42d152a15636529b0861ec493927200ebebca8e/3.15.0/platform007/ca4da3d
|
||||
LUA_BASE=/mnt/gvfs/third-party2/lua/f0cd714433206d5139df61659eb7b28b1dea6683/5.3.4/platform007/5007832
|
||||
@@ -15,3 +15,4 @@ TBB_BASE=/mnt/gvfs/third-party2/tbb/9d9a554877d0c5bef330fe818ab7178806dd316a/4.0
|
||||
KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/7c111ff27e0c466235163f00f280a9d617c3d2ec/4.0.9-36_fbk5_2933_gd092e3f/gcc-4.8.1-glibc-2.17/da39a3e
|
||||
BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/b7fd454c4b10c6a81015d4524ed06cdeab558490/2.26/centos6-native/da39a3e
|
||||
VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/d7f4d4d86674a57668e3a96f76f0e17dd0eb8765/3.8.1/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
LUA_BASE=/mnt/gvfs/third-party2/lua/61e4abf5813bbc39bc4f548757ccfcadde175a48/5.2.3/centos6-native/730f94e
|
||||
@@ -13,7 +13,7 @@ source "$BASEDIR/dependencies.sh"
|
||||
CFLAGS=""
|
||||
|
||||
# libgcc
|
||||
LIBGCC_INCLUDE="$LIBGCC_BASE/include"
|
||||
LIBGCC_INCLUDE="$LIBGCC_BASE/include/c++/7.3.0"
|
||||
LIBGCC_LIBS=" -L $LIBGCC_BASE/lib"
|
||||
|
||||
# glibc
|
||||
@@ -43,12 +43,16 @@ if test -z $PIC_BUILD; then
|
||||
LZ4_INCLUDE=" -I $LZ4_BASE/include/"
|
||||
LZ4_LIBS=" $LZ4_BASE/lib/liblz4.a"
|
||||
CFLAGS+=" -DLZ4"
|
||||
|
||||
ZSTD_INCLUDE=" -I $ZSTD_BASE/include/"
|
||||
ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd.a"
|
||||
CFLAGS+=" -DZSTD"
|
||||
fi
|
||||
|
||||
ZSTD_INCLUDE=" -I $ZSTD_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd.a"
|
||||
else
|
||||
ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DZSTD"
|
||||
|
||||
# location of gflags headers and libraries
|
||||
GFLAGS_INCLUDE=" -I $GFLAGS_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
@@ -56,7 +60,7 @@ if test -z $PIC_BUILD; then
|
||||
else
|
||||
GFLAGS_LIBS=" $GFLAGS_BASE/lib/libgflags_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DGFLAGS=google"
|
||||
CFLAGS+=" -DGFLAGS=gflags"
|
||||
|
||||
# location of jemalloc
|
||||
JEMALLOC_INCLUDE=" -I $JEMALLOC_BASE/include/"
|
||||
@@ -104,8 +108,8 @@ if [ -z "$USE_CLANG" ]; then
|
||||
CXX="$GCC_BASE/bin/g++"
|
||||
|
||||
CFLAGS+=" -B$BINUTILS/gold"
|
||||
CFLAGS+=" -isystem $GLIBC_INCLUDE"
|
||||
CFLAGS+=" -isystem $LIBGCC_INCLUDE"
|
||||
CFLAGS+=" -isystem $GLIBC_INCLUDE"
|
||||
JEMALLOC=1
|
||||
else
|
||||
# clang
|
||||
@@ -116,8 +120,8 @@ else
|
||||
KERNEL_HEADERS_INCLUDE="$KERNEL_HEADERS_BASE/include"
|
||||
|
||||
CFLAGS+=" -B$BINUTILS/gold -nostdinc -nostdlib"
|
||||
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/4.9.x "
|
||||
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/4.9.x/x86_64-facebook-linux "
|
||||
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/7.x "
|
||||
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/7.x/x86_64-facebook-linux "
|
||||
CFLAGS+=" -isystem $GLIBC_INCLUDE"
|
||||
CFLAGS+=" -isystem $LIBGCC_INCLUDE"
|
||||
CFLAGS+=" -isystem $CLANG_INCLUDE"
|
||||
@@ -128,13 +132,14 @@ else
|
||||
fi
|
||||
|
||||
CFLAGS+=" $DEPS_INCLUDE"
|
||||
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX -DROCKSDB_FALLOCATE_PRESENT -DROCKSDB_MALLOC_USABLE_SIZE"
|
||||
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX -DROCKSDB_FALLOCATE_PRESENT -DROCKSDB_MALLOC_USABLE_SIZE -DROCKSDB_RANGESYNC_PRESENT -DROCKSDB_SCHED_GETCPU_PRESENT -DROCKSDB_SUPPORT_THREAD_LOCAL -DHAVE_SSE42"
|
||||
CXXFLAGS+=" $CFLAGS"
|
||||
|
||||
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB $TBB_LIBS"
|
||||
EXEC_LDFLAGS+=" -Wl,--dynamic-linker,/usr/local/fbcode/gcc-4.9-glibc-2.20/lib/ld.so"
|
||||
EXEC_LDFLAGS+=" -B$BINUTILS/gold"
|
||||
EXEC_LDFLAGS+=" -Wl,--dynamic-linker,/usr/local/fbcode/platform007/lib/ld.so"
|
||||
EXEC_LDFLAGS+=" $LIBUNWIND"
|
||||
EXEC_LDFLAGS+=" -Wl,-rpath=/usr/local/fbcode/gcc-4.9-glibc-2.20/lib"
|
||||
EXEC_LDFLAGS+=" -Wl,-rpath=/usr/local/fbcode/platform007/lib"
|
||||
# required by libtbb
|
||||
EXEC_LDFLAGS+=" -ldl"
|
||||
|
||||
@@ -144,4 +149,4 @@ EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GF
|
||||
|
||||
VALGRIND_VER="$VALGRIND_BASE/bin/"
|
||||
|
||||
export CC CXX AR CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD
|
||||
export CC CXX AR CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD
|
||||
@@ -110,4 +110,6 @@ EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP2_LIBS $LZ4_LIBS $ZSTD_LIBS $G
|
||||
|
||||
VALGRIND_VER="$VALGRIND_BASE/bin/"
|
||||
|
||||
export CC CXX AR CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE
|
||||
LUA_PATH="$LUA_BASE"
|
||||
|
||||
export CC CXX AR CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE LUA_PATH
|
||||
@@ -65,8 +65,8 @@ function main() {
|
||||
|
||||
if [[ -d /vagrant ]]; then
|
||||
if [[ $OS = "ubuntu" ]]; then
|
||||
package g++-4.7
|
||||
export CXX=g++-4.7
|
||||
package g++-4.8
|
||||
export CXX=g++-4.8
|
||||
|
||||
# the deb would depend on libgflags2, but the static lib is the only thing
|
||||
# installed by make install
|
||||
|
||||
@@ -100,7 +100,7 @@ CONTRUN_NAME="ROCKSDB_CONTRUN_NAME"
|
||||
# the value of $ONCALL. If it's a diff then just call `false` to make sure
|
||||
# that errors will be properly propagated to the caller.
|
||||
if [ ! -z $ONCALL ]; then
|
||||
TASK_CREATION_TOOL="/usr/local/bin/mysql_mtr_filter --rocksdb"
|
||||
TASK_CREATION_TOOL="/usr/local/bin/mysql_mtr_filter --rocksdb --oncall $ONCALL"
|
||||
else
|
||||
TASK_CREATION_TOOL="false"
|
||||
fi
|
||||
@@ -548,7 +548,7 @@ VALGRIND_TEST_COMMANDS="[
|
||||
{
|
||||
'name':'Run RocksDB debug unit tests',
|
||||
'timeout': 86400,
|
||||
'shell':'$DISABLE_JEMALLOC $SHM $DEBUG make $PARALLELISM valgrind_check || $CONTRUN_NAME=valgrind_check $TASK_CREATION_TOOL',
|
||||
'shell':'$SHM $DEBUG make $PARALLELISM valgrind_test || $CONTRUN_NAME=valgrind_check $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
|
||||
@@ -87,6 +87,7 @@ get_lib_base tbb 4.0_update2 gcc-4.9-glibc-2.20
|
||||
get_lib_base kernel-headers LATEST
|
||||
get_lib_base binutils LATEST centos6-native
|
||||
get_lib_base valgrind 3.10.0 gcc-4.9-glibc-2.20
|
||||
get_lib_base lua 5.2.3 gcc-4.9-glibc-2.20
|
||||
|
||||
git diff $OUTPUT
|
||||
|
||||
@@ -125,5 +126,6 @@ get_lib_base tbb 4.0_update2 gcc-4.8.1-glibc-2.17
|
||||
get_lib_base kernel-headers LATEST gcc-4.8.1-glibc-2.17
|
||||
get_lib_base binutils LATEST centos6-native
|
||||
get_lib_base valgrind 3.8.1 gcc-4.8.1-glibc-2.17
|
||||
get_lib_base lua 5.2.3 centos6-native
|
||||
|
||||
git diff $OUTPUT
|
||||
+15
-5
@@ -61,7 +61,8 @@ TableBuilder* NewTableBuilder(
|
||||
Status BuildTable(
|
||||
const std::string& dbname, Env* env, const ImmutableCFOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options, const EnvOptions& env_options,
|
||||
TableCache* table_cache, InternalIterator* iter, FileMetaData* meta,
|
||||
TableCache* table_cache, InternalIterator* iter,
|
||||
std::unique_ptr<InternalIterator> range_del_iter, FileMetaData* meta,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
|
||||
int_tbl_prop_collector_factories,
|
||||
@@ -81,6 +82,13 @@ Status BuildTable(
|
||||
Status s;
|
||||
meta->fd.file_size = 0;
|
||||
iter->SeekToFirst();
|
||||
std::unique_ptr<RangeDelAggregator> range_del_agg(
|
||||
new RangeDelAggregator(internal_comparator, snapshots));
|
||||
s = range_del_agg->AddTombstones(std::move(range_del_iter));
|
||||
if (!s.ok()) {
|
||||
// may be non-ok if a range tombstone key is unparsable
|
||||
return s;
|
||||
}
|
||||
|
||||
std::string fname = TableFileName(ioptions.db_paths, meta->fd.GetNumber(),
|
||||
meta->fd.GetPathId());
|
||||
@@ -90,7 +98,7 @@ Status BuildTable(
|
||||
#endif // !ROCKSDB_LITE
|
||||
TableProperties tp;
|
||||
|
||||
if (iter->Valid()) {
|
||||
if (iter->Valid() || range_del_agg->ShouldAddTombstones()) {
|
||||
TableBuilder* builder;
|
||||
unique_ptr<WritableFileWriter> file_writer;
|
||||
{
|
||||
@@ -112,8 +120,6 @@ Status BuildTable(
|
||||
compression_opts, level);
|
||||
}
|
||||
|
||||
std::unique_ptr<RangeDelAggregator> range_del_agg;
|
||||
range_del_agg.reset(new RangeDelAggregator(internal_comparator, snapshots));
|
||||
MergeHelper merge(env, internal_comparator.user_comparator(),
|
||||
ioptions.merge_operator, nullptr, ioptions.info_log,
|
||||
mutable_cf_options.min_partial_merge_operands,
|
||||
@@ -138,6 +144,9 @@ Status BuildTable(
|
||||
ThreadStatus::FLUSH_BYTES_WRITTEN, IOSTATS(bytes_written));
|
||||
}
|
||||
}
|
||||
// nullptr for table_{min,max} so all range tombstones will be flushed
|
||||
range_del_agg->AddToBuilder(builder, nullptr /* lower_bound */,
|
||||
nullptr /* upper_bound */, meta);
|
||||
|
||||
// Finish and check for builder errors
|
||||
bool empty = builder->NumEntries() == 0;
|
||||
@@ -172,7 +181,8 @@ Status BuildTable(
|
||||
if (s.ok() && !empty) {
|
||||
// Verify that the table is usable
|
||||
std::unique_ptr<InternalIterator> it(table_cache->NewIterator(
|
||||
ReadOptions(), env_options, internal_comparator, meta->fd, nullptr,
|
||||
ReadOptions(), env_options, internal_comparator, meta->fd,
|
||||
nullptr /* range_del_agg */, nullptr,
|
||||
(internal_stats == nullptr) ? nullptr
|
||||
: internal_stats->GetFileReadHist(0),
|
||||
false /* for_compaction */, nullptr /* arena */,
|
||||
|
||||
+3
-1
@@ -17,6 +17,7 @@
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/table_properties.h"
|
||||
#include "rocksdb/types.h"
|
||||
#include "table/scoped_arena_iterator.h"
|
||||
#include "util/cf_options.h"
|
||||
#include "util/event_logger.h"
|
||||
|
||||
@@ -63,7 +64,8 @@ TableBuilder* NewTableBuilder(
|
||||
extern Status BuildTable(
|
||||
const std::string& dbname, Env* env, const ImmutableCFOptions& options,
|
||||
const MutableCFOptions& mutable_cf_options, const EnvOptions& env_options,
|
||||
TableCache* table_cache, InternalIterator* iter, FileMetaData* meta,
|
||||
TableCache* table_cache, InternalIterator* iter,
|
||||
std::unique_ptr<InternalIterator> range_del_iter, FileMetaData* meta,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
|
||||
int_tbl_prop_collector_factories,
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "rocksdb/slice_transform.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "rocksdb/rate_limiter.h"
|
||||
#include "rocksdb/utilities/backupable_db.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
|
||||
@@ -47,10 +48,12 @@ using rocksdb::WALRecoveryMode;
|
||||
using rocksdb::DB;
|
||||
using rocksdb::DBOptions;
|
||||
using rocksdb::Env;
|
||||
using rocksdb::EnvOptions;
|
||||
using rocksdb::InfoLogLevel;
|
||||
using rocksdb::FileLock;
|
||||
using rocksdb::FilterPolicy;
|
||||
using rocksdb::FlushOptions;
|
||||
using rocksdb::IngestExternalFileOptions;
|
||||
using rocksdb::Iterator;
|
||||
using rocksdb::Logger;
|
||||
using rocksdb::MergeOperator;
|
||||
@@ -68,6 +71,7 @@ using rocksdb::Slice;
|
||||
using rocksdb::SliceParts;
|
||||
using rocksdb::SliceTransform;
|
||||
using rocksdb::Snapshot;
|
||||
using rocksdb::SstFileWriter;
|
||||
using rocksdb::Status;
|
||||
using rocksdb::WritableFile;
|
||||
using rocksdb::WriteBatch;
|
||||
@@ -78,6 +82,8 @@ using rocksdb::BackupableDBOptions;
|
||||
using rocksdb::BackupInfo;
|
||||
using rocksdb::RestoreOptions;
|
||||
using rocksdb::CompactRangeOptions;
|
||||
using rocksdb::RateLimiter;
|
||||
using rocksdb::NewGenericRateLimiter;
|
||||
|
||||
using std::shared_ptr;
|
||||
|
||||
@@ -108,6 +114,10 @@ struct rocksdb_logger_t { shared_ptr<Logger> rep; };
|
||||
struct rocksdb_cache_t { shared_ptr<Cache> rep; };
|
||||
struct rocksdb_livefiles_t { std::vector<LiveFileMetaData> rep; };
|
||||
struct rocksdb_column_family_handle_t { ColumnFamilyHandle* rep; };
|
||||
struct rocksdb_envoptions_t { EnvOptions rep; };
|
||||
struct rocksdb_ingestexternalfileoptions_t { IngestExternalFileOptions rep; };
|
||||
struct rocksdb_sstfilewriter_t { SstFileWriter* rep; };
|
||||
struct rocksdb_ratelimiter_t { RateLimiter* rep; };
|
||||
|
||||
struct rocksdb_compactionfiltercontext_t {
|
||||
CompactionFilter::Context rep;
|
||||
@@ -1021,6 +1031,11 @@ void rocksdb_iter_seek(rocksdb_iterator_t* iter, const char* k, size_t klen) {
|
||||
iter->rep->Seek(Slice(k, klen));
|
||||
}
|
||||
|
||||
void rocksdb_iter_seek_for_prev(rocksdb_iterator_t* iter, const char* k,
|
||||
size_t klen) {
|
||||
iter->rep->SeekForPrev(Slice(k, klen));
|
||||
}
|
||||
|
||||
void rocksdb_iter_next(rocksdb_iterator_t* iter) {
|
||||
iter->rep->Next();
|
||||
}
|
||||
@@ -1511,8 +1526,13 @@ void rocksdb_options_set_max_bytes_for_level_base(
|
||||
opt->rep.max_bytes_for_level_base = n;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_max_bytes_for_level_multiplier(
|
||||
rocksdb_options_t* opt, int n) {
|
||||
void rocksdb_options_set_level_compaction_dynamic_level_bytes(
|
||||
rocksdb_options_t* opt, unsigned char v) {
|
||||
opt->rep.level_compaction_dynamic_level_bytes = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_max_bytes_for_level_multiplier(rocksdb_options_t* opt,
|
||||
double n) {
|
||||
opt->rep.max_bytes_for_level_multiplier = n;
|
||||
}
|
||||
|
||||
@@ -1688,6 +1708,16 @@ void rocksdb_options_set_bytes_per_sync(
|
||||
opt->rep.bytes_per_sync = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_allow_concurrent_memtable_write(rocksdb_options_t* opt,
|
||||
unsigned char v) {
|
||||
opt->rep.allow_concurrent_memtable_write = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_enable_write_thread_adaptive_yield(
|
||||
rocksdb_options_t* opt, unsigned char v) {
|
||||
opt->rep.enable_write_thread_adaptive_yield = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_verify_checksums_in_compaction(
|
||||
rocksdb_options_t* opt, unsigned char v) {
|
||||
opt->rep.verify_checksums_in_compaction = v;
|
||||
@@ -1715,6 +1745,11 @@ void rocksdb_options_set_max_background_compactions(rocksdb_options_t* opt, int
|
||||
opt->rep.max_background_compactions = n;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_base_background_compactions(rocksdb_options_t* opt,
|
||||
int n) {
|
||||
opt->rep.base_background_compactions = n;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_max_background_flushes(rocksdb_options_t* opt, int n) {
|
||||
opt->rep.max_background_flushes = n;
|
||||
}
|
||||
@@ -1874,6 +1909,28 @@ char *rocksdb_options_statistics_get_string(rocksdb_options_t *opt) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_ratelimiter(rocksdb_options_t *opt, rocksdb_ratelimiter_t *limiter) {
|
||||
opt->rep.rate_limiter.reset(limiter->rep);
|
||||
limiter->rep = nullptr;
|
||||
}
|
||||
|
||||
rocksdb_ratelimiter_t* rocksdb_ratelimiter_create(
|
||||
int64_t rate_bytes_per_sec,
|
||||
int64_t refill_period_us,
|
||||
int32_t fairness) {
|
||||
rocksdb_ratelimiter_t* rate_limiter = new rocksdb_ratelimiter_t;
|
||||
rate_limiter->rep = NewGenericRateLimiter(rate_bytes_per_sec,
|
||||
refill_period_us, fairness);
|
||||
return rate_limiter;
|
||||
}
|
||||
|
||||
void rocksdb_ratelimiter_destroy(rocksdb_ratelimiter_t *limiter) {
|
||||
if (limiter->rep) {
|
||||
delete limiter->rep;
|
||||
}
|
||||
delete limiter;
|
||||
}
|
||||
|
||||
/*
|
||||
TODO:
|
||||
DB::OpenForReadOnly
|
||||
@@ -2187,6 +2244,107 @@ void rocksdb_env_destroy(rocksdb_env_t* env) {
|
||||
delete env;
|
||||
}
|
||||
|
||||
rocksdb_envoptions_t* rocksdb_envoptions_create() {
|
||||
rocksdb_envoptions_t* opt = new rocksdb_envoptions_t;
|
||||
return opt;
|
||||
}
|
||||
|
||||
void rocksdb_envoptions_destroy(rocksdb_envoptions_t* opt) { delete opt; }
|
||||
|
||||
rocksdb_sstfilewriter_t* rocksdb_sstfilewriter_create(
|
||||
const rocksdb_envoptions_t* env, const rocksdb_options_t* io_options) {
|
||||
rocksdb_sstfilewriter_t* writer = new rocksdb_sstfilewriter_t;
|
||||
writer->rep =
|
||||
new SstFileWriter(env->rep, io_options->rep, io_options->rep.comparator);
|
||||
return writer;
|
||||
}
|
||||
|
||||
rocksdb_sstfilewriter_t* rocksdb_sstfilewriter_create_with_comparator(
|
||||
const rocksdb_envoptions_t* env, const rocksdb_options_t* io_options,
|
||||
const rocksdb_comparator_t* comparator) {
|
||||
rocksdb_sstfilewriter_t* writer = new rocksdb_sstfilewriter_t;
|
||||
writer->rep =
|
||||
new SstFileWriter(env->rep, io_options->rep, io_options->rep.comparator);
|
||||
return writer;
|
||||
}
|
||||
|
||||
void rocksdb_sstfilewriter_open(rocksdb_sstfilewriter_t* writer,
|
||||
const char* name, char** errptr) {
|
||||
SaveError(errptr, writer->rep->Open(std::string(name)));
|
||||
}
|
||||
|
||||
void rocksdb_sstfilewriter_add(rocksdb_sstfilewriter_t* writer, const char* key,
|
||||
size_t keylen, const char* val, size_t vallen,
|
||||
char** errptr) {
|
||||
SaveError(errptr, writer->rep->Add(Slice(key, keylen), Slice(val, vallen)));
|
||||
}
|
||||
|
||||
void rocksdb_sstfilewriter_finish(rocksdb_sstfilewriter_t* writer,
|
||||
char** errptr) {
|
||||
SaveError(errptr, writer->rep->Finish(NULL));
|
||||
}
|
||||
|
||||
void rocksdb_sstfilewriter_destroy(rocksdb_sstfilewriter_t* writer) {
|
||||
delete writer->rep;
|
||||
delete writer;
|
||||
}
|
||||
|
||||
rocksdb_ingestexternalfileoptions_t*
|
||||
rocksdb_ingestexternalfileoptions_create() {
|
||||
rocksdb_ingestexternalfileoptions_t* opt =
|
||||
new rocksdb_ingestexternalfileoptions_t;
|
||||
return opt;
|
||||
}
|
||||
|
||||
void rocksdb_ingestexternalfileoptions_set_move_files(
|
||||
rocksdb_ingestexternalfileoptions_t* opt, unsigned char move_files) {
|
||||
opt->rep.move_files = move_files;
|
||||
}
|
||||
|
||||
void rocksdb_ingestexternalfileoptions_set_snapshot_consistency(
|
||||
rocksdb_ingestexternalfileoptions_t* opt,
|
||||
unsigned char snapshot_consistency) {
|
||||
opt->rep.snapshot_consistency = snapshot_consistency;
|
||||
}
|
||||
|
||||
void rocksdb_ingestexternalfileoptions_set_allow_global_seqno(
|
||||
rocksdb_ingestexternalfileoptions_t* opt,
|
||||
unsigned char allow_global_seqno) {
|
||||
opt->rep.allow_global_seqno = allow_global_seqno;
|
||||
}
|
||||
|
||||
void rocksdb_ingestexternalfileoptions_set_allow_blocking_flush(
|
||||
rocksdb_ingestexternalfileoptions_t* opt,
|
||||
unsigned char allow_blocking_flush) {
|
||||
opt->rep.allow_blocking_flush = allow_blocking_flush;
|
||||
}
|
||||
|
||||
void rocksdb_ingestexternalfileoptions_destroy(
|
||||
rocksdb_ingestexternalfileoptions_t* opt) {
|
||||
delete opt;
|
||||
}
|
||||
|
||||
void rocksdb_ingest_external_file(
|
||||
rocksdb_t* db, const char* const* file_list, const size_t list_len,
|
||||
const rocksdb_ingestexternalfileoptions_t* opt, char** errptr) {
|
||||
std::vector<std::string> files(list_len);
|
||||
for (size_t i = 0; i < list_len; ++i) {
|
||||
files[i] = std::string(file_list[i]);
|
||||
}
|
||||
SaveError(errptr, db->rep->IngestExternalFile(files, opt->rep));
|
||||
}
|
||||
|
||||
void rocksdb_ingest_external_file_cf(
|
||||
rocksdb_t* db, rocksdb_column_family_handle_t* handle,
|
||||
const char* const* file_list, const size_t list_len,
|
||||
const rocksdb_ingestexternalfileoptions_t* opt, char** errptr) {
|
||||
std::vector<std::string> files(list_len);
|
||||
for (size_t i = 0; i < list_len; ++i) {
|
||||
files[i] = std::string(file_list[i]);
|
||||
}
|
||||
SaveError(errptr, db->rep->IngestExternalFile(handle->rep, files, opt->rep));
|
||||
}
|
||||
|
||||
rocksdb_slicetransform_t* rocksdb_slicetransform_create(
|
||||
void* state,
|
||||
void (*destructor)(void*),
|
||||
|
||||
+74
-7
@@ -2,17 +2,18 @@
|
||||
Use of this source code is governed by a BSD-style license that can be
|
||||
found in the LICENSE file. See the AUTHORS file for names of contributors. */
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#ifndef ROCKSDB_LITE // Lite does not support C API
|
||||
|
||||
#include "rocksdb/c.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#ifndef OS_WIN
|
||||
# include <unistd.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <inttypes.h>
|
||||
|
||||
@@ -21,11 +22,10 @@
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
# define snprintf _snprintf
|
||||
#define snprintf _snprintf
|
||||
|
||||
// Ok for uniqueness
|
||||
int geteuid() {
|
||||
|
||||
int result = 0;
|
||||
|
||||
result = ((int)GetCurrentProcessId() << 16);
|
||||
@@ -38,13 +38,13 @@ int geteuid() {
|
||||
|
||||
const char* phase = "";
|
||||
static char dbname[200];
|
||||
static char sstfilename[200];
|
||||
static char dbbackupname[200];
|
||||
|
||||
static void StartPhase(const char* name) {
|
||||
fprintf(stderr, "=== Test %s\n", name);
|
||||
phase = name;
|
||||
}
|
||||
|
||||
static const char* GetTempDir(void) {
|
||||
const char* ret = getenv("TEST_TMPDIR");
|
||||
if (ret == NULL || ret[0] == '\0')
|
||||
@@ -291,6 +291,7 @@ int main(int argc, char** argv) {
|
||||
rocksdb_block_based_table_options_t* table_options;
|
||||
rocksdb_readoptions_t* roptions;
|
||||
rocksdb_writeoptions_t* woptions;
|
||||
rocksdb_ratelimiter_t* rate_limiter;
|
||||
char* err = NULL;
|
||||
int run = -1;
|
||||
|
||||
@@ -304,6 +305,11 @@ int main(int argc, char** argv) {
|
||||
GetTempDir(),
|
||||
((int) geteuid()));
|
||||
|
||||
snprintf(sstfilename, sizeof(sstfilename),
|
||||
"%s/rocksdb_c_test-%d-sst",
|
||||
GetTempDir(),
|
||||
((int)geteuid()));
|
||||
|
||||
StartPhase("create_objects");
|
||||
cmp = rocksdb_comparator_create(NULL, CmpDestroy, CmpCompare, CmpName);
|
||||
env = rocksdb_create_default_env();
|
||||
@@ -317,6 +323,7 @@ int main(int argc, char** argv) {
|
||||
rocksdb_options_set_write_buffer_size(options, 100000);
|
||||
rocksdb_options_set_paranoid_checks(options, 1);
|
||||
rocksdb_options_set_max_open_files(options, 10);
|
||||
rocksdb_options_set_base_background_compactions(options, 1);
|
||||
table_options = rocksdb_block_based_options_create();
|
||||
rocksdb_block_based_options_set_block_cache(table_options, cache);
|
||||
rocksdb_options_set_block_based_table_factory(options, table_options);
|
||||
@@ -326,6 +333,9 @@ int main(int argc, char** argv) {
|
||||
int compression_levels[] = {rocksdb_no_compression, rocksdb_no_compression,
|
||||
rocksdb_no_compression, rocksdb_no_compression};
|
||||
rocksdb_options_set_compression_per_level(options, compression_levels, 4);
|
||||
rate_limiter = rocksdb_ratelimiter_create(1000 * 1024 * 1024, 100 * 1000, 10);
|
||||
rocksdb_options_set_ratelimiter(options, rate_limiter);
|
||||
rocksdb_ratelimiter_destroy(rate_limiter);
|
||||
|
||||
roptions = rocksdb_readoptions_create();
|
||||
rocksdb_readoptions_set_verify_checksums(roptions, 1);
|
||||
@@ -487,6 +497,10 @@ int main(int argc, char** argv) {
|
||||
CheckIter(iter, "foo", "hello");
|
||||
rocksdb_iter_seek(iter, "b", 1);
|
||||
CheckIter(iter, "box", "c");
|
||||
rocksdb_iter_seek_for_prev(iter, "g", 1);
|
||||
CheckIter(iter, "foo", "hello");
|
||||
rocksdb_iter_seek_for_prev(iter, "box", 3);
|
||||
CheckIter(iter, "box", "c");
|
||||
rocksdb_iter_get_error(iter, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_iter_destroy(iter);
|
||||
@@ -565,6 +579,59 @@ int main(int argc, char** argv) {
|
||||
rocksdb_release_snapshot(db, snap);
|
||||
}
|
||||
|
||||
StartPhase("addfile");
|
||||
{
|
||||
rocksdb_envoptions_t* env_opt = rocksdb_envoptions_create();
|
||||
rocksdb_options_t* io_options = rocksdb_options_create();
|
||||
rocksdb_sstfilewriter_t* writer =
|
||||
rocksdb_sstfilewriter_create(env_opt, io_options);
|
||||
|
||||
unlink(sstfilename);
|
||||
rocksdb_sstfilewriter_open(writer, sstfilename, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_sstfilewriter_add(writer, "sstk1", 5, "v1", 2, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_sstfilewriter_add(writer, "sstk2", 5, "v2", 2, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_sstfilewriter_add(writer, "sstk3", 5, "v3", 2, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_sstfilewriter_finish(writer, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
rocksdb_ingestexternalfileoptions_t* ing_opt =
|
||||
rocksdb_ingestexternalfileoptions_create();
|
||||
const char* file_list[1] = {sstfilename};
|
||||
rocksdb_ingest_external_file(db, file_list, 1, ing_opt, &err);
|
||||
CheckNoError(err);
|
||||
CheckGet(db, roptions, "sstk1", "v1");
|
||||
CheckGet(db, roptions, "sstk2", "v2");
|
||||
CheckGet(db, roptions, "sstk3", "v3");
|
||||
|
||||
unlink(sstfilename);
|
||||
rocksdb_sstfilewriter_open(writer, sstfilename, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_sstfilewriter_add(writer, "sstk2", 5, "v4", 2, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_sstfilewriter_add(writer, "sstk22", 6, "v5", 2, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_sstfilewriter_add(writer, "sstk3", 5, "v6", 2, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_sstfilewriter_finish(writer, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
rocksdb_ingest_external_file(db, file_list, 1, ing_opt, &err);
|
||||
CheckNoError(err);
|
||||
CheckGet(db, roptions, "sstk1", "v1");
|
||||
CheckGet(db, roptions, "sstk2", "v4");
|
||||
CheckGet(db, roptions, "sstk22", "v5");
|
||||
CheckGet(db, roptions, "sstk3", "v6");
|
||||
|
||||
rocksdb_ingestexternalfileoptions_destroy(ing_opt);
|
||||
rocksdb_sstfilewriter_destroy(writer);
|
||||
rocksdb_options_destroy(io_options);
|
||||
rocksdb_envoptions_destroy(env_opt);
|
||||
}
|
||||
|
||||
StartPhase("repair");
|
||||
{
|
||||
// If we do not compact here, then the lazy deletion of
|
||||
@@ -700,7 +767,7 @@ int main(int argc, char** argv) {
|
||||
rocksdb_destroy_db(options, dbname, &err);
|
||||
CheckNoError(err)
|
||||
|
||||
rocksdb_options_t* db_options = rocksdb_options_create();
|
||||
rocksdb_options_t* db_options = rocksdb_options_create();
|
||||
rocksdb_options_set_create_if_missing(db_options, 1);
|
||||
db = rocksdb_open(db_options, dbname, &err);
|
||||
CheckNoError(err)
|
||||
@@ -829,6 +896,7 @@ int main(int argc, char** argv) {
|
||||
rocksdb_options_set_prefix_extractor(options, rocksdb_slicetransform_create_fixed_prefix(3));
|
||||
rocksdb_options_set_hash_skip_list_rep(options, 5000, 4, 4);
|
||||
rocksdb_options_set_plain_table_factory(options, 4, 10, 0.75, 16);
|
||||
rocksdb_options_set_allow_concurrent_memtable_write(options, 0);
|
||||
|
||||
db = rocksdb_open(options, dbname, &err);
|
||||
CheckNoError(err);
|
||||
@@ -981,7 +1049,6 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
|
||||
#else
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
fprintf(stderr, "SKIPPED\n");
|
||||
|
||||
+138
-50
@@ -27,6 +27,7 @@
|
||||
#include "db/version_set.h"
|
||||
#include "db/write_controller.h"
|
||||
#include "memtable/hash_skiplist_rep.h"
|
||||
#include "table/block_based_table_factory.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/compression.h"
|
||||
#include "util/options_helper.h"
|
||||
@@ -139,10 +140,8 @@ Status CheckConcurrentWritesSupported(const ColumnFamilyOptions& cf_options) {
|
||||
}
|
||||
|
||||
ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
|
||||
const InternalKeyComparator* icmp,
|
||||
const ColumnFamilyOptions& src) {
|
||||
ColumnFamilyOptions result = src;
|
||||
result.comparator = icmp;
|
||||
size_t clamp_max = std::conditional<
|
||||
sizeof(size_t) == 4, std::integral_constant<size_t, 0xffffffff>,
|
||||
std::integral_constant<uint64_t, 64ull << 30>>::type::value;
|
||||
@@ -160,6 +159,10 @@ ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
|
||||
result.min_write_buffer_number_to_merge =
|
||||
std::min(result.min_write_buffer_number_to_merge,
|
||||
result.max_write_buffer_number - 1);
|
||||
if (result.min_write_buffer_number_to_merge < 1) {
|
||||
result.min_write_buffer_number_to_merge = 1;
|
||||
}
|
||||
|
||||
if (result.num_levels < 1) {
|
||||
result.num_levels = 1;
|
||||
}
|
||||
@@ -349,10 +352,11 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
refs_(0),
|
||||
dropped_(false),
|
||||
internal_comparator_(cf_options.comparator),
|
||||
initial_cf_options_(
|
||||
SanitizeOptions(db_options, &internal_comparator_, cf_options)),
|
||||
initial_cf_options_(SanitizeOptions(db_options, cf_options)),
|
||||
ioptions_(db_options, initial_cf_options_),
|
||||
mutable_cf_options_(initial_cf_options_),
|
||||
is_delete_range_supported_(strcmp(cf_options.table_factory->Name(),
|
||||
BlockBasedTableFactory().Name()) == 0),
|
||||
write_buffer_manager_(write_buffer_manager),
|
||||
mem_(nullptr),
|
||||
imm_(ioptions_.min_write_buffer_number_to_merge,
|
||||
@@ -366,7 +370,8 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
column_family_set_(column_family_set),
|
||||
pending_flush_(false),
|
||||
pending_compaction_(false),
|
||||
prev_compaction_needed_bytes_(0) {
|
||||
prev_compaction_needed_bytes_(0),
|
||||
allow_2pc_(db_options.allow_2pc) {
|
||||
Ref();
|
||||
|
||||
// Convert user defined table properties collector factories to internal ones.
|
||||
@@ -488,15 +493,39 @@ ColumnFamilyOptions ColumnFamilyData::GetLatestCFOptions() const {
|
||||
return BuildColumnFamilyOptions(initial_cf_options_, mutable_cf_options_);
|
||||
}
|
||||
|
||||
const double kSlowdownRatio = 1.2;
|
||||
uint64_t ColumnFamilyData::OldestLogToKeep() {
|
||||
auto current_log = GetLogNumber();
|
||||
|
||||
if (allow_2pc_) {
|
||||
auto imm_prep_log = imm()->GetMinLogContainingPrepSection();
|
||||
auto mem_prep_log = mem()->GetMinLogContainingPrepSection();
|
||||
|
||||
if (imm_prep_log > 0 && imm_prep_log < current_log) {
|
||||
current_log = imm_prep_log;
|
||||
}
|
||||
|
||||
if (mem_prep_log > 0 && mem_prep_log < current_log) {
|
||||
current_log = mem_prep_log;
|
||||
}
|
||||
}
|
||||
|
||||
return current_log;
|
||||
}
|
||||
|
||||
const double kIncSlowdownRatio = 0.8;
|
||||
const double kDecSlowdownRatio = 1 / kIncSlowdownRatio;
|
||||
const double kNearStopSlowdownRatio = 0.6;
|
||||
const double kDelayRecoverSlowdownRatio = 1.4;
|
||||
|
||||
namespace {
|
||||
// If penalize_stop is true, we further reduce slowdown rate.
|
||||
std::unique_ptr<WriteControllerToken> SetupDelay(
|
||||
uint64_t max_write_rate, WriteController* write_controller,
|
||||
uint64_t compaction_needed_bytes, uint64_t prev_compaction_neeed_bytes,
|
||||
WriteController* write_controller, uint64_t compaction_needed_bytes,
|
||||
uint64_t prev_compaction_need_bytes, bool penalize_stop,
|
||||
bool auto_comapctions_disabled) {
|
||||
const uint64_t kMinWriteRate = 1024u; // Minimum write rate 1KB/s.
|
||||
const uint64_t kMinWriteRate = 16 * 1024u; // Minimum write rate 16KB/s.
|
||||
|
||||
uint64_t max_write_rate = write_controller->max_delayed_write_rate();
|
||||
uint64_t write_rate = write_controller->delayed_write_rate();
|
||||
|
||||
if (auto_comapctions_disabled) {
|
||||
@@ -519,19 +548,32 @@ std::unique_ptr<WriteControllerToken> SetupDelay(
|
||||
// insert to mem tables, so we need to actively slow down before we get
|
||||
// feedback signal from compaction and flushes to avoid the full stop
|
||||
// because of hitting the max write buffer number.
|
||||
if (prev_compaction_neeed_bytes > 0 &&
|
||||
prev_compaction_neeed_bytes <= compaction_needed_bytes) {
|
||||
write_rate = static_cast<uint64_t>(static_cast<double>(write_rate) /
|
||||
kSlowdownRatio);
|
||||
//
|
||||
// If DB just falled into the stop condition, we need to further reduce
|
||||
// the write rate to avoid the stop condition.
|
||||
if (penalize_stop) {
|
||||
// Penalize the near stop or stop condition by more agressive slowdown.
|
||||
// This is to provide the long term slowdown increase signal.
|
||||
// The penalty is more than the reward of recovering to the normal
|
||||
// condition.
|
||||
write_rate = static_cast<uint64_t>(static_cast<double>(write_rate) *
|
||||
kNearStopSlowdownRatio);
|
||||
if (write_rate < kMinWriteRate) {
|
||||
write_rate = kMinWriteRate;
|
||||
}
|
||||
} else if (prev_compaction_neeed_bytes > compaction_needed_bytes) {
|
||||
} else if (prev_compaction_need_bytes > 0 &&
|
||||
prev_compaction_need_bytes <= compaction_needed_bytes) {
|
||||
write_rate = static_cast<uint64_t>(static_cast<double>(write_rate) *
|
||||
kIncSlowdownRatio);
|
||||
if (write_rate < kMinWriteRate) {
|
||||
write_rate = kMinWriteRate;
|
||||
}
|
||||
} else if (prev_compaction_need_bytes > compaction_needed_bytes) {
|
||||
// We are speeding up by ratio of kSlowdownRatio when we have paid
|
||||
// compaction debt. But we'll never speed up to faster than the write rate
|
||||
// given by users.
|
||||
write_rate = static_cast<uint64_t>(static_cast<double>(write_rate) *
|
||||
kSlowdownRatio);
|
||||
kDecSlowdownRatio);
|
||||
if (write_rate > max_write_rate) {
|
||||
write_rate = max_write_rate;
|
||||
}
|
||||
@@ -545,14 +587,31 @@ int GetL0ThresholdSpeedupCompaction(int level0_file_num_compaction_trigger,
|
||||
// SanitizeOptions() ensures it.
|
||||
assert(level0_file_num_compaction_trigger <= level0_slowdown_writes_trigger);
|
||||
|
||||
if (level0_file_num_compaction_trigger < 0) {
|
||||
return std::numeric_limits<int>::max();
|
||||
}
|
||||
|
||||
const int64_t twice_level0_trigger =
|
||||
static_cast<int64_t>(level0_file_num_compaction_trigger) * 2;
|
||||
|
||||
const int64_t one_fourth_trigger_slowdown =
|
||||
static_cast<int64_t>(level0_file_num_compaction_trigger) +
|
||||
((level0_slowdown_writes_trigger - level0_file_num_compaction_trigger) /
|
||||
4);
|
||||
|
||||
assert(twice_level0_trigger >= 0);
|
||||
assert(one_fourth_trigger_slowdown >= 0);
|
||||
|
||||
// 1/4 of the way between L0 compaction trigger threshold and slowdown
|
||||
// condition.
|
||||
// Or twice as compaction trigger, if it is smaller.
|
||||
return std::min(level0_file_num_compaction_trigger * 2,
|
||||
level0_file_num_compaction_trigger +
|
||||
(level0_slowdown_writes_trigger -
|
||||
level0_file_num_compaction_trigger) /
|
||||
4);
|
||||
int64_t res = std::min(twice_level0_trigger, one_fourth_trigger_slowdown);
|
||||
if (res >= port::kMaxInt32) {
|
||||
return port::kMaxInt32;
|
||||
} else {
|
||||
// res fits in int
|
||||
return static_cast<int>(res);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -564,6 +623,9 @@ void ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
uint64_t compaction_needed_bytes =
|
||||
vstorage->estimated_compaction_needed_bytes();
|
||||
|
||||
bool was_stopped = write_controller->IsStopped();
|
||||
bool needed_delay = write_controller->NeedsDelay();
|
||||
|
||||
if (imm()->NumNotFlushed() >= mutable_cf_options.max_write_buffer_number) {
|
||||
write_controller_token_ = write_controller->GetStopToken();
|
||||
internal_stats_->AddCFStats(InternalStats::MEMTABLE_COMPACTION, 1);
|
||||
@@ -599,8 +661,8 @@ void ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
imm()->NumNotFlushed() >=
|
||||
mutable_cf_options.max_write_buffer_number - 1) {
|
||||
write_controller_token_ =
|
||||
SetupDelay(ioptions_.delayed_write_rate, write_controller,
|
||||
compaction_needed_bytes, prev_compaction_needed_bytes_,
|
||||
SetupDelay(write_controller, compaction_needed_bytes,
|
||||
prev_compaction_needed_bytes_, was_stopped,
|
||||
mutable_cf_options.disable_auto_compactions);
|
||||
internal_stats_->AddCFStats(InternalStats::MEMTABLE_SLOWDOWN, 1);
|
||||
Log(InfoLogLevel::WARN_LEVEL, ioptions_.info_log,
|
||||
@@ -614,9 +676,12 @@ void ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
mutable_cf_options.level0_slowdown_writes_trigger >= 0 &&
|
||||
vstorage->l0_delay_trigger_count() >=
|
||||
mutable_cf_options.level0_slowdown_writes_trigger) {
|
||||
// L0 is the last two files from stopping.
|
||||
bool near_stop = vstorage->l0_delay_trigger_count() >=
|
||||
mutable_cf_options.level0_stop_writes_trigger - 2;
|
||||
write_controller_token_ =
|
||||
SetupDelay(ioptions_.delayed_write_rate, write_controller,
|
||||
compaction_needed_bytes, prev_compaction_needed_bytes_,
|
||||
SetupDelay(write_controller, compaction_needed_bytes,
|
||||
prev_compaction_needed_bytes_, was_stopped || near_stop,
|
||||
mutable_cf_options.disable_auto_compactions);
|
||||
internal_stats_->AddCFStats(InternalStats::LEVEL0_SLOWDOWN_TOTAL, 1);
|
||||
if (compaction_picker_->IsLevel0CompactionInProgress()) {
|
||||
@@ -632,9 +697,20 @@ void ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
mutable_cf_options.soft_pending_compaction_bytes_limit > 0 &&
|
||||
vstorage->estimated_compaction_needed_bytes() >=
|
||||
mutable_cf_options.soft_pending_compaction_bytes_limit) {
|
||||
// If the distance to hard limit is less than 1/4 of the gap between soft
|
||||
// and
|
||||
// hard bytes limit, we think it is near stop and speed up the slowdown.
|
||||
bool near_stop =
|
||||
mutable_cf_options.hard_pending_compaction_bytes_limit > 0 &&
|
||||
(compaction_needed_bytes -
|
||||
mutable_cf_options.soft_pending_compaction_bytes_limit) >
|
||||
3 * (mutable_cf_options.hard_pending_compaction_bytes_limit -
|
||||
mutable_cf_options.soft_pending_compaction_bytes_limit) /
|
||||
4;
|
||||
|
||||
write_controller_token_ =
|
||||
SetupDelay(ioptions_.delayed_write_rate, write_controller,
|
||||
compaction_needed_bytes, prev_compaction_needed_bytes_,
|
||||
SetupDelay(write_controller, compaction_needed_bytes,
|
||||
prev_compaction_needed_bytes_, was_stopped || near_stop,
|
||||
mutable_cf_options.disable_auto_compactions);
|
||||
internal_stats_->AddCFStats(
|
||||
InternalStats::SOFT_PENDING_COMPACTION_BYTES_LIMIT, 1);
|
||||
@@ -643,31 +719,43 @@ void ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
"bytes %" PRIu64 " rate %" PRIu64,
|
||||
name_.c_str(), vstorage->estimated_compaction_needed_bytes(),
|
||||
write_controller->delayed_write_rate());
|
||||
} else if (vstorage->l0_delay_trigger_count() >=
|
||||
GetL0ThresholdSpeedupCompaction(
|
||||
mutable_cf_options.level0_file_num_compaction_trigger,
|
||||
mutable_cf_options.level0_slowdown_writes_trigger)) {
|
||||
write_controller_token_ = write_controller->GetCompactionPressureToken();
|
||||
Log(InfoLogLevel::WARN_LEVEL, ioptions_.info_log,
|
||||
"[%s] Increasing compaction threads because we have %d level-0 "
|
||||
"files ",
|
||||
name_.c_str(), vstorage->l0_delay_trigger_count());
|
||||
} else if (vstorage->estimated_compaction_needed_bytes() >=
|
||||
mutable_cf_options.soft_pending_compaction_bytes_limit / 4) {
|
||||
// Increase compaction threads if bytes needed for compaction exceeds
|
||||
// 1/4 of threshold for slowing down.
|
||||
// If soft pending compaction byte limit is not set, always speed up
|
||||
// compaction.
|
||||
write_controller_token_ = write_controller->GetCompactionPressureToken();
|
||||
if (mutable_cf_options.soft_pending_compaction_bytes_limit > 0) {
|
||||
Log(InfoLogLevel::WARN_LEVEL, ioptions_.info_log,
|
||||
"[%s] Increasing compaction threads because of estimated pending "
|
||||
"compaction "
|
||||
"bytes %" PRIu64,
|
||||
name_.c_str(), vstorage->estimated_compaction_needed_bytes());
|
||||
}
|
||||
} else {
|
||||
write_controller_token_.reset();
|
||||
if (vstorage->l0_delay_trigger_count() >=
|
||||
GetL0ThresholdSpeedupCompaction(
|
||||
mutable_cf_options.level0_file_num_compaction_trigger,
|
||||
mutable_cf_options.level0_slowdown_writes_trigger)) {
|
||||
write_controller_token_ =
|
||||
write_controller->GetCompactionPressureToken();
|
||||
Log(InfoLogLevel::WARN_LEVEL, ioptions_.info_log,
|
||||
"[%s] Increasing compaction threads because we have %d level-0 "
|
||||
"files ",
|
||||
name_.c_str(), vstorage->l0_delay_trigger_count());
|
||||
} else if (vstorage->estimated_compaction_needed_bytes() >=
|
||||
mutable_cf_options.soft_pending_compaction_bytes_limit / 4) {
|
||||
// Increase compaction threads if bytes needed for compaction exceeds
|
||||
// 1/4 of threshold for slowing down.
|
||||
// If soft pending compaction byte limit is not set, always speed up
|
||||
// compaction.
|
||||
write_controller_token_ =
|
||||
write_controller->GetCompactionPressureToken();
|
||||
if (mutable_cf_options.soft_pending_compaction_bytes_limit > 0) {
|
||||
Log(InfoLogLevel::WARN_LEVEL, ioptions_.info_log,
|
||||
"[%s] Increasing compaction threads because of estimated pending "
|
||||
"compaction "
|
||||
"bytes %" PRIu64,
|
||||
name_.c_str(), vstorage->estimated_compaction_needed_bytes());
|
||||
}
|
||||
} else {
|
||||
write_controller_token_.reset();
|
||||
}
|
||||
// If the DB recovers from delay conditions, we reward with reducing
|
||||
// double the slowdown ratio. This is to balance the long term slowdown
|
||||
// increase signal.
|
||||
if (needed_delay) {
|
||||
uint64_t write_rate = write_controller->delayed_write_rate();
|
||||
write_controller->set_delayed_write_rate(static_cast<uint64_t>(
|
||||
static_cast<double>(write_rate) * kDelayRecoverSlowdownRatio));
|
||||
}
|
||||
}
|
||||
prev_compaction_needed_bytes_ = compaction_needed_bytes;
|
||||
}
|
||||
|
||||
+11
-2
@@ -42,7 +42,7 @@ class LogBuffer;
|
||||
class InstrumentedMutex;
|
||||
class InstrumentedMutexLock;
|
||||
|
||||
extern const double kSlowdownRatio;
|
||||
extern const double kIncSlowdownRatio;
|
||||
|
||||
// ColumnFamilyHandleImpl is the class that clients use to access different
|
||||
// column families. It has non-trivial destructor, which gets called when client
|
||||
@@ -137,7 +137,6 @@ extern Status CheckConcurrentWritesSupported(
|
||||
const ColumnFamilyOptions& cf_options);
|
||||
|
||||
extern ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
|
||||
const InternalKeyComparator* icmp,
|
||||
const ColumnFamilyOptions& src);
|
||||
// Wrap user defined table proproties collector factories `from cf_options`
|
||||
// into internal ones in int_tbl_prop_collector_factories. Add a system internal
|
||||
@@ -221,6 +220,8 @@ class ColumnFamilyData {
|
||||
// options.
|
||||
ColumnFamilyOptions GetLatestCFOptions() const;
|
||||
|
||||
bool is_delete_range_supported() { return is_delete_range_supported_; }
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// REQUIRES: DB mutex held
|
||||
Status SetOptions(
|
||||
@@ -238,6 +239,9 @@ class ColumnFamilyData {
|
||||
uint64_t GetTotalSstFilesSize() const; // REQUIRE: DB mutex held
|
||||
void SetMemtable(MemTable* new_mem) { mem_ = new_mem; }
|
||||
|
||||
// calculate the oldest log needed for the durability of this column family
|
||||
uint64_t OldestLogToKeep();
|
||||
|
||||
// See Memtable constructor for explanation of earliest_seq param.
|
||||
MemTable* ConstructNewMemtable(const MutableCFOptions& mutable_cf_options,
|
||||
SequenceNumber earliest_seq);
|
||||
@@ -355,6 +359,8 @@ class ColumnFamilyData {
|
||||
const ImmutableCFOptions ioptions_;
|
||||
MutableCFOptions mutable_cf_options_;
|
||||
|
||||
const bool is_delete_range_supported_;
|
||||
|
||||
std::unique_ptr<TableCache> table_cache_;
|
||||
|
||||
std::unique_ptr<InternalStats> internal_stats_;
|
||||
@@ -401,6 +407,9 @@ class ColumnFamilyData {
|
||||
bool pending_compaction_;
|
||||
|
||||
uint64_t prev_compaction_needed_bytes_;
|
||||
|
||||
// if the database was opened with 2pc enabled
|
||||
bool allow_2pc_;
|
||||
};
|
||||
|
||||
// ColumnFamilySet has interesting thread-safety requirements
|
||||
|
||||
+26
-20
@@ -941,6 +941,12 @@ TEST_F(ColumnFamilyTest, CrashAfterFlush) {
|
||||
db_options_.env = env_;
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyTest, OpenNonexistentColumnFamily) {
|
||||
ASSERT_OK(TryOpen({"default"}));
|
||||
Close();
|
||||
ASSERT_TRUE(TryOpen({"default", "dne"}).IsInvalidArgument());
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE // WaitForFlush() is not supported
|
||||
// Makes sure that obsolete log files get deleted
|
||||
TEST_F(ColumnFamilyTest, DifferentWriteBufferSizes) {
|
||||
@@ -1050,6 +1056,7 @@ TEST_F(ColumnFamilyTest, DifferentWriteBufferSizes) {
|
||||
|
||||
#ifndef ROCKSDB_LITE // Cuckoo is not supported in lite
|
||||
TEST_F(ColumnFamilyTest, MemtableNotSupportSnapshot) {
|
||||
db_options_.allow_concurrent_memtable_write = false;
|
||||
Open();
|
||||
auto* s1 = dbfull()->GetSnapshot();
|
||||
ASSERT_TRUE(s1 != nullptr);
|
||||
@@ -2182,8 +2189,8 @@ TEST_F(ColumnFamilyTest, SanitizeOptions) {
|
||||
original.write_buffer_size =
|
||||
l * 4 * 1024 * 1024 + i * 1024 * 1024 + j * 1024 + k;
|
||||
|
||||
ColumnFamilyOptions result = SanitizeOptions(
|
||||
ImmutableDBOptions(db_options), nullptr, original);
|
||||
ColumnFamilyOptions result =
|
||||
SanitizeOptions(ImmutableDBOptions(db_options), original);
|
||||
ASSERT_TRUE(result.level0_stop_writes_trigger >=
|
||||
result.level0_slowdown_writes_trigger);
|
||||
ASSERT_TRUE(result.level0_slowdown_writes_trigger >=
|
||||
@@ -2433,7 +2440,7 @@ TEST_F(ColumnFamilyTest, CreateAndDropRace) {
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
TEST_F(ColumnFamilyTest, WriteStallSingleColumnFamily) {
|
||||
const uint64_t kBaseRate = 810000u;
|
||||
const uint64_t kBaseRate = 800000u;
|
||||
db_options_.delayed_write_rate = kBaseRate;
|
||||
db_options_.base_background_compactions = 2;
|
||||
db_options_.max_background_compactions = 6;
|
||||
@@ -2468,7 +2475,7 @@ TEST_F(ColumnFamilyTest, WriteStallSingleColumnFamily) {
|
||||
cfd->RecalculateWriteStallConditions(mutable_cf_options);
|
||||
ASSERT_TRUE(!dbfull()->TEST_write_controler().IsStopped());
|
||||
ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay());
|
||||
ASSERT_EQ(kBaseRate / 1.2,
|
||||
ASSERT_EQ(kBaseRate / 1.25,
|
||||
dbfull()->TEST_write_controler().delayed_write_rate());
|
||||
ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed());
|
||||
|
||||
@@ -2476,14 +2483,14 @@ TEST_F(ColumnFamilyTest, WriteStallSingleColumnFamily) {
|
||||
cfd->RecalculateWriteStallConditions(mutable_cf_options);
|
||||
ASSERT_TRUE(!dbfull()->TEST_write_controler().IsStopped());
|
||||
ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay());
|
||||
ASSERT_EQ(kBaseRate / 1.2 / 1.2,
|
||||
ASSERT_EQ(kBaseRate / 1.25 / 1.25,
|
||||
dbfull()->TEST_write_controler().delayed_write_rate());
|
||||
|
||||
vstorage->TEST_set_estimated_compaction_needed_bytes(450);
|
||||
cfd->RecalculateWriteStallConditions(mutable_cf_options);
|
||||
ASSERT_TRUE(!dbfull()->TEST_write_controler().IsStopped());
|
||||
ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay());
|
||||
ASSERT_EQ(kBaseRate / 1.2,
|
||||
ASSERT_EQ(kBaseRate / 1.25,
|
||||
dbfull()->TEST_write_controler().delayed_write_rate());
|
||||
|
||||
vstorage->TEST_set_estimated_compaction_needed_bytes(205);
|
||||
@@ -2519,7 +2526,7 @@ TEST_F(ColumnFamilyTest, WriteStallSingleColumnFamily) {
|
||||
cfd->RecalculateWriteStallConditions(mutable_cf_options);
|
||||
ASSERT_TRUE(!dbfull()->TEST_write_controler().IsStopped());
|
||||
ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay());
|
||||
ASSERT_EQ(kBaseRate / 1.2,
|
||||
ASSERT_EQ(kBaseRate / 1.25,
|
||||
dbfull()->TEST_write_controler().delayed_write_rate());
|
||||
|
||||
vstorage->TEST_set_estimated_compaction_needed_bytes(2001);
|
||||
@@ -2537,7 +2544,7 @@ TEST_F(ColumnFamilyTest, WriteStallSingleColumnFamily) {
|
||||
cfd->RecalculateWriteStallConditions(mutable_cf_options);
|
||||
ASSERT_TRUE(!dbfull()->TEST_write_controler().IsStopped());
|
||||
ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay());
|
||||
ASSERT_EQ(kBaseRate / 1.2,
|
||||
ASSERT_EQ(kBaseRate / 1.25,
|
||||
dbfull()->TEST_write_controler().delayed_write_rate());
|
||||
|
||||
vstorage->TEST_set_estimated_compaction_needed_bytes(100);
|
||||
@@ -2549,15 +2556,14 @@ TEST_F(ColumnFamilyTest, WriteStallSingleColumnFamily) {
|
||||
cfd->RecalculateWriteStallConditions(mutable_cf_options);
|
||||
ASSERT_TRUE(!dbfull()->TEST_write_controler().IsStopped());
|
||||
ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay());
|
||||
ASSERT_EQ(kBaseRate / 1.2,
|
||||
dbfull()->TEST_write_controler().delayed_write_rate());
|
||||
ASSERT_EQ(kBaseRate, dbfull()->TEST_write_controler().delayed_write_rate());
|
||||
ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed());
|
||||
|
||||
vstorage->set_l0_delay_trigger_count(101);
|
||||
cfd->RecalculateWriteStallConditions(mutable_cf_options);
|
||||
ASSERT_TRUE(!dbfull()->TEST_write_controler().IsStopped());
|
||||
ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay());
|
||||
ASSERT_EQ(kBaseRate / 1.2 / 1.2,
|
||||
ASSERT_EQ(kBaseRate / 1.25,
|
||||
dbfull()->TEST_write_controler().delayed_write_rate());
|
||||
|
||||
vstorage->set_l0_delay_trigger_count(0);
|
||||
@@ -2565,21 +2571,21 @@ TEST_F(ColumnFamilyTest, WriteStallSingleColumnFamily) {
|
||||
cfd->RecalculateWriteStallConditions(mutable_cf_options);
|
||||
ASSERT_TRUE(!dbfull()->TEST_write_controler().IsStopped());
|
||||
ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay());
|
||||
ASSERT_EQ(kBaseRate / 1.2 / 1.2 / 1.2,
|
||||
ASSERT_EQ(kBaseRate / 1.25 / 1.25,
|
||||
dbfull()->TEST_write_controler().delayed_write_rate());
|
||||
|
||||
vstorage->set_l0_delay_trigger_count(101);
|
||||
cfd->RecalculateWriteStallConditions(mutable_cf_options);
|
||||
ASSERT_TRUE(!dbfull()->TEST_write_controler().IsStopped());
|
||||
ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay());
|
||||
ASSERT_EQ(kBaseRate / 1.2 / 1.2 / 1.2 / 1.2,
|
||||
ASSERT_EQ(kBaseRate / 1.25 / 1.25 / 1.25,
|
||||
dbfull()->TEST_write_controler().delayed_write_rate());
|
||||
|
||||
vstorage->TEST_set_estimated_compaction_needed_bytes(200);
|
||||
cfd->RecalculateWriteStallConditions(mutable_cf_options);
|
||||
ASSERT_TRUE(!dbfull()->TEST_write_controler().IsStopped());
|
||||
ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay());
|
||||
ASSERT_EQ(kBaseRate / 1.2 / 1.2 / 1.2,
|
||||
ASSERT_EQ(kBaseRate / 1.25 / 1.25,
|
||||
dbfull()->TEST_write_controler().delayed_write_rate());
|
||||
|
||||
vstorage->set_l0_delay_trigger_count(0);
|
||||
@@ -2620,7 +2626,7 @@ TEST_F(ColumnFamilyTest, WriteStallSingleColumnFamily) {
|
||||
cfd->RecalculateWriteStallConditions(mutable_cf_options);
|
||||
ASSERT_TRUE(!dbfull()->TEST_write_controler().IsStopped());
|
||||
ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay());
|
||||
ASSERT_EQ(kBaseRate / 1.2,
|
||||
ASSERT_EQ(kBaseRate / 1.25,
|
||||
dbfull()->TEST_write_controler().delayed_write_rate());
|
||||
}
|
||||
|
||||
@@ -2737,35 +2743,35 @@ TEST_F(ColumnFamilyTest, WriteStallTwoColumnFamilies) {
|
||||
cfd1->RecalculateWriteStallConditions(mutable_cf_options);
|
||||
ASSERT_TRUE(!dbfull()->TEST_write_controler().IsStopped());
|
||||
ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay());
|
||||
ASSERT_EQ(kBaseRate / 1.2,
|
||||
ASSERT_EQ(kBaseRate / 1.25,
|
||||
dbfull()->TEST_write_controler().delayed_write_rate());
|
||||
|
||||
vstorage->TEST_set_estimated_compaction_needed_bytes(300);
|
||||
cfd->RecalculateWriteStallConditions(mutable_cf_options);
|
||||
ASSERT_TRUE(!dbfull()->TEST_write_controler().IsStopped());
|
||||
ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay());
|
||||
ASSERT_EQ(kBaseRate / 1.2 / 1.2,
|
||||
ASSERT_EQ(kBaseRate / 1.25 / 1.25,
|
||||
dbfull()->TEST_write_controler().delayed_write_rate());
|
||||
|
||||
vstorage1->TEST_set_estimated_compaction_needed_bytes(700);
|
||||
cfd1->RecalculateWriteStallConditions(mutable_cf_options);
|
||||
ASSERT_TRUE(!dbfull()->TEST_write_controler().IsStopped());
|
||||
ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay());
|
||||
ASSERT_EQ(kBaseRate / 1.2,
|
||||
ASSERT_EQ(kBaseRate / 1.25,
|
||||
dbfull()->TEST_write_controler().delayed_write_rate());
|
||||
|
||||
vstorage->TEST_set_estimated_compaction_needed_bytes(500);
|
||||
cfd->RecalculateWriteStallConditions(mutable_cf_options);
|
||||
ASSERT_TRUE(!dbfull()->TEST_write_controler().IsStopped());
|
||||
ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay());
|
||||
ASSERT_EQ(kBaseRate / 1.2 / 1.2,
|
||||
ASSERT_EQ(kBaseRate / 1.25 / 1.25,
|
||||
dbfull()->TEST_write_controler().delayed_write_rate());
|
||||
|
||||
vstorage1->TEST_set_estimated_compaction_needed_bytes(600);
|
||||
cfd1->RecalculateWriteStallConditions(mutable_cf_options);
|
||||
ASSERT_TRUE(!dbfull()->TEST_write_controler().IsStopped());
|
||||
ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay());
|
||||
ASSERT_EQ(kBaseRate / 1.2,
|
||||
ASSERT_EQ(kBaseRate / 1.25,
|
||||
dbfull()->TEST_write_controler().delayed_write_rate());
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ Status CompactedDBImpl::Get(const ReadOptions& options,
|
||||
ColumnFamilyHandle*, const Slice& key, std::string* value) {
|
||||
GetContext get_context(user_comparator_, nullptr, nullptr, nullptr,
|
||||
GetContext::kNotFound, key, value, nullptr, nullptr,
|
||||
nullptr);
|
||||
nullptr, nullptr);
|
||||
LookupKey lkey(key, kMaxSequenceNumber);
|
||||
files_.files[FindFile(key)].fd.table_reader->Get(
|
||||
options, lkey.internal_key(), &get_context);
|
||||
@@ -77,7 +77,7 @@ std::vector<Status> CompactedDBImpl::MultiGet(const ReadOptions& options,
|
||||
if (r != nullptr) {
|
||||
GetContext get_context(user_comparator_, nullptr, nullptr, nullptr,
|
||||
GetContext::kNotFound, keys[idx], &(*values)[idx],
|
||||
nullptr, nullptr, nullptr);
|
||||
nullptr, nullptr, nullptr, nullptr);
|
||||
LookupKey lkey(keys[idx], kMaxSequenceNumber);
|
||||
r->Get(options, lkey.internal_key(), &get_context);
|
||||
if (get_context.State() == GetContext::kFound) {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#pragma once
|
||||
|
||||
struct CompactionIterationStats {
|
||||
// Compaction statistics
|
||||
|
||||
// Doesn't include records skipped because of
|
||||
// CompactionFilter::Decision::kRemoveAndSkipUntil.
|
||||
int64_t num_record_drop_user = 0;
|
||||
|
||||
int64_t num_record_drop_hidden = 0;
|
||||
int64_t num_record_drop_obsolete = 0;
|
||||
int64_t num_record_drop_range_del = 0;
|
||||
int64_t num_range_del_drop_obsolete = 0;
|
||||
uint64_t total_filter_time = 0;
|
||||
|
||||
// Input statistics
|
||||
// TODO(noetzli): The stats are incomplete. They are lacking everything
|
||||
// consumed by MergeHelper.
|
||||
uint64_t num_input_records = 0;
|
||||
uint64_t num_input_deletion_records = 0;
|
||||
uint64_t num_input_corrupt_records = 0;
|
||||
uint64_t total_input_raw_key_bytes = 0;
|
||||
uint64_t total_input_raw_value_bytes = 0;
|
||||
|
||||
// Single-Delete diagnostics for exceptional situations
|
||||
uint64_t num_single_del_fallthru = 0;
|
||||
uint64_t num_single_del_mismatch = 0;
|
||||
};
|
||||
+62
-13
@@ -17,6 +17,21 @@ CompactionIterator::CompactionIterator(
|
||||
bool expect_valid_internal_key, RangeDelAggregator* range_del_agg,
|
||||
const Compaction* compaction, const CompactionFilter* compaction_filter,
|
||||
LogBuffer* log_buffer)
|
||||
: CompactionIterator(
|
||||
input, cmp, merge_helper, last_sequence, snapshots,
|
||||
earliest_write_conflict_snapshot, env, expect_valid_internal_key,
|
||||
range_del_agg,
|
||||
std::unique_ptr<CompactionProxy>(
|
||||
compaction ? new CompactionProxy(compaction) : nullptr),
|
||||
compaction_filter, log_buffer) {}
|
||||
|
||||
CompactionIterator::CompactionIterator(
|
||||
InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
|
||||
SequenceNumber last_sequence, std::vector<SequenceNumber>* snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot, Env* env,
|
||||
bool expect_valid_internal_key, RangeDelAggregator* range_del_agg,
|
||||
std::unique_ptr<CompactionProxy> compaction,
|
||||
const CompactionFilter* compaction_filter, LogBuffer* log_buffer)
|
||||
: input_(input),
|
||||
cmp_(cmp),
|
||||
merge_helper_(merge_helper),
|
||||
@@ -25,7 +40,7 @@ CompactionIterator::CompactionIterator(
|
||||
env_(env),
|
||||
expect_valid_internal_key_(expect_valid_internal_key),
|
||||
range_del_agg_(range_del_agg),
|
||||
compaction_(compaction),
|
||||
compaction_(std::move(compaction)),
|
||||
compaction_filter_(compaction_filter),
|
||||
log_buffer_(log_buffer),
|
||||
merge_out_iter_(merge_helper_) {
|
||||
@@ -63,6 +78,8 @@ void CompactionIterator::ResetRecordCounts() {
|
||||
iter_stats_.num_record_drop_user = 0;
|
||||
iter_stats_.num_record_drop_hidden = 0;
|
||||
iter_stats_.num_record_drop_obsolete = 0;
|
||||
iter_stats_.num_record_drop_range_del = 0;
|
||||
iter_stats_.num_range_del_drop_obsolete = 0;
|
||||
}
|
||||
|
||||
void CompactionIterator::SeekToFirst() {
|
||||
@@ -108,7 +125,7 @@ void CompactionIterator::Next() {
|
||||
}
|
||||
|
||||
if (valid_) {
|
||||
// Record that we've ouputted a record for the current key.
|
||||
// Record that we've outputted a record for the current key.
|
||||
has_outputted_key_ = true;
|
||||
}
|
||||
|
||||
@@ -149,6 +166,13 @@ void CompactionIterator::NextFromInput() {
|
||||
iter_stats_.total_input_raw_key_bytes += key_.size();
|
||||
iter_stats_.total_input_raw_value_bytes += value_.size();
|
||||
|
||||
// If need_skip is true, we should seek the input iterator
|
||||
// to internal key skip_until and continue from there.
|
||||
bool need_skip = false;
|
||||
// Points either into compaction_filter_skip_until_ or into
|
||||
// merge_helper_->compaction_filter_skip_until_.
|
||||
Slice skip_until;
|
||||
|
||||
// Check whether the user key changed. After this if statement current_key_
|
||||
// is a copy of the current input key (maybe converted to a delete by the
|
||||
// compaction filter). ikey_.user_key is pointing to the copy.
|
||||
@@ -171,26 +195,41 @@ void CompactionIterator::NextFromInput() {
|
||||
// number is greater than any external snapshot, then invoke the
|
||||
// filter. If the return value of the compaction filter is true,
|
||||
// replace the entry with a deletion marker.
|
||||
bool value_changed = false;
|
||||
bool to_delete = false;
|
||||
CompactionFilter::Decision filter;
|
||||
compaction_filter_value_.clear();
|
||||
compaction_filter_skip_until_.Clear();
|
||||
{
|
||||
StopWatchNano timer(env_, true);
|
||||
to_delete = compaction_filter_->Filter(
|
||||
compaction_->level(), ikey_.user_key, value_,
|
||||
&compaction_filter_value_, &value_changed);
|
||||
filter = compaction_filter_->FilterV2(
|
||||
compaction_->level(), ikey_.user_key,
|
||||
CompactionFilter::ValueType::kValue, value_,
|
||||
&compaction_filter_value_, compaction_filter_skip_until_.rep());
|
||||
iter_stats_.total_filter_time +=
|
||||
env_ != nullptr ? timer.ElapsedNanos() : 0;
|
||||
}
|
||||
if (to_delete) {
|
||||
|
||||
if (filter == CompactionFilter::Decision::kRemoveAndSkipUntil &&
|
||||
cmp_->Compare(*compaction_filter_skip_until_.rep(),
|
||||
ikey_.user_key) <= 0) {
|
||||
// Can't skip to a key smaller than the current one.
|
||||
// Keep the key as per FilterV2 documentation.
|
||||
filter = CompactionFilter::Decision::kKeep;
|
||||
}
|
||||
|
||||
if (filter == CompactionFilter::Decision::kRemove) {
|
||||
// convert the current key to a delete
|
||||
ikey_.type = kTypeDeletion;
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, kTypeDeletion);
|
||||
// no value associated with delete
|
||||
value_.clear();
|
||||
iter_stats_.num_record_drop_user++;
|
||||
} else if (value_changed) {
|
||||
} else if (filter == CompactionFilter::Decision::kChangeValue) {
|
||||
value_ = compaction_filter_value_;
|
||||
} else if (filter == CompactionFilter::Decision::kRemoveAndSkipUntil) {
|
||||
need_skip = true;
|
||||
compaction_filter_skip_until_.ConvertFromUserKey(kMaxSequenceNumber,
|
||||
kValueTypeForSeek);
|
||||
skip_until = compaction_filter_skip_until_.Encode();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -217,7 +256,9 @@ void CompactionIterator::NextFromInput() {
|
||||
? earliest_snapshot_
|
||||
: findEarliestVisibleSnapshot(ikey_.sequence, &prev_snapshot);
|
||||
|
||||
if (clear_and_output_next_key_) {
|
||||
if (need_skip) {
|
||||
// This case is handled below.
|
||||
} else if (clear_and_output_next_key_) {
|
||||
// In the previous iteration we encountered a single delete that we could
|
||||
// not compact out. We will keep this Put, but can drop it's data.
|
||||
// (See Optimization 3, below.)
|
||||
@@ -308,7 +349,6 @@ void CompactionIterator::NextFromInput() {
|
||||
// to know that a write happened in this snapshot (Rule 2 above).
|
||||
// Clear the value and output the SingleDelete. (The value will be
|
||||
// outputted on the next iteration.)
|
||||
++iter_stats_.num_record_drop_hidden;
|
||||
|
||||
// Setting valid_ to true will output the current SingleDelete
|
||||
valid_ = true;
|
||||
@@ -418,18 +458,27 @@ void CompactionIterator::NextFromInput() {
|
||||
// coming after the merges
|
||||
has_current_user_key_ = false;
|
||||
pinned_iters_mgr_.ReleasePinnedData();
|
||||
|
||||
if (merge_helper_->FilteredUntil(&skip_until)) {
|
||||
need_skip = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 1. new user key -OR-
|
||||
// 2. different snapshot stripe
|
||||
bool should_delete =
|
||||
range_del_agg_->ShouldDelete(key_, true /* for_compaction */);
|
||||
bool should_delete = range_del_agg_->ShouldDelete(key_);
|
||||
if (should_delete) {
|
||||
++iter_stats_.num_record_drop_hidden;
|
||||
++iter_stats_.num_record_drop_range_del;
|
||||
input_->Next();
|
||||
} else {
|
||||
valid_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (need_skip) {
|
||||
input_->Seek(skip_until);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+46
-24
@@ -12,6 +12,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "db/compaction.h"
|
||||
#include "db/compaction_iteration_stats.h"
|
||||
#include "db/merge_helper.h"
|
||||
#include "db/pinned_iterators_manager.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
@@ -20,29 +21,38 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
struct CompactionIteratorStats {
|
||||
// Compaction statistics
|
||||
int64_t num_record_drop_user = 0;
|
||||
int64_t num_record_drop_hidden = 0;
|
||||
int64_t num_record_drop_obsolete = 0;
|
||||
uint64_t total_filter_time = 0;
|
||||
|
||||
// Input statistics
|
||||
// TODO(noetzli): The stats are incomplete. They are lacking everything
|
||||
// consumed by MergeHelper.
|
||||
uint64_t num_input_records = 0;
|
||||
uint64_t num_input_deletion_records = 0;
|
||||
uint64_t num_input_corrupt_records = 0;
|
||||
uint64_t total_input_raw_key_bytes = 0;
|
||||
uint64_t total_input_raw_value_bytes = 0;
|
||||
|
||||
// Single-Delete diagnostics for exceptional situations
|
||||
uint64_t num_single_del_fallthru = 0;
|
||||
uint64_t num_single_del_mismatch = 0;
|
||||
};
|
||||
|
||||
class CompactionIterator {
|
||||
public:
|
||||
// A wrapper around Compaction. Has a much smaller interface, only what
|
||||
// CompactionIterator uses. Tests can override it.
|
||||
class CompactionProxy {
|
||||
public:
|
||||
explicit CompactionProxy(const Compaction* compaction)
|
||||
: compaction_(compaction) {}
|
||||
|
||||
virtual ~CompactionProxy() = default;
|
||||
virtual int level(size_t compaction_input_level = 0) const {
|
||||
return compaction_->level();
|
||||
}
|
||||
virtual bool KeyNotExistsBeyondOutputLevel(
|
||||
const Slice& user_key, std::vector<size_t>* level_ptrs) const {
|
||||
return compaction_->KeyNotExistsBeyondOutputLevel(user_key, level_ptrs);
|
||||
}
|
||||
virtual bool bottommost_level() const {
|
||||
return compaction_->bottommost_level();
|
||||
}
|
||||
virtual int number_levels() const { return compaction_->number_levels(); }
|
||||
virtual Slice GetLargestUserKey() const {
|
||||
return compaction_->GetLargestUserKey();
|
||||
}
|
||||
|
||||
protected:
|
||||
CompactionProxy() = default;
|
||||
|
||||
private:
|
||||
const Compaction* compaction_;
|
||||
};
|
||||
|
||||
CompactionIterator(InternalIterator* input, const Comparator* cmp,
|
||||
MergeHelper* merge_helper, SequenceNumber last_sequence,
|
||||
std::vector<SequenceNumber>* snapshots,
|
||||
@@ -53,6 +63,17 @@ class CompactionIterator {
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
LogBuffer* log_buffer = nullptr);
|
||||
|
||||
// Constructor with custom CompactionProxy, used for tests.
|
||||
CompactionIterator(InternalIterator* input, const Comparator* cmp,
|
||||
MergeHelper* merge_helper, SequenceNumber last_sequence,
|
||||
std::vector<SequenceNumber>* snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot, Env* env,
|
||||
bool expect_valid_internal_key,
|
||||
RangeDelAggregator* range_del_agg,
|
||||
std::unique_ptr<CompactionProxy> compaction,
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
LogBuffer* log_buffer = nullptr);
|
||||
|
||||
~CompactionIterator();
|
||||
|
||||
void ResetRecordCounts();
|
||||
@@ -74,7 +95,7 @@ class CompactionIterator {
|
||||
const ParsedInternalKey& ikey() const { return ikey_; }
|
||||
bool Valid() const { return valid_; }
|
||||
const Slice& user_key() const { return current_user_key_; }
|
||||
const CompactionIteratorStats& iter_stats() const { return iter_stats_; }
|
||||
const CompactionIterationStats& iter_stats() const { return iter_stats_; }
|
||||
|
||||
private:
|
||||
// Processes the input stream to find the next output
|
||||
@@ -102,7 +123,7 @@ class CompactionIterator {
|
||||
Env* env_;
|
||||
bool expect_valid_internal_key_;
|
||||
RangeDelAggregator* range_del_agg_;
|
||||
const Compaction* compaction_;
|
||||
std::unique_ptr<CompactionProxy> compaction_;
|
||||
const CompactionFilter* compaction_filter_;
|
||||
LogBuffer* log_buffer_;
|
||||
bool bottommost_level_;
|
||||
@@ -150,6 +171,7 @@ class CompactionIterator {
|
||||
// merge operands and then releasing them after consuming them.
|
||||
PinnedIteratorsManager pinned_iters_mgr_;
|
||||
std::string compaction_filter_value_;
|
||||
InternalKey compaction_filter_skip_until_;
|
||||
// "level_ptrs" holds indices that remember which file of an associated
|
||||
// level we were last checking during the last call to compaction->
|
||||
// KeyNotExistsBeyondOutputLevel(). This allows future calls to the function
|
||||
@@ -157,6 +179,6 @@ class CompactionIterator {
|
||||
// increasing so a later call to the function must be looking for a key that
|
||||
// is in or beyond the last file checked during the previous call
|
||||
std::vector<size_t> level_ptrs_;
|
||||
CompactionIteratorStats iter_stats_;
|
||||
CompactionIterationStats iter_stats_;
|
||||
};
|
||||
} // namespace rocksdb
|
||||
@@ -13,6 +13,90 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class LoggingForwardVectorIterator : public InternalIterator {
|
||||
public:
|
||||
struct Action {
|
||||
enum class Type {
|
||||
SEEK_TO_FIRST,
|
||||
SEEK,
|
||||
NEXT,
|
||||
};
|
||||
|
||||
Type type;
|
||||
std::string arg;
|
||||
|
||||
explicit Action(Type _type, std::string _arg = "")
|
||||
: type(_type), arg(_arg) {}
|
||||
|
||||
bool operator==(const Action& rhs) const {
|
||||
return std::tie(type, arg) == std::tie(rhs.type, rhs.arg);
|
||||
}
|
||||
};
|
||||
|
||||
LoggingForwardVectorIterator(const std::vector<std::string>& keys,
|
||||
const std::vector<std::string>& values)
|
||||
: keys_(keys), values_(values), current_(keys.size()) {
|
||||
assert(keys_.size() == values_.size());
|
||||
}
|
||||
|
||||
virtual bool Valid() const override { return current_ < keys_.size(); }
|
||||
|
||||
virtual void SeekToFirst() override {
|
||||
log.emplace_back(Action::Type::SEEK_TO_FIRST);
|
||||
current_ = 0;
|
||||
}
|
||||
virtual void SeekToLast() override { assert(false); }
|
||||
|
||||
virtual void Seek(const Slice& target) override {
|
||||
log.emplace_back(Action::Type::SEEK, target.ToString());
|
||||
current_ = std::lower_bound(keys_.begin(), keys_.end(), target.ToString()) -
|
||||
keys_.begin();
|
||||
}
|
||||
|
||||
virtual void SeekForPrev(const Slice& target) override { assert(false); }
|
||||
|
||||
virtual void Next() override {
|
||||
assert(Valid());
|
||||
log.emplace_back(Action::Type::NEXT);
|
||||
current_++;
|
||||
}
|
||||
virtual void Prev() override { assert(false); }
|
||||
|
||||
virtual Slice key() const override {
|
||||
assert(Valid());
|
||||
return Slice(keys_[current_]);
|
||||
}
|
||||
virtual Slice value() const override {
|
||||
assert(Valid());
|
||||
return Slice(values_[current_]);
|
||||
}
|
||||
|
||||
virtual Status status() const override { return Status::OK(); }
|
||||
|
||||
std::vector<Action> log;
|
||||
|
||||
private:
|
||||
std::vector<std::string> keys_;
|
||||
std::vector<std::string> values_;
|
||||
size_t current_;
|
||||
};
|
||||
|
||||
class FakeCompaction : public CompactionIterator::CompactionProxy {
|
||||
public:
|
||||
FakeCompaction() = default;
|
||||
|
||||
virtual int level(size_t compaction_input_level) const { return 0; }
|
||||
virtual bool KeyNotExistsBeyondOutputLevel(
|
||||
const Slice& user_key, std::vector<size_t>* level_ptrs) const {
|
||||
return false;
|
||||
}
|
||||
virtual bool bottommost_level() const { return false; }
|
||||
virtual int number_levels() const { return 1; }
|
||||
virtual Slice GetLargestUserKey() const {
|
||||
return "\xff\xff\xff\xff\xff\xff\xff\xff\xff";
|
||||
}
|
||||
};
|
||||
|
||||
class CompactionIteratorTest : public testing::Test {
|
||||
public:
|
||||
CompactionIteratorTest()
|
||||
@@ -22,19 +106,27 @@ class CompactionIteratorTest : public testing::Test {
|
||||
const std::vector<std::string>& vs,
|
||||
const std::vector<std::string>& range_del_ks,
|
||||
const std::vector<std::string>& range_del_vs,
|
||||
SequenceNumber last_sequence) {
|
||||
SequenceNumber last_sequence,
|
||||
MergeOperator* merge_op = nullptr,
|
||||
CompactionFilter* filter = nullptr) {
|
||||
std::unique_ptr<InternalIterator> range_del_iter(
|
||||
new test::VectorIterator(range_del_ks, range_del_vs));
|
||||
range_del_agg_.reset(new RangeDelAggregator(icmp_, snapshots_));
|
||||
ASSERT_OK(range_del_agg_->AddTombstones(std::move(range_del_iter)));
|
||||
|
||||
merge_helper_.reset(new MergeHelper(Env::Default(), cmp_, nullptr, nullptr,
|
||||
std::unique_ptr<CompactionIterator::CompactionProxy> compaction;
|
||||
if (filter) {
|
||||
compaction.reset(new FakeCompaction());
|
||||
}
|
||||
|
||||
merge_helper_.reset(new MergeHelper(Env::Default(), cmp_, merge_op, filter,
|
||||
nullptr, 0U, false, 0));
|
||||
iter_.reset(new test::VectorIterator(ks, vs));
|
||||
iter_.reset(new LoggingForwardVectorIterator(ks, vs));
|
||||
iter_->SeekToFirst();
|
||||
c_iter_.reset(new CompactionIterator(
|
||||
iter_.get(), cmp_, merge_helper_.get(), last_sequence, &snapshots_,
|
||||
kMaxSequenceNumber, Env::Default(), false, range_del_agg_.get()));
|
||||
kMaxSequenceNumber, Env::Default(), false, range_del_agg_.get(),
|
||||
std::move(compaction), filter));
|
||||
}
|
||||
|
||||
void AddSnapshot(SequenceNumber snapshot) { snapshots_.push_back(snapshot); }
|
||||
@@ -43,7 +135,7 @@ class CompactionIteratorTest : public testing::Test {
|
||||
const InternalKeyComparator icmp_;
|
||||
std::vector<SequenceNumber> snapshots_;
|
||||
std::unique_ptr<MergeHelper> merge_helper_;
|
||||
std::unique_ptr<test::VectorIterator> iter_;
|
||||
std::unique_ptr<LoggingForwardVectorIterator> iter_;
|
||||
std::unique_ptr<CompactionIterator> c_iter_;
|
||||
std::unique_ptr<RangeDelAggregator> range_del_agg_;
|
||||
};
|
||||
@@ -116,6 +208,136 @@ TEST_F(CompactionIteratorTest, RangeDeletionWithSnapshots) {
|
||||
ASSERT_FALSE(c_iter_->Valid());
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorTest, CompactionFilterSkipUntil) {
|
||||
// Expect no merging attempts.
|
||||
class MergeOp : public MergeOperator {
|
||||
public:
|
||||
bool FullMergeV2(const MergeOperationInput& merge_in,
|
||||
MergeOperationOutput* merge_out) const override {
|
||||
ADD_FAILURE();
|
||||
return false;
|
||||
}
|
||||
bool PartialMergeMulti(const Slice& key,
|
||||
const std::deque<Slice>& operand_list,
|
||||
std::string* new_value,
|
||||
Logger* logger) const override {
|
||||
ADD_FAILURE();
|
||||
return false;
|
||||
}
|
||||
const char* Name() const override {
|
||||
return "CompactionIteratorTest.CompactionFilterSkipUntil::MergeOp";
|
||||
}
|
||||
};
|
||||
|
||||
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::kValue, t);
|
||||
EXPECT_EQ("av50", v);
|
||||
return Decision::kKeep;
|
||||
}
|
||||
if (k == "b") {
|
||||
EXPECT_EQ(ValueType::kValue, t);
|
||||
EXPECT_EQ("bv60", v);
|
||||
*skip_until = "d+";
|
||||
return Decision::kRemoveAndSkipUntil;
|
||||
}
|
||||
if (k == "e") {
|
||||
EXPECT_EQ(ValueType::kMergeOperand, t);
|
||||
EXPECT_EQ("em71", v);
|
||||
return Decision::kKeep;
|
||||
}
|
||||
if (k == "f") {
|
||||
if (v == "fm65") {
|
||||
EXPECT_EQ(ValueType::kMergeOperand, t);
|
||||
*skip_until = "f";
|
||||
} else {
|
||||
EXPECT_EQ("fm30", v);
|
||||
EXPECT_EQ(ValueType::kMergeOperand, t);
|
||||
*skip_until = "g+";
|
||||
}
|
||||
return Decision::kRemoveAndSkipUntil;
|
||||
}
|
||||
if (k == "h") {
|
||||
EXPECT_EQ(ValueType::kValue, t);
|
||||
EXPECT_EQ("hv91", v);
|
||||
return Decision::kKeep;
|
||||
}
|
||||
if (k == "i") {
|
||||
EXPECT_EQ(ValueType::kMergeOperand, t);
|
||||
EXPECT_EQ("im95", v);
|
||||
*skip_until = "z";
|
||||
return Decision::kRemoveAndSkipUntil;
|
||||
}
|
||||
ADD_FAILURE();
|
||||
return Decision::kKeep;
|
||||
}
|
||||
|
||||
const char* Name() const override {
|
||||
return "CompactionIteratorTest.CompactionFilterSkipUntil::Filter";
|
||||
}
|
||||
};
|
||||
|
||||
MergeOp merge_op;
|
||||
Filter filter;
|
||||
InitIterators(
|
||||
{test::KeyStr("a", 50, kTypeValue), // keep
|
||||
test::KeyStr("a", 45, kTypeMerge),
|
||||
test::KeyStr("b", 60, kTypeValue), // skip to "d+"
|
||||
test::KeyStr("b", 40, kTypeValue), test::KeyStr("c", 35, kTypeValue),
|
||||
test::KeyStr("d", 70, kTypeMerge),
|
||||
test::KeyStr("e", 71, kTypeMerge), // keep
|
||||
test::KeyStr("f", 65, kTypeMerge), // skip to "f", aka keep
|
||||
test::KeyStr("f", 30, kTypeMerge), // skip to "g+"
|
||||
test::KeyStr("f", 25, kTypeValue), test::KeyStr("g", 90, kTypeValue),
|
||||
test::KeyStr("h", 91, kTypeValue), // keep
|
||||
test::KeyStr("i", 95, kTypeMerge), // skip to "z"
|
||||
test::KeyStr("j", 99, kTypeValue)},
|
||||
{"av50", "am45", "bv60", "bv40", "cv35", "dm70", "em71", "fm65", "fm30",
|
||||
"fv25", "gv90", "hv91", "im95", "jv99"},
|
||||
{}, {}, kMaxSequenceNumber, &merge_op, &filter);
|
||||
|
||||
// Compaction should output just "a", "e" and "h" keys.
|
||||
c_iter_->SeekToFirst();
|
||||
ASSERT_TRUE(c_iter_->Valid());
|
||||
ASSERT_EQ(test::KeyStr("a", 50, kTypeValue), c_iter_->key().ToString());
|
||||
ASSERT_EQ("av50", c_iter_->value().ToString());
|
||||
c_iter_->Next();
|
||||
ASSERT_TRUE(c_iter_->Valid());
|
||||
ASSERT_EQ(test::KeyStr("e", 71, kTypeMerge), c_iter_->key().ToString());
|
||||
ASSERT_EQ("em71", c_iter_->value().ToString());
|
||||
c_iter_->Next();
|
||||
ASSERT_TRUE(c_iter_->Valid());
|
||||
ASSERT_EQ(test::KeyStr("h", 91, kTypeValue), c_iter_->key().ToString());
|
||||
ASSERT_EQ("hv91", c_iter_->value().ToString());
|
||||
c_iter_->Next();
|
||||
ASSERT_FALSE(c_iter_->Valid());
|
||||
|
||||
// Check that the compaction iterator did the correct sequence of calls on
|
||||
// the underlying iterator.
|
||||
using A = LoggingForwardVectorIterator::Action;
|
||||
using T = A::Type;
|
||||
std::vector<A> expected_actions = {
|
||||
A(T::SEEK_TO_FIRST),
|
||||
A(T::NEXT),
|
||||
A(T::NEXT),
|
||||
A(T::SEEK, test::KeyStr("d+", kMaxSequenceNumber, kValueTypeForSeek)),
|
||||
A(T::NEXT),
|
||||
A(T::NEXT),
|
||||
A(T::SEEK, test::KeyStr("g+", kMaxSequenceNumber, kValueTypeForSeek)),
|
||||
A(T::NEXT),
|
||||
A(T::SEEK, test::KeyStr("z", kMaxSequenceNumber, kValueTypeForSeek))};
|
||||
ASSERT_EQ(expected_actions, iter_->log);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+77
-30
@@ -484,9 +484,8 @@ void CompactionJob::GenSubcompactionBoundaries() {
|
||||
static_cast<uint64_t>(db_options_.max_subcompactions),
|
||||
max_output_files});
|
||||
|
||||
double mean = sum * 1.0 / subcompactions;
|
||||
|
||||
if (subcompactions > 1) {
|
||||
double mean = sum * 1.0 / subcompactions;
|
||||
// Greedily add ranges to the subcompaction until the sum of the ranges'
|
||||
// sizes becomes >= the expected mean size of a subcompaction
|
||||
sum = 0;
|
||||
@@ -591,6 +590,16 @@ Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options) {
|
||||
VersionStorageInfo::LevelSummaryStorage tmp;
|
||||
auto vstorage = cfd->current()->storage_info();
|
||||
const auto& stats = compaction_stats_;
|
||||
|
||||
double read_write_amp = 0.0;
|
||||
double write_amp = 0.0;
|
||||
if (stats.bytes_read_non_output_levels > 0) {
|
||||
read_write_amp = (stats.bytes_written + stats.bytes_read_output_level +
|
||||
stats.bytes_read_non_output_levels) /
|
||||
static_cast<double>(stats.bytes_read_non_output_levels);
|
||||
write_amp = stats.bytes_written /
|
||||
static_cast<double>(stats.bytes_read_non_output_levels);
|
||||
}
|
||||
LogToBuffer(
|
||||
log_buffer_,
|
||||
"[%s] compacted to: %s, MB/sec: %.1f rd, %.1f wr, level %d, "
|
||||
@@ -603,16 +612,10 @@ Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options) {
|
||||
stats.bytes_written / static_cast<double>(stats.micros),
|
||||
compact_->compaction->output_level(),
|
||||
stats.num_input_files_in_non_output_levels,
|
||||
stats.num_input_files_in_output_level,
|
||||
stats.num_output_files,
|
||||
stats.num_input_files_in_output_level, stats.num_output_files,
|
||||
stats.bytes_read_non_output_levels / 1048576.0,
|
||||
stats.bytes_read_output_level / 1048576.0,
|
||||
stats.bytes_written / 1048576.0,
|
||||
(stats.bytes_written + stats.bytes_read_output_level +
|
||||
stats.bytes_read_non_output_levels) /
|
||||
static_cast<double>(stats.bytes_read_non_output_levels),
|
||||
stats.bytes_written /
|
||||
static_cast<double>(stats.bytes_read_non_output_levels),
|
||||
stats.bytes_written / 1048576.0, read_write_amp, write_amp,
|
||||
status.ToString().c_str(), stats.num_input_records,
|
||||
stats.num_dropped_records);
|
||||
|
||||
@@ -768,8 +771,12 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
sub_compact->ShouldStopBefore(
|
||||
key, sub_compact->current_output_file_size) &&
|
||||
sub_compact->builder != nullptr) {
|
||||
CompactionIterationStats range_del_out_stats;
|
||||
status = FinishCompactionOutputFile(input->status(), sub_compact,
|
||||
range_del_agg.get());
|
||||
range_del_agg.get(),
|
||||
&range_del_out_stats, &key);
|
||||
RecordDroppedKeys(range_del_out_stats,
|
||||
&sub_compact->compaction_job_stats);
|
||||
if (!status.ok()) {
|
||||
break;
|
||||
}
|
||||
@@ -846,9 +853,6 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
}
|
||||
}
|
||||
|
||||
Status input_status = input->status();
|
||||
c_iter->Next();
|
||||
|
||||
// Close output file if it is big enough
|
||||
// TODO(aekmekji): determine if file should be closed earlier than this
|
||||
// during subcompactions (i.e. if output size, estimated by input size, is
|
||||
@@ -857,17 +861,26 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
if (sub_compact->compaction->output_level() != 0 &&
|
||||
sub_compact->current_output_file_size >=
|
||||
sub_compact->compaction->max_output_file_size()) {
|
||||
Status input_status = input->status();
|
||||
c_iter->Next();
|
||||
|
||||
const Slice* next_key = nullptr;
|
||||
if (c_iter->Valid()) {
|
||||
next_key = &c_iter->key();
|
||||
}
|
||||
CompactionIterationStats range_del_out_stats;
|
||||
status = FinishCompactionOutputFile(input_status, sub_compact,
|
||||
range_del_agg.get(), next_key);
|
||||
range_del_agg.get(),
|
||||
&range_del_out_stats, next_key);
|
||||
RecordDroppedKeys(range_del_out_stats,
|
||||
&sub_compact->compaction_job_stats);
|
||||
if (sub_compact->outputs.size() == 1) {
|
||||
// Use dictionary from first output file for compression of subsequent
|
||||
// files.
|
||||
sub_compact->compression_dict = std::move(compression_dict);
|
||||
}
|
||||
} else {
|
||||
c_iter->Next();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -896,12 +909,17 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
"Database shutdown or Column family drop during compaction");
|
||||
}
|
||||
if (status.ok() && sub_compact->builder == nullptr &&
|
||||
sub_compact->outputs.size() == 0 &&
|
||||
range_del_agg->ShouldAddTombstones(bottommost_level_)) {
|
||||
// handle subcompaction containing only range deletions
|
||||
status = OpenCompactionOutputFile(sub_compact);
|
||||
}
|
||||
if (status.ok() && sub_compact->builder != nullptr) {
|
||||
status = FinishCompactionOutputFile(input->status(), sub_compact,
|
||||
range_del_agg.get());
|
||||
CompactionIterationStats range_del_out_stats;
|
||||
status =
|
||||
FinishCompactionOutputFile(input->status(), sub_compact,
|
||||
range_del_agg.get(), &range_del_out_stats);
|
||||
RecordDroppedKeys(range_del_out_stats, &sub_compact->compaction_job_stats);
|
||||
}
|
||||
if (status.ok()) {
|
||||
status = input->status();
|
||||
@@ -927,7 +945,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
}
|
||||
|
||||
void CompactionJob::RecordDroppedKeys(
|
||||
const CompactionIteratorStats& c_iter_stats,
|
||||
const CompactionIterationStats& c_iter_stats,
|
||||
CompactionJobStats* compaction_job_stats) {
|
||||
if (c_iter_stats.num_record_drop_user > 0) {
|
||||
RecordTick(stats_, COMPACTION_KEY_DROP_USER,
|
||||
@@ -949,11 +967,20 @@ void CompactionJob::RecordDroppedKeys(
|
||||
c_iter_stats.num_record_drop_obsolete;
|
||||
}
|
||||
}
|
||||
if (c_iter_stats.num_record_drop_range_del > 0) {
|
||||
RecordTick(stats_, COMPACTION_KEY_DROP_RANGE_DEL,
|
||||
c_iter_stats.num_record_drop_range_del);
|
||||
}
|
||||
if (c_iter_stats.num_range_del_drop_obsolete > 0) {
|
||||
RecordTick(stats_, COMPACTION_RANGE_DEL_DROP_OBSOLETE,
|
||||
c_iter_stats.num_range_del_drop_obsolete);
|
||||
}
|
||||
}
|
||||
|
||||
Status CompactionJob::FinishCompactionOutputFile(
|
||||
const Status& input_status, SubcompactionState* sub_compact,
|
||||
RangeDelAggregator* range_del_agg,
|
||||
CompactionIterationStats* range_del_out_stats,
|
||||
const Slice* next_table_min_key /* = nullptr */) {
|
||||
AutoThreadOperationStageUpdater stage_updater(
|
||||
ThreadStatus::STAGE_COMPACTION_SYNC_FILE);
|
||||
@@ -970,14 +997,34 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
Status s = input_status;
|
||||
auto meta = &sub_compact->current_output()->meta;
|
||||
if (s.ok()) {
|
||||
// For the first output table, include range tombstones before the min key
|
||||
// boundary. For subsequent output tables, this is unnecessary because we
|
||||
// extend each file's max key boundary up until the next file's min key when
|
||||
// range tombstones fall in the gap.
|
||||
range_del_agg->AddToBuilder(
|
||||
sub_compact->builder.get(),
|
||||
sub_compact->outputs.size() == 1 /* extend_before_min_key */,
|
||||
next_table_min_key, meta, bottommost_level_);
|
||||
Slice lower_bound_guard, upper_bound_guard;
|
||||
const Slice *lower_bound, *upper_bound;
|
||||
if (sub_compact->outputs.size() == 1) {
|
||||
// For the first output table, include range tombstones before the min key
|
||||
// but after the subcompaction boundary.
|
||||
lower_bound = sub_compact->start;
|
||||
} else if (meta->smallest.size() > 0) {
|
||||
// For subsequent output tables, only include range tombstones from min
|
||||
// key onwards since the previous file was extended to contain range
|
||||
// tombstones falling before min key.
|
||||
lower_bound_guard = meta->smallest.user_key();
|
||||
lower_bound = &lower_bound_guard;
|
||||
} else {
|
||||
lower_bound = nullptr;
|
||||
}
|
||||
if (next_table_min_key != nullptr) {
|
||||
// This isn't the last file in the subcompaction, so extend until the next
|
||||
// file starts.
|
||||
upper_bound_guard = ExtractUserKey(*next_table_min_key);
|
||||
upper_bound = &upper_bound_guard;
|
||||
} else {
|
||||
// This is the last file in the subcompaction, so extend until the
|
||||
// subcompaction ends.
|
||||
upper_bound = sub_compact->end;
|
||||
}
|
||||
range_del_agg->AddToBuilder(sub_compact->builder.get(), lower_bound,
|
||||
upper_bound, meta, range_del_out_stats,
|
||||
bottommost_level_);
|
||||
}
|
||||
const uint64_t current_entries = sub_compact->builder->NumEntries();
|
||||
meta->marked_for_compaction = sub_compact->builder->NeedCompact();
|
||||
@@ -1007,8 +1054,9 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
// Verify that the table is usable
|
||||
InternalIterator* iter = cfd->table_cache()->NewIterator(
|
||||
ReadOptions(), env_options_, cfd->internal_comparator(), meta->fd,
|
||||
nullptr, cfd->internal_stats()->GetFileReadHist(
|
||||
compact_->compaction->output_level()),
|
||||
nullptr /* range_del_agg */, nullptr,
|
||||
cfd->internal_stats()->GetFileReadHist(
|
||||
compact_->compaction->output_level()),
|
||||
false);
|
||||
s = iter->status();
|
||||
|
||||
@@ -1215,8 +1263,7 @@ void CompactionJob::UpdateCompactionStats() {
|
||||
for (int input_level = 0;
|
||||
input_level < static_cast<int>(compaction->num_input_levels());
|
||||
++input_level) {
|
||||
if (compaction->start_level() + input_level
|
||||
!= compaction->output_level()) {
|
||||
if (compaction->level(input_level) != compaction->output_level()) {
|
||||
UpdateCompactionInputStatsHelper(
|
||||
&compaction_stats_.num_input_files_in_non_output_levels,
|
||||
&compaction_stats_.bytes_read_non_output_levels,
|
||||
|
||||
+6
-5
@@ -96,17 +96,18 @@ class CompactionJob {
|
||||
// kv-pairs
|
||||
void ProcessKeyValueCompaction(SubcompactionState* sub_compact);
|
||||
|
||||
Status FinishCompactionOutputFile(const Status& input_status,
|
||||
SubcompactionState* sub_compact,
|
||||
RangeDelAggregator* range_del_agg = nullptr,
|
||||
const Slice* next_table_min_key = nullptr);
|
||||
Status FinishCompactionOutputFile(
|
||||
const Status& input_status, SubcompactionState* sub_compact,
|
||||
RangeDelAggregator* range_del_agg,
|
||||
CompactionIterationStats* range_del_out_stats,
|
||||
const Slice* next_table_min_key = nullptr);
|
||||
Status InstallCompactionResults(const MutableCFOptions& mutable_cf_options);
|
||||
void RecordCompactionIOStats();
|
||||
Status OpenCompactionOutputFile(SubcompactionState* sub_compact);
|
||||
void CleanupCompaction();
|
||||
void UpdateCompactionJobStats(
|
||||
const InternalStats::CompactionStats& stats) const;
|
||||
void RecordDroppedKeys(const CompactionIteratorStats& c_iter_stats,
|
||||
void RecordDroppedKeys(const CompactionIterationStats& c_iter_stats,
|
||||
CompactionJobStats* compaction_job_stats = nullptr);
|
||||
|
||||
void UpdateCompactionStats();
|
||||
|
||||
+16
-10
@@ -451,13 +451,14 @@ bool CompactionPicker::SetupOtherInputs(
|
||||
vstorage->GetOverlappingInputs(output_level, &smallest, &largest,
|
||||
&output_level_inputs->files, *parent_index,
|
||||
parent_index);
|
||||
if (!output_level_inputs->empty()) {
|
||||
ExpandWhileOverlapping(cf_name, vstorage, output_level_inputs);
|
||||
}
|
||||
|
||||
if (FilesInCompaction(output_level_inputs->files)) {
|
||||
return false;
|
||||
}
|
||||
if (!output_level_inputs->empty()) {
|
||||
if (!ExpandWhileOverlapping(cf_name, vstorage, output_level_inputs)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// See if we can further grow the number of inputs in "level" without
|
||||
// changing the number of "level+1" files we pick up. We also choose NOT
|
||||
@@ -484,11 +485,15 @@ bool CompactionPicker::SetupOtherInputs(
|
||||
!vstorage->HasOverlappingUserKey(&expanded0.files, input_level)) {
|
||||
InternalKey new_start, new_limit;
|
||||
GetRange(expanded0, &new_start, &new_limit);
|
||||
std::vector<FileMetaData*> expanded1;
|
||||
CompactionInputFiles expanded1;
|
||||
expanded1.level = output_level;
|
||||
vstorage->GetOverlappingInputs(output_level, &new_start, &new_limit,
|
||||
&expanded1, *parent_index, parent_index);
|
||||
if (expanded1.size() == output_level_inputs->size() &&
|
||||
!FilesInCompaction(expanded1)) {
|
||||
&expanded1.files, *parent_index,
|
||||
parent_index);
|
||||
assert(!expanded1.empty());
|
||||
if (!FilesInCompaction(expanded1.files) &&
|
||||
ExpandWhileOverlapping(cf_name, vstorage, &expanded1) &&
|
||||
expanded1.size() == output_level_inputs->size()) {
|
||||
Log(InfoLogLevel::INFO_LEVEL, ioptions_.info_log,
|
||||
"[%s] Expanding@%d %" ROCKSDB_PRIszt "+%" ROCKSDB_PRIszt "(%" PRIu64
|
||||
"+%" PRIu64 " bytes) to %" ROCKSDB_PRIszt "+%" ROCKSDB_PRIszt
|
||||
@@ -499,7 +504,7 @@ bool CompactionPicker::SetupOtherInputs(
|
||||
smallest = new_start;
|
||||
largest = new_limit;
|
||||
inputs->files = expanded0.files;
|
||||
output_level_inputs->files = expanded1;
|
||||
output_level_inputs->files = expanded1.files;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1179,7 +1184,8 @@ uint32_t LevelCompactionPicker::GetPathId(
|
||||
return p;
|
||||
} else {
|
||||
current_path_size -= level_size;
|
||||
level_size *= mutable_cf_options.max_bytes_for_level_multiplier;
|
||||
level_size = static_cast<uint64_t>(
|
||||
level_size * mutable_cf_options.max_bytes_for_level_multiplier);
|
||||
cur_level++;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -121,6 +121,7 @@ class CompactionPickerTest : public testing::Test {
|
||||
vstorage_->GenerateLevelFilesBrief();
|
||||
vstorage_->ComputeCompactionScore(ioptions_, mutable_cf_options_);
|
||||
vstorage_->GenerateLevel0NonOverlapping();
|
||||
vstorage_->ComputeFilesMarkedForCompaction();
|
||||
vstorage_->SetFinalized();
|
||||
}
|
||||
};
|
||||
@@ -693,6 +694,70 @@ TEST_F(CompactionPickerTest, OverlappingUserKeys4) {
|
||||
ASSERT_EQ(7U, compaction->input(1, 0)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, OverlappingUserKeys5) {
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
// Overlapping user keys on same level and output level
|
||||
Add(1, 1U, "200", "400", 1000000000U);
|
||||
Add(1, 2U, "400", "500", 1U, 0, 0);
|
||||
Add(2, 3U, "000", "100", 1U);
|
||||
Add(2, 4U, "100", "600", 1U, 0, 0);
|
||||
Add(2, 5U, "600", "700", 1U, 0, 0);
|
||||
|
||||
vstorage_->LevelFiles(2)[2]->being_compacted = true;
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
|
||||
ASSERT_TRUE(compaction.get() == nullptr);
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, OverlappingUserKeys6) {
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
// Overlapping user keys on same level and output level
|
||||
Add(1, 1U, "200", "400", 1U, 0, 0);
|
||||
Add(1, 2U, "401", "500", 1U, 0, 0);
|
||||
Add(2, 3U, "000", "100", 1U);
|
||||
Add(2, 4U, "100", "300", 1U, 0, 0);
|
||||
Add(2, 5U, "305", "450", 1U, 0, 0);
|
||||
Add(2, 6U, "460", "600", 1U, 0, 0);
|
||||
Add(2, 7U, "600", "700", 1U, 0, 0);
|
||||
|
||||
vstorage_->LevelFiles(1)[0]->marked_for_compaction = true;
|
||||
vstorage_->LevelFiles(1)[1]->marked_for_compaction = true;
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(2U, compaction->num_input_levels());
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(3U, compaction->num_input_files(1));
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, OverlappingUserKeys7) {
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
mutable_cf_options_.max_compaction_bytes = 100000000000u;
|
||||
// Overlapping user keys on same level and output level
|
||||
Add(1, 1U, "200", "400", 1U, 0, 0);
|
||||
Add(1, 2U, "401", "500", 1000000000U, 0, 0);
|
||||
Add(2, 3U, "100", "250", 1U);
|
||||
Add(2, 4U, "300", "600", 1U, 0, 0);
|
||||
Add(2, 5U, "600", "800", 1U, 0, 0);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(2U, compaction->num_input_levels());
|
||||
ASSERT_GE(1U, compaction->num_input_files(0));
|
||||
ASSERT_GE(2U, compaction->num_input_files(1));
|
||||
// File 5 has to be included in the compaction
|
||||
ASSERT_EQ(5U, compaction->inputs(1)->back()->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, NotScheduleL1IfL0WithHigherPri1) {
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 2;
|
||||
|
||||
@@ -41,6 +41,7 @@ class CuckooTableDBTest : public testing::Test {
|
||||
options.memtable_factory.reset(NewHashLinkListRepFactory(4, 0, 3, true));
|
||||
options.allow_mmap_reads = true;
|
||||
options.create_if_missing = true;
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
return options;
|
||||
}
|
||||
|
||||
|
||||
@@ -810,6 +810,7 @@ TEST_F(DBBloomFilterTest, PrefixScan) {
|
||||
options.max_background_compactions = 2;
|
||||
options.create_if_missing = true;
|
||||
options.memtable_factory.reset(NewHashSkipListRepFactory(16));
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.no_block_cache = true;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
namespace rocksdb {
|
||||
|
||||
static int cfilter_count = 0;
|
||||
static int cfilter_skips = 0;
|
||||
|
||||
// This is a static filter used for filtering
|
||||
// kvs during the compaction process.
|
||||
@@ -65,6 +66,30 @@ class DeleteISFilter : public CompactionFilter {
|
||||
virtual const char* Name() const override { return "DeleteFilter"; }
|
||||
};
|
||||
|
||||
// Skip x if floor(x/10) is even, use range skips. Requires that keys are
|
||||
// zero-padded to length 10.
|
||||
class SkipEvenFilter : public CompactionFilter {
|
||||
public:
|
||||
virtual Decision FilterV2(int level, const Slice& key, ValueType value_type,
|
||||
const Slice& existing_value, std::string* new_value,
|
||||
std::string* skip_until) const override {
|
||||
cfilter_count++;
|
||||
int i = std::stoi(key.ToString());
|
||||
if (i / 10 % 2 == 0) {
|
||||
char key_str[100];
|
||||
snprintf(key_str, sizeof(key), "%010d", i / 10 * 10 + 10);
|
||||
*skip_until = key_str;
|
||||
++cfilter_skips;
|
||||
return Decision::kRemoveAndSkipUntil;
|
||||
}
|
||||
return Decision::kKeep;
|
||||
}
|
||||
|
||||
virtual bool IgnoreSnapshots() const override { return true; }
|
||||
|
||||
virtual const char* Name() const override { return "DeleteFilter"; }
|
||||
};
|
||||
|
||||
class DelayFilter : public CompactionFilter {
|
||||
public:
|
||||
explicit DelayFilter(DBTestBase* d) : db_test(d) {}
|
||||
@@ -174,6 +199,20 @@ class DeleteISFilterFactory : public CompactionFilterFactory {
|
||||
virtual const char* Name() const override { return "DeleteFilterFactory"; }
|
||||
};
|
||||
|
||||
class SkipEvenFilterFactory : public CompactionFilterFactory {
|
||||
public:
|
||||
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
const CompactionFilter::Context& context) override {
|
||||
if (context.is_manual_compaction) {
|
||||
return std::unique_ptr<CompactionFilter>(new SkipEvenFilter());
|
||||
} else {
|
||||
return std::unique_ptr<CompactionFilter>(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "SkipEvenFilterFactory"; }
|
||||
};
|
||||
|
||||
class DelayFilterFactory : public CompactionFilterFactory {
|
||||
public:
|
||||
explicit DelayFilterFactory(DBTestBase* d) : db_test(d) {}
|
||||
@@ -261,8 +300,10 @@ TEST_F(DBTestCompactionFilter, CompactionFilter) {
|
||||
int total = 0;
|
||||
Arena arena;
|
||||
{
|
||||
InternalKeyComparator icmp(options.comparator);
|
||||
RangeDelAggregator range_del_agg(icmp, {} /* snapshots */);
|
||||
ScopedArenaIterator iter(
|
||||
dbfull()->NewInternalIterator(&arena, handles_[1]));
|
||||
dbfull()->NewInternalIterator(&arena, &range_del_agg, handles_[1]));
|
||||
iter->SeekToFirst();
|
||||
ASSERT_OK(iter->status());
|
||||
while (iter->Valid()) {
|
||||
@@ -349,8 +390,10 @@ TEST_F(DBTestCompactionFilter, CompactionFilter) {
|
||||
// level Lmax because this record is at the tip
|
||||
count = 0;
|
||||
{
|
||||
InternalKeyComparator icmp(options.comparator);
|
||||
RangeDelAggregator range_del_agg(icmp, {} /* snapshots */);
|
||||
ScopedArenaIterator iter(
|
||||
dbfull()->NewInternalIterator(&arena, handles_[1]));
|
||||
dbfull()->NewInternalIterator(&arena, &range_del_agg, handles_[1]));
|
||||
iter->SeekToFirst();
|
||||
ASSERT_OK(iter->status());
|
||||
while (iter->Valid()) {
|
||||
@@ -566,7 +609,10 @@ TEST_F(DBTestCompactionFilter, CompactionFilterContextManual) {
|
||||
int count = 0;
|
||||
int total = 0;
|
||||
Arena arena;
|
||||
ScopedArenaIterator iter(dbfull()->NewInternalIterator(&arena));
|
||||
InternalKeyComparator icmp(options.comparator);
|
||||
RangeDelAggregator range_del_agg(icmp, {} /* snapshots */);
|
||||
ScopedArenaIterator iter(
|
||||
dbfull()->NewInternalIterator(&arena, &range_del_agg));
|
||||
iter->SeekToFirst();
|
||||
ASSERT_OK(iter->status());
|
||||
while (iter->Valid()) {
|
||||
@@ -712,6 +758,47 @@ TEST_F(DBTestCompactionFilter, CompactionFilterIgnoreSnapshot) {
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBTestCompactionFilter, SkipUntil) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_filter_factory = std::make_shared<SkipEvenFilterFactory>();
|
||||
options.disable_auto_compactions = true;
|
||||
options.create_if_missing = true;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Write 100K keys, these are written to a few files in L0.
|
||||
for (int table = 0; table < 4; ++table) {
|
||||
// Key ranges in tables are [0, 38], [106, 149], [212, 260], [318, 371].
|
||||
for (int i = table * 6; i < 39 + table * 11; ++i) {
|
||||
char key[100];
|
||||
snprintf(key, sizeof(key), "%010d", table * 100 + i);
|
||||
Put(key, std::to_string(table * 1000 + i));
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
|
||||
cfilter_skips = 0;
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
// Numberof skips in tables: 2, 3, 3, 3.
|
||||
ASSERT_EQ(11, cfilter_skips);
|
||||
|
||||
for (int table = 0; table < 4; ++table) {
|
||||
for (int i = table * 6; i < 39 + table * 11; ++i) {
|
||||
int k = table * 100 + i;
|
||||
char key[100];
|
||||
snprintf(key, sizeof(key), "%010d", table * 100 + i);
|
||||
auto expected = std::to_string(table * 1000 + i);
|
||||
std::string val;
|
||||
Status s = db_->Get(ReadOptions(), key, &val);
|
||||
if (k / 10 % 2 == 0) {
|
||||
ASSERT_TRUE(s.IsNotFound());
|
||||
} else {
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(expected, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -305,8 +305,8 @@ TEST_F(DBCompactionTest, TestTableReaderForCompaction) {
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
// Preloading iterator issues one table cache lookup and creates
|
||||
// a new table reader. One file is created for flush and one for compaction.
|
||||
// Compaction inputs make no table cache look-up for data iterators or
|
||||
// range tombstone iterators since they're already cached.
|
||||
// Compaction inputs make no table cache look-up for data/range deletion
|
||||
// iterators
|
||||
ASSERT_EQ(num_table_cache_lookup, 2);
|
||||
// Create new iterator for:
|
||||
// (1) 1 for verifying flush results
|
||||
@@ -327,7 +327,8 @@ TEST_F(DBCompactionTest, TestTableReaderForCompaction) {
|
||||
cro.target_level = 2;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
db_->CompactRange(cro, nullptr, nullptr);
|
||||
// Only verifying compaction outputs issues one table cache lookup.
|
||||
// Only verifying compaction outputs issues one table cache lookup
|
||||
// for both data block and range deletion block).
|
||||
ASSERT_EQ(num_table_cache_lookup, 1);
|
||||
// One for compaction input, one for verifying compaction results.
|
||||
ASSERT_EQ(num_new_table_reader, 2);
|
||||
@@ -1259,7 +1260,8 @@ TEST_F(DBCompactionTest, ManualPartialFill) {
|
||||
uint64_t target_size = 4 * options.max_bytes_for_level_base;
|
||||
for (int32_t i = 1; i < options.num_levels; i++) {
|
||||
ASSERT_LE(SizeAtLevel(i), target_size);
|
||||
target_size *= options.max_bytes_for_level_multiplier;
|
||||
target_size = static_cast<uint64_t>(target_size *
|
||||
options.max_bytes_for_level_multiplier);
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT("DBCompaction::PartialFill:2");
|
||||
@@ -1336,7 +1338,8 @@ TEST_F(DBCompactionTest, DeleteFileRange) {
|
||||
uint64_t target_size = 4 * options.max_bytes_for_level_base;
|
||||
for (int32_t i = 1; i < options.num_levels; i++) {
|
||||
ASSERT_LE(SizeAtLevel(i), target_size);
|
||||
target_size *= options.max_bytes_for_level_multiplier;
|
||||
target_size = static_cast<uint64_t>(target_size *
|
||||
options.max_bytes_for_level_multiplier);
|
||||
}
|
||||
|
||||
size_t old_num_files = CountFiles();
|
||||
|
||||
+432
-160
@@ -39,6 +39,7 @@
|
||||
#include "db/db_iter.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/event_helpers.h"
|
||||
#include "db/external_sst_file_ingestion_job.h"
|
||||
#include "db/filename.h"
|
||||
#include "db/flush_job.h"
|
||||
#include "db/forward_iterator.h"
|
||||
@@ -50,6 +51,7 @@
|
||||
#include "db/memtable_list.h"
|
||||
#include "db/merge_context.h"
|
||||
#include "db/merge_helper.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "db/table_cache.h"
|
||||
#include "db/table_properties_collector.h"
|
||||
#include "db/transaction_log_impl.h"
|
||||
@@ -121,12 +123,11 @@ struct DBImpl::WriteContext {
|
||||
};
|
||||
|
||||
Options SanitizeOptions(const std::string& dbname,
|
||||
const InternalKeyComparator* icmp,
|
||||
const Options& src) {
|
||||
auto db_options = SanitizeOptions(dbname, DBOptions(src));
|
||||
ImmutableDBOptions immutable_db_options(db_options);
|
||||
auto cf_options =
|
||||
SanitizeOptions(immutable_db_options, icmp, ColumnFamilyOptions(src));
|
||||
SanitizeOptions(immutable_db_options, ColumnFamilyOptions(src));
|
||||
return Options(db_options, cf_options);
|
||||
}
|
||||
|
||||
@@ -270,36 +271,28 @@ CompressionType GetCompressionFlush(
|
||||
// Compressing memtable flushes might not help unless the sequential load
|
||||
// optimization is used for leveled compaction. Otherwise the CPU and
|
||||
// latency overhead is not offset by saving much space.
|
||||
|
||||
bool can_compress;
|
||||
|
||||
if (ioptions.compaction_style == kCompactionStyleUniversal) {
|
||||
can_compress =
|
||||
(ioptions.compaction_options_universal.compression_size_percent < 0);
|
||||
if (ioptions.compaction_options_universal.compression_size_percent < 0) {
|
||||
return mutable_cf_options.compression;
|
||||
} else {
|
||||
return kNoCompression;
|
||||
}
|
||||
} else if (!ioptions.compression_per_level.empty()) {
|
||||
// For leveled compress when min_level_to_compress != 0.
|
||||
return ioptions.compression_per_level[0];
|
||||
} else {
|
||||
// For leveled compress when min_level_to_compress == 0.
|
||||
can_compress = ioptions.compression_per_level.empty() ||
|
||||
ioptions.compression_per_level[0] != kNoCompression;
|
||||
}
|
||||
|
||||
if (can_compress) {
|
||||
return mutable_cf_options.compression;
|
||||
} else {
|
||||
return kNoCompression;
|
||||
}
|
||||
}
|
||||
|
||||
void DumpSupportInfo(Logger* logger) {
|
||||
Log(InfoLogLevel::INFO_LEVEL, logger, "Compression algorithms supported:");
|
||||
Log(InfoLogLevel::INFO_LEVEL, logger, "\tSnappy supported: %d",
|
||||
Snappy_Supported());
|
||||
Log(InfoLogLevel::INFO_LEVEL, logger, "\tZlib supported: %d",
|
||||
Zlib_Supported());
|
||||
Log(InfoLogLevel::INFO_LEVEL, logger, "\tBzip supported: %d",
|
||||
BZip2_Supported());
|
||||
Log(InfoLogLevel::INFO_LEVEL, logger, "\tLZ4 supported: %d", LZ4_Supported());
|
||||
Log(InfoLogLevel::INFO_LEVEL, logger, "Fast CRC32 supported: %d",
|
||||
crc32c::IsFastCrc32Supported());
|
||||
Header(logger, "Compression algorithms supported:");
|
||||
Header(logger, "\tSnappy supported: %d", Snappy_Supported());
|
||||
Header(logger, "\tZlib supported: %d", Zlib_Supported());
|
||||
Header(logger, "\tBzip supported: %d", BZip2_Supported());
|
||||
Header(logger, "\tLZ4 supported: %d", LZ4_Supported());
|
||||
Header(logger, "\tZSTD supported: %d", ZSTD_Supported());
|
||||
Header(logger, "Fast CRC32 supported: %d", crc32c::IsFastCrc32Supported());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -329,7 +322,7 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname)
|
||||
? immutable_db_options_.write_thread_max_yield_usec
|
||||
: 0,
|
||||
immutable_db_options_.write_thread_slow_yield_usec),
|
||||
write_controller_(immutable_db_options_.delayed_write_rate),
|
||||
write_controller_(mutable_db_options_.delayed_write_rate),
|
||||
last_batch_group_size_(0),
|
||||
unscheduled_flushes_(0),
|
||||
unscheduled_compactions_(0),
|
||||
@@ -345,8 +338,9 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname)
|
||||
last_stats_dump_time_microsec_(0),
|
||||
next_job_id_(1),
|
||||
has_unpersisted_data_(false),
|
||||
unable_to_flush_oldest_log_(false),
|
||||
env_options_(BuildDBOptions(immutable_db_options_, mutable_db_options_)),
|
||||
num_running_addfile_(0),
|
||||
num_running_ingest_file_(0),
|
||||
#ifndef ROCKSDB_LITE
|
||||
wal_manager_(immutable_db_options_, env_options_),
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -383,7 +377,8 @@ void DBImpl::CancelAllBackgroundWork(bool wait) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
|
||||
if (!shutting_down_.load(std::memory_order_acquire) &&
|
||||
has_unpersisted_data_) {
|
||||
has_unpersisted_data_ &&
|
||||
!mutable_db_options_.avoid_flush_during_shutdown) {
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (!cfd->IsDropped() && !cfd->mem()->IsEmpty()) {
|
||||
cfd->Ref();
|
||||
@@ -639,13 +634,20 @@ void DBImpl::MaybeDumpStats() {
|
||||
default_cf_internal_stats_->GetStringProperty(
|
||||
*db_property_info, DB::Properties::kDBStats, &stats);
|
||||
}
|
||||
if (immutable_db_options_.dump_malloc_stats) {
|
||||
DumpMallocStats(&stats);
|
||||
}
|
||||
Log(InfoLogLevel::WARN_LEVEL, immutable_db_options_.info_log,
|
||||
"------- DUMPING STATS -------");
|
||||
Log(InfoLogLevel::WARN_LEVEL, immutable_db_options_.info_log, "%s",
|
||||
stats.c_str());
|
||||
if (immutable_db_options_.dump_malloc_stats) {
|
||||
stats.clear();
|
||||
DumpMallocStats(&stats);
|
||||
if (!stats.empty()) {
|
||||
Log(InfoLogLevel::WARN_LEVEL, immutable_db_options_.info_log,
|
||||
"------- Malloc STATS -------");
|
||||
Log(InfoLogLevel::WARN_LEVEL, immutable_db_options_.info_log, "%s",
|
||||
stats.c_str());
|
||||
}
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
PrintStatistics();
|
||||
@@ -653,6 +655,10 @@ void DBImpl::MaybeDumpStats() {
|
||||
}
|
||||
|
||||
uint64_t DBImpl::FindMinPrepLogReferencedByMemTable() {
|
||||
if (!allow_2pc()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t min_log = 0;
|
||||
|
||||
// we must look through the memtables for two phase transactions
|
||||
@@ -697,6 +703,11 @@ void DBImpl::MarkLogAsContainingPrepSection(uint64_t log) {
|
||||
}
|
||||
|
||||
uint64_t DBImpl::FindMinLogContainingOutstandingPrep() {
|
||||
|
||||
if (!allow_2pc()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(prep_heap_mutex_);
|
||||
uint64_t min_log = 0;
|
||||
|
||||
@@ -735,6 +746,34 @@ void DBImpl::ScheduleBgLogWriterClose(JobContext* job_context) {
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t DBImpl::MinLogNumberToKeep() {
|
||||
uint64_t log_number = versions_->MinLogNumber();
|
||||
|
||||
if (allow_2pc()) {
|
||||
// if are 2pc we must consider logs containing prepared
|
||||
// sections of outstanding transactions.
|
||||
//
|
||||
// We must check min logs with outstanding prep before we check
|
||||
// logs referneces by memtables because a log referenced by the
|
||||
// first data structure could transition to the second under us.
|
||||
//
|
||||
// TODO(horuff): iterating over all column families under db mutex.
|
||||
// should find more optimial solution
|
||||
auto min_log_in_prep_heap = FindMinLogContainingOutstandingPrep();
|
||||
|
||||
if (min_log_in_prep_heap != 0 && min_log_in_prep_heap < log_number) {
|
||||
log_number = min_log_in_prep_heap;
|
||||
}
|
||||
|
||||
auto min_log_refed_by_mem = FindMinPrepLogReferencedByMemTable();
|
||||
|
||||
if (min_log_refed_by_mem != 0 && min_log_refed_by_mem < log_number) {
|
||||
log_number = min_log_refed_by_mem;
|
||||
}
|
||||
}
|
||||
return log_number;
|
||||
}
|
||||
|
||||
// * Returns the list of live files in 'sst_live'
|
||||
// If it's doing full scan:
|
||||
// * Returns the list of all files in the filesystem in
|
||||
@@ -793,32 +832,7 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
job_context->manifest_file_number = versions_->manifest_file_number();
|
||||
job_context->pending_manifest_file_number =
|
||||
versions_->pending_manifest_file_number();
|
||||
job_context->log_number = versions_->MinLogNumber();
|
||||
|
||||
if (allow_2pc()) {
|
||||
// if are 2pc we must consider logs containing prepared
|
||||
// sections of outstanding transactions.
|
||||
//
|
||||
// We must check min logs with outstanding prep before we check
|
||||
// logs referneces by memtables because a log referenced by the
|
||||
// first data structure could transition to the second under us.
|
||||
//
|
||||
// TODO(horuff): iterating over all column families under db mutex.
|
||||
// should find more optimial solution
|
||||
auto min_log_in_prep_heap = FindMinLogContainingOutstandingPrep();
|
||||
|
||||
if (min_log_in_prep_heap != 0 &&
|
||||
min_log_in_prep_heap < job_context->log_number) {
|
||||
job_context->log_number = min_log_in_prep_heap;
|
||||
}
|
||||
|
||||
auto min_log_refed_by_mem = FindMinPrepLogReferencedByMemTable();
|
||||
|
||||
if (min_log_refed_by_mem != 0 &&
|
||||
min_log_refed_by_mem < job_context->log_number) {
|
||||
job_context->log_number = min_log_refed_by_mem;
|
||||
}
|
||||
}
|
||||
job_context->log_number = MinLogNumberToKeep();
|
||||
|
||||
job_context->prev_log_number = versions_->prev_log_number();
|
||||
|
||||
@@ -1347,7 +1361,6 @@ Status DBImpl::Recover(
|
||||
}
|
||||
}
|
||||
}
|
||||
SetTickerCount(stats_, SEQUENCE_NUMBER, versions_->LastSequence());
|
||||
}
|
||||
|
||||
// Initial value
|
||||
@@ -1515,18 +1528,6 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
}
|
||||
}
|
||||
|
||||
bool no_prev_seq = true;
|
||||
if (!immutable_db_options_.allow_2pc) {
|
||||
*next_sequence = sequence;
|
||||
} else {
|
||||
if (*next_sequence == kMaxSequenceNumber) {
|
||||
*next_sequence = sequence;
|
||||
} else {
|
||||
no_prev_seq = false;
|
||||
WriteBatchInternal::SetSequence(&batch, *next_sequence);
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (immutable_db_options_.wal_filter != nullptr) {
|
||||
WriteBatch new_batch;
|
||||
@@ -1613,15 +1614,6 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
&batch, column_family_memtables_.get(), &flush_scheduler_, true,
|
||||
log_number, this, false /* concurrent_memtable_writes */,
|
||||
next_sequence, &has_valid_writes);
|
||||
// If it is the first log file and there is no column family updated
|
||||
// after replaying the file, this file may be a stale file. We ignore
|
||||
// sequence IDs from the file. Otherwise, if a newer stale log file that
|
||||
// has been deleted, the sequenceID may be wrong.
|
||||
if (immutable_db_options_.allow_2pc) {
|
||||
if (no_prev_seq && !has_valid_writes) {
|
||||
*next_sequence = kMaxSequenceNumber;
|
||||
}
|
||||
}
|
||||
MaybeIgnoreError(&status);
|
||||
if (!status.ok()) {
|
||||
// We are treating this as a failure while reading since we read valid
|
||||
@@ -1687,6 +1679,9 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
}
|
||||
}
|
||||
|
||||
// True if there's any data in the WALs; if not, we can skip re-processing
|
||||
// them later
|
||||
bool data_seen = false;
|
||||
if (!read_only) {
|
||||
// no need to refcount since client still doesn't have access
|
||||
// to the DB and can not drop column families while we iterate
|
||||
@@ -1720,8 +1715,9 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
flushed = true;
|
||||
|
||||
cfd->CreateNewMemtable(*cfd->GetLatestMutableCFOptions(),
|
||||
*next_sequence);
|
||||
versions_->LastSequence());
|
||||
}
|
||||
data_seen = true;
|
||||
}
|
||||
|
||||
// write MANIFEST with update
|
||||
@@ -1747,7 +1743,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
}
|
||||
}
|
||||
|
||||
if (!flushed) {
|
||||
if (data_seen && !flushed) {
|
||||
// Mark these as alive so they'll be considered for deletion later by
|
||||
// FindObsoleteFiles()
|
||||
for (auto log_number : log_numbers) {
|
||||
@@ -1795,7 +1791,9 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
|
||||
s = BuildTable(
|
||||
dbname_, env_, *cfd->ioptions(), mutable_cf_options, env_options_,
|
||||
cfd->table_cache(), iter.get(), &meta, cfd->internal_comparator(),
|
||||
cfd->table_cache(), iter.get(),
|
||||
std::unique_ptr<InternalIterator>(mem->NewRangeTombstoneIterator(ro)),
|
||||
&meta, cfd->internal_comparator(),
|
||||
cfd->int_tbl_prop_collector_factories(), cfd->GetID(), cfd->GetName(),
|
||||
snapshot_seqs, earliest_write_conflict_snapshot,
|
||||
GetCompressionFlush(*cfd->ioptions(), mutable_cf_options),
|
||||
@@ -2143,8 +2141,8 @@ Status DBImpl::CompactFiles(
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
|
||||
// This call will unlock/lock the mutex to wait for current running
|
||||
// AddFile() calls to finish.
|
||||
WaitForAddFile();
|
||||
// IngestExternalFile() calls to finish.
|
||||
WaitForIngestFile();
|
||||
|
||||
s = CompactFilesImpl(compact_options, cfd, sv->current,
|
||||
input_file_names, output_level,
|
||||
@@ -2503,8 +2501,14 @@ Status DBImpl::SetDBOptions(
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
|
||||
write_controller_.set_max_delayed_write_rate(new_options.delayed_write_rate);
|
||||
|
||||
mutable_db_options_ = new_options;
|
||||
|
||||
if (total_log_size_ > GetMaxTotalWalSize()) {
|
||||
MaybeFlushColumnFamilies();
|
||||
}
|
||||
|
||||
persist_options_status = PersistOptions();
|
||||
}
|
||||
}
|
||||
@@ -2882,7 +2886,8 @@ Status DBImpl::RunManualCompaction(ColumnFamilyData* cfd, int input_level,
|
||||
}
|
||||
|
||||
InternalIterator* DBImpl::NewInternalIterator(
|
||||
Arena* arena, ColumnFamilyHandle* column_family) {
|
||||
Arena* arena, RangeDelAggregator* range_del_agg,
|
||||
ColumnFamilyHandle* column_family) {
|
||||
ColumnFamilyData* cfd;
|
||||
if (column_family == nullptr) {
|
||||
cfd = default_cf_handle_->cfd();
|
||||
@@ -2895,11 +2900,13 @@ InternalIterator* DBImpl::NewInternalIterator(
|
||||
SuperVersion* super_version = cfd->GetSuperVersion()->Ref();
|
||||
mutex_.Unlock();
|
||||
ReadOptions roptions;
|
||||
return NewInternalIterator(roptions, cfd, super_version, arena);
|
||||
return NewInternalIterator(roptions, cfd, super_version, arena,
|
||||
range_del_agg);
|
||||
}
|
||||
|
||||
Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
|
||||
const FlushOptions& flush_options) {
|
||||
const FlushOptions& flush_options,
|
||||
bool writes_stopped) {
|
||||
Status s;
|
||||
{
|
||||
WriteContext context;
|
||||
@@ -2911,12 +2918,17 @@ Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
|
||||
}
|
||||
|
||||
WriteThread::Writer w;
|
||||
write_thread_.EnterUnbatched(&w, &mutex_);
|
||||
if (!writes_stopped) {
|
||||
write_thread_.EnterUnbatched(&w, &mutex_);
|
||||
}
|
||||
|
||||
// SwitchMemtable() will release and reacquire mutex
|
||||
// during execution
|
||||
s = SwitchMemtable(cfd, &context);
|
||||
write_thread_.ExitUnbatched(&w);
|
||||
|
||||
if (!writes_stopped) {
|
||||
write_thread_.ExitUnbatched(&w);
|
||||
}
|
||||
|
||||
cfd->imm()->FlushRequested();
|
||||
|
||||
@@ -2940,6 +2952,12 @@ Status DBImpl::WaitForFlushMemTable(ColumnFamilyData* cfd) {
|
||||
if (shutting_down_.load(std::memory_order_acquire)) {
|
||||
return Status::ShutdownInProgress();
|
||||
}
|
||||
if (cfd->IsDropped()) {
|
||||
// FlushJob cannot flush a dropped CF, if we did not break here
|
||||
// we will loop forever since cfd->imm()->NumNotFlushed() will never
|
||||
// drop to zero
|
||||
return Status::InvalidArgument("Cannot flush a dropped CF");
|
||||
}
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
if (!bg_error_.ok()) {
|
||||
@@ -3295,8 +3313,8 @@ void DBImpl::BackgroundCallCompaction(void* arg) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
|
||||
// This call will unlock/lock the mutex to wait for current running
|
||||
// AddFile() calls to finish.
|
||||
WaitForAddFile();
|
||||
// IngestExternalFile() calls to finish.
|
||||
WaitForIngestFile();
|
||||
|
||||
num_running_compactions_++;
|
||||
|
||||
@@ -3704,8 +3722,8 @@ void DBImpl::RemoveManualCompaction(DBImpl::ManualCompaction* m) {
|
||||
}
|
||||
|
||||
bool DBImpl::ShouldntRunManualCompaction(ManualCompaction* m) {
|
||||
if (num_running_addfile_ > 0) {
|
||||
// We need to wait for other AddFile() calls to finish
|
||||
if (num_running_ingest_file_ > 0) {
|
||||
// We need to wait for other IngestExternalFile() calls to finish
|
||||
// before running a manual compaction.
|
||||
return true;
|
||||
}
|
||||
@@ -3773,11 +3791,12 @@ bool DBImpl::MCOverlap(ManualCompaction* m, ManualCompaction* m1) {
|
||||
}
|
||||
|
||||
size_t DBImpl::GetWalPreallocateBlockSize(uint64_t write_buffer_size) const {
|
||||
mutex_.AssertHeld();
|
||||
size_t bsize = write_buffer_size / 10 + write_buffer_size;
|
||||
// Some users might set very high write_buffer_size and rely on
|
||||
// max_total_wal_size or other parameters to control the WAL size.
|
||||
if (immutable_db_options_.max_total_wal_size > 0) {
|
||||
bsize = std::min<size_t>(bsize, immutable_db_options_.max_total_wal_size);
|
||||
if (mutable_db_options_.max_total_wal_size > 0) {
|
||||
bsize = std::min<size_t>(bsize, mutable_db_options_.max_total_wal_size);
|
||||
}
|
||||
if (immutable_db_options_.db_write_buffer_size > 0) {
|
||||
bsize = std::min<size_t>(bsize, immutable_db_options_.db_write_buffer_size);
|
||||
@@ -3843,29 +3862,49 @@ static void CleanupIteratorState(void* arg1, void* arg2) {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
InternalIterator* DBImpl::NewInternalIterator(const ReadOptions& read_options,
|
||||
ColumnFamilyData* cfd,
|
||||
SuperVersion* super_version,
|
||||
Arena* arena) {
|
||||
InternalIterator* DBImpl::NewInternalIterator(
|
||||
const ReadOptions& read_options, ColumnFamilyData* cfd,
|
||||
SuperVersion* super_version, Arena* arena,
|
||||
RangeDelAggregator* range_del_agg) {
|
||||
InternalIterator* internal_iter;
|
||||
assert(arena != nullptr);
|
||||
assert(range_del_agg != nullptr);
|
||||
// Need to create internal iterator from the arena.
|
||||
MergeIteratorBuilder merge_iter_builder(&cfd->internal_comparator(), arena);
|
||||
MergeIteratorBuilder merge_iter_builder(
|
||||
&cfd->internal_comparator(), arena,
|
||||
!read_options.total_order_seek &&
|
||||
cfd->ioptions()->prefix_extractor != nullptr);
|
||||
// Collect iterator for mutable mem
|
||||
merge_iter_builder.AddIterator(
|
||||
super_version->mem->NewIterator(read_options, arena));
|
||||
std::unique_ptr<InternalIterator> range_del_iter;
|
||||
Status s;
|
||||
if (!read_options.ignore_range_deletions) {
|
||||
range_del_iter.reset(
|
||||
super_version->mem->NewRangeTombstoneIterator(read_options));
|
||||
s = range_del_agg->AddTombstones(std::move(range_del_iter));
|
||||
}
|
||||
// Collect all needed child iterators for immutable memtables
|
||||
super_version->imm->AddIterators(read_options, &merge_iter_builder);
|
||||
// Collect iterators for files in L0 - Ln
|
||||
super_version->current->AddIterators(read_options, env_options_,
|
||||
&merge_iter_builder);
|
||||
internal_iter = merge_iter_builder.Finish();
|
||||
IterState* cleanup =
|
||||
new IterState(this, &mutex_, super_version,
|
||||
read_options.background_purge_on_iterator_cleanup);
|
||||
internal_iter->RegisterCleanup(CleanupIteratorState, cleanup, nullptr);
|
||||
if (s.ok()) {
|
||||
super_version->imm->AddIterators(read_options, &merge_iter_builder);
|
||||
if (!read_options.ignore_range_deletions) {
|
||||
s = super_version->imm->AddRangeTombstoneIterators(read_options, arena,
|
||||
range_del_agg);
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
// Collect iterators for files in L0 - Ln
|
||||
super_version->current->AddIterators(read_options, env_options_,
|
||||
&merge_iter_builder, range_del_agg);
|
||||
internal_iter = merge_iter_builder.Finish();
|
||||
IterState* cleanup =
|
||||
new IterState(this, &mutex_, super_version,
|
||||
read_options.background_purge_on_iterator_cleanup);
|
||||
internal_iter->RegisterCleanup(CleanupIteratorState, cleanup, nullptr);
|
||||
|
||||
return internal_iter;
|
||||
return internal_iter;
|
||||
}
|
||||
return NewErrorInternalIterator(s);
|
||||
}
|
||||
|
||||
ColumnFamilyHandle* DBImpl::DefaultColumnFamily() const {
|
||||
@@ -3950,6 +3989,7 @@ Status DBImpl::GetImpl(const ReadOptions& read_options,
|
||||
SuperVersion* sv = GetAndRefSuperVersion(cfd);
|
||||
// Prepare to store a list of merge operations if merge occurs.
|
||||
MergeContext merge_context;
|
||||
RangeDelAggregator range_del_agg(cfd->internal_comparator(), snapshot);
|
||||
|
||||
Status s;
|
||||
// First look in the memtable, then in the immutable memtable (if any).
|
||||
@@ -3962,18 +4002,24 @@ Status DBImpl::GetImpl(const ReadOptions& read_options,
|
||||
(read_options.read_tier == kPersistedTier && has_unpersisted_data_);
|
||||
bool done = false;
|
||||
if (!skip_memtable) {
|
||||
if (sv->mem->Get(lkey, value, &s, &merge_context)) {
|
||||
if (sv->mem->Get(lkey, value, &s, &merge_context, &range_del_agg,
|
||||
read_options)) {
|
||||
done = true;
|
||||
RecordTick(stats_, MEMTABLE_HIT);
|
||||
} else if (sv->imm->Get(lkey, value, &s, &merge_context)) {
|
||||
} else if ((s.ok() || s.IsMergeInProgress()) &&
|
||||
sv->imm->Get(lkey, value, &s, &merge_context, &range_del_agg,
|
||||
read_options)) {
|
||||
done = true;
|
||||
RecordTick(stats_, MEMTABLE_HIT);
|
||||
}
|
||||
if (!done && !s.ok() && !s.IsMergeInProgress()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
if (!done) {
|
||||
PERF_TIMER_GUARD(get_from_output_files_time);
|
||||
sv->current->Get(read_options, lkey, value, &s, &merge_context,
|
||||
value_found);
|
||||
&range_del_agg, value_found);
|
||||
RecordTick(stats_, MEMTABLE_MISS);
|
||||
}
|
||||
|
||||
@@ -4051,6 +4097,8 @@ std::vector<Status> DBImpl::MultiGet(
|
||||
|
||||
LookupKey lkey(keys[i], snapshot);
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family[i]);
|
||||
RangeDelAggregator range_del_agg(cfh->cfd()->internal_comparator(),
|
||||
snapshot);
|
||||
auto mgd_iter = multiget_cf_data.find(cfh->cfd()->GetID());
|
||||
assert(mgd_iter != multiget_cf_data.end());
|
||||
auto mgd = mgd_iter->second;
|
||||
@@ -4059,18 +4107,20 @@ std::vector<Status> DBImpl::MultiGet(
|
||||
(read_options.read_tier == kPersistedTier && has_unpersisted_data_);
|
||||
bool done = false;
|
||||
if (!skip_memtable) {
|
||||
if (super_version->mem->Get(lkey, value, &s, &merge_context)) {
|
||||
if (super_version->mem->Get(lkey, value, &s, &merge_context,
|
||||
&range_del_agg, read_options)) {
|
||||
done = true;
|
||||
// TODO(?): RecordTick(stats_, MEMTABLE_HIT)?
|
||||
} else if (super_version->imm->Get(lkey, value, &s, &merge_context)) {
|
||||
} else if (super_version->imm->Get(lkey, value, &s, &merge_context,
|
||||
&range_del_agg, read_options)) {
|
||||
done = true;
|
||||
// TODO(?): RecordTick(stats_, MEMTABLE_HIT)?
|
||||
}
|
||||
}
|
||||
if (!done) {
|
||||
PERF_TIMER_GUARD(get_from_output_files_time);
|
||||
super_version->current->Get(read_options, lkey, value, &s,
|
||||
&merge_context);
|
||||
super_version->current->Get(read_options, lkey, value, &s, &merge_context,
|
||||
&range_del_agg);
|
||||
// TODO(?): RecordTick(stats_, MEMTABLE_MISS)?
|
||||
}
|
||||
|
||||
@@ -4396,7 +4446,8 @@ Iterator* DBImpl::NewIterator(const ReadOptions& read_options,
|
||||
read_options.total_order_seek);
|
||||
|
||||
InternalIterator* internal_iter =
|
||||
NewInternalIterator(read_options, cfd, sv, db_iter->GetArena());
|
||||
NewInternalIterator(read_options, cfd, sv, db_iter->GetArena(),
|
||||
db_iter->GetRangeDelAggregator());
|
||||
db_iter->SetIterUnderDBIter(internal_iter);
|
||||
|
||||
return db_iter;
|
||||
@@ -4469,7 +4520,8 @@ Status DBImpl::NewIterators(
|
||||
sv->mutable_cf_options.max_sequential_skip_in_iterations,
|
||||
sv->version_number, nullptr, false, read_options.pin_data);
|
||||
InternalIterator* internal_iter =
|
||||
NewInternalIterator(read_options, cfd, sv, db_iter->GetArena());
|
||||
NewInternalIterator(read_options, cfd, sv, db_iter->GetArena(),
|
||||
db_iter->GetRangeDelAggregator());
|
||||
db_iter->SetIterUnderDBIter(internal_iter);
|
||||
iterators->push_back(db_iter);
|
||||
}
|
||||
@@ -4610,7 +4662,6 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
if (write_thread_.CompleteParallelWorker(&w)) {
|
||||
// we're responsible for early exit
|
||||
auto last_sequence = w.parallel_group->last_sequence;
|
||||
SetTickerCount(stats_, SEQUENCE_NUMBER, last_sequence);
|
||||
versions_->SetLastSequence(last_sequence);
|
||||
write_thread_.EarlyExitParallelGroup(&w);
|
||||
}
|
||||
@@ -4647,34 +4698,9 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
assert(!single_column_family_mode_ ||
|
||||
versions_->GetColumnFamilySet()->NumberOfColumnFamilies() == 1);
|
||||
|
||||
uint64_t max_total_wal_size = (immutable_db_options_.max_total_wal_size == 0)
|
||||
? 4 * max_total_in_memory_state_
|
||||
: immutable_db_options_.max_total_wal_size;
|
||||
if (UNLIKELY(!single_column_family_mode_ &&
|
||||
alive_log_files_.begin()->getting_flushed == false &&
|
||||
total_log_size_ > max_total_wal_size)) {
|
||||
uint64_t flush_column_family_if_log_file = alive_log_files_.begin()->number;
|
||||
alive_log_files_.begin()->getting_flushed = true;
|
||||
Log(InfoLogLevel::INFO_LEVEL, immutable_db_options_.info_log,
|
||||
"Flushing all column families with data in WAL number %" PRIu64
|
||||
". Total log size is %" PRIu64 " while max_total_wal_size is %" PRIu64,
|
||||
flush_column_family_if_log_file, total_log_size_, max_total_wal_size);
|
||||
// no need to refcount because drop is happening in write thread, so can't
|
||||
// happen while we're in the write thread
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (cfd->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
if (cfd->GetLogNumber() <= flush_column_family_if_log_file) {
|
||||
status = SwitchMemtable(cfd, &context);
|
||||
if (!status.ok()) {
|
||||
break;
|
||||
}
|
||||
cfd->imm()->FlushRequested();
|
||||
SchedulePendingFlush(cfd);
|
||||
}
|
||||
}
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
total_log_size_ > GetMaxTotalWalSize())) {
|
||||
MaybeFlushColumnFamilies();
|
||||
} else if (UNLIKELY(write_buffer_manager_->ShouldFlush())) {
|
||||
// Before a new memtable is added in SwitchMemtable(),
|
||||
// write_buffer_manager_->ShouldFlush() will keep returning true. If another
|
||||
@@ -4731,7 +4757,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
// for previous one. It might create a fairness issue that expiration
|
||||
// might happen for smaller writes but larger writes can go through.
|
||||
// Can optimize it if it is an issue.
|
||||
status = DelayWrite(last_batch_group_size_);
|
||||
status = DelayWrite(last_batch_group_size_, write_options);
|
||||
PERF_TIMER_START(write_pre_and_post_process_time);
|
||||
}
|
||||
|
||||
@@ -4741,6 +4767,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
bool need_log_sync = !write_options.disableWAL && write_options.sync;
|
||||
bool need_log_dir_sync = need_log_sync && !log_dir_synced_;
|
||||
|
||||
bool logs_getting_synced = false;
|
||||
if (status.ok()) {
|
||||
if (need_log_sync) {
|
||||
while (logs_.front().getting_synced) {
|
||||
@@ -4750,6 +4777,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
assert(!log.getting_synced);
|
||||
log.getting_synced = true;
|
||||
}
|
||||
logs_getting_synced = true;
|
||||
}
|
||||
|
||||
// Add to log and apply to memtable. We can release the lock
|
||||
@@ -4946,7 +4974,6 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
}
|
||||
|
||||
if (!exit_completed_early && w.status.ok()) {
|
||||
SetTickerCount(stats_, SEQUENCE_NUMBER, last_sequence);
|
||||
versions_->SetLastSequence(last_sequence);
|
||||
if (!need_log_sync) {
|
||||
write_thread_.ExitAsBatchGroupLeader(&w, last_writer, w.status);
|
||||
@@ -4970,7 +4997,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
PERF_TIMER_START(write_pre_and_post_process_time);
|
||||
|
||||
if (immutable_db_options_.paranoid_checks && !status.ok() &&
|
||||
!w.CallbackFailed() && !status.IsBusy()) {
|
||||
!w.CallbackFailed() && !status.IsBusy() && !status.IsIncomplete()) {
|
||||
mutex_.Lock();
|
||||
if (bg_error_.ok()) {
|
||||
bg_error_ = status; // stop compaction & fail any further writes
|
||||
@@ -4978,7 +5005,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
mutex_.Unlock();
|
||||
}
|
||||
|
||||
if (need_log_sync) {
|
||||
if (logs_getting_synced) {
|
||||
mutex_.Lock();
|
||||
MarkLogsSynced(logfile_number_, need_log_dir_sync, status);
|
||||
mutex_.Unlock();
|
||||
@@ -4991,15 +5018,91 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
return status;
|
||||
}
|
||||
|
||||
void DBImpl::MaybeFlushColumnFamilies() {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
if (alive_log_files_.begin()->getting_flushed) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto oldest_alive_log = alive_log_files_.begin()->number;
|
||||
auto oldest_log_with_uncommited_prep = FindMinLogContainingOutstandingPrep();
|
||||
|
||||
if (allow_2pc() &&
|
||||
unable_to_flush_oldest_log_ &&
|
||||
oldest_log_with_uncommited_prep > 0 &&
|
||||
oldest_log_with_uncommited_prep <= oldest_alive_log) {
|
||||
// we already attempted to flush all column families dependent on
|
||||
// the oldest alive log but the log still contained uncommited transactions.
|
||||
// the oldest alive log STILL contains uncommited transaction so there
|
||||
// is still nothing that we can do.
|
||||
return;
|
||||
}
|
||||
|
||||
WriteContext context;
|
||||
|
||||
Log(InfoLogLevel::INFO_LEVEL, immutable_db_options_.info_log,
|
||||
"Flushing all column families with data in WAL number %" PRIu64
|
||||
". Total log size is %" PRIu64 " while max_total_wal_size is %" PRIu64,
|
||||
oldest_alive_log, total_log_size_, GetMaxTotalWalSize());
|
||||
// no need to refcount because drop is happening in write thread, so can't
|
||||
// happen while we're in the write thread
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (cfd->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
if (cfd->OldestLogToKeep() <= oldest_alive_log) {
|
||||
auto status = SwitchMemtable(cfd, &context);
|
||||
if (!status.ok()) {
|
||||
break;
|
||||
}
|
||||
cfd->imm()->FlushRequested();
|
||||
SchedulePendingFlush(cfd);
|
||||
}
|
||||
}
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
|
||||
// we only mark this log as getting flushed if we have successfully
|
||||
// flushed all data in this log. If this log contains outstanding prepred
|
||||
// transactions then we cannot flush this log until those transactions are commited.
|
||||
|
||||
unable_to_flush_oldest_log_ = false;
|
||||
|
||||
if (allow_2pc()) {
|
||||
if (oldest_log_with_uncommited_prep == 0 ||
|
||||
oldest_log_with_uncommited_prep > oldest_alive_log) {
|
||||
// this log contains no outstanding prepared transactions
|
||||
alive_log_files_.begin()->getting_flushed = true;
|
||||
} else {
|
||||
Log(InfoLogLevel::WARN_LEVEL, immutable_db_options_.info_log,
|
||||
"Unable to release oldest log due to uncommited transaction");
|
||||
unable_to_flush_oldest_log_ = true;
|
||||
}
|
||||
} else {
|
||||
alive_log_files_.begin()->getting_flushed = true;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t DBImpl::GetMaxTotalWalSize() const {
|
||||
mutex_.AssertHeld();
|
||||
return mutable_db_options_.max_total_wal_size == 0
|
||||
? 4 * max_total_in_memory_state_
|
||||
: mutable_db_options_.max_total_wal_size;
|
||||
}
|
||||
|
||||
// REQUIRES: mutex_ is held
|
||||
// REQUIRES: this thread is currently at the front of the writer queue
|
||||
Status DBImpl::DelayWrite(uint64_t num_bytes) {
|
||||
Status DBImpl::DelayWrite(uint64_t num_bytes,
|
||||
const WriteOptions& write_options) {
|
||||
uint64_t time_delayed = 0;
|
||||
bool delayed = false;
|
||||
{
|
||||
StopWatch sw(env_, stats_, WRITE_STALL, &time_delayed);
|
||||
auto delay = write_controller_.GetDelay(env_, num_bytes);
|
||||
if (delay > 0) {
|
||||
if (write_options.no_slowdown) {
|
||||
return Status::Incomplete();
|
||||
}
|
||||
mutex_.Unlock();
|
||||
delayed = true;
|
||||
TEST_SYNC_POINT("DBImpl::DelayWrite:Sleep");
|
||||
@@ -5009,11 +5112,15 @@ Status DBImpl::DelayWrite(uint64_t num_bytes) {
|
||||
}
|
||||
|
||||
while (bg_error_.ok() && write_controller_.IsStopped()) {
|
||||
if (write_options.no_slowdown) {
|
||||
return Status::Incomplete();
|
||||
}
|
||||
delayed = true;
|
||||
TEST_SYNC_POINT("DBImpl::DelayWrite:Wait");
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
}
|
||||
assert(!delayed || !write_options.no_slowdown);
|
||||
if (delayed) {
|
||||
default_cf_internal_stats_->AddDBStats(InternalStats::WRITE_STALL_MICROS,
|
||||
time_delayed);
|
||||
@@ -5090,6 +5197,8 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
int num_imm_unflushed = cfd->imm()->NumNotFlushed();
|
||||
DBOptions db_options =
|
||||
BuildDBOptions(immutable_db_options_, mutable_db_options_);
|
||||
const auto preallocate_block_size =
|
||||
GetWalPreallocateBlockSize(mutable_cf_options.write_buffer_size);
|
||||
mutex_.Unlock();
|
||||
Status s;
|
||||
{
|
||||
@@ -5111,8 +5220,10 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
if (s.ok()) {
|
||||
// Our final size should be less than write_buffer_size
|
||||
// (compression, etc) but err on the side of caution.
|
||||
lfile->SetPreallocationBlockSize(
|
||||
GetWalPreallocateBlockSize(mutable_cf_options.write_buffer_size));
|
||||
|
||||
// use preallocate_block_size instead
|
||||
// of calling GetWalPreallocateBlockSize()
|
||||
lfile->SetPreallocationBlockSize(preallocate_block_size);
|
||||
unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(lfile), opt_env_opt));
|
||||
new_log =
|
||||
@@ -5265,6 +5376,24 @@ bool DBImpl::GetProperty(ColumnFamilyHandle* column_family,
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DBImpl::GetMapProperty(ColumnFamilyHandle* column_family,
|
||||
const Slice& property,
|
||||
std::map<std::string, double>* value) {
|
||||
const DBPropertyInfo* property_info = GetPropertyInfo(property);
|
||||
value->clear();
|
||||
auto cfd = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family)->cfd();
|
||||
if (property_info == nullptr) {
|
||||
return false;
|
||||
} else if (property_info->handle_map) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
return cfd->internal_stats()->GetMapProperty(*property_info, property,
|
||||
value);
|
||||
}
|
||||
// If we reach this point it means that handle_map is not provided for the
|
||||
// requested property
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DBImpl::GetIntProperty(ColumnFamilyHandle* column_family,
|
||||
const Slice& property, uint64_t* value) {
|
||||
const DBPropertyInfo* property_info = GetPropertyInfo(property);
|
||||
@@ -5773,6 +5902,14 @@ Status DB::SingleDelete(const WriteOptions& opt,
|
||||
return Write(opt, &batch);
|
||||
}
|
||||
|
||||
Status DB::DeleteRange(const WriteOptions& opt,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice& begin_key, const Slice& end_key) {
|
||||
WriteBatch batch;
|
||||
batch.DeleteRange(column_family, begin_key, end_key);
|
||||
return Write(opt, &batch);
|
||||
}
|
||||
|
||||
Status DB::Merge(const WriteOptions& opt, ColumnFamilyHandle* column_family,
|
||||
const Slice& key, const Slice& value) {
|
||||
WriteBatch batch;
|
||||
@@ -6282,7 +6419,10 @@ Status DBImpl::GetLatestSequenceForKey(SuperVersion* sv, const Slice& key,
|
||||
bool* found_record_for_key) {
|
||||
Status s;
|
||||
MergeContext merge_context;
|
||||
RangeDelAggregator range_del_agg(sv->mem->GetInternalKeyComparator(),
|
||||
kMaxSequenceNumber);
|
||||
|
||||
ReadOptions read_options;
|
||||
SequenceNumber current_seq = versions_->LastSequence();
|
||||
LookupKey lkey(key, current_seq);
|
||||
|
||||
@@ -6290,7 +6430,8 @@ Status DBImpl::GetLatestSequenceForKey(SuperVersion* sv, const Slice& key,
|
||||
*found_record_for_key = false;
|
||||
|
||||
// Check if there is a record for this key in the latest memtable
|
||||
sv->mem->Get(lkey, nullptr, &s, &merge_context, seq);
|
||||
sv->mem->Get(lkey, nullptr, &s, &merge_context, &range_del_agg, seq,
|
||||
read_options);
|
||||
|
||||
if (!(s.ok() || s.IsNotFound() || s.IsMergeInProgress())) {
|
||||
// unexpected error reading memtable.
|
||||
@@ -6308,7 +6449,8 @@ Status DBImpl::GetLatestSequenceForKey(SuperVersion* sv, const Slice& key,
|
||||
}
|
||||
|
||||
// Check if there is a record for this key in the immutable memtables
|
||||
sv->imm->Get(lkey, nullptr, &s, &merge_context, seq);
|
||||
sv->imm->Get(lkey, nullptr, &s, &merge_context, &range_del_agg, seq,
|
||||
read_options);
|
||||
|
||||
if (!(s.ok() || s.IsNotFound() || s.IsMergeInProgress())) {
|
||||
// unexpected error reading memtable.
|
||||
@@ -6326,7 +6468,8 @@ Status DBImpl::GetLatestSequenceForKey(SuperVersion* sv, const Slice& key,
|
||||
}
|
||||
|
||||
// Check if there is a record for this key in the immutable memtables
|
||||
sv->imm->GetFromHistory(lkey, nullptr, &s, &merge_context, seq);
|
||||
sv->imm->GetFromHistory(lkey, nullptr, &s, &merge_context, &range_del_agg,
|
||||
seq, read_options);
|
||||
|
||||
if (!(s.ok() || s.IsNotFound() || s.IsMergeInProgress())) {
|
||||
// unexpected error reading memtable.
|
||||
@@ -6347,10 +6490,9 @@ Status DBImpl::GetLatestSequenceForKey(SuperVersion* sv, const Slice& key,
|
||||
// SST files if cache_only=true?
|
||||
if (!cache_only) {
|
||||
// Check tables
|
||||
ReadOptions read_options;
|
||||
|
||||
sv->current->Get(read_options, lkey, nullptr, &s, &merge_context,
|
||||
nullptr /* value_found */, found_record_for_key, seq);
|
||||
&range_del_agg, nullptr /* value_found */,
|
||||
found_record_for_key, seq);
|
||||
|
||||
if (!(s.ok() || s.IsNotFound() || s.IsMergeInProgress())) {
|
||||
// unexpected error reading SST files
|
||||
@@ -6364,6 +6506,136 @@ Status DBImpl::GetLatestSequenceForKey(SuperVersion* sv, const Slice& key,
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DBImpl::IngestExternalFile(
|
||||
ColumnFamilyHandle* column_family,
|
||||
const std::vector<std::string>& external_files,
|
||||
const IngestExternalFileOptions& ingestion_options) {
|
||||
Status status;
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
auto cfd = cfh->cfd();
|
||||
|
||||
ExternalSstFileIngestionJob ingestion_job(env_, versions_.get(), cfd,
|
||||
immutable_db_options_, env_options_,
|
||||
&snapshots_, ingestion_options);
|
||||
|
||||
// Make sure that bg cleanup wont delete the files that we are ingesting
|
||||
std::list<uint64_t>::iterator pending_output_elem;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
pending_output_elem = CaptureCurrentFileNumberInPendingOutputs();
|
||||
}
|
||||
|
||||
status = ingestion_job.Prepare(external_files);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT("DBImpl::AddFile:Start");
|
||||
{
|
||||
// Lock db mutex
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
TEST_SYNC_POINT("DBImpl::AddFile:MutexLock");
|
||||
|
||||
// Stop writes to the DB
|
||||
WriteThread::Writer w;
|
||||
write_thread_.EnterUnbatched(&w, &mutex_);
|
||||
|
||||
num_running_ingest_file_++;
|
||||
|
||||
// We cannot ingest a file into a dropped CF
|
||||
if (cfd->IsDropped()) {
|
||||
status = Status::InvalidArgument(
|
||||
"Cannot ingest an external file into a dropped CF");
|
||||
}
|
||||
|
||||
// Figure out if we need to flush the memtable first
|
||||
if (status.ok()) {
|
||||
bool need_flush = false;
|
||||
status = ingestion_job.NeedsFlush(&need_flush);
|
||||
|
||||
if (status.ok() && need_flush) {
|
||||
mutex_.Unlock();
|
||||
status = FlushMemTable(cfd, FlushOptions(), true /* writes_stopped */);
|
||||
mutex_.Lock();
|
||||
}
|
||||
}
|
||||
|
||||
// Run the ingestion job
|
||||
if (status.ok()) {
|
||||
status = ingestion_job.Run();
|
||||
}
|
||||
|
||||
// Install job edit [Mutex will be unlocked here]
|
||||
auto mutable_cf_options = cfd->GetLatestMutableCFOptions();
|
||||
if (status.ok()) {
|
||||
status =
|
||||
versions_->LogAndApply(cfd, *mutable_cf_options, ingestion_job.edit(),
|
||||
&mutex_, directories_.GetDbDir());
|
||||
}
|
||||
if (status.ok()) {
|
||||
delete InstallSuperVersionAndScheduleWork(cfd, nullptr,
|
||||
*mutable_cf_options);
|
||||
}
|
||||
|
||||
// Resume writes to the DB
|
||||
write_thread_.ExitUnbatched(&w);
|
||||
|
||||
// Update stats
|
||||
if (status.ok()) {
|
||||
ingestion_job.UpdateStats();
|
||||
}
|
||||
|
||||
ReleaseFileNumberFromPendingOutputs(pending_output_elem);
|
||||
|
||||
num_running_ingest_file_--;
|
||||
if (num_running_ingest_file_ == 0) {
|
||||
bg_cv_.SignalAll();
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT("DBImpl::AddFile:MutexUnlock");
|
||||
}
|
||||
// mutex_ is unlocked here
|
||||
|
||||
// Cleanup
|
||||
ingestion_job.Cleanup(status);
|
||||
|
||||
if (status.ok()) {
|
||||
NotifyOnExternalFileIngested(cfd, ingestion_job);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void DBImpl::NotifyOnExternalFileIngested(
|
||||
ColumnFamilyData* cfd, const ExternalSstFileIngestionJob& ingestion_job) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (immutable_db_options_.listeners.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const IngestedFileInfo& f : ingestion_job.files_to_ingest()) {
|
||||
ExternalFileIngestionInfo info;
|
||||
info.cf_name = cfd->GetName();
|
||||
info.external_file_path = f.external_file_path;
|
||||
info.internal_file_path = f.internal_file_path;
|
||||
info.global_seqno = f.assigned_seqno;
|
||||
info.table_properties = f.table_properties;
|
||||
for (auto listener : immutable_db_options_.listeners) {
|
||||
listener->OnExternalFileIngested(this, info);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void DBImpl::WaitForIngestFile() {
|
||||
mutex_.AssertHeld();
|
||||
while (num_running_ingest_file_ > 0) {
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
||||
+54
-28
@@ -13,6 +13,7 @@
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <string>
|
||||
@@ -22,6 +23,7 @@
|
||||
#include "db/column_family.h"
|
||||
#include "db/compaction_job.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/external_sst_file_ingestion_job.h"
|
||||
#include "db/flush_job.h"
|
||||
#include "db/flush_scheduler.h"
|
||||
#include "db/internal_stats.h"
|
||||
@@ -123,6 +125,10 @@ class DBImpl : public DB {
|
||||
using DB::GetProperty;
|
||||
virtual bool GetProperty(ColumnFamilyHandle* column_family,
|
||||
const Slice& property, std::string* value) override;
|
||||
using DB::GetMapProperty;
|
||||
virtual bool GetMapProperty(ColumnFamilyHandle* column_family,
|
||||
const Slice& property,
|
||||
std::map<std::string, double>* value) override;
|
||||
using DB::GetIntProperty;
|
||||
virtual bool GetIntProperty(ColumnFamilyHandle* column_family,
|
||||
const Slice& property, uint64_t* value) override;
|
||||
@@ -260,13 +266,11 @@ class DBImpl : public DB {
|
||||
bool cache_only, SequenceNumber* seq,
|
||||
bool* found_record_for_key);
|
||||
|
||||
using DB::AddFile;
|
||||
virtual Status AddFile(ColumnFamilyHandle* column_family,
|
||||
const std::vector<ExternalSstFileInfo>& file_info_list,
|
||||
bool move_file, bool skip_snapshot_check) override;
|
||||
virtual Status AddFile(ColumnFamilyHandle* column_family,
|
||||
const std::vector<std::string>& file_path_list,
|
||||
bool move_file, bool skip_snapshot_check) override;
|
||||
using DB::IngestExternalFile;
|
||||
virtual Status IngestExternalFile(
|
||||
ColumnFamilyHandle* column_family,
|
||||
const std::vector<std::string>& external_files,
|
||||
const IngestExternalFileOptions& ingestion_options) override;
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
@@ -292,7 +296,8 @@ class DBImpl : public DB {
|
||||
// The keys of this iterator are internal keys (see format.h).
|
||||
// The returned iterator should be deleted when no longer needed.
|
||||
InternalIterator* NewInternalIterator(
|
||||
Arena* arena, ColumnFamilyHandle* column_family = nullptr);
|
||||
Arena* arena, RangeDelAggregator* range_del_agg,
|
||||
ColumnFamilyHandle* column_family = nullptr);
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Extra methods (for testing) that are not in the public DB interface
|
||||
@@ -303,6 +308,16 @@ class DBImpl : public DB {
|
||||
ColumnFamilyHandle* column_family = nullptr,
|
||||
bool disallow_trivial_move = false);
|
||||
|
||||
void TEST_MaybeFlushColumnFamilies();
|
||||
|
||||
bool TEST_UnableToFlushOldestLog() {
|
||||
return unable_to_flush_oldest_log_;
|
||||
}
|
||||
|
||||
bool TEST_IsLogGettingFlushed() {
|
||||
return alive_log_files_.begin()->getting_flushed;
|
||||
}
|
||||
|
||||
// Force current memtable contents to be flushed.
|
||||
Status TEST_FlushMemTable(bool wait = true,
|
||||
ColumnFamilyHandle* cfh = nullptr);
|
||||
@@ -375,6 +390,8 @@ class DBImpl : public DB {
|
||||
// schedule a purge
|
||||
void ScheduleBgLogWriterClose(JobContext* job_context);
|
||||
|
||||
uint64_t MinLogNumberToKeep();
|
||||
|
||||
// Returns the list of live files in 'live' and the list
|
||||
// of all files in the filesystem in 'candidate_files'.
|
||||
// If force == false and the last call was less than
|
||||
@@ -527,7 +544,8 @@ class DBImpl : public DB {
|
||||
InternalIterator* NewInternalIterator(const ReadOptions&,
|
||||
ColumnFamilyData* cfd,
|
||||
SuperVersion* super_version,
|
||||
Arena* arena);
|
||||
Arena* arena,
|
||||
RangeDelAggregator* range_del_agg);
|
||||
|
||||
// Except in DB::Open(), WriteOptionsFile can only be called when:
|
||||
// 1. WriteThread::Writer::EnterUnbatched() is used.
|
||||
@@ -551,6 +569,9 @@ class DBImpl : public DB {
|
||||
void NotifyOnMemTableSealed(ColumnFamilyData* cfd,
|
||||
const MemTableInfo& mem_table_info);
|
||||
|
||||
void NotifyOnExternalFileIngested(
|
||||
ColumnFamilyData* cfd, const ExternalSstFileIngestionJob& ingestion_job);
|
||||
|
||||
void NewThreadStatusCfInfo(ColumnFamilyData* cfd) const;
|
||||
|
||||
void EraseThreadStatusCfInfo(ColumnFamilyData* cfd) const;
|
||||
@@ -643,27 +664,20 @@ class DBImpl : public DB {
|
||||
|
||||
// num_bytes: for slowdown case, delay time is calculated based on
|
||||
// `num_bytes` going through.
|
||||
Status DelayWrite(uint64_t num_bytes);
|
||||
Status DelayWrite(uint64_t num_bytes, const WriteOptions& write_options);
|
||||
|
||||
Status ScheduleFlushes(WriteContext* context);
|
||||
|
||||
Status SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context);
|
||||
|
||||
// Force current memtable contents to be flushed.
|
||||
Status FlushMemTable(ColumnFamilyData* cfd, const FlushOptions& options);
|
||||
Status FlushMemTable(ColumnFamilyData* cfd, const FlushOptions& options,
|
||||
bool writes_stopped = false);
|
||||
|
||||
// Wait for memtable flushed
|
||||
Status WaitForFlushMemTable(ColumnFamilyData* cfd);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Finds the lowest level in the DB that the ingested file can be added to
|
||||
// REQUIRES: mutex_ held
|
||||
int PickLevelForIngestedFile(ColumnFamilyData* cfd,
|
||||
const ExternalSstFileInfo& file_info);
|
||||
|
||||
// Wait for current AddFile() calls to finish.
|
||||
// REQUIRES: mutex_ held
|
||||
void WaitForAddFile();
|
||||
|
||||
Status CompactFilesImpl(const CompactionOptions& compact_options,
|
||||
ColumnFamilyData* cfd, Version* version,
|
||||
@@ -671,14 +685,14 @@ class DBImpl : public DB {
|
||||
const int output_level, int output_path_id,
|
||||
JobContext* job_context, LogBuffer* log_buffer);
|
||||
|
||||
Status ReadExternalSstFileInfo(ColumnFamilyHandle* column_family,
|
||||
const std::string& file_path,
|
||||
ExternalSstFileInfo* file_info);
|
||||
// Wait for current IngestExternalFile() calls to finish.
|
||||
// REQUIRES: mutex_ held
|
||||
void WaitForIngestFile();
|
||||
|
||||
#else
|
||||
// AddFile is not supported in ROCKSDB_LITE so this function
|
||||
// IngestExternalFile is not supported in ROCKSDB_LITE so this function
|
||||
// will be no-op
|
||||
void WaitForAddFile() {}
|
||||
void WaitForIngestFile() {}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
ColumnFamilyData* GetColumnFamilyDataByName(const std::string& cf_name);
|
||||
@@ -730,6 +744,10 @@ class DBImpl : public DB {
|
||||
// REQUIRES: mutex locked
|
||||
Status PersistOptions();
|
||||
|
||||
void MaybeFlushColumnFamilies();
|
||||
|
||||
uint64_t GetMaxTotalWalSize() const;
|
||||
|
||||
// table_cache_ provides its own synchronization
|
||||
std::shared_ptr<Cache> table_cache_;
|
||||
|
||||
@@ -752,7 +770,7 @@ class DBImpl : public DB {
|
||||
// * whenever bg_flush_scheduled_ or bg_purge_scheduled_ value decreases
|
||||
// (i.e. whenever a flush is done, even if it didn't make any progress)
|
||||
// * whenever there is an error in background purge, flush or compaction
|
||||
// * whenever num_running_addfile_ goes to 0.
|
||||
// * whenever num_running_ingest_file_ goes to 0.
|
||||
InstrumentedCondVar bg_cv_;
|
||||
uint64_t logfile_number_;
|
||||
std::deque<uint64_t>
|
||||
@@ -985,6 +1003,15 @@ class DBImpl : public DB {
|
||||
// Used when disableWAL is true.
|
||||
bool has_unpersisted_data_;
|
||||
|
||||
|
||||
// if an attempt was made to flush all column families that
|
||||
// the oldest log depends on but uncommited data in the oldest
|
||||
// log prevents the log from being released.
|
||||
// We must attempt to free the dependent memtables again
|
||||
// at a later time after the transaction in the oldest
|
||||
// log is fully commited.
|
||||
bool unable_to_flush_oldest_log_;
|
||||
|
||||
static const int KEEP_LOG_FILE_NUM = 1000;
|
||||
// MSVC version 1800 still does not have constexpr for ::max()
|
||||
static const uint64_t kNoTimeOut = port::kMaxUint64;
|
||||
@@ -994,9 +1021,9 @@ class DBImpl : public DB {
|
||||
// The options to access storage files
|
||||
const EnvOptions env_options_;
|
||||
|
||||
// Number of running AddFile() calls.
|
||||
// Number of running IngestExternalFile() calls.
|
||||
// REQUIRES: mutex held
|
||||
int num_running_addfile_;
|
||||
int num_running_ingest_file_;
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
WalManager wal_manager_;
|
||||
@@ -1098,7 +1125,6 @@ class DBImpl : public DB {
|
||||
};
|
||||
|
||||
extern Options SanitizeOptions(const std::string& db,
|
||||
const InternalKeyComparator* icmp,
|
||||
const Options& src);
|
||||
|
||||
extern DBOptions SanitizeOptions(const std::string& db, const DBOptions& src);
|
||||
|
||||
@@ -1,430 +0,0 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#include "db/db_impl.h"
|
||||
|
||||
#ifndef __STDC_FORMAT_MACROS
|
||||
#define __STDC_FORMAT_MACROS
|
||||
#endif
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/sst_file_writer.h"
|
||||
|
||||
#include "db/builder.h"
|
||||
#include "table/sst_file_writer_collectors.h"
|
||||
#include "table/table_builder.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
#include "util/file_util.h"
|
||||
#include "util/sync_point.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
Status DBImpl::ReadExternalSstFileInfo(ColumnFamilyHandle* column_family,
|
||||
const std::string& file_path,
|
||||
ExternalSstFileInfo* file_info) {
|
||||
Status status;
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
auto cfd = cfh->cfd();
|
||||
|
||||
file_info->file_path = file_path;
|
||||
status = env_->GetFileSize(file_path, &file_info->file_size);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Access the file using TableReader to extract
|
||||
// version, number of entries, smallest user key, largest user key
|
||||
std::unique_ptr<RandomAccessFile> sst_file;
|
||||
status = env_->NewRandomAccessFile(file_path, &sst_file, env_options_);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
std::unique_ptr<RandomAccessFileReader> sst_file_reader;
|
||||
sst_file_reader.reset(new RandomAccessFileReader(std::move(sst_file)));
|
||||
|
||||
std::unique_ptr<TableReader> table_reader;
|
||||
status = cfd->ioptions()->table_factory->NewTableReader(
|
||||
TableReaderOptions(*cfd->ioptions(), env_options_,
|
||||
cfd->internal_comparator()),
|
||||
std::move(sst_file_reader), file_info->file_size, &table_reader);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Get the external sst file version from table properties
|
||||
const UserCollectedProperties& user_collected_properties =
|
||||
table_reader->GetTableProperties()->user_collected_properties;
|
||||
UserCollectedProperties::const_iterator external_sst_file_version_iter =
|
||||
user_collected_properties.find(ExternalSstFilePropertyNames::kVersion);
|
||||
if (external_sst_file_version_iter == user_collected_properties.end()) {
|
||||
return Status::InvalidArgument("Generated table version not found");
|
||||
}
|
||||
|
||||
file_info->version =
|
||||
DecodeFixed32(external_sst_file_version_iter->second.c_str());
|
||||
if (file_info->version == 2) {
|
||||
// version 2 imply that we have global sequence number
|
||||
|
||||
// TODO(tec): Implement version 2 ingestion
|
||||
file_info->sequence_number = 0;
|
||||
} else if (file_info->version == 1) {
|
||||
// version 1 imply that all sequence numbers in table equal 0
|
||||
file_info->sequence_number = 0;
|
||||
} else {
|
||||
return Status::InvalidArgument("Generated table version is not supported");
|
||||
}
|
||||
// Get number of entries in table
|
||||
file_info->num_entries = table_reader->GetTableProperties()->num_entries;
|
||||
|
||||
ParsedInternalKey key;
|
||||
std::unique_ptr<InternalIterator> iter(
|
||||
table_reader->NewIterator(ReadOptions()));
|
||||
|
||||
// Get first (smallest) key from file
|
||||
iter->SeekToFirst();
|
||||
if (!ParseInternalKey(iter->key(), &key)) {
|
||||
return Status::Corruption("Generated table have corrupted keys");
|
||||
}
|
||||
if (key.sequence != 0) {
|
||||
return Status::Corruption("Generated table have non zero sequence number");
|
||||
}
|
||||
file_info->smallest_key = key.user_key.ToString();
|
||||
|
||||
// Get last (largest) key from file
|
||||
iter->SeekToLast();
|
||||
if (!ParseInternalKey(iter->key(), &key)) {
|
||||
return Status::Corruption("Generated table have corrupted keys");
|
||||
}
|
||||
if (key.sequence != 0) {
|
||||
return Status::Corruption("Generated table have non zero sequence number");
|
||||
}
|
||||
file_info->largest_key = key.user_key.ToString();
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DBImpl::AddFile(ColumnFamilyHandle* column_family,
|
||||
const std::vector<std::string>& file_path_list,
|
||||
bool move_file, bool skip_snapshot_check) {
|
||||
Status status;
|
||||
auto num_files = file_path_list.size();
|
||||
if (num_files == 0) {
|
||||
return Status::InvalidArgument("The list of files is empty");
|
||||
}
|
||||
|
||||
std::vector<ExternalSstFileInfo> file_info_list(num_files);
|
||||
for (size_t i = 0; i < num_files; i++) {
|
||||
status = ReadExternalSstFileInfo(column_family, file_path_list[i],
|
||||
&file_info_list[i]);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return AddFile(column_family, file_info_list, move_file, skip_snapshot_check);
|
||||
}
|
||||
|
||||
Status DBImpl::AddFile(ColumnFamilyHandle* column_family,
|
||||
const std::vector<ExternalSstFileInfo>& file_info_list,
|
||||
bool move_file, bool skip_snapshot_check) {
|
||||
Status status;
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
ColumnFamilyData* cfd = cfh->cfd();
|
||||
const Comparator* user_cmp = cfd->internal_comparator().user_comparator();
|
||||
|
||||
auto num_files = file_info_list.size();
|
||||
if (num_files == 0) {
|
||||
return Status::InvalidArgument("The list of files is empty");
|
||||
}
|
||||
|
||||
// Verify that passed files dont have overlapping ranges
|
||||
if (num_files > 1) {
|
||||
std::vector<const ExternalSstFileInfo*> sorted_file_info_list(num_files);
|
||||
for (size_t i = 0; i < num_files; i++) {
|
||||
sorted_file_info_list[i] = &file_info_list[i];
|
||||
}
|
||||
|
||||
std::sort(sorted_file_info_list.begin(), sorted_file_info_list.end(),
|
||||
[&user_cmp, &file_info_list](const ExternalSstFileInfo* info1,
|
||||
const ExternalSstFileInfo* info2) {
|
||||
return user_cmp->Compare(info1->smallest_key,
|
||||
info2->smallest_key) < 0;
|
||||
});
|
||||
|
||||
for (size_t i = 0; i < num_files - 1; i++) {
|
||||
if (user_cmp->Compare(sorted_file_info_list[i]->largest_key,
|
||||
sorted_file_info_list[i + 1]->smallest_key) >= 0) {
|
||||
return Status::NotSupported("Files have overlapping ranges");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint64_t> micro_list(num_files, 0);
|
||||
std::vector<FileMetaData> meta_list(num_files);
|
||||
for (size_t i = 0; i < num_files; i++) {
|
||||
StopWatch sw(env_, nullptr, 0, µ_list[i], false);
|
||||
if (file_info_list[i].num_entries == 0) {
|
||||
return Status::InvalidArgument("File contain no entries");
|
||||
}
|
||||
|
||||
if (file_info_list[i].version == 2) {
|
||||
// version 2 imply that file have only Put Operations
|
||||
// with global Sequence Number
|
||||
|
||||
// TODO(tec): Implement changing file global sequence number
|
||||
} else if (file_info_list[i].version == 1) {
|
||||
// version 1 imply that file have only Put Operations
|
||||
// with Sequence Number = 0
|
||||
} else {
|
||||
// Unknown version !
|
||||
return Status::InvalidArgument(
|
||||
"Generated table version is not supported");
|
||||
}
|
||||
|
||||
meta_list[i].smallest =
|
||||
InternalKey(file_info_list[i].smallest_key,
|
||||
file_info_list[i].sequence_number, ValueType::kTypeValue);
|
||||
meta_list[i].largest =
|
||||
InternalKey(file_info_list[i].largest_key,
|
||||
file_info_list[i].sequence_number, ValueType::kTypeValue);
|
||||
if (!meta_list[i].smallest.Valid() || !meta_list[i].largest.Valid()) {
|
||||
return Status::Corruption("Generated table have corrupted keys");
|
||||
}
|
||||
meta_list[i].smallest_seqno = file_info_list[i].sequence_number;
|
||||
meta_list[i].largest_seqno = file_info_list[i].sequence_number;
|
||||
if (meta_list[i].smallest_seqno != 0 || meta_list[i].largest_seqno != 0) {
|
||||
return Status::InvalidArgument(
|
||||
"Non zero sequence numbers are not supported");
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::list<uint64_t>::iterator> pending_outputs_inserted_elem_list(
|
||||
num_files);
|
||||
// Generate locations for the new tables
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
for (size_t i = 0; i < num_files; i++) {
|
||||
StopWatch sw(env_, nullptr, 0, µ_list[i], false);
|
||||
pending_outputs_inserted_elem_list[i] =
|
||||
CaptureCurrentFileNumberInPendingOutputs();
|
||||
meta_list[i].fd = FileDescriptor(versions_->NewFileNumber(), 0,
|
||||
file_info_list[i].file_size);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy/Move external files into DB
|
||||
std::vector<std::string> db_fname_list(num_files);
|
||||
size_t j = 0;
|
||||
for (; j < num_files; j++) {
|
||||
StopWatch sw(env_, nullptr, 0, µ_list[j], false);
|
||||
db_fname_list[j] =
|
||||
TableFileName(immutable_db_options_.db_paths,
|
||||
meta_list[j].fd.GetNumber(), meta_list[j].fd.GetPathId());
|
||||
if (move_file) {
|
||||
status = env_->LinkFile(file_info_list[j].file_path, db_fname_list[j]);
|
||||
if (status.IsNotSupported()) {
|
||||
// Original file is on a different FS, use copy instead of hard linking
|
||||
status =
|
||||
CopyFile(env_, file_info_list[j].file_path, db_fname_list[j], 0);
|
||||
}
|
||||
} else {
|
||||
status = CopyFile(env_, file_info_list[j].file_path, db_fname_list[j], 0);
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::AddFile:FileCopied");
|
||||
if (!status.ok()) {
|
||||
for (size_t i = 0; i < j; i++) {
|
||||
Status s = env_->DeleteFile(db_fname_list[i]);
|
||||
if (!s.ok()) {
|
||||
Log(InfoLogLevel::WARN_LEVEL, immutable_db_options_.info_log,
|
||||
"AddFile() clean up for file %s failed : %s",
|
||||
db_fname_list[i].c_str(), s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
TEST_SYNC_POINT("DBImpl::AddFile:MutexLock");
|
||||
|
||||
const MutableCFOptions mutable_cf_options =
|
||||
*cfd->GetLatestMutableCFOptions();
|
||||
|
||||
WriteThread::Writer w;
|
||||
write_thread_.EnterUnbatched(&w, &mutex_);
|
||||
|
||||
num_running_addfile_++;
|
||||
|
||||
if (!skip_snapshot_check && !snapshots_.empty()) {
|
||||
// Check that no snapshots are being held
|
||||
status =
|
||||
Status::NotSupported("Cannot add a file while holding snapshots");
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
// Verify that added file key range dont overlap with any keys in DB
|
||||
SuperVersion* sv = cfd->GetSuperVersion()->Ref();
|
||||
Arena arena;
|
||||
ReadOptions ro;
|
||||
ro.total_order_seek = true;
|
||||
ScopedArenaIterator iter(NewInternalIterator(ro, cfd, sv, &arena));
|
||||
|
||||
for (size_t i = 0; i < num_files; i++) {
|
||||
StopWatch sw(env_, nullptr, 0, µ_list[i], false);
|
||||
InternalKey range_start(file_info_list[i].smallest_key,
|
||||
kMaxSequenceNumber, kValueTypeForSeek);
|
||||
iter->Seek(range_start.Encode());
|
||||
status = iter->status();
|
||||
|
||||
if (status.ok() && iter->Valid()) {
|
||||
ParsedInternalKey seek_result;
|
||||
if (ParseInternalKey(iter->key(), &seek_result)) {
|
||||
if (user_cmp->Compare(seek_result.user_key,
|
||||
file_info_list[i].largest_key) <= 0) {
|
||||
status = Status::NotSupported("Cannot add overlapping range");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
status = Status::Corruption("DB have corrupted keys");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The levels that the files will be ingested into
|
||||
std::vector<int> target_level_list(num_files, 0);
|
||||
if (status.ok()) {
|
||||
VersionEdit edit;
|
||||
edit.SetColumnFamily(cfd->GetID());
|
||||
for (size_t i = 0; i < num_files; i++) {
|
||||
StopWatch sw(env_, nullptr, 0, µ_list[i], false);
|
||||
// Add file to the lowest possible level
|
||||
target_level_list[i] = PickLevelForIngestedFile(cfd, file_info_list[i]);
|
||||
edit.AddFile(target_level_list[i], meta_list[i].fd.GetNumber(),
|
||||
meta_list[i].fd.GetPathId(), meta_list[i].fd.GetFileSize(),
|
||||
meta_list[i].smallest, meta_list[i].largest,
|
||||
meta_list[i].smallest_seqno, meta_list[i].largest_seqno,
|
||||
meta_list[i].marked_for_compaction);
|
||||
}
|
||||
status = versions_->LogAndApply(cfd, mutable_cf_options, &edit, &mutex_,
|
||||
directories_.GetDbDir());
|
||||
}
|
||||
write_thread_.ExitUnbatched(&w);
|
||||
|
||||
if (status.ok()) {
|
||||
delete InstallSuperVersionAndScheduleWork(cfd, nullptr,
|
||||
mutable_cf_options);
|
||||
|
||||
// Update internal stats for new ingested files
|
||||
uint64_t total_keys = 0;
|
||||
uint64_t total_l0_files = 0;
|
||||
for (size_t i = 0; i < num_files; i++) {
|
||||
InternalStats::CompactionStats stats(1);
|
||||
stats.micros = micro_list[i];
|
||||
stats.bytes_written = meta_list[i].fd.GetFileSize();
|
||||
stats.num_output_files = 1;
|
||||
cfd->internal_stats()->AddCompactionStats(target_level_list[i], stats);
|
||||
cfd->internal_stats()->AddCFStats(
|
||||
InternalStats::BYTES_INGESTED_ADD_FILE,
|
||||
meta_list[i].fd.GetFileSize());
|
||||
total_keys += file_info_list[i].num_entries;
|
||||
if (target_level_list[i] == 0) {
|
||||
total_l0_files += 1;
|
||||
}
|
||||
Log(InfoLogLevel::INFO_LEVEL, immutable_db_options_.info_log,
|
||||
"[AddFile] External SST file %s was ingested in L%d with path %s\n",
|
||||
file_info_list[i].file_path.c_str(), target_level_list[i],
|
||||
db_fname_list[i].c_str());
|
||||
}
|
||||
cfd->internal_stats()->AddCFStats(InternalStats::INGESTED_NUM_KEYS_TOTAL,
|
||||
total_keys);
|
||||
cfd->internal_stats()->AddCFStats(InternalStats::INGESTED_NUM_FILES_TOTAL,
|
||||
num_files);
|
||||
cfd->internal_stats()->AddCFStats(
|
||||
InternalStats::INGESTED_LEVEL0_NUM_FILES_TOTAL, total_l0_files);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < num_files; i++) {
|
||||
ReleaseFileNumberFromPendingOutputs(
|
||||
pending_outputs_inserted_elem_list[i]);
|
||||
}
|
||||
|
||||
num_running_addfile_--;
|
||||
if (num_running_addfile_ == 0) {
|
||||
bg_cv_.SignalAll();
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::AddFile:MutexUnlock");
|
||||
} // mutex_ is unlocked here;
|
||||
|
||||
if (!status.ok()) {
|
||||
// We failed to add the files to the database
|
||||
for (size_t i = 0; i < num_files; i++) {
|
||||
Status s = env_->DeleteFile(db_fname_list[i]);
|
||||
if (!s.ok()) {
|
||||
Log(InfoLogLevel::WARN_LEVEL, immutable_db_options_.info_log,
|
||||
"AddFile() clean up for file %s failed : %s",
|
||||
db_fname_list[i].c_str(), s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
} else if (status.ok() && move_file) {
|
||||
// The files were moved and added successfully, remove original file links
|
||||
for (size_t i = 0; i < num_files; i++) {
|
||||
Status s = env_->DeleteFile(file_info_list[i].file_path);
|
||||
if (!s.ok()) {
|
||||
Log(InfoLogLevel::WARN_LEVEL, immutable_db_options_.info_log,
|
||||
"%s was added to DB successfully but failed to remove original "
|
||||
"file "
|
||||
"link : %s",
|
||||
file_info_list[i].file_path.c_str(), s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
// Finds the lowest level in the DB that the ingested file can be added to
|
||||
int DBImpl::PickLevelForIngestedFile(ColumnFamilyData* cfd,
|
||||
const ExternalSstFileInfo& file_info) {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
int target_level = 0;
|
||||
auto* vstorage = cfd->current()->storage_info();
|
||||
Slice file_smallest_user_key(file_info.smallest_key);
|
||||
Slice file_largest_user_key(file_info.largest_key);
|
||||
|
||||
for (int lvl = cfd->NumberLevels() - 1; lvl >= vstorage->base_level();
|
||||
lvl--) {
|
||||
// Make sure that the file fits in Level `lvl` and dont overlap with
|
||||
// the output of any compaction running right now.
|
||||
if (vstorage->OverlapInLevel(lvl, &file_smallest_user_key,
|
||||
&file_largest_user_key) == false &&
|
||||
cfd->RangeOverlapWithCompaction(file_smallest_user_key,
|
||||
file_largest_user_key, lvl) == false) {
|
||||
// Level lvl is the lowest level that dont have any files with key
|
||||
// range overlapping with our file key range and no compactions
|
||||
// planning to add overlapping files in it.
|
||||
target_level = lvl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return target_level;
|
||||
}
|
||||
|
||||
void DBImpl::WaitForAddFile() {
|
||||
mutex_.AssertHeld();
|
||||
while (num_running_addfile_ > 0) {
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -19,6 +19,11 @@ uint64_t DBImpl::TEST_GetLevel0TotalSize() {
|
||||
return default_cf_handle_->cfd()->current()->storage_info()->NumLevelBytes(0);
|
||||
}
|
||||
|
||||
void DBImpl::TEST_MaybeFlushColumnFamilies() {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
MaybeFlushColumnFamilies();
|
||||
}
|
||||
|
||||
int64_t DBImpl::TEST_MaxNextLevelOverlappingBytes(
|
||||
ColumnFamilyHandle* column_family) {
|
||||
ColumnFamilyData* cfd;
|
||||
|
||||
+13
-7
@@ -8,8 +8,9 @@
|
||||
|
||||
#include "db/compacted_db_impl.h"
|
||||
#include "db/db_impl.h"
|
||||
#include "db/merge_context.h"
|
||||
#include "db/db_iter.h"
|
||||
#include "db/merge_context.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "util/perf_context_imp.h"
|
||||
|
||||
namespace rocksdb {
|
||||
@@ -37,11 +38,14 @@ Status DBImplReadOnly::Get(const ReadOptions& read_options,
|
||||
auto cfd = cfh->cfd();
|
||||
SuperVersion* super_version = cfd->GetSuperVersion();
|
||||
MergeContext merge_context;
|
||||
RangeDelAggregator range_del_agg(cfd->internal_comparator(), snapshot);
|
||||
LookupKey lkey(key, snapshot);
|
||||
if (super_version->mem->Get(lkey, value, &s, &merge_context)) {
|
||||
if (super_version->mem->Get(lkey, value, &s, &merge_context, &range_del_agg,
|
||||
read_options)) {
|
||||
} else {
|
||||
PERF_TIMER_GUARD(get_from_output_files_time);
|
||||
super_version->current->Get(read_options, lkey, value, &s, &merge_context);
|
||||
super_version->current->Get(read_options, lkey, value, &s, &merge_context,
|
||||
&range_del_agg);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -60,8 +64,9 @@ Iterator* DBImplReadOnly::NewIterator(const ReadOptions& read_options,
|
||||
: latest_snapshot),
|
||||
super_version->mutable_cf_options.max_sequential_skip_in_iterations,
|
||||
super_version->version_number);
|
||||
auto internal_iter = NewInternalIterator(
|
||||
read_options, cfd, super_version, db_iter->GetArena());
|
||||
auto internal_iter =
|
||||
NewInternalIterator(read_options, cfd, super_version, db_iter->GetArena(),
|
||||
db_iter->GetRangeDelAggregator());
|
||||
db_iter->SetIterUnderDBIter(internal_iter);
|
||||
return db_iter;
|
||||
}
|
||||
@@ -88,8 +93,9 @@ Status DBImplReadOnly::NewIterators(
|
||||
: latest_snapshot),
|
||||
sv->mutable_cf_options.max_sequential_skip_in_iterations,
|
||||
sv->version_number);
|
||||
auto* internal_iter = NewInternalIterator(
|
||||
read_options, cfd, sv, db_iter->GetArena());
|
||||
auto* internal_iter =
|
||||
NewInternalIterator(read_options, cfd, sv, db_iter->GetArena(),
|
||||
db_iter->GetRangeDelAggregator());
|
||||
db_iter->SetIterUnderDBIter(internal_iter);
|
||||
iterators->push_back(db_iter);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ TEST_F(DBTestInPlaceUpdate, InPlaceUpdate) {
|
||||
options.inplace_update_support = true;
|
||||
options.env = env_;
|
||||
options.write_buffer_size = 100000;
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
Reopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
// Update key with values of smaller size
|
||||
@@ -45,6 +47,8 @@ TEST_F(DBTestInPlaceUpdate, InPlaceUpdateLargeNewValue) {
|
||||
options.inplace_update_support = true;
|
||||
options.env = env_;
|
||||
options.write_buffer_size = 100000;
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
Reopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
// Update key with values of larger size
|
||||
@@ -70,6 +74,8 @@ TEST_F(DBTestInPlaceUpdate, InPlaceUpdateCallbackSmallerSize) {
|
||||
options.write_buffer_size = 100000;
|
||||
options.inplace_callback =
|
||||
rocksdb::DBTestInPlaceUpdate::updateInPlaceSmallerSize;
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
Reopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
// Update key with values of smaller size
|
||||
@@ -97,6 +103,8 @@ TEST_F(DBTestInPlaceUpdate, InPlaceUpdateCallbackSmallerVarintSize) {
|
||||
options.write_buffer_size = 100000;
|
||||
options.inplace_callback =
|
||||
rocksdb::DBTestInPlaceUpdate::updateInPlaceSmallerVarintSize;
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
Reopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
// Update key with values of smaller varint size
|
||||
@@ -124,6 +132,8 @@ TEST_F(DBTestInPlaceUpdate, InPlaceUpdateCallbackLargeNewValue) {
|
||||
options.write_buffer_size = 100000;
|
||||
options.inplace_callback =
|
||||
rocksdb::DBTestInPlaceUpdate::updateInPlaceLargerSize;
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
Reopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
// Update key with values of larger size
|
||||
@@ -148,7 +158,9 @@ TEST_F(DBTestInPlaceUpdate, InPlaceUpdateCallbackNoAction) {
|
||||
options.env = env_;
|
||||
options.write_buffer_size = 100000;
|
||||
options.inplace_callback =
|
||||
rocksdb::DBTestInPlaceUpdate::updateInPlaceNoAction;
|
||||
rocksdb::DBTestInPlaceUpdate::updateInPlaceNoAction;
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
Reopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
// Callback function requests no actions from db
|
||||
|
||||
@@ -33,7 +33,7 @@ TEST_F(DBIOFailureTest, DropWrites) {
|
||||
// Force out-of-space errors
|
||||
env_->drop_writes_.store(true, std::memory_order_release);
|
||||
env_->sleep_counter_.Reset();
|
||||
env_->no_sleep_ = true;
|
||||
env_->no_slowdown_ = true;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (option_config_ != kUniversalCompactionMultiLevel &&
|
||||
option_config_ != kUniversalSubcompactions) {
|
||||
|
||||
+206
-83
@@ -122,7 +122,8 @@ class DBIter: public Iterator {
|
||||
iterate_upper_bound_(iterate_upper_bound),
|
||||
prefix_same_as_start_(prefix_same_as_start),
|
||||
pin_thru_lifetime_(pin_data),
|
||||
total_order_seek_(total_order_seek) {
|
||||
total_order_seek_(total_order_seek),
|
||||
range_del_agg_(ioptions.internal_comparator, s) {
|
||||
RecordTick(statistics_, NO_ITERATORS);
|
||||
prefix_extractor_ = ioptions.prefix_extractor;
|
||||
max_skip_ = max_sequential_skip_in_iterations;
|
||||
@@ -151,6 +152,10 @@ class DBIter: public Iterator {
|
||||
iter_ = iter;
|
||||
iter_->SetPinnedItersMgr(&pinned_iters_mgr_);
|
||||
}
|
||||
virtual RangeDelAggregator* GetRangeDelAggregator() {
|
||||
return &range_del_agg_;
|
||||
}
|
||||
|
||||
virtual bool Valid() const override { return valid_; }
|
||||
virtual Slice key() const override {
|
||||
assert(valid_);
|
||||
@@ -273,6 +278,7 @@ class DBIter: public Iterator {
|
||||
const bool total_order_seek_;
|
||||
// List of operands for merge operator.
|
||||
MergeContext merge_context_;
|
||||
RangeDelAggregator range_del_agg_;
|
||||
LocalStatistics local_stats_;
|
||||
PinnedIteratorsManager pinned_iters_mgr_;
|
||||
|
||||
@@ -333,7 +339,7 @@ void DBIter::Next() {
|
||||
// saved_value_ => the merged value
|
||||
//
|
||||
// NOTE: In between, saved_key_ can point to a user key that has
|
||||
// a delete marker
|
||||
// a delete marker or a sequence number higher than sequence_
|
||||
//
|
||||
// The prefix_check parameter controls whether we check the iterated
|
||||
// keys against the prefix of the seeked key. Set to false when
|
||||
@@ -350,71 +356,136 @@ void DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) {
|
||||
assert(iter_->Valid());
|
||||
assert(direction_ == kForward);
|
||||
current_entry_is_merged_ = false;
|
||||
|
||||
// How many times in a row we have skipped an entry with user key less than
|
||||
// or equal to saved_key_. We could skip these entries either because
|
||||
// sequence numbers were too high or because skipping = true.
|
||||
// What saved_key_ contains throughout this method:
|
||||
// - if skipping : saved_key_ contains the key that we need to skip,
|
||||
// and we haven't seen any keys greater than that,
|
||||
// - if num_skipped > 0 : saved_key_ contains the key that we have skipped
|
||||
// num_skipped times, and we haven't seen any keys
|
||||
// greater than that,
|
||||
// - none of the above : saved_key_ can contain anything, it doesn't matter.
|
||||
uint64_t num_skipped = 0;
|
||||
|
||||
do {
|
||||
ParsedInternalKey ikey;
|
||||
|
||||
if (ParseKey(&ikey)) {
|
||||
if (iterate_upper_bound_ != nullptr &&
|
||||
user_comparator_->Compare(ikey.user_key, *iterate_upper_bound_) >= 0) {
|
||||
break;
|
||||
}
|
||||
if (!ParseKey(&ikey)) {
|
||||
// Skip corrupted keys.
|
||||
iter_->Next();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (prefix_extractor_ && prefix_check &&
|
||||
prefix_extractor_->Transform(ikey.user_key).compare(prefix_start_key_) != 0) {
|
||||
break;
|
||||
}
|
||||
if (iterate_upper_bound_ != nullptr &&
|
||||
user_comparator_->Compare(ikey.user_key, *iterate_upper_bound_) >= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (ikey.sequence <= sequence_) {
|
||||
if (skipping &&
|
||||
user_comparator_->Compare(ikey.user_key, saved_key_.GetKey()) <= 0) {
|
||||
num_skipped++; // skip this entry
|
||||
PERF_COUNTER_ADD(internal_key_skipped_count, 1);
|
||||
} else {
|
||||
switch (ikey.type) {
|
||||
case kTypeDeletion:
|
||||
case kTypeSingleDeletion:
|
||||
if (prefix_extractor_ && prefix_check &&
|
||||
prefix_extractor_->Transform(ikey.user_key)
|
||||
.compare(prefix_start_key_) != 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (ikey.sequence <= sequence_) {
|
||||
if (skipping &&
|
||||
user_comparator_->Compare(ikey.user_key, saved_key_.GetKey()) <= 0) {
|
||||
num_skipped++; // skip this entry
|
||||
PERF_COUNTER_ADD(internal_key_skipped_count, 1);
|
||||
} else {
|
||||
num_skipped = 0;
|
||||
switch (ikey.type) {
|
||||
case kTypeDeletion:
|
||||
case kTypeSingleDeletion:
|
||||
// Arrange to skip all upcoming entries for this key since
|
||||
// they are hidden by this deletion.
|
||||
saved_key_.SetKey(
|
||||
ikey.user_key,
|
||||
!iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
skipping = true;
|
||||
PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
|
||||
break;
|
||||
case kTypeValue:
|
||||
saved_key_.SetKey(
|
||||
ikey.user_key,
|
||||
!iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
if (range_del_agg_.ShouldDelete(ikey)) {
|
||||
// Arrange to skip all upcoming entries for this key since
|
||||
// they are hidden by this deletion.
|
||||
saved_key_.SetKey(
|
||||
ikey.user_key,
|
||||
!iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
skipping = true;
|
||||
num_skipped = 0;
|
||||
PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
|
||||
break;
|
||||
case kTypeValue:
|
||||
} else {
|
||||
valid_ = true;
|
||||
saved_key_.SetKey(
|
||||
ikey.user_key,
|
||||
!iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
return;
|
||||
case kTypeMerge:
|
||||
// By now, we are sure the current ikey is going to yield a value
|
||||
saved_key_.SetKey(
|
||||
ikey.user_key,
|
||||
!iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
}
|
||||
break;
|
||||
case kTypeMerge:
|
||||
saved_key_.SetKey(
|
||||
ikey.user_key,
|
||||
!iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
if (range_del_agg_.ShouldDelete(ikey)) {
|
||||
// Arrange to skip all upcoming entries for this key since
|
||||
// they are hidden by this deletion.
|
||||
skipping = true;
|
||||
num_skipped = 0;
|
||||
PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
|
||||
} else {
|
||||
// By now, we are sure the current ikey is going to yield a
|
||||
// value
|
||||
current_entry_is_merged_ = true;
|
||||
valid_ = true;
|
||||
MergeValuesNewToOld(); // Go to a different state machine
|
||||
return;
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// This key was inserted after our snapshot was taken.
|
||||
PERF_COUNTER_ADD(internal_recent_skipped_count, 1);
|
||||
|
||||
// Here saved_key_ may contain some old key, or the default empty key, or
|
||||
// key assigned by some random other method. We don't care.
|
||||
if (user_comparator_->Compare(ikey.user_key, saved_key_.GetKey()) <= 0) {
|
||||
num_skipped++;
|
||||
} else {
|
||||
saved_key_.SetKey(
|
||||
ikey.user_key,
|
||||
!iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
skipping = false;
|
||||
num_skipped = 0;
|
||||
}
|
||||
}
|
||||
// If we have sequentially iterated via numerous keys and still not
|
||||
// found the next user-key, then it is better to seek so that we can
|
||||
// avoid too many key comparisons. We seek to the last occurrence of
|
||||
// our current key by looking for sequence number 0 and type deletion
|
||||
// (the smallest type).
|
||||
if (skipping && num_skipped > max_skip_) {
|
||||
|
||||
// If we have sequentially iterated via numerous equal keys, then it's
|
||||
// better to seek so that we can avoid too many key comparisons.
|
||||
if (num_skipped > max_skip_) {
|
||||
num_skipped = 0;
|
||||
std::string last_key;
|
||||
AppendInternalKey(&last_key, ParsedInternalKey(saved_key_.GetKey(), 0,
|
||||
kTypeDeletion));
|
||||
if (skipping) {
|
||||
// We're looking for the next user-key but all we see are the same
|
||||
// user-key with decreasing sequence numbers. Fast forward to
|
||||
// sequence number 0 and type deletion (the smallest type).
|
||||
AppendInternalKey(&last_key, ParsedInternalKey(saved_key_.GetKey(), 0,
|
||||
kTypeDeletion));
|
||||
// Don't set skipping = false because we may still see more user-keys
|
||||
// equal to saved_key_.
|
||||
} else {
|
||||
// We saw multiple entries with this user key and sequence numbers
|
||||
// higher than sequence_. Fast forward to sequence_.
|
||||
// Note that this only covers a case when a higher key was overwritten
|
||||
// many times since our snapshot was taken, not the case when a lot of
|
||||
// different keys were inserted after our snapshot was taken.
|
||||
AppendInternalKey(&last_key,
|
||||
ParsedInternalKey(saved_key_.GetKey(), sequence_,
|
||||
kValueTypeForSeek));
|
||||
}
|
||||
iter_->Seek(last_key);
|
||||
RecordTick(statistics_, NUMBER_OF_RESEEKS_IN_ITERATION);
|
||||
} else {
|
||||
@@ -447,6 +518,7 @@ void DBIter::MergeValuesNewToOld() {
|
||||
iter_->IsValuePinned() /* operand_pinned */);
|
||||
|
||||
ParsedInternalKey ikey;
|
||||
Status s;
|
||||
for (iter_->Next(); iter_->Valid(); iter_->Next()) {
|
||||
if (!ParseKey(&ikey)) {
|
||||
// skip corrupted key
|
||||
@@ -456,7 +528,8 @@ void DBIter::MergeValuesNewToOld() {
|
||||
if (!user_comparator_->Equal(ikey.user_key, saved_key_.GetKey())) {
|
||||
// hit the next user key, stop right here
|
||||
break;
|
||||
} else if (kTypeDeletion == ikey.type || kTypeSingleDeletion == ikey.type) {
|
||||
} else if (kTypeDeletion == ikey.type || kTypeSingleDeletion == ikey.type ||
|
||||
range_del_agg_.ShouldDelete(ikey)) {
|
||||
// hit a delete with the same user key, stop right here
|
||||
// iter_ is positioned after delete
|
||||
iter_->Next();
|
||||
@@ -466,9 +539,12 @@ void DBIter::MergeValuesNewToOld() {
|
||||
// final result in saved_value_. We are done!
|
||||
// ignore corruption if there is any.
|
||||
const Slice val = iter_->value();
|
||||
MergeHelper::TimedFullMerge(merge_operator_, ikey.user_key, &val,
|
||||
merge_context_.GetOperands(), &saved_value_,
|
||||
logger_, statistics_, env_, &pinned_value_);
|
||||
s = MergeHelper::TimedFullMerge(
|
||||
merge_operator_, ikey.user_key, &val, merge_context_.GetOperands(),
|
||||
&saved_value_, logger_, statistics_, env_, &pinned_value_);
|
||||
if (!s.ok()) {
|
||||
status_ = s;
|
||||
}
|
||||
// iter_ is positioned after put
|
||||
iter_->Next();
|
||||
return;
|
||||
@@ -477,6 +553,7 @@ void DBIter::MergeValuesNewToOld() {
|
||||
// when complete, add result to operands and continue.
|
||||
merge_context_.PushOperand(iter_->value(),
|
||||
iter_->IsValuePinned() /* operand_pinned */);
|
||||
PERF_COUNTER_ADD(internal_merge_count, 1);
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
@@ -486,9 +563,12 @@ void DBIter::MergeValuesNewToOld() {
|
||||
// a deletion marker.
|
||||
// feed null as the existing value to the merge operator, such that
|
||||
// client can differentiate this scenario and do things accordingly.
|
||||
MergeHelper::TimedFullMerge(merge_operator_, saved_key_.GetKey(), nullptr,
|
||||
merge_context_.GetOperands(), &saved_value_,
|
||||
logger_, statistics_, env_, &pinned_value_);
|
||||
s = MergeHelper::TimedFullMerge(merge_operator_, saved_key_.GetKey(), nullptr,
|
||||
merge_context_.GetOperands(), &saved_value_,
|
||||
logger_, statistics_, env_, &pinned_value_);
|
||||
if (!s.ok()) {
|
||||
status_ = s;
|
||||
}
|
||||
}
|
||||
|
||||
void DBIter::Prev() {
|
||||
@@ -538,6 +618,11 @@ void DBIter::ReverseToBackward() {
|
||||
FindParseableKey(&ikey, kReverse);
|
||||
while (iter_->Valid() &&
|
||||
user_comparator_->Compare(ikey.user_key, saved_key_.GetKey()) > 0) {
|
||||
if (ikey.sequence > sequence_) {
|
||||
PERF_COUNTER_ADD(internal_recent_skipped_count, 1);
|
||||
} else {
|
||||
PERF_COUNTER_ADD(internal_key_skipped_count, 1);
|
||||
}
|
||||
iter_->Prev();
|
||||
FindParseableKey(&ikey, kReverse);
|
||||
}
|
||||
@@ -624,10 +709,15 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
last_key_entry_type = ikey.type;
|
||||
switch (last_key_entry_type) {
|
||||
case kTypeValue:
|
||||
if (range_del_agg_.ShouldDelete(ikey)) {
|
||||
last_key_entry_type = kTypeRangeDeletion;
|
||||
PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
|
||||
} else {
|
||||
assert(iter_->IsValuePinned());
|
||||
pinned_value_ = iter_->value();
|
||||
}
|
||||
merge_context_.Clear();
|
||||
assert(iter_->IsValuePinned());
|
||||
pinned_value_ = iter_->value();
|
||||
last_not_merge_type = kTypeValue;
|
||||
last_not_merge_type = last_key_entry_type;
|
||||
break;
|
||||
case kTypeDeletion:
|
||||
case kTypeSingleDeletion:
|
||||
@@ -636,9 +726,17 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
|
||||
break;
|
||||
case kTypeMerge:
|
||||
assert(merge_operator_ != nullptr);
|
||||
merge_context_.PushOperandBack(
|
||||
iter_->value(), iter_->IsValuePinned() /* operand_pinned */);
|
||||
if (range_del_agg_.ShouldDelete(ikey)) {
|
||||
merge_context_.Clear();
|
||||
last_key_entry_type = kTypeRangeDeletion;
|
||||
last_not_merge_type = last_key_entry_type;
|
||||
PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
|
||||
} else {
|
||||
assert(merge_operator_ != nullptr);
|
||||
merge_context_.PushOperandBack(
|
||||
iter_->value(), iter_->IsValuePinned() /* operand_pinned */);
|
||||
PERF_COUNTER_ADD(internal_merge_count, 1);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
@@ -651,25 +749,28 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
FindParseableKey(&ikey, kReverse);
|
||||
}
|
||||
|
||||
Status s;
|
||||
switch (last_key_entry_type) {
|
||||
case kTypeDeletion:
|
||||
case kTypeSingleDeletion:
|
||||
case kTypeRangeDeletion:
|
||||
valid_ = false;
|
||||
return false;
|
||||
case kTypeMerge:
|
||||
current_entry_is_merged_ = true;
|
||||
if (last_not_merge_type == kTypeDeletion ||
|
||||
last_not_merge_type == kTypeSingleDeletion) {
|
||||
MergeHelper::TimedFullMerge(merge_operator_, saved_key_.GetKey(),
|
||||
nullptr, merge_context_.GetOperands(),
|
||||
&saved_value_, logger_, statistics_, env_,
|
||||
&pinned_value_);
|
||||
last_not_merge_type == kTypeSingleDeletion ||
|
||||
last_not_merge_type == kTypeRangeDeletion) {
|
||||
s = MergeHelper::TimedFullMerge(merge_operator_, saved_key_.GetKey(),
|
||||
nullptr, merge_context_.GetOperands(),
|
||||
&saved_value_, logger_, statistics_,
|
||||
env_, &pinned_value_);
|
||||
} else {
|
||||
assert(last_not_merge_type == kTypeValue);
|
||||
MergeHelper::TimedFullMerge(merge_operator_, saved_key_.GetKey(),
|
||||
&pinned_value_,
|
||||
merge_context_.GetOperands(), &saved_value_,
|
||||
logger_, statistics_, env_, &pinned_value_);
|
||||
s = MergeHelper::TimedFullMerge(
|
||||
merge_operator_, saved_key_.GetKey(), &pinned_value_,
|
||||
merge_context_.GetOperands(), &saved_value_, logger_, statistics_,
|
||||
env_, &pinned_value_);
|
||||
}
|
||||
break;
|
||||
case kTypeValue:
|
||||
@@ -680,6 +781,9 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
break;
|
||||
}
|
||||
valid_ = true;
|
||||
if (!s.ok()) {
|
||||
status_ = s;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -699,17 +803,17 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
ParsedInternalKey ikey;
|
||||
FindParseableKey(&ikey, kForward);
|
||||
|
||||
if (ikey.type == kTypeValue || ikey.type == kTypeDeletion ||
|
||||
ikey.type == kTypeSingleDeletion) {
|
||||
if (ikey.type == kTypeValue) {
|
||||
assert(iter_->IsValuePinned());
|
||||
pinned_value_ = iter_->value();
|
||||
valid_ = true;
|
||||
return true;
|
||||
}
|
||||
if (ikey.type == kTypeDeletion || ikey.type == kTypeSingleDeletion ||
|
||||
range_del_agg_.ShouldDelete(ikey)) {
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
if (ikey.type == kTypeValue) {
|
||||
assert(iter_->IsValuePinned());
|
||||
pinned_value_ = iter_->value();
|
||||
valid_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// kTypeMerge. We need to collect all kTypeMerge values and save them
|
||||
// in operands
|
||||
@@ -717,19 +821,23 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
merge_context_.Clear();
|
||||
while (iter_->Valid() &&
|
||||
user_comparator_->Equal(ikey.user_key, saved_key_.GetKey()) &&
|
||||
ikey.type == kTypeMerge) {
|
||||
ikey.type == kTypeMerge && !range_del_agg_.ShouldDelete(ikey)) {
|
||||
merge_context_.PushOperand(iter_->value(),
|
||||
iter_->IsValuePinned() /* operand_pinned */);
|
||||
PERF_COUNTER_ADD(internal_merge_count, 1);
|
||||
iter_->Next();
|
||||
FindParseableKey(&ikey, kForward);
|
||||
}
|
||||
|
||||
Status s;
|
||||
if (!iter_->Valid() ||
|
||||
!user_comparator_->Equal(ikey.user_key, saved_key_.GetKey()) ||
|
||||
ikey.type == kTypeDeletion || ikey.type == kTypeSingleDeletion) {
|
||||
MergeHelper::TimedFullMerge(merge_operator_, saved_key_.GetKey(), nullptr,
|
||||
merge_context_.GetOperands(), &saved_value_,
|
||||
logger_, statistics_, env_, &pinned_value_);
|
||||
ikey.type == kTypeDeletion || ikey.type == kTypeSingleDeletion ||
|
||||
range_del_agg_.ShouldDelete(ikey)) {
|
||||
s = MergeHelper::TimedFullMerge(merge_operator_, saved_key_.GetKey(),
|
||||
nullptr, merge_context_.GetOperands(),
|
||||
&saved_value_, logger_, statistics_, env_,
|
||||
&pinned_value_);
|
||||
// Make iter_ valid and point to saved_key_
|
||||
if (!iter_->Valid() ||
|
||||
!user_comparator_->Equal(ikey.user_key, saved_key_.GetKey())) {
|
||||
@@ -737,14 +845,20 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
RecordTick(statistics_, NUMBER_OF_RESEEKS_IN_ITERATION);
|
||||
}
|
||||
valid_ = true;
|
||||
if (!s.ok()) {
|
||||
status_ = s;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const Slice& val = iter_->value();
|
||||
MergeHelper::TimedFullMerge(merge_operator_, saved_key_.GetKey(), &val,
|
||||
merge_context_.GetOperands(), &saved_value_,
|
||||
logger_, statistics_, env_, &pinned_value_);
|
||||
s = MergeHelper::TimedFullMerge(merge_operator_, saved_key_.GetKey(), &val,
|
||||
merge_context_.GetOperands(), &saved_value_,
|
||||
logger_, statistics_, env_, &pinned_value_);
|
||||
valid_ = true;
|
||||
if (!s.ok()) {
|
||||
status_ = s;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -789,6 +903,11 @@ void DBIter::FindPrevUserKey() {
|
||||
++num_skipped;
|
||||
}
|
||||
}
|
||||
if (ikey.sequence > sequence_) {
|
||||
PERF_COUNTER_ADD(internal_recent_skipped_count, 1);
|
||||
} else {
|
||||
PERF_COUNTER_ADD(internal_key_skipped_count, 1);
|
||||
}
|
||||
iter_->Prev();
|
||||
FindParseableKey(&ikey, kReverse);
|
||||
}
|
||||
@@ -972,6 +1091,10 @@ ArenaWrappedDBIter::~ArenaWrappedDBIter() { db_iter_->~DBIter(); }
|
||||
|
||||
void ArenaWrappedDBIter::SetDBIter(DBIter* iter) { db_iter_ = iter; }
|
||||
|
||||
RangeDelAggregator* ArenaWrappedDBIter::GetRangeDelAggregator() {
|
||||
return db_iter_->GetRangeDelAggregator();
|
||||
}
|
||||
|
||||
void ArenaWrappedDBIter::SetIterUnderDBIter(InternalIterator* iter) {
|
||||
static_cast<DBIter*>(db_iter_)->SetIter(iter);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include "db/dbformat.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "util/arena.h"
|
||||
@@ -46,6 +47,7 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
// Get the arena to be used to allocate memory for DBIter to be wrapped,
|
||||
// as well as child iterators in it.
|
||||
virtual Arena* GetArena() { return &arena_; }
|
||||
virtual RangeDelAggregator* GetRangeDelAggregator();
|
||||
|
||||
// Set the DB Iterator to be wrapped
|
||||
|
||||
|
||||
+63
-6
@@ -8,10 +8,10 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/port.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/iostats_context.h"
|
||||
#include "rocksdb/perf_context.h"
|
||||
#include "port/port.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -1698,13 +1698,14 @@ TEST_F(DBIteratorTest, IteratorWithLocalStatistics) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
ASSERT_EQ(TestGetTickerCount(options, NUMBER_DB_NEXT), total_next);
|
||||
ASSERT_EQ(TestGetTickerCount(options, NUMBER_DB_NEXT), (uint64_t)total_next);
|
||||
ASSERT_EQ(TestGetTickerCount(options, NUMBER_DB_NEXT_FOUND),
|
||||
total_next_found);
|
||||
ASSERT_EQ(TestGetTickerCount(options, NUMBER_DB_PREV), total_prev);
|
||||
(uint64_t)total_next_found);
|
||||
ASSERT_EQ(TestGetTickerCount(options, NUMBER_DB_PREV), (uint64_t)total_prev);
|
||||
ASSERT_EQ(TestGetTickerCount(options, NUMBER_DB_PREV_FOUND),
|
||||
total_prev_found);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ITER_BYTES_READ), total_bytes);
|
||||
(uint64_t)total_prev_found);
|
||||
ASSERT_EQ(TestGetTickerCount(options, ITER_BYTES_READ), (uint64_t)total_bytes);
|
||||
|
||||
}
|
||||
|
||||
TEST_F(DBIteratorTest, ReadAhead) {
|
||||
@@ -1777,6 +1778,62 @@ TEST_F(DBIteratorTest, ReadAhead) {
|
||||
delete iter;
|
||||
}
|
||||
|
||||
// Insert a key, create a snapshot iterator, overwrite key lots of times,
|
||||
// seek to a smaller key. Expect DBIter to fall back to a seek instead of
|
||||
// going through all the overwrites linearly.
|
||||
TEST_F(DBIteratorTest, DBIteratorSkipRecentDuplicatesTest) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.max_sequential_skip_in_iterations = 3;
|
||||
options.prefix_extractor = nullptr;
|
||||
options.write_buffer_size = 1 << 27; // big enough to avoid flush
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Insert.
|
||||
ASSERT_OK(Put("b", "0"));
|
||||
|
||||
// Create iterator.
|
||||
ReadOptions ro;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(ro));
|
||||
|
||||
// Insert a lot.
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
ASSERT_OK(Put("b", std::to_string(i + 1).c_str()));
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Check that memtable wasn't flushed.
|
||||
std::string val;
|
||||
ASSERT_TRUE(db_->GetProperty("rocksdb.num-files-at-level0", &val));
|
||||
EXPECT_EQ("0", val);
|
||||
#endif
|
||||
|
||||
// Seek iterator to a smaller key.
|
||||
perf_context.Reset();
|
||||
iter->Seek("a");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
EXPECT_EQ("b", iter->key().ToString());
|
||||
EXPECT_EQ("0", iter->value().ToString());
|
||||
|
||||
// Check that the seek didn't do too much work.
|
||||
// Checks are not tight, just make sure that everything is well below 100.
|
||||
EXPECT_LT(perf_context.internal_key_skipped_count, 4);
|
||||
EXPECT_LT(perf_context.internal_recent_skipped_count, 8);
|
||||
EXPECT_LT(perf_context.seek_on_memtable_count, 10);
|
||||
EXPECT_LT(perf_context.next_on_memtable_count, 10);
|
||||
EXPECT_LT(perf_context.prev_on_memtable_count, 10);
|
||||
|
||||
// Check that iterator did something like what we expect.
|
||||
EXPECT_EQ(perf_context.internal_delete_skipped_count, 0);
|
||||
EXPECT_EQ(perf_context.internal_merge_count, 0);
|
||||
EXPECT_GE(perf_context.internal_recent_skipped_count, 2);
|
||||
EXPECT_GE(perf_context.seek_on_memtable_count, 2);
|
||||
EXPECT_EQ(1, options.statistics->getTickerCount(
|
||||
NUMBER_OF_RESEEKS_IN_ITERATION));
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "db/memtable.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/memtablerep.h"
|
||||
#include "rocksdb/slice_transform.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class DBMemTableTest : public DBTestBase {
|
||||
public:
|
||||
DBMemTableTest() : DBTestBase("/db_memtable_test") {}
|
||||
};
|
||||
|
||||
class MockMemTableRep : public MemTableRep {
|
||||
public:
|
||||
explicit MockMemTableRep(MemTableAllocator* allocator, MemTableRep* rep)
|
||||
: MemTableRep(allocator), rep_(rep), num_insert_with_hint_(0) {}
|
||||
|
||||
virtual KeyHandle Allocate(const size_t len, char** buf) override {
|
||||
return rep_->Allocate(len, buf);
|
||||
}
|
||||
|
||||
virtual void Insert(KeyHandle handle) override {
|
||||
return rep_->Insert(handle);
|
||||
}
|
||||
|
||||
virtual void InsertWithHint(KeyHandle handle, void** hint) override {
|
||||
num_insert_with_hint_++;
|
||||
ASSERT_NE(nullptr, hint);
|
||||
last_hint_in_ = *hint;
|
||||
rep_->InsertWithHint(handle, hint);
|
||||
last_hint_out_ = *hint;
|
||||
}
|
||||
|
||||
virtual bool Contains(const char* key) const override {
|
||||
return rep_->Contains(key);
|
||||
}
|
||||
|
||||
virtual void Get(const LookupKey& k, void* callback_args,
|
||||
bool (*callback_func)(void* arg,
|
||||
const char* entry)) override {
|
||||
rep_->Get(k, callback_args, callback_func);
|
||||
}
|
||||
|
||||
virtual size_t ApproximateMemoryUsage() override {
|
||||
return rep_->ApproximateMemoryUsage();
|
||||
}
|
||||
|
||||
virtual Iterator* GetIterator(Arena* arena) override {
|
||||
return rep_->GetIterator(arena);
|
||||
}
|
||||
|
||||
void* last_hint_in() { return last_hint_in_; }
|
||||
void* last_hint_out() { return last_hint_out_; }
|
||||
int num_insert_with_hint() { return num_insert_with_hint_; }
|
||||
|
||||
private:
|
||||
std::unique_ptr<MemTableRep> rep_;
|
||||
void* last_hint_in_;
|
||||
void* last_hint_out_;
|
||||
int num_insert_with_hint_;
|
||||
};
|
||||
|
||||
class MockMemTableRepFactory : public MemTableRepFactory {
|
||||
public:
|
||||
virtual MemTableRep* CreateMemTableRep(const MemTableRep::KeyComparator& cmp,
|
||||
MemTableAllocator* allocator,
|
||||
const SliceTransform* transform,
|
||||
Logger* logger) override {
|
||||
SkipListFactory factory;
|
||||
MemTableRep* skiplist_rep =
|
||||
factory.CreateMemTableRep(cmp, allocator, transform, logger);
|
||||
mock_rep_ = new MockMemTableRep(allocator, skiplist_rep);
|
||||
return mock_rep_;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "MockMemTableRepFactory"; }
|
||||
|
||||
MockMemTableRep* rep() { return mock_rep_; }
|
||||
|
||||
bool IsInsertConcurrentlySupported() const override { return false; }
|
||||
|
||||
private:
|
||||
MockMemTableRep* mock_rep_;
|
||||
};
|
||||
|
||||
class TestPrefixExtractor : public SliceTransform {
|
||||
public:
|
||||
virtual const char* Name() const override { return "TestPrefixExtractor"; }
|
||||
|
||||
virtual Slice Transform(const Slice& key) const override {
|
||||
const char* p = separator(key);
|
||||
if (p == nullptr) {
|
||||
return Slice();
|
||||
}
|
||||
return Slice(key.data(), p - key.data() + 1);
|
||||
}
|
||||
|
||||
virtual bool InDomain(const Slice& key) const override {
|
||||
return separator(key) != nullptr;
|
||||
}
|
||||
|
||||
virtual bool InRange(const Slice& key) const override { return false; }
|
||||
|
||||
private:
|
||||
const char* separator(const Slice& key) const {
|
||||
return reinterpret_cast<const char*>(memchr(key.data(), '_', key.size()));
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(DBMemTableTest, InsertWithHint) {
|
||||
Options options;
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
options.create_if_missing = true;
|
||||
options.memtable_factory.reset(new MockMemTableRepFactory());
|
||||
options.memtable_insert_with_hint_prefix_extractor.reset(
|
||||
new TestPrefixExtractor());
|
||||
Reopen(options);
|
||||
MockMemTableRep* rep =
|
||||
reinterpret_cast<MockMemTableRepFactory*>(options.memtable_factory.get())
|
||||
->rep();
|
||||
ASSERT_OK(Put("foo_k1", "foo_v1"));
|
||||
ASSERT_EQ(nullptr, rep->last_hint_in());
|
||||
void* hint_foo = rep->last_hint_out();
|
||||
ASSERT_OK(Put("foo_k2", "foo_v2"));
|
||||
ASSERT_EQ(hint_foo, rep->last_hint_in());
|
||||
ASSERT_EQ(hint_foo, rep->last_hint_out());
|
||||
ASSERT_OK(Put("foo_k3", "foo_v3"));
|
||||
ASSERT_EQ(hint_foo, rep->last_hint_in());
|
||||
ASSERT_EQ(hint_foo, rep->last_hint_out());
|
||||
ASSERT_OK(Put("bar_k1", "bar_v1"));
|
||||
ASSERT_EQ(nullptr, rep->last_hint_in());
|
||||
void* hint_bar = rep->last_hint_out();
|
||||
ASSERT_NE(hint_foo, hint_bar);
|
||||
ASSERT_OK(Put("bar_k2", "bar_v2"));
|
||||
ASSERT_EQ(hint_bar, rep->last_hint_in());
|
||||
ASSERT_EQ(hint_bar, rep->last_hint_out());
|
||||
ASSERT_EQ(5, rep->num_insert_with_hint());
|
||||
ASSERT_OK(Put("whitelisted", "vvv"));
|
||||
ASSERT_EQ(5, rep->num_insert_with_hint());
|
||||
ASSERT_EQ("foo_v1", Get("foo_k1"));
|
||||
ASSERT_EQ("foo_v2", Get("foo_k2"));
|
||||
ASSERT_EQ("foo_v3", Get("foo_k3"));
|
||||
ASSERT_EQ("bar_v1", Get("bar_k1"));
|
||||
ASSERT_EQ("bar_v2", Get("bar_k2"));
|
||||
ASSERT_EQ("vvv", Get("whitelisted"));
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
rocksdb::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "db/forward_iterator.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// Test merge operator functionality.
|
||||
class DBMergeOperatorTest : public DBTestBase {
|
||||
public:
|
||||
DBMergeOperatorTest() : DBTestBase("/db_merge_operator_test") {}
|
||||
};
|
||||
|
||||
// A test merge operator mimics put but also fails if one of merge operands is
|
||||
// "corrupted".
|
||||
class TestPutOperator : public MergeOperator {
|
||||
public:
|
||||
virtual bool FullMergeV2(const MergeOperationInput& merge_in,
|
||||
MergeOperationOutput* merge_out) const override {
|
||||
if (merge_in.existing_value != nullptr &&
|
||||
*(merge_in.existing_value) == "corrupted") {
|
||||
return false;
|
||||
}
|
||||
for (auto value : merge_in.operand_list) {
|
||||
if (value == "corrupted") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
merge_out->existing_operand = merge_in.operand_list.back();
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "TestPutOperator"; }
|
||||
};
|
||||
|
||||
TEST_F(DBMergeOperatorTest, MergeErrorOnRead) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.merge_operator.reset(new TestPutOperator());
|
||||
Reopen(options);
|
||||
ASSERT_OK(Merge("k1", "v1"));
|
||||
ASSERT_OK(Merge("k1", "corrupted"));
|
||||
std::string value;
|
||||
ASSERT_TRUE(db_->Get(ReadOptions(), "k1", &value).IsCorruption());
|
||||
VerifyDBInternal({{"k1", "corrupted"}, {"k1", "v1"}});
|
||||
}
|
||||
|
||||
TEST_F(DBMergeOperatorTest, MergeErrorOnWrite) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.merge_operator.reset(new TestPutOperator());
|
||||
options.max_successive_merges = 3;
|
||||
Reopen(options);
|
||||
ASSERT_OK(Merge("k1", "v1"));
|
||||
ASSERT_OK(Merge("k1", "v2"));
|
||||
// Will trigger a merge when hitting max_successive_merges and the merge
|
||||
// will fail. The delta will be inserted nevertheless.
|
||||
ASSERT_OK(Merge("k1", "corrupted"));
|
||||
// Data should stay unmerged after the error.
|
||||
VerifyDBInternal({{"k1", "corrupted"}, {"k1", "v2"}, {"k1", "v1"}});
|
||||
}
|
||||
|
||||
TEST_F(DBMergeOperatorTest, MergeErrorOnIteration) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.merge_operator.reset(new TestPutOperator());
|
||||
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Merge("k1", "v1"));
|
||||
ASSERT_OK(Merge("k1", "corrupted"));
|
||||
ASSERT_OK(Put("k2", "v2"));
|
||||
VerifyDBFromMap({{"k1", ""}, {"k2", "v2"}}, nullptr, false,
|
||||
{{"k1", Status::Corruption()}});
|
||||
VerifyDBInternal({{"k1", "corrupted"}, {"k1", "v1"}, {"k2", "v2"}});
|
||||
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Merge("k1", "v1"));
|
||||
ASSERT_OK(Put("k2", "v2"));
|
||||
ASSERT_OK(Merge("k2", "corrupted"));
|
||||
VerifyDBFromMap({{"k1", "v1"}, {"k2", ""}}, nullptr, false,
|
||||
{{"k2", Status::Corruption()}});
|
||||
VerifyDBInternal({{"k1", "v1"}, {"k2", "corrupted"}, {"k2", "v2"}});
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
rocksdb::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
+61
-1
@@ -64,7 +64,7 @@ class DBOptionsTest : public DBTestBase {
|
||||
Options options;
|
||||
ImmutableDBOptions db_options(options);
|
||||
test::RandomInitCFOptions(&options, rnd);
|
||||
auto sanitized_options = SanitizeOptions(db_options, nullptr, options);
|
||||
auto sanitized_options = SanitizeOptions(db_options, options);
|
||||
auto opt_map = GetMutableCFOptionsMap(sanitized_options);
|
||||
delete options.compaction_filter;
|
||||
return opt_map;
|
||||
@@ -256,6 +256,66 @@ TEST_F(DBOptionsTest, SetBackgroundCompactionThreads) {
|
||||
ASSERT_EQ(3, dbfull()->TEST_BGCompactionsAllowed());
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, AvoidFlushDuringShutdown) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
WriteOptions write_without_wal;
|
||||
write_without_wal.disableWAL = true;
|
||||
|
||||
ASSERT_FALSE(options.avoid_flush_during_shutdown);
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("foo", "v1", write_without_wal));
|
||||
Reopen(options);
|
||||
ASSERT_EQ("v1", Get("foo"));
|
||||
ASSERT_EQ("1", FilesPerLevel());
|
||||
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("foo", "v2", write_without_wal));
|
||||
ASSERT_OK(dbfull()->SetDBOptions({{"avoid_flush_during_shutdown", "true"}}));
|
||||
Reopen(options);
|
||||
ASSERT_EQ("NOT_FOUND", Get("foo"));
|
||||
ASSERT_EQ("", FilesPerLevel());
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, SetDelayedWriteRateOption) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.delayed_write_rate = 2 * 1024U * 1024U;
|
||||
Reopen(options);
|
||||
ASSERT_EQ(2 * 1024U * 1024U, dbfull()->TEST_write_controler().max_delayed_write_rate());
|
||||
|
||||
ASSERT_OK(dbfull()->SetDBOptions({{"delayed_write_rate", "20000"}}));
|
||||
ASSERT_EQ(20000, dbfull()->TEST_write_controler().max_delayed_write_rate());
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, MaxTotalWalSizeChange) {
|
||||
Random rnd(1044);
|
||||
const auto value_size = size_t(1024);
|
||||
std::string value;
|
||||
test::RandomString(&rnd, value_size, &value);
|
||||
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
CreateColumnFamilies({"1", "2", "3"}, options);
|
||||
ReopenWithColumnFamilies({"default", "1", "2", "3"}, options);
|
||||
|
||||
WriteOptions write_options;
|
||||
|
||||
const int key_count = 100;
|
||||
for (int i = 0; i < key_count; ++i) {
|
||||
for (size_t cf = 0; cf < handles_.size(); ++cf) {
|
||||
ASSERT_OK(Put(static_cast<int>(cf), Key(i), value));
|
||||
}
|
||||
}
|
||||
ASSERT_OK(dbfull()->SetDBOptions({{"max_total_wal_size", "10"}}));
|
||||
|
||||
for (size_t cf = 0; cf < handles_.size(); ++cf) {
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[cf]);
|
||||
ASSERT_EQ("1", FilesPerLevel(static_cast<int>(cf)));
|
||||
}
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -34,6 +34,7 @@ TEST_F(DBPropertiesTest, Empty) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.write_buffer_size = 100000; // Small write buffer
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
options = CurrentOptions(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
@@ -491,6 +492,7 @@ TEST_F(DBPropertiesTest, NumImmutableMemTable) {
|
||||
|
||||
std::string big_value(1000000 * 2, 'x');
|
||||
std::string num;
|
||||
uint64_t value;
|
||||
SetPerfLevel(kEnableTime);
|
||||
ASSERT_TRUE(GetPerfLevel() == kEnableTime);
|
||||
|
||||
@@ -555,11 +557,11 @@ TEST_F(DBPropertiesTest, NumImmutableMemTable) {
|
||||
ASSERT_TRUE(dbfull()->GetProperty(
|
||||
handles_[1], DB::Properties::kNumImmutableMemTableFlushed, &num));
|
||||
ASSERT_EQ(num, "3");
|
||||
ASSERT_TRUE(dbfull()->GetProperty(
|
||||
handles_[1], "rocksdb.cur-size-active-mem-table", &num));
|
||||
// "384" is the size of the metadata of two empty skiplists, this would
|
||||
// break if we change the default vectorrep/skiplist implementation
|
||||
ASSERT_EQ(num, "384");
|
||||
ASSERT_TRUE(dbfull()->GetIntProperty(
|
||||
handles_[1], "rocksdb.cur-size-active-mem-table", &value));
|
||||
// "192" is the size of the metadata of two empty skiplists, this would
|
||||
// break if we change the default skiplist implementation
|
||||
ASSERT_GE(value, 192);
|
||||
|
||||
uint64_t int_num;
|
||||
uint64_t base_total_size;
|
||||
|
||||
@@ -0,0 +1,492 @@
|
||||
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "util/testutil.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class DBRangeDelTest : public DBTestBase {
|
||||
public:
|
||||
DBRangeDelTest() : DBTestBase("/db_range_del_test") {}
|
||||
|
||||
std::string GetNumericStr(int key) {
|
||||
uint64_t uint64_key = static_cast<uint64_t>(key);
|
||||
std::string str;
|
||||
str.resize(8);
|
||||
memcpy(&str[0], static_cast<void*>(&uint64_key), 8);
|
||||
return str;
|
||||
}
|
||||
};
|
||||
|
||||
// PlainTableFactory and NumTableFilesAtLevel() are not supported in
|
||||
// ROCKSDB_LITE
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBRangeDelTest, NonBlockBasedTableNotSupported) {
|
||||
Options opts = CurrentOptions();
|
||||
opts.table_factory.reset(new PlainTableFactory());
|
||||
opts.prefix_extractor.reset(NewNoopTransform());
|
||||
opts.allow_mmap_reads = true;
|
||||
opts.max_sequential_skip_in_iterations = 999999;
|
||||
Reopen(opts);
|
||||
|
||||
ASSERT_TRUE(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "dr1", "dr1")
|
||||
.IsNotSupported());
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, FlushOutputHasOnlyRangeTombstones) {
|
||||
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "dr1",
|
||||
"dr1"));
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, CompactionOutputHasOnlyRangeTombstone) {
|
||||
Options opts = CurrentOptions();
|
||||
opts.disable_auto_compactions = true;
|
||||
opts.statistics = CreateDBStatistics();
|
||||
Reopen(opts);
|
||||
|
||||
// snapshot protects range tombstone from dropping due to becoming obsolete.
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z");
|
||||
db_->Flush(FlushOptions());
|
||||
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(1));
|
||||
dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr,
|
||||
true /* disallow_trivial_move */);
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(1));
|
||||
ASSERT_EQ(0, TestGetTickerCount(opts, COMPACTION_RANGE_DEL_DROP_OBSOLETE));
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, CompactionOutputFilesExactlyFilled) {
|
||||
// regression test for exactly filled compaction output files. Previously
|
||||
// another file would be generated containing all range deletions, which
|
||||
// could invalidate the non-overlapping file boundary invariant.
|
||||
const int kNumPerFile = 4, kNumFiles = 2, kFileBytes = 9 << 10;
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.level0_file_num_compaction_trigger = kNumFiles;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumPerFile));
|
||||
options.num_levels = 2;
|
||||
options.target_file_size_base = kFileBytes;
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_size_deviation = 50; // each block holds two keys
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
Reopen(options);
|
||||
|
||||
// snapshot protects range tombstone from dropping due to becoming obsolete.
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(0), Key(1));
|
||||
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < kNumFiles; ++i) {
|
||||
std::vector<std::string> values;
|
||||
// Write 12K (4 values, each 3K)
|
||||
for (int j = 0; j < kNumPerFile; j++) {
|
||||
values.push_back(RandomString(&rnd, 3 << 10));
|
||||
ASSERT_OK(Put(Key(i * kNumPerFile + j), values[j]));
|
||||
if (j == 0 && i > 0) {
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
}
|
||||
}
|
||||
}
|
||||
// put extra key to trigger final flush
|
||||
ASSERT_OK(Put("", ""));
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
ASSERT_EQ(kNumFiles, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(1));
|
||||
|
||||
dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr,
|
||||
true /* disallow_trivial_move */);
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(1));
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, MaxCompactionBytesCutsOutputFiles) {
|
||||
// Ensures range deletion spanning multiple compaction output files that are
|
||||
// cut by max_compaction_bytes will have non-overlapping key-ranges.
|
||||
// https://github.com/facebook/rocksdb/issues/1778
|
||||
const int kNumFiles = 2, kNumPerFile = 1 << 8, kBytesPerVal = 1 << 12;
|
||||
Options opts = CurrentOptions();
|
||||
opts.comparator = test::Uint64Comparator();
|
||||
opts.disable_auto_compactions = true;
|
||||
opts.level0_file_num_compaction_trigger = kNumFiles;
|
||||
opts.max_compaction_bytes = kNumPerFile * kBytesPerVal;
|
||||
opts.memtable_factory.reset(new SpecialSkipListFactory(kNumPerFile));
|
||||
// Want max_compaction_bytes to trigger the end of compaction output file, not
|
||||
// target_file_size_base, so make the latter much bigger
|
||||
opts.target_file_size_base = 100 * opts.max_compaction_bytes;
|
||||
Reopen(opts);
|
||||
|
||||
// snapshot protects range tombstone from dropping due to becoming obsolete.
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
|
||||
// It spans the whole key-range, thus will be included in all output files
|
||||
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
|
||||
GetNumericStr(0),
|
||||
GetNumericStr(kNumFiles * kNumPerFile - 1)));
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < kNumFiles; ++i) {
|
||||
std::vector<std::string> values;
|
||||
// Write 1MB (256 values, each 4K)
|
||||
for (int j = 0; j < kNumPerFile; j++) {
|
||||
values.push_back(RandomString(&rnd, kBytesPerVal));
|
||||
ASSERT_OK(Put(GetNumericStr(kNumPerFile * i + j), values[j]));
|
||||
}
|
||||
// extra entry to trigger SpecialSkipListFactory's flush
|
||||
ASSERT_OK(Put(GetNumericStr(kNumPerFile), ""));
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
ASSERT_EQ(i + 1, NumTableFilesAtLevel(0));
|
||||
}
|
||||
|
||||
dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr,
|
||||
true /* disallow_trivial_move */);
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_GE(NumTableFilesAtLevel(1), 2);
|
||||
|
||||
std::vector<std::vector<FileMetaData>> files;
|
||||
dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(), &files);
|
||||
|
||||
for (size_t i = 0; i < files[1].size() - 1; ++i) {
|
||||
ASSERT_TRUE(InternalKeyComparator(opts.comparator)
|
||||
.Compare(files[1][i].largest, files[1][i + 1].smallest) <
|
||||
0);
|
||||
}
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, FlushRangeDelsSameStartKey) {
|
||||
db_->Put(WriteOptions(), "b1", "val");
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "c"));
|
||||
db_->Put(WriteOptions(), "b2", "val");
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "b"));
|
||||
// first iteration verifies query correctness in memtable, second verifies
|
||||
// query correctness for a single SST file
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
if (i > 0) {
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
}
|
||||
std::string value;
|
||||
ASSERT_TRUE(db_->Get(ReadOptions(), "b1", &value).IsNotFound());
|
||||
ASSERT_OK(db_->Get(ReadOptions(), "b2", &value));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, CompactRangeDelsSameStartKey) {
|
||||
db_->Put(WriteOptions(), "unused", "val"); // prevents empty after compaction
|
||||
db_->Put(WriteOptions(), "b1", "val");
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "c"));
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "b"));
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
ASSERT_EQ(3, NumTableFilesAtLevel(0));
|
||||
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
if (i > 0) {
|
||||
dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr,
|
||||
true /* disallow_trivial_move */);
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(1));
|
||||
}
|
||||
std::string value;
|
||||
ASSERT_TRUE(db_->Get(ReadOptions(), "b1", &value).IsNotFound());
|
||||
}
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBRangeDelTest, FlushRemovesCoveredKeys) {
|
||||
const int kNum = 300, kRangeBegin = 50, kRangeEnd = 250;
|
||||
Options opts = CurrentOptions();
|
||||
opts.comparator = test::Uint64Comparator();
|
||||
Reopen(opts);
|
||||
|
||||
// Write a third before snapshot, a third between snapshot and tombstone, and
|
||||
// a third after the tombstone. Keys older than snapshot or newer than the
|
||||
// tombstone should be preserved.
|
||||
const Snapshot* snapshot = nullptr;
|
||||
for (int i = 0; i < kNum; ++i) {
|
||||
if (i == kNum / 3) {
|
||||
snapshot = db_->GetSnapshot();
|
||||
} else if (i == 2 * kNum / 3) {
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
|
||||
GetNumericStr(kRangeBegin), GetNumericStr(kRangeEnd));
|
||||
}
|
||||
db_->Put(WriteOptions(), GetNumericStr(i), "val");
|
||||
}
|
||||
db_->Flush(FlushOptions());
|
||||
|
||||
for (int i = 0; i < kNum; ++i) {
|
||||
ReadOptions read_opts;
|
||||
read_opts.ignore_range_deletions = true;
|
||||
std::string value;
|
||||
if (i < kRangeBegin || i > kRangeEnd || i < kNum / 3 || i >= 2 * kNum / 3) {
|
||||
ASSERT_OK(db_->Get(read_opts, GetNumericStr(i), &value));
|
||||
} else {
|
||||
ASSERT_TRUE(db_->Get(read_opts, GetNumericStr(i), &value).IsNotFound());
|
||||
}
|
||||
}
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
|
||||
// NumTableFilesAtLevel() is not supported in ROCKSDB_LITE
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBRangeDelTest, CompactionRemovesCoveredKeys) {
|
||||
const int kNumPerFile = 100, kNumFiles = 4;
|
||||
Options opts = CurrentOptions();
|
||||
opts.comparator = test::Uint64Comparator();
|
||||
opts.disable_auto_compactions = true;
|
||||
opts.memtable_factory.reset(new SpecialSkipListFactory(kNumPerFile));
|
||||
opts.num_levels = 2;
|
||||
opts.statistics = CreateDBStatistics();
|
||||
Reopen(opts);
|
||||
|
||||
for (int i = 0; i < kNumFiles; ++i) {
|
||||
if (i > 0) {
|
||||
// range tombstone covers first half of the previous file
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
|
||||
GetNumericStr((i - 1) * kNumPerFile),
|
||||
GetNumericStr((i - 1) * kNumPerFile + kNumPerFile / 2));
|
||||
}
|
||||
// Make sure a given key appears in each file so compaction won't be able to
|
||||
// use trivial move, which would happen if the ranges were non-overlapping.
|
||||
// Also, we need an extra element since flush is only triggered when the
|
||||
// number of keys is one greater than SpecialSkipListFactory's limit.
|
||||
// We choose a key outside the key-range used by the test to avoid conflict.
|
||||
db_->Put(WriteOptions(), GetNumericStr(kNumPerFile * kNumFiles), "val");
|
||||
|
||||
for (int j = 0; j < kNumPerFile; ++j) {
|
||||
db_->Put(WriteOptions(), GetNumericStr(i * kNumPerFile + j), "val");
|
||||
}
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
ASSERT_EQ(i + 1, NumTableFilesAtLevel(0));
|
||||
}
|
||||
db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_GT(NumTableFilesAtLevel(1), 0);
|
||||
ASSERT_EQ((kNumFiles - 1) * kNumPerFile / 2,
|
||||
TestGetTickerCount(opts, COMPACTION_KEY_DROP_RANGE_DEL));
|
||||
|
||||
for (int i = 0; i < kNumFiles; ++i) {
|
||||
for (int j = 0; j < kNumPerFile; ++j) {
|
||||
ReadOptions read_opts;
|
||||
read_opts.ignore_range_deletions = true;
|
||||
std::string value;
|
||||
if (i == kNumFiles - 1 || j >= kNumPerFile / 2) {
|
||||
ASSERT_OK(
|
||||
db_->Get(read_opts, GetNumericStr(i * kNumPerFile + j), &value));
|
||||
} else {
|
||||
ASSERT_TRUE(
|
||||
db_->Get(read_opts, GetNumericStr(i * kNumPerFile + j), &value)
|
||||
.IsNotFound());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, ValidLevelSubcompactionBoundaries) {
|
||||
const int kNumPerFile = 100, kNumFiles = 4, kFileBytes = 100 << 10;
|
||||
Options options = CurrentOptions();
|
||||
options.level0_file_num_compaction_trigger = kNumFiles;
|
||||
options.max_bytes_for_level_base = 2 * kFileBytes;
|
||||
options.max_subcompactions = 4;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumPerFile));
|
||||
options.num_levels = 3;
|
||||
options.target_file_size_base = kFileBytes;
|
||||
options.target_file_size_multiplier = 1;
|
||||
Reopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
for (int j = 0; j < kNumFiles; ++j) {
|
||||
if (i > 0) {
|
||||
// delete [95,105) in two files, [295,305) in next two
|
||||
int mid = (j + (1 - j % 2)) * kNumPerFile;
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
|
||||
Key(mid - 5), Key(mid + 5));
|
||||
}
|
||||
std::vector<std::string> values;
|
||||
// Write 100KB (100 values, each 1K)
|
||||
for (int k = 0; k < kNumPerFile; k++) {
|
||||
values.push_back(RandomString(&rnd, 990));
|
||||
ASSERT_OK(Put(Key(j * kNumPerFile + k), values[k]));
|
||||
}
|
||||
// put extra key to trigger flush
|
||||
ASSERT_OK(Put("", ""));
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
if (j < kNumFiles - 1) {
|
||||
// background compaction may happen early for kNumFiles'th file
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), j + 1);
|
||||
}
|
||||
if (j == options.level0_file_num_compaction_trigger - 1) {
|
||||
// When i == 1, compaction will output some files to L1, at which point
|
||||
// L1 is not bottommost so range deletions cannot be compacted away. The
|
||||
// new L1 files must be generated with non-overlapping key ranges even
|
||||
// though multiple subcompactions see the same ranges deleted, else an
|
||||
// assertion will fail.
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 0);
|
||||
ASSERT_GT(NumTableFilesAtLevel(1), 0);
|
||||
ASSERT_GT(NumTableFilesAtLevel(2), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, ValidUniversalSubcompactionBoundaries) {
|
||||
const int kNumPerFile = 100, kFilesPerLevel = 4, kNumLevels = 4;
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_options_universal.min_merge_width = kFilesPerLevel;
|
||||
options.compaction_options_universal.max_merge_width = kFilesPerLevel;
|
||||
options.compaction_options_universal.size_ratio = 10;
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.level0_file_num_compaction_trigger = kFilesPerLevel;
|
||||
options.max_subcompactions = 4;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumPerFile));
|
||||
options.num_levels = kNumLevels;
|
||||
options.target_file_size_base = kNumPerFile << 10;
|
||||
options.target_file_size_multiplier = 1;
|
||||
Reopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < kNumLevels - 1; ++i) {
|
||||
for (int j = 0; j < kFilesPerLevel; ++j) {
|
||||
if (i == kNumLevels - 2) {
|
||||
// insert range deletions [95,105) in two files, [295,305) in next two
|
||||
// to prepare L1 for later manual compaction.
|
||||
int mid = (j + (1 - j % 2)) * kNumPerFile;
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
|
||||
Key(mid - 5), Key(mid + 5));
|
||||
}
|
||||
std::vector<std::string> values;
|
||||
// Write 100KB (100 values, each 1K)
|
||||
for (int k = 0; k < kNumPerFile; k++) {
|
||||
values.push_back(RandomString(&rnd, 990));
|
||||
ASSERT_OK(Put(Key(j * kNumPerFile + k), values[k]));
|
||||
}
|
||||
// put extra key to trigger flush
|
||||
ASSERT_OK(Put("", ""));
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
if (j < kFilesPerLevel - 1) {
|
||||
// background compaction may happen early for kFilesPerLevel'th file
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), j + 1);
|
||||
}
|
||||
}
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 0);
|
||||
ASSERT_GT(NumTableFilesAtLevel(kNumLevels - 1 - i), kFilesPerLevel - 1);
|
||||
}
|
||||
// Now L1-L3 are full, when we compact L1->L2 we should see (1) subcompactions
|
||||
// happen since input level > 0; (2) range deletions are not dropped since
|
||||
// output level is not bottommost. If no file boundary assertion fails, that
|
||||
// probably means universal compaction + subcompaction + range deletion are
|
||||
// compatible.
|
||||
ASSERT_OK(dbfull()->RunManualCompaction(
|
||||
reinterpret_cast<ColumnFamilyHandleImpl*>(db_->DefaultColumnFamily())
|
||||
->cfd(),
|
||||
1 /* input_level */, 2 /* output_level */, 0 /* output_path_id */,
|
||||
nullptr /* begin */, nullptr /* end */, true /* exclusive */,
|
||||
true /* disallow_trivial_move */));
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBRangeDelTest, CompactionRemovesCoveredMergeOperands) {
|
||||
const int kNumPerFile = 3, kNumFiles = 3;
|
||||
Options opts = CurrentOptions();
|
||||
opts.disable_auto_compactions = true;
|
||||
opts.memtable_factory.reset(new SpecialSkipListFactory(2 * kNumPerFile));
|
||||
opts.merge_operator = MergeOperators::CreateUInt64AddOperator();
|
||||
opts.num_levels = 2;
|
||||
Reopen(opts);
|
||||
|
||||
// Iterates kNumFiles * kNumPerFile + 1 times since flushing the last file
|
||||
// requires an extra entry.
|
||||
for (int i = 0; i <= kNumFiles * kNumPerFile; ++i) {
|
||||
if (i % kNumPerFile == 0 && i / kNumPerFile == kNumFiles - 1) {
|
||||
// Delete merge operands from all but the last file
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "key",
|
||||
"key_");
|
||||
}
|
||||
std::string val;
|
||||
PutFixed64(&val, i);
|
||||
db_->Merge(WriteOptions(), "key", val);
|
||||
// we need to prevent trivial move using Puts so compaction will actually
|
||||
// process the merge operands.
|
||||
db_->Put(WriteOptions(), "prevent_trivial_move", "");
|
||||
if (i > 0 && i % kNumPerFile == 0) {
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
}
|
||||
}
|
||||
|
||||
ReadOptions read_opts;
|
||||
read_opts.ignore_range_deletions = true;
|
||||
std::string expected, actual;
|
||||
ASSERT_OK(db_->Get(read_opts, "key", &actual));
|
||||
PutFixed64(&expected, 45); // 1+2+...+9
|
||||
ASSERT_EQ(expected, actual);
|
||||
|
||||
db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
|
||||
expected.clear();
|
||||
ASSERT_OK(db_->Get(read_opts, "key", &actual));
|
||||
uint64_t tmp;
|
||||
Slice tmp2(actual);
|
||||
GetFixed64(&tmp2, &tmp);
|
||||
PutFixed64(&expected, 30); // 6+7+8+9 (earlier operands covered by tombstone)
|
||||
ASSERT_EQ(expected, actual);
|
||||
}
|
||||
|
||||
// NumTableFilesAtLevel() is not supported in ROCKSDB_LITE
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBRangeDelTest, ObsoleteTombstoneCleanup) {
|
||||
// During compaction to bottommost level, verify range tombstones older than
|
||||
// the oldest snapshot are removed, while others are preserved.
|
||||
Options opts = CurrentOptions();
|
||||
opts.disable_auto_compactions = true;
|
||||
opts.num_levels = 2;
|
||||
opts.statistics = CreateDBStatistics();
|
||||
Reopen(opts);
|
||||
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "dr1",
|
||||
"dr1"); // obsolete after compaction
|
||||
db_->Put(WriteOptions(), "key", "val");
|
||||
db_->Flush(FlushOptions());
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "dr2",
|
||||
"dr2"); // protected by snapshot
|
||||
db_->Put(WriteOptions(), "key", "val");
|
||||
db_->Flush(FlushOptions());
|
||||
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(1));
|
||||
db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(1));
|
||||
ASSERT_EQ(1, TestGetTickerCount(opts, COMPACTION_RANGE_DEL_DROP_OBSOLETE));
|
||||
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
rocksdb::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -299,8 +299,31 @@ TEST_F(DBSSTTest, RateLimitedDelete) {
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DeleteScheduler::BackgroundEmptyTrash:Wait",
|
||||
[&](void* arg) { penalties.push_back(*(static_cast<int*>(arg))); });
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"InstrumentedCondVar::TimedWaitInternal", [&](void* arg) {
|
||||
// Turn timed wait into a simulated sleep
|
||||
uint64_t* abs_time_us = static_cast<uint64_t*>(arg);
|
||||
int64_t cur_time = 0;
|
||||
env_->GetCurrentTime(&cur_time);
|
||||
if (*abs_time_us > static_cast<uint64_t>(cur_time)) {
|
||||
env_->addon_time_.fetch_add(*abs_time_us -
|
||||
static_cast<uint64_t>(cur_time));
|
||||
}
|
||||
|
||||
// Randomly sleep shortly
|
||||
env_->addon_time_.fetch_add(
|
||||
static_cast<uint64_t>(Random::GetTLSInstance()->Uniform(10)));
|
||||
|
||||
// Set wait until time to before current to force not to sleep.
|
||||
int64_t real_cur_time = 0;
|
||||
Env::Default()->GetCurrentTime(&real_cur_time);
|
||||
*abs_time_us = static_cast<uint64_t>(real_cur_time);
|
||||
});
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
env_->no_slowdown_ = true;
|
||||
env_->time_elapse_only_sleep_ = true;
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.env = env_;
|
||||
@@ -348,6 +371,7 @@ TEST_F(DBSSTTest, RateLimitedDelete) {
|
||||
ASSERT_EQ(expected_penlty, penalties[i]);
|
||||
}
|
||||
ASSERT_GT(time_spent_deleting, expected_penlty * 0.9);
|
||||
ASSERT_LT(time_spent_deleting, expected_penlty * 1.1);
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
@@ -305,6 +305,7 @@ TEST_F(DBTestTailingIterator, TailingIteratorPrefixSeek) {
|
||||
options.disable_auto_compactions = true;
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(2));
|
||||
options.memtable_factory.reset(NewHashSkipListRepFactory(16));
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
@@ -428,6 +429,59 @@ TEST_F(DBTestTailingIterator, TailingIteratorUpperBound) {
|
||||
ASSERT_EQ(0, immutable_seeks);
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, TailingIteratorGap) {
|
||||
// level 1: [20, 25] [35, 40]
|
||||
// level 2: [10 - 15] [45 - 50]
|
||||
// level 3: [20, 30, 40]
|
||||
// Previously there is a bug in tailing_iterator that if there is a gap in
|
||||
// lower level, the key will be skipped if it is within the range between
|
||||
// the largest key of index n file and the smallest key of index n+1 file
|
||||
// if both file fit in that gap. In this example, 25 < key < 35
|
||||
// https://github.com/facebook/rocksdb/issues/1372
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
|
||||
ASSERT_OK(Put(1, "20", "20"));
|
||||
ASSERT_OK(Put(1, "30", "30"));
|
||||
ASSERT_OK(Put(1, "40", "40"));
|
||||
ASSERT_OK(Flush(1));
|
||||
MoveFilesToLevel(3, 1);
|
||||
|
||||
ASSERT_OK(Put(1, "10", "10"));
|
||||
ASSERT_OK(Put(1, "15", "15"));
|
||||
ASSERT_OK(Flush(1));
|
||||
ASSERT_OK(Put(1, "45", "45"));
|
||||
ASSERT_OK(Put(1, "50", "50"));
|
||||
ASSERT_OK(Flush(1));
|
||||
MoveFilesToLevel(2, 1);
|
||||
|
||||
ASSERT_OK(Put(1, "20", "20"));
|
||||
ASSERT_OK(Put(1, "25", "25"));
|
||||
ASSERT_OK(Flush(1));
|
||||
ASSERT_OK(Put(1, "35", "35"));
|
||||
ASSERT_OK(Put(1, "40", "40"));
|
||||
ASSERT_OK(Flush(1));
|
||||
MoveFilesToLevel(1, 1);
|
||||
|
||||
ColumnFamilyMetaData meta;
|
||||
db_->GetColumnFamilyMetaData(handles_[1], &meta);
|
||||
|
||||
std::unique_ptr<Iterator> it(db_->NewIterator(read_options, handles_[1]));
|
||||
it->Seek("30");
|
||||
ASSERT_TRUE(it->Valid());
|
||||
ASSERT_EQ("30", it->key().ToString());
|
||||
|
||||
it->Next();
|
||||
ASSERT_TRUE(it->Valid());
|
||||
ASSERT_EQ("35", it->key().ToString());
|
||||
|
||||
it->Next();
|
||||
ASSERT_TRUE(it->Valid());
|
||||
ASSERT_EQ("40", it->key().ToString());
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, ManagedTailingIteratorSingle) {
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
@@ -572,6 +626,7 @@ TEST_F(DBTestTailingIterator, ManagedTailingIteratorPrefixSeek) {
|
||||
options.disable_auto_compactions = true;
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(2));
|
||||
options.memtable_factory.reset(NewHashSkipListRepFactory(16));
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
@@ -699,6 +754,58 @@ TEST_F(DBTestTailingIterator, ForwardIteratorVersionProperty) {
|
||||
}
|
||||
ASSERT_EQ(v3, v4);
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, SeekWithUpperBoundBug) {
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
const Slice upper_bound("cc", 3);
|
||||
read_options.iterate_upper_bound = &upper_bound;
|
||||
|
||||
|
||||
// 1st L0 file
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "aa", "SEEN"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// 2nd L0 file
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "zz", "NOT-SEEN"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
|
||||
iter->Seek("aa");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().ToString(), "aa");
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, SeekToFirstWithUpperBoundBug) {
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
const Slice upper_bound("cc", 3);
|
||||
read_options.iterate_upper_bound = &upper_bound;
|
||||
|
||||
|
||||
// 1st L0 file
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "aa", "SEEN"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// 2nd L0 file
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "zz", "NOT-SEEN"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
|
||||
iter->SeekToFirst();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().ToString(), "aa");
|
||||
|
||||
iter->Next();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
|
||||
iter->SeekToFirst();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().ToString(), "aa");
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
#endif // !defined(ROCKSDB_LITE)
|
||||
|
||||
+84
-24
@@ -215,6 +215,50 @@ TEST_F(DBTest, WriteEmptyBatch) {
|
||||
ASSERT_EQ("bar", Get(1, "foo"));
|
||||
}
|
||||
|
||||
TEST_F(DBTest, SkipDelay) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.write_buffer_size = 100000;
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
for (bool sync : {true, false}) {
|
||||
for (bool disableWAL : {true, false}) {
|
||||
// Use a small number to ensure a large delay that is still effective
|
||||
// when we do Put
|
||||
// TODO(myabandeh): this is time dependent and could potentially make
|
||||
// the test flaky
|
||||
auto token = dbfull()->TEST_write_controler().GetDelayToken(1);
|
||||
std::atomic<int> sleep_count(0);
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::DelayWrite:Sleep",
|
||||
[&](void* arg) { sleep_count.fetch_add(1); });
|
||||
std::atomic<int> wait_count(0);
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::DelayWrite:Wait",
|
||||
[&](void* arg) { wait_count.fetch_add(1); });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
WriteOptions wo;
|
||||
wo.sync = sync;
|
||||
wo.disableWAL = disableWAL;
|
||||
wo.no_slowdown = true;
|
||||
dbfull()->Put(wo, "foo", "bar");
|
||||
// We need the 2nd write to trigger delay. This is because delay is
|
||||
// estimated based on the last write size which is 0 for the first write.
|
||||
ASSERT_NOK(dbfull()->Put(wo, "foo2", "bar2"));
|
||||
ASSERT_GE(sleep_count.load(), 0);
|
||||
ASSERT_GE(wait_count.load(), 0);
|
||||
token.reset();
|
||||
|
||||
token = dbfull()->TEST_write_controler().GetDelayToken(1000000000);
|
||||
wo.no_slowdown = false;
|
||||
ASSERT_OK(dbfull()->Put(wo, "foo3", "bar3"));
|
||||
ASSERT_GE(sleep_count.load(), 1);
|
||||
token.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBTest, ReadOnlyDB) {
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
@@ -1252,7 +1296,9 @@ TEST_F(DBTest, MinLevelToCompress2) {
|
||||
MinLevelHelper(this, options);
|
||||
}
|
||||
|
||||
TEST_F(DBTest, RepeatedWritesToSameKey) {
|
||||
// This test may fail because of a legit case that multiple L0 files
|
||||
// are trivial moved to L1.
|
||||
TEST_F(DBTest, DISABLED_RepeatedWritesToSameKey) {
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
@@ -2467,11 +2513,13 @@ class MultiThreadedDBTest : public DBTest,
|
||||
TEST_P(MultiThreadedDBTest, MultiThreaded) {
|
||||
anon::OptionsOverride options_override;
|
||||
options_override.skip_policy = kSkipNoSnapshot;
|
||||
Options options = CurrentOptions(options_override);
|
||||
std::vector<std::string> cfs;
|
||||
for (int i = 1; i < kColumnFamilies; ++i) {
|
||||
cfs.push_back(ToString(i));
|
||||
}
|
||||
CreateAndReopenWithCF(cfs, CurrentOptions(options_override));
|
||||
Reopen(options);
|
||||
CreateAndReopenWithCF(cfs, options);
|
||||
// Initialize state
|
||||
MTState mt;
|
||||
mt.test = this;
|
||||
@@ -2646,15 +2694,11 @@ class ModelDB : public DB {
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
using DB::AddFile;
|
||||
virtual Status AddFile(ColumnFamilyHandle* column_family,
|
||||
const std::vector<ExternalSstFileInfo>& file_info_list,
|
||||
bool move_file, bool skip_snapshot_check) override {
|
||||
return Status::NotSupported("Not implemented.");
|
||||
}
|
||||
virtual Status AddFile(ColumnFamilyHandle* column_family,
|
||||
const std::vector<std::string>& file_path_list,
|
||||
bool move_file, bool skip_snapshot_check) override {
|
||||
using DB::IngestExternalFile;
|
||||
virtual Status IngestExternalFile(
|
||||
ColumnFamilyHandle* column_family,
|
||||
const std::vector<std::string>& external_files,
|
||||
const IngestExternalFileOptions& options) override {
|
||||
return Status::NotSupported("Not implemented.");
|
||||
}
|
||||
|
||||
@@ -2742,6 +2786,12 @@ class ModelDB : public DB {
|
||||
const Slice& property, uint64_t* value) override {
|
||||
return false;
|
||||
}
|
||||
using DB::GetMapProperty;
|
||||
virtual bool GetMapProperty(ColumnFamilyHandle* column_family,
|
||||
const Slice& property,
|
||||
std::map<std::string, double>* value) override {
|
||||
return false;
|
||||
}
|
||||
using DB::GetAggregatedIntProperty;
|
||||
virtual bool GetAggregatedIntProperty(const Slice& property,
|
||||
uint64_t* value) override {
|
||||
@@ -3648,14 +3698,24 @@ TEST_F(DBTest, GetThreadStatus) {
|
||||
env_->SetBackgroundThreads(kHighPriCounts[test], Env::HIGH);
|
||||
env_->SetBackgroundThreads(kLowPriCounts[test], Env::LOW);
|
||||
// Wait to ensure the all threads has been registered
|
||||
env_->SleepForMicroseconds(100000);
|
||||
s = env_->GetThreadList(&thread_list);
|
||||
ASSERT_OK(s);
|
||||
unsigned int thread_type_counts[ThreadStatus::NUM_THREAD_TYPES];
|
||||
memset(thread_type_counts, 0, sizeof(thread_type_counts));
|
||||
for (auto thread : thread_list) {
|
||||
ASSERT_LT(thread.thread_type, ThreadStatus::NUM_THREAD_TYPES);
|
||||
thread_type_counts[thread.thread_type]++;
|
||||
// Try up to 60 seconds.
|
||||
for (int num_try = 0; num_try < 60000; num_try++) {
|
||||
env_->SleepForMicroseconds(1000);
|
||||
thread_list.clear();
|
||||
s = env_->GetThreadList(&thread_list);
|
||||
ASSERT_OK(s);
|
||||
memset(thread_type_counts, 0, sizeof(thread_type_counts));
|
||||
for (auto thread : thread_list) {
|
||||
ASSERT_LT(thread.thread_type, ThreadStatus::NUM_THREAD_TYPES);
|
||||
thread_type_counts[thread.thread_type]++;
|
||||
}
|
||||
if (thread_type_counts[ThreadStatus::HIGH_PRIORITY] ==
|
||||
kHighPriCounts[test] &&
|
||||
thread_type_counts[ThreadStatus::LOW_PRIORITY] ==
|
||||
kLowPriCounts[test]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Verify the total number of threades
|
||||
ASSERT_EQ(thread_type_counts[ThreadStatus::HIGH_PRIORITY] +
|
||||
@@ -4878,7 +4938,7 @@ TEST_F(DBTest, MergeTestTime) {
|
||||
SetPerfLevel(kEnableTime);
|
||||
this->env_->addon_time_.store(0);
|
||||
this->env_->time_elapse_only_sleep_ = true;
|
||||
this->env_->no_sleep_ = true;
|
||||
this->env_->no_slowdown_ = true;
|
||||
Options options = CurrentOptions();
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
options.merge_operator.reset(new DelayedMergeOperator(this));
|
||||
@@ -5318,12 +5378,12 @@ TEST_F(DBTest, FlushesInParallelWithCompactRange) {
|
||||
|
||||
TEST_F(DBTest, DelayedWriteRate) {
|
||||
const int kEntriesPerMemTable = 100;
|
||||
const int kTotalFlushes = 20;
|
||||
const int kTotalFlushes = 12;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
env_->SetBackgroundThreads(1, Env::LOW);
|
||||
options.env = env_;
|
||||
env_->no_sleep_ = true;
|
||||
env_->no_slowdown_ = true;
|
||||
options.write_buffer_size = 100000000;
|
||||
options.max_write_buffer_number = 256;
|
||||
options.max_background_compactions = 1;
|
||||
@@ -5368,8 +5428,8 @@ TEST_F(DBTest, DelayedWriteRate) {
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
estimated_sleep_time += size_memtable * 1000000u / cur_rate;
|
||||
// Slow down twice. One for memtable switch and one for flush finishes.
|
||||
cur_rate = static_cast<uint64_t>(static_cast<double>(cur_rate) /
|
||||
kSlowdownRatio / kSlowdownRatio);
|
||||
cur_rate = static_cast<uint64_t>(static_cast<double>(cur_rate) *
|
||||
kIncSlowdownRatio * kIncSlowdownRatio);
|
||||
}
|
||||
// Estimate the total sleep time fall into the rough range.
|
||||
ASSERT_GT(env_->addon_time_.load(),
|
||||
@@ -5377,7 +5437,7 @@ TEST_F(DBTest, DelayedWriteRate) {
|
||||
ASSERT_LT(env_->addon_time_.load(),
|
||||
static_cast<int64_t>(estimated_sleep_time * 2));
|
||||
|
||||
env_->no_sleep_ = false;
|
||||
env_->no_slowdown_ = false;
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
sleeping_task_low.WakeUp();
|
||||
sleeping_task_low.WaitUntilDone();
|
||||
|
||||
@@ -956,6 +956,7 @@ TEST_F(DBTest2, PresetCompressionDict) {
|
||||
const int kNumL0Files = 5;
|
||||
|
||||
Options options;
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
options.arena_block_size = kBlockSizeBytes;
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.create_if_missing = true;
|
||||
@@ -1469,6 +1470,10 @@ class MockPersistentCache : public PersistentCache {
|
||||
|
||||
virtual ~MockPersistentCache() {}
|
||||
|
||||
PersistentCache::StatsType Stats() override {
|
||||
return PersistentCache::StatsType();
|
||||
}
|
||||
|
||||
Status Insert(const Slice& page_key, const char* data,
|
||||
const size_t size) override {
|
||||
MutexLock _(&lock_);
|
||||
@@ -2085,8 +2090,26 @@ TEST_F(DBTest2, AutomaticCompactionOverlapManualCompaction) {
|
||||
cro.target_level = 2;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
|
||||
auto get_stat = [](std::string level_str, LevelStatType type,
|
||||
std::map<std::string, double> props) {
|
||||
auto prop_str =
|
||||
level_str + "." +
|
||||
InternalStats::compaction_level_stats.at(type).property_name.c_str();
|
||||
auto prop_item = props.find(prop_str);
|
||||
return prop_item == props.end() ? 0 : prop_item->second;
|
||||
};
|
||||
|
||||
// Trivial move 2 files to L2
|
||||
ASSERT_EQ("0,0,2", FilesPerLevel());
|
||||
// Also test that the stats GetMapProperty API reporting the same result
|
||||
{
|
||||
std::map<std::string, double> prop;
|
||||
ASSERT_TRUE(dbfull()->GetMapProperty("rocksdb.cfstats", &prop));
|
||||
ASSERT_EQ(0, get_stat("L0", LevelStatType::NUM_FILES, prop));
|
||||
ASSERT_EQ(0, get_stat("L1", LevelStatType::NUM_FILES, prop));
|
||||
ASSERT_EQ(2, get_stat("L2", LevelStatType::NUM_FILES, prop));
|
||||
ASSERT_EQ(2, get_stat("Sum", LevelStatType::NUM_FILES, prop));
|
||||
}
|
||||
|
||||
// While the compaction is running, we will create 2 new files that
|
||||
// can fit in L2, these 2 files will be moved to L2 and overlap with
|
||||
@@ -2113,6 +2136,13 @@ TEST_F(DBTest2, AutomaticCompactionOverlapManualCompaction) {
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
// Test that the stats GetMapProperty API reporting 1 file in L2
|
||||
{
|
||||
std::map<std::string, double> prop;
|
||||
ASSERT_TRUE(dbfull()->GetMapProperty("rocksdb.cfstats", &prop));
|
||||
ASSERT_EQ(1, get_stat("L2", LevelStatType::NUM_FILES, prop));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, ManualCompactionOverlapManualCompaction) {
|
||||
@@ -2182,6 +2212,18 @@ TEST_F(DBTest2, ManualCompactionOverlapManualCompaction) {
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, OptimizeForPointLookup) {
|
||||
Options options = CurrentOptions();
|
||||
Close();
|
||||
options.OptimizeForPointLookup(2);
|
||||
ASSERT_OK(DB::Open(options, dbname_, &db_));
|
||||
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
ASSERT_EQ("v1", Get("foo"));
|
||||
Flush();
|
||||
ASSERT_EQ("v1", Get("foo"));
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
+103
-35
@@ -8,6 +8,7 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "db/forward_iterator.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -19,7 +20,7 @@ SpecialEnv::SpecialEnv(Env* base)
|
||||
sleep_counter_(this),
|
||||
addon_time_(0),
|
||||
time_elapse_only_sleep_(false),
|
||||
no_sleep_(false) {
|
||||
no_slowdown_(false) {
|
||||
delay_sstable_sync_.store(false, std::memory_order_release);
|
||||
drop_writes_.store(false, std::memory_order_release);
|
||||
no_space_.store(false, std::memory_order_release);
|
||||
@@ -233,6 +234,7 @@ Options DBTestBase::CurrentOptions(
|
||||
case kHashSkipList:
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(1));
|
||||
options.memtable_factory.reset(NewHashSkipListRepFactory(16));
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
break;
|
||||
case kPlainTableFirstBytePrefix:
|
||||
options.table_factory.reset(new PlainTableFactory());
|
||||
@@ -264,15 +266,18 @@ Options DBTestBase::CurrentOptions(
|
||||
break;
|
||||
case kVectorRep:
|
||||
options.memtable_factory.reset(new VectorRepFactory(100));
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
break;
|
||||
case kHashLinkList:
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(1));
|
||||
options.memtable_factory.reset(
|
||||
NewHashLinkListRepFactory(4, 0, 3, true, 4));
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
break;
|
||||
case kHashCuckoo:
|
||||
options.memtable_factory.reset(
|
||||
NewHashCuckooRepFactory(options.write_buffer_size));
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
break;
|
||||
#endif // ROCKSDB_LITE
|
||||
case kMergePut:
|
||||
@@ -497,6 +502,15 @@ Status DBTestBase::Put(int cf, const Slice& k, const Slice& v,
|
||||
}
|
||||
}
|
||||
|
||||
Status DBTestBase::Merge(const Slice& k, const Slice& v, WriteOptions wo) {
|
||||
return db_->Merge(wo, k, v);
|
||||
}
|
||||
|
||||
Status DBTestBase::Merge(int cf, const Slice& k, const Slice& v,
|
||||
WriteOptions wo) {
|
||||
return db_->Merge(wo, handles_[cf], k, v);
|
||||
}
|
||||
|
||||
Status DBTestBase::Delete(const std::string& k) {
|
||||
return db_->Delete(WriteOptions(), k);
|
||||
}
|
||||
@@ -585,11 +599,15 @@ std::string DBTestBase::Contents(int cf) {
|
||||
|
||||
std::string DBTestBase::AllEntriesFor(const Slice& user_key, int cf) {
|
||||
Arena arena;
|
||||
auto options = CurrentOptions();
|
||||
InternalKeyComparator icmp(options.comparator);
|
||||
RangeDelAggregator range_del_agg(icmp, {} /* snapshots */);
|
||||
ScopedArenaIterator iter;
|
||||
if (cf == 0) {
|
||||
iter.set(dbfull()->NewInternalIterator(&arena));
|
||||
iter.set(dbfull()->NewInternalIterator(&arena, &range_del_agg));
|
||||
} else {
|
||||
iter.set(dbfull()->NewInternalIterator(&arena, handles_[cf]));
|
||||
iter.set(
|
||||
dbfull()->NewInternalIterator(&arena, &range_del_agg, handles_[cf]));
|
||||
}
|
||||
InternalKey target(user_key, kMaxSequenceNumber, kTypeValue);
|
||||
iter->Seek(target.Encode());
|
||||
@@ -990,10 +1008,14 @@ UpdateStatus DBTestBase::updateInPlaceNoAction(char* prevValue,
|
||||
void DBTestBase::validateNumberOfEntries(int numValues, int cf) {
|
||||
ScopedArenaIterator iter;
|
||||
Arena arena;
|
||||
auto options = CurrentOptions();
|
||||
InternalKeyComparator icmp(options.comparator);
|
||||
RangeDelAggregator range_del_agg(icmp, {} /* snapshots */);
|
||||
if (cf != 0) {
|
||||
iter.set(dbfull()->NewInternalIterator(&arena, handles_[cf]));
|
||||
iter.set(
|
||||
dbfull()->NewInternalIterator(&arena, &range_del_agg, handles_[cf]));
|
||||
} else {
|
||||
iter.set(dbfull()->NewInternalIterator(&arena));
|
||||
iter.set(dbfull()->NewInternalIterator(&arena, &range_del_agg));
|
||||
}
|
||||
iter->SeekToFirst();
|
||||
ASSERT_EQ(iter->status().ok(), true);
|
||||
@@ -1077,11 +1099,18 @@ std::vector<std::uint64_t> DBTestBase::ListTableFiles(Env* env,
|
||||
}
|
||||
|
||||
void DBTestBase::VerifyDBFromMap(std::map<std::string, std::string> true_data,
|
||||
size_t* total_reads_res) {
|
||||
size_t* total_reads_res, bool tailing_iter,
|
||||
std::map<std::string, Status> status) {
|
||||
size_t total_reads = 0;
|
||||
|
||||
for (auto& kv : true_data) {
|
||||
ASSERT_EQ(Get(kv.first), kv.second);
|
||||
Status s = status[kv.first];
|
||||
if (s.ok()) {
|
||||
ASSERT_EQ(Get(kv.first), kv.second);
|
||||
} else {
|
||||
std::string value;
|
||||
ASSERT_EQ(s, db_->Get(ReadOptions(), kv.first, &value));
|
||||
}
|
||||
total_reads++;
|
||||
}
|
||||
|
||||
@@ -1094,21 +1123,40 @@ void DBTestBase::VerifyDBFromMap(std::map<std::string, std::string> true_data,
|
||||
// Verify Iterator::Next()
|
||||
iter_cnt = 0;
|
||||
auto data_iter = true_data.begin();
|
||||
Status s;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next(), data_iter++) {
|
||||
ASSERT_EQ(iter->key().ToString(), data_iter->first);
|
||||
ASSERT_EQ(iter->value().ToString(), data_iter->second);
|
||||
Status current_status = status[data_iter->first];
|
||||
if (!current_status.ok()) {
|
||||
s = current_status;
|
||||
}
|
||||
ASSERT_EQ(iter->status(), s);
|
||||
if (current_status.ok()) {
|
||||
ASSERT_EQ(iter->value().ToString(), data_iter->second);
|
||||
}
|
||||
iter_cnt++;
|
||||
total_reads++;
|
||||
}
|
||||
ASSERT_EQ(data_iter, true_data.end()) << iter_cnt << " / "
|
||||
<< true_data.size();
|
||||
delete iter;
|
||||
|
||||
// Verify Iterator::Prev()
|
||||
// Use a new iterator to make sure its status is clean.
|
||||
iter = db_->NewIterator(ro);
|
||||
iter_cnt = 0;
|
||||
s = Status::OK();
|
||||
auto data_rev = true_data.rbegin();
|
||||
for (iter->SeekToLast(); iter->Valid(); iter->Prev(), data_rev++) {
|
||||
ASSERT_EQ(iter->key().ToString(), data_rev->first);
|
||||
ASSERT_EQ(iter->value().ToString(), data_rev->second);
|
||||
Status current_status = status[data_rev->first];
|
||||
if (!current_status.ok()) {
|
||||
s = current_status;
|
||||
}
|
||||
ASSERT_EQ(iter->status(), s);
|
||||
if (current_status.ok()) {
|
||||
ASSERT_EQ(iter->value().ToString(), data_rev->second);
|
||||
}
|
||||
iter_cnt++;
|
||||
total_reads++;
|
||||
}
|
||||
@@ -1122,46 +1170,66 @@ void DBTestBase::VerifyDBFromMap(std::map<std::string, std::string> true_data,
|
||||
ASSERT_EQ(kv.second, iter->value().ToString());
|
||||
total_reads++;
|
||||
}
|
||||
|
||||
delete iter;
|
||||
}
|
||||
|
||||
if (tailing_iter) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Tailing iterator
|
||||
int iter_cnt = 0;
|
||||
ReadOptions ro;
|
||||
ro.tailing = true;
|
||||
ro.total_order_seek = true;
|
||||
Iterator* iter = db_->NewIterator(ro);
|
||||
// Tailing iterator
|
||||
int iter_cnt = 0;
|
||||
ReadOptions ro;
|
||||
ro.tailing = true;
|
||||
ro.total_order_seek = true;
|
||||
Iterator* iter = db_->NewIterator(ro);
|
||||
|
||||
// Verify ForwardIterator::Next()
|
||||
iter_cnt = 0;
|
||||
auto data_iter = true_data.begin();
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next(), data_iter++) {
|
||||
ASSERT_EQ(iter->key().ToString(), data_iter->first);
|
||||
ASSERT_EQ(iter->value().ToString(), data_iter->second);
|
||||
iter_cnt++;
|
||||
total_reads++;
|
||||
}
|
||||
ASSERT_EQ(data_iter, true_data.end()) << iter_cnt << " / "
|
||||
<< true_data.size();
|
||||
// Verify ForwardIterator::Next()
|
||||
iter_cnt = 0;
|
||||
auto data_iter = true_data.begin();
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next(), data_iter++) {
|
||||
ASSERT_EQ(iter->key().ToString(), data_iter->first);
|
||||
ASSERT_EQ(iter->value().ToString(), data_iter->second);
|
||||
iter_cnt++;
|
||||
total_reads++;
|
||||
}
|
||||
ASSERT_EQ(data_iter, true_data.end()) << iter_cnt << " / "
|
||||
<< true_data.size();
|
||||
|
||||
// Verify ForwardIterator::Seek()
|
||||
for (auto kv : true_data) {
|
||||
iter->Seek(kv.first);
|
||||
ASSERT_EQ(kv.first, iter->key().ToString());
|
||||
ASSERT_EQ(kv.second, iter->value().ToString());
|
||||
total_reads++;
|
||||
}
|
||||
// Verify ForwardIterator::Seek()
|
||||
for (auto kv : true_data) {
|
||||
iter->Seek(kv.first);
|
||||
ASSERT_EQ(kv.first, iter->key().ToString());
|
||||
ASSERT_EQ(kv.second, iter->value().ToString());
|
||||
total_reads++;
|
||||
}
|
||||
|
||||
delete iter;
|
||||
delete iter;
|
||||
#endif // ROCKSDB_LITE
|
||||
}
|
||||
|
||||
if (total_reads_res) {
|
||||
*total_reads_res = total_reads;
|
||||
}
|
||||
}
|
||||
|
||||
void DBTestBase::VerifyDBInternal(
|
||||
std::vector<std::pair<std::string, std::string>> true_data) {
|
||||
Arena arena;
|
||||
InternalKeyComparator icmp(last_options_.comparator);
|
||||
RangeDelAggregator range_del_agg(icmp, {});
|
||||
auto iter = dbfull()->NewInternalIterator(&arena, &range_del_agg);
|
||||
iter->SeekToFirst();
|
||||
for (auto p : true_data) {
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ParsedInternalKey ikey;
|
||||
ASSERT_TRUE(ParseInternalKey(iter->key(), &ikey));
|
||||
ASSERT_EQ(p.first, ikey.user_key);
|
||||
ASSERT_EQ(p.second, iter->value());
|
||||
iter->Next();
|
||||
};
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
iter->~InternalIterator();
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
uint64_t DBTestBase::GetNumberOfSstFilesForColumnFamily(
|
||||
|
||||
+19
-4
@@ -193,6 +193,10 @@ class SpecialSkipListFactory : public MemTableRepFactory {
|
||||
}
|
||||
virtual const char* Name() const override { return "SkipListFactory"; }
|
||||
|
||||
bool IsInsertConcurrentlySupported() const override {
|
||||
return factory_.IsInsertConcurrentlySupported();
|
||||
}
|
||||
|
||||
private:
|
||||
SkipListFactory factory_;
|
||||
int num_entries_flush_;
|
||||
@@ -428,10 +432,10 @@ class SpecialEnv : public EnvWrapper {
|
||||
|
||||
virtual void SleepForMicroseconds(int micros) override {
|
||||
sleep_counter_.Increment();
|
||||
if (no_sleep_ || time_elapse_only_sleep_) {
|
||||
if (no_slowdown_ || time_elapse_only_sleep_) {
|
||||
addon_time_.fetch_add(micros);
|
||||
}
|
||||
if (!no_sleep_) {
|
||||
if (!no_slowdown_) {
|
||||
target()->SleepForMicroseconds(micros);
|
||||
}
|
||||
}
|
||||
@@ -520,7 +524,7 @@ class SpecialEnv : public EnvWrapper {
|
||||
|
||||
bool time_elapse_only_sleep_;
|
||||
|
||||
bool no_sleep_;
|
||||
bool no_slowdown_;
|
||||
|
||||
std::atomic<bool> is_wal_sync_thread_safe_{true};
|
||||
};
|
||||
@@ -695,6 +699,12 @@ class DBTestBase : public testing::Test {
|
||||
Status Put(int cf, const Slice& k, const Slice& v,
|
||||
WriteOptions wo = WriteOptions());
|
||||
|
||||
Status Merge(const Slice& k, const Slice& v,
|
||||
WriteOptions wo = WriteOptions());
|
||||
|
||||
Status Merge(int cf, const Slice& k, const Slice& v,
|
||||
WriteOptions wo = WriteOptions());
|
||||
|
||||
Status Delete(const std::string& k);
|
||||
|
||||
Status Delete(int cf, const std::string& k);
|
||||
@@ -822,7 +832,12 @@ class DBTestBase : public testing::Test {
|
||||
std::vector<std::uint64_t> ListTableFiles(Env* env, const std::string& path);
|
||||
|
||||
void VerifyDBFromMap(std::map<std::string, std::string> true_data,
|
||||
size_t* total_reads_res = nullptr);
|
||||
size_t* total_reads_res = nullptr,
|
||||
bool tailing_iter = false,
|
||||
std::map<std::string, Status> status = {});
|
||||
|
||||
void VerifyDBInternal(
|
||||
std::vector<std::pair<std::string, std::string>> true_data);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
uint64_t GetNumberOfSstFilesForColumnFamily(DB* db,
|
||||
|
||||
@@ -1044,7 +1044,7 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionTrivialMoveTest2) {
|
||||
options.num_levels = 15;
|
||||
options.write_buffer_size = 100 << 10; // 100KB
|
||||
options.level0_file_num_compaction_trigger = 8;
|
||||
options.max_background_compactions = 4;
|
||||
options.max_background_compactions = 1;
|
||||
options.target_file_size_base = 64 * 1024;
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
@@ -645,8 +645,6 @@ TEST_F(DBWALTest, PartOfWritesWithWALDisabled) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = fault_env.get();
|
||||
options.disable_auto_compactions = true;
|
||||
// TODO(yiwu): fix for 2PC.
|
||||
options.allow_2pc = false;
|
||||
WriteOptions wal_on, wal_off;
|
||||
wal_on.sync = true;
|
||||
wal_on.disableWAL = false;
|
||||
|
||||
@@ -48,6 +48,11 @@ void AppendInternalKey(std::string* result, const ParsedInternalKey& key) {
|
||||
PutFixed64(result, PackSequenceAndType(key.sequence, key.type));
|
||||
}
|
||||
|
||||
void AppendInternalKeyFooter(std::string* result, SequenceNumber s,
|
||||
ValueType t) {
|
||||
PutFixed64(result, PackSequenceAndType(s, t));
|
||||
}
|
||||
|
||||
std::string ParsedInternalKey::DebugString(bool hex) const {
|
||||
char buf[50];
|
||||
snprintf(buf, sizeof(buf), "' @ %" PRIu64 ": %d", sequence,
|
||||
|
||||
@@ -99,6 +99,11 @@ extern void UnPackSequenceAndType(uint64_t packed, uint64_t* seq, ValueType* t);
|
||||
// Append the serialization of "key" to *result.
|
||||
extern void AppendInternalKey(std::string* result,
|
||||
const ParsedInternalKey& key);
|
||||
// Serialized internal key consists of user key followed by footer.
|
||||
// This function appends the footer to *result, assuming that *result already
|
||||
// contains the user key at the end.
|
||||
extern void AppendInternalKeyFooter(std::string* result, SequenceNumber s,
|
||||
ValueType t);
|
||||
|
||||
// Attempt to parse an internal key from "internal_key". On success,
|
||||
// stores the parsed data in "*result", and returns true.
|
||||
@@ -197,6 +202,16 @@ class InternalKey {
|
||||
|
||||
void Clear() { rep_.clear(); }
|
||||
|
||||
// The underlying representation.
|
||||
// Intended only to be used together with ConvertFromUserKey().
|
||||
std::string* rep() { return &rep_; }
|
||||
|
||||
// Assuming that *rep() contains a user key, this method makes internal key
|
||||
// out of it in-place. This saves a memcpy compared to Set()/SetFrom().
|
||||
void ConvertFromUserKey(SequenceNumber s, ValueType t) {
|
||||
AppendInternalKeyFooter(&rep_, s, t);
|
||||
}
|
||||
|
||||
std::string DebugString(bool hex = false) const;
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,518 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#include "db/external_sst_file_ingestion_job.h"
|
||||
|
||||
#define __STDC_FORMAT_MACROS
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "db/version_edit.h"
|
||||
#include "table/merger.h"
|
||||
#include "table/scoped_arena_iterator.h"
|
||||
#include "table/sst_file_writer_collectors.h"
|
||||
#include "table/table_builder.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
#include "util/file_util.h"
|
||||
#include "util/stop_watch.h"
|
||||
#include "util/sync_point.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
Status ExternalSstFileIngestionJob::Prepare(
|
||||
const std::vector<std::string>& external_files_paths) {
|
||||
Status status;
|
||||
|
||||
// Read the information of files we are ingesting
|
||||
for (const std::string& file_path : external_files_paths) {
|
||||
IngestedFileInfo file_to_ingest;
|
||||
status = GetIngestedFileInfo(file_path, &file_to_ingest);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
files_to_ingest_.push_back(file_to_ingest);
|
||||
}
|
||||
|
||||
for (const IngestedFileInfo& f : files_to_ingest_) {
|
||||
if (f.cf_id !=
|
||||
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily &&
|
||||
f.cf_id != cfd_->GetID()) {
|
||||
return Status::InvalidArgument(
|
||||
"External file column family id dont match");
|
||||
}
|
||||
}
|
||||
|
||||
const Comparator* ucmp = cfd_->internal_comparator().user_comparator();
|
||||
auto num_files = files_to_ingest_.size();
|
||||
if (num_files == 0) {
|
||||
return Status::InvalidArgument("The list of files is empty");
|
||||
} else if (num_files > 1) {
|
||||
// Verify that passed files dont have overlapping ranges
|
||||
autovector<const IngestedFileInfo*> sorted_files;
|
||||
for (size_t i = 0; i < num_files; i++) {
|
||||
sorted_files.push_back(&files_to_ingest_[i]);
|
||||
}
|
||||
|
||||
std::sort(
|
||||
sorted_files.begin(), sorted_files.end(),
|
||||
[&ucmp](const IngestedFileInfo* info1, const IngestedFileInfo* info2) {
|
||||
return ucmp->Compare(info1->smallest_user_key,
|
||||
info2->smallest_user_key) < 0;
|
||||
});
|
||||
|
||||
for (size_t i = 0; i < num_files - 1; i++) {
|
||||
if (ucmp->Compare(sorted_files[i]->largest_user_key,
|
||||
sorted_files[i + 1]->smallest_user_key) >= 0) {
|
||||
return Status::NotSupported("Files have overlapping ranges");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (IngestedFileInfo& f : files_to_ingest_) {
|
||||
if (f.num_entries == 0) {
|
||||
return Status::InvalidArgument("File contain no entries");
|
||||
}
|
||||
|
||||
if (!f.smallest_internal_key().Valid() ||
|
||||
!f.largest_internal_key().Valid()) {
|
||||
return Status::Corruption("Generated table have corrupted keys");
|
||||
}
|
||||
}
|
||||
|
||||
// Copy/Move external files into DB
|
||||
for (IngestedFileInfo& f : files_to_ingest_) {
|
||||
f.fd = FileDescriptor(versions_->NewFileNumber(), 0, f.file_size);
|
||||
|
||||
const std::string path_outside_db = f.external_file_path;
|
||||
const std::string path_inside_db =
|
||||
TableFileName(db_options_.db_paths, f.fd.GetNumber(), f.fd.GetPathId());
|
||||
|
||||
if (ingestion_options_.move_files) {
|
||||
status = env_->LinkFile(path_outside_db, path_inside_db);
|
||||
if (status.IsNotSupported()) {
|
||||
// Original file is on a different FS, use copy instead of hard linking
|
||||
status = CopyFile(env_, path_outside_db, path_inside_db, 0,
|
||||
db_options_.use_fsync);
|
||||
}
|
||||
} else {
|
||||
status = CopyFile(env_, path_outside_db, path_inside_db, 0,
|
||||
db_options_.use_fsync);
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::AddFile:FileCopied");
|
||||
if (!status.ok()) {
|
||||
break;
|
||||
}
|
||||
f.internal_file_path = path_inside_db;
|
||||
}
|
||||
|
||||
if (!status.ok()) {
|
||||
// We failed, remove all files that we copied into the db
|
||||
for (IngestedFileInfo& f : files_to_ingest_) {
|
||||
if (f.internal_file_path == "") {
|
||||
break;
|
||||
}
|
||||
Status s = env_->DeleteFile(f.internal_file_path);
|
||||
if (!s.ok()) {
|
||||
Log(InfoLogLevel::WARN_LEVEL, db_options_.info_log,
|
||||
"AddFile() clean up for file %s failed : %s",
|
||||
f.internal_file_path.c_str(), s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
Status ExternalSstFileIngestionJob::NeedsFlush(bool* flush_needed) {
|
||||
SuperVersion* super_version = cfd_->GetSuperVersion();
|
||||
Status status =
|
||||
IngestedFilesOverlapWithMemtables(super_version, flush_needed);
|
||||
|
||||
if (status.ok() && *flush_needed &&
|
||||
!ingestion_options_.allow_blocking_flush) {
|
||||
status = Status::InvalidArgument("External file requires flush");
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
Status ExternalSstFileIngestionJob::Run() {
|
||||
Status status;
|
||||
#ifndef NDEBUG
|
||||
// We should never run the job with a memtable that is overlapping
|
||||
// with the files we are ingesting
|
||||
bool need_flush = false;
|
||||
status = NeedsFlush(&need_flush);
|
||||
assert(status.ok() && need_flush == false);
|
||||
#endif
|
||||
|
||||
bool consumed_seqno = false;
|
||||
bool force_global_seqno = false;
|
||||
const SequenceNumber last_seqno = versions_->LastSequence();
|
||||
if (ingestion_options_.snapshot_consistency && !db_snapshots_->empty()) {
|
||||
// We need to assign a global sequence number to all the files even
|
||||
// if the dont overlap with any ranges since we have snapshots
|
||||
force_global_seqno = true;
|
||||
}
|
||||
|
||||
SuperVersion* super_version = cfd_->GetSuperVersion();
|
||||
edit_.SetColumnFamily(cfd_->GetID());
|
||||
// The levels that the files will be ingested into
|
||||
for (IngestedFileInfo& f : files_to_ingest_) {
|
||||
bool overlap_with_db = false;
|
||||
status = AssignLevelForIngestedFile(super_version, &f, &overlap_with_db);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
if (overlap_with_db || force_global_seqno) {
|
||||
status = AssignGlobalSeqnoForIngestedFile(&f, last_seqno + 1);
|
||||
consumed_seqno = true;
|
||||
} else {
|
||||
status = AssignGlobalSeqnoForIngestedFile(&f, 0);
|
||||
}
|
||||
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
edit_.AddFile(f.picked_level, f.fd.GetNumber(), f.fd.GetPathId(),
|
||||
f.fd.GetFileSize(), f.smallest_internal_key(),
|
||||
f.largest_internal_key(), f.assigned_seqno, f.assigned_seqno,
|
||||
false);
|
||||
}
|
||||
|
||||
if (consumed_seqno) {
|
||||
versions_->SetLastSequence(last_seqno + 1);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void ExternalSstFileIngestionJob::UpdateStats() {
|
||||
// Update internal stats for new ingested files
|
||||
uint64_t total_keys = 0;
|
||||
uint64_t total_l0_files = 0;
|
||||
uint64_t total_time = env_->NowMicros() - job_start_time_;
|
||||
for (IngestedFileInfo& f : files_to_ingest_) {
|
||||
InternalStats::CompactionStats stats(1);
|
||||
stats.micros = total_time;
|
||||
stats.bytes_written = f.fd.GetFileSize();
|
||||
stats.num_output_files = 1;
|
||||
cfd_->internal_stats()->AddCompactionStats(f.picked_level, stats);
|
||||
cfd_->internal_stats()->AddCFStats(InternalStats::BYTES_INGESTED_ADD_FILE,
|
||||
f.fd.GetFileSize());
|
||||
total_keys += f.num_entries;
|
||||
if (f.picked_level == 0) {
|
||||
total_l0_files += 1;
|
||||
}
|
||||
Log(InfoLogLevel::INFO_LEVEL, db_options_.info_log,
|
||||
"[AddFile] External SST file %s was ingested in L%d with path %s "
|
||||
"(global_seqno=%" PRIu64 ")\n",
|
||||
f.external_file_path.c_str(), f.picked_level,
|
||||
f.internal_file_path.c_str(), f.assigned_seqno);
|
||||
}
|
||||
cfd_->internal_stats()->AddCFStats(InternalStats::INGESTED_NUM_KEYS_TOTAL,
|
||||
total_keys);
|
||||
cfd_->internal_stats()->AddCFStats(InternalStats::INGESTED_NUM_FILES_TOTAL,
|
||||
files_to_ingest_.size());
|
||||
cfd_->internal_stats()->AddCFStats(
|
||||
InternalStats::INGESTED_LEVEL0_NUM_FILES_TOTAL, total_l0_files);
|
||||
}
|
||||
|
||||
void ExternalSstFileIngestionJob::Cleanup(const Status& status) {
|
||||
if (!status.ok()) {
|
||||
// We failed to add the files to the database
|
||||
// remove all the files we copied
|
||||
for (IngestedFileInfo& f : files_to_ingest_) {
|
||||
Status s = env_->DeleteFile(f.internal_file_path);
|
||||
if (!s.ok()) {
|
||||
Log(InfoLogLevel::WARN_LEVEL, db_options_.info_log,
|
||||
"AddFile() clean up for file %s failed : %s",
|
||||
f.internal_file_path.c_str(), s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
} else if (status.ok() && ingestion_options_.move_files) {
|
||||
// The files were moved and added successfully, remove original file links
|
||||
for (IngestedFileInfo& f : files_to_ingest_) {
|
||||
Status s = env_->DeleteFile(f.external_file_path);
|
||||
if (!s.ok()) {
|
||||
Log(InfoLogLevel::WARN_LEVEL, db_options_.info_log,
|
||||
"%s was added to DB successfully but failed to remove original "
|
||||
"file link : %s",
|
||||
f.external_file_path.c_str(), s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
const std::string& external_file, IngestedFileInfo* file_to_ingest) {
|
||||
file_to_ingest->external_file_path = external_file;
|
||||
|
||||
// Get external file size
|
||||
Status status = env_->GetFileSize(external_file, &file_to_ingest->file_size);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Create TableReader for external file
|
||||
std::unique_ptr<TableReader> table_reader;
|
||||
std::unique_ptr<RandomAccessFile> sst_file;
|
||||
std::unique_ptr<RandomAccessFileReader> sst_file_reader;
|
||||
|
||||
status = env_->NewRandomAccessFile(external_file, &sst_file, env_options_);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
sst_file_reader.reset(new RandomAccessFileReader(std::move(sst_file)));
|
||||
|
||||
status = cfd_->ioptions()->table_factory->NewTableReader(
|
||||
TableReaderOptions(*cfd_->ioptions(), env_options_,
|
||||
cfd_->internal_comparator()),
|
||||
std::move(sst_file_reader), file_to_ingest->file_size, &table_reader);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Get the external file properties
|
||||
auto props = table_reader->GetTableProperties();
|
||||
const auto& uprops = props->user_collected_properties;
|
||||
|
||||
// Get table version
|
||||
auto version_iter = uprops.find(ExternalSstFilePropertyNames::kVersion);
|
||||
if (version_iter == uprops.end()) {
|
||||
return Status::Corruption("External file version not found");
|
||||
}
|
||||
file_to_ingest->version = DecodeFixed32(version_iter->second.c_str());
|
||||
|
||||
auto seqno_iter = uprops.find(ExternalSstFilePropertyNames::kGlobalSeqno);
|
||||
if (file_to_ingest->version == 2) {
|
||||
// version 2 imply that we have global sequence number
|
||||
if (seqno_iter == uprops.end()) {
|
||||
return Status::Corruption(
|
||||
"External file global sequence number not found");
|
||||
}
|
||||
|
||||
// Set the global sequence number
|
||||
file_to_ingest->original_seqno = DecodeFixed64(seqno_iter->second.c_str());
|
||||
file_to_ingest->global_seqno_offset = props->properties_offsets.at(
|
||||
ExternalSstFilePropertyNames::kGlobalSeqno);
|
||||
|
||||
if (file_to_ingest->global_seqno_offset == 0) {
|
||||
return Status::Corruption("Was not able to find file global seqno field");
|
||||
}
|
||||
} else {
|
||||
return Status::InvalidArgument("external file version is not supported");
|
||||
}
|
||||
// Get number of entries in table
|
||||
file_to_ingest->num_entries = props->num_entries;
|
||||
|
||||
ParsedInternalKey key;
|
||||
ReadOptions ro;
|
||||
// During reading the external file we can cache blocks that we read into
|
||||
// the block cache, if we later change the global seqno of this file, we will
|
||||
// have block in cache that will include keys with wrong seqno.
|
||||
// We need to disable fill_cache so that we read from the file without
|
||||
// updating the block cache.
|
||||
ro.fill_cache = false;
|
||||
std::unique_ptr<InternalIterator> iter(table_reader->NewIterator(ro));
|
||||
|
||||
// Get first (smallest) key from file
|
||||
iter->SeekToFirst();
|
||||
if (!ParseInternalKey(iter->key(), &key)) {
|
||||
return Status::Corruption("external file have corrupted keys");
|
||||
}
|
||||
if (key.sequence != 0) {
|
||||
return Status::Corruption("external file have non zero sequence number");
|
||||
}
|
||||
file_to_ingest->smallest_user_key = key.user_key.ToString();
|
||||
|
||||
// Get last (largest) key from file
|
||||
iter->SeekToLast();
|
||||
if (!ParseInternalKey(iter->key(), &key)) {
|
||||
return Status::Corruption("external file have corrupted keys");
|
||||
}
|
||||
if (key.sequence != 0) {
|
||||
return Status::Corruption("external file have non zero sequence number");
|
||||
}
|
||||
file_to_ingest->largest_user_key = key.user_key.ToString();
|
||||
|
||||
file_to_ingest->cf_id = static_cast<uint32_t>(props->column_family_id);
|
||||
|
||||
file_to_ingest->table_properties = *props;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
Status ExternalSstFileIngestionJob::IngestedFilesOverlapWithMemtables(
|
||||
SuperVersion* sv, bool* overlap) {
|
||||
// Create an InternalIterator over all memtables
|
||||
Arena arena;
|
||||
ReadOptions ro;
|
||||
ro.total_order_seek = true;
|
||||
MergeIteratorBuilder merge_iter_builder(&cfd_->internal_comparator(), &arena);
|
||||
merge_iter_builder.AddIterator(sv->mem->NewIterator(ro, &arena));
|
||||
sv->imm->AddIterators(ro, &merge_iter_builder);
|
||||
ScopedArenaIterator memtable_iter(merge_iter_builder.Finish());
|
||||
|
||||
Status status;
|
||||
*overlap = false;
|
||||
for (IngestedFileInfo& f : files_to_ingest_) {
|
||||
status =
|
||||
IngestedFileOverlapWithIteratorRange(&f, memtable_iter.get(), overlap);
|
||||
if (!status.ok() || *overlap == true) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
Status ExternalSstFileIngestionJob::AssignLevelForIngestedFile(
|
||||
SuperVersion* sv, IngestedFileInfo* file_to_ingest, bool* overlap_with_db) {
|
||||
*overlap_with_db = false;
|
||||
|
||||
Arena arena;
|
||||
ReadOptions ro;
|
||||
ro.total_order_seek = true;
|
||||
|
||||
Status status;
|
||||
int target_level = 0;
|
||||
auto* vstorage = cfd_->current()->storage_info();
|
||||
for (int lvl = 0; lvl < cfd_->NumberLevels(); lvl++) {
|
||||
if (lvl > 0 && lvl < vstorage->base_level()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (vstorage->NumLevelFiles(lvl) > 0) {
|
||||
bool overlap_with_level = false;
|
||||
MergeIteratorBuilder merge_iter_builder(&cfd_->internal_comparator(),
|
||||
&arena);
|
||||
RangeDelAggregator range_del_agg(cfd_->internal_comparator(),
|
||||
{} /* snapshots */);
|
||||
sv->current->AddIteratorsForLevel(ro, env_options_, &merge_iter_builder,
|
||||
lvl, &range_del_agg);
|
||||
if (!range_del_agg.IsEmpty()) {
|
||||
return Status::NotSupported(
|
||||
"file ingestion with range tombstones is currently unsupported");
|
||||
}
|
||||
ScopedArenaIterator level_iter(merge_iter_builder.Finish());
|
||||
|
||||
status = IngestedFileOverlapWithIteratorRange(
|
||||
file_to_ingest, level_iter.get(), &overlap_with_level);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
if (overlap_with_level) {
|
||||
// We must use L0 or any level higher than `lvl` to be able to overwrite
|
||||
// the keys that we overlap with in this level, We also need to assign
|
||||
// this file a seqno to overwrite the existing keys in level `lvl`
|
||||
*overlap_with_db = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// We dont overlap with any keys in this level, but we still need to check
|
||||
// if our file can fit in it
|
||||
|
||||
if (IngestedFileFitInLevel(file_to_ingest, lvl)) {
|
||||
target_level = lvl;
|
||||
}
|
||||
}
|
||||
file_to_ingest->picked_level = target_level;
|
||||
return status;
|
||||
}
|
||||
|
||||
Status ExternalSstFileIngestionJob::AssignGlobalSeqnoForIngestedFile(
|
||||
IngestedFileInfo* file_to_ingest, SequenceNumber seqno) {
|
||||
if (file_to_ingest->original_seqno == seqno) {
|
||||
// This file already have the correct global seqno
|
||||
return Status::OK();
|
||||
} else if (!ingestion_options_.allow_global_seqno) {
|
||||
return Status::InvalidArgument("Global seqno is required, but disabled");
|
||||
} else if (file_to_ingest->global_seqno_offset == 0) {
|
||||
return Status::InvalidArgument(
|
||||
"Trying to set global seqno for a file that dont have a global seqno "
|
||||
"field");
|
||||
}
|
||||
|
||||
std::unique_ptr<RandomRWFile> rwfile;
|
||||
Status status = env_->NewRandomRWFile(file_to_ingest->internal_file_path,
|
||||
&rwfile, env_options_);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Write the new seqno in the global sequence number field in the file
|
||||
std::string seqno_val;
|
||||
PutFixed64(&seqno_val, seqno);
|
||||
status = rwfile->Write(file_to_ingest->global_seqno_offset, seqno_val);
|
||||
if (status.ok()) {
|
||||
file_to_ingest->assigned_seqno = seqno;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
Status ExternalSstFileIngestionJob::IngestedFileOverlapWithIteratorRange(
|
||||
const IngestedFileInfo* file_to_ingest, InternalIterator* iter,
|
||||
bool* overlap) {
|
||||
auto* vstorage = cfd_->current()->storage_info();
|
||||
auto* ucmp = vstorage->InternalComparator()->user_comparator();
|
||||
InternalKey range_start(file_to_ingest->smallest_user_key, kMaxSequenceNumber,
|
||||
kValueTypeForSeek);
|
||||
iter->Seek(range_start.Encode());
|
||||
if (!iter->status().ok()) {
|
||||
return iter->status();
|
||||
}
|
||||
|
||||
*overlap = false;
|
||||
if (iter->Valid()) {
|
||||
ParsedInternalKey seek_result;
|
||||
if (!ParseInternalKey(iter->key(), &seek_result)) {
|
||||
return Status::Corruption("DB have corrupted keys");
|
||||
}
|
||||
|
||||
if (ucmp->Compare(seek_result.user_key, file_to_ingest->largest_user_key) <=
|
||||
0) {
|
||||
*overlap = true;
|
||||
}
|
||||
}
|
||||
|
||||
return iter->status();
|
||||
}
|
||||
|
||||
bool ExternalSstFileIngestionJob::IngestedFileFitInLevel(
|
||||
const IngestedFileInfo* file_to_ingest, int level) {
|
||||
if (level == 0) {
|
||||
// Files can always fit in L0
|
||||
return true;
|
||||
}
|
||||
|
||||
auto* vstorage = cfd_->current()->storage_info();
|
||||
Slice file_smallest_user_key(file_to_ingest->smallest_user_key);
|
||||
Slice file_largest_user_key(file_to_ingest->largest_user_key);
|
||||
|
||||
if (vstorage->OverlapInLevel(level, &file_smallest_user_key,
|
||||
&file_largest_user_key)) {
|
||||
// File overlap with another files in this level, we cannot
|
||||
// add it to this level
|
||||
return false;
|
||||
}
|
||||
if (cfd_->RangeOverlapWithCompaction(file_smallest_user_key,
|
||||
file_largest_user_key, level)) {
|
||||
// File overlap with a running compaction output that will be stored
|
||||
// in this level, we cannot add this file to this level
|
||||
return false;
|
||||
}
|
||||
|
||||
// File did not overlap with level files, our compaction output
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -0,0 +1,151 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "db/column_family.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/internal_stats.h"
|
||||
#include "db/snapshot_impl.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/sst_file_writer.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/db_options.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
struct IngestedFileInfo {
|
||||
// External file path
|
||||
std::string external_file_path;
|
||||
// Smallest user key in external file
|
||||
std::string smallest_user_key;
|
||||
// Largest user key in external file
|
||||
std::string largest_user_key;
|
||||
// Sequence number for keys in external file
|
||||
SequenceNumber original_seqno;
|
||||
// Offset of the global sequence number field in the file, will
|
||||
// be zero if version is 1 (global seqno is not supported)
|
||||
size_t global_seqno_offset;
|
||||
// External file size
|
||||
uint64_t file_size;
|
||||
// total number of keys in external file
|
||||
uint64_t num_entries;
|
||||
// Id of column family this file shoule be ingested into
|
||||
uint32_t cf_id;
|
||||
// TableProperties read from external file
|
||||
TableProperties table_properties;
|
||||
// Version of external file
|
||||
int version;
|
||||
|
||||
// FileDescriptor for the file inside the DB
|
||||
FileDescriptor fd;
|
||||
// file path that we picked for file inside the DB
|
||||
std::string internal_file_path = "";
|
||||
// Global sequence number that we picked for the file inside the DB
|
||||
SequenceNumber assigned_seqno = 0;
|
||||
// Level inside the DB we picked for the external file.
|
||||
int picked_level = 0;
|
||||
|
||||
InternalKey smallest_internal_key() const {
|
||||
return InternalKey(smallest_user_key, assigned_seqno,
|
||||
ValueType::kTypeValue);
|
||||
}
|
||||
|
||||
InternalKey largest_internal_key() const {
|
||||
return InternalKey(largest_user_key, assigned_seqno, ValueType::kTypeValue);
|
||||
}
|
||||
};
|
||||
|
||||
class ExternalSstFileIngestionJob {
|
||||
public:
|
||||
ExternalSstFileIngestionJob(
|
||||
Env* env, VersionSet* versions, ColumnFamilyData* cfd,
|
||||
const ImmutableDBOptions& db_options, const EnvOptions& env_options,
|
||||
SnapshotList* db_snapshots,
|
||||
const IngestExternalFileOptions& ingestion_options)
|
||||
: env_(env),
|
||||
versions_(versions),
|
||||
cfd_(cfd),
|
||||
db_options_(db_options),
|
||||
env_options_(env_options),
|
||||
db_snapshots_(db_snapshots),
|
||||
ingestion_options_(ingestion_options),
|
||||
job_start_time_(env_->NowMicros()) {}
|
||||
|
||||
// Prepare the job by copying external files into the DB.
|
||||
Status Prepare(const std::vector<std::string>& external_files_paths);
|
||||
|
||||
// Check if we need to flush the memtable before running the ingestion job
|
||||
// This will be true if the files we are ingesting are overlapping with any
|
||||
// key range in the memtable.
|
||||
// REQUIRES: Mutex held
|
||||
Status NeedsFlush(bool* flush_needed);
|
||||
|
||||
// Will execute the ingestion job and prepare edit() to be applied.
|
||||
// REQUIRES: Mutex held
|
||||
Status Run();
|
||||
|
||||
// Update column family stats.
|
||||
// REQUIRES: Mutex held
|
||||
void UpdateStats();
|
||||
|
||||
// Cleanup after successfull/failed job
|
||||
void Cleanup(const Status& status);
|
||||
|
||||
VersionEdit* edit() { return &edit_; }
|
||||
|
||||
const autovector<IngestedFileInfo>& files_to_ingest() const {
|
||||
return files_to_ingest_;
|
||||
}
|
||||
|
||||
private:
|
||||
// Open the external file and populate `file_to_ingest` with all the
|
||||
// external information we need to ingest this file.
|
||||
Status GetIngestedFileInfo(const std::string& external_file,
|
||||
IngestedFileInfo* file_to_ingest);
|
||||
|
||||
// Check if the files we are ingesting overlap with any memtable.
|
||||
// REQUIRES: Mutex held
|
||||
Status IngestedFilesOverlapWithMemtables(SuperVersion* sv, bool* overlap);
|
||||
|
||||
// Assign `file_to_ingest` the lowest possible level that it can
|
||||
// be ingested to.
|
||||
// REQUIRES: Mutex held
|
||||
Status AssignLevelForIngestedFile(SuperVersion* sv,
|
||||
IngestedFileInfo* file_to_ingest,
|
||||
bool* overlap_with_db);
|
||||
|
||||
// Set the file global sequence number to `seqno`
|
||||
Status AssignGlobalSeqnoForIngestedFile(IngestedFileInfo* file_to_ingest,
|
||||
SequenceNumber seqno);
|
||||
|
||||
// Check if `file_to_ingest` key range overlap with the range `iter` represent
|
||||
// REQUIRES: Mutex held
|
||||
Status IngestedFileOverlapWithIteratorRange(
|
||||
const IngestedFileInfo* file_to_ingest, InternalIterator* iter,
|
||||
bool* overlap);
|
||||
|
||||
// Check if `file_to_ingest` can fit in level `level`
|
||||
// REQUIRES: Mutex held
|
||||
bool IngestedFileFitInLevel(const IngestedFileInfo* file_to_ingest,
|
||||
int level);
|
||||
|
||||
Env* env_;
|
||||
VersionSet* versions_;
|
||||
ColumnFamilyData* cfd_;
|
||||
const ImmutableDBOptions& db_options_;
|
||||
const EnvOptions& env_options_;
|
||||
SnapshotList* db_snapshots_;
|
||||
autovector<IngestedFileInfo> files_to_ingest_;
|
||||
const IngestExternalFileOptions& ingestion_options_;
|
||||
VersionEdit edit_;
|
||||
uint64_t job_start_time_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
+668
-136
File diff suppressed because it is too large.
Load diff
@@ -228,6 +228,11 @@ class FaultInjectionTest : public testing::Test,
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
#if __clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 9)
|
||||
__attribute__((__no_sanitize__("undefined")))
|
||||
#elif __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)
|
||||
__attribute__((__no_sanitize_undefined__))
|
||||
#endif
|
||||
// Return the ith key
|
||||
Slice Key(int i, std::string* storage) const {
|
||||
int num = i;
|
||||
|
||||
+16
-3
@@ -241,7 +241,11 @@ Status FlushJob::WriteLevel0Table() {
|
||||
if (log_buffer_) {
|
||||
log_buffer_->FlushBufferToLog();
|
||||
}
|
||||
// memtables and range_del_iters store internal iterators over each data
|
||||
// memtable and its associated range deletion memtable, respectively, at
|
||||
// corresponding indexes.
|
||||
std::vector<InternalIterator*> memtables;
|
||||
std::vector<InternalIterator*> range_del_iters;
|
||||
ReadOptions ro;
|
||||
ro.total_order_seek = true;
|
||||
Arena arena;
|
||||
@@ -252,6 +256,10 @@ Status FlushJob::WriteLevel0Table() {
|
||||
"[%s] [JOB %d] Flushing memtable with next log file: %" PRIu64 "\n",
|
||||
cfd_->GetName().c_str(), job_context_->job_id, m->GetNextLogNumber());
|
||||
memtables.push_back(m->NewIterator(ro, &arena));
|
||||
auto* range_del_iter = m->NewRangeTombstoneIterator(ro);
|
||||
if (range_del_iter != nullptr) {
|
||||
range_del_iters.push_back(range_del_iter);
|
||||
}
|
||||
total_num_entries += m->num_entries();
|
||||
total_num_deletes += m->num_deletes();
|
||||
total_memory_usage += m->ApproximateMemoryUsage();
|
||||
@@ -268,6 +276,10 @@ Status FlushJob::WriteLevel0Table() {
|
||||
ScopedArenaIterator iter(
|
||||
NewMergingIterator(&cfd_->internal_comparator(), &memtables[0],
|
||||
static_cast<int>(memtables.size()), &arena));
|
||||
std::unique_ptr<InternalIterator> range_del_iter(NewMergingIterator(
|
||||
&cfd_->internal_comparator(),
|
||||
range_del_iters.empty() ? nullptr : &range_del_iters[0],
|
||||
static_cast<int>(range_del_iters.size())));
|
||||
Log(InfoLogLevel::INFO_LEVEL, db_options_.info_log,
|
||||
"[%s] [JOB %d] Level-0 flush table #%" PRIu64 ": started",
|
||||
cfd_->GetName().c_str(), job_context_->job_id, meta_.fd.GetNumber());
|
||||
@@ -276,9 +288,10 @@ Status FlushJob::WriteLevel0Table() {
|
||||
&output_compression_);
|
||||
s = BuildTable(
|
||||
dbname_, db_options_.env, *cfd_->ioptions(), mutable_cf_options_,
|
||||
env_options_, cfd_->table_cache(), iter.get(), &meta_,
|
||||
cfd_->internal_comparator(), cfd_->int_tbl_prop_collector_factories(),
|
||||
cfd_->GetID(), cfd_->GetName(), existing_snapshots_,
|
||||
env_options_, cfd_->table_cache(), iter.get(),
|
||||
std::move(range_del_iter), &meta_, cfd_->internal_comparator(),
|
||||
cfd_->int_tbl_prop_collector_factories(), cfd_->GetID(),
|
||||
cfd_->GetName(), existing_snapshots_,
|
||||
earliest_write_conflict_snapshot_, output_compression_,
|
||||
cfd_->ioptions()->compression_opts,
|
||||
mutable_cf_options_.paranoid_file_checks, cfd_->internal_stats(),
|
||||
|
||||
+11
-9
@@ -115,18 +115,19 @@ TEST_F(FlushJobTest, NonEmpty) {
|
||||
// Test data:
|
||||
// seqno [ 1, 2 ... 8998, 8999, 9000, 9001, 9002 ... 9999 ]
|
||||
// key [ 1001, 1002 ... 9998, 9999, 0, 1, 2 ... 999 ]
|
||||
// Expected:
|
||||
// smallest_key = "0"
|
||||
// largest_key = "9999"
|
||||
// smallest_seqno = 1
|
||||
// smallest_seqno = 9999
|
||||
// range-delete "9995" -> "9999" at seqno 10000
|
||||
for (int i = 1; i < 10000; ++i) {
|
||||
std::string key(ToString((i + 1000) % 10000));
|
||||
std::string value("value" + key);
|
||||
new_mem->Add(SequenceNumber(i), kTypeValue, key, value);
|
||||
InternalKey internal_key(key, SequenceNumber(i), kTypeValue);
|
||||
inserted_keys.insert({internal_key.Encode().ToString(), value});
|
||||
if ((i + 1000) % 10000 < 9995) {
|
||||
InternalKey internal_key(key, SequenceNumber(i), kTypeValue);
|
||||
inserted_keys.insert({internal_key.Encode().ToString(), value});
|
||||
}
|
||||
}
|
||||
new_mem->Add(SequenceNumber(10000), kTypeRangeDeletion, "9995", "9999a");
|
||||
InternalKey internal_key("9995", SequenceNumber(10000), kTypeRangeDeletion);
|
||||
inserted_keys.insert({internal_key.Encode().ToString(), "9999a"});
|
||||
|
||||
autovector<MemTable*> to_delete;
|
||||
cfd->imm()->Add(new_mem, &to_delete);
|
||||
@@ -146,9 +147,10 @@ TEST_F(FlushJobTest, NonEmpty) {
|
||||
ASSERT_OK(flush_job.Run(&fd));
|
||||
mutex_.Unlock();
|
||||
ASSERT_EQ(ToString(0), fd.smallest.user_key().ToString());
|
||||
ASSERT_EQ(ToString(9999), fd.largest.user_key().ToString());
|
||||
ASSERT_EQ("9999a",
|
||||
fd.largest.user_key().ToString()); // range tombstone end key
|
||||
ASSERT_EQ(1, fd.smallest_seqno);
|
||||
ASSERT_EQ(9999, fd.largest_seqno);
|
||||
ASSERT_EQ(10000, fd.largest_seqno); // range tombstone seqnum 10000
|
||||
mock_table_factory_->AssertSingleFile(inserted_keys);
|
||||
job_context.Clean();
|
||||
}
|
||||
|
||||
+17
-52
@@ -71,8 +71,8 @@ class LevelIterator : public InternalIterator {
|
||||
|
||||
file_iter_ = cfd_->table_cache()->NewIterator(
|
||||
read_options_, *(cfd_->soptions()), cfd_->internal_comparator(),
|
||||
files_[file_index_]->fd, nullptr /* table_reader_ptr */, nullptr,
|
||||
false);
|
||||
files_[file_index_]->fd, nullptr /* range_del_agg */,
|
||||
nullptr /* table_reader_ptr */, nullptr, false);
|
||||
|
||||
file_iter_->SetPinnedItersMgr(pinned_iters_mgr_);
|
||||
}
|
||||
@@ -296,9 +296,15 @@ void ForwardIterator::SeekInternal(const Slice& internal_key,
|
||||
// an option to turn it off.
|
||||
if (seek_to_first || NeedToSeekImmutable(internal_key)) {
|
||||
immutable_status_ = Status::OK();
|
||||
if ((has_iter_trimmed_for_upper_bound_) &&
|
||||
(cfd_->internal_comparator().InternalKeyComparator::Compare(
|
||||
prev_key_.GetKey(), internal_key) > 0)) {
|
||||
if (has_iter_trimmed_for_upper_bound_ &&
|
||||
(
|
||||
// prev_ is not set yet
|
||||
is_prev_set_ == false ||
|
||||
// We are doing SeekToFirst() and internal_key.size() = 0
|
||||
seek_to_first ||
|
||||
// prev_key_ > internal_key
|
||||
cfd_->internal_comparator().InternalKeyComparator::Compare(
|
||||
prev_key_.GetKey(), internal_key) > 0)) {
|
||||
// Some iterators are trimmed. Need to rebuild.
|
||||
RebuildIterators(true);
|
||||
// Already seeked mutable iter, so seek again
|
||||
@@ -359,61 +365,19 @@ void ForwardIterator::SeekInternal(const Slice& internal_key,
|
||||
}
|
||||
}
|
||||
|
||||
int32_t search_left_bound = 0;
|
||||
int32_t search_right_bound = FileIndexer::kLevelMaxIndex;
|
||||
for (int32_t level = 1; level < vstorage->num_levels(); ++level) {
|
||||
const std::vector<FileMetaData*>& level_files =
|
||||
vstorage->LevelFiles(level);
|
||||
if (level_files.empty()) {
|
||||
search_left_bound = 0;
|
||||
search_right_bound = FileIndexer::kLevelMaxIndex;
|
||||
continue;
|
||||
}
|
||||
if (level_iters_[level - 1] == nullptr) {
|
||||
continue;
|
||||
}
|
||||
uint32_t f_idx = 0;
|
||||
const auto& indexer = vstorage->file_indexer();
|
||||
if (!seek_to_first) {
|
||||
if (search_left_bound == search_right_bound) {
|
||||
f_idx = search_left_bound;
|
||||
} else if (search_left_bound < search_right_bound) {
|
||||
f_idx =
|
||||
FindFileInRange(level_files, internal_key, search_left_bound,
|
||||
search_right_bound == FileIndexer::kLevelMaxIndex
|
||||
? static_cast<uint32_t>(level_files.size())
|
||||
: search_right_bound);
|
||||
} else {
|
||||
// search_left_bound > search_right_bound
|
||||
// There are only 2 cases this can happen:
|
||||
// (1) target key is smaller than left most file
|
||||
// (2) target key is larger than right most file
|
||||
assert(search_left_bound == (int32_t)level_files.size() ||
|
||||
search_right_bound == -1);
|
||||
if (search_right_bound == -1) {
|
||||
assert(search_left_bound == 0);
|
||||
f_idx = 0;
|
||||
} else {
|
||||
indexer.GetNextLevelIndex(
|
||||
level, level_files.size() - 1,
|
||||
1, 1, &search_left_bound, &search_right_bound);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare hints for the next level
|
||||
if (f_idx < level_files.size()) {
|
||||
int cmp_smallest = user_comparator_->Compare(
|
||||
user_key, level_files[f_idx]->smallest.user_key());
|
||||
assert(user_comparator_->Compare(
|
||||
user_key, level_files[f_idx]->largest.user_key()) <= 0);
|
||||
indexer.GetNextLevelIndex(level, f_idx, cmp_smallest, -1,
|
||||
&search_left_bound, &search_right_bound);
|
||||
} else {
|
||||
indexer.GetNextLevelIndex(
|
||||
level, level_files.size() - 1,
|
||||
1, 1, &search_left_bound, &search_right_bound);
|
||||
}
|
||||
f_idx = FindFileInRange(level_files, internal_key, 0,
|
||||
static_cast<uint32_t>(level_files.size()));
|
||||
}
|
||||
|
||||
// Seek
|
||||
@@ -610,7 +574,8 @@ void ForwardIterator::RebuildIterators(bool refresh_sv) {
|
||||
continue;
|
||||
}
|
||||
l0_iters_.push_back(cfd_->table_cache()->NewIterator(
|
||||
read_options_, *cfd_->soptions(), cfd_->internal_comparator(), l0->fd));
|
||||
read_options_, *cfd_->soptions(), cfd_->internal_comparator(), l0->fd,
|
||||
nullptr /* range_del_agg */));
|
||||
}
|
||||
BuildLevelIterators(vstorage);
|
||||
current_ = nullptr;
|
||||
@@ -665,7 +630,7 @@ void ForwardIterator::RenewIterators() {
|
||||
}
|
||||
l0_iters_new.push_back(cfd_->table_cache()->NewIterator(
|
||||
read_options_, *cfd_->soptions(), cfd_->internal_comparator(),
|
||||
l0_files_new[inew]->fd));
|
||||
l0_files_new[inew]->fd, nullptr /* range_del_agg */));
|
||||
}
|
||||
|
||||
for (auto* f : l0_iters_) {
|
||||
@@ -717,7 +682,7 @@ void ForwardIterator::ResetIncompleteIterators() {
|
||||
DeleteIterator(l0_iters_[i]);
|
||||
l0_iters_[i] = cfd_->table_cache()->NewIterator(
|
||||
read_options_, *cfd_->soptions(), cfd_->internal_comparator(),
|
||||
l0_files[i]->fd);
|
||||
l0_files[i]->fd, nullptr /* range_del_agg */);
|
||||
l0_iters_[i]->SetPinnedItersMgr(pinned_iters_mgr_);
|
||||
}
|
||||
|
||||
|
||||
+343
-119
@@ -44,6 +44,7 @@
|
||||
#pragma once
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include "port/port.h"
|
||||
#include "util/allocator.h"
|
||||
@@ -55,8 +56,11 @@ template <class Comparator>
|
||||
class InlineSkipList {
|
||||
private:
|
||||
struct Node;
|
||||
struct Splice;
|
||||
|
||||
public:
|
||||
static const uint16_t kMaxPossibleHeight = 32;
|
||||
|
||||
// Create a new InlineSkipList object that will use "cmp" for comparing
|
||||
// keys, and will allocate memory using "*allocator". Objects allocated
|
||||
// in the allocator must remain allocated for the lifetime of the
|
||||
@@ -70,22 +74,58 @@ class InlineSkipList {
|
||||
// is thread-safe.
|
||||
char* AllocateKey(size_t key_size);
|
||||
|
||||
// Allocate a splice using allocator.
|
||||
Splice* AllocateSplice();
|
||||
|
||||
// Inserts a key allocated by AllocateKey, after the actual key value
|
||||
// has been filled in.
|
||||
//
|
||||
// REQUIRES: nothing that compares equal to key is currently in the list.
|
||||
// REQUIRES: no concurrent calls to INSERT
|
||||
// REQUIRES: no concurrent calls to any of inserts.
|
||||
void Insert(const char* key);
|
||||
|
||||
// Inserts a key allocated by AllocateKey with a hint of last insert
|
||||
// position in the skip-list. If hint points to nullptr, a new hint will be
|
||||
// populated, which can be used in subsequent calls.
|
||||
//
|
||||
// It can be used to optimize the workload where there are multiple groups
|
||||
// of keys, and each key is likely to insert to a location close to the last
|
||||
// inserted key in the same group. One example is sequential inserts.
|
||||
//
|
||||
// REQUIRES: nothing that compares equal to key is currently in the list.
|
||||
// REQUIRES: no concurrent calls to any of inserts.
|
||||
void InsertWithHint(const char* key, void** hint);
|
||||
|
||||
// Like Insert, but external synchronization is not required.
|
||||
void InsertConcurrently(const char* key);
|
||||
|
||||
// Inserts a node into the skip list. key must have been allocated by
|
||||
// AllocateKey and then filled in by the caller. If UseCAS is true,
|
||||
// then external synchronization is not required, otherwise this method
|
||||
// may not be called concurrently with any other insertions.
|
||||
//
|
||||
// Regardless of whether UseCAS is true, the splice must be owned
|
||||
// exclusively by the current thread. If allow_partial_splice_fix is
|
||||
// true, then the cost of insertion is amortized O(log D), where D is
|
||||
// the distance from the splice to the inserted key (measured as the
|
||||
// number of intervening nodes). Note that this bound is very good for
|
||||
// sequential insertions! If allow_partial_splice_fix is false then
|
||||
// the existing splice will be ignored unless the current key is being
|
||||
// inserted immediately after the splice. allow_partial_splice_fix ==
|
||||
// false has worse running time for the non-sequential case O(log N),
|
||||
// but a better constant factor.
|
||||
template <bool UseCAS>
|
||||
void Insert(const char* key, Splice* splice, bool allow_partial_splice_fix);
|
||||
|
||||
// Returns true iff an entry that compares equal to key is in the list.
|
||||
bool Contains(const char* key) const;
|
||||
|
||||
// Return estimated number of entries smaller than `key`.
|
||||
uint64_t EstimateCount(const char* key) const;
|
||||
|
||||
// Validate correctness of the skip-list.
|
||||
void TEST_Validate() const;
|
||||
|
||||
// Iteration over the contents of a skip list
|
||||
class Iterator {
|
||||
public:
|
||||
@@ -134,8 +174,6 @@ class InlineSkipList {
|
||||
};
|
||||
|
||||
private:
|
||||
enum MaxPossibleHeightEnum : uint16_t { kMaxPossibleHeight = 32 };
|
||||
|
||||
const uint16_t kMaxHeight_;
|
||||
const uint16_t kBranching_;
|
||||
const uint32_t kScaledInverseBranching_;
|
||||
@@ -150,13 +188,10 @@ class InlineSkipList {
|
||||
// values are ok.
|
||||
std::atomic<int> max_height_; // Height of the entire list
|
||||
|
||||
// Used for optimizing sequential insert patterns. Tricky. prev_height_
|
||||
// of zero means prev_ is undefined. Otherwise: prev_[i] for i up
|
||||
// to max_height_ - 1 (inclusive) is the predecessor of prev_[0], and
|
||||
// prev_height_ is the height of prev_[0]. prev_[0] can only be equal
|
||||
// to head when max_height_ and prev_height_ are both 1.
|
||||
Node** prev_;
|
||||
std::atomic<int32_t> prev_height_;
|
||||
// seq_splice_ is a Splice used for insertions in the non-concurrent
|
||||
// case. It caches the prev and next found during the most recent
|
||||
// non-concurrent insertion.
|
||||
Splice* seq_splice_;
|
||||
|
||||
inline int GetMaxHeight() const {
|
||||
return max_height_.load(std::memory_order_relaxed);
|
||||
@@ -175,7 +210,7 @@ class InlineSkipList {
|
||||
}
|
||||
|
||||
// Return true if key is greater than the data stored in "n". Null n
|
||||
// is considered infinite.
|
||||
// is considered infinite. n should not be head_.
|
||||
bool KeyIsAfterNode(const char* key, Node* n) const;
|
||||
|
||||
// Returns the earliest node with a key >= key.
|
||||
@@ -188,6 +223,13 @@ class InlineSkipList {
|
||||
// level in [0..max_height_-1], if prev is non-null.
|
||||
Node* FindLessThan(const char* key, Node** prev = nullptr) const;
|
||||
|
||||
// Return the latest node with a key < key on bottom_level. Start searching
|
||||
// from root node on the level below top_level.
|
||||
// Fills prev[level] with pointer to previous node at "level" for every
|
||||
// level in [bottom_level..top_level-1], if prev is non-null.
|
||||
Node* FindLessThan(const char* key, Node** prev, Node* root, int top_level,
|
||||
int bottom_level) const;
|
||||
|
||||
// Return the last node in the list.
|
||||
// Return head_ if list is empty.
|
||||
Node* FindLast() const;
|
||||
@@ -198,8 +240,13 @@ class InlineSkipList {
|
||||
// point to a node that is before the key, and after should point to
|
||||
// a node that is after the key. after should be nullptr if a good after
|
||||
// node isn't conveniently available.
|
||||
void FindLevelSplice(const char* key, Node* before, Node* after, int level,
|
||||
Node** out_prev, Node** out_next);
|
||||
void FindSpliceForLevel(const char* key, Node* before, Node* after, int level,
|
||||
Node** out_prev, Node** out_next);
|
||||
|
||||
// Recomputes Splice levels from highest_level (inclusive) down to
|
||||
// lowest_level (inclusive).
|
||||
void RecomputeSpliceLevels(const char* key, Splice* splice,
|
||||
int recompute_level);
|
||||
|
||||
// No copying allowed
|
||||
InlineSkipList(const InlineSkipList&);
|
||||
@@ -208,6 +255,19 @@ class InlineSkipList {
|
||||
|
||||
// Implementation details follow
|
||||
|
||||
template <class Comparator>
|
||||
struct InlineSkipList<Comparator>::Splice {
|
||||
// The invariant of a Splice is that prev_[i+1].key <= prev_[i].key <
|
||||
// next_[i].key <= next_[i+1].key for all i. That means that if a
|
||||
// key is bracketed by prev_[i] and next_[i] then it is bracketed by
|
||||
// all higher levels. It is _not_ required that prev_[i]->Next(i) ==
|
||||
// next_[i] (it probably did at some point in the past, but intervening
|
||||
// or concurrent operations might have inserted nodes in between).
|
||||
int height_ = 0;
|
||||
Node** prev_;
|
||||
Node** next_;
|
||||
};
|
||||
|
||||
// The Node data type is more of a pointer into custom-managed memory than
|
||||
// a traditional C++ struct. The key is stored in the bytes immediately
|
||||
// after the struct, and the next_ pointers for nodes with height > 1 are
|
||||
@@ -265,6 +325,14 @@ struct InlineSkipList<Comparator>::Node {
|
||||
next_[-n].store(x, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Insert node after prev on specific level.
|
||||
void InsertAfter(Node* prev, int level) {
|
||||
// NoBarrier_SetNext() suffices since we will add a barrier when
|
||||
// we publish a pointer to "this" in prev.
|
||||
NoBarrier_SetNext(level, prev->NoBarrier_Next(level));
|
||||
prev->SetNext(level, this);
|
||||
}
|
||||
|
||||
private:
|
||||
// next_[0] is the lowest level link (level 0). Higher levels are
|
||||
// stored _earlier_, so level 1 is at next_[-1].
|
||||
@@ -362,6 +430,7 @@ template <class Comparator>
|
||||
bool InlineSkipList<Comparator>::KeyIsAfterNode(const char* key,
|
||||
Node* n) const {
|
||||
// nullptr n is considered infinite
|
||||
assert(n != head_);
|
||||
return (n != nullptr) && (compare_(n->Key(), key) < 0);
|
||||
}
|
||||
|
||||
@@ -401,8 +470,17 @@ InlineSkipList<Comparator>::FindGreaterOrEqual(const char* key) const {
|
||||
template <class Comparator>
|
||||
typename InlineSkipList<Comparator>::Node*
|
||||
InlineSkipList<Comparator>::FindLessThan(const char* key, Node** prev) const {
|
||||
Node* x = head_;
|
||||
int level = GetMaxHeight() - 1;
|
||||
return FindLessThan(key, prev, head_, GetMaxHeight(), 0);
|
||||
}
|
||||
|
||||
template <class Comparator>
|
||||
typename InlineSkipList<Comparator>::Node*
|
||||
InlineSkipList<Comparator>::FindLessThan(const char* key, Node** prev,
|
||||
Node* root, int top_level,
|
||||
int bottom_level) const {
|
||||
assert(top_level > bottom_level);
|
||||
int level = top_level - 1;
|
||||
Node* x = root;
|
||||
// KeyIsAfter(key, last_not_after) is definitely false
|
||||
Node* last_not_after = nullptr;
|
||||
while (true) {
|
||||
@@ -416,10 +494,10 @@ InlineSkipList<Comparator>::FindLessThan(const char* key, Node** prev) const {
|
||||
if (prev != nullptr) {
|
||||
prev[level] = x;
|
||||
}
|
||||
if (level == 0) {
|
||||
if (level == bottom_level) {
|
||||
return x;
|
||||
} else {
|
||||
// Switch to next list, reuse KeyIUsAfterNode() result
|
||||
// Switch to next list, reuse KeyIsAfterNode() result
|
||||
last_not_after = next;
|
||||
level--;
|
||||
}
|
||||
@@ -483,19 +561,14 @@ InlineSkipList<Comparator>::InlineSkipList(const Comparator cmp,
|
||||
allocator_(allocator),
|
||||
head_(AllocateNode(0, max_height)),
|
||||
max_height_(1),
|
||||
prev_height_(1) {
|
||||
seq_splice_(AllocateSplice()) {
|
||||
assert(max_height > 0 && kMaxHeight_ == static_cast<uint32_t>(max_height));
|
||||
assert(branching_factor > 1 &&
|
||||
kBranching_ == static_cast<uint32_t>(branching_factor));
|
||||
assert(kScaledInverseBranching_ > 0);
|
||||
// Allocate the prev_ Node* array, directly from the passed-in allocator.
|
||||
// prev_ does not need to be freed, as its life cycle is tied up with
|
||||
// the allocator as a whole.
|
||||
prev_ = reinterpret_cast<Node**>(
|
||||
allocator_->AllocateAligned(sizeof(Node*) * kMaxHeight_));
|
||||
for (int i = 0; i < kMaxHeight_; i++) {
|
||||
|
||||
for (int i = 0; i < kMaxHeight_; ++i) {
|
||||
head_->SetNext(i, nullptr);
|
||||
prev_[i] = head_;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -529,70 +602,49 @@ InlineSkipList<Comparator>::AllocateNode(size_t key_size, int height) {
|
||||
}
|
||||
|
||||
template <class Comparator>
|
||||
void InlineSkipList<Comparator>::Insert(const char* key) {
|
||||
// InsertConcurrently often can't maintain the prev_ invariants, so
|
||||
// it just sets prev_height_ to zero, letting us know that we should
|
||||
// ignore it. A relaxed load suffices here because write thread
|
||||
// synchronization separates Insert calls from InsertConcurrently calls.
|
||||
auto prev_height = prev_height_.load(std::memory_order_relaxed);
|
||||
|
||||
// fast path for sequential insertion
|
||||
if (prev_height > 0 && !KeyIsAfterNode(key, prev_[0]->NoBarrier_Next(0)) &&
|
||||
(prev_[0] == head_ || KeyIsAfterNode(key, prev_[0]))) {
|
||||
assert(prev_[0] != head_ || (prev_height == 1 && GetMaxHeight() == 1));
|
||||
|
||||
// Outside of this method prev_[1..max_height_] is the predecessor
|
||||
// of prev_[0], and prev_height_ refers to prev_[0]. Inside Insert
|
||||
// prev_[0..max_height - 1] is the predecessor of key. Switch from
|
||||
// the external state to the internal
|
||||
for (int i = 1; i < prev_height; i++) {
|
||||
prev_[i] = prev_[0];
|
||||
}
|
||||
} else {
|
||||
// TODO(opt): we could use a NoBarrier predecessor search as an
|
||||
// optimization for architectures where memory_order_acquire needs
|
||||
// a synchronization instruction. Doesn't matter on x86
|
||||
FindLessThan(key, prev_);
|
||||
}
|
||||
|
||||
// Our data structure does not allow duplicate insertion
|
||||
assert(prev_[0]->Next(0) == nullptr || !Equal(key, prev_[0]->Next(0)->Key()));
|
||||
|
||||
// Find the Node that we placed before the key in AllocateKey
|
||||
Node* x = reinterpret_cast<Node*>(const_cast<char*>(key)) - 1;
|
||||
int height = x->UnstashHeight();
|
||||
assert(height >= 1 && height <= kMaxHeight_);
|
||||
|
||||
if (height > GetMaxHeight()) {
|
||||
for (int i = GetMaxHeight(); i < height; i++) {
|
||||
prev_[i] = head_;
|
||||
}
|
||||
|
||||
// It is ok to mutate max_height_ without any synchronization
|
||||
// with concurrent readers. A concurrent reader that observes
|
||||
// the new value of max_height_ will see either the old value of
|
||||
// new level pointers from head_ (nullptr), or a new value set in
|
||||
// the loop below. In the former case the reader will
|
||||
// immediately drop to the next level since nullptr sorts after all
|
||||
// keys. In the latter case the reader will use the new node.
|
||||
max_height_.store(height, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
for (int i = 0; i < height; i++) {
|
||||
// NoBarrier_SetNext() suffices since we will add a barrier when
|
||||
// we publish a pointer to "x" in prev[i].
|
||||
x->NoBarrier_SetNext(i, prev_[i]->NoBarrier_Next(i));
|
||||
prev_[i]->SetNext(i, x);
|
||||
}
|
||||
prev_[0] = x;
|
||||
prev_height_.store(height, std::memory_order_relaxed);
|
||||
typename InlineSkipList<Comparator>::Splice*
|
||||
InlineSkipList<Comparator>::AllocateSplice() {
|
||||
// size of prev_ and next_
|
||||
size_t array_size = sizeof(Node*) * (kMaxHeight_ + 1);
|
||||
char* raw = allocator_->AllocateAligned(sizeof(Splice) + array_size * 2);
|
||||
Splice* splice = reinterpret_cast<Splice*>(raw);
|
||||
splice->height_ = 0;
|
||||
splice->prev_ = reinterpret_cast<Node**>(raw + sizeof(Splice));
|
||||
splice->next_ = reinterpret_cast<Node**>(raw + sizeof(Splice) + array_size);
|
||||
return splice;
|
||||
}
|
||||
|
||||
template <class Comparator>
|
||||
void InlineSkipList<Comparator>::FindLevelSplice(const char* key, Node* before,
|
||||
Node* after, int level,
|
||||
Node** out_prev,
|
||||
Node** out_next) {
|
||||
void InlineSkipList<Comparator>::Insert(const char* key) {
|
||||
Insert<false>(key, seq_splice_, false);
|
||||
}
|
||||
|
||||
template <class Comparator>
|
||||
void InlineSkipList<Comparator>::InsertConcurrently(const char* key) {
|
||||
Node* prev[kMaxPossibleHeight];
|
||||
Node* next[kMaxPossibleHeight];
|
||||
Splice splice;
|
||||
splice.prev_ = prev;
|
||||
splice.next_ = next;
|
||||
Insert<true>(key, &splice, false);
|
||||
}
|
||||
|
||||
template <class Comparator>
|
||||
void InlineSkipList<Comparator>::InsertWithHint(const char* key, void** hint) {
|
||||
assert(hint != nullptr);
|
||||
Splice* splice = reinterpret_cast<Splice*>(*hint);
|
||||
if (splice == nullptr) {
|
||||
splice = AllocateSplice();
|
||||
*hint = reinterpret_cast<void*>(splice);
|
||||
}
|
||||
Insert<false>(key, splice, true);
|
||||
}
|
||||
|
||||
template <class Comparator>
|
||||
void InlineSkipList<Comparator>::FindSpliceForLevel(const char* key,
|
||||
Node* before, Node* after,
|
||||
int level, Node** out_prev,
|
||||
Node** out_next) {
|
||||
while (true) {
|
||||
Node* next = before->Next(level);
|
||||
assert(before == head_ || next == nullptr ||
|
||||
@@ -609,27 +661,28 @@ void InlineSkipList<Comparator>::FindLevelSplice(const char* key, Node* before,
|
||||
}
|
||||
|
||||
template <class Comparator>
|
||||
void InlineSkipList<Comparator>::InsertConcurrently(const char* key) {
|
||||
void InlineSkipList<Comparator>::RecomputeSpliceLevels(const char* key,
|
||||
Splice* splice,
|
||||
int recompute_level) {
|
||||
assert(recompute_level > 0);
|
||||
assert(recompute_level <= splice->height_);
|
||||
for (int i = recompute_level - 1; i >= 0; --i) {
|
||||
FindSpliceForLevel(key, splice->prev_[i + 1], splice->next_[i + 1], i,
|
||||
&splice->prev_[i], &splice->next_[i]);
|
||||
}
|
||||
}
|
||||
|
||||
template <class Comparator>
|
||||
template <bool UseCAS>
|
||||
void InlineSkipList<Comparator>::Insert(const char* key, Splice* splice,
|
||||
bool allow_partial_splice_fix) {
|
||||
Node* x = reinterpret_cast<Node*>(const_cast<char*>(key)) - 1;
|
||||
int height = x->UnstashHeight();
|
||||
assert(height >= 1 && height <= kMaxHeight_);
|
||||
|
||||
// We don't have a lock-free algorithm for updating prev_, but we do have
|
||||
// the option of invalidating the entire sequential-insertion cache.
|
||||
// prev_'s invariant is that prev_[i] (i > 0) is the predecessor of
|
||||
// prev_[0] at that level. We're only going to violate that if height
|
||||
// > 1 and key lands after prev_[height - 1] but before prev_[0].
|
||||
// Comparisons are pretty expensive, so an easier version is to just
|
||||
// clear the cache if height > 1. We only write to prev_height_ if the
|
||||
// nobody else has, to avoid invalidating the root of the skip list in
|
||||
// all of the other CPU caches.
|
||||
if (height > 1 && prev_height_.load(std::memory_order_relaxed) != 0) {
|
||||
prev_height_.store(0, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
int max_height = max_height_.load(std::memory_order_relaxed);
|
||||
while (height > max_height) {
|
||||
if (max_height_.compare_exchange_strong(max_height, height)) {
|
||||
if (max_height_.compare_exchange_weak(max_height, height)) {
|
||||
// successfully updated it
|
||||
max_height = height;
|
||||
break;
|
||||
@@ -639,28 +692,159 @@ void InlineSkipList<Comparator>::InsertConcurrently(const char* key) {
|
||||
}
|
||||
assert(max_height <= kMaxPossibleHeight);
|
||||
|
||||
Node* prev[kMaxPossibleHeight + 1];
|
||||
Node* next[kMaxPossibleHeight + 1];
|
||||
prev[max_height] = head_;
|
||||
next[max_height] = nullptr;
|
||||
for (int i = max_height - 1; i >= 0; --i) {
|
||||
FindLevelSplice(key, prev[i + 1], next[i + 1], i, &prev[i], &next[i]);
|
||||
}
|
||||
for (int i = 0; i < height; ++i) {
|
||||
while (true) {
|
||||
x->NoBarrier_SetNext(i, next[i]);
|
||||
if (prev[i]->CASNext(i, next[i], x)) {
|
||||
// success
|
||||
int recompute_height = 0;
|
||||
if (splice->height_ < max_height) {
|
||||
// Either splice has never been used or max_height has grown since
|
||||
// last use. We could potentially fix it in the latter case, but
|
||||
// that is tricky.
|
||||
splice->prev_[max_height] = head_;
|
||||
splice->next_[max_height] = nullptr;
|
||||
splice->height_ = max_height;
|
||||
recompute_height = max_height;
|
||||
} else {
|
||||
// Splice is a valid proper-height splice that brackets some
|
||||
// key, but does it bracket this one? We need to validate it and
|
||||
// recompute a portion of the splice (levels 0..recompute_height-1)
|
||||
// that is a superset of all levels that don't bracket the new key.
|
||||
// Several choices are reasonable, because we have to balance the work
|
||||
// saved against the extra comparisons required to validate the Splice.
|
||||
//
|
||||
// One strategy is just to recompute all of orig_splice_height if the
|
||||
// bottom level isn't bracketing. This pessimistically assumes that
|
||||
// we will either get a perfect Splice hit (increasing sequential
|
||||
// inserts) or have no locality.
|
||||
//
|
||||
// Another strategy is to walk up the Splice's levels until we find
|
||||
// a level that brackets the key. This strategy lets the Splice
|
||||
// hint help for other cases: it turns insertion from O(log N) into
|
||||
// O(log D), where D is the number of nodes in between the key that
|
||||
// produced the Splice and the current insert (insertion is aided
|
||||
// whether the new key is before or after the splice). If you have
|
||||
// a way of using a prefix of the key to map directly to the closest
|
||||
// Splice out of O(sqrt(N)) Splices and we make it so that splices
|
||||
// can also be used as hints during read, then we end up with Oshman's
|
||||
// and Shavit's SkipTrie, which has O(log log N) lookup and insertion
|
||||
// (compare to O(log N) for skip list).
|
||||
//
|
||||
// We control the pessimistic strategy with allow_partial_splice_fix.
|
||||
// A good strategy is probably to be pessimistic for seq_splice_,
|
||||
// optimistic if the caller actually went to the work of providing
|
||||
// a Splice.
|
||||
while (recompute_height < max_height) {
|
||||
if (splice->prev_[recompute_height]->Next(recompute_height) !=
|
||||
splice->next_[recompute_height]) {
|
||||
// splice isn't tight at this level, there must have been some inserts
|
||||
// to this
|
||||
// location that didn't update the splice. We might only be a little
|
||||
// stale, but if
|
||||
// the splice is very stale it would be O(N) to fix it. We haven't used
|
||||
// up any of
|
||||
// our budget of comparisons, so always move up even if we are
|
||||
// pessimistic about
|
||||
// our chances of success.
|
||||
++recompute_height;
|
||||
} else if (splice->prev_[recompute_height] != head_ &&
|
||||
!KeyIsAfterNode(key, splice->prev_[recompute_height])) {
|
||||
// key is from before splice
|
||||
if (allow_partial_splice_fix) {
|
||||
// skip all levels with the same node without more comparisons
|
||||
Node* bad = splice->prev_[recompute_height];
|
||||
while (splice->prev_[recompute_height] == bad) {
|
||||
++recompute_height;
|
||||
}
|
||||
} else {
|
||||
// we're pessimistic, recompute everything
|
||||
recompute_height = max_height;
|
||||
}
|
||||
} else if (KeyIsAfterNode(key, splice->next_[recompute_height])) {
|
||||
// key is from after splice
|
||||
if (allow_partial_splice_fix) {
|
||||
Node* bad = splice->next_[recompute_height];
|
||||
while (splice->next_[recompute_height] == bad) {
|
||||
++recompute_height;
|
||||
}
|
||||
} else {
|
||||
recompute_height = max_height;
|
||||
}
|
||||
} else {
|
||||
// this level brackets the key, we won!
|
||||
break;
|
||||
}
|
||||
// CAS failed, we need to recompute prev and next. It is unlikely
|
||||
// to be helpful to try to use a different level as we redo the
|
||||
// search, because it should be unlikely that lots of nodes have
|
||||
// been inserted between prev[i] and next[i]. No point in using
|
||||
// next[i] as the after hint, because we know it is stale.
|
||||
FindLevelSplice(key, prev[i], nullptr, i, &prev[i], &next[i]);
|
||||
}
|
||||
}
|
||||
assert(recompute_height <= max_height);
|
||||
if (recompute_height > 0) {
|
||||
RecomputeSpliceLevels(key, splice, recompute_height);
|
||||
}
|
||||
|
||||
bool splice_is_valid = true;
|
||||
if (UseCAS) {
|
||||
for (int i = 0; i < height; ++i) {
|
||||
while (true) {
|
||||
assert(splice->next_[i] == nullptr ||
|
||||
compare_(x->Key(), splice->next_[i]->Key()) < 0);
|
||||
assert(splice->prev_[i] == head_ ||
|
||||
compare_(splice->prev_[i]->Key(), x->Key()) < 0);
|
||||
x->NoBarrier_SetNext(i, splice->next_[i]);
|
||||
if (splice->prev_[i]->CASNext(i, splice->next_[i], x)) {
|
||||
// success
|
||||
break;
|
||||
}
|
||||
// CAS failed, we need to recompute prev and next. It is unlikely
|
||||
// to be helpful to try to use a different level as we redo the
|
||||
// search, because it should be unlikely that lots of nodes have
|
||||
// been inserted between prev[i] and next[i]. No point in using
|
||||
// next[i] as the after hint, because we know it is stale.
|
||||
FindSpliceForLevel(key, splice->prev_[i], nullptr, i, &splice->prev_[i],
|
||||
&splice->next_[i]);
|
||||
|
||||
// Since we've narrowed the bracket for level i, we might have
|
||||
// violated the Splice constraint between i and i-1. Make sure
|
||||
// we recompute the whole thing next time.
|
||||
if (i > 0) {
|
||||
splice_is_valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < height; ++i) {
|
||||
if (i >= recompute_height &&
|
||||
splice->prev_[i]->Next(i) != splice->next_[i]) {
|
||||
FindSpliceForLevel(key, splice->prev_[i], nullptr, i, &splice->prev_[i],
|
||||
&splice->next_[i]);
|
||||
}
|
||||
assert(splice->next_[i] == nullptr ||
|
||||
compare_(x->Key(), splice->next_[i]->Key()) < 0);
|
||||
assert(splice->prev_[i] == head_ ||
|
||||
compare_(splice->prev_[i]->Key(), x->Key()) < 0);
|
||||
assert(splice->prev_[i]->Next(i) == splice->next_[i]);
|
||||
x->NoBarrier_SetNext(i, splice->next_[i]);
|
||||
splice->prev_[i]->SetNext(i, x);
|
||||
}
|
||||
}
|
||||
if (splice_is_valid) {
|
||||
for (int i = 0; i < height; ++i) {
|
||||
splice->prev_[i] = x;
|
||||
}
|
||||
assert(splice->prev_[splice->height_] == head_);
|
||||
assert(splice->next_[splice->height_] == nullptr);
|
||||
for (int i = 0; i < splice->height_; ++i) {
|
||||
assert(splice->next_[i] == nullptr ||
|
||||
compare_(key, splice->next_[i]->Key()) < 0);
|
||||
assert(splice->prev_[i] == head_ ||
|
||||
compare_(splice->prev_[i]->Key(), key) <= 0);
|
||||
assert(splice->prev_[i + 1] == splice->prev_[i] ||
|
||||
splice->prev_[i + 1] == head_ ||
|
||||
compare_(splice->prev_[i + 1]->Key(), splice->prev_[i]->Key()) <
|
||||
0);
|
||||
assert(splice->next_[i + 1] == splice->next_[i] ||
|
||||
splice->next_[i + 1] == nullptr ||
|
||||
compare_(splice->next_[i]->Key(), splice->next_[i + 1]->Key()) <
|
||||
0);
|
||||
}
|
||||
} else {
|
||||
splice->height_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
template <class Comparator>
|
||||
@@ -673,4 +857,44 @@ bool InlineSkipList<Comparator>::Contains(const char* key) const {
|
||||
}
|
||||
}
|
||||
|
||||
template <class Comparator>
|
||||
void InlineSkipList<Comparator>::TEST_Validate() const {
|
||||
// Interate over all levels at the same time, and verify nodes appear in
|
||||
// the right order, and nodes appear in upper level also appear in lower
|
||||
// levels.
|
||||
Node* nodes[kMaxPossibleHeight];
|
||||
int max_height = GetMaxHeight();
|
||||
for (int i = 0; i < max_height; i++) {
|
||||
nodes[i] = head_;
|
||||
}
|
||||
while (nodes[0] != nullptr) {
|
||||
Node* l0_next = nodes[0]->Next(0);
|
||||
if (l0_next == nullptr) {
|
||||
break;
|
||||
}
|
||||
assert(nodes[0] == head_ || compare_(nodes[0]->Key(), l0_next->Key()) < 0);
|
||||
nodes[0] = l0_next;
|
||||
|
||||
int i = 1;
|
||||
while (i < max_height) {
|
||||
Node* next = nodes[i]->Next(i);
|
||||
if (next == nullptr) {
|
||||
break;
|
||||
}
|
||||
auto cmp = compare_(nodes[0]->Key(), next->Key());
|
||||
assert(cmp <= 0);
|
||||
if (cmp == 0) {
|
||||
assert(next == nodes[0]);
|
||||
nodes[i] = next;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
for (int i = 1; i < max_height; i++) {
|
||||
assert(nodes[i]->Next(i) == nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
+140
-1
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "db/inlineskiplist.h"
|
||||
#include <set>
|
||||
#include <unordered_set>
|
||||
#include "rocksdb/env.h"
|
||||
#include "util/concurrent_arena.h"
|
||||
#include "util/hash.h"
|
||||
@@ -42,7 +43,48 @@ struct TestComparator {
|
||||
}
|
||||
};
|
||||
|
||||
class InlineSkipTest : public testing::Test {};
|
||||
typedef InlineSkipList<TestComparator> TestInlineSkipList;
|
||||
|
||||
class InlineSkipTest : public testing::Test {
|
||||
public:
|
||||
void Insert(TestInlineSkipList* list, Key key) {
|
||||
char* buf = list->AllocateKey(sizeof(Key));
|
||||
memcpy(buf, &key, sizeof(Key));
|
||||
list->Insert(buf);
|
||||
keys_.insert(key);
|
||||
}
|
||||
|
||||
void InsertWithHint(TestInlineSkipList* list, Key key, void** hint) {
|
||||
char* buf = list->AllocateKey(sizeof(Key));
|
||||
memcpy(buf, &key, sizeof(Key));
|
||||
list->InsertWithHint(buf, hint);
|
||||
keys_.insert(key);
|
||||
}
|
||||
|
||||
void Validate(TestInlineSkipList* list) {
|
||||
// Check keys exist.
|
||||
for (Key key : keys_) {
|
||||
ASSERT_TRUE(list->Contains(Encode(&key)));
|
||||
}
|
||||
// Iterate over the list, make sure keys appears in order and no extra
|
||||
// keys exist.
|
||||
TestInlineSkipList::Iterator iter(list);
|
||||
ASSERT_FALSE(iter.Valid());
|
||||
Key zero = 0;
|
||||
iter.Seek(Encode(&zero));
|
||||
for (Key key : keys_) {
|
||||
ASSERT_TRUE(iter.Valid());
|
||||
ASSERT_EQ(key, Decode(iter.key()));
|
||||
iter.Next();
|
||||
}
|
||||
ASSERT_FALSE(iter.Valid());
|
||||
// Validate the list is well-formed.
|
||||
list->TEST_Validate();
|
||||
}
|
||||
|
||||
private:
|
||||
std::set<Key> keys_;
|
||||
};
|
||||
|
||||
TEST_F(InlineSkipTest, Empty) {
|
||||
Arena arena;
|
||||
@@ -153,6 +195,103 @@ TEST_F(InlineSkipTest, InsertAndLookup) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(InlineSkipTest, InsertWithHint_Sequential) {
|
||||
const int N = 100000;
|
||||
Arena arena;
|
||||
TestComparator cmp;
|
||||
TestInlineSkipList list(cmp, &arena);
|
||||
void* hint = nullptr;
|
||||
for (int i = 0; i < N; i++) {
|
||||
Key key = i;
|
||||
InsertWithHint(&list, key, &hint);
|
||||
}
|
||||
Validate(&list);
|
||||
}
|
||||
|
||||
TEST_F(InlineSkipTest, InsertWithHint_MultipleHints) {
|
||||
const int N = 100000;
|
||||
const int S = 100;
|
||||
Random rnd(534);
|
||||
Arena arena;
|
||||
TestComparator cmp;
|
||||
TestInlineSkipList list(cmp, &arena);
|
||||
void* hints[S];
|
||||
Key last_key[S];
|
||||
for (int i = 0; i < S; i++) {
|
||||
hints[i] = nullptr;
|
||||
last_key[i] = 0;
|
||||
}
|
||||
for (int i = 0; i < N; i++) {
|
||||
Key s = rnd.Uniform(S);
|
||||
Key key = (s << 32) + (++last_key[s]);
|
||||
InsertWithHint(&list, key, &hints[s]);
|
||||
}
|
||||
Validate(&list);
|
||||
}
|
||||
|
||||
TEST_F(InlineSkipTest, InsertWithHint_MultipleHintsRandom) {
|
||||
const int N = 100000;
|
||||
const int S = 100;
|
||||
Random rnd(534);
|
||||
Arena arena;
|
||||
TestComparator cmp;
|
||||
TestInlineSkipList list(cmp, &arena);
|
||||
void* hints[S];
|
||||
for (int i = 0; i < S; i++) {
|
||||
hints[i] = nullptr;
|
||||
}
|
||||
for (int i = 0; i < N; i++) {
|
||||
Key s = rnd.Uniform(S);
|
||||
Key key = (s << 32) + rnd.Next();
|
||||
InsertWithHint(&list, key, &hints[s]);
|
||||
}
|
||||
Validate(&list);
|
||||
}
|
||||
|
||||
TEST_F(InlineSkipTest, InsertWithHint_CompatibleWithInsertWithoutHint) {
|
||||
const int N = 100000;
|
||||
const int S1 = 100;
|
||||
const int S2 = 100;
|
||||
Random rnd(534);
|
||||
Arena arena;
|
||||
TestComparator cmp;
|
||||
TestInlineSkipList list(cmp, &arena);
|
||||
std::unordered_set<Key> used;
|
||||
Key with_hint[S1];
|
||||
Key without_hint[S2];
|
||||
void* hints[S1];
|
||||
for (int i = 0; i < S1; i++) {
|
||||
hints[i] = nullptr;
|
||||
while (true) {
|
||||
Key s = rnd.Next();
|
||||
if (used.insert(s).second) {
|
||||
with_hint[i] = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < S2; i++) {
|
||||
while (true) {
|
||||
Key s = rnd.Next();
|
||||
if (used.insert(s).second) {
|
||||
without_hint[i] = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < N; i++) {
|
||||
Key s = rnd.Uniform(S1 + S2);
|
||||
if (s < S1) {
|
||||
Key key = (with_hint[s] << 32) + rnd.Next();
|
||||
InsertWithHint(&list, key, &hints[s]);
|
||||
} else {
|
||||
Key key = (without_hint[s - S1] << 32) + rnd.Next();
|
||||
Insert(&list, key);
|
||||
}
|
||||
}
|
||||
Validate(&list);
|
||||
}
|
||||
|
||||
// We want to make sure that with a single writer and multiple
|
||||
// concurrent readers (with no synchronization other than when a
|
||||
// reader's iterator is created), the reader always observes all the
|
||||
|
||||
+307
-134
@@ -25,62 +25,146 @@
|
||||
namespace rocksdb {
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
const std::map<LevelStatType, LevelStat> InternalStats::compaction_level_stats =
|
||||
{
|
||||
{LevelStatType::NUM_FILES, LevelStat{"NumFiles", "Files"}},
|
||||
{LevelStatType::COMPACTED_FILES,
|
||||
LevelStat{"CompactedFiles", "CompactedFiles"}},
|
||||
{LevelStatType::SIZE_MB, LevelStat{"SizeMB", "Size(MB}"}},
|
||||
{LevelStatType::SCORE, LevelStat{"Score", "Score"}},
|
||||
{LevelStatType::READ_GB, LevelStat{"ReadGB", "Read(GB}"}},
|
||||
{LevelStatType::RN_GB, LevelStat{"RnGB", "Rn(GB}"}},
|
||||
{LevelStatType::RNP1_GB, LevelStat{"Rnp1GB", "Rnp1(GB}"}},
|
||||
{LevelStatType::WRITE_GB, LevelStat{"WriteGB", "Write(GB}"}},
|
||||
{LevelStatType::W_NEW_GB, LevelStat{"WnewGB", "Wnew(GB}"}},
|
||||
{LevelStatType::MOVED_GB, LevelStat{"MovedGB", "Moved(GB}"}},
|
||||
{LevelStatType::WRITE_AMP, LevelStat{"WriteAmp", "W-Amp"}},
|
||||
{LevelStatType::READ_MBPS, LevelStat{"ReadMBps", "Rd(MB/s}"}},
|
||||
{LevelStatType::WRITE_MBPS, LevelStat{"WriteMBps", "Wr(MB/s}"}},
|
||||
{LevelStatType::COMP_SEC, LevelStat{"CompSec", "Comp(sec}"}},
|
||||
{LevelStatType::COMP_COUNT, LevelStat{"CompCount", "Comp(cnt}"}},
|
||||
{LevelStatType::AVG_SEC, LevelStat{"AvgSec", "Avg(sec}"}},
|
||||
{LevelStatType::KEY_IN, LevelStat{"KeyIn", "KeyIn"}},
|
||||
{LevelStatType::KEY_DROP, LevelStat{"KeyDrop", "KeyDrop"}},
|
||||
};
|
||||
|
||||
namespace {
|
||||
const double kMB = 1048576.0;
|
||||
const double kGB = kMB * 1024;
|
||||
const double kMicrosInSec = 1000000.0;
|
||||
|
||||
void PrintLevelStatsHeader(char* buf, size_t len, const std::string& cf_name) {
|
||||
snprintf(
|
||||
buf, len,
|
||||
"\n** Compaction Stats [%s] **\n"
|
||||
"Level Files Size(MB) Score Read(GB) Rn(GB) Rnp1(GB) "
|
||||
"Write(GB) Wnew(GB) Moved(GB) W-Amp Rd(MB/s) Wr(MB/s) "
|
||||
"Comp(sec) Comp(cnt) Avg(sec) KeyIn KeyDrop\n"
|
||||
"--------------------------------------------------------------------"
|
||||
"-----------------------------------------------------------"
|
||||
"--------------------------------------\n",
|
||||
cf_name.c_str());
|
||||
int written_size =
|
||||
snprintf(buf, len, "\n** Compaction Stats [%s] **\n", cf_name.c_str());
|
||||
auto hdr = [](LevelStatType t) {
|
||||
return InternalStats::compaction_level_stats.at(t).header_name.c_str();
|
||||
};
|
||||
int line_size = snprintf(
|
||||
buf + written_size, len - written_size,
|
||||
"Level %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s\n",
|
||||
// Note that we skip COMPACTED_FILES and merge it with Files column
|
||||
hdr(LevelStatType::NUM_FILES), hdr(LevelStatType::SIZE_MB),
|
||||
hdr(LevelStatType::SCORE), hdr(LevelStatType::READ_GB),
|
||||
hdr(LevelStatType::RN_GB), hdr(LevelStatType::RNP1_GB),
|
||||
hdr(LevelStatType::WRITE_GB), hdr(LevelStatType::W_NEW_GB),
|
||||
hdr(LevelStatType::MOVED_GB), hdr(LevelStatType::WRITE_AMP),
|
||||
hdr(LevelStatType::READ_MBPS), hdr(LevelStatType::WRITE_MBPS),
|
||||
hdr(LevelStatType::COMP_SEC), hdr(LevelStatType::COMP_COUNT),
|
||||
hdr(LevelStatType::AVG_SEC), hdr(LevelStatType::KEY_IN),
|
||||
hdr(LevelStatType::KEY_DROP));
|
||||
|
||||
written_size += line_size;
|
||||
snprintf(buf + written_size, len - written_size, "%s\n",
|
||||
std::string(line_size, '-').c_str());
|
||||
}
|
||||
|
||||
void PrintLevelStats(char* buf, size_t len, const std::string& name,
|
||||
int num_files, int being_compacted, double total_file_size,
|
||||
double score, double w_amp,
|
||||
const InternalStats::CompactionStats& stats) {
|
||||
void PrepareLevelStats(std::map<LevelStatType, double>* level_stats,
|
||||
int num_files, int being_compacted,
|
||||
double total_file_size, double score, double w_amp,
|
||||
const InternalStats::CompactionStats& stats) {
|
||||
uint64_t bytes_read =
|
||||
stats.bytes_read_non_output_levels + stats.bytes_read_output_level;
|
||||
int64_t bytes_new =
|
||||
stats.bytes_written - stats.bytes_read_output_level;
|
||||
double elapsed = (stats.micros + 1) / kMicrosInSec;
|
||||
std::string num_input_records = NumberToHumanString(stats.num_input_records);
|
||||
std::string num_dropped_records =
|
||||
NumberToHumanString(stats.num_dropped_records);
|
||||
|
||||
(*level_stats)[LevelStatType::NUM_FILES] = num_files;
|
||||
(*level_stats)[LevelStatType::COMPACTED_FILES] = being_compacted;
|
||||
(*level_stats)[LevelStatType::SIZE_MB] = total_file_size / kMB;
|
||||
(*level_stats)[LevelStatType::SCORE] = score;
|
||||
(*level_stats)[LevelStatType::READ_GB] = bytes_read / kGB;
|
||||
(*level_stats)[LevelStatType::RN_GB] =
|
||||
stats.bytes_read_non_output_levels / kGB;
|
||||
(*level_stats)[LevelStatType::RNP1_GB] = stats.bytes_read_output_level / kGB;
|
||||
(*level_stats)[LevelStatType::WRITE_GB] = stats.bytes_written / kGB;
|
||||
(*level_stats)[LevelStatType::W_NEW_GB] = bytes_new / kGB;
|
||||
(*level_stats)[LevelStatType::MOVED_GB] = stats.bytes_moved / kGB;
|
||||
(*level_stats)[LevelStatType::WRITE_AMP] = w_amp;
|
||||
(*level_stats)[LevelStatType::READ_MBPS] = bytes_read / kMB / elapsed;
|
||||
(*level_stats)[LevelStatType::WRITE_MBPS] =
|
||||
stats.bytes_written / kMB / elapsed;
|
||||
(*level_stats)[LevelStatType::COMP_SEC] = stats.micros / kMicrosInSec;
|
||||
(*level_stats)[LevelStatType::COMP_COUNT] = stats.count;
|
||||
(*level_stats)[LevelStatType::AVG_SEC] =
|
||||
stats.count == 0 ? 0 : stats.micros / kMicrosInSec / stats.count;
|
||||
(*level_stats)[LevelStatType::KEY_IN] =
|
||||
static_cast<double>(stats.num_input_records);
|
||||
(*level_stats)[LevelStatType::KEY_DROP] =
|
||||
static_cast<double>(stats.num_dropped_records);
|
||||
}
|
||||
|
||||
void PrintLevelStats(char* buf, size_t len, const std::string& name,
|
||||
const std::map<LevelStatType, double>& stat_value) {
|
||||
snprintf(buf, len,
|
||||
"%4s %6d/%-3d %8.2f %5.1f " /* Level, Files, Size(MB), Score */
|
||||
"%8.1f " /* Read(GB) */
|
||||
"%7.1f " /* Rn(GB) */
|
||||
"%8.1f " /* Rnp1(GB) */
|
||||
"%9.1f " /* Write(GB) */
|
||||
"%8.1f " /* Wnew(GB) */
|
||||
"%9.1f " /* Moved(GB) */
|
||||
"%5.1f " /* W-Amp */
|
||||
"%8.1f " /* Rd(MB/s) */
|
||||
"%8.1f " /* Wr(MB/s) */
|
||||
"%9.0f " /* Comp(sec) */
|
||||
"%9d " /* Comp(cnt) */
|
||||
"%8.3f " /* Avg(sec) */
|
||||
"%7s " /* KeyIn */
|
||||
"%6s\n", /* KeyDrop */
|
||||
"%4s %6d/%-3d %8.2f %5.1f " /* Level, Files, Size(MB), Score */
|
||||
"%8.1f " /* Read(GB) */
|
||||
"%7.1f " /* Rn(GB) */
|
||||
"%8.1f " /* Rnp1(GB) */
|
||||
"%9.1f " /* Write(GB) */
|
||||
"%8.1f " /* Wnew(GB) */
|
||||
"%9.1f " /* Moved(GB) */
|
||||
"%5.1f " /* W-Amp */
|
||||
"%8.1f " /* Rd(MB/s) */
|
||||
"%8.1f " /* Wr(MB/s) */
|
||||
"%9.0f " /* Comp(sec) */
|
||||
"%9d " /* Comp(cnt) */
|
||||
"%8.3f " /* Avg(sec) */
|
||||
"%7s " /* KeyIn */
|
||||
"%6s\n", /* KeyDrop */
|
||||
name.c_str(),
|
||||
num_files, being_compacted, total_file_size / kMB, score,
|
||||
bytes_read / kGB, stats.bytes_read_non_output_levels / kGB,
|
||||
stats.bytes_read_output_level / kGB, stats.bytes_written / kGB,
|
||||
bytes_new / kGB, stats.bytes_moved / kGB, w_amp,
|
||||
bytes_read / kMB / elapsed, stats.bytes_written / kMB / elapsed,
|
||||
stats.micros / kMicrosInSec, stats.count,
|
||||
stats.count == 0 ? 0 : stats.micros / kMicrosInSec / stats.count,
|
||||
num_input_records.c_str(), num_dropped_records.c_str());
|
||||
static_cast<int>(stat_value.at(LevelStatType::NUM_FILES)),
|
||||
static_cast<int>(stat_value.at(LevelStatType::COMPACTED_FILES)),
|
||||
stat_value.at(LevelStatType::SIZE_MB),
|
||||
stat_value.at(LevelStatType::SCORE),
|
||||
stat_value.at(LevelStatType::READ_GB),
|
||||
stat_value.at(LevelStatType::RN_GB),
|
||||
stat_value.at(LevelStatType::RNP1_GB),
|
||||
stat_value.at(LevelStatType::WRITE_GB),
|
||||
stat_value.at(LevelStatType::W_NEW_GB),
|
||||
stat_value.at(LevelStatType::MOVED_GB),
|
||||
stat_value.at(LevelStatType::WRITE_AMP),
|
||||
stat_value.at(LevelStatType::READ_MBPS),
|
||||
stat_value.at(LevelStatType::WRITE_MBPS),
|
||||
stat_value.at(LevelStatType::COMP_SEC),
|
||||
static_cast<int>(stat_value.at(LevelStatType::COMP_COUNT)),
|
||||
stat_value.at(LevelStatType::AVG_SEC),
|
||||
NumberToHumanString(
|
||||
static_cast<std::int64_t>(stat_value.at(LevelStatType::KEY_IN)))
|
||||
.c_str(),
|
||||
NumberToHumanString(static_cast<std::int64_t>(
|
||||
stat_value.at(LevelStatType::KEY_DROP)))
|
||||
.c_str());
|
||||
}
|
||||
|
||||
void PrintLevelStats(char* buf, size_t len, const std::string& name,
|
||||
int num_files, int being_compacted, double total_file_size,
|
||||
double score, double w_amp,
|
||||
const InternalStats::CompactionStats& stats) {
|
||||
std::map<LevelStatType, double> level_stats;
|
||||
PrepareLevelStats(&level_stats, num_files, being_compacted, total_file_size,
|
||||
score, w_amp, stats);
|
||||
PrintLevelStats(buf, len, name, level_stats);
|
||||
}
|
||||
|
||||
// Assumes that trailing numbers represent an optional argument. This requires
|
||||
@@ -137,6 +221,7 @@ static const std::string num_live_versions = "num-live-versions";
|
||||
static const std::string current_version_number =
|
||||
"current-super-version-number";
|
||||
static const std::string estimate_live_data_size = "estimate-live-data-size";
|
||||
static const std::string min_log_number_to_keep = "min-log-number-to-keep";
|
||||
static const std::string base_level = "base-level";
|
||||
static const std::string total_sst_files_size = "total-sst-files-size";
|
||||
static const std::string estimate_pending_comp_bytes =
|
||||
@@ -201,6 +286,8 @@ const std::string DB::Properties::kCurrentSuperVersionNumber =
|
||||
rocksdb_prefix + current_version_number;
|
||||
const std::string DB::Properties::kEstimateLiveDataSize =
|
||||
rocksdb_prefix + estimate_live_data_size;
|
||||
const std::string DB::Properties::kMinLogNumberToKeep =
|
||||
rocksdb_prefix + min_log_number_to_keep;
|
||||
const std::string DB::Properties::kTotalSstFilesSize =
|
||||
rocksdb_prefix + total_sst_files_size;
|
||||
const std::string DB::Properties::kBaseLevel = rocksdb_prefix + base_level;
|
||||
@@ -211,73 +298,93 @@ const std::string DB::Properties::kAggregatedTableProperties =
|
||||
const std::string DB::Properties::kAggregatedTablePropertiesAtLevel =
|
||||
rocksdb_prefix + aggregated_table_properties_at_level;
|
||||
|
||||
const std::unordered_map<std::string,
|
||||
DBPropertyInfo> InternalStats::ppt_name_to_info = {
|
||||
{DB::Properties::kNumFilesAtLevelPrefix,
|
||||
{false, &InternalStats::HandleNumFilesAtLevel, nullptr}},
|
||||
{DB::Properties::kCompressionRatioAtLevelPrefix,
|
||||
{false, &InternalStats::HandleCompressionRatioAtLevelPrefix, nullptr}},
|
||||
{DB::Properties::kLevelStats,
|
||||
{false, &InternalStats::HandleLevelStats, nullptr}},
|
||||
{DB::Properties::kStats, {false, &InternalStats::HandleStats, nullptr}},
|
||||
{DB::Properties::kCFStats, {false, &InternalStats::HandleCFStats, nullptr}},
|
||||
{DB::Properties::kDBStats, {false, &InternalStats::HandleDBStats, nullptr}},
|
||||
{DB::Properties::kSSTables,
|
||||
{false, &InternalStats::HandleSsTables, nullptr}},
|
||||
{DB::Properties::kAggregatedTableProperties,
|
||||
{false, &InternalStats::HandleAggregatedTableProperties, nullptr}},
|
||||
{DB::Properties::kAggregatedTablePropertiesAtLevel,
|
||||
{false, &InternalStats::HandleAggregatedTablePropertiesAtLevel, nullptr}},
|
||||
{DB::Properties::kNumImmutableMemTable,
|
||||
{false, nullptr, &InternalStats::HandleNumImmutableMemTable}},
|
||||
{DB::Properties::kNumImmutableMemTableFlushed,
|
||||
{false, nullptr, &InternalStats::HandleNumImmutableMemTableFlushed}},
|
||||
{DB::Properties::kMemTableFlushPending,
|
||||
{false, nullptr, &InternalStats::HandleMemTableFlushPending}},
|
||||
{DB::Properties::kCompactionPending,
|
||||
{false, nullptr, &InternalStats::HandleCompactionPending}},
|
||||
{DB::Properties::kBackgroundErrors,
|
||||
{false, nullptr, &InternalStats::HandleBackgroundErrors}},
|
||||
{DB::Properties::kCurSizeActiveMemTable,
|
||||
{false, nullptr, &InternalStats::HandleCurSizeActiveMemTable}},
|
||||
{DB::Properties::kCurSizeAllMemTables,
|
||||
{false, nullptr, &InternalStats::HandleCurSizeAllMemTables}},
|
||||
{DB::Properties::kSizeAllMemTables,
|
||||
{false, nullptr, &InternalStats::HandleSizeAllMemTables}},
|
||||
{DB::Properties::kNumEntriesActiveMemTable,
|
||||
{false, nullptr, &InternalStats::HandleNumEntriesActiveMemTable}},
|
||||
{DB::Properties::kNumEntriesImmMemTables,
|
||||
{false, nullptr, &InternalStats::HandleNumEntriesImmMemTables}},
|
||||
{DB::Properties::kNumDeletesActiveMemTable,
|
||||
{false, nullptr, &InternalStats::HandleNumDeletesActiveMemTable}},
|
||||
{DB::Properties::kNumDeletesImmMemTables,
|
||||
{false, nullptr, &InternalStats::HandleNumDeletesImmMemTables}},
|
||||
{DB::Properties::kEstimateNumKeys,
|
||||
{false, nullptr, &InternalStats::HandleEstimateNumKeys}},
|
||||
{DB::Properties::kEstimateTableReadersMem,
|
||||
{true, nullptr, &InternalStats::HandleEstimateTableReadersMem}},
|
||||
{DB::Properties::kIsFileDeletionsEnabled,
|
||||
{false, nullptr, &InternalStats::HandleIsFileDeletionsEnabled}},
|
||||
{DB::Properties::kNumSnapshots,
|
||||
{false, nullptr, &InternalStats::HandleNumSnapshots}},
|
||||
{DB::Properties::kOldestSnapshotTime,
|
||||
{false, nullptr, &InternalStats::HandleOldestSnapshotTime}},
|
||||
{DB::Properties::kNumLiveVersions,
|
||||
{false, nullptr, &InternalStats::HandleNumLiveVersions}},
|
||||
{DB::Properties::kCurrentSuperVersionNumber,
|
||||
{false, nullptr, &InternalStats::HandleCurrentSuperVersionNumber}},
|
||||
{DB::Properties::kEstimateLiveDataSize,
|
||||
{true, nullptr, &InternalStats::HandleEstimateLiveDataSize}},
|
||||
{DB::Properties::kBaseLevel,
|
||||
{false, nullptr, &InternalStats::HandleBaseLevel}},
|
||||
{DB::Properties::kTotalSstFilesSize,
|
||||
{false, nullptr, &InternalStats::HandleTotalSstFilesSize}},
|
||||
{DB::Properties::kEstimatePendingCompactionBytes,
|
||||
{false, nullptr, &InternalStats::HandleEstimatePendingCompactionBytes}},
|
||||
{DB::Properties::kNumRunningFlushes,
|
||||
{false, nullptr, &InternalStats::HandleNumRunningFlushes}},
|
||||
{DB::Properties::kNumRunningCompactions,
|
||||
{false, nullptr, &InternalStats::HandleNumRunningCompactions}},
|
||||
const std::unordered_map<std::string, DBPropertyInfo>
|
||||
InternalStats::ppt_name_to_info = {
|
||||
{DB::Properties::kNumFilesAtLevelPrefix,
|
||||
{false, &InternalStats::HandleNumFilesAtLevel, nullptr, nullptr}},
|
||||
{DB::Properties::kCompressionRatioAtLevelPrefix,
|
||||
{false, &InternalStats::HandleCompressionRatioAtLevelPrefix, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kLevelStats,
|
||||
{false, &InternalStats::HandleLevelStats, nullptr, nullptr}},
|
||||
{DB::Properties::kStats,
|
||||
{false, &InternalStats::HandleStats, nullptr, nullptr}},
|
||||
{DB::Properties::kCFStats,
|
||||
{false, &InternalStats::HandleCFStats, nullptr,
|
||||
&InternalStats::HandleCFMapStats}},
|
||||
{DB::Properties::kDBStats,
|
||||
{false, &InternalStats::HandleDBStats, nullptr, nullptr}},
|
||||
{DB::Properties::kSSTables,
|
||||
{false, &InternalStats::HandleSsTables, nullptr, nullptr}},
|
||||
{DB::Properties::kAggregatedTableProperties,
|
||||
{false, &InternalStats::HandleAggregatedTableProperties, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kAggregatedTablePropertiesAtLevel,
|
||||
{false, &InternalStats::HandleAggregatedTablePropertiesAtLevel,
|
||||
nullptr, nullptr}},
|
||||
{DB::Properties::kNumImmutableMemTable,
|
||||
{false, nullptr, &InternalStats::HandleNumImmutableMemTable, nullptr}},
|
||||
{DB::Properties::kNumImmutableMemTableFlushed,
|
||||
{false, nullptr, &InternalStats::HandleNumImmutableMemTableFlushed,
|
||||
nullptr}},
|
||||
{DB::Properties::kMemTableFlushPending,
|
||||
{false, nullptr, &InternalStats::HandleMemTableFlushPending, nullptr}},
|
||||
{DB::Properties::kCompactionPending,
|
||||
{false, nullptr, &InternalStats::HandleCompactionPending, nullptr}},
|
||||
{DB::Properties::kBackgroundErrors,
|
||||
{false, nullptr, &InternalStats::HandleBackgroundErrors, nullptr}},
|
||||
{DB::Properties::kCurSizeActiveMemTable,
|
||||
{false, nullptr, &InternalStats::HandleCurSizeActiveMemTable,
|
||||
nullptr}},
|
||||
{DB::Properties::kCurSizeAllMemTables,
|
||||
{false, nullptr, &InternalStats::HandleCurSizeAllMemTables, nullptr}},
|
||||
{DB::Properties::kSizeAllMemTables,
|
||||
{false, nullptr, &InternalStats::HandleSizeAllMemTables, nullptr}},
|
||||
{DB::Properties::kNumEntriesActiveMemTable,
|
||||
{false, nullptr, &InternalStats::HandleNumEntriesActiveMemTable,
|
||||
nullptr}},
|
||||
{DB::Properties::kNumEntriesImmMemTables,
|
||||
{false, nullptr, &InternalStats::HandleNumEntriesImmMemTables,
|
||||
nullptr}},
|
||||
{DB::Properties::kNumDeletesActiveMemTable,
|
||||
{false, nullptr, &InternalStats::HandleNumDeletesActiveMemTable,
|
||||
nullptr}},
|
||||
{DB::Properties::kNumDeletesImmMemTables,
|
||||
{false, nullptr, &InternalStats::HandleNumDeletesImmMemTables,
|
||||
nullptr}},
|
||||
{DB::Properties::kEstimateNumKeys,
|
||||
{false, nullptr, &InternalStats::HandleEstimateNumKeys, nullptr}},
|
||||
{DB::Properties::kEstimateTableReadersMem,
|
||||
{true, nullptr, &InternalStats::HandleEstimateTableReadersMem,
|
||||
nullptr}},
|
||||
{DB::Properties::kIsFileDeletionsEnabled,
|
||||
{false, nullptr, &InternalStats::HandleIsFileDeletionsEnabled,
|
||||
nullptr}},
|
||||
{DB::Properties::kNumSnapshots,
|
||||
{false, nullptr, &InternalStats::HandleNumSnapshots, nullptr}},
|
||||
{DB::Properties::kOldestSnapshotTime,
|
||||
{false, nullptr, &InternalStats::HandleOldestSnapshotTime, nullptr}},
|
||||
{DB::Properties::kNumLiveVersions,
|
||||
{false, nullptr, &InternalStats::HandleNumLiveVersions, nullptr}},
|
||||
{DB::Properties::kCurrentSuperVersionNumber,
|
||||
{false, nullptr, &InternalStats::HandleCurrentSuperVersionNumber,
|
||||
nullptr}},
|
||||
{DB::Properties::kEstimateLiveDataSize,
|
||||
{true, nullptr, &InternalStats::HandleEstimateLiveDataSize, nullptr}},
|
||||
{DB::Properties::kMinLogNumberToKeep,
|
||||
{false, nullptr, &InternalStats::HandleMinLogNumberToKeep, nullptr}},
|
||||
{DB::Properties::kBaseLevel,
|
||||
{false, nullptr, &InternalStats::HandleBaseLevel, nullptr}},
|
||||
{DB::Properties::kTotalSstFilesSize,
|
||||
{false, nullptr, &InternalStats::HandleTotalSstFilesSize, nullptr}},
|
||||
{DB::Properties::kEstimatePendingCompactionBytes,
|
||||
{false, nullptr, &InternalStats::HandleEstimatePendingCompactionBytes,
|
||||
nullptr}},
|
||||
{DB::Properties::kNumRunningFlushes,
|
||||
{false, nullptr, &InternalStats::HandleNumRunningFlushes, nullptr}},
|
||||
{DB::Properties::kNumRunningCompactions,
|
||||
{false, nullptr, &InternalStats::HandleNumRunningCompactions,
|
||||
nullptr}},
|
||||
};
|
||||
|
||||
const DBPropertyInfo* GetPropertyInfo(const Slice& property) {
|
||||
@@ -298,6 +405,14 @@ bool InternalStats::GetStringProperty(const DBPropertyInfo& property_info,
|
||||
return (this->*(property_info.handle_string))(value, arg);
|
||||
}
|
||||
|
||||
bool InternalStats::GetMapProperty(const DBPropertyInfo& property_info,
|
||||
const Slice& property,
|
||||
std::map<std::string, double>* value) {
|
||||
assert(value != nullptr);
|
||||
assert(property_info.handle_map != nullptr);
|
||||
return (this->*(property_info.handle_map))(value);
|
||||
}
|
||||
|
||||
bool InternalStats::GetIntProperty(const DBPropertyInfo& property_info,
|
||||
uint64_t* value, DBImpl* db) {
|
||||
assert(value != nullptr);
|
||||
@@ -370,6 +485,11 @@ bool InternalStats::HandleStats(std::string* value, Slice suffix) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InternalStats::HandleCFMapStats(std::map<std::string, double>* cf_stats) {
|
||||
DumpCFMapStats(cf_stats);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InternalStats::HandleCFStats(std::string* value, Slice suffix) {
|
||||
DumpCFStats(value);
|
||||
return true;
|
||||
@@ -590,6 +710,12 @@ bool InternalStats::HandleEstimateLiveDataSize(uint64_t* value, DBImpl* db,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InternalStats::HandleMinLogNumberToKeep(uint64_t* value, DBImpl* db,
|
||||
Version* version) {
|
||||
*value = db->MinLogNumberToKeep();
|
||||
return true;
|
||||
}
|
||||
|
||||
void InternalStats::DumpDBStats(std::string* value) {
|
||||
char buf[1000];
|
||||
// DB-level stats, only available from default column family
|
||||
@@ -716,7 +842,32 @@ void InternalStats::DumpDBStats(std::string* value) {
|
||||
db_stats_snapshot_.write_stall_micros = write_stall_micros;
|
||||
}
|
||||
|
||||
void InternalStats::DumpCFStats(std::string* value) {
|
||||
/**
|
||||
* Dump Compaction Level stats to a map of stat name to value in double.
|
||||
* The level in stat name is represented with a prefix "Lx" where "x"
|
||||
* is the level number. A special level "Sum" represents the sum of a stat
|
||||
* for all levels.
|
||||
*/
|
||||
void InternalStats::DumpCFMapStats(std::map<std::string, double>* cf_stats) {
|
||||
CompactionStats compaction_stats_sum(0);
|
||||
std::map<int, std::map<LevelStatType, double>> levels_stats;
|
||||
DumpCFMapStats(&levels_stats, &compaction_stats_sum);
|
||||
for (auto const& level_ent : levels_stats) {
|
||||
auto level_str =
|
||||
level_ent.first == -1 ? "Sum" : "L" + ToString(level_ent.first);
|
||||
for (auto const& stat_ent : level_ent.second) {
|
||||
auto stat_type = stat_ent.first;
|
||||
auto key_str =
|
||||
level_str + "." +
|
||||
InternalStats::compaction_level_stats.at(stat_type).property_name;
|
||||
(*cf_stats)[key_str] = stat_ent.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int InternalStats::DumpCFMapStats(
|
||||
std::map<int, std::map<LevelStatType, double>>* levels_stats,
|
||||
CompactionStats* compaction_stats_sum) {
|
||||
const VersionStorageInfo* vstorage = cfd_->current()->storage_info();
|
||||
|
||||
int num_levels_to_check =
|
||||
@@ -724,7 +875,7 @@ void InternalStats::DumpCFStats(std::string* value) {
|
||||
? vstorage->num_levels() - 1
|
||||
: 1;
|
||||
|
||||
// Compaction scores are sorted base on its value. Restore them to the
|
||||
// Compaction scores are sorted based on its value. Restore them to the
|
||||
// level order
|
||||
std::vector<double> compaction_score(number_levels_, 0);
|
||||
for (int i = 0; i < num_levels_to_check; ++i) {
|
||||
@@ -741,12 +892,6 @@ void InternalStats::DumpCFStats(std::string* value) {
|
||||
}
|
||||
}
|
||||
|
||||
char buf[1000];
|
||||
// Per-ColumnFamily stats
|
||||
PrintLevelStatsHeader(buf, sizeof(buf), cfd_->GetName());
|
||||
value->append(buf);
|
||||
|
||||
CompactionStats stats_sum(0);
|
||||
int total_files = 0;
|
||||
int total_files_being_compacted = 0;
|
||||
double total_file_size = 0;
|
||||
@@ -755,40 +900,67 @@ void InternalStats::DumpCFStats(std::string* value) {
|
||||
total_files += files;
|
||||
total_files_being_compacted += files_being_compacted[level];
|
||||
if (comp_stats_[level].micros > 0 || files > 0) {
|
||||
stats_sum.Add(comp_stats_[level]);
|
||||
compaction_stats_sum->Add(comp_stats_[level]);
|
||||
total_file_size += vstorage->NumLevelBytes(level);
|
||||
double w_amp =
|
||||
(comp_stats_[level].bytes_read_non_output_levels == 0) ? 0.0
|
||||
: static_cast<double>(comp_stats_[level].bytes_written) /
|
||||
comp_stats_[level].bytes_read_non_output_levels;
|
||||
PrintLevelStats(buf, sizeof(buf), "L" + ToString(level), files,
|
||||
files_being_compacted[level],
|
||||
static_cast<double>(vstorage->NumLevelBytes(level)),
|
||||
compaction_score[level],
|
||||
w_amp, comp_stats_[level]);
|
||||
value->append(buf);
|
||||
(comp_stats_[level].bytes_read_non_output_levels == 0)
|
||||
? 0.0
|
||||
: static_cast<double>(comp_stats_[level].bytes_written) /
|
||||
comp_stats_[level].bytes_read_non_output_levels;
|
||||
std::map<LevelStatType, double> level_stats;
|
||||
PrepareLevelStats(&level_stats, files, files_being_compacted[level],
|
||||
static_cast<double>(vstorage->NumLevelBytes(level)),
|
||||
compaction_score[level], w_amp, comp_stats_[level]);
|
||||
(*levels_stats)[level] = level_stats;
|
||||
}
|
||||
}
|
||||
uint64_t flush_ingest = cf_stats_value_[BYTES_FLUSHED];
|
||||
uint64_t add_file_ingest = cf_stats_value_[BYTES_INGESTED_ADD_FILE];
|
||||
uint64_t curr_ingest = flush_ingest + add_file_ingest;
|
||||
// Cumulative summary
|
||||
double w_amp = compaction_stats_sum->bytes_written /
|
||||
static_cast<double>(curr_ingest + 1);
|
||||
// Stats summary across levels
|
||||
std::map<LevelStatType, double> sum_stats;
|
||||
PrepareLevelStats(&sum_stats, total_files, total_files_being_compacted,
|
||||
total_file_size, 0, w_amp, *compaction_stats_sum);
|
||||
(*levels_stats)[-1] = sum_stats; // -1 is for the Sum level
|
||||
return num_levels_to_check;
|
||||
}
|
||||
|
||||
void InternalStats::DumpCFStats(std::string* value) {
|
||||
char buf[1000];
|
||||
// Per-ColumnFamily stats
|
||||
PrintLevelStatsHeader(buf, sizeof(buf), cfd_->GetName());
|
||||
value->append(buf);
|
||||
|
||||
// Print stats for each level
|
||||
std::map<int, std::map<LevelStatType, double>> levels_stats;
|
||||
CompactionStats compaction_stats_sum(0);
|
||||
int levels = DumpCFMapStats(&levels_stats, &compaction_stats_sum);
|
||||
for (int l = 0; l < levels; ++l) {
|
||||
if (levels_stats.find(l) != levels_stats.end()) {
|
||||
PrintLevelStats(buf, sizeof(buf), "L" + ToString(l), levels_stats[l]);
|
||||
value->append(buf);
|
||||
}
|
||||
}
|
||||
// Print sum of level stats
|
||||
PrintLevelStats(buf, sizeof(buf), "Sum", levels_stats[-1]);
|
||||
value->append(buf);
|
||||
|
||||
uint64_t flush_ingest = cf_stats_value_[BYTES_FLUSHED];
|
||||
uint64_t add_file_ingest = cf_stats_value_[BYTES_INGESTED_ADD_FILE];
|
||||
uint64_t ingest_files_addfile = cf_stats_value_[INGESTED_NUM_FILES_TOTAL];
|
||||
uint64_t ingest_l0_files_addfile =
|
||||
cf_stats_value_[INGESTED_LEVEL0_NUM_FILES_TOTAL];
|
||||
uint64_t ingest_keys_addfile = cf_stats_value_[INGESTED_NUM_KEYS_TOTAL];
|
||||
// Cumulative summary
|
||||
double w_amp = stats_sum.bytes_written / static_cast<double>(curr_ingest + 1);
|
||||
uint64_t total_stall_count =
|
||||
cf_stats_count_[LEVEL0_SLOWDOWN_TOTAL] +
|
||||
cf_stats_count_[LEVEL0_NUM_FILES_TOTAL] +
|
||||
cf_stats_count_[SOFT_PENDING_COMPACTION_BYTES_LIMIT] +
|
||||
cf_stats_count_[HARD_PENDING_COMPACTION_BYTES_LIMIT] +
|
||||
cf_stats_count_[MEMTABLE_COMPACTION] + cf_stats_count_[MEMTABLE_SLOWDOWN];
|
||||
// Stats summary across levels
|
||||
PrintLevelStats(buf, sizeof(buf), "Sum", total_files,
|
||||
total_files_being_compacted, total_file_size, 0, w_amp,
|
||||
stats_sum);
|
||||
value->append(buf);
|
||||
// Interval summary
|
||||
uint64_t interval_flush_ingest =
|
||||
flush_ingest - cf_stats_snapshot_.ingest_bytes_flush;
|
||||
@@ -796,9 +968,10 @@ void InternalStats::DumpCFStats(std::string* value) {
|
||||
add_file_ingest - cf_stats_snapshot_.ingest_bytes_addfile;
|
||||
uint64_t interval_ingest =
|
||||
interval_flush_ingest + interval_add_file_inget + 1;
|
||||
CompactionStats interval_stats(stats_sum);
|
||||
CompactionStats interval_stats(compaction_stats_sum);
|
||||
interval_stats.Subtract(cf_stats_snapshot_.comp_stats);
|
||||
w_amp = interval_stats.bytes_written / static_cast<double>(interval_ingest);
|
||||
double w_amp =
|
||||
interval_stats.bytes_written / static_cast<double>(interval_ingest);
|
||||
PrintLevelStats(buf, sizeof(buf), "Int", 0, 0, 0, 0, w_amp, interval_stats);
|
||||
value->append(buf);
|
||||
|
||||
@@ -909,7 +1082,7 @@ void InternalStats::DumpCFStats(std::string* value) {
|
||||
cf_stats_snapshot_.ingest_files_addfile = ingest_files_addfile;
|
||||
cf_stats_snapshot_.ingest_l0_files_addfile = ingest_l0_files_addfile;
|
||||
cf_stats_snapshot_.ingest_keys_addfile = ingest_keys_addfile;
|
||||
cf_stats_snapshot_.comp_stats = stats_sum;
|
||||
cf_stats_snapshot_.comp_stats = compaction_stats_sum;
|
||||
cf_stats_snapshot_.stall_count = total_stall_count;
|
||||
}
|
||||
|
||||
|
||||
+54
-3
@@ -9,10 +9,11 @@
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "db/version_set.h"
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "db/version_set.h"
|
||||
|
||||
class ColumnFamilyData;
|
||||
|
||||
@@ -41,13 +42,47 @@ struct DBPropertyInfo {
|
||||
// holding db mutex, which is only supported for int properties.
|
||||
bool (InternalStats::*handle_int)(uint64_t* value, DBImpl* db,
|
||||
Version* version);
|
||||
bool (InternalStats::*handle_map)(
|
||||
std::map<std::string, double>* compaction_stats);
|
||||
};
|
||||
|
||||
extern const DBPropertyInfo* GetPropertyInfo(const Slice& property);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
enum class LevelStatType {
|
||||
INVALID = 0,
|
||||
NUM_FILES,
|
||||
COMPACTED_FILES,
|
||||
SIZE_MB,
|
||||
SCORE,
|
||||
READ_GB,
|
||||
RN_GB,
|
||||
RNP1_GB,
|
||||
WRITE_GB,
|
||||
W_NEW_GB,
|
||||
MOVED_GB,
|
||||
WRITE_AMP,
|
||||
READ_MBPS,
|
||||
WRITE_MBPS,
|
||||
COMP_SEC,
|
||||
COMP_COUNT,
|
||||
AVG_SEC,
|
||||
KEY_IN,
|
||||
KEY_DROP,
|
||||
TOTAL // total number of types
|
||||
};
|
||||
|
||||
struct LevelStat {
|
||||
// This what will be L?.property_name in the flat map returned to the user
|
||||
std::string property_name;
|
||||
// This will be what we will print in the header in the cli
|
||||
std::string header_name;
|
||||
};
|
||||
|
||||
class InternalStats {
|
||||
public:
|
||||
static const std::map<LevelStatType, LevelStat> compaction_level_stats;
|
||||
|
||||
enum InternalCFStatsType {
|
||||
LEVEL0_SLOWDOWN_TOTAL,
|
||||
LEVEL0_SLOWDOWN_WITH_COMPACTION,
|
||||
@@ -221,6 +256,10 @@ class InternalStats {
|
||||
bool GetStringProperty(const DBPropertyInfo& property_info,
|
||||
const Slice& property, std::string* value);
|
||||
|
||||
bool GetMapProperty(const DBPropertyInfo& property_info,
|
||||
const Slice& property,
|
||||
std::map<std::string, double>* value);
|
||||
|
||||
bool GetIntProperty(const DBPropertyInfo& property_info, uint64_t* value,
|
||||
DBImpl* db);
|
||||
|
||||
@@ -233,6 +272,10 @@ class InternalStats {
|
||||
|
||||
private:
|
||||
void DumpDBStats(std::string* value);
|
||||
void DumpCFMapStats(std::map<std::string, double>* cf_stats);
|
||||
int DumpCFMapStats(
|
||||
std::map<int, std::map<LevelStatType, double>>* level_stats,
|
||||
CompactionStats* compaction_stats_sum);
|
||||
void DumpCFStats(std::string* value);
|
||||
|
||||
// Per-DB stats
|
||||
@@ -313,6 +356,7 @@ class InternalStats {
|
||||
bool HandleCompressionRatioAtLevelPrefix(std::string* value, Slice suffix);
|
||||
bool HandleLevelStats(std::string* value, Slice suffix);
|
||||
bool HandleStats(std::string* value, Slice suffix);
|
||||
bool HandleCFMapStats(std::map<std::string, double>* compaction_stats);
|
||||
bool HandleCFStats(std::string* value, Slice suffix);
|
||||
bool HandleDBStats(std::string* value, Slice suffix);
|
||||
bool HandleSsTables(std::string* value, Slice suffix);
|
||||
@@ -357,6 +401,7 @@ class InternalStats {
|
||||
Version* version);
|
||||
bool HandleEstimateLiveDataSize(uint64_t* value, DBImpl* db,
|
||||
Version* version);
|
||||
bool HandleMinLogNumberToKeep(uint64_t* value, DBImpl* db, Version* version);
|
||||
|
||||
// Total number of background errors encountered. Every time a flush task
|
||||
// or compaction task fails, this counter is incremented. The failure can
|
||||
@@ -448,6 +493,12 @@ class InternalStats {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GetMapProperty(const DBPropertyInfo& property_info,
|
||||
const Slice& property,
|
||||
std::map<std::string, double>* value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GetIntProperty(const DBPropertyInfo& property_info, uint64_t* value,
|
||||
DBImpl* db) const {
|
||||
return false;
|
||||
|
||||
+62
-20
@@ -17,6 +17,7 @@
|
||||
#include "db/merge_context.h"
|
||||
#include "db/merge_helper.h"
|
||||
#include "db/pinned_iterators_manager.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
@@ -24,9 +25,12 @@
|
||||
#include "rocksdb/slice_transform.h"
|
||||
#include "rocksdb/write_buffer_manager.h"
|
||||
#include "table/internal_iterator.h"
|
||||
#include "table/iterator_wrapper.h"
|
||||
#include "table/merger.h"
|
||||
#include "util/arena.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/memory_usage.h"
|
||||
#include "util/murmurhash.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/perf_context_imp.h"
|
||||
@@ -68,9 +72,10 @@ MemTable::MemTable(const InternalKeyComparator& cmp,
|
||||
table_(ioptions.memtable_factory->CreateMemTableRep(
|
||||
comparator_, &allocator_, ioptions.prefix_extractor,
|
||||
ioptions.info_log)),
|
||||
range_del_table_(ioptions.memtable_factory->CreateMemTableRep(
|
||||
range_del_table_(SkipListFactory().CreateMemTableRep(
|
||||
comparator_, &allocator_, nullptr /* transform */,
|
||||
ioptions.info_log)),
|
||||
is_range_del_table_empty_(true),
|
||||
data_size_(0),
|
||||
num_entries_(0),
|
||||
num_deletes_(0),
|
||||
@@ -86,7 +91,9 @@ MemTable::MemTable(const InternalKeyComparator& cmp,
|
||||
: 0),
|
||||
prefix_extractor_(ioptions.prefix_extractor),
|
||||
flush_state_(FLUSH_NOT_REQUESTED),
|
||||
env_(ioptions.env) {
|
||||
env_(ioptions.env),
|
||||
insert_with_hint_prefix_extractor_(
|
||||
ioptions.memtable_insert_with_hint_prefix_extractor) {
|
||||
UpdateFlushState();
|
||||
// something went wrong if we need to flush before inserting anything
|
||||
assert(!ShouldScheduleFlush());
|
||||
@@ -102,17 +109,21 @@ MemTable::MemTable(const InternalKeyComparator& cmp,
|
||||
MemTable::~MemTable() { assert(refs_ == 0); }
|
||||
|
||||
size_t MemTable::ApproximateMemoryUsage() {
|
||||
size_t arena_usage = arena_.ApproximateMemoryUsage();
|
||||
size_t table_usage = table_->ApproximateMemoryUsage();
|
||||
table_usage += range_del_table_->ApproximateMemoryUsage();
|
||||
// let MAX_USAGE = std::numeric_limits<size_t>::max()
|
||||
// then if arena_usage + total_usage >= MAX_USAGE, return MAX_USAGE.
|
||||
// the following variation is to avoid numeric overflow.
|
||||
if (arena_usage >= std::numeric_limits<size_t>::max() - table_usage) {
|
||||
return std::numeric_limits<size_t>::max();
|
||||
autovector<size_t> usages = {arena_.ApproximateMemoryUsage(),
|
||||
table_->ApproximateMemoryUsage(),
|
||||
range_del_table_->ApproximateMemoryUsage(),
|
||||
rocksdb::ApproximateMemoryUsage(insert_hints_)};
|
||||
size_t total_usage = 0;
|
||||
for (size_t usage : usages) {
|
||||
// If usage + total_usage >= kMaxSizet, return kMaxSizet.
|
||||
// the following variation is to avoid numeric overflow.
|
||||
if (usage >= port::kMaxSizet - total_usage) {
|
||||
return port::kMaxSizet;
|
||||
}
|
||||
total_usage += usage;
|
||||
}
|
||||
// otherwise, return the actual usage
|
||||
return arena_usage + table_usage;
|
||||
return total_usage;
|
||||
}
|
||||
|
||||
bool MemTable::ShouldFlushNow() const {
|
||||
@@ -311,11 +322,13 @@ class MemTableIterator : public InternalIterator {
|
||||
valid_ = iter_->Valid();
|
||||
}
|
||||
virtual void Next() override {
|
||||
PERF_COUNTER_ADD(next_on_memtable_count, 1);
|
||||
assert(Valid());
|
||||
iter_->Next();
|
||||
valid_ = iter_->Valid();
|
||||
}
|
||||
virtual void Prev() override {
|
||||
PERF_COUNTER_ADD(prev_on_memtable_count, 1);
|
||||
assert(Valid());
|
||||
iter_->Prev();
|
||||
valid_ = iter_->Valid();
|
||||
@@ -364,11 +377,12 @@ InternalIterator* MemTable::NewIterator(const ReadOptions& read_options,
|
||||
}
|
||||
|
||||
InternalIterator* MemTable::NewRangeTombstoneIterator(
|
||||
const ReadOptions& read_options, Arena* arena) {
|
||||
assert(arena != nullptr);
|
||||
auto mem = arena->AllocateAligned(sizeof(MemTableIterator));
|
||||
return new (mem) MemTableIterator(*this, read_options, arena,
|
||||
true /* use_range_del_table */);
|
||||
const ReadOptions& read_options) {
|
||||
if (read_options.ignore_range_deletions || is_range_del_table_empty_) {
|
||||
return nullptr;
|
||||
}
|
||||
return new MemTableIterator(*this, read_options, nullptr /* arena */,
|
||||
true /* use_range_del_table */);
|
||||
}
|
||||
|
||||
port::RWMutex* MemTable::GetLock(const Slice& key) {
|
||||
@@ -419,6 +433,7 @@ void MemTable::Add(SequenceNumber s, ValueType type,
|
||||
|
||||
char* p = EncodeVarint32(buf, internal_key_size);
|
||||
memcpy(p, key.data(), key_size);
|
||||
Slice key_slice(p, key_size);
|
||||
p += key_size;
|
||||
uint64_t packed = PackSequenceAndType(s, type);
|
||||
EncodeFixed64(p, packed);
|
||||
@@ -427,7 +442,14 @@ void MemTable::Add(SequenceNumber s, ValueType type,
|
||||
memcpy(p, value.data(), val_size);
|
||||
assert((unsigned)(p + val_size - buf) == (unsigned)encoded_len);
|
||||
if (!allow_concurrent) {
|
||||
table->Insert(handle);
|
||||
// Extract prefix for insert with hint.
|
||||
if (insert_with_hint_prefix_extractor_ != nullptr &&
|
||||
insert_with_hint_prefix_extractor_->InDomain(key_slice)) {
|
||||
Slice prefix = insert_with_hint_prefix_extractor_->Transform(key_slice);
|
||||
table->InsertWithHint(handle, &insert_hints_[prefix]);
|
||||
} else {
|
||||
table->Insert(handle);
|
||||
}
|
||||
|
||||
// this is a bit ugly, but is the way to avoid locked instructions
|
||||
// when incrementing an atomic
|
||||
@@ -485,6 +507,9 @@ void MemTable::Add(SequenceNumber s, ValueType type,
|
||||
!first_seqno_.compare_exchange_weak(cur_earliest_seqno, s)) {
|
||||
}
|
||||
}
|
||||
if (is_range_del_table_empty_ && type == kTypeRangeDeletion) {
|
||||
is_range_del_table_empty_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Callback from MemTable::Get()
|
||||
@@ -500,6 +525,7 @@ struct Saver {
|
||||
const MergeOperator* merge_operator;
|
||||
// the merge operations encountered;
|
||||
MergeContext* merge_context;
|
||||
RangeDelAggregator* range_del_agg;
|
||||
MemTable* mem;
|
||||
Logger* logger;
|
||||
Statistics* statistics;
|
||||
@@ -511,9 +537,10 @@ struct Saver {
|
||||
static bool SaveValue(void* arg, const char* entry) {
|
||||
Saver* s = reinterpret_cast<Saver*>(arg);
|
||||
MergeContext* merge_context = s->merge_context;
|
||||
RangeDelAggregator* range_del_agg = s->range_del_agg;
|
||||
const MergeOperator* merge_operator = s->merge_operator;
|
||||
|
||||
assert(s != nullptr && merge_context != nullptr);
|
||||
assert(s != nullptr && merge_context != nullptr && range_del_agg != nullptr);
|
||||
|
||||
// entry format is:
|
||||
// klength varint32
|
||||
@@ -533,6 +560,10 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
ValueType type;
|
||||
UnPackSequenceAndType(tag, &s->seq, &type);
|
||||
|
||||
if ((type == kTypeValue || type == kTypeDeletion) &&
|
||||
range_del_agg->ShouldDelete(Slice(key_ptr, key_length))) {
|
||||
type = kTypeRangeDeletion;
|
||||
}
|
||||
switch (type) {
|
||||
case kTypeValue: {
|
||||
if (s->inplace_update_support) {
|
||||
@@ -555,7 +586,8 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
return false;
|
||||
}
|
||||
case kTypeDeletion:
|
||||
case kTypeSingleDeletion: {
|
||||
case kTypeSingleDeletion:
|
||||
case kTypeRangeDeletion: {
|
||||
if (*(s->merge_in_progress)) {
|
||||
*(s->status) = MergeHelper::TimedFullMerge(
|
||||
merge_operator, s->key->user_key(), nullptr,
|
||||
@@ -595,7 +627,9 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
}
|
||||
|
||||
bool MemTable::Get(const LookupKey& key, std::string* value, Status* s,
|
||||
MergeContext* merge_context, SequenceNumber* seq) {
|
||||
MergeContext* merge_context,
|
||||
RangeDelAggregator* range_del_agg, SequenceNumber* seq,
|
||||
const ReadOptions& read_opts) {
|
||||
// The sequence number is updated synchronously in version_set.h
|
||||
if (IsEmpty()) {
|
||||
// Avoiding recording stats for speed.
|
||||
@@ -618,6 +652,13 @@ 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;
|
||||
@@ -627,6 +668,7 @@ bool MemTable::Get(const LookupKey& key, std::string* value, Status* s,
|
||||
saver.seq = kMaxSequenceNumber;
|
||||
saver.mem = this;
|
||||
saver.merge_context = merge_context;
|
||||
saver.range_del_agg = range_del_agg;
|
||||
saver.merge_operator = moptions_.merge_operator;
|
||||
saver.logger = moptions_.info_log;
|
||||
saver.inplace_update_support = moptions_.inplace_update_support;
|
||||
|
||||
+16
-5
@@ -13,9 +13,11 @@
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include "db/dbformat.h"
|
||||
#include "db/memtable_allocator.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "db/skiplist.h"
|
||||
#include "db/version_edit.h"
|
||||
#include "rocksdb/db.h"
|
||||
@@ -24,6 +26,7 @@
|
||||
#include "util/cf_options.h"
|
||||
#include "util/concurrent_arena.h"
|
||||
#include "util/dynamic_bloom.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/instrumented_mutex.h"
|
||||
|
||||
namespace rocksdb {
|
||||
@@ -158,8 +161,7 @@ class MemTable {
|
||||
// those allocated in arena.
|
||||
InternalIterator* NewIterator(const ReadOptions& read_options, Arena* arena);
|
||||
|
||||
InternalIterator* NewRangeTombstoneIterator(const ReadOptions& read_options,
|
||||
Arena* arena);
|
||||
InternalIterator* NewRangeTombstoneIterator(const ReadOptions& read_options);
|
||||
|
||||
// Add an entry into memtable that maps key to value at the
|
||||
// specified sequence number and with the specified type.
|
||||
@@ -185,12 +187,14 @@ class MemTable {
|
||||
// On success, *s may be set to OK, NotFound, or MergeInProgress. Any other
|
||||
// status returned indicates a corruption or other unexpected error.
|
||||
bool Get(const LookupKey& key, std::string* value, Status* s,
|
||||
MergeContext* merge_context, SequenceNumber* seq);
|
||||
MergeContext* merge_context, RangeDelAggregator* range_del_agg,
|
||||
SequenceNumber* seq, const ReadOptions& read_opts);
|
||||
|
||||
bool Get(const LookupKey& key, std::string* value, Status* s,
|
||||
MergeContext* merge_context) {
|
||||
MergeContext* merge_context, RangeDelAggregator* range_del_agg,
|
||||
const ReadOptions& read_opts) {
|
||||
SequenceNumber seq;
|
||||
return Get(key, value, s, merge_context, &seq);
|
||||
return Get(key, value, s, merge_context, range_del_agg, &seq, read_opts);
|
||||
}
|
||||
|
||||
// Attempts to update the new_value inplace, else does normal Add
|
||||
@@ -348,6 +352,7 @@ class MemTable {
|
||||
MemTableAllocator allocator_;
|
||||
unique_ptr<MemTableRep> table_;
|
||||
unique_ptr<MemTableRep> range_del_table_;
|
||||
bool is_range_del_table_empty_;
|
||||
|
||||
// Total data size of all data inserted
|
||||
std::atomic<uint64_t> data_size_;
|
||||
@@ -387,6 +392,12 @@ class MemTable {
|
||||
|
||||
Env* env_;
|
||||
|
||||
// Extract sequential insert prefixes.
|
||||
const SliceTransform* insert_with_hint_prefix_extractor_;
|
||||
|
||||
// Insert hints for each prefix.
|
||||
std::unordered_map<Slice, void*, SliceHasher> insert_hints_;
|
||||
|
||||
// Returns a heuristic flush decision
|
||||
bool ShouldFlushNow() const;
|
||||
|
||||
|
||||
+33
-6
@@ -102,27 +102,36 @@ int MemTableList::NumFlushed() const {
|
||||
// Operands stores the list of merge operations to apply, so far.
|
||||
bool MemTableListVersion::Get(const LookupKey& key, std::string* value,
|
||||
Status* s, MergeContext* merge_context,
|
||||
SequenceNumber* seq) {
|
||||
return GetFromList(&memlist_, key, value, s, merge_context, seq);
|
||||
RangeDelAggregator* range_del_agg,
|
||||
SequenceNumber* seq,
|
||||
const ReadOptions& read_opts) {
|
||||
return GetFromList(&memlist_, key, value, s, merge_context, range_del_agg,
|
||||
seq, read_opts);
|
||||
}
|
||||
|
||||
bool MemTableListVersion::GetFromHistory(const LookupKey& key,
|
||||
std::string* value, Status* s,
|
||||
MergeContext* merge_context,
|
||||
SequenceNumber* seq) {
|
||||
return GetFromList(&memlist_history_, key, value, s, merge_context, seq);
|
||||
RangeDelAggregator* range_del_agg,
|
||||
SequenceNumber* seq,
|
||||
const ReadOptions& read_opts) {
|
||||
return GetFromList(&memlist_history_, key, value, s, merge_context,
|
||||
range_del_agg, seq, read_opts);
|
||||
}
|
||||
|
||||
bool MemTableListVersion::GetFromList(std::list<MemTable*>* list,
|
||||
const LookupKey& key, std::string* value,
|
||||
Status* s, MergeContext* merge_context,
|
||||
SequenceNumber* seq) {
|
||||
RangeDelAggregator* range_del_agg,
|
||||
SequenceNumber* seq,
|
||||
const ReadOptions& read_opts) {
|
||||
*seq = kMaxSequenceNumber;
|
||||
|
||||
for (auto& memtable : *list) {
|
||||
SequenceNumber current_seq = kMaxSequenceNumber;
|
||||
|
||||
bool done = memtable->Get(key, value, s, merge_context, ¤t_seq);
|
||||
bool done = memtable->Get(key, value, s, merge_context, range_del_agg,
|
||||
¤t_seq, read_opts);
|
||||
if (*seq == kMaxSequenceNumber) {
|
||||
// Store the most recent sequence number of any operation on this key.
|
||||
// Since we only care about the most recent change, we only need to
|
||||
@@ -135,10 +144,28 @@ bool MemTableListVersion::GetFromList(std::list<MemTable*>* list,
|
||||
assert(*seq != kMaxSequenceNumber);
|
||||
return true;
|
||||
}
|
||||
if (!done && !s->ok() && !s->IsMergeInProgress() && !s->IsNotFound()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Status MemTableListVersion::AddRangeTombstoneIterators(
|
||||
const ReadOptions& read_opts, Arena* arena,
|
||||
RangeDelAggregator* range_del_agg) {
|
||||
assert(range_del_agg != nullptr);
|
||||
for (auto& m : memlist_) {
|
||||
std::unique_ptr<InternalIterator> range_del_iter(
|
||||
m->NewRangeTombstoneIterator(read_opts));
|
||||
Status s = range_del_agg->AddTombstones(std::move(range_del_iter));
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void MemTableListVersion::AddIterators(
|
||||
const ReadOptions& options, std::vector<InternalIterator*>* iterator_list,
|
||||
Arena* arena) {
|
||||
|
||||
+21
-7
@@ -14,6 +14,7 @@
|
||||
#include "db/dbformat.h"
|
||||
#include "db/filename.h"
|
||||
#include "db/memtable.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "db/skiplist.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
@@ -53,12 +54,14 @@ class MemTableListVersion {
|
||||
// will be stored in *seq on success (regardless of whether true/false is
|
||||
// returned). Otherwise, *seq will be set to kMaxSequenceNumber.
|
||||
bool Get(const LookupKey& key, std::string* value, Status* s,
|
||||
MergeContext* merge_context, SequenceNumber* seq);
|
||||
MergeContext* merge_context, RangeDelAggregator* range_del_agg,
|
||||
SequenceNumber* seq, const ReadOptions& read_opts);
|
||||
|
||||
bool Get(const LookupKey& key, std::string* value, Status* s,
|
||||
MergeContext* merge_context) {
|
||||
MergeContext* merge_context, RangeDelAggregator* range_del_agg,
|
||||
const ReadOptions& read_opts) {
|
||||
SequenceNumber seq;
|
||||
return Get(key, value, s, merge_context, &seq);
|
||||
return Get(key, value, s, merge_context, range_del_agg, &seq, read_opts);
|
||||
}
|
||||
|
||||
// Similar to Get(), but searches the Memtable history of memtables that
|
||||
@@ -66,13 +69,21 @@ class MemTableListVersion {
|
||||
// queries (such as Transaction validation) as the history may contain
|
||||
// writes that are also present in the SST files.
|
||||
bool GetFromHistory(const LookupKey& key, std::string* value, Status* s,
|
||||
MergeContext* merge_context, SequenceNumber* seq);
|
||||
MergeContext* merge_context,
|
||||
RangeDelAggregator* range_del_agg, SequenceNumber* seq,
|
||||
const ReadOptions& read_opts);
|
||||
bool GetFromHistory(const LookupKey& key, std::string* value, Status* s,
|
||||
MergeContext* merge_context) {
|
||||
MergeContext* merge_context,
|
||||
RangeDelAggregator* range_del_agg,
|
||||
const ReadOptions& read_opts) {
|
||||
SequenceNumber seq;
|
||||
return GetFromHistory(key, value, s, merge_context, &seq);
|
||||
return GetFromHistory(key, value, s, merge_context, range_del_agg, &seq,
|
||||
read_opts);
|
||||
}
|
||||
|
||||
Status AddRangeTombstoneIterators(const ReadOptions& read_opts, Arena* arena,
|
||||
RangeDelAggregator* range_del_agg);
|
||||
|
||||
void AddIterators(const ReadOptions& options,
|
||||
std::vector<InternalIterator*>* iterator_list,
|
||||
Arena* arena);
|
||||
@@ -102,7 +113,8 @@ class MemTableListVersion {
|
||||
|
||||
bool GetFromList(std::list<MemTable*>* list, const LookupKey& key,
|
||||
std::string* value, Status* s, MergeContext* merge_context,
|
||||
SequenceNumber* seq);
|
||||
RangeDelAggregator* range_del_agg, SequenceNumber* seq,
|
||||
const ReadOptions& read_opts);
|
||||
|
||||
void AddMemTable(MemTable* m);
|
||||
|
||||
@@ -209,6 +221,8 @@ class MemTableList {
|
||||
// PickMemtablesToFlush() is called.
|
||||
void FlushRequested() { flush_requested_ = true; }
|
||||
|
||||
bool HasFlushRequested() { return flush_requested_; }
|
||||
|
||||
// Copying allowed
|
||||
// MemTableList(const MemTableList&);
|
||||
// void operator=(const MemTableList&);
|
||||
|
||||
+50
-33
@@ -8,6 +8,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "db/merge_context.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "db/version_set.h"
|
||||
#include "db/write_controller.h"
|
||||
#include "rocksdb/db.h"
|
||||
@@ -116,10 +117,13 @@ TEST_F(MemTableListTest, GetTest) {
|
||||
std::string value;
|
||||
Status s;
|
||||
MergeContext merge_context;
|
||||
InternalKeyComparator ikey_cmp(options.comparator);
|
||||
RangeDelAggregator range_del_agg(ikey_cmp, {} /* snapshots */);
|
||||
autovector<MemTable*> to_delete;
|
||||
|
||||
LookupKey lkey("key1", seq);
|
||||
bool found = list.current()->Get(lkey, &value, &s, &merge_context);
|
||||
bool found = list.current()->Get(lkey, &value, &s, &merge_context,
|
||||
&range_del_agg, ReadOptions());
|
||||
ASSERT_FALSE(found);
|
||||
|
||||
// Create a MemTable
|
||||
@@ -141,17 +145,20 @@ TEST_F(MemTableListTest, GetTest) {
|
||||
|
||||
// Fetch the newly written keys
|
||||
merge_context.Clear();
|
||||
found = mem->Get(LookupKey("key1", seq), &value, &s, &merge_context);
|
||||
found = mem->Get(LookupKey("key1", seq), &value, &s, &merge_context,
|
||||
&range_del_agg, ReadOptions());
|
||||
ASSERT_TRUE(s.ok() && found);
|
||||
ASSERT_EQ(value, "value1");
|
||||
|
||||
merge_context.Clear();
|
||||
found = mem->Get(LookupKey("key1", 2), &value, &s, &merge_context);
|
||||
found = mem->Get(LookupKey("key1", 2), &value, &s, &merge_context,
|
||||
&range_del_agg, ReadOptions());
|
||||
// MemTable found out that this key is *not* found (at this sequence#)
|
||||
ASSERT_TRUE(found && s.IsNotFound());
|
||||
|
||||
merge_context.Clear();
|
||||
found = mem->Get(LookupKey("key2", seq), &value, &s, &merge_context);
|
||||
found = mem->Get(LookupKey("key2", seq), &value, &s, &merge_context,
|
||||
&range_del_agg, ReadOptions());
|
||||
ASSERT_TRUE(s.ok() && found);
|
||||
ASSERT_EQ(value, "value2.2");
|
||||
|
||||
@@ -177,24 +184,25 @@ TEST_F(MemTableListTest, GetTest) {
|
||||
|
||||
// Fetch keys via MemTableList
|
||||
merge_context.Clear();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key1", seq), &value, &s, &merge_context);
|
||||
found = list.current()->Get(LookupKey("key1", seq), &value, &s,
|
||||
&merge_context, &range_del_agg, ReadOptions());
|
||||
ASSERT_TRUE(found && s.IsNotFound());
|
||||
|
||||
merge_context.Clear();
|
||||
found = list.current()->Get(LookupKey("key1", saved_seq), &value, &s,
|
||||
&merge_context);
|
||||
&merge_context, &range_del_agg, ReadOptions());
|
||||
ASSERT_TRUE(s.ok() && found);
|
||||
ASSERT_EQ("value1", value);
|
||||
|
||||
merge_context.Clear();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key2", seq), &value, &s, &merge_context);
|
||||
found = list.current()->Get(LookupKey("key2", seq), &value, &s,
|
||||
&merge_context, &range_del_agg, ReadOptions());
|
||||
ASSERT_TRUE(s.ok() && found);
|
||||
ASSERT_EQ(value, "value2.3");
|
||||
|
||||
merge_context.Clear();
|
||||
found = list.current()->Get(LookupKey("key2", 1), &value, &s, &merge_context);
|
||||
found = list.current()->Get(LookupKey("key2", 1), &value, &s, &merge_context,
|
||||
&range_del_agg, ReadOptions());
|
||||
ASSERT_FALSE(found);
|
||||
|
||||
ASSERT_EQ(2, list.NumNotFlushed());
|
||||
@@ -216,10 +224,13 @@ TEST_F(MemTableListTest, GetFromHistoryTest) {
|
||||
std::string value;
|
||||
Status s;
|
||||
MergeContext merge_context;
|
||||
InternalKeyComparator ikey_cmp(options.comparator);
|
||||
RangeDelAggregator range_del_agg(ikey_cmp, {} /* snapshots */);
|
||||
autovector<MemTable*> to_delete;
|
||||
|
||||
LookupKey lkey("key1", seq);
|
||||
bool found = list.current()->Get(lkey, &value, &s, &merge_context);
|
||||
bool found = list.current()->Get(lkey, &value, &s, &merge_context,
|
||||
&range_del_agg, ReadOptions());
|
||||
ASSERT_FALSE(found);
|
||||
|
||||
// Create a MemTable
|
||||
@@ -240,12 +251,14 @@ TEST_F(MemTableListTest, GetFromHistoryTest) {
|
||||
|
||||
// Fetch the newly written keys
|
||||
merge_context.Clear();
|
||||
found = mem->Get(LookupKey("key1", seq), &value, &s, &merge_context);
|
||||
found = mem->Get(LookupKey("key1", seq), &value, &s, &merge_context,
|
||||
&range_del_agg, ReadOptions());
|
||||
// MemTable found out that this key is *not* found (at this sequence#)
|
||||
ASSERT_TRUE(found && s.IsNotFound());
|
||||
|
||||
merge_context.Clear();
|
||||
found = mem->Get(LookupKey("key2", seq), &value, &s, &merge_context);
|
||||
found = mem->Get(LookupKey("key2", seq), &value, &s, &merge_context,
|
||||
&range_del_agg, ReadOptions());
|
||||
ASSERT_TRUE(s.ok() && found);
|
||||
ASSERT_EQ(value, "value2.2");
|
||||
|
||||
@@ -255,13 +268,13 @@ TEST_F(MemTableListTest, GetFromHistoryTest) {
|
||||
|
||||
// Fetch keys via MemTableList
|
||||
merge_context.Clear();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key1", seq), &value, &s, &merge_context);
|
||||
found = list.current()->Get(LookupKey("key1", seq), &value, &s,
|
||||
&merge_context, &range_del_agg, ReadOptions());
|
||||
ASSERT_TRUE(found && s.IsNotFound());
|
||||
|
||||
merge_context.Clear();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key2", seq), &value, &s, &merge_context);
|
||||
found = list.current()->Get(LookupKey("key2", seq), &value, &s,
|
||||
&merge_context, &range_del_agg, ReadOptions());
|
||||
ASSERT_TRUE(s.ok() && found);
|
||||
ASSERT_EQ("value2.2", value);
|
||||
|
||||
@@ -280,24 +293,26 @@ TEST_F(MemTableListTest, GetFromHistoryTest) {
|
||||
|
||||
// Verify keys are no longer in MemTableList
|
||||
merge_context.Clear();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key1", seq), &value, &s, &merge_context);
|
||||
found = list.current()->Get(LookupKey("key1", seq), &value, &s,
|
||||
&merge_context, &range_del_agg, ReadOptions());
|
||||
ASSERT_FALSE(found);
|
||||
|
||||
merge_context.Clear();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key2", seq), &value, &s, &merge_context);
|
||||
found = list.current()->Get(LookupKey("key2", seq), &value, &s,
|
||||
&merge_context, &range_del_agg, ReadOptions());
|
||||
ASSERT_FALSE(found);
|
||||
|
||||
// Verify keys are present in history
|
||||
merge_context.Clear();
|
||||
found = list.current()->GetFromHistory(LookupKey("key1", seq), &value, &s,
|
||||
&merge_context);
|
||||
&merge_context, &range_del_agg,
|
||||
ReadOptions());
|
||||
ASSERT_TRUE(found && s.IsNotFound());
|
||||
|
||||
merge_context.Clear();
|
||||
found = list.current()->GetFromHistory(LookupKey("key2", seq), &value, &s,
|
||||
&merge_context);
|
||||
&merge_context, &range_del_agg,
|
||||
ReadOptions());
|
||||
ASSERT_TRUE(found);
|
||||
ASSERT_EQ("value2.2", value);
|
||||
|
||||
@@ -338,36 +353,38 @@ TEST_F(MemTableListTest, GetFromHistoryTest) {
|
||||
|
||||
// Verify keys are no longer in MemTableList
|
||||
merge_context.Clear();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key1", seq), &value, &s, &merge_context);
|
||||
found = list.current()->Get(LookupKey("key1", seq), &value, &s,
|
||||
&merge_context, &range_del_agg, ReadOptions());
|
||||
ASSERT_FALSE(found);
|
||||
|
||||
merge_context.Clear();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key2", seq), &value, &s, &merge_context);
|
||||
found = list.current()->Get(LookupKey("key2", seq), &value, &s,
|
||||
&merge_context, &range_del_agg, ReadOptions());
|
||||
ASSERT_FALSE(found);
|
||||
|
||||
merge_context.Clear();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key3", seq), &value, &s, &merge_context);
|
||||
found = list.current()->Get(LookupKey("key3", seq), &value, &s,
|
||||
&merge_context, &range_del_agg, ReadOptions());
|
||||
ASSERT_FALSE(found);
|
||||
|
||||
// Verify that the second memtable's keys are in the history
|
||||
merge_context.Clear();
|
||||
found = list.current()->GetFromHistory(LookupKey("key1", seq), &value, &s,
|
||||
&merge_context);
|
||||
&merge_context, &range_del_agg,
|
||||
ReadOptions());
|
||||
ASSERT_TRUE(found && s.IsNotFound());
|
||||
|
||||
merge_context.Clear();
|
||||
found = list.current()->GetFromHistory(LookupKey("key3", seq), &value, &s,
|
||||
&merge_context);
|
||||
&merge_context, &range_del_agg,
|
||||
ReadOptions());
|
||||
ASSERT_TRUE(found);
|
||||
ASSERT_EQ("value3", value);
|
||||
|
||||
// Verify that key2 from the first memtable is no longer in the history
|
||||
merge_context.Clear();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key2", seq), &value, &s, &merge_context);
|
||||
found = list.current()->Get(LookupKey("key2", seq), &value, &s,
|
||||
&merge_context, &range_del_agg, ReadOptions());
|
||||
ASSERT_FALSE(found);
|
||||
|
||||
// Cleanup
|
||||
|
||||
+49
-12
@@ -83,6 +83,7 @@ Status MergeHelper::MergeUntil(InternalIterator* iter,
|
||||
assert(HasOperator());
|
||||
keys_.clear();
|
||||
merge_context_.Clear();
|
||||
has_compaction_filter_skip_until_ = false;
|
||||
assert(user_merge_operator_);
|
||||
bool first_key = true;
|
||||
|
||||
@@ -145,7 +146,8 @@ Status MergeHelper::MergeUntil(InternalIterator* iter,
|
||||
|
||||
// TODO(noetzli) If the merge operator returns false, we are currently
|
||||
// (almost) silently dropping the put/delete. That's probably not what we
|
||||
// want.
|
||||
// want. Also if we're in compaction and it's a put, it would be nice to
|
||||
// run compaction filter on it.
|
||||
const Slice val = iter->value();
|
||||
const Slice* val_ptr = (kTypeValue == ikey.type) ? &val : nullptr;
|
||||
std::string merge_result;
|
||||
@@ -185,10 +187,17 @@ Status MergeHelper::MergeUntil(InternalIterator* iter,
|
||||
// 1) it's included in one of the snapshots. in that case we *must* write
|
||||
// it out, no matter what compaction filter says
|
||||
// 2) it's not filtered by a compaction filter
|
||||
if ((ikey.sequence <= latest_snapshot_ ||
|
||||
!FilterMerge(orig_ikey.user_key, value_slice)) &&
|
||||
(range_del_agg == nullptr ||
|
||||
!range_del_agg->ShouldDelete(iter->key()))) {
|
||||
CompactionFilter::Decision filter =
|
||||
ikey.sequence <= latest_snapshot_
|
||||
? CompactionFilter::Decision::kKeep
|
||||
: FilterMerge(orig_ikey.user_key, value_slice);
|
||||
if (range_del_agg != nullptr &&
|
||||
range_del_agg->ShouldDelete(iter->key()) &&
|
||||
filter != CompactionFilter::Decision::kRemoveAndSkipUntil) {
|
||||
filter = CompactionFilter::Decision::kRemove;
|
||||
}
|
||||
if (filter == CompactionFilter::Decision::kKeep ||
|
||||
filter == CompactionFilter::Decision::kChangeValue) {
|
||||
if (original_key_is_iter) {
|
||||
// this is just an optimization that saves us one memcpy
|
||||
keys_.push_front(std::move(original_key));
|
||||
@@ -200,8 +209,21 @@ Status MergeHelper::MergeUntil(InternalIterator* iter,
|
||||
// original_key before
|
||||
ParseInternalKey(keys_.back(), &orig_ikey);
|
||||
}
|
||||
merge_context_.PushOperand(value_slice,
|
||||
iter->IsValuePinned() /* operand_pinned */);
|
||||
if (filter == CompactionFilter::Decision::kKeep) {
|
||||
merge_context_.PushOperand(
|
||||
value_slice, iter->IsValuePinned() /* operand_pinned */);
|
||||
} else { // kChangeValue
|
||||
// Compaction filter asked us to change the operand from value_slice
|
||||
// to compaction_filter_value_.
|
||||
merge_context_.PushOperand(compaction_filter_value_, false);
|
||||
}
|
||||
} else if (filter == CompactionFilter::Decision::kRemoveAndSkipUntil) {
|
||||
// Compaction filter asked us to remove this key altogether
|
||||
// (not just this operand), along with some keys following it.
|
||||
keys_.clear();
|
||||
merge_context_.Clear();
|
||||
has_compaction_filter_skip_until_ = true;
|
||||
return Status::OK();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -305,17 +327,32 @@ void MergeOutputIterator::Next() {
|
||||
++it_values_;
|
||||
}
|
||||
|
||||
bool MergeHelper::FilterMerge(const Slice& user_key, const Slice& value_slice) {
|
||||
CompactionFilter::Decision MergeHelper::FilterMerge(const Slice& user_key,
|
||||
const Slice& value_slice) {
|
||||
if (compaction_filter_ == nullptr) {
|
||||
return false;
|
||||
return CompactionFilter::Decision::kKeep;
|
||||
}
|
||||
if (stats_ != nullptr) {
|
||||
filter_timer_.Start();
|
||||
}
|
||||
bool to_delete =
|
||||
compaction_filter_->FilterMergeOperand(level_, user_key, value_slice);
|
||||
compaction_filter_value_.clear();
|
||||
compaction_filter_skip_until_.Clear();
|
||||
auto ret = compaction_filter_->FilterV2(
|
||||
level_, user_key, CompactionFilter::ValueType::kMergeOperand, value_slice,
|
||||
&compaction_filter_value_, compaction_filter_skip_until_.rep());
|
||||
if (ret == CompactionFilter::Decision::kRemoveAndSkipUntil) {
|
||||
if (user_comparator_->Compare(*compaction_filter_skip_until_.rep(),
|
||||
user_key) <= 0) {
|
||||
// Invalid skip_until returned from compaction filter.
|
||||
// Keep the key as per FilterV2 documentation.
|
||||
ret = CompactionFilter::Decision::kKeep;
|
||||
} else {
|
||||
compaction_filter_skip_until_.ConvertFromUserKey(kMaxSequenceNumber,
|
||||
kValueTypeForSeek);
|
||||
}
|
||||
}
|
||||
total_filter_time_ += filter_timer_.ElapsedNanosSafe();
|
||||
return to_delete;
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
+24
-2
@@ -69,6 +69,7 @@ class MergeHelper {
|
||||
// - a Put/Delete,
|
||||
// - a different user key,
|
||||
// - a specific sequence number (snapshot boundary),
|
||||
// - REMOVE_AND_SKIP_UNTIL returned from compaction filter,
|
||||
// or - the end of iteration
|
||||
// iter: (IN) points to the first merge type entry
|
||||
// (OUT) points to the first entry not included in the merge process
|
||||
@@ -92,8 +93,11 @@ class MergeHelper {
|
||||
const bool at_bottom = false);
|
||||
|
||||
// Filters a merge operand using the compaction filter specified
|
||||
// in the constructor. Returns true if the operand should be filtered out.
|
||||
bool FilterMerge(const Slice& user_key, const Slice& value_slice);
|
||||
// in the constructor. Returns the decision that the filter made.
|
||||
// Uses compaction_filter_value_ and compaction_filter_skip_until_ for the
|
||||
// optional outputs of compaction filter.
|
||||
CompactionFilter::Decision FilterMerge(const Slice& user_key,
|
||||
const Slice& value_slice);
|
||||
|
||||
// Query the merge result
|
||||
// These are valid until the next MergeUntil call
|
||||
@@ -127,6 +131,20 @@ class MergeHelper {
|
||||
uint64_t TotalFilterTime() const { return total_filter_time_; }
|
||||
bool HasOperator() const { return user_merge_operator_ != nullptr; }
|
||||
|
||||
// If compaction filter returned REMOVE_AND_SKIP_UNTIL, this method will
|
||||
// return true and fill *until with the key to which we should skip.
|
||||
// If true, keys() and values() are empty.
|
||||
bool FilteredUntil(Slice* skip_until) const {
|
||||
if (!has_compaction_filter_skip_until_) {
|
||||
return false;
|
||||
}
|
||||
assert(compaction_filter_ != nullptr);
|
||||
assert(skip_until != nullptr);
|
||||
assert(compaction_filter_skip_until_.Valid());
|
||||
*skip_until = compaction_filter_skip_until_.Encode();
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
Env* env_;
|
||||
const Comparator* user_comparator_;
|
||||
@@ -149,6 +167,10 @@ class MergeHelper {
|
||||
StopWatchNano filter_timer_;
|
||||
uint64_t total_filter_time_;
|
||||
Statistics* stats_;
|
||||
|
||||
bool has_compaction_filter_skip_until_ = false;
|
||||
std::string compaction_filter_value_;
|
||||
InternalKey compaction_filter_skip_until_;
|
||||
};
|
||||
|
||||
// MergeOutputIterator can be used to iterate over the result of a merge.
|
||||
|
||||
@@ -142,6 +142,7 @@ class PlainTableDBTest : public testing::Test,
|
||||
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(8));
|
||||
options.allow_mmap_reads = mmap_mode_;
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
return options;
|
||||
}
|
||||
|
||||
@@ -386,10 +387,6 @@ TEST_P(PlainTableDBTest, Flush) {
|
||||
for (int total_order = 0; total_order <= 1; total_order++) {
|
||||
for (int store_index_in_file = 0; store_index_in_file <= 1;
|
||||
++store_index_in_file) {
|
||||
if (!bloom_bits && store_index_in_file) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
// Set only one bucket to force bucket conflict.
|
||||
|
||||
@@ -222,6 +222,7 @@ class PrefixTest : public testing::Test {
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
bbto.whole_key_filtering = false;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
|
||||
Status s = DB::Open(options, kDbName, &db);
|
||||
EXPECT_OK(s);
|
||||
|
||||
+146
-95
@@ -12,59 +12,72 @@ namespace rocksdb {
|
||||
RangeDelAggregator::RangeDelAggregator(
|
||||
const InternalKeyComparator& icmp,
|
||||
const std::vector<SequenceNumber>& snapshots)
|
||||
: icmp_(icmp) {
|
||||
pinned_iters_mgr_.StartPinning();
|
||||
for (auto snapshot : snapshots) {
|
||||
stripe_map_.emplace(
|
||||
snapshot,
|
||||
TombstoneMap(stl_wrappers::LessOfComparator(icmp_.user_comparator())));
|
||||
}
|
||||
// Data newer than any snapshot falls in this catch-all stripe
|
||||
stripe_map_.emplace(kMaxSequenceNumber, TombstoneMap());
|
||||
: upper_bound_(kMaxSequenceNumber), icmp_(icmp) {
|
||||
InitRep(snapshots);
|
||||
}
|
||||
|
||||
bool RangeDelAggregator::ShouldDelete(const Slice& internal_key,
|
||||
bool for_compaction /* = false */) {
|
||||
RangeDelAggregator::RangeDelAggregator(const InternalKeyComparator& icmp,
|
||||
SequenceNumber snapshot)
|
||||
: upper_bound_(snapshot), icmp_(icmp) {}
|
||||
|
||||
void RangeDelAggregator::InitRep(const std::vector<SequenceNumber>& snapshots) {
|
||||
assert(rep_ == nullptr);
|
||||
rep_.reset(new Rep());
|
||||
for (auto snapshot : snapshots) {
|
||||
rep_->stripe_map_.emplace(
|
||||
snapshot, TombstoneMap(stl_wrappers::LessOfComparator(&icmp_)));
|
||||
}
|
||||
// Data newer than any snapshot falls in this catch-all stripe
|
||||
rep_->stripe_map_.emplace(
|
||||
kMaxSequenceNumber, TombstoneMap(stl_wrappers::LessOfComparator(&icmp_)));
|
||||
rep_->pinned_iters_mgr_.StartPinning();
|
||||
}
|
||||
|
||||
bool RangeDelAggregator::ShouldDelete(const Slice& internal_key) {
|
||||
if (rep_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
ParsedInternalKey parsed;
|
||||
if (!ParseInternalKey(internal_key, &parsed)) {
|
||||
assert(false);
|
||||
}
|
||||
assert(IsValueType(parsed.type));
|
||||
return ShouldDelete(parsed);
|
||||
}
|
||||
|
||||
// Starting point is the snapshot stripe in which the key lives, then need to
|
||||
// search all earlier stripes too, unless it's for compaction.
|
||||
for (auto stripe_map_iter = GetStripeMapIter(parsed.sequence);
|
||||
stripe_map_iter != stripe_map_.end(); ++stripe_map_iter) {
|
||||
const auto& tombstone_map = stripe_map_iter->second;
|
||||
for (const auto& start_key_and_tombstone : tombstone_map) {
|
||||
const auto& tombstone = start_key_and_tombstone.second;
|
||||
if (icmp_.user_comparator()->Compare(parsed.user_key,
|
||||
tombstone.start_key_) < 0) {
|
||||
break;
|
||||
}
|
||||
if (parsed.sequence < tombstone.seq_ &&
|
||||
icmp_.user_comparator()->Compare(parsed.user_key,
|
||||
tombstone.end_key_) <= 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (for_compaction) {
|
||||
bool RangeDelAggregator::ShouldDelete(const ParsedInternalKey& parsed) {
|
||||
assert(IsValueType(parsed.type));
|
||||
if (rep_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const auto& tombstone_map = GetTombstoneMap(parsed.sequence);
|
||||
for (const auto& start_key_and_tombstone : tombstone_map) {
|
||||
const auto& tombstone = start_key_and_tombstone.second;
|
||||
if (icmp_.user_comparator()->Compare(parsed.user_key,
|
||||
tombstone.start_key_) < 0) {
|
||||
break;
|
||||
}
|
||||
if (parsed.sequence < tombstone.seq_ &&
|
||||
icmp_.user_comparator()->Compare(parsed.user_key, tombstone.end_key_) <
|
||||
0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RangeDelAggregator::ShouldAddTombstones(
|
||||
bool bottommost_level /* = false */) {
|
||||
auto stripe_map_iter = stripe_map_.begin();
|
||||
assert(stripe_map_iter != stripe_map_.end());
|
||||
if (rep_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto stripe_map_iter = rep_->stripe_map_.begin();
|
||||
assert(stripe_map_iter != rep_->stripe_map_.end());
|
||||
if (bottommost_level) {
|
||||
// For the bottommost level, keys covered by tombstones in the first
|
||||
// (oldest) stripe have been compacted away, so the tombstones are obsolete.
|
||||
++stripe_map_iter;
|
||||
}
|
||||
while (stripe_map_iter != stripe_map_.end()) {
|
||||
while (stripe_map_iter != rep_->stripe_map_.end()) {
|
||||
if (!stripe_map_iter->second.empty()) {
|
||||
return true;
|
||||
}
|
||||
@@ -73,46 +86,50 @@ bool RangeDelAggregator::ShouldAddTombstones(
|
||||
return false;
|
||||
}
|
||||
|
||||
Status RangeDelAggregator::AddTombstones(ScopedArenaIterator input) {
|
||||
return AddTombstones(input.release(), true /* arena */);
|
||||
}
|
||||
|
||||
Status RangeDelAggregator::AddTombstones(
|
||||
std::unique_ptr<InternalIterator> input) {
|
||||
return AddTombstones(input.release(), false /* arena */);
|
||||
}
|
||||
|
||||
Status RangeDelAggregator::AddTombstones(InternalIterator* input, bool arena) {
|
||||
pinned_iters_mgr_.PinIterator(input, arena);
|
||||
if (input == nullptr) {
|
||||
return Status::OK();
|
||||
}
|
||||
input->SeekToFirst();
|
||||
bool first_iter = true;
|
||||
while (input->Valid()) {
|
||||
if (first_iter) {
|
||||
if (rep_ == nullptr) {
|
||||
InitRep({upper_bound_});
|
||||
}
|
||||
first_iter = false;
|
||||
}
|
||||
ParsedInternalKey parsed_key;
|
||||
if (!ParseInternalKey(input->key(), &parsed_key)) {
|
||||
return Status::Corruption("Unable to parse range tombstone InternalKey");
|
||||
}
|
||||
RangeTombstone tombstone(parsed_key, input->value());
|
||||
auto& tombstone_map = GetStripeMapIter(tombstone.seq_)->second;
|
||||
tombstone_map.emplace(tombstone.start_key_.ToString(),
|
||||
std::move(tombstone));
|
||||
auto& tombstone_map = GetTombstoneMap(tombstone.seq_);
|
||||
tombstone_map.emplace(input->key(), std::move(tombstone));
|
||||
input->Next();
|
||||
}
|
||||
if (!first_iter) {
|
||||
rep_->pinned_iters_mgr_.PinIterator(input.release(), false /* arena */);
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
RangeDelAggregator::StripeMap::iterator RangeDelAggregator::GetStripeMapIter(
|
||||
RangeDelAggregator::TombstoneMap& RangeDelAggregator::GetTombstoneMap(
|
||||
SequenceNumber seq) {
|
||||
assert(rep_ != nullptr);
|
||||
// The stripe includes seqnum for the snapshot above and excludes seqnum for
|
||||
// the snapshot below.
|
||||
StripeMap::iterator iter;
|
||||
if (seq > 0) {
|
||||
// upper_bound() checks strict inequality so need to subtract one
|
||||
iter = stripe_map_.upper_bound(seq - 1);
|
||||
iter = rep_->stripe_map_.upper_bound(seq - 1);
|
||||
} else {
|
||||
iter = stripe_map_.begin();
|
||||
iter = rep_->stripe_map_.begin();
|
||||
}
|
||||
// catch-all stripe justifies this assertion in either of above cases
|
||||
assert(iter != stripe_map_.end());
|
||||
return iter;
|
||||
assert(iter != rep_->stripe_map_.end());
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
// TODO(andrewkr): We should implement an iterator over range tombstones in our
|
||||
@@ -120,14 +137,21 @@ RangeDelAggregator::StripeMap::iterator RangeDelAggregator::GetStripeMapIter(
|
||||
// tombstones are known to be available, without the code duplication we have
|
||||
// in ShouldAddTombstones(). It'll also allow us to move the table-modifying
|
||||
// code into more coherent places: CompactionJob and BuildTable().
|
||||
void RangeDelAggregator::AddToBuilder(TableBuilder* builder,
|
||||
bool extend_before_min_key,
|
||||
const Slice* next_table_min_key,
|
||||
FileMetaData* meta,
|
||||
bool bottommost_level /* = false */) {
|
||||
auto stripe_map_iter = stripe_map_.begin();
|
||||
assert(stripe_map_iter != stripe_map_.end());
|
||||
void RangeDelAggregator::AddToBuilder(
|
||||
TableBuilder* builder, const Slice* lower_bound, const Slice* upper_bound,
|
||||
FileMetaData* meta,
|
||||
CompactionIterationStats* range_del_out_stats /* = nullptr */,
|
||||
bool bottommost_level /* = false */) {
|
||||
if (rep_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
auto stripe_map_iter = rep_->stripe_map_.begin();
|
||||
assert(stripe_map_iter != rep_->stripe_map_.end());
|
||||
if (bottommost_level) {
|
||||
range_del_out_stats->num_range_del_drop_obsolete +=
|
||||
static_cast<int64_t>(stripe_map_iter->second.size());
|
||||
range_del_out_stats->num_record_drop_obsolete +=
|
||||
static_cast<int64_t>(stripe_map_iter->second.size());
|
||||
// For the bottommost level, keys covered by tombstones in the first
|
||||
// (oldest) stripe have been compacted away, so the tombstones are obsolete.
|
||||
++stripe_map_iter;
|
||||
@@ -136,23 +160,23 @@ void RangeDelAggregator::AddToBuilder(TableBuilder* builder,
|
||||
// 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 != stripe_map_.end()) {
|
||||
while (stripe_map_iter != rep_->stripe_map_.end()) {
|
||||
for (const auto& start_key_and_tombstone : stripe_map_iter->second) {
|
||||
const auto& tombstone = start_key_and_tombstone.second;
|
||||
if (next_table_min_key != nullptr &&
|
||||
icmp_.user_comparator()->Compare(*next_table_min_key,
|
||||
tombstone.start_key_) < 0) {
|
||||
// Tombstones starting after next_table_min_key only need to be included
|
||||
// in the next table.
|
||||
if (upper_bound != nullptr &&
|
||||
icmp_.user_comparator()->Compare(*upper_bound,
|
||||
tombstone.start_key_) <= 0) {
|
||||
// Tombstones starting at upper_bound or later only need to be included
|
||||
// in the next table. Break because subsequent tombstones will start
|
||||
// even later.
|
||||
break;
|
||||
}
|
||||
if (!extend_before_min_key && meta->smallest.size() != 0 &&
|
||||
if (lower_bound != nullptr &&
|
||||
icmp_.user_comparator()->Compare(tombstone.end_key_,
|
||||
meta->smallest.user_key()) < 0) {
|
||||
// Tombstones ending before this table's smallest key can conditionally
|
||||
// be excluded, e.g., when this table is a non-first compaction output,
|
||||
// we know such tombstones are included in the previous table. In that
|
||||
// case extend_before_min_key would be false.
|
||||
*lower_bound) <= 0) {
|
||||
// Tombstones ending before or at lower_bound only need to be included
|
||||
// in the prev table. Continue because subsequent tombstones may still
|
||||
// overlap [lower_bound, upper_bound).
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -160,35 +184,49 @@ void RangeDelAggregator::AddToBuilder(TableBuilder* builder,
|
||||
builder->Add(ikey_and_end_key.first.Encode(), ikey_and_end_key.second);
|
||||
if (!first_added) {
|
||||
first_added = true;
|
||||
if (extend_before_min_key &&
|
||||
(meta->smallest.size() == 0 ||
|
||||
icmp_.Compare(ikey_and_end_key.first, meta->smallest) < 0)) {
|
||||
meta->smallest = 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) {
|
||||
// Pretend the smallest key has the same user key as lower_bound
|
||||
// (the max key in the previous table or subcompaction) in order for
|
||||
// files to appear key-space partitioned.
|
||||
//
|
||||
// Choose lowest seqnum so this file's smallest internal key comes
|
||||
// after the previous file's/subcompaction's largest. The fake seqnum
|
||||
// is OK because the read path's file-picking code only considers user
|
||||
// key.
|
||||
smallest_candidate = InternalKey(*lower_bound, 0, kTypeRangeDeletion);
|
||||
}
|
||||
if (meta->smallest.size() == 0 ||
|
||||
icmp_.Compare(smallest_candidate, meta->smallest) < 0) {
|
||||
meta->smallest = std::move(smallest_candidate);
|
||||
}
|
||||
}
|
||||
auto end_ikey = tombstone.SerializeEndKey();
|
||||
InternalKey largest_candidate = tombstone.SerializeEndKey();
|
||||
if (upper_bound != nullptr &&
|
||||
icmp_.user_comparator()->Compare(*upper_bound,
|
||||
largest_candidate.user_key()) <= 0) {
|
||||
// Pretend the largest key has the same user key as upper_bound (the
|
||||
// min key in the following table or subcompaction) in order for files
|
||||
// to appear key-space partitioned.
|
||||
//
|
||||
// Choose highest seqnum so this file's largest internal key comes
|
||||
// before the next file's/subcompaction's smallest. The fake seqnum is
|
||||
// OK because the read path's file-picking code only considers the user
|
||||
// key portion.
|
||||
//
|
||||
// Note Seek() also creates InternalKey with (user_key,
|
||||
// kMaxSequenceNumber), but with kTypeDeletion (0x7) instead of
|
||||
// kTypeRangeDeletion (0xF), so the range tombstone comes before the
|
||||
// Seek() key in InternalKey's ordering. So Seek() will look in the
|
||||
// next file for the user key.
|
||||
largest_candidate = InternalKey(*upper_bound, kMaxSequenceNumber,
|
||||
kTypeRangeDeletion);
|
||||
}
|
||||
if (meta->largest.size() == 0 ||
|
||||
icmp_.Compare(meta->largest, end_ikey) < 0) {
|
||||
if (next_table_min_key != nullptr &&
|
||||
icmp_.Compare(*next_table_min_key, end_ikey.Encode()) < 0) {
|
||||
// Pretend the largest key has the same user key as the min key in the
|
||||
// following table in order for files to appear key-space partitioned.
|
||||
// Choose highest seqnum so this file's largest comes before the next
|
||||
// file's smallest. The fake seqnum is OK because the read path's
|
||||
// file-picking code only considers the user key portion.
|
||||
//
|
||||
// Note Seek() also creates InternalKey with (user_key,
|
||||
// kMaxSequenceNumber), but with kTypeDeletion (0x7) instead of
|
||||
// kTypeRangeDeletion (0xF), so the range tombstone comes before the
|
||||
// Seek() key in InternalKey's ordering. So Seek() will look in the
|
||||
// next file for the user key.
|
||||
ParsedInternalKey parsed;
|
||||
ParseInternalKey(*next_table_min_key, &parsed);
|
||||
meta->largest = InternalKey(parsed.user_key, kMaxSequenceNumber,
|
||||
kTypeRangeDeletion);
|
||||
} else {
|
||||
meta->largest = std::move(end_ikey);
|
||||
}
|
||||
icmp_.Compare(meta->largest, largest_candidate) < 0) {
|
||||
meta->largest = std::move(largest_candidate);
|
||||
}
|
||||
meta->smallest_seqno = std::min(meta->smallest_seqno, tombstone.seq_);
|
||||
meta->largest_seqno = std::max(meta->largest_seqno, tombstone.seq_);
|
||||
@@ -197,4 +235,17 @@ void RangeDelAggregator::AddToBuilder(TableBuilder* builder,
|
||||
}
|
||||
}
|
||||
|
||||
bool RangeDelAggregator::IsEmpty() {
|
||||
if (rep_ == nullptr) {
|
||||
return true;
|
||||
}
|
||||
for (auto stripe_map_iter = rep_->stripe_map_.begin();
|
||||
stripe_map_iter != rep_->stripe_map_.end(); ++stripe_map_iter) {
|
||||
if (!stripe_map_iter->second.empty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
+73
-13
@@ -9,6 +9,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "db/compaction_iteration_stats.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/pinned_iterators_manager.h"
|
||||
#include "db/version_edit.h"
|
||||
@@ -20,34 +21,93 @@
|
||||
#include "util/kv_map.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// A RangeDelAggregator aggregates range deletion tombstones as they are
|
||||
// encountered in memtables/SST files. It provides methods that check whether a
|
||||
// key is covered by range tombstones or write the relevant tombstones to a new
|
||||
// SST file.
|
||||
class RangeDelAggregator {
|
||||
public:
|
||||
// @param snapshots These are used to organize the tombstones into snapshot
|
||||
// stripes, which is the seqnum range between consecutive snapshots,
|
||||
// including the higher snapshot and excluding the lower one. Currently,
|
||||
// this is used by ShouldDelete() to prevent deletion of keys that are
|
||||
// covered by range tombstones in other snapshot stripes. In case of writes
|
||||
// (flush/compaction), all DB snapshots are provided such that no keys are
|
||||
// removed that are uncovered according to any DB snapshot. In case of read
|
||||
// (get/iterator), only the user snapshot is provided such that the seqnum
|
||||
// space is divided into two stripes, where only tombstones in the older
|
||||
// stripe are considered by ShouldDelete().
|
||||
// Note this overload does not lazily initialize Rep.
|
||||
RangeDelAggregator(const InternalKeyComparator& icmp,
|
||||
const std::vector<SequenceNumber>& snapshots);
|
||||
|
||||
bool ShouldDelete(const Slice& internal_key, bool for_compaction = false);
|
||||
// @param upper_bound Similar to snapshots above, except with a single
|
||||
// snapshot, which allows us to store the snapshot on the stack and defer
|
||||
// initialization of heap-allocating members (in Rep) until the first range
|
||||
// deletion is encountered.
|
||||
RangeDelAggregator(const InternalKeyComparator& icmp,
|
||||
SequenceNumber upper_bound);
|
||||
|
||||
// Returns whether the key should be deleted, which is the case when it is
|
||||
// covered by a range tombstone residing in the same snapshot stripe.
|
||||
bool ShouldDelete(const ParsedInternalKey& parsed);
|
||||
bool ShouldDelete(const Slice& internal_key);
|
||||
bool ShouldAddTombstones(bool bottommost_level = false);
|
||||
Status AddTombstones(ScopedArenaIterator input);
|
||||
|
||||
// Adds tombstones to the tombstone aggregation structure maintained by this
|
||||
// object.
|
||||
// @return non-OK status if any of the tombstone keys are corrupted.
|
||||
Status AddTombstones(std::unique_ptr<InternalIterator> input);
|
||||
// write tombstones covering a range to a table builder
|
||||
// usually don't add to a max-level table builder
|
||||
void AddToBuilder(TableBuilder* builder, bool extend_before_min_key,
|
||||
const Slice* next_table_min_key, FileMetaData* meta,
|
||||
|
||||
// Writes tombstones covering a range to a table builder.
|
||||
// @param extend_before_min_key If true, the range of tombstones to be added
|
||||
// to the TableBuilder starts from the beginning of the key-range;
|
||||
// otherwise, it starts from meta->smallest.
|
||||
// @param lower_bound/upper_bound Any range deletion with [start_key, end_key)
|
||||
// that overlaps the target range [*lower_bound, *upper_bound) is added to
|
||||
// the builder. If lower_bound is nullptr, the target range extends
|
||||
// infinitely to the left. If upper_bound is nullptr, the target range
|
||||
// extends infinitely to the right. If both are nullptr, the target range
|
||||
// extends infinitely in both directions, i.e., all range deletions are
|
||||
// added to the builder.
|
||||
// @param meta The file's metadata. We modify the begin and end keys according
|
||||
// to the range tombstones added to this file such that the read path does
|
||||
// not miss range tombstones that cover gaps before/after/between files in
|
||||
// a level. lower_bound/upper_bound above constrain how far file boundaries
|
||||
// can be extended.
|
||||
// @param bottommost_level If true, we will filter out any tombstones
|
||||
// belonging to the oldest snapshot stripe, because all keys potentially
|
||||
// covered by this tombstone are guaranteed to have been deleted by
|
||||
// compaction.
|
||||
void AddToBuilder(TableBuilder* builder, const Slice* lower_bound,
|
||||
const Slice* upper_bound, FileMetaData* meta,
|
||||
CompactionIterationStats* range_del_out_stats = nullptr,
|
||||
bool bottommost_level = false);
|
||||
bool IsEmpty();
|
||||
|
||||
private:
|
||||
// Maps tombstone start key -> tombstone object
|
||||
typedef std::map<std::string, RangeTombstone, stl_wrappers::LessOfComparator>
|
||||
// Maps tombstone internal start key -> tombstone object
|
||||
typedef std::map<Slice, RangeTombstone, stl_wrappers::LessOfComparator>
|
||||
TombstoneMap;
|
||||
// Maps snapshot seqnum -> map of tombstones that fall in that stripe, i.e.,
|
||||
// their seqnums are greater than the next smaller snapshot's seqnum.
|
||||
typedef std::map<SequenceNumber, TombstoneMap> StripeMap;
|
||||
|
||||
Status AddTombstones(InternalIterator* input, bool arena);
|
||||
StripeMap::iterator GetStripeMapIter(SequenceNumber seq);
|
||||
struct Rep {
|
||||
StripeMap stripe_map_;
|
||||
PinnedIteratorsManager pinned_iters_mgr_;
|
||||
};
|
||||
// Initializes rep_ lazily. This aggregator object is constructed for every
|
||||
// read, so expensive members should only be created when necessary, i.e.,
|
||||
// once the first range deletion is encountered.
|
||||
void InitRep(const std::vector<SequenceNumber>& snapshots);
|
||||
|
||||
PinnedIteratorsManager pinned_iters_mgr_;
|
||||
StripeMap stripe_map_;
|
||||
const InternalKeyComparator icmp_;
|
||||
TombstoneMap& GetTombstoneMap(SequenceNumber seq);
|
||||
|
||||
SequenceNumber upper_bound_;
|
||||
std::unique_ptr<Rep> rep_;
|
||||
const InternalKeyComparator& icmp_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
+8
-6
@@ -380,11 +380,12 @@ class Repairer {
|
||||
ScopedArenaIterator iter(mem->NewIterator(ro, &arena));
|
||||
status = BuildTable(
|
||||
dbname_, env_, *cfd->ioptions(), *cfd->GetLatestMutableCFOptions(),
|
||||
env_options_, table_cache_, iter.get(), &meta,
|
||||
cfd->internal_comparator(), cfd->int_tbl_prop_collector_factories(),
|
||||
cfd->GetID(), cfd->GetName(), {}, kMaxSequenceNumber, kNoCompression,
|
||||
CompressionOptions(), false, nullptr /* internal_stats */,
|
||||
TableFileCreationReason::kRecovery);
|
||||
env_options_, table_cache_, iter.get(),
|
||||
std::unique_ptr<InternalIterator>(mem->NewRangeTombstoneIterator(ro)),
|
||||
&meta, cfd->internal_comparator(),
|
||||
cfd->int_tbl_prop_collector_factories(), cfd->GetID(), cfd->GetName(),
|
||||
{}, kMaxSequenceNumber, kNoCompression, CompressionOptions(), false,
|
||||
nullptr /* internal_stats */, TableFileCreationReason::kRecovery);
|
||||
Log(InfoLogLevel::INFO_LEVEL, db_options_.info_log,
|
||||
"Log #%" PRIu64 ": %d ops saved to Table #%" PRIu64 " %s", log,
|
||||
counter, meta.fd.GetNumber(), status.ToString().c_str());
|
||||
@@ -466,7 +467,8 @@ class Repairer {
|
||||
}
|
||||
if (status.ok()) {
|
||||
InternalIterator* iter = table_cache_->NewIterator(
|
||||
ReadOptions(), env_options_, cfd->internal_comparator(), t->meta.fd);
|
||||
ReadOptions(), env_options_, cfd->internal_comparator(), t->meta.fd,
|
||||
nullptr /* range_del_agg */);
|
||||
bool empty = true;
|
||||
ParsedInternalKey parsed;
|
||||
t->min_sequence = 0;
|
||||
|
||||
+93
-84
@@ -169,25 +169,20 @@ Status TableCache::FindTable(const EnvOptions& env_options,
|
||||
InternalIterator* TableCache::NewIterator(
|
||||
const ReadOptions& options, const EnvOptions& env_options,
|
||||
const InternalKeyComparator& icomparator, const FileDescriptor& fd,
|
||||
TableReader** table_reader_ptr, HistogramImpl* file_read_hist,
|
||||
bool for_compaction, Arena* arena, bool skip_filters, int level,
|
||||
RangeDelAggregator* range_del_agg /* = nullptr */,
|
||||
bool is_range_del_only /* = false */) {
|
||||
assert(!is_range_del_only || range_del_agg != nullptr);
|
||||
RangeDelAggregator* range_del_agg, TableReader** table_reader_ptr,
|
||||
HistogramImpl* file_read_hist, bool for_compaction, Arena* arena,
|
||||
bool skip_filters, int level) {
|
||||
PERF_TIMER_GUARD(new_table_iterator_nanos);
|
||||
|
||||
if (table_reader_ptr != nullptr) {
|
||||
*table_reader_ptr = nullptr;
|
||||
}
|
||||
|
||||
Status s;
|
||||
bool create_new_table_reader = false;
|
||||
TableReader* table_reader = nullptr;
|
||||
Cache::Handle* handle = nullptr;
|
||||
|
||||
size_t readahead = 0;
|
||||
bool create_new_table_reader = false;
|
||||
// pointless to create a new table reader for range tombstones only since the
|
||||
// reader isn't reused
|
||||
if (!is_range_del_only) {
|
||||
if (s.ok()) {
|
||||
if (table_reader_ptr != nullptr) {
|
||||
*table_reader_ptr = nullptr;
|
||||
}
|
||||
size_t readahead = 0;
|
||||
if (for_compaction) {
|
||||
if (ioptions_.new_table_reader_for_compaction_inputs) {
|
||||
readahead = ioptions_.compaction_readahead_size;
|
||||
@@ -197,49 +192,38 @@ InternalIterator* TableCache::NewIterator(
|
||||
readahead = options.readahead_size;
|
||||
create_new_table_reader = readahead > 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (create_new_table_reader) {
|
||||
unique_ptr<TableReader> table_reader_unique_ptr;
|
||||
Status s = GetTableReader(
|
||||
env_options, icomparator, fd, true /* sequential_mode */, readahead,
|
||||
!for_compaction /* record stats */, nullptr, &table_reader_unique_ptr,
|
||||
false /* skip_filters */, level);
|
||||
if (!s.ok()) {
|
||||
return NewErrorInternalIterator(s, arena);
|
||||
}
|
||||
table_reader = table_reader_unique_ptr.release();
|
||||
} else {
|
||||
table_reader = fd.table_reader;
|
||||
if (table_reader == nullptr) {
|
||||
Status s = FindTable(env_options, icomparator, fd, &handle,
|
||||
options.read_tier == kBlockCacheTier /* no_io */,
|
||||
!for_compaction /* record read_stats */,
|
||||
file_read_hist, skip_filters, level);
|
||||
if (!s.ok()) {
|
||||
return NewErrorInternalIterator(s, arena);
|
||||
if (create_new_table_reader) {
|
||||
unique_ptr<TableReader> table_reader_unique_ptr;
|
||||
s = GetTableReader(
|
||||
env_options, icomparator, fd, true /* sequential_mode */, readahead,
|
||||
!for_compaction /* record stats */, nullptr, &table_reader_unique_ptr,
|
||||
false /* skip_filters */, level);
|
||||
if (s.ok()) {
|
||||
table_reader = table_reader_unique_ptr.release();
|
||||
}
|
||||
} else {
|
||||
table_reader = fd.table_reader;
|
||||
if (table_reader == nullptr) {
|
||||
s = FindTable(env_options, icomparator, fd, &handle,
|
||||
options.read_tier == kBlockCacheTier /* no_io */,
|
||||
!for_compaction /* record read_stats */, file_read_hist,
|
||||
skip_filters, level);
|
||||
if (s.ok()) {
|
||||
table_reader = GetTableReaderFromHandle(handle);
|
||||
}
|
||||
}
|
||||
table_reader = GetTableReaderFromHandle(handle);
|
||||
}
|
||||
}
|
||||
|
||||
if (range_del_agg != nullptr) {
|
||||
std::unique_ptr<InternalIterator> iter(
|
||||
table_reader->NewRangeTombstoneIterator(options));
|
||||
Status s = range_del_agg->AddTombstones(std::move(iter));
|
||||
if (!s.ok()) {
|
||||
return NewErrorInternalIterator(s, arena);
|
||||
}
|
||||
}
|
||||
|
||||
InternalIterator* result = nullptr;
|
||||
if (!is_range_del_only) {
|
||||
if (s.ok()) {
|
||||
result = table_reader->NewIterator(options, arena, skip_filters);
|
||||
if (create_new_table_reader) {
|
||||
assert(handle == nullptr);
|
||||
result->RegisterCleanup(&DeleteTableReader, table_reader, nullptr);
|
||||
} else if (handle != nullptr) {
|
||||
result->RegisterCleanup(&UnrefEntry, cache_, handle);
|
||||
handle = nullptr; // prevent from releasing below
|
||||
}
|
||||
|
||||
if (for_compaction) {
|
||||
@@ -248,13 +232,24 @@ InternalIterator* TableCache::NewIterator(
|
||||
if (table_reader_ptr != nullptr) {
|
||||
*table_reader_ptr = table_reader;
|
||||
}
|
||||
} else {
|
||||
assert(!create_new_table_reader);
|
||||
// don't need the table reader at all since the iterator over the meta-block
|
||||
// doesn't require it
|
||||
if (handle != nullptr) {
|
||||
UnrefEntry(cache_, handle);
|
||||
}
|
||||
if (s.ok() && range_del_agg != nullptr && !options.ignore_range_deletions) {
|
||||
std::unique_ptr<InternalIterator> range_del_iter(
|
||||
table_reader->NewRangeTombstoneIterator(options));
|
||||
if (range_del_iter != nullptr) {
|
||||
s = range_del_iter->status();
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = range_del_agg->AddTombstones(std::move(range_del_iter));
|
||||
}
|
||||
}
|
||||
|
||||
if (handle != nullptr) {
|
||||
ReleaseHandle(handle);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
assert(result == nullptr);
|
||||
result = NewErrorInternalIterator(s, arena);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -264,15 +259,11 @@ Status TableCache::Get(const ReadOptions& options,
|
||||
const FileDescriptor& fd, const Slice& k,
|
||||
GetContext* get_context, HistogramImpl* file_read_hist,
|
||||
bool skip_filters, int level) {
|
||||
TableReader* t = fd.table_reader;
|
||||
Status s;
|
||||
Cache::Handle* handle = nullptr;
|
||||
std::string* row_cache_entry = nullptr;
|
||||
|
||||
bool done = false;
|
||||
#ifndef ROCKSDB_LITE
|
||||
IterKey row_cache_key;
|
||||
std::string row_cache_entry_buffer;
|
||||
|
||||
// Check row cache if enabled. Since row cache does not currently store
|
||||
// sequence numbers, we cannot use it if we need to fetch the sequence.
|
||||
if (ioptions_.row_cache && !get_context->NeedToReadSequence()) {
|
||||
@@ -294,46 +285,61 @@ Status TableCache::Get(const ReadOptions& options,
|
||||
row_cache_key.TrimAppend(row_cache_key.Size(), user_key.data(),
|
||||
user_key.size());
|
||||
|
||||
if (auto row_handle = ioptions_.row_cache->Lookup(row_cache_key.GetKey())) {
|
||||
if (auto row_handle =
|
||||
ioptions_.row_cache->Lookup(row_cache_key.GetKey())) {
|
||||
auto found_row_cache_entry = static_cast<const std::string*>(
|
||||
ioptions_.row_cache->Value(row_handle));
|
||||
replayGetContextLog(*found_row_cache_entry, user_key, get_context);
|
||||
ioptions_.row_cache->Release(row_handle);
|
||||
RecordTick(ioptions_.statistics, ROW_CACHE_HIT);
|
||||
return Status::OK();
|
||||
done = true;
|
||||
} else {
|
||||
// Not found, setting up the replay log.
|
||||
RecordTick(ioptions_.statistics, ROW_CACHE_MISS);
|
||||
row_cache_entry = &row_cache_entry_buffer;
|
||||
}
|
||||
|
||||
// Not found, setting up the replay log.
|
||||
RecordTick(ioptions_.statistics, ROW_CACHE_MISS);
|
||||
row_cache_entry = &row_cache_entry_buffer;
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
if (!t) {
|
||||
s = FindTable(env_options_, internal_comparator, fd, &handle,
|
||||
options.read_tier == kBlockCacheTier /* no_io */,
|
||||
true /* record_read_stats */, file_read_hist, skip_filters,
|
||||
level);
|
||||
Status s;
|
||||
TableReader* t = fd.table_reader;
|
||||
Cache::Handle* handle = nullptr;
|
||||
if (!done && s.ok()) {
|
||||
if (t == nullptr) {
|
||||
s = FindTable(env_options_, internal_comparator, fd, &handle,
|
||||
options.read_tier == kBlockCacheTier /* no_io */,
|
||||
true /* record_read_stats */, file_read_hist, skip_filters,
|
||||
level);
|
||||
if (s.ok()) {
|
||||
t = GetTableReaderFromHandle(handle);
|
||||
}
|
||||
}
|
||||
if (s.ok() && get_context->range_del_agg() != nullptr &&
|
||||
!options.ignore_range_deletions) {
|
||||
std::unique_ptr<InternalIterator> range_del_iter(
|
||||
t->NewRangeTombstoneIterator(options));
|
||||
if (range_del_iter != nullptr) {
|
||||
s = range_del_iter->status();
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = get_context->range_del_agg()->AddTombstones(
|
||||
std::move(range_del_iter));
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
t = GetTableReaderFromHandle(handle);
|
||||
get_context->SetReplayLog(row_cache_entry); // nullptr if no cache.
|
||||
s = t->Get(options, k, get_context, skip_filters);
|
||||
get_context->SetReplayLog(nullptr);
|
||||
} else if (options.read_tier == kBlockCacheTier && s.IsIncomplete()) {
|
||||
// Couldn't find Table in cache but treat as kFound if no_io set
|
||||
get_context->MarkKeyMayExist();
|
||||
s = Status::OK();
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
get_context->SetReplayLog(row_cache_entry); // nullptr if no cache.
|
||||
s = t->Get(options, k, get_context, skip_filters);
|
||||
get_context->SetReplayLog(nullptr);
|
||||
if (handle != nullptr) {
|
||||
ReleaseHandle(handle);
|
||||
}
|
||||
} else if (options.read_tier == kBlockCacheTier && s.IsIncomplete()) {
|
||||
// Couldn't find Table in cache but treat as kFound if no_io set
|
||||
get_context->MarkKeyMayExist();
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Put the replay log in row cache only if something was found.
|
||||
if (s.ok() && row_cache_entry && !row_cache_entry->empty()) {
|
||||
if (!done && s.ok() && row_cache_entry && !row_cache_entry->empty()) {
|
||||
size_t charge =
|
||||
row_cache_key.Size() + row_cache_entry->size() + sizeof(std::string);
|
||||
void* row_ptr = new std::string(std::move(*row_cache_entry));
|
||||
@@ -342,6 +348,9 @@ Status TableCache::Get(const ReadOptions& options,
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
if (handle != nullptr) {
|
||||
ReleaseHandle(handle);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
+8
-8
@@ -46,24 +46,24 @@ class TableCache {
|
||||
// the returned iterator. The returned "*tableptr" object is owned by
|
||||
// the cache and should not be deleted, and is valid for as long as the
|
||||
// returned iterator is live.
|
||||
// @param range_del_agg If non-nullptr, adds range deletions to the
|
||||
// aggregator. If an error occurs, returns it in a NewErrorInternalIterator
|
||||
// @param skip_filters Disables loading/accessing the filter block
|
||||
// @param level The level this table is at, -1 for "not set / don't know"
|
||||
// @param range_del_agg When non-nullptr, creates a range tombstone iterator
|
||||
// over this file's meta-block and gives it to this object
|
||||
// @param is_range_del_only When set, this function only gives a range
|
||||
// tombstone iterator to range_del_agg and then returns nullptr
|
||||
InternalIterator* NewIterator(
|
||||
const ReadOptions& options, const EnvOptions& toptions,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileDescriptor& file_fd, TableReader** table_reader_ptr = nullptr,
|
||||
const FileDescriptor& file_fd, RangeDelAggregator* range_del_agg,
|
||||
TableReader** table_reader_ptr = nullptr,
|
||||
HistogramImpl* file_read_hist = nullptr, bool for_compaction = false,
|
||||
Arena* arena = nullptr, bool skip_filters = false, int level = -1,
|
||||
RangeDelAggregator* range_del_agg = nullptr,
|
||||
bool is_range_del_only = false);
|
||||
Arena* arena = nullptr, bool skip_filters = false, int level = -1);
|
||||
|
||||
// If a seek to internal key "k" in specified file finds an entry,
|
||||
// call (*handle_result)(arg, found_key, found_value) repeatedly until
|
||||
// it returns false.
|
||||
// @param get_context State for get operation. If its range_del_agg() returns
|
||||
// non-nullptr, adds range deletions to the aggregator. If an error occurs,
|
||||
// returns non-ok status.
|
||||
// @param skip_filters Disables loading/accessing the filter block
|
||||
// @param level The level this table is at, -1 for "not set / don't know"
|
||||
Status Get(const ReadOptions& options,
|
||||
|
||||
@@ -391,7 +391,6 @@ void TestInternalKeyPropertiesCollector(
|
||||
// SanitizeOptions().
|
||||
options.info_log = std::make_shared<test::NullLogger>();
|
||||
options = SanitizeOptions("db", // just a place holder
|
||||
&pikc,
|
||||
options);
|
||||
ImmutableCFOptions ioptions(options);
|
||||
GetIntTblPropCollectorFactory(ioptions, &int_tbl_prop_collector_factories);
|
||||
|
||||
+19
-10
@@ -32,12 +32,12 @@
|
||||
namespace rocksdb {
|
||||
|
||||
bool NewestFirstBySeqNo(FileMetaData* a, FileMetaData* b) {
|
||||
if (a->smallest_seqno != b->smallest_seqno) {
|
||||
return a->smallest_seqno > b->smallest_seqno;
|
||||
}
|
||||
if (a->largest_seqno != b->largest_seqno) {
|
||||
return a->largest_seqno > b->largest_seqno;
|
||||
}
|
||||
if (a->smallest_seqno != b->smallest_seqno) {
|
||||
return a->smallest_seqno > b->smallest_seqno;
|
||||
}
|
||||
// Break ties by file number
|
||||
return a->fd.GetNumber() > b->fd.GetNumber();
|
||||
}
|
||||
@@ -146,13 +146,22 @@ class VersionBuilder::Rep {
|
||||
abort();
|
||||
}
|
||||
|
||||
if (!(f1->largest_seqno > f2->largest_seqno ||
|
||||
// We can have multiple files with seqno = 0 as a result of
|
||||
// using DB::AddFile()
|
||||
(f1->largest_seqno == 0 && f2->largest_seqno == 0))) {
|
||||
fprintf(stderr,
|
||||
"L0 files seqno missmatch %" PRIu64 " vs. %" PRIu64 "\n",
|
||||
f1->largest_seqno, f2->largest_seqno);
|
||||
if (f2->smallest_seqno == f2->largest_seqno) {
|
||||
// This is an external file that we ingested
|
||||
SequenceNumber external_file_seqno = f2->smallest_seqno;
|
||||
if (!(external_file_seqno < f1->largest_seqno ||
|
||||
external_file_seqno == 0)) {
|
||||
fprintf(stderr, "L0 file with seqno %" PRIu64 " %" PRIu64
|
||||
" vs. file with global_seqno %" PRIu64 "\n",
|
||||
f1->smallest_seqno, f1->largest_seqno,
|
||||
external_file_seqno);
|
||||
abort();
|
||||
}
|
||||
} else if (f1->smallest_seqno <= f2->smallest_seqno) {
|
||||
fprintf(stderr, "L0 files seqno %" PRIu64 " %" PRIu64
|
||||
" vs. %" PRIu64 " %" PRIu64 "\n",
|
||||
f1->smallest_seqno, f1->largest_seqno, f2->smallest_seqno,
|
||||
f2->largest_seqno);
|
||||
abort();
|
||||
}
|
||||
} else {
|
||||
|
||||
+64
-50
@@ -514,15 +514,13 @@ class LevelFileIteratorState : public TwoLevelIteratorState {
|
||||
if (meta_handle.size() != sizeof(FileDescriptor)) {
|
||||
return NewErrorInternalIterator(
|
||||
Status::Corruption("FileReader invoked with unexpected value"));
|
||||
} else {
|
||||
const FileDescriptor* fd =
|
||||
reinterpret_cast<const FileDescriptor*>(meta_handle.data());
|
||||
return table_cache_->NewIterator(
|
||||
read_options_, env_options_, icomparator_, *fd,
|
||||
nullptr /* don't need reference to table*/, file_read_hist_,
|
||||
for_compaction_, nullptr /* arena */, skip_filters_, level_,
|
||||
range_del_agg_, false /* is_range_del_only */);
|
||||
}
|
||||
const FileDescriptor* fd =
|
||||
reinterpret_cast<const FileDescriptor*>(meta_handle.data());
|
||||
return table_cache_->NewIterator(
|
||||
read_options_, env_options_, icomparator_, *fd, range_del_agg_,
|
||||
nullptr /* don't need reference to table */, file_read_hist_,
|
||||
for_compaction_, nullptr /* arena */, skip_filters_, level_);
|
||||
}
|
||||
|
||||
bool PrefixMayMatch(const Slice& internal_key) override {
|
||||
@@ -805,44 +803,57 @@ double VersionStorageInfo::GetEstimatedCompressionRatioAtLevel(
|
||||
|
||||
void Version::AddIterators(const ReadOptions& read_options,
|
||||
const EnvOptions& soptions,
|
||||
MergeIteratorBuilder* merge_iter_builder) {
|
||||
MergeIteratorBuilder* merge_iter_builder,
|
||||
RangeDelAggregator* range_del_agg) {
|
||||
assert(storage_info_.finalized_);
|
||||
|
||||
if (storage_info_.num_non_empty_levels() == 0) {
|
||||
// No file in the Version.
|
||||
for (int level = 0; level < storage_info_.num_non_empty_levels(); level++) {
|
||||
AddIteratorsForLevel(read_options, soptions, merge_iter_builder, level,
|
||||
range_del_agg);
|
||||
}
|
||||
}
|
||||
|
||||
void Version::AddIteratorsForLevel(const ReadOptions& read_options,
|
||||
const EnvOptions& soptions,
|
||||
MergeIteratorBuilder* merge_iter_builder,
|
||||
int level,
|
||||
RangeDelAggregator* range_del_agg) {
|
||||
assert(storage_info_.finalized_);
|
||||
if (level >= storage_info_.num_non_empty_levels()) {
|
||||
// This is an empty level
|
||||
return;
|
||||
} else if (storage_info_.LevelFilesBrief(level).num_files == 0) {
|
||||
// No files in this level
|
||||
return;
|
||||
}
|
||||
|
||||
auto* arena = merge_iter_builder->GetArena();
|
||||
|
||||
// Merge all level zero files together since they may overlap
|
||||
for (size_t i = 0; i < storage_info_.LevelFilesBrief(0).num_files; i++) {
|
||||
const auto& file = storage_info_.LevelFilesBrief(0).files[i];
|
||||
merge_iter_builder->AddIterator(cfd_->table_cache()->NewIterator(
|
||||
read_options, soptions, cfd_->internal_comparator(), file.fd, nullptr,
|
||||
cfd_->internal_stats()->GetFileReadHist(0), false, arena,
|
||||
false /* skip_filters */, 0 /* level */));
|
||||
}
|
||||
|
||||
// For levels > 0, we can use a concatenating iterator that sequentially
|
||||
// walks through the non-overlapping files in the level, opening them
|
||||
// lazily.
|
||||
for (int level = 1; level < storage_info_.num_non_empty_levels(); level++) {
|
||||
if (storage_info_.LevelFilesBrief(level).num_files != 0) {
|
||||
auto* mem = arena->AllocateAligned(sizeof(LevelFileIteratorState));
|
||||
auto* state = new (mem) LevelFileIteratorState(
|
||||
cfd_->table_cache(), read_options, soptions,
|
||||
cfd_->internal_comparator(),
|
||||
cfd_->internal_stats()->GetFileReadHist(level),
|
||||
false /* for_compaction */,
|
||||
cfd_->ioptions()->prefix_extractor != nullptr, IsFilterSkipped(level),
|
||||
level, nullptr /* range_del_agg */);
|
||||
mem = arena->AllocateAligned(sizeof(LevelFileNumIterator));
|
||||
auto* first_level_iter = new (mem) LevelFileNumIterator(
|
||||
cfd_->internal_comparator(), &storage_info_.LevelFilesBrief(level));
|
||||
merge_iter_builder->AddIterator(
|
||||
NewTwoLevelIterator(state, first_level_iter, arena, false));
|
||||
if (level == 0) {
|
||||
// Merge all level zero files together since they may overlap
|
||||
for (size_t i = 0; i < storage_info_.LevelFilesBrief(0).num_files; i++) {
|
||||
const auto& file = storage_info_.LevelFilesBrief(0).files[i];
|
||||
merge_iter_builder->AddIterator(cfd_->table_cache()->NewIterator(
|
||||
read_options, soptions, cfd_->internal_comparator(), file.fd,
|
||||
range_del_agg, nullptr, cfd_->internal_stats()->GetFileReadHist(0),
|
||||
false, arena, false /* skip_filters */, 0 /* level */));
|
||||
}
|
||||
} else {
|
||||
// For levels > 0, we can use a concatenating iterator that sequentially
|
||||
// walks through the non-overlapping files in the level, opening them
|
||||
// lazily.
|
||||
auto* mem = arena->AllocateAligned(sizeof(LevelFileIteratorState));
|
||||
auto* state = new (mem)
|
||||
LevelFileIteratorState(cfd_->table_cache(), read_options, soptions,
|
||||
cfd_->internal_comparator(),
|
||||
cfd_->internal_stats()->GetFileReadHist(level),
|
||||
false /* for_compaction */,
|
||||
cfd_->ioptions()->prefix_extractor != nullptr,
|
||||
IsFilterSkipped(level), level, range_del_agg);
|
||||
mem = arena->AllocateAligned(sizeof(LevelFileNumIterator));
|
||||
auto* first_level_iter = new (mem) LevelFileNumIterator(
|
||||
cfd_->internal_comparator(), &storage_info_.LevelFilesBrief(level));
|
||||
merge_iter_builder->AddIterator(
|
||||
NewTwoLevelIterator(state, first_level_iter, arena, false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -918,7 +929,8 @@ Version::Version(ColumnFamilyData* column_family_data, VersionSet* vset,
|
||||
|
||||
void Version::Get(const ReadOptions& read_options, const LookupKey& k,
|
||||
std::string* value, Status* status,
|
||||
MergeContext* merge_context, bool* value_found,
|
||||
MergeContext* merge_context,
|
||||
RangeDelAggregator* range_del_agg, bool* value_found,
|
||||
bool* key_exists, SequenceNumber* seq) {
|
||||
Slice ikey = k.internal_key();
|
||||
Slice user_key = k.user_key();
|
||||
@@ -934,7 +946,7 @@ void Version::Get(const ReadOptions& read_options, const LookupKey& k,
|
||||
GetContext get_context(
|
||||
user_comparator(), merge_operator_, info_log_, db_statistics_,
|
||||
status->ok() ? GetContext::kNotFound : GetContext::kMerge, user_key,
|
||||
value, value_found, merge_context, this->env_, seq,
|
||||
value, value_found, merge_context, range_del_agg, this->env_, seq,
|
||||
merge_operator_ ? &pinned_iters_mgr : nullptr);
|
||||
|
||||
// Pin blocks that we read to hold merge operands
|
||||
@@ -1970,14 +1982,15 @@ void VersionStorageInfo::CalculateBaseBytes(const ImmutableCFOptions& ioptions,
|
||||
base_level_ = num_levels_ - 1;
|
||||
} else {
|
||||
uint64_t base_bytes_max = options.max_bytes_for_level_base;
|
||||
uint64_t base_bytes_min =
|
||||
base_bytes_max / options.max_bytes_for_level_multiplier;
|
||||
uint64_t base_bytes_min = static_cast<uint64_t>(
|
||||
base_bytes_max / options.max_bytes_for_level_multiplier);
|
||||
|
||||
// Try whether we can make last level's target size to be max_level_size
|
||||
uint64_t cur_level_size = max_level_size;
|
||||
for (int i = num_levels_ - 2; i >= first_non_empty_level; i--) {
|
||||
// Round up after dividing
|
||||
cur_level_size /= options.max_bytes_for_level_multiplier;
|
||||
cur_level_size = static_cast<uint64_t>(
|
||||
cur_level_size / options.max_bytes_for_level_multiplier);
|
||||
}
|
||||
|
||||
// Calculate base level and its size.
|
||||
@@ -1996,8 +2009,8 @@ void VersionStorageInfo::CalculateBaseBytes(const ImmutableCFOptions& ioptions,
|
||||
base_level_ = first_non_empty_level;
|
||||
while (base_level_ > 1 && cur_level_size > base_bytes_max) {
|
||||
--base_level_;
|
||||
cur_level_size =
|
||||
cur_level_size / options.max_bytes_for_level_multiplier;
|
||||
cur_level_size = static_cast<uint64_t>(
|
||||
cur_level_size / options.max_bytes_for_level_multiplier);
|
||||
}
|
||||
if (cur_level_size > base_bytes_max) {
|
||||
// Even L1 will be too large
|
||||
@@ -3302,7 +3315,7 @@ uint64_t VersionSet::ApproximateSize(Version* v, const FdWithKeyRange& f,
|
||||
TableReader* table_reader_ptr;
|
||||
InternalIterator* iter = v->cfd_->table_cache()->NewIterator(
|
||||
ReadOptions(), env_options_, v->cfd_->internal_comparator(), f.fd,
|
||||
&table_reader_ptr);
|
||||
nullptr /* range_del_agg */, &table_reader_ptr);
|
||||
if (table_reader_ptr != nullptr) {
|
||||
result = table_reader_ptr->ApproximateOffsetOf(key);
|
||||
}
|
||||
@@ -3373,10 +3386,11 @@ InternalIterator* VersionSet::MakeInputIterator(
|
||||
for (size_t i = 0; i < flevel->num_files; i++) {
|
||||
list[num++] = cfd->table_cache()->NewIterator(
|
||||
read_options, env_options_compactions_,
|
||||
cfd->internal_comparator(), flevel->files[i].fd, nullptr,
|
||||
nullptr, /* no per level latency histogram*/
|
||||
cfd->internal_comparator(), flevel->files[i].fd, range_del_agg,
|
||||
nullptr /* table_reader_ptr */,
|
||||
nullptr /* no per level latency histogram */,
|
||||
true /* for_compaction */, nullptr /* arena */,
|
||||
false /* skip_filters */, (int)which /* level */, range_del_agg);
|
||||
false /* skip_filters */, (int)which /* level */);
|
||||
}
|
||||
} else {
|
||||
// Create concatenating iterator for the files from this level
|
||||
|
||||
+9
-3
@@ -34,6 +34,7 @@
|
||||
#include "db/dbformat.h"
|
||||
#include "db/file_indexer.h"
|
||||
#include "db/log_reader.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "db/table_cache.h"
|
||||
#include "db/version_builder.h"
|
||||
#include "db/version_edit.h"
|
||||
@@ -433,7 +434,12 @@ class Version {
|
||||
// yield the contents of this Version when merged together.
|
||||
// REQUIRES: This version has been saved (see VersionSet::SaveTo)
|
||||
void AddIterators(const ReadOptions&, const EnvOptions& soptions,
|
||||
MergeIteratorBuilder* merger_iter_builder);
|
||||
MergeIteratorBuilder* merger_iter_builder,
|
||||
RangeDelAggregator* range_del_agg);
|
||||
|
||||
void AddIteratorsForLevel(const ReadOptions&, const EnvOptions& soptions,
|
||||
MergeIteratorBuilder* merger_iter_builder,
|
||||
int level, RangeDelAggregator* range_del_agg);
|
||||
|
||||
// Lookup the value for key. If found, store it in *val and
|
||||
// return OK. Else return a non-OK status.
|
||||
@@ -453,8 +459,8 @@ class Version {
|
||||
// REQUIRES: lock is not held
|
||||
void Get(const ReadOptions&, const LookupKey& key, std::string* val,
|
||||
Status* status, MergeContext* merge_context,
|
||||
bool* value_found = nullptr, bool* key_exists = nullptr,
|
||||
SequenceNumber* seq = nullptr);
|
||||
RangeDelAggregator* range_del_agg, bool* value_found = nullptr,
|
||||
bool* key_exists = nullptr, SequenceNumber* seq = nullptr);
|
||||
|
||||
// Loads some stats information from files. Call without mutex held. It needs
|
||||
// to be called before applying the version to the version set.
|
||||
|
||||
+15
-1
@@ -48,6 +48,7 @@
|
||||
#include "util/coding.h"
|
||||
#include "util/perf_context_imp.h"
|
||||
#include "util/statistics.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -886,7 +887,7 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
|
||||
auto cf_handle = cf_mems_->GetColumnFamilyHandle();
|
||||
Status s = Status::NotSupported();
|
||||
if (db_ != nullptr && recovering_log_number_ != 0) {
|
||||
if (db_ != nullptr && recovering_log_number_ == 0) {
|
||||
if (cf_handle == nullptr) {
|
||||
cf_handle = db_->DefaultColumnFamily();
|
||||
}
|
||||
@@ -973,6 +974,19 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
++sequence_;
|
||||
return seek_status;
|
||||
}
|
||||
if (db_ != nullptr) {
|
||||
auto cf_handle = cf_mems_->GetColumnFamilyHandle();
|
||||
if (cf_handle == nullptr) {
|
||||
cf_handle = db_->DefaultColumnFamily();
|
||||
}
|
||||
auto* cfd = reinterpret_cast<ColumnFamilyHandleImpl*>(cf_handle)->cfd();
|
||||
if (!cfd->is_delete_range_supported()) {
|
||||
return Status::NotSupported(
|
||||
std::string("DeleteRange not supported for table type ") +
|
||||
cfd->ioptions()->table_factory->Name() + " in CF " +
|
||||
cfd->GetName());
|
||||
}
|
||||
}
|
||||
|
||||
return DeleteImpl(column_family_id, begin_key, end_key, kTypeRangeDeletion);
|
||||
}
|
||||
|
||||
+13
-4
@@ -45,10 +45,19 @@ static std::string PrintContents(WriteBatch* b) {
|
||||
int merge_count = 0;
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
Arena arena;
|
||||
auto iter =
|
||||
i == 0 ? ScopedArenaIterator(mem->NewIterator(ReadOptions(), &arena))
|
||||
: ScopedArenaIterator(
|
||||
mem->NewRangeTombstoneIterator(ReadOptions(), &arena));
|
||||
ScopedArenaIterator arena_iter_guard;
|
||||
std::unique_ptr<InternalIterator> iter_guard;
|
||||
InternalIterator* iter;
|
||||
if (i == 0) {
|
||||
iter = mem->NewIterator(ReadOptions(), &arena);
|
||||
arena_iter_guard.set(iter);
|
||||
} else {
|
||||
iter = mem->NewRangeTombstoneIterator(ReadOptions());
|
||||
iter_guard.reset(iter);
|
||||
}
|
||||
if (iter == nullptr) {
|
||||
continue;
|
||||
}
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
ParsedInternalKey ikey;
|
||||
memset((void*)&ikey, 0, sizeof(ikey));
|
||||
|
||||
+19
-1
@@ -26,7 +26,7 @@ class WriteController {
|
||||
total_compaction_pressure_(0),
|
||||
bytes_left_(0),
|
||||
last_refill_time_(0) {
|
||||
set_delayed_write_rate(_delayed_write_rate);
|
||||
set_max_delayed_write_rate(_delayed_write_rate);
|
||||
}
|
||||
~WriteController() = default;
|
||||
|
||||
@@ -57,11 +57,26 @@ class WriteController {
|
||||
// avoid divide 0
|
||||
if (write_rate == 0) {
|
||||
write_rate = 1u;
|
||||
} else if (write_rate > max_delayed_write_rate()) {
|
||||
write_rate = max_delayed_write_rate();
|
||||
}
|
||||
delayed_write_rate_ = write_rate;
|
||||
}
|
||||
|
||||
void set_max_delayed_write_rate(uint64_t write_rate) {
|
||||
// avoid divide 0
|
||||
if (write_rate == 0) {
|
||||
write_rate = 1u;
|
||||
}
|
||||
max_delayed_write_rate_ = write_rate;
|
||||
// update delayed_write_rate_ as well
|
||||
delayed_write_rate_ = write_rate;
|
||||
}
|
||||
|
||||
uint64_t delayed_write_rate() const { return delayed_write_rate_; }
|
||||
|
||||
uint64_t max_delayed_write_rate() const { return max_delayed_write_rate_; }
|
||||
|
||||
private:
|
||||
friend class WriteControllerToken;
|
||||
friend class StopWriteToken;
|
||||
@@ -73,6 +88,9 @@ class WriteController {
|
||||
int total_compaction_pressure_;
|
||||
uint64_t bytes_left_;
|
||||
uint64_t last_refill_time_;
|
||||
// write rate set when initialization or by `DBImpl::SetDBOptions`
|
||||
uint64_t max_delayed_write_rate_;
|
||||
// current write rate
|
||||
uint64_t delayed_write_rate_;
|
||||
};
|
||||
|
||||
|
||||
@@ -21,7 +21,8 @@ class TimeSetEnv : public EnvWrapper {
|
||||
|
||||
TEST_F(WriteControllerTest, ChangeDelayRateTest) {
|
||||
TimeSetEnv env;
|
||||
WriteController controller(10000000u);
|
||||
WriteController controller(40000000u); // also set max delayed rate
|
||||
controller.set_delayed_write_rate(10000000u);
|
||||
auto delay_token_0 =
|
||||
controller.GetDelayToken(controller.delayed_write_rate());
|
||||
ASSERT_EQ(static_cast<uint64_t>(2000000),
|
||||
@@ -35,8 +36,9 @@ TEST_F(WriteControllerTest, ChangeDelayRateTest) {
|
||||
auto delay_token_3 = controller.GetDelayToken(20000000u);
|
||||
ASSERT_EQ(static_cast<uint64_t>(1000000),
|
||||
controller.GetDelay(&env, 20000000u));
|
||||
// This is more than max rate. Max delayed rate will be used.
|
||||
auto delay_token_4 =
|
||||
controller.GetDelayToken(controller.delayed_write_rate() * 2);
|
||||
controller.GetDelayToken(controller.delayed_write_rate() * 3);
|
||||
ASSERT_EQ(static_cast<uint64_t>(500000),
|
||||
controller.GetDelay(&env, 20000000u));
|
||||
}
|
||||
|
||||
+8
-2
@@ -268,6 +268,12 @@ size_t WriteThread::EnterAsBatchGroupLeader(
|
||||
break;
|
||||
}
|
||||
|
||||
if (w->no_slowdown != leader->no_slowdown) {
|
||||
// Do not mix writes that are ok with delays with the ones that
|
||||
// request fail on delays.
|
||||
break;
|
||||
}
|
||||
|
||||
if (!w->disableWAL && leader->disableWAL) {
|
||||
// Do not include a write that needs WAL into a batch that has
|
||||
// WAL disabled.
|
||||
@@ -311,7 +317,7 @@ void WriteThread::LaunchParallelFollowers(ParallelGroup* pg,
|
||||
|
||||
while (w != pg->last_writer) {
|
||||
// Writers that won't write don't get sequence allotment
|
||||
if (!w->CallbackFailed()) {
|
||||
if (!w->CallbackFailed() && w->ShouldWriteToMemtable()) {
|
||||
sequence += WriteBatchInternal::Count(w->batch);
|
||||
}
|
||||
w = w->link_newer;
|
||||
@@ -327,7 +333,7 @@ bool WriteThread::CompleteParallelWorker(Writer* w) {
|
||||
|
||||
auto* pg = w->parallel_group;
|
||||
if (!w->status.ok()) {
|
||||
std::lock_guard<std::mutex> guard(w->StateMutex());
|
||||
std::lock_guard<std::mutex> guard(pg->leader->StateMutex());
|
||||
pg->status = w->status;
|
||||
}
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@ class WriteThread {
|
||||
struct Writer {
|
||||
WriteBatch* batch;
|
||||
bool sync;
|
||||
bool no_slowdown;
|
||||
bool disableWAL;
|
||||
bool disable_memtable;
|
||||
uint64_t log_used; // log number that this batch was inserted into
|
||||
@@ -99,6 +100,7 @@ class WriteThread {
|
||||
Writer()
|
||||
: batch(nullptr),
|
||||
sync(false),
|
||||
no_slowdown(false),
|
||||
disableWAL(false),
|
||||
disable_memtable(false),
|
||||
log_used(0),
|
||||
|
||||
@@ -7,7 +7,7 @@ You should only have to do these one time.
|
||||
- Rename this file to `CONTRIBUTING.md`.
|
||||
- Rename `EXAMPLE-README-FOR-RUNNING-DOCS.md` to `README.md` (replacing the existing `README.md` that came with the template).
|
||||
- Rename `EXAMPLE-LICENSE` to `LICENSE`.
|
||||
- Checkout the [template information](./TEMPLATE-INFORMATION.md).
|
||||
- Review the [template information](./TEMPLATE-INFORMATION.md).
|
||||
- Review `./_config.yml`.
|
||||
- Make sure you update `title`, `description`, `tagline` and `gacode` (Google Analytics) in `./_config.yml`.
|
||||
|
||||
|
||||
+1
-2
@@ -1,3 +1,2 @@
|
||||
source 'https://rubygems.org'
|
||||
gem 'github-pages', group: :jekyll_plugins
|
||||
gem 'jekyll-redirect-from'
|
||||
gem 'github-pages', '~> 104'
|
||||
Loaded 100 of 250 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user