mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-07 03:22:34 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3500b728d1 | ||
|
|
275488d483 | ||
|
|
b8674494bc | ||
|
|
f659afc64a | ||
|
|
68c5ae432f | ||
|
|
8277cd14aa | ||
|
|
acb2e98a90 | ||
|
|
241c45d1df | ||
|
|
d19646e1c1 | ||
|
|
462c21dda6 | ||
|
|
d6cfa64721 | ||
|
|
1f055723b7 | ||
|
|
dcafd40aed | ||
|
|
a3576c7c4b | ||
|
|
6fa04041de | ||
|
|
4abd57290c | ||
|
|
f35574a16d | ||
|
|
00c6c53fc8 | ||
|
|
c2ca7a9f99 | ||
|
|
d4b0ac2760 | ||
|
|
a7a2005873 | ||
|
|
acf6d6bcab | ||
|
|
241267966c | ||
|
|
b642e94c88 | ||
|
|
53571a7a70 | ||
|
|
7079b78827 | ||
|
|
512e441819 | ||
|
|
62a1c55418 | ||
|
|
adce9c0a87 | ||
|
|
dabcce9dfb | ||
|
|
bc5d7b7029 | ||
|
|
3e6899d116 | ||
|
|
d4e07a8459 | ||
|
|
dc2584eea0 | ||
|
|
d18dd2c41f | ||
|
|
62384ebe9c | ||
|
|
9f246298e2 | ||
|
|
fe395fb63d | ||
|
|
2172b660eb | ||
|
|
3c233ca4ea | ||
|
|
7631734563 | ||
|
|
7a02ad070f | ||
|
|
60c509ff18 | ||
|
|
d0ba8ec8f9 | ||
|
|
54d94e9c2c | ||
|
|
e04480faed | ||
|
|
c081f7215b | ||
|
|
ac73d7558b | ||
|
|
b104b87814 | ||
|
|
640d724808 | ||
|
|
653ac1f9c6 | ||
|
|
85ac1a320a | ||
|
|
76711b6e77 | ||
|
|
c963460dbc | ||
|
|
33c86d677f | ||
|
|
e425ec1162 | ||
|
|
0712d541d1 | ||
|
|
d58ef52ba6 | ||
|
|
17a4b75cc3 | ||
|
|
2fb70dc798 | ||
|
|
a738af8f84 | ||
|
|
906523d98a | ||
|
|
438f22bc56 | ||
|
|
335981d473 | ||
|
|
548b628054 | ||
|
|
4e07b08eff | ||
|
|
ab48c165a9 | ||
|
|
972f96b3fb | ||
|
|
989e644ed8 | ||
|
|
1beef6569a | ||
|
|
3d692822f8 | ||
|
|
046099c9b5 | ||
|
|
6ff2c8d7f7 | ||
|
|
3cd9ed1c31 | ||
|
|
50e305de98 | ||
|
|
5d1457dbbf | ||
|
|
7bd725e962 | ||
|
|
296691847d | ||
|
|
342370f1d3 | ||
|
|
1a136c1f13 | ||
|
|
fbff4628a9 | ||
|
|
bd6cf7b51d | ||
|
|
fc0c6fd984 | ||
|
|
816c1e30ca | ||
|
|
c270735861 | ||
|
|
a8bf4d63f9 | ||
|
|
8f5d24ae68 | ||
|
|
0ab6fc167f | ||
|
|
b7239bf7e0 | ||
|
|
477b6ea578 | ||
|
|
83f9a6fd21 | ||
|
|
a79eae4b01 | ||
|
|
d71e728c7a | ||
|
|
7004a6f7b8 | ||
|
|
3cdfaeca3f | ||
|
|
e097222e64 | ||
|
|
cfc34d7c4e | ||
|
|
bfbcec2339 | ||
|
|
6653e32ac2 | ||
|
|
67adc937b6 | ||
|
|
1a146f89c7 | ||
|
|
c3e5ee7154 | ||
|
|
5334d8b44c | ||
|
|
36d42e65d0 | ||
|
|
b57dd9262a | ||
|
|
2ba59b5a1e | ||
|
|
1f6f7e3e89 | ||
|
|
243975d5da | ||
|
|
f0c509e2c8 | ||
|
|
dc64f46b1c | ||
|
|
2cabdb8f44 | ||
|
|
4a17b47bb5 | ||
|
|
8c2b921fdf | ||
|
|
20ce081fae | ||
|
|
5241e0dbfc | ||
|
|
c04f6a0b4c | ||
|
|
45c7ce1377 | ||
|
|
2c2ba68247 | ||
|
|
b821984d31 | ||
|
|
2f4fc539c6 | ||
|
|
1dce75b2e2 | ||
|
|
304b3c706b | ||
|
|
fa50fffaf5 | ||
|
|
c26a4d8e8a | ||
|
|
ed8fbdb560 | ||
|
|
2005c88a75 | ||
|
|
0b0f235724 | ||
|
|
23db48e8d8 | ||
|
|
beb36d9c1e | ||
|
|
67f37cf198 | ||
|
|
9053fe2a5c |
No files matched your search
+14
-6
@@ -1,17 +1,22 @@
|
||||
sudo: false
|
||||
dist: trusty
|
||||
language: cpp
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
compiler:
|
||||
- clang
|
||||
- gcc
|
||||
jdk:
|
||||
- oraclejdk7
|
||||
cache:
|
||||
- ccache
|
||||
- apt
|
||||
|
||||
addons:
|
||||
apt:
|
||||
sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.6']
|
||||
packages: ['clang-3.6' , 'zlib1g-dev', 'libbz2-dev', 'libsnappy-dev', 'curl', 'gflags']
|
||||
packages: ['clang-3.6' , 'g++-6', 'zlib1g-dev', 'libbz2-dev', 'libsnappy-dev', 'curl', 'libgflags-dev']
|
||||
env:
|
||||
# Run all tests before db_block_cache_test (db_test, db_test2)
|
||||
- JOB_NAME=unittests ROCKSDBTESTS_END=db_block_cache_test
|
||||
@@ -22,17 +27,20 @@ env:
|
||||
# Build ROCKSDB_LITE
|
||||
- JOB_NAME=lite_build
|
||||
|
||||
install:
|
||||
# Build gflags
|
||||
# TODO(noetzli): Remove when gflags available through Travis
|
||||
- pushd /tmp/ && curl -L https://github.com/gflags/gflags/archive/v2.1.2.tar.gz -o gflags.tar.gz && tar xfz gflags.tar.gz && cd gflags-2.1.2 && cmake . && make && popd
|
||||
matrix:
|
||||
exclude:
|
||||
- os: osx
|
||||
compiler: gcc
|
||||
|
||||
before_script:
|
||||
- if [[ "${TRAVIS_OS_NAME}" == 'linux' ]]; then CXX=clang++-3.6; fi
|
||||
- if [[ "${TRAVIS_OS_NAME}" == 'linux' && "${CXX}" == 'clang++' ]]; then CXX=clang++-3.6; fi
|
||||
# test one linux g++ build with g++-6
|
||||
- if [[ "${TRAVIS_OS_NAME}" == 'linux' && "${CXX}" == 'g++' && "${JOB_NAME}" == 'unittests' ]]; then CXX=g++-6; fi
|
||||
# Limit the maximum number of open file descriptors to 8192
|
||||
- ulimit -n 8192 || true
|
||||
|
||||
script:
|
||||
- ${CXX} --version
|
||||
- if [[ "${JOB_NAME}" == 'unittests' ]]; then OPT=-DTRAVIS V=1 make -j4 check_some; fi
|
||||
- if [[ "${JOB_NAME}" == 'java_test' ]]; then OPT=-DTRAVIS V=1 make clean jclean rocksdbjava jtest; fi
|
||||
- if [[ "${JOB_NAME}" == 'lite_build' ]]; then OPT="-DTRAVIS -DROCKSDB_LITE" V=1 make -j4 static_lib; fi
|
||||
|
||||
@@ -502,6 +502,7 @@ set(TESTS
|
||||
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
|
||||
|
||||
+12
-4
@@ -1,9 +1,13 @@
|
||||
# Rocksdb Change Log
|
||||
## Unreleased
|
||||
## 5.1.0 (01/13/2017)
|
||||
### Public API Change
|
||||
* 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.
|
||||
* Support dynamically change `delete_obsolete_files_period_micros` option via SetDBOptions().
|
||||
* Added EventListener::OnExternalFileIngested which will be called when IngestExternalFile() add a file successfully.
|
||||
* BackupEngine::Open and BackupEngineReadOnly::Open now always return error statuses matching those of the backup Env.
|
||||
|
||||
### Bug Fixes
|
||||
* 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
|
||||
@@ -13,6 +17,10 @@
|
||||
* 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().
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
|
||||
#-----------------------------------------------
|
||||
|
||||
BASH_EXISTS := $(shell which bash)
|
||||
SHELL := $(shell which bash)
|
||||
|
||||
CLEAN_FILES = # deliberately empty, so we can append below.
|
||||
CFLAGS += ${EXTRA_CFLAGS}
|
||||
CXXFLAGS += ${EXTRA_CXXFLAGS}
|
||||
@@ -137,7 +140,7 @@ endif
|
||||
|
||||
AM_LINK = $(AM_V_CCLD)$(CXX) $^ $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
|
||||
# detect what platform we're building on
|
||||
dummy := $(shell (export ROCKSDB_ROOT="$(CURDIR)"; "$(CURDIR)/build_tools/build_detect_platform" "$(CURDIR)/make_config.mk"))
|
||||
dummy := $(shell (export ROCKSDB_ROOT="$(CURDIR)"; export PORTABLE="$(PORTABLE)"; "$(CURDIR)/build_tools/build_detect_platform" "$(CURDIR)/make_config.mk"))
|
||||
# this file is generated by the previous line to set build flags and sources
|
||||
include make_config.mk
|
||||
CLEAN_FILES += make_config.mk
|
||||
@@ -220,11 +223,6 @@ default: all
|
||||
WARNING_FLAGS = -W -Wextra -Wall -Wsign-compare -Wshadow \
|
||||
-Wno-unused-parameter
|
||||
|
||||
ifndef DISABLE_WARNING_AS_ERROR
|
||||
WARNING_FLAGS += -Werror
|
||||
endif
|
||||
|
||||
|
||||
ifdef LUA_PATH
|
||||
|
||||
ifndef LUA_INCLUDE
|
||||
@@ -309,6 +307,7 @@ TESTS = \
|
||||
db_inplace_update_test \
|
||||
db_iterator_test \
|
||||
db_memtable_test \
|
||||
db_merge_operator_test \
|
||||
db_options_test \
|
||||
db_range_del_test \
|
||||
db_sst_test \
|
||||
@@ -320,6 +319,7 @@ TESTS = \
|
||||
db_properties_test \
|
||||
db_table_properties_test \
|
||||
autovector_test \
|
||||
cleanable_test \
|
||||
column_family_test \
|
||||
table_properties_collector_test \
|
||||
arena_test \
|
||||
@@ -414,6 +414,7 @@ TESTS = \
|
||||
persistent_cache_test \
|
||||
statistics_test \
|
||||
lua_test \
|
||||
range_del_aggregator_test \
|
||||
lru_cache_test \
|
||||
|
||||
PARALLEL_TEST = \
|
||||
@@ -996,6 +997,9 @@ db_iterator_test: db/db_iterator_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHA
|
||||
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)
|
||||
|
||||
@@ -1139,6 +1143,9 @@ full_filter_block_test: table/full_filter_block_test.o $(LIBOBJECTS) $(TESTHARNE
|
||||
log_test: db/log_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
cleanable_test: table/cleanable_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
table_test: table/table_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1301,6 +1308,9 @@ lru_cache_test: util/lru_cache_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
lua_test: utilities/lua/rocks_lua_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
range_del_aggregator_test: db/range_del_aggregator_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
#-------------------------------------------------
|
||||
# make install related stuff
|
||||
INSTALL_PATH ?= /usr/local
|
||||
|
||||
@@ -7,7 +7,7 @@ At Facebook, we use RocksDB as storage engines in multiple data management servi
|
||||
2. MongoRocks -- https://github.com/mongodb-partners/mongo-rocks
|
||||
3. ZippyDB -- Facebook's distributed key-value store with Paxos-style replication, built on top of RocksDB.[*] https://www.youtube.com/watch?v=DfiN7pG0D0khtt
|
||||
4. Laser -- Laser is a high query throughput, low (millisecond) latency, key-value storage service built on top of RocksDB.[*]
|
||||
4. Dragan -- a distributed graph query engine. https://code.facebook.com/posts/1737605303120405/dragon-a-distributed-graph-query-engine/
|
||||
4. Dragon -- a distributed graph query engine. https://code.facebook.com/posts/1737605303120405/dragon-a-distributed-graph-query-engine/
|
||||
5. Stylus -- a low-level stream processing framework writtenin C++.[*]
|
||||
|
||||
[*] https://research.facebook.com/publications/realtime-data-processing-at-facebook/
|
||||
@@ -64,7 +64,7 @@ Pinterest's Object Retrieval System uses RocksDB for storage: https://www.youtub
|
||||
quasardb uses a heavily tuned RocksDB as its persistence layer.
|
||||
|
||||
## Netflix
|
||||
[Netflix](http://techblog.netflix.com/2016/05/application-data-caching-using-ssds.html) uses RocksDB on spinning disks to cache application data.
|
||||
[Netflix](http://techblog.netflix.com/2016/05/application-data-caching-using-ssds.html) Netflix uses RocksDB on AWS EC2 instances with local SSD drives to cache application data.
|
||||
|
||||
## TiKV
|
||||
[TiKV](https://github.com/pingcap/tikv) is a GEO-replicated, high-performance, distributed, transactional key-value database. TiKV is powered by Rust and Raft. TiKV uses RocksDB as its persistence layer.
|
||||
@@ -74,3 +74,6 @@ quasardb uses a heavily tuned RocksDB as its persistence layer.
|
||||
|
||||
## Dgraph
|
||||
[Dgraph](https://github.com/dgraph-io/dgraph) is an open-source, scalable, distributed, low latency, high throughput Graph database .They use RocksDB to store state locally on a machine.
|
||||
|
||||
## Uber
|
||||
[Uber](http://eng.uber.com/cherami/) uses RocksDB as a durable and and scalable task queue.
|
||||
@@ -21,7 +21,7 @@ function postURL($diffID, $url) {
|
||||
assert(is_numeric($diffID));
|
||||
assert(strlen($url) > 0);
|
||||
|
||||
$cmd = 'echo \'{"diff_id": "' . $diffID . '", '
|
||||
$cmd = 'echo \'{"diff_id": ' . $diffID . ', '
|
||||
. '"name":"click here for sandcastle tests for D' . $diffID . '", '
|
||||
. '"link":"' . $url . '"}\' | '
|
||||
. 'arc call-conduit '
|
||||
@@ -35,7 +35,7 @@ function buildUpdateTestStatusCmd($diffID, $test, $status) {
|
||||
assert(strlen($test) > 0);
|
||||
assert(strlen($status) > 0);
|
||||
|
||||
$cmd = 'echo \'{"diff_id": "' . $diffID . '", '
|
||||
$cmd = 'echo \'{"diff_id": ' . $diffID . ', '
|
||||
. '"name":"' . $test . '", '
|
||||
. '"result":"' . $status . '"}\' | '
|
||||
. 'arc call-conduit '
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
# for a certain interval.
|
||||
#
|
||||
|
||||
SRC_GIT_REPO=/data/git/rocksdb
|
||||
SRC_GIT_REPO=/data/git/rocksdb-public
|
||||
error=0
|
||||
|
||||
function log {
|
||||
|
||||
+19
-18
@@ -1,18 +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
|
||||
LUA_BASE=/mnt/gvfs/third-party2/lua/61e4abf5813bbc39bc4f548757ccfcadde175a48/5.2.3/gcc-4.9-glibc-2.20/690f0d7
|
||||
# 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
|
||||
@@ -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/"
|
||||
@@ -82,7 +86,7 @@ fi
|
||||
CFLAGS+=" -DTBB"
|
||||
|
||||
# use Intel SSE support for checksum calculations
|
||||
export USE_SSE=1
|
||||
export USE_SSE=" -msse -msse4.2 "
|
||||
|
||||
BINUTILS="$BINUTILS_BASE/bin"
|
||||
AR="$BINUTILS/ar"
|
||||
@@ -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,12 +149,4 @@ EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GF
|
||||
|
||||
VALGRIND_VER="$VALGRIND_BASE/bin/"
|
||||
|
||||
LUA_PATH="$LUA_BASE"
|
||||
|
||||
if test -z $PIC_BUILD; then
|
||||
LUA_LIB=" $LUA_PATH/lib/liblua.a"
|
||||
else
|
||||
LUA_LIB=" $LUA_PATH/lib/liblua_pic.a"
|
||||
fi
|
||||
|
||||
export CC CXX AR CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD LUA_PATH LUA_LIB
|
||||
export CC CXX AR CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD
|
||||
@@ -11,7 +11,8 @@ if ! which $CLANG_FORMAT_DIFF &> /dev/null
|
||||
then
|
||||
echo "You didn't have clang-format-diff.py available in your computer!"
|
||||
echo "You can download it by running: "
|
||||
echo " curl http://goo.gl/iUW1u2"
|
||||
echo " curl --location http://goo.gl/iUW1u2 -o ${CLANG_FORMAT_DIFF}"
|
||||
echo "and move ${CLANG_FORMAT_DIFF} to some directory within PATH=${PATH}"
|
||||
exit 128
|
||||
fi
|
||||
|
||||
@@ -50,14 +51,15 @@ fi
|
||||
set -e
|
||||
|
||||
uncommitted_code=`git diff HEAD`
|
||||
LAST_MASTER=`git merge-base master HEAD`
|
||||
|
||||
# If there's no uncommitted changes, we assume user are doing post-commit
|
||||
# format check, in which case we'll check the modified lines from latest commit.
|
||||
# Otherwise, we'll check format of the uncommitted code only.
|
||||
# format check, in which case we'll check the modified lines since last commit
|
||||
# from master. Otherwise, we'll check format of the uncommitted code only.
|
||||
if [ -z "$uncommitted_code" ]
|
||||
then
|
||||
# Check the format of last commit
|
||||
diffs=$(git diff -U0 HEAD^ | $CLANG_FORMAT_DIFF -p 1)
|
||||
diffs=$(git diff -U0 $LAST_MASTER^ | $CLANG_FORMAT_DIFF -p 1)
|
||||
else
|
||||
# Check the format of uncommitted lines,
|
||||
diffs=$(git diff -U0 HEAD | $CLANG_FORMAT_DIFF -p 1)
|
||||
@@ -97,7 +99,12 @@ then
|
||||
fi
|
||||
|
||||
# Do in-place format adjustment.
|
||||
git diff -U0 HEAD^ | $CLANG_FORMAT_DIFF -i -p 1
|
||||
if [ -z "$uncommitted_code" ]
|
||||
then
|
||||
git diff -U0 $LAST_MASTER^ | $CLANG_FORMAT_DIFF -i -p 1
|
||||
else
|
||||
git diff -U0 HEAD^ | $CLANG_FORMAT_DIFF -i -p 1
|
||||
fi
|
||||
echo "Files reformatted!"
|
||||
|
||||
# Amend to last commit if user do the post-commit format check
|
||||
|
||||
@@ -104,6 +104,9 @@ struct rocksdb_readoptions_t {
|
||||
};
|
||||
struct rocksdb_writeoptions_t { WriteOptions rep; };
|
||||
struct rocksdb_options_t { Options rep; };
|
||||
struct rocksdb_compactoptions_t {
|
||||
CompactRangeOptions rep;
|
||||
};
|
||||
struct rocksdb_block_based_table_options_t { BlockBasedTableOptions rep; };
|
||||
struct rocksdb_cuckoo_table_options_t { CuckooTableOptions rep; };
|
||||
struct rocksdb_seqfile_t { SequentialFile* rep; };
|
||||
@@ -445,7 +448,11 @@ rocksdb_backup_engine_t* rocksdb_backup_engine_open(
|
||||
const rocksdb_options_t* options, const char* path, char** errptr) {
|
||||
BackupEngine* be;
|
||||
if (SaveError(errptr, BackupEngine::Open(options->rep.env,
|
||||
BackupableDBOptions(path), &be))) {
|
||||
BackupableDBOptions(path,
|
||||
nullptr,
|
||||
true,
|
||||
options->rep.info_log.get()),
|
||||
&be))) {
|
||||
return nullptr;
|
||||
}
|
||||
rocksdb_backup_engine_t* result = new rocksdb_backup_engine_t;
|
||||
@@ -976,6 +983,30 @@ void rocksdb_compact_range_cf(
|
||||
(limit_key ? (b = Slice(limit_key, limit_key_len), &b) : nullptr));
|
||||
}
|
||||
|
||||
void rocksdb_compact_range_opt(rocksdb_t* db, rocksdb_compactoptions_t* opt,
|
||||
const char* start_key, size_t start_key_len,
|
||||
const char* limit_key, size_t limit_key_len) {
|
||||
Slice a, b;
|
||||
db->rep->CompactRange(
|
||||
opt->rep,
|
||||
// Pass nullptr Slice if corresponding "const char*" is nullptr
|
||||
(start_key ? (a = Slice(start_key, start_key_len), &a) : nullptr),
|
||||
(limit_key ? (b = Slice(limit_key, limit_key_len), &b) : nullptr));
|
||||
}
|
||||
|
||||
void rocksdb_compact_range_cf_opt(rocksdb_t* db,
|
||||
rocksdb_column_family_handle_t* column_family,
|
||||
rocksdb_compactoptions_t* opt,
|
||||
const char* start_key, size_t start_key_len,
|
||||
const char* limit_key, size_t limit_key_len) {
|
||||
Slice a, b;
|
||||
db->rep->CompactRange(
|
||||
opt->rep, column_family->rep,
|
||||
// Pass nullptr Slice if corresponding "const char*" is nullptr
|
||||
(start_key ? (a = Slice(start_key, start_key_len), &a) : nullptr),
|
||||
(limit_key ? (b = Slice(limit_key, limit_key_len), &b) : nullptr));
|
||||
}
|
||||
|
||||
void rocksdb_flush(
|
||||
rocksdb_t* db,
|
||||
const rocksdb_flushoptions_t* options,
|
||||
@@ -1223,6 +1254,53 @@ void rocksdb_writebatch_deletev_cf(
|
||||
b->rep.Delete(column_family->rep, SliceParts(key_slices.data(), num_keys));
|
||||
}
|
||||
|
||||
void rocksdb_writebatch_delete_range(rocksdb_writebatch_t* b,
|
||||
const char* start_key,
|
||||
size_t start_key_len, const char* end_key,
|
||||
size_t end_key_len) {
|
||||
b->rep.DeleteRange(Slice(start_key, start_key_len),
|
||||
Slice(end_key, end_key_len));
|
||||
}
|
||||
|
||||
void rocksdb_writebatch_delete_range_cf(
|
||||
rocksdb_writebatch_t* b, rocksdb_column_family_handle_t* column_family,
|
||||
const char* start_key, size_t start_key_len, const char* end_key,
|
||||
size_t end_key_len) {
|
||||
b->rep.DeleteRange(column_family->rep, Slice(start_key, start_key_len),
|
||||
Slice(end_key, end_key_len));
|
||||
}
|
||||
|
||||
void rocksdb_writebatch_delete_rangev(rocksdb_writebatch_t* b, int num_keys,
|
||||
const char* const* start_keys_list,
|
||||
const size_t* start_keys_list_sizes,
|
||||
const char* const* end_keys_list,
|
||||
const size_t* end_keys_list_sizes) {
|
||||
std::vector<Slice> start_key_slices(num_keys);
|
||||
std::vector<Slice> end_key_slices(num_keys);
|
||||
for (int i = 0; i < num_keys; i++) {
|
||||
start_key_slices[i] = Slice(start_keys_list[i], start_keys_list_sizes[i]);
|
||||
end_key_slices[i] = Slice(end_keys_list[i], end_keys_list_sizes[i]);
|
||||
}
|
||||
b->rep.DeleteRange(SliceParts(start_key_slices.data(), num_keys),
|
||||
SliceParts(end_key_slices.data(), num_keys));
|
||||
}
|
||||
|
||||
void rocksdb_writebatch_delete_rangev_cf(
|
||||
rocksdb_writebatch_t* b, rocksdb_column_family_handle_t* column_family,
|
||||
int num_keys, const char* const* start_keys_list,
|
||||
const size_t* start_keys_list_sizes, const char* const* end_keys_list,
|
||||
const size_t* end_keys_list_sizes) {
|
||||
std::vector<Slice> start_key_slices(num_keys);
|
||||
std::vector<Slice> end_key_slices(num_keys);
|
||||
for (int i = 0; i < num_keys; i++) {
|
||||
start_key_slices[i] = Slice(start_keys_list[i], start_keys_list_sizes[i]);
|
||||
end_key_slices[i] = Slice(end_keys_list[i], end_keys_list_sizes[i]);
|
||||
}
|
||||
b->rep.DeleteRange(column_family->rep,
|
||||
SliceParts(start_key_slices.data(), num_keys),
|
||||
SliceParts(end_key_slices.data(), num_keys));
|
||||
}
|
||||
|
||||
void rocksdb_writebatch_put_log_data(
|
||||
rocksdb_writebatch_t* b,
|
||||
const char* blob, size_t len) {
|
||||
@@ -1645,9 +1723,14 @@ void rocksdb_options_set_manifest_preallocation_size(
|
||||
void rocksdb_options_set_purge_redundant_kvs_while_flush(rocksdb_options_t* opt,
|
||||
unsigned char v) {}
|
||||
|
||||
void rocksdb_options_set_allow_os_buffer(rocksdb_options_t* opt,
|
||||
unsigned char v) {
|
||||
opt->rep.allow_os_buffer = v;
|
||||
void rocksdb_options_set_use_direct_reads(rocksdb_options_t* opt,
|
||||
unsigned char v) {
|
||||
opt->rep.use_direct_reads = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_use_direct_writes(rocksdb_options_t* opt,
|
||||
unsigned char v) {
|
||||
opt->rep.use_direct_writes = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_allow_mmap_reads(
|
||||
@@ -1926,7 +2009,7 @@ rocksdb_ratelimiter_t* rocksdb_ratelimiter_create(
|
||||
|
||||
void rocksdb_ratelimiter_destroy(rocksdb_ratelimiter_t *limiter) {
|
||||
if (limiter->rep) {
|
||||
delete limiter->rep;
|
||||
delete limiter->rep;
|
||||
}
|
||||
delete limiter;
|
||||
}
|
||||
@@ -2168,6 +2251,16 @@ void rocksdb_readoptions_set_readahead_size(
|
||||
opt->rep.readahead_size = v;
|
||||
}
|
||||
|
||||
void rocksdb_readoptions_set_pin_data(rocksdb_readoptions_t* opt,
|
||||
unsigned char v) {
|
||||
opt->rep.pin_data = v;
|
||||
}
|
||||
|
||||
void rocksdb_readoptions_set_total_order_seek(rocksdb_readoptions_t* opt,
|
||||
unsigned char v) {
|
||||
opt->rep.total_order_seek = v;
|
||||
}
|
||||
|
||||
rocksdb_writeoptions_t* rocksdb_writeoptions_create() {
|
||||
return new rocksdb_writeoptions_t;
|
||||
}
|
||||
@@ -2185,6 +2278,28 @@ void rocksdb_writeoptions_disable_WAL(rocksdb_writeoptions_t* opt, int disable)
|
||||
opt->rep.disableWAL = disable;
|
||||
}
|
||||
|
||||
rocksdb_compactoptions_t* rocksdb_compactoptions_create() {
|
||||
return new rocksdb_compactoptions_t;
|
||||
}
|
||||
|
||||
void rocksdb_compactoptions_destroy(rocksdb_compactoptions_t* opt) {
|
||||
delete opt;
|
||||
}
|
||||
|
||||
void rocksdb_compactoptions_set_exclusive_manual_compaction(
|
||||
rocksdb_compactoptions_t* opt, unsigned char v) {
|
||||
opt->rep.exclusive_manual_compaction = v;
|
||||
}
|
||||
|
||||
void rocksdb_compactoptions_set_change_level(rocksdb_compactoptions_t* opt,
|
||||
unsigned char v) {
|
||||
opt->rep.change_level = v;
|
||||
}
|
||||
|
||||
void rocksdb_compactoptions_set_target_level(rocksdb_compactoptions_t* opt,
|
||||
int n) {
|
||||
opt->rep.target_level = n;
|
||||
}
|
||||
|
||||
rocksdb_flushoptions_t* rocksdb_flushoptions_create() {
|
||||
return new rocksdb_flushoptions_t;
|
||||
@@ -2213,6 +2328,14 @@ void rocksdb_cache_set_capacity(rocksdb_cache_t* cache, size_t capacity) {
|
||||
cache->rep->SetCapacity(capacity);
|
||||
}
|
||||
|
||||
size_t rocksdb_cache_get_usage(rocksdb_cache_t* cache) {
|
||||
return cache->rep->GetUsage();
|
||||
}
|
||||
|
||||
size_t rocksdb_cache_get_pinned_usage(rocksdb_cache_t* cache) {
|
||||
return cache->rep->GetPinnedUsage();
|
||||
}
|
||||
|
||||
rocksdb_env_t* rocksdb_create_default_env() {
|
||||
rocksdb_env_t* result = new rocksdb_env_t;
|
||||
result->rep = Env::Default();
|
||||
|
||||
+128
-56
@@ -288,6 +288,7 @@ int main(int argc, char** argv) {
|
||||
rocksdb_cache_t* cache;
|
||||
rocksdb_env_t* env;
|
||||
rocksdb_options_t* options;
|
||||
rocksdb_compactoptions_t* coptions;
|
||||
rocksdb_block_based_table_options_t* table_options;
|
||||
rocksdb_readoptions_t* roptions;
|
||||
rocksdb_writeoptions_t* woptions;
|
||||
@@ -339,11 +340,14 @@ int main(int argc, char** argv) {
|
||||
|
||||
roptions = rocksdb_readoptions_create();
|
||||
rocksdb_readoptions_set_verify_checksums(roptions, 1);
|
||||
rocksdb_readoptions_set_fill_cache(roptions, 0);
|
||||
rocksdb_readoptions_set_fill_cache(roptions, 1);
|
||||
|
||||
woptions = rocksdb_writeoptions_create();
|
||||
rocksdb_writeoptions_set_sync(woptions, 1);
|
||||
|
||||
coptions = rocksdb_compactoptions_create();
|
||||
rocksdb_compactoptions_set_exclusive_manual_compaction(coptions, 1);
|
||||
|
||||
StartPhase("destroy");
|
||||
rocksdb_destroy_db(options, dbname, &err);
|
||||
Free(&err);
|
||||
@@ -425,6 +429,95 @@ int main(int argc, char** argv) {
|
||||
rocksdb_compact_range(db, "a", 1, "z", 1);
|
||||
CheckGet(db, roptions, "foo", "hello");
|
||||
|
||||
StartPhase("compactallopt");
|
||||
rocksdb_compact_range_opt(db, coptions, NULL, 0, NULL, 0);
|
||||
CheckGet(db, roptions, "foo", "hello");
|
||||
|
||||
StartPhase("compactrangeopt");
|
||||
rocksdb_compact_range_opt(db, coptions, "a", 1, "z", 1);
|
||||
CheckGet(db, roptions, "foo", "hello");
|
||||
|
||||
// Simple check cache usage
|
||||
StartPhase("cache_usage");
|
||||
{
|
||||
rocksdb_readoptions_set_pin_data(roptions, 1);
|
||||
rocksdb_iterator_t* iter = rocksdb_create_iterator(db, roptions);
|
||||
rocksdb_iter_seek(iter, "foo", 3);
|
||||
|
||||
size_t usage = rocksdb_cache_get_usage(cache);
|
||||
CheckCondition(usage > 0);
|
||||
|
||||
size_t pin_usage = rocksdb_cache_get_pinned_usage(cache);
|
||||
CheckCondition(pin_usage > 0);
|
||||
|
||||
rocksdb_iter_next(iter);
|
||||
rocksdb_iter_destroy(iter);
|
||||
rocksdb_readoptions_set_pin_data(roptions, 0);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// Delete all keys we just ingested
|
||||
rocksdb_delete(db, woptions, "sstk1", 5, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_delete(db, woptions, "sstk2", 5, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_delete(db, woptions, "sstk22", 6, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_delete(db, woptions, "sstk3", 5, &err);
|
||||
CheckNoError(err);
|
||||
}
|
||||
|
||||
StartPhase("writebatch");
|
||||
{
|
||||
rocksdb_writebatch_t* wb = rocksdb_writebatch_create();
|
||||
@@ -441,6 +534,24 @@ int main(int argc, char** argv) {
|
||||
int pos = 0;
|
||||
rocksdb_writebatch_iterate(wb, &pos, CheckPut, CheckDel);
|
||||
CheckCondition(pos == 3);
|
||||
rocksdb_writebatch_clear(wb);
|
||||
rocksdb_writebatch_put(wb, "bar", 3, "b", 1);
|
||||
rocksdb_writebatch_put(wb, "bay", 3, "d", 1);
|
||||
rocksdb_writebatch_delete_range(wb, "bar", 3, "bay", 3);
|
||||
rocksdb_write(db, woptions, wb, &err);
|
||||
CheckNoError(err);
|
||||
CheckGet(db, roptions, "bar", NULL);
|
||||
CheckGet(db, roptions, "bay", "d");
|
||||
rocksdb_writebatch_clear(wb);
|
||||
const char* start_list[1] = {"bay"};
|
||||
const size_t start_sizes[1] = {3};
|
||||
const char* end_list[1] = {"baz"};
|
||||
const size_t end_sizes[1] = {3};
|
||||
rocksdb_writebatch_delete_rangev(wb, 1, start_list, start_sizes, end_list,
|
||||
end_sizes);
|
||||
rocksdb_write(db, woptions, wb, &err);
|
||||
CheckNoError(err);
|
||||
CheckGet(db, roptions, "bay", NULL);
|
||||
rocksdb_writebatch_destroy(wb);
|
||||
}
|
||||
|
||||
@@ -579,59 +690,6 @@ 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
|
||||
@@ -765,9 +823,9 @@ int main(int argc, char** argv) {
|
||||
{
|
||||
rocksdb_close(db);
|
||||
rocksdb_destroy_db(options, dbname, &err);
|
||||
CheckNoError(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)
|
||||
@@ -931,6 +989,19 @@ int main(int argc, char** argv) {
|
||||
CheckNoError(err);
|
||||
rocksdb_iter_destroy(iter);
|
||||
|
||||
rocksdb_readoptions_set_total_order_seek(roptions, 1);
|
||||
iter = rocksdb_create_iterator(db, roptions);
|
||||
CheckCondition(!rocksdb_iter_valid(iter));
|
||||
|
||||
rocksdb_iter_seek(iter, "ba", 2);
|
||||
rocksdb_iter_get_error(iter, &err);
|
||||
CheckNoError(err);
|
||||
CheckCondition(rocksdb_iter_valid(iter));
|
||||
CheckIter(iter, "bar1", "bar");
|
||||
|
||||
rocksdb_iter_destroy(iter);
|
||||
rocksdb_readoptions_set_total_order_seek(roptions, 0);
|
||||
|
||||
rocksdb_close(db);
|
||||
rocksdb_destroy_db(options, dbname, &err);
|
||||
}
|
||||
@@ -1040,6 +1111,7 @@ int main(int argc, char** argv) {
|
||||
rocksdb_block_based_options_destroy(table_options);
|
||||
rocksdb_readoptions_destroy(roptions);
|
||||
rocksdb_writeoptions_destroy(woptions);
|
||||
rocksdb_compactoptions_destroy(coptions);
|
||||
rocksdb_cache_destroy(cache);
|
||||
rocksdb_comparator_destroy(cmp);
|
||||
rocksdb_env_destroy(env);
|
||||
|
||||
+21
-1
@@ -370,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.
|
||||
@@ -492,6 +493,25 @@ ColumnFamilyOptions ColumnFamilyData::GetLatestCFOptions() const {
|
||||
return BuildColumnFamilyOptions(initial_cf_options_, mutable_cf_options_);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -239,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);
|
||||
@@ -404,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
|
||||
|
||||
@@ -253,6 +253,61 @@ TEST_F(CompactFilesTest, CapturingPendingFiles) {
|
||||
delete db;
|
||||
}
|
||||
|
||||
TEST_F(CompactFilesTest, CompactionFilterWithGetSv) {
|
||||
class FilterWithGet : public CompactionFilter {
|
||||
public:
|
||||
virtual bool Filter(int level, const Slice& key, const Slice& value,
|
||||
std::string* new_value,
|
||||
bool* value_changed) const override {
|
||||
if (db_ == nullptr) {
|
||||
return true;
|
||||
}
|
||||
std::string res;
|
||||
db_->Get(ReadOptions(), "", &res);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SetDB(DB* db) {
|
||||
db_ = db;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "FilterWithGet"; }
|
||||
|
||||
private:
|
||||
DB* db_;
|
||||
};
|
||||
|
||||
|
||||
std::shared_ptr<FilterWithGet> cf(new FilterWithGet());
|
||||
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.compaction_filter = cf.get();
|
||||
|
||||
DB* db = nullptr;
|
||||
DestroyDB(db_name_, options);
|
||||
Status s = DB::Open(options, db_name_, &db);
|
||||
ASSERT_OK(s);
|
||||
|
||||
cf->SetDB(db);
|
||||
|
||||
// Write one L0 file
|
||||
db->Put(WriteOptions(), "K1", "V1");
|
||||
db->Flush(FlushOptions());
|
||||
|
||||
// Compact all L0 files using CompactFiles
|
||||
rocksdb::ColumnFamilyMetaData meta;
|
||||
db->GetColumnFamilyMetaData(&meta);
|
||||
for (auto& file : meta.levels[0].files) {
|
||||
std::string fname = file.db_path + "/" + file.name;
|
||||
ASSERT_OK(
|
||||
db->CompactFiles(rocksdb::CompactionOptions(), {fname}, 0));
|
||||
}
|
||||
|
||||
|
||||
delete db;
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+34
-17
@@ -150,7 +150,8 @@ Compaction::Compaction(VersionStorageInfo* vstorage,
|
||||
bool _manual_compaction, double _score,
|
||||
bool _deletion_compaction,
|
||||
CompactionReason _compaction_reason)
|
||||
: start_level_(_inputs[0].level),
|
||||
: input_vstorage_(vstorage),
|
||||
start_level_(_inputs[0].level),
|
||||
output_level_(_output_level),
|
||||
max_output_file_size_(_target_file_size),
|
||||
max_compaction_bytes_(_max_compaction_bytes),
|
||||
@@ -204,11 +205,10 @@ Compaction::~Compaction() {
|
||||
}
|
||||
|
||||
bool Compaction::InputCompressionMatchesOutput() const {
|
||||
VersionStorageInfo* vstorage = input_version_->storage_info();
|
||||
int base_level = vstorage->base_level();
|
||||
bool matches =
|
||||
(GetCompressionType(*cfd_->ioptions(), vstorage, mutable_cf_options_,
|
||||
start_level_, base_level) == output_compression_);
|
||||
int base_level = input_vstorage_->base_level();
|
||||
bool matches = (GetCompressionType(immutable_cf_options_, input_vstorage_,
|
||||
mutable_cf_options_, start_level_,
|
||||
base_level) == output_compression_);
|
||||
if (matches) {
|
||||
TEST_SYNC_POINT("Compaction::InputCompressionMatchesOutput:Matches");
|
||||
return true;
|
||||
@@ -225,15 +225,14 @@ bool Compaction::IsTrivialMove() const {
|
||||
// filter to be applied to that level, and thus cannot be a trivial move.
|
||||
|
||||
// Check if start level have files with overlapping ranges
|
||||
if (start_level_ == 0 &&
|
||||
input_version_->storage_info()->level0_non_overlapping() == false) {
|
||||
if (start_level_ == 0 && input_vstorage_->level0_non_overlapping() == false) {
|
||||
// We cannot move files from L0 to L1 if the files are overlapping
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_manual_compaction_ &&
|
||||
(cfd_->ioptions()->compaction_filter != nullptr ||
|
||||
cfd_->ioptions()->compaction_filter_factory != nullptr)) {
|
||||
(immutable_cf_options_.compaction_filter != nullptr ||
|
||||
immutable_cf_options_.compaction_filter_factory != nullptr)) {
|
||||
// This is a manual compaction and we have a compaction filter that should
|
||||
// be executed, we cannot do a trivial move
|
||||
return false;
|
||||
@@ -241,15 +240,34 @@ bool Compaction::IsTrivialMove() const {
|
||||
|
||||
// Used in universal compaction, where trivial move can be done if the
|
||||
// input files are non overlapping
|
||||
if ((cfd_->ioptions()->compaction_options_universal.allow_trivial_move) &&
|
||||
if ((immutable_cf_options_.compaction_options_universal.allow_trivial_move) &&
|
||||
(output_level_ != 0)) {
|
||||
return is_trivial_move_;
|
||||
}
|
||||
|
||||
return (start_level_ != output_level_ && num_input_levels() == 1 &&
|
||||
if (!(start_level_ != output_level_ && num_input_levels() == 1 &&
|
||||
input(0, 0)->fd.GetPathId() == output_path_id() &&
|
||||
InputCompressionMatchesOutput() &&
|
||||
TotalFileSize(grandparents_) <= max_compaction_bytes_);
|
||||
InputCompressionMatchesOutput())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// assert inputs_.size() == 1
|
||||
|
||||
for (const auto& file : inputs_.front().files) {
|
||||
std::vector<FileMetaData*> file_grand_parents;
|
||||
if (output_level_ + 1 >= number_levels_) {
|
||||
continue;
|
||||
}
|
||||
input_vstorage_->GetOverlappingInputs(output_level_ + 1, &file->smallest,
|
||||
&file->largest, &file_grand_parents);
|
||||
const auto compaction_size =
|
||||
file->fd.GetFileSize() + TotalFileSize(file_grand_parents);
|
||||
if (compaction_size > max_compaction_bytes_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Compaction::AddInputDeletions(VersionEdit* out_edit) {
|
||||
@@ -272,8 +290,7 @@ bool Compaction::KeyNotExistsBeyondOutputLevel(
|
||||
// Maybe use binary search to find right entry instead of linear search?
|
||||
const Comparator* user_cmp = cfd_->user_comparator();
|
||||
for (int lvl = output_level_ + 1; lvl < number_levels_; lvl++) {
|
||||
const std::vector<FileMetaData*>& files =
|
||||
input_version_->storage_info()->LevelFiles(lvl);
|
||||
const std::vector<FileMetaData*>& files = input_vstorage_->LevelFiles(lvl);
|
||||
for (; level_ptrs->at(lvl) < files.size(); level_ptrs->at(lvl)++) {
|
||||
auto* f = files[level_ptrs->at(lvl)];
|
||||
if (user_cmp->Compare(user_key, f->largest.user_key()) <= 0) {
|
||||
@@ -345,7 +362,7 @@ void Compaction::ReleaseCompactionFiles(Status status) {
|
||||
|
||||
void Compaction::ResetNextCompactionIndex() {
|
||||
assert(input_version_ != nullptr);
|
||||
input_version_->storage_info()->ResetNextCompactionIndex(start_level_);
|
||||
input_vstorage_->ResetNextCompactionIndex(start_level_);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -258,6 +258,8 @@ class Compaction {
|
||||
static bool IsFullCompaction(VersionStorageInfo* vstorage,
|
||||
const std::vector<CompactionInputFiles>& inputs);
|
||||
|
||||
VersionStorageInfo* input_vstorage_;
|
||||
|
||||
const int start_level_; // the lowest level to be compacted
|
||||
const int output_level_; // levels to which output files are stored
|
||||
uint64_t max_output_file_size_;
|
||||
|
||||
+23
-12
@@ -16,14 +16,14 @@ CompactionIterator::CompactionIterator(
|
||||
SequenceNumber earliest_write_conflict_snapshot, Env* env,
|
||||
bool expect_valid_internal_key, RangeDelAggregator* range_del_agg,
|
||||
const Compaction* compaction, const CompactionFilter* compaction_filter,
|
||||
LogBuffer* log_buffer)
|
||||
const std::atomic<bool>* shutting_down)
|
||||
: 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) {}
|
||||
compaction_filter, shutting_down) {}
|
||||
|
||||
CompactionIterator::CompactionIterator(
|
||||
InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
|
||||
@@ -31,7 +31,8 @@ CompactionIterator::CompactionIterator(
|
||||
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)
|
||||
const CompactionFilter* compaction_filter,
|
||||
const std::atomic<bool>* shutting_down)
|
||||
: input_(input),
|
||||
cmp_(cmp),
|
||||
merge_helper_(merge_helper),
|
||||
@@ -42,7 +43,7 @@ CompactionIterator::CompactionIterator(
|
||||
range_del_agg_(range_del_agg),
|
||||
compaction_(std::move(compaction)),
|
||||
compaction_filter_(compaction_filter),
|
||||
log_buffer_(log_buffer),
|
||||
shutting_down_(shutting_down),
|
||||
merge_out_iter_(merge_helper_) {
|
||||
assert(compaction_filter_ == nullptr || compaction_ != nullptr);
|
||||
bottommost_level_ =
|
||||
@@ -136,7 +137,7 @@ void CompactionIterator::NextFromInput() {
|
||||
at_next_ = false;
|
||||
valid_ = false;
|
||||
|
||||
while (!valid_ && input_->Valid()) {
|
||||
while (!valid_ && input_->Valid() && !IsShuttingDown()) {
|
||||
key_ = input_->key();
|
||||
value_ = input_->value();
|
||||
iter_stats_.num_input_records++;
|
||||
@@ -217,7 +218,8 @@ void CompactionIterator::NextFromInput() {
|
||||
}
|
||||
|
||||
if (filter == CompactionFilter::Decision::kRemove) {
|
||||
// convert the current key to a delete
|
||||
// convert the current key to a delete; key_ is pointing into
|
||||
// current_key_ at this point, so updating current_key_ updates key()
|
||||
ikey_.type = kTypeDeletion;
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, kTypeDeletion);
|
||||
// no value associated with delete
|
||||
@@ -422,7 +424,6 @@ void CompactionIterator::NextFromInput() {
|
||||
input_->Next();
|
||||
} else if (ikey_.type == kTypeMerge) {
|
||||
if (!merge_helper_->HasOperator()) {
|
||||
LogToBuffer(log_buffer_, "Options::merge_operator is null.");
|
||||
status_ = Status::InvalidArgument(
|
||||
"merge_operator is not properly initialized.");
|
||||
return;
|
||||
@@ -433,12 +434,13 @@ void CompactionIterator::NextFromInput() {
|
||||
// have hit (A)
|
||||
// We encapsulate the merge related state machine in a different
|
||||
// object to minimize change to the existing flow.
|
||||
merge_helper_->MergeUntil(input_, range_del_agg_, prev_snapshot,
|
||||
bottommost_level_);
|
||||
Status s = merge_helper_->MergeUntil(input_, range_del_agg_,
|
||||
prev_snapshot, bottommost_level_);
|
||||
merge_out_iter_.SeekToFirst();
|
||||
|
||||
if (merge_helper_->FilteredUntil(&skip_until)) {
|
||||
need_skip = true;
|
||||
if (!s.ok() && !s.IsMergeInProgress()) {
|
||||
status_ = s;
|
||||
return;
|
||||
} else if (merge_out_iter_.Valid()) {
|
||||
// NOTE: key, value, and ikey_ refer to old entries.
|
||||
// These will be correctly set below.
|
||||
@@ -460,11 +462,16 @@ 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_);
|
||||
bool should_delete = range_del_agg_->ShouldDelete(
|
||||
key_, RangeDelAggregator::RangePositioningMode::kForwardTraversal);
|
||||
if (should_delete) {
|
||||
++iter_stats_.num_record_drop_hidden;
|
||||
++iter_stats_.num_record_drop_range_del;
|
||||
@@ -478,6 +485,10 @@ void CompactionIterator::NextFromInput() {
|
||||
input_->Seek(skip_until);
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid_ && IsShuttingDown()) {
|
||||
status_ = Status::ShutdownInProgress();
|
||||
}
|
||||
}
|
||||
|
||||
void CompactionIterator::PrepareOutput() {
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include "db/pinned_iterators_manager.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "rocksdb/compaction_filter.h"
|
||||
#include "util/log_buffer.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -61,7 +60,7 @@ class CompactionIterator {
|
||||
RangeDelAggregator* range_del_agg,
|
||||
const Compaction* compaction = nullptr,
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
LogBuffer* log_buffer = nullptr);
|
||||
const std::atomic<bool>* shutting_down = nullptr);
|
||||
|
||||
// Constructor with custom CompactionProxy, used for tests.
|
||||
CompactionIterator(InternalIterator* input, const Comparator* cmp,
|
||||
@@ -72,7 +71,7 @@ class CompactionIterator {
|
||||
RangeDelAggregator* range_del_agg,
|
||||
std::unique_ptr<CompactionProxy> compaction,
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
LogBuffer* log_buffer = nullptr);
|
||||
const std::atomic<bool>* shutting_down = nullptr);
|
||||
|
||||
~CompactionIterator();
|
||||
|
||||
@@ -125,7 +124,7 @@ class CompactionIterator {
|
||||
RangeDelAggregator* range_del_agg_;
|
||||
std::unique_ptr<CompactionProxy> compaction_;
|
||||
const CompactionFilter* compaction_filter_;
|
||||
LogBuffer* log_buffer_;
|
||||
const std::atomic<bool>* shutting_down_;
|
||||
bool bottommost_level_;
|
||||
bool valid_ = false;
|
||||
bool visible_at_tip_;
|
||||
@@ -180,5 +179,10 @@ class CompactionIterator {
|
||||
// is in or beyond the last file checked during the previous call
|
||||
std::vector<size_t> level_ptrs_;
|
||||
CompactionIterationStats iter_stats_;
|
||||
|
||||
bool IsShuttingDown() {
|
||||
// This is a best-effort facility, so memory_order_relaxed is sufficient.
|
||||
return shutting_down_ && shutting_down_->load(std::memory_order_relaxed);
|
||||
}
|
||||
};
|
||||
} // namespace rocksdb
|
||||
+144
-29
@@ -13,6 +13,64 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// Expects no merging attempts.
|
||||
class NoMergingMergeOp : 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 NoMergingMergeOp";
|
||||
}
|
||||
};
|
||||
|
||||
// Compaction filter that gets stuck when it sees a particular key,
|
||||
// then gets unstuck when told to.
|
||||
// Always returns Decition::kRemove.
|
||||
class StallingFilter : public CompactionFilter {
|
||||
public:
|
||||
virtual Decision FilterV2(int level, const Slice& key, ValueType t,
|
||||
const Slice& existing_value, std::string* new_value,
|
||||
std::string* skip_until) const override {
|
||||
int k = std::atoi(key.ToString().c_str());
|
||||
last_seen.store(k);
|
||||
while (k >= stall_at.load()) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
return Decision::kRemove;
|
||||
}
|
||||
|
||||
const char* Name() const override {
|
||||
return "CompactionIteratorTest StallingFilter";
|
||||
}
|
||||
|
||||
// Wait until the filter sees a key >= k and stalls at that key.
|
||||
// If `exact`, asserts that the seen key is equal to k.
|
||||
void WaitForStall(int k, bool exact = true) {
|
||||
stall_at.store(k);
|
||||
while (last_seen.load() < k) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
if (exact) {
|
||||
EXPECT_EQ(k, last_seen.load());
|
||||
}
|
||||
}
|
||||
|
||||
// Filter will stall on key >= stall_at. Advance stall_at to unstall.
|
||||
mutable std::atomic<int> stall_at{0};
|
||||
// Last key the filter was called with.
|
||||
mutable std::atomic<int> last_seen{0};
|
||||
};
|
||||
|
||||
class LoggingForwardVectorIterator : public InternalIterator {
|
||||
public:
|
||||
struct Action {
|
||||
@@ -88,13 +146,15 @@ class FakeCompaction : public CompactionIterator::CompactionProxy {
|
||||
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;
|
||||
return key_not_exists_beyond_output_level;
|
||||
}
|
||||
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";
|
||||
}
|
||||
|
||||
bool key_not_exists_beyond_output_level = false;
|
||||
};
|
||||
|
||||
class CompactionIteratorTest : public testing::Test {
|
||||
@@ -116,17 +176,19 @@ class CompactionIteratorTest : public testing::Test {
|
||||
|
||||
std::unique_ptr<CompactionIterator::CompactionProxy> compaction;
|
||||
if (filter) {
|
||||
compaction.reset(new FakeCompaction());
|
||||
compaction_proxy_ = new FakeCompaction();
|
||||
compaction.reset(compaction_proxy_);
|
||||
}
|
||||
|
||||
merge_helper_.reset(new MergeHelper(Env::Default(), cmp_, merge_op, filter,
|
||||
nullptr, 0U, false, 0));
|
||||
nullptr, 0U, false, 0, 0, nullptr,
|
||||
&shutting_down_));
|
||||
iter_.reset(new LoggingForwardVectorIterator(ks, vs));
|
||||
iter_->SeekToFirst();
|
||||
c_iter_.reset(new CompactionIterator(
|
||||
iter_.get(), cmp_, merge_helper_.get(), last_sequence, &snapshots_,
|
||||
kMaxSequenceNumber, Env::Default(), false, range_del_agg_.get(),
|
||||
std::move(compaction), filter));
|
||||
std::move(compaction), filter, &shutting_down_));
|
||||
}
|
||||
|
||||
void AddSnapshot(SequenceNumber snapshot) { snapshots_.push_back(snapshot); }
|
||||
@@ -138,6 +200,8 @@ class CompactionIteratorTest : public testing::Test {
|
||||
std::unique_ptr<LoggingForwardVectorIterator> iter_;
|
||||
std::unique_ptr<CompactionIterator> c_iter_;
|
||||
std::unique_ptr<RangeDelAggregator> range_del_agg_;
|
||||
std::atomic<bool> shutting_down_{false};
|
||||
FakeCompaction* compaction_proxy_;
|
||||
};
|
||||
|
||||
// It is possible that the output of the compaction iterator is empty even if
|
||||
@@ -209,26 +273,6 @@ TEST_F(CompactionIteratorTest, RangeDeletionWithSnapshots) {
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -272,8 +316,8 @@ TEST_F(CompactionIteratorTest, CompactionFilterSkipUntil) {
|
||||
return Decision::kKeep;
|
||||
}
|
||||
if (k == "i") {
|
||||
EXPECT_EQ(ValueType::kValue, t);
|
||||
EXPECT_EQ("iv95", v);
|
||||
EXPECT_EQ(ValueType::kMergeOperand, t);
|
||||
EXPECT_EQ("im95", v);
|
||||
*skip_until = "z";
|
||||
return Decision::kRemoveAndSkipUntil;
|
||||
}
|
||||
@@ -286,7 +330,7 @@ TEST_F(CompactionIteratorTest, CompactionFilterSkipUntil) {
|
||||
}
|
||||
};
|
||||
|
||||
MergeOp merge_op;
|
||||
NoMergingMergeOp merge_op;
|
||||
Filter filter;
|
||||
InitIterators(
|
||||
{test::KeyStr("a", 50, kTypeValue), // keep
|
||||
@@ -299,10 +343,10 @@ TEST_F(CompactionIteratorTest, CompactionFilterSkipUntil) {
|
||||
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, kTypeValue), // skip to "z"
|
||||
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", "iv95", "jv99"},
|
||||
"fv25", "gv90", "hv91", "im95", "jv99"},
|
||||
{}, {}, kMaxSequenceNumber, &merge_op, &filter);
|
||||
|
||||
// Compaction should output just "a", "e" and "h" keys.
|
||||
@@ -338,6 +382,77 @@ TEST_F(CompactionIteratorTest, CompactionFilterSkipUntil) {
|
||||
ASSERT_EQ(expected_actions, iter_->log);
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorTest, ShuttingDownInFilter) {
|
||||
NoMergingMergeOp merge_op;
|
||||
StallingFilter filter;
|
||||
InitIterators(
|
||||
{test::KeyStr("1", 1, kTypeValue), test::KeyStr("2", 2, kTypeValue),
|
||||
test::KeyStr("3", 3, kTypeValue), test::KeyStr("4", 4, kTypeValue)},
|
||||
{"v1", "v2", "v3", "v4"}, {}, {}, kMaxSequenceNumber, &merge_op, &filter);
|
||||
// Don't leave tombstones (kTypeDeletion) for filtered keys.
|
||||
compaction_proxy_->key_not_exists_beyond_output_level = true;
|
||||
|
||||
std::atomic<bool> seek_done{false};
|
||||
std::thread compaction_thread([&] {
|
||||
c_iter_->SeekToFirst();
|
||||
EXPECT_FALSE(c_iter_->Valid());
|
||||
EXPECT_TRUE(c_iter_->status().IsShutdownInProgress());
|
||||
seek_done.store(true);
|
||||
});
|
||||
|
||||
// Let key 1 through.
|
||||
filter.WaitForStall(1);
|
||||
|
||||
// Shutdown during compaction filter call for key 2.
|
||||
filter.WaitForStall(2);
|
||||
shutting_down_.store(true);
|
||||
EXPECT_FALSE(seek_done.load());
|
||||
|
||||
// Unstall filter and wait for SeekToFirst() to return.
|
||||
filter.stall_at.store(3);
|
||||
compaction_thread.join();
|
||||
assert(seek_done.load());
|
||||
|
||||
// Check that filter was never called again.
|
||||
EXPECT_EQ(2, filter.last_seen.load());
|
||||
}
|
||||
|
||||
// Same as ShuttingDownInFilter, but shutdown happens during filter call for
|
||||
// a merge operand, not for a value.
|
||||
TEST_F(CompactionIteratorTest, ShuttingDownInMerge) {
|
||||
NoMergingMergeOp merge_op;
|
||||
StallingFilter filter;
|
||||
InitIterators(
|
||||
{test::KeyStr("1", 1, kTypeValue), test::KeyStr("2", 2, kTypeMerge),
|
||||
test::KeyStr("3", 3, kTypeMerge), test::KeyStr("4", 4, kTypeValue)},
|
||||
{"v1", "v2", "v3", "v4"}, {}, {}, kMaxSequenceNumber, &merge_op, &filter);
|
||||
compaction_proxy_->key_not_exists_beyond_output_level = true;
|
||||
|
||||
std::atomic<bool> seek_done{false};
|
||||
std::thread compaction_thread([&] {
|
||||
c_iter_->SeekToFirst();
|
||||
ASSERT_FALSE(c_iter_->Valid());
|
||||
ASSERT_TRUE(c_iter_->status().IsShutdownInProgress());
|
||||
seek_done.store(true);
|
||||
});
|
||||
|
||||
// Let key 1 through.
|
||||
filter.WaitForStall(1);
|
||||
|
||||
// Shutdown during compaction filter call for key 2.
|
||||
filter.WaitForStall(2);
|
||||
shutting_down_.store(true);
|
||||
EXPECT_FALSE(seek_done.load());
|
||||
|
||||
// Unstall filter and wait for SeekToFirst() to return.
|
||||
filter.stall_at.store(3);
|
||||
compaction_thread.join();
|
||||
assert(seek_done.load());
|
||||
|
||||
// Check that filter was never called again.
|
||||
EXPECT_EQ(2, filter.last_seen.load());
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+34
-22
@@ -264,7 +264,7 @@ void CompactionJob::AggregateStatistics() {
|
||||
CompactionJob::CompactionJob(
|
||||
int job_id, Compaction* compaction, const ImmutableDBOptions& db_options,
|
||||
const EnvOptions& env_options, VersionSet* versions,
|
||||
std::atomic<bool>* shutting_down, LogBuffer* log_buffer,
|
||||
const std::atomic<bool>* shutting_down, LogBuffer* log_buffer,
|
||||
Directory* db_directory, Directory* output_directory, Statistics* stats,
|
||||
InstrumentedMutex* db_mutex, Status* db_bg_error,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
@@ -724,7 +724,8 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
mutable_cf_options->min_partial_merge_operands,
|
||||
false /* internal key corruption is expected */,
|
||||
existing_snapshots_.empty() ? 0 : existing_snapshots_.back(),
|
||||
compact_->compaction->level(), db_options_.statistics.get());
|
||||
compact_->compaction->level(), db_options_.statistics.get(),
|
||||
shutting_down_);
|
||||
|
||||
TEST_SYNC_POINT("CompactionJob::Run():Inprogress");
|
||||
|
||||
@@ -742,7 +743,8 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
sub_compact->c_iter.reset(new CompactionIterator(
|
||||
input.get(), cfd->user_comparator(), &merge, versions_->LastSequence(),
|
||||
&existing_snapshots_, earliest_write_conflict_snapshot_, env_, false,
|
||||
range_del_agg.get(), sub_compact->compaction, compaction_filter));
|
||||
range_del_agg.get(), sub_compact->compaction, compaction_filter,
|
||||
shutting_down_));
|
||||
auto c_iter = sub_compact->c_iter.get();
|
||||
c_iter->SeekToFirst();
|
||||
const auto& c_iter_stats = c_iter->iter_stats();
|
||||
@@ -753,10 +755,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
std::string compression_dict;
|
||||
compression_dict.reserve(cfd->ioptions()->compression_opts.max_dict_bytes);
|
||||
|
||||
// TODO(noetzli): check whether we could check !shutting_down_->... only
|
||||
// only occasionally (see diff D42687)
|
||||
while (status.ok() && !shutting_down_->load(std::memory_order_acquire) &&
|
||||
!cfd->IsDropped() && c_iter->Valid()) {
|
||||
while (status.ok() && !cfd->IsDropped() && c_iter->Valid()) {
|
||||
// Invariant: c_iter.status() is guaranteed to be OK if c_iter->Valid()
|
||||
// returns true.
|
||||
const Slice& key = c_iter->key();
|
||||
@@ -772,9 +771,9 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
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_out_stats);
|
||||
status = FinishCompactionOutputFile(input->status(), sub_compact,
|
||||
range_del_agg.get(),
|
||||
&range_del_out_stats, &key);
|
||||
RecordDroppedKeys(range_del_out_stats,
|
||||
&sub_compact->compaction_job_stats);
|
||||
if (!status.ok()) {
|
||||
@@ -853,9 +852,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
|
||||
@@ -864,6 +860,9 @@ 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();
|
||||
@@ -879,6 +878,8 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
// files.
|
||||
sub_compact->compression_dict = std::move(compression_dict);
|
||||
}
|
||||
} else {
|
||||
c_iter->Next();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -901,27 +902,36 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
RecordDroppedKeys(c_iter_stats, &sub_compact->compaction_job_stats);
|
||||
RecordCompactionIOStats();
|
||||
|
||||
if (status.ok() &&
|
||||
(shutting_down_->load(std::memory_order_acquire) || cfd->IsDropped())) {
|
||||
if (status.ok() && (shutting_down_->load(std::memory_order_relaxed) ||
|
||||
cfd->IsDropped())) {
|
||||
status = Status::ShutdownInProgress(
|
||||
"Database shutdown or Column family drop during compaction");
|
||||
}
|
||||
if (status.ok()) {
|
||||
status = input->status();
|
||||
}
|
||||
if (status.ok()) {
|
||||
status = c_iter->status();
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
// Call FinishCompactionOutputFile() even if status is not ok: it needs to
|
||||
// close the output file.
|
||||
if (sub_compact->builder != nullptr) {
|
||||
CompactionIterationStats range_del_out_stats;
|
||||
status =
|
||||
FinishCompactionOutputFile(input->status(), sub_compact,
|
||||
range_del_agg.get(), &range_del_out_stats);
|
||||
Status s = FinishCompactionOutputFile(
|
||||
status, sub_compact, range_del_agg.get(), &range_del_out_stats);
|
||||
if (status.ok()) {
|
||||
status = s;
|
||||
}
|
||||
RecordDroppedKeys(range_del_out_stats, &sub_compact->compaction_job_stats);
|
||||
}
|
||||
if (status.ok()) {
|
||||
status = input->status();
|
||||
}
|
||||
|
||||
if (measure_io_stats_) {
|
||||
sub_compact->compaction_job_stats.file_write_nanos +=
|
||||
@@ -1084,6 +1094,7 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
event_logger_, cfd->ioptions()->listeners, dbname_, cfd->GetName(), fname,
|
||||
job_id_, meta->fd, tp, TableFileCreationReason::kCompaction, s);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Report new file to SstFileManagerImpl
|
||||
auto sfm =
|
||||
static_cast<SstFileManagerImpl*>(db_options_.sst_file_manager.get());
|
||||
@@ -1101,6 +1112,7 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
sub_compact->builder.reset();
|
||||
sub_compact->current_output_file_size = 0;
|
||||
|
||||
+2
-2
@@ -57,7 +57,7 @@ class CompactionJob {
|
||||
CompactionJob(int job_id, Compaction* compaction,
|
||||
const ImmutableDBOptions& db_options,
|
||||
const EnvOptions& env_options, VersionSet* versions,
|
||||
std::atomic<bool>* shutting_down, LogBuffer* log_buffer,
|
||||
const std::atomic<bool>* shutting_down, LogBuffer* log_buffer,
|
||||
Directory* db_directory, Directory* output_directory,
|
||||
Statistics* stats, InstrumentedMutex* db_mutex,
|
||||
Status* db_bg_error,
|
||||
@@ -131,7 +131,7 @@ class CompactionJob {
|
||||
|
||||
Env* env_;
|
||||
VersionSet* versions_;
|
||||
std::atomic<bool>* shutting_down_;
|
||||
const std::atomic<bool>* shutting_down_;
|
||||
LogBuffer* log_buffer_;
|
||||
Directory* db_directory_;
|
||||
Directory* output_directory_;
|
||||
|
||||
@@ -119,9 +119,6 @@ class CompactionPicker {
|
||||
const Slice& largest_user_key,
|
||||
int level) const;
|
||||
|
||||
protected:
|
||||
int NumberLevels() const { return ioptions_.num_levels; }
|
||||
|
||||
// Stores the minimal range that covers all entries in inputs in
|
||||
// *smallest, *largest.
|
||||
// REQUIRES: inputs is not empty
|
||||
@@ -141,6 +138,9 @@ class CompactionPicker {
|
||||
void GetRange(const std::vector<CompactionInputFiles>& inputs,
|
||||
InternalKey* smallest, InternalKey* largest) const;
|
||||
|
||||
protected:
|
||||
int NumberLevels() const { return ioptions_.num_levels; }
|
||||
|
||||
// Add more files to the inputs on "level" to make sure that
|
||||
// no newer version of a key is compacted to "level+1" while leaving an older
|
||||
// version in a "level". Otherwise, any Get() will search "level" first,
|
||||
|
||||
@@ -1177,6 +1177,50 @@ TEST_F(CompactionPickerTest, MaxCompactionBytesNotHit) {
|
||||
ASSERT_EQ(5U, compaction->input(1, 0)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, IsTrivialMoveOn) {
|
||||
mutable_cf_options_.max_bytes_for_level_base = 10000u;
|
||||
mutable_cf_options_.max_compaction_bytes = 10001u;
|
||||
ioptions_.level_compaction_dynamic_level_bytes = false;
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
// A compaction should be triggered and pick file 2
|
||||
Add(1, 1U, "100", "150", 3000U);
|
||||
Add(1, 2U, "151", "200", 3001U);
|
||||
Add(1, 3U, "201", "250", 3000U);
|
||||
Add(1, 4U, "251", "300", 3000U);
|
||||
|
||||
Add(3, 5U, "120", "130", 7000U);
|
||||
Add(3, 6U, "170", "180", 7000U);
|
||||
Add(3, 5U, "220", "230", 7000U);
|
||||
Add(3, 5U, "270", "280", 7000U);
|
||||
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_TRUE(compaction->IsTrivialMove());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, IsTrivialMoveOff) {
|
||||
mutable_cf_options_.max_bytes_for_level_base = 1000000u;
|
||||
mutable_cf_options_.max_compaction_bytes = 10000u;
|
||||
ioptions_.level_compaction_dynamic_level_bytes = false;
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
// A compaction should be triggered and pick all files from level 1
|
||||
Add(1, 1U, "100", "150", 300000U, 0, 0);
|
||||
Add(1, 2U, "150", "200", 300000U, 0, 0);
|
||||
Add(1, 3U, "200", "250", 300000U, 0, 0);
|
||||
Add(1, 4U, "250", "300", 300000U, 0, 0);
|
||||
|
||||
Add(3, 5U, "120", "130", 6000U);
|
||||
Add(3, 6U, "140", "150", 6000U);
|
||||
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_FALSE(compaction->IsTrivialMove());
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -406,7 +406,7 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBaseInc) {
|
||||
env_->SetBackgroundThreads(1, Env::HIGH);
|
||||
}
|
||||
|
||||
TEST_F(DBTestDynamicLevel, MigrateToDynamicLevelMaxBytesBase) {
|
||||
TEST_F(DBTestDynamicLevel, DISABLED_MigrateToDynamicLevelMaxBytesBase) {
|
||||
Random rnd(301);
|
||||
const int kMaxKey = 2000;
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
#include "util/file_util.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/sync_point.h"
|
||||
#include "util/testharness.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "util/fault_injection_test_env.h"
|
||||
#include "util/sync_point.h"
|
||||
|
||||
namespace rocksdb {
|
||||
@@ -47,6 +48,38 @@ TEST_F(DBFlushTest, FlushWhileWritingManifest) {
|
||||
#endif // ROCKSDB_LITE
|
||||
}
|
||||
|
||||
TEST_F(DBFlushTest, SyncFail) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_injection_env(
|
||||
new FaultInjectionTestEnv(Env::Default()));
|
||||
Options options;
|
||||
options.disable_auto_compactions = true;
|
||||
options.env = fault_injection_env.get();
|
||||
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBFlushTest::SyncFail:1", "DBImpl::SyncClosedLogs:Start"},
|
||||
{"DBImpl::SyncClosedLogs:Failed", "DBFlushTest::SyncFail:2"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Reopen(options);
|
||||
Put("key", "value");
|
||||
auto* cfd =
|
||||
reinterpret_cast<ColumnFamilyHandleImpl*>(db_->DefaultColumnFamily())
|
||||
->cfd();
|
||||
int refs_before = cfd->current()->TEST_refs();
|
||||
FlushOptions flush_options;
|
||||
flush_options.wait = false;
|
||||
ASSERT_OK(dbfull()->Flush(flush_options));
|
||||
fault_injection_env->SetFilesystemActive(false);
|
||||
TEST_SYNC_POINT("DBFlushTest::SyncFail:1");
|
||||
TEST_SYNC_POINT("DBFlushTest::SyncFail:2");
|
||||
fault_injection_env->SetFilesystemActive(true);
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
ASSERT_EQ("", FilesPerLevel()); // flush failed.
|
||||
// Flush job should release ref count to current version.
|
||||
ASSERT_EQ(refs_before, cfd->current()->TEST_refs());
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+163
-58
@@ -255,11 +255,17 @@ static Status ValidateOptions(
|
||||
"More than four DB paths are not supported yet. ");
|
||||
}
|
||||
|
||||
if (db_options.allow_mmap_reads && !db_options.allow_os_buffer) {
|
||||
if (db_options.allow_mmap_reads && db_options.use_direct_reads) {
|
||||
// Protect against assert in PosixMMapReadableFile constructor
|
||||
return Status::NotSupported(
|
||||
"If memory mapped reads (allow_mmap_reads) are enabled "
|
||||
"then os caching (allow_os_buffer) must also be enabled. ");
|
||||
"then direct I/O reads (use_direct_reads) must be disabled. ");
|
||||
}
|
||||
|
||||
if (db_options.allow_mmap_writes && db_options.use_direct_writes) {
|
||||
return Status::NotSupported(
|
||||
"If memory mapped writes (allow_mmap_writes) are enabled "
|
||||
"then direct I/O writes (use_direct_writes) must be disabled. ");
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
@@ -332,12 +338,11 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname)
|
||||
num_running_flushes_(0),
|
||||
bg_purge_scheduled_(0),
|
||||
disable_delete_obsolete_files_(0),
|
||||
delete_obsolete_files_next_run_(
|
||||
env_->NowMicros() +
|
||||
immutable_db_options_.delete_obsolete_files_period_micros),
|
||||
delete_obsolete_files_last_run_(env_->NowMicros()),
|
||||
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_ingest_file_(0),
|
||||
#ifndef ROCKSDB_LITE
|
||||
@@ -375,6 +380,9 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname)
|
||||
void DBImpl::CancelAllBackgroundWork(bool wait) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
|
||||
Log(InfoLogLevel::INFO_LEVEL, immutable_db_options_.info_log,
|
||||
"Shutdown: canceling all background work");
|
||||
|
||||
if (!shutting_down_.load(std::memory_order_acquire) &&
|
||||
has_unpersisted_data_ &&
|
||||
!mutable_db_options_.avoid_flush_during_shutdown) {
|
||||
@@ -499,6 +507,8 @@ DBImpl::~DBImpl() {
|
||||
env_->UnlockFile(db_lock_);
|
||||
}
|
||||
|
||||
Log(InfoLogLevel::INFO_LEVEL, immutable_db_options_.info_log,
|
||||
"Shutdown complete");
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
}
|
||||
|
||||
@@ -654,6 +664,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
|
||||
@@ -698,6 +712,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;
|
||||
|
||||
@@ -736,6 +755,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
|
||||
@@ -743,7 +790,7 @@ void DBImpl::ScheduleBgLogWriterClose(JobContext* job_context) {
|
||||
// Otherwise, gets obsolete files from VersionSet.
|
||||
// no_full_scan = true -- never do the full scan using GetChildren()
|
||||
// force = false -- don't force the full scan, except every
|
||||
// immutable_db_options_.delete_obsolete_files_period_micros
|
||||
// mutable_db_options_.delete_obsolete_files_period_micros
|
||||
// force = true -- force the full scan
|
||||
void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
bool no_full_scan) {
|
||||
@@ -760,15 +807,15 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
if (no_full_scan) {
|
||||
doing_the_full_scan = false;
|
||||
} else if (force ||
|
||||
immutable_db_options_.delete_obsolete_files_period_micros == 0) {
|
||||
mutable_db_options_.delete_obsolete_files_period_micros == 0) {
|
||||
doing_the_full_scan = true;
|
||||
} else {
|
||||
const uint64_t now_micros = env_->NowMicros();
|
||||
if (delete_obsolete_files_next_run_ < now_micros) {
|
||||
if ((delete_obsolete_files_last_run_ +
|
||||
mutable_db_options_.delete_obsolete_files_period_micros) <
|
||||
now_micros) {
|
||||
doing_the_full_scan = true;
|
||||
delete_obsolete_files_next_run_ =
|
||||
now_micros +
|
||||
immutable_db_options_.delete_obsolete_files_period_micros;
|
||||
delete_obsolete_files_last_run_ = now_micros;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -794,32 +841,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();
|
||||
|
||||
@@ -1820,6 +1842,7 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
}
|
||||
|
||||
Status DBImpl::SyncClosedLogs(JobContext* job_context) {
|
||||
TEST_SYNC_POINT("DBImpl::SyncClosedLogs:Start");
|
||||
mutex_.AssertHeld();
|
||||
autovector<log::Writer*, 1> logs_to_sync;
|
||||
uint64_t current_log_number = logfile_number_;
|
||||
@@ -1856,6 +1879,7 @@ Status DBImpl::SyncClosedLogs(JobContext* job_context) {
|
||||
MarkLogsSynced(current_log_number - 1, true, s);
|
||||
if (!s.ok()) {
|
||||
bg_error_ = s;
|
||||
TEST_SYNC_POINT("DBImpl::SyncClosedLogs:Failed");
|
||||
return s;
|
||||
}
|
||||
}
|
||||
@@ -1906,6 +1930,8 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
// is unlocked by the current thread.
|
||||
if (s.ok()) {
|
||||
s = flush_job.Run(&file_meta);
|
||||
} else {
|
||||
flush_job.Cancel();
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
@@ -1930,7 +1956,6 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
// may temporarily unlock and lock the mutex.
|
||||
NotifyOnFlushCompleted(cfd, &file_meta, mutable_cf_options,
|
||||
job_context->job_id, flush_job.GetTableProperties());
|
||||
#endif // ROCKSDB_LITE
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(
|
||||
immutable_db_options_.sst_file_manager.get());
|
||||
if (sfm) {
|
||||
@@ -1944,6 +1969,7 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
"DBImpl::FlushMemTableToOutputFile:MaxAllowedSpaceReached");
|
||||
}
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -2123,7 +2149,7 @@ Status DBImpl::CompactFiles(
|
||||
immutable_db_options_.info_log.get());
|
||||
|
||||
// Perform CompactFiles
|
||||
SuperVersion* sv = GetAndRefSuperVersion(cfd);
|
||||
SuperVersion* sv = cfd->GetReferencedSuperVersion(&mutex_);
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
|
||||
@@ -2135,7 +2161,12 @@ Status DBImpl::CompactFiles(
|
||||
input_file_names, output_level,
|
||||
output_path_id, &job_context, &log_buffer);
|
||||
}
|
||||
ReturnAndCleanupSuperVersion(cfd, sv);
|
||||
if (sv->Unref()) {
|
||||
mutex_.Lock();
|
||||
sv->Cleanup();
|
||||
mutex_.Unlock();
|
||||
delete sv;
|
||||
}
|
||||
|
||||
// Find and delete obsolete files
|
||||
{
|
||||
@@ -2493,7 +2524,7 @@ Status DBImpl::SetDBOptions(
|
||||
mutable_db_options_ = new_options;
|
||||
|
||||
if (total_log_size_ > GetMaxTotalWalSize()) {
|
||||
FlushColumnFamilies();
|
||||
MaybeFlushColumnFamilies();
|
||||
}
|
||||
|
||||
persist_options_status = PersistOptions();
|
||||
@@ -2740,7 +2771,7 @@ void DBImpl::MarkLogsSynced(
|
||||
++it;
|
||||
}
|
||||
}
|
||||
assert(logs_.empty() || logs_[0].number > up_to ||
|
||||
assert(!status.ok() || logs_.empty() || logs_[0].number > up_to ||
|
||||
(logs_.size() == 1 && !logs_[0].getting_synced));
|
||||
log_sync_cv_.SignalAll();
|
||||
}
|
||||
@@ -2939,6 +2970,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()) {
|
||||
@@ -4680,9 +4717,8 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
versions_->GetColumnFamilySet()->NumberOfColumnFamilies() == 1);
|
||||
|
||||
if (UNLIKELY(!single_column_family_mode_ &&
|
||||
!alive_log_files_.begin()->getting_flushed &&
|
||||
total_log_size_ > GetMaxTotalWalSize())) {
|
||||
FlushColumnFamilies();
|
||||
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
|
||||
@@ -5000,28 +5036,40 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
return status;
|
||||
}
|
||||
|
||||
void DBImpl::FlushColumnFamilies() {
|
||||
void DBImpl::MaybeFlushColumnFamilies() {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
WriteContext context;
|
||||
|
||||
if (alive_log_files_.begin()->getting_flushed) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t flush_column_family_if_log_file = alive_log_files_.begin()->number;
|
||||
alive_log_files_.begin()->getting_flushed = true;
|
||||
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,
|
||||
flush_column_family_if_log_file, total_log_size_, GetMaxTotalWalSize());
|
||||
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->GetLogNumber() <= flush_column_family_if_log_file) {
|
||||
if (cfd->OldestLogToKeep() <= oldest_alive_log) {
|
||||
auto status = SwitchMemtable(cfd, &context);
|
||||
if (!status.ok()) {
|
||||
break;
|
||||
@@ -5031,6 +5079,26 @@ void DBImpl::FlushColumnFamilies() {
|
||||
}
|
||||
}
|
||||
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 {
|
||||
@@ -6050,6 +6118,7 @@ Status DB::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
}
|
||||
impl->mutex_.Unlock();
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(
|
||||
impl->immutable_db_options_.sst_file_manager.get());
|
||||
if (s.ok() && sfm) {
|
||||
@@ -6068,6 +6137,7 @@ Status DB::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
if (s.ok()) {
|
||||
Log(InfoLogLevel::INFO_LEVEL, impl->immutable_db_options_.info_log,
|
||||
@@ -6298,7 +6368,7 @@ Status DBImpl::RenameTempFileToOptionsFile(const std::string& file_name) {
|
||||
#endif // !ROCKSDB_LITE
|
||||
}
|
||||
|
||||
#if ROCKSDB_USING_THREAD_STATUS
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
|
||||
void DBImpl::NewThreadStatusCfInfo(
|
||||
ColumnFamilyData* cfd) const {
|
||||
@@ -6493,13 +6563,22 @@ Status DBImpl::IngestExternalFile(
|
||||
|
||||
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
|
||||
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();
|
||||
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
|
||||
@@ -6541,9 +6620,35 @@ Status DBImpl::IngestExternalFile(
|
||||
// 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) {
|
||||
|
||||
+29
-3
@@ -23,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"
|
||||
@@ -307,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);
|
||||
@@ -379,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
|
||||
@@ -556,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;
|
||||
@@ -728,7 +744,7 @@ class DBImpl : public DB {
|
||||
// REQUIRES: mutex locked
|
||||
Status PersistOptions();
|
||||
|
||||
void FlushColumnFamilies();
|
||||
void MaybeFlushColumnFamilies();
|
||||
|
||||
uint64_t GetMaxTotalWalSize() const;
|
||||
|
||||
@@ -972,8 +988,9 @@ class DBImpl : public DB {
|
||||
// without any synchronization
|
||||
int disable_delete_obsolete_files_;
|
||||
|
||||
// next time when we should run DeleteObsoleteFiles with full scan
|
||||
uint64_t delete_obsolete_files_next_run_;
|
||||
// last time when DeleteObsoleteFiles with full scan was executed. Originaly
|
||||
// initialized with startup time.
|
||||
uint64_t delete_obsolete_files_last_run_;
|
||||
|
||||
// last time stats were dumped to LOG
|
||||
std::atomic<uint64_t> last_stats_dump_time_microsec_;
|
||||
@@ -987,6 +1004,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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
+79
-32
@@ -123,7 +123,8 @@ class DBIter: public Iterator {
|
||||
prefix_same_as_start_(prefix_same_as_start),
|
||||
pin_thru_lifetime_(pin_data),
|
||||
total_order_seek_(total_order_seek),
|
||||
range_del_agg_(ioptions.internal_comparator, s) {
|
||||
range_del_agg_(ioptions.internal_comparator, s,
|
||||
true /* collapse_deletions */) {
|
||||
RecordTick(statistics_, NO_ITERATORS);
|
||||
prefix_extractor_ = ioptions.prefix_extractor;
|
||||
max_skip_ = max_sequential_skip_in_iterations;
|
||||
@@ -340,6 +341,7 @@ void DBIter::Next() {
|
||||
//
|
||||
// NOTE: In between, saved_key_ can point to a user key that has
|
||||
// a delete marker or a sequence number higher than sequence_
|
||||
// saved_key_ MUST have a proper user_key before calling this function
|
||||
//
|
||||
// The prefix_check parameter controls whether we check the iterated
|
||||
// keys against the prefix of the seeked key. Set to false when
|
||||
@@ -411,7 +413,9 @@ void DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) {
|
||||
saved_key_.SetKey(
|
||||
ikey.user_key,
|
||||
!iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
if (range_del_agg_.ShouldDelete(ikey)) {
|
||||
if (range_del_agg_.ShouldDelete(
|
||||
ikey, RangeDelAggregator::RangePositioningMode::
|
||||
kForwardTraversal)) {
|
||||
// Arrange to skip all upcoming entries for this key since
|
||||
// they are hidden by this deletion.
|
||||
skipping = true;
|
||||
@@ -426,7 +430,9 @@ void DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) {
|
||||
saved_key_.SetKey(
|
||||
ikey.user_key,
|
||||
!iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
if (range_del_agg_.ShouldDelete(ikey)) {
|
||||
if (range_del_agg_.ShouldDelete(
|
||||
ikey, RangeDelAggregator::RangePositioningMode::
|
||||
kForwardTraversal)) {
|
||||
// Arrange to skip all upcoming entries for this key since
|
||||
// they are hidden by this deletion.
|
||||
skipping = true;
|
||||
@@ -518,6 +524,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
|
||||
@@ -528,7 +535,9 @@ void DBIter::MergeValuesNewToOld() {
|
||||
// hit the next user key, stop right here
|
||||
break;
|
||||
} else if (kTypeDeletion == ikey.type || kTypeSingleDeletion == ikey.type ||
|
||||
range_del_agg_.ShouldDelete(ikey)) {
|
||||
range_del_agg_.ShouldDelete(
|
||||
ikey, RangeDelAggregator::RangePositioningMode::
|
||||
kForwardTraversal)) {
|
||||
// hit a delete with the same user key, stop right here
|
||||
// iter_ is positioned after delete
|
||||
iter_->Next();
|
||||
@@ -538,9 +547,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;
|
||||
@@ -559,9 +571,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() {
|
||||
@@ -591,6 +606,7 @@ void DBIter::ReverseToForward() {
|
||||
direction_ = kForward;
|
||||
if (!iter_->Valid()) {
|
||||
iter_->SeekToFirst();
|
||||
range_del_agg_.InvalidateTombstoneMapPositions();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -606,6 +622,7 @@ void DBIter::ReverseToBackward() {
|
||||
// previous key.
|
||||
if (!iter_->Valid()) {
|
||||
iter_->SeekToLast();
|
||||
range_del_agg_.InvalidateTombstoneMapPositions();
|
||||
}
|
||||
ParsedInternalKey ikey;
|
||||
FindParseableKey(&ikey, kReverse);
|
||||
@@ -702,7 +719,9 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
last_key_entry_type = ikey.type;
|
||||
switch (last_key_entry_type) {
|
||||
case kTypeValue:
|
||||
if (range_del_agg_.ShouldDelete(ikey)) {
|
||||
if (range_del_agg_.ShouldDelete(
|
||||
ikey,
|
||||
RangeDelAggregator::RangePositioningMode::kBackwardTraversal)) {
|
||||
last_key_entry_type = kTypeRangeDeletion;
|
||||
PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
|
||||
} else {
|
||||
@@ -719,7 +738,9 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
|
||||
break;
|
||||
case kTypeMerge:
|
||||
if (range_del_agg_.ShouldDelete(ikey)) {
|
||||
if (range_del_agg_.ShouldDelete(
|
||||
ikey,
|
||||
RangeDelAggregator::RangePositioningMode::kBackwardTraversal)) {
|
||||
merge_context_.Clear();
|
||||
last_key_entry_type = kTypeRangeDeletion;
|
||||
last_not_merge_type = last_key_entry_type;
|
||||
@@ -742,6 +763,7 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
FindParseableKey(&ikey, kReverse);
|
||||
}
|
||||
|
||||
Status s;
|
||||
switch (last_key_entry_type) {
|
||||
case kTypeDeletion:
|
||||
case kTypeSingleDeletion:
|
||||
@@ -753,16 +775,16 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
if (last_not_merge_type == kTypeDeletion ||
|
||||
last_not_merge_type == kTypeSingleDeletion ||
|
||||
last_not_merge_type == kTypeRangeDeletion) {
|
||||
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_);
|
||||
} 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:
|
||||
@@ -773,6 +795,9 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
break;
|
||||
}
|
||||
valid_ = true;
|
||||
if (!s.ok()) {
|
||||
status_ = s;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -793,7 +818,8 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
FindParseableKey(&ikey, kForward);
|
||||
|
||||
if (ikey.type == kTypeDeletion || ikey.type == kTypeSingleDeletion ||
|
||||
range_del_agg_.ShouldDelete(ikey)) {
|
||||
range_del_agg_.ShouldDelete(
|
||||
ikey, RangeDelAggregator::RangePositioningMode::kBackwardTraversal)) {
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
@@ -808,9 +834,12 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
// in operands
|
||||
current_entry_is_merged_ = true;
|
||||
merge_context_.Clear();
|
||||
while (iter_->Valid() &&
|
||||
user_comparator_->Equal(ikey.user_key, saved_key_.GetKey()) &&
|
||||
ikey.type == kTypeMerge && !range_del_agg_.ShouldDelete(ikey)) {
|
||||
while (
|
||||
iter_->Valid() &&
|
||||
user_comparator_->Equal(ikey.user_key, saved_key_.GetKey()) &&
|
||||
ikey.type == kTypeMerge &&
|
||||
!range_del_agg_.ShouldDelete(
|
||||
ikey, RangeDelAggregator::RangePositioningMode::kBackwardTraversal)) {
|
||||
merge_context_.PushOperand(iter_->value(),
|
||||
iter_->IsValuePinned() /* operand_pinned */);
|
||||
PERF_COUNTER_ADD(internal_merge_count, 1);
|
||||
@@ -818,13 +847,16 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
FindParseableKey(&ikey, kForward);
|
||||
}
|
||||
|
||||
Status s;
|
||||
if (!iter_->Valid() ||
|
||||
!user_comparator_->Equal(ikey.user_key, saved_key_.GetKey()) ||
|
||||
ikey.type == kTypeDeletion || ikey.type == kTypeSingleDeletion ||
|
||||
range_del_agg_.ShouldDelete(ikey)) {
|
||||
MergeHelper::TimedFullMerge(merge_operator_, saved_key_.GetKey(), nullptr,
|
||||
merge_context_.GetOperands(), &saved_value_,
|
||||
logger_, statistics_, env_, &pinned_value_);
|
||||
range_del_agg_.ShouldDelete(
|
||||
ikey, RangeDelAggregator::RangePositioningMode::kBackwardTraversal)) {
|
||||
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())) {
|
||||
@@ -832,14 +864,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;
|
||||
}
|
||||
|
||||
@@ -909,12 +947,12 @@ void DBIter::Seek(const Slice& target) {
|
||||
StopWatch sw(env_, statistics_, DB_SEEK);
|
||||
ReleaseTempPinnedData();
|
||||
saved_key_.Clear();
|
||||
// now savved_key is used to store internal key.
|
||||
saved_key_.SetInternalKey(target, sequence_);
|
||||
|
||||
{
|
||||
PERF_TIMER_GUARD(seek_internal_seek_time);
|
||||
iter_->Seek(saved_key_.GetKey());
|
||||
range_del_agg_.InvalidateTombstoneMapPositions();
|
||||
}
|
||||
RecordTick(statistics_, NUMBER_DB_SEEK);
|
||||
if (iter_->Valid()) {
|
||||
@@ -923,6 +961,9 @@ void DBIter::Seek(const Slice& target) {
|
||||
}
|
||||
direction_ = kForward;
|
||||
ClearSavedValue();
|
||||
// convert the InternalKey to UserKey in saved_key_ before
|
||||
// passed to FindNextUserEntry
|
||||
saved_key_.Reserve(saved_key_.Size() - 8);
|
||||
FindNextUserEntry(false /* not skipping */, prefix_same_as_start_);
|
||||
if (!valid_) {
|
||||
prefix_start_key_.clear();
|
||||
@@ -954,6 +995,7 @@ void DBIter::SeekForPrev(const Slice& target) {
|
||||
{
|
||||
PERF_TIMER_GUARD(seek_internal_seek_time);
|
||||
iter_->SeekForPrev(saved_key_.GetKey());
|
||||
range_del_agg_.InvalidateTombstoneMapPositions();
|
||||
}
|
||||
|
||||
RecordTick(statistics_, NUMBER_DB_SEEK);
|
||||
@@ -995,10 +1037,13 @@ void DBIter::SeekToFirst() {
|
||||
{
|
||||
PERF_TIMER_GUARD(seek_internal_seek_time);
|
||||
iter_->SeekToFirst();
|
||||
range_del_agg_.InvalidateTombstoneMapPositions();
|
||||
}
|
||||
|
||||
RecordTick(statistics_, NUMBER_DB_SEEK);
|
||||
if (iter_->Valid()) {
|
||||
saved_key_.SetKey(ExtractUserKey(iter_->key()),
|
||||
!iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
FindNextUserEntry(false /* not skipping */, false /* no prefix check */);
|
||||
if (statistics_ != nullptr) {
|
||||
if (valid_) {
|
||||
@@ -1028,12 +1073,14 @@ void DBIter::SeekToLast() {
|
||||
{
|
||||
PERF_TIMER_GUARD(seek_internal_seek_time);
|
||||
iter_->SeekToLast();
|
||||
range_del_agg_.InvalidateTombstoneMapPositions();
|
||||
}
|
||||
// When the iterate_upper_bound is set to a value,
|
||||
// it will seek to the last key before the
|
||||
// ReadOptions.iterate_upper_bound
|
||||
if (iter_->Valid() && iterate_upper_bound_ != nullptr) {
|
||||
SeekForPrev(*iterate_upper_bound_);
|
||||
range_del_agg_.InvalidateTombstoneMapPositions();
|
||||
if (!Valid()) {
|
||||
return;
|
||||
} else if (user_comparator_->Equal(*iterate_upper_bound_, key())) {
|
||||
|
||||
@@ -1897,6 +1897,66 @@ TEST_F(DBIteratorTest, DBIterator12) {
|
||||
ASSERT_FALSE(db_iter->Valid());
|
||||
}
|
||||
|
||||
TEST_F(DBIteratorTest, DBIterator13) {
|
||||
Options options;
|
||||
options.merge_operator = nullptr;
|
||||
|
||||
std::string key;
|
||||
key.resize(9);
|
||||
key.assign(9, static_cast<char>(0));
|
||||
key[0] = 'b';
|
||||
|
||||
TestIterator* internal_iter = new TestIterator(BytewiseComparator());
|
||||
internal_iter->AddPut(key, "0");
|
||||
internal_iter->AddPut(key, "1");
|
||||
internal_iter->AddPut(key, "2");
|
||||
internal_iter->AddPut(key, "3");
|
||||
internal_iter->AddPut(key, "4");
|
||||
internal_iter->AddPut(key, "5");
|
||||
internal_iter->AddPut(key, "6");
|
||||
internal_iter->AddPut(key, "7");
|
||||
internal_iter->AddPut(key, "8");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(
|
||||
NewDBIterator(env_, ImmutableCFOptions(options), BytewiseComparator(),
|
||||
internal_iter, 2, 3, 0));
|
||||
db_iter->Seek("b");
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), key);
|
||||
ASSERT_EQ(db_iter->value().ToString(), "2");
|
||||
}
|
||||
|
||||
TEST_F(DBIteratorTest, DBIterator14) {
|
||||
Options options;
|
||||
options.merge_operator = nullptr;
|
||||
|
||||
std::string key("b");
|
||||
TestIterator* internal_iter = new TestIterator(BytewiseComparator());
|
||||
internal_iter->AddPut("b", "0");
|
||||
internal_iter->AddPut("b", "1");
|
||||
internal_iter->AddPut("b", "2");
|
||||
internal_iter->AddPut("b", "3");
|
||||
internal_iter->AddPut("a", "4");
|
||||
internal_iter->AddPut("a", "5");
|
||||
internal_iter->AddPut("a", "6");
|
||||
internal_iter->AddPut("c", "7");
|
||||
internal_iter->AddPut("c", "8");
|
||||
internal_iter->AddPut("c", "9");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(
|
||||
NewDBIterator(env_, ImmutableCFOptions(options), BytewiseComparator(),
|
||||
internal_iter, 4, 1, 0));
|
||||
db_iter->Seek("b");
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "b");
|
||||
ASSERT_EQ(db_iter->value().ToString(), "3");
|
||||
db_iter->SeekToFirst();
|
||||
ASSERT_EQ(db_iter->key().ToString(), "a");
|
||||
ASSERT_EQ(db_iter->value().ToString(), "4");
|
||||
}
|
||||
|
||||
class DBIterWithMergeIterTest : public testing::Test {
|
||||
public:
|
||||
DBIterWithMergeIterTest()
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
// 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 <functional>
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/port.h"
|
||||
#include "port/stack_trace.h"
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -316,6 +316,47 @@ TEST_F(DBOptionsTest, MaxTotalWalSizeChange) {
|
||||
}
|
||||
}
|
||||
|
||||
static void assert_candidate_files_empty(DBImpl* dbfull, const bool empty) {
|
||||
dbfull->TEST_LockMutex();
|
||||
JobContext job_context(0);
|
||||
dbfull->FindObsoleteFiles(&job_context, false);
|
||||
ASSERT_EQ(empty, job_context.full_scan_candidate_files.empty());
|
||||
job_context.Clean();
|
||||
dbfull->TEST_UnlockMutex();
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, DeleteObsoleteFilesPeriodChange) {
|
||||
SpecialEnv env(Env::Default());
|
||||
env.time_elapse_only_sleep_ = true;
|
||||
Options options;
|
||||
options.env = &env;
|
||||
options.create_if_missing = true;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
|
||||
// Verify that candidate files set is empty when no full scan requested.
|
||||
assert_candidate_files_empty(dbfull(), true);
|
||||
|
||||
ASSERT_OK(
|
||||
dbfull()->SetDBOptions({{"delete_obsolete_files_period_micros", "0"}}));
|
||||
|
||||
// After delete_obsolete_files_period_micros updated to 0, the next call
|
||||
// to FindObsoleteFiles should make a full scan
|
||||
assert_candidate_files_empty(dbfull(), false);
|
||||
|
||||
ASSERT_OK(
|
||||
dbfull()->SetDBOptions({{"delete_obsolete_files_period_micros", "20"}}));
|
||||
|
||||
assert_candidate_files_empty(dbfull(), true);
|
||||
|
||||
env.addon_time_.store(20);
|
||||
assert_candidate_files_empty(dbfull(), true);
|
||||
|
||||
env.addon_time_.store(21);
|
||||
assert_candidate_files_empty(dbfull(), false);
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
+279
-1
@@ -41,7 +41,7 @@ TEST_F(DBRangeDelTest, NonBlockBasedTableNotSupported) {
|
||||
|
||||
TEST_F(DBRangeDelTest, FlushOutputHasOnlyRangeTombstones) {
|
||||
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "dr1",
|
||||
"dr1"));
|
||||
"dr2"));
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
}
|
||||
@@ -112,6 +112,81 @@ TEST_F(DBRangeDelTest, CompactionOutputFilesExactlyFilled) {
|
||||
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, SentinelsOmittedFromOutputFile) {
|
||||
// Regression test for bug where sentinel range deletions (i.e., ones with
|
||||
// sequence number of zero) were included in output files.
|
||||
// snapshot protects range tombstone from dropping due to becoming obsolete.
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
|
||||
// gaps between ranges creates sentinels in our internal representation
|
||||
std::vector<std::pair<std::string, std::string>> range_dels = {{"a", "b"}, {"c", "d"}, {"e", "f"}};
|
||||
for (const auto& range_del : range_dels) {
|
||||
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
|
||||
range_del.first, range_del.second));
|
||||
}
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
|
||||
std::vector<std::vector<FileMetaData>> files;
|
||||
dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(), &files);
|
||||
ASSERT_GT(files[0][0].smallest_seqno, 0);
|
||||
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, FlushRangeDelsSameStartKey) {
|
||||
db_->Put(WriteOptions(), "b1", "val");
|
||||
ASSERT_OK(
|
||||
@@ -430,6 +505,209 @@ TEST_F(DBRangeDelTest, ObsoleteTombstoneCleanup) {
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBRangeDelTest, GetCoveredKeyFromMutableMemtable) {
|
||||
db_->Put(WriteOptions(), "key", "val");
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z"));
|
||||
|
||||
ReadOptions read_opts;
|
||||
std::string value;
|
||||
ASSERT_TRUE(db_->Get(read_opts, "key", &value).IsNotFound());
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, GetCoveredKeyFromImmutableMemtable) {
|
||||
Options opts = CurrentOptions();
|
||||
opts.max_write_buffer_number = 3;
|
||||
opts.min_write_buffer_number_to_merge = 2;
|
||||
// SpecialSkipListFactory lets us specify maximum number of elements the
|
||||
// memtable can hold. It switches the active memtable to immutable (flush is
|
||||
// prevented by the above options) upon inserting an element that would
|
||||
// overflow the memtable.
|
||||
opts.memtable_factory.reset(new SpecialSkipListFactory(1));
|
||||
Reopen(opts);
|
||||
|
||||
db_->Put(WriteOptions(), "key", "val");
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z"));
|
||||
db_->Put(WriteOptions(), "blah", "val");
|
||||
|
||||
ReadOptions read_opts;
|
||||
std::string value;
|
||||
ASSERT_TRUE(db_->Get(read_opts, "key", &value).IsNotFound());
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, GetCoveredKeyFromSst) {
|
||||
db_->Put(WriteOptions(), "key", "val");
|
||||
// snapshot prevents key from being deleted during flush
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z"));
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
|
||||
ReadOptions read_opts;
|
||||
std::string value;
|
||||
ASSERT_TRUE(db_->Get(read_opts, "key", &value).IsNotFound());
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, GetCoveredMergeOperandFromMemtable) {
|
||||
const int kNumMergeOps = 10;
|
||||
Options opts = CurrentOptions();
|
||||
opts.merge_operator = MergeOperators::CreateUInt64AddOperator();
|
||||
Reopen(opts);
|
||||
|
||||
for (int i = 0; i < kNumMergeOps; ++i) {
|
||||
std::string val;
|
||||
PutFixed64(&val, i);
|
||||
db_->Merge(WriteOptions(), "key", val);
|
||||
if (i == kNumMergeOps / 2) {
|
||||
// deletes [0, 5]
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "key",
|
||||
"key_");
|
||||
}
|
||||
}
|
||||
|
||||
ReadOptions read_opts;
|
||||
std::string expected, actual;
|
||||
ASSERT_OK(db_->Get(read_opts, "key", &actual));
|
||||
PutFixed64(&expected, 30); // 6+7+8+9
|
||||
ASSERT_EQ(expected, actual);
|
||||
|
||||
expected.clear();
|
||||
read_opts.ignore_range_deletions = true;
|
||||
ASSERT_OK(db_->Get(read_opts, "key", &actual));
|
||||
PutFixed64(&expected, 45); // 0+1+2+...+9
|
||||
ASSERT_EQ(expected, actual);
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, GetIgnoresRangeDeletions) {
|
||||
Options opts = CurrentOptions();
|
||||
opts.max_write_buffer_number = 4;
|
||||
opts.min_write_buffer_number_to_merge = 3;
|
||||
opts.memtable_factory.reset(new SpecialSkipListFactory(1));
|
||||
Reopen(opts);
|
||||
|
||||
db_->Put(WriteOptions(), "sst_key", "val");
|
||||
// snapshot prevents key from being deleted during flush
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z"));
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
db_->Put(WriteOptions(), "imm_key", "val");
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z"));
|
||||
db_->Put(WriteOptions(), "mem_key", "val");
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z"));
|
||||
|
||||
ReadOptions read_opts;
|
||||
read_opts.ignore_range_deletions = true;
|
||||
for (std::string key : {"sst_key", "imm_key", "mem_key"}) {
|
||||
std::string value;
|
||||
ASSERT_OK(db_->Get(read_opts, key, &value));
|
||||
}
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, IteratorRemovesCoveredKeys) {
|
||||
const int kNum = 200, kRangeBegin = 50, kRangeEnd = 150, kNumPerFile = 25;
|
||||
Options opts = CurrentOptions();
|
||||
opts.comparator = test::Uint64Comparator();
|
||||
opts.memtable_factory.reset(new SpecialSkipListFactory(kNumPerFile));
|
||||
Reopen(opts);
|
||||
|
||||
// Write half of the keys before the tombstone and half after the tombstone.
|
||||
// Only covered keys (i.e., within the range and older than the tombstone)
|
||||
// should be deleted.
|
||||
for (int i = 0; i < kNum; ++i) {
|
||||
if (i == kNum / 2) {
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
|
||||
GetNumericStr(kRangeBegin), GetNumericStr(kRangeEnd));
|
||||
}
|
||||
db_->Put(WriteOptions(), GetNumericStr(i), "val");
|
||||
}
|
||||
ReadOptions read_opts;
|
||||
auto* iter = db_->NewIterator(read_opts);
|
||||
|
||||
int expected = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
ASSERT_EQ(GetNumericStr(expected), iter->key());
|
||||
if (expected == kRangeBegin - 1) {
|
||||
expected = kNum / 2;
|
||||
} else {
|
||||
++expected;
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(kNum, expected);
|
||||
delete iter;
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, IteratorOverUserSnapshot) {
|
||||
const int kNum = 200, kRangeBegin = 50, kRangeEnd = 150, kNumPerFile = 25;
|
||||
Options opts = CurrentOptions();
|
||||
opts.comparator = test::Uint64Comparator();
|
||||
opts.memtable_factory.reset(new SpecialSkipListFactory(kNumPerFile));
|
||||
Reopen(opts);
|
||||
|
||||
const Snapshot* snapshot = nullptr;
|
||||
// Put a snapshot before the range tombstone, verify an iterator using that
|
||||
// snapshot sees all inserted keys.
|
||||
for (int i = 0; i < kNum; ++i) {
|
||||
if (i == kNum / 2) {
|
||||
snapshot = db_->GetSnapshot();
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
|
||||
GetNumericStr(kRangeBegin), GetNumericStr(kRangeEnd));
|
||||
}
|
||||
db_->Put(WriteOptions(), GetNumericStr(i), "val");
|
||||
}
|
||||
ReadOptions read_opts;
|
||||
read_opts.snapshot = snapshot;
|
||||
auto* iter = db_->NewIterator(read_opts);
|
||||
|
||||
int expected = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
ASSERT_EQ(GetNumericStr(expected), iter->key());
|
||||
++expected;
|
||||
}
|
||||
ASSERT_EQ(kNum / 2, expected);
|
||||
delete iter;
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, IteratorIgnoresRangeDeletions) {
|
||||
Options opts = CurrentOptions();
|
||||
opts.max_write_buffer_number = 4;
|
||||
opts.min_write_buffer_number_to_merge = 3;
|
||||
opts.memtable_factory.reset(new SpecialSkipListFactory(1));
|
||||
Reopen(opts);
|
||||
|
||||
db_->Put(WriteOptions(), "sst_key", "val");
|
||||
// snapshot prevents key from being deleted during flush
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z"));
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
db_->Put(WriteOptions(), "imm_key", "val");
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z"));
|
||||
db_->Put(WriteOptions(), "mem_key", "val");
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z"));
|
||||
|
||||
ReadOptions read_opts;
|
||||
read_opts.ignore_range_deletions = true;
|
||||
auto* iter = db_->NewIterator(read_opts);
|
||||
int i = 0;
|
||||
std::string expected[] = {"imm_key", "mem_key", "sst_key"};
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next(), ++i) {
|
||||
std::string key;
|
||||
ASSERT_EQ(expected[i], iter->key());
|
||||
}
|
||||
ASSERT_EQ(3, i);
|
||||
delete iter;
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -221,8 +221,6 @@ TEST_F(DBSSTTest, DeleteObsoleteFilesPendingOutputs) {
|
||||
listener->VerifyMatchedCount(1);
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBSSTTest, DBWithSstFileManager) {
|
||||
std::shared_ptr<SstFileManager> sst_file_manager(NewSstFileManager(env_));
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(sst_file_manager.get());
|
||||
@@ -287,7 +285,6 @@ TEST_F(DBSSTTest, DBWithSstFileManager) {
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBSSTTest, RateLimitedDelete) {
|
||||
Destroy(last_options_);
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency({
|
||||
@@ -482,7 +479,6 @@ TEST_F(DBSSTTest, DestroyDBWithRateLimitedDelete) {
|
||||
// We have deleted the 4 sst files in the delete_scheduler
|
||||
ASSERT_EQ(bg_delete_file, 4);
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBSSTTest, DBWithMaxSpaceAllowed) {
|
||||
std::shared_ptr<SstFileManager> sst_file_manager(NewSstFileManager(env_));
|
||||
@@ -580,7 +576,6 @@ TEST_F(DBSSTTest, DBWithMaxSpaceAllowedRandomized) {
|
||||
ASSERT_GT(reached_max_space_on_compaction, 0);
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBSSTTest, OpenDBWithInfiniteMaxOpenFiles) {
|
||||
// Open DB with infinite max open files
|
||||
// - First iteration use 1 thread to open files
|
||||
|
||||
+23
-8
@@ -197,6 +197,19 @@ TEST_F(DBTest, MemEnvTest) {
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBTest, OpenWhenOpen) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
rocksdb::DB* db2 = nullptr;
|
||||
rocksdb::Status s = DB::Open(options, dbname_, &db2);
|
||||
|
||||
ASSERT_EQ(Status::Code::kIOError, s.code());
|
||||
ASSERT_EQ(Status::SubCode::kNone, s.subcode());
|
||||
ASSERT_TRUE(strstr(s.getState(), "lock ") != nullptr);
|
||||
|
||||
delete db2;
|
||||
}
|
||||
|
||||
TEST_F(DBTest, WriteEmptyBatch) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
@@ -3396,19 +3409,21 @@ TEST_F(DBTest, TableOptionsSanitizeTest) {
|
||||
TEST_F(DBTest, MmapAndBufferOptions) {
|
||||
Options options = CurrentOptions();
|
||||
|
||||
options.allow_os_buffer = false;
|
||||
options.use_direct_reads = true;
|
||||
options.allow_mmap_reads = true;
|
||||
ASSERT_NOK(TryReopen(options));
|
||||
|
||||
// All other combinations are acceptable
|
||||
options.allow_os_buffer = true;
|
||||
options.use_direct_reads = false;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
|
||||
options.allow_os_buffer = false;
|
||||
options.allow_mmap_reads = false;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
if (IsDirectIOSupported()) {
|
||||
options.use_direct_reads = true;
|
||||
options.allow_mmap_reads = false;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
}
|
||||
|
||||
options.allow_os_buffer = true;
|
||||
options.use_direct_reads = false;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
}
|
||||
#endif
|
||||
@@ -3663,7 +3678,7 @@ TEST_F(DBTest, DynamicMemtableOptions) {
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
#if ROCKSDB_USING_THREAD_STATUS
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
namespace {
|
||||
void VerifyOperationCount(Env* env, ThreadStatus::OperationType op_type,
|
||||
int expected_count) {
|
||||
@@ -4970,7 +4985,7 @@ TEST_F(DBTest, MergeTestTime) {
|
||||
|
||||
ASSERT_EQ(1, count);
|
||||
ASSERT_EQ(2000000, TestGetTickerCount(options, MERGE_OPERATION_TOTAL_TIME));
|
||||
#if ROCKSDB_USING_THREAD_STATUS
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
ASSERT_GT(TestGetTickerCount(options, FLUSH_WRITE_BYTES), 0);
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
this->env_->time_elapse_only_sleep_ = false;
|
||||
|
||||
@@ -1505,6 +1505,10 @@ class MockPersistentCache : public PersistentCache {
|
||||
|
||||
bool IsCompressed() override { return is_compressed_; }
|
||||
|
||||
std::string GetPrintableOptions() const override {
|
||||
return "MockPersistentCache";
|
||||
}
|
||||
|
||||
port::Mutex lock_;
|
||||
std::map<std::string, std::string> data_;
|
||||
const bool is_compressed_ = true;
|
||||
@@ -2212,8 +2216,40 @@ 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
|
||||
|
||||
TEST_F(DBTest2, DirectIO) {
|
||||
if (!IsDirectIOSupported()) {
|
||||
return;
|
||||
}
|
||||
Options options = CurrentOptions();
|
||||
options.use_direct_reads = options.use_direct_writes = true;
|
||||
options.allow_mmap_reads = options.allow_mmap_writes = false;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(Put(Key(0), "a"));
|
||||
ASSERT_OK(Put(Key(5), "a"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(Put(Key(10), "a"));
|
||||
ASSERT_OK(Put(Key(15), "a"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
Reopen(options);
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+76
-5
@@ -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 {
|
||||
|
||||
@@ -307,6 +308,7 @@ Options DBTestBase::CurrentOptions(
|
||||
break;
|
||||
case kManifestFileSize:
|
||||
options.max_manifest_file_size = 50; // 50 bytes
|
||||
break;
|
||||
case kPerfOptions:
|
||||
options.soft_rate_limit = 2.0;
|
||||
options.delayed_write_rate = 8 * 1024 * 1024;
|
||||
@@ -476,6 +478,22 @@ Status DBTestBase::TryReopen(const Options& options) {
|
||||
return DB::Open(options, dbname_, &db_);
|
||||
}
|
||||
|
||||
bool DBTestBase::IsDirectIOSupported() {
|
||||
EnvOptions env_options;
|
||||
env_options.use_mmap_writes = false;
|
||||
env_options.use_direct_writes = true;
|
||||
std::string tmp = TempFileName(dbname_, 999);
|
||||
Status s;
|
||||
{
|
||||
unique_ptr<WritableFile> file;
|
||||
s = env_->NewWritableFile(tmp, &file, env_options);
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = env_->DeleteFile(tmp);
|
||||
}
|
||||
return s.ok();
|
||||
}
|
||||
|
||||
Status DBTestBase::Flush(int cf) {
|
||||
if (cf == 0) {
|
||||
return db_->Flush(FlushOptions());
|
||||
@@ -501,6 +519,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);
|
||||
}
|
||||
@@ -1089,11 +1116,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, bool tailing_iter) {
|
||||
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++;
|
||||
}
|
||||
|
||||
@@ -1106,21 +1140,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++;
|
||||
}
|
||||
@@ -1134,7 +1187,6 @@ void DBTestBase::VerifyDBFromMap(std::map<std::string, std::string> true_data,
|
||||
ASSERT_EQ(kv.second, iter->value().ToString());
|
||||
total_reads++;
|
||||
}
|
||||
|
||||
delete iter;
|
||||
}
|
||||
|
||||
@@ -1176,6 +1228,25 @@ void DBTestBase::VerifyDBFromMap(std::map<std::string, std::string> true_data,
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
|
||||
+40
-4
@@ -231,6 +231,20 @@ class SpecialEnv : public EnvWrapper {
|
||||
return base_->Append(data);
|
||||
}
|
||||
}
|
||||
Status PositionedAppend(const Slice& data, uint64_t offset) override {
|
||||
if (env_->table_write_callback_) {
|
||||
(*env_->table_write_callback_)();
|
||||
}
|
||||
if (env_->drop_writes_.load(std::memory_order_acquire)) {
|
||||
// Drop writes on the floor
|
||||
return Status::OK();
|
||||
} else if (env_->no_space_.load(std::memory_order_acquire)) {
|
||||
return Status::NoSpace("No space left on device");
|
||||
} else {
|
||||
env_->bytes_written_ += data.size();
|
||||
return base_->PositionedAppend(data, offset);
|
||||
}
|
||||
}
|
||||
Status Truncate(uint64_t size) override { return base_->Truncate(size); }
|
||||
Status Close() override {
|
||||
// SyncPoint is not supported in Released Windows Mode.
|
||||
@@ -257,6 +271,9 @@ class SpecialEnv : public EnvWrapper {
|
||||
Env::IOPriority GetIOPriority() override {
|
||||
return base_->GetIOPriority();
|
||||
}
|
||||
bool use_direct_io() const override {
|
||||
return base_->use_direct_io();
|
||||
}
|
||||
};
|
||||
class ManifestFile : public WritableFile {
|
||||
public:
|
||||
@@ -358,7 +375,14 @@ class SpecialEnv : public EnvWrapper {
|
||||
return Status::IOError("simulated write error");
|
||||
}
|
||||
|
||||
Status s = target()->NewWritableFile(f, r, soptions);
|
||||
EnvOptions optimized = soptions;
|
||||
if (strstr(f.c_str(), "MANIFEST") != nullptr ||
|
||||
strstr(f.c_str(), "log") != nullptr) {
|
||||
optimized.use_mmap_writes = false;
|
||||
optimized.use_direct_writes = false;
|
||||
}
|
||||
|
||||
Status s = target()->NewWritableFile(f, r, optimized);
|
||||
if (s.ok()) {
|
||||
if (strstr(f.c_str(), ".sst") != nullptr) {
|
||||
r->reset(new SSTableFile(this, std::move(*r)));
|
||||
@@ -692,6 +716,8 @@ class DBTestBase : public testing::Test {
|
||||
|
||||
Status TryReopen(const Options& options);
|
||||
|
||||
bool IsDirectIOSupported();
|
||||
|
||||
Status Flush(int cf = 0);
|
||||
|
||||
Status Put(const Slice& k, const Slice& v, WriteOptions wo = WriteOptions());
|
||||
@@ -699,6 +725,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);
|
||||
@@ -825,9 +857,13 @@ 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,
|
||||
bool tailing_iter = false);
|
||||
void VerifyDBFromMap(
|
||||
std::map<std::string, std::string> true_data,
|
||||
size_t* total_reads_res = nullptr, bool tailing_iter = false,
|
||||
std::map<std::string, Status> status = std::map<std::string, Status>());
|
||||
|
||||
void VerifyDBInternal(
|
||||
std::vector<std::pair<std::string, std::string>> true_data);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
uint64_t GetNumberOfSstFilesForColumnFamily(DB* db,
|
||||
|
||||
+3
-2
@@ -528,10 +528,11 @@ struct RangeTombstone {
|
||||
Slice start_key_;
|
||||
Slice end_key_;
|
||||
SequenceNumber seq_;
|
||||
explicit RangeTombstone(Slice sk, Slice ek, SequenceNumber sn)
|
||||
RangeTombstone() = default;
|
||||
RangeTombstone(Slice sk, Slice ek, SequenceNumber sn)
|
||||
: start_key_(sk), end_key_(ek), seq_(sn) {}
|
||||
|
||||
explicit RangeTombstone(ParsedInternalKey parsed_key, Slice value) {
|
||||
RangeTombstone(ParsedInternalKey parsed_key, Slice value) {
|
||||
start_key_ = parsed_key.user_key;
|
||||
seq_ = parsed_key.sequence;
|
||||
end_key_ = value;
|
||||
|
||||
+1
-1
@@ -128,7 +128,7 @@ void EventHelpers::LogAndNotifyTableFileDeletion(
|
||||
info.job_id = job_id;
|
||||
info.file_path = file_path;
|
||||
info.status = status;
|
||||
for (auto listener : listeners) {
|
||||
for (auto& listener : listeners) {
|
||||
listener->OnTableFileDeleted(info);
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
@@ -38,6 +38,15 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
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) {
|
||||
@@ -87,10 +96,12 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
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);
|
||||
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);
|
||||
status = CopyFile(env_, path_outside_db, path_inside_db, 0,
|
||||
db_options_.use_fsync);
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::AddFile:FileCopied");
|
||||
if (!status.ok()) {
|
||||
@@ -302,8 +313,14 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
file_to_ingest->num_entries = props->num_entries;
|
||||
|
||||
ParsedInternalKey key;
|
||||
std::unique_ptr<InternalIterator> iter(
|
||||
table_reader->NewIterator(ReadOptions()));
|
||||
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();
|
||||
@@ -325,6 +342,10 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,10 @@ struct IngestedFileInfo {
|
||||
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;
|
||||
|
||||
@@ -96,6 +100,10 @@ class ExternalSstFileIngestionJob {
|
||||
|
||||
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.
|
||||
|
||||
+216
-10
@@ -3,6 +3,8 @@
|
||||
// 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 <functional>
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/port.h"
|
||||
#include "port/stack_trace.h"
|
||||
@@ -15,7 +17,7 @@ namespace rocksdb {
|
||||
class ExternalSSTFileTest : public DBTestBase {
|
||||
public:
|
||||
ExternalSSTFileTest() : DBTestBase("/external_sst_file_test") {
|
||||
sst_files_dir_ = test::TmpDir(env_) + "/sst_files/";
|
||||
sst_files_dir_ = dbname_ + "/sst_files/";
|
||||
DestroyAndRecreateExternalSSTFilesDir();
|
||||
}
|
||||
|
||||
@@ -28,7 +30,8 @@ class ExternalSSTFileTest : public DBTestBase {
|
||||
const Options options,
|
||||
std::vector<std::pair<std::string, std::string>> data, int file_id = -1,
|
||||
bool allow_global_seqno = false, bool sort_data = false,
|
||||
std::map<std::string, std::string>* true_data = nullptr) {
|
||||
std::map<std::string, std::string>* true_data = nullptr,
|
||||
ColumnFamilyHandle* cfh = nullptr) {
|
||||
// Generate a file id if not provided
|
||||
if (file_id == -1) {
|
||||
file_id = last_file_id_ + 1;
|
||||
@@ -51,7 +54,8 @@ class ExternalSSTFileTest : public DBTestBase {
|
||||
data.resize(uniq_iter - data.begin());
|
||||
}
|
||||
std::string file_path = sst_files_dir_ + ToString(file_id);
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options, options.comparator);
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options, options.comparator,
|
||||
cfh);
|
||||
|
||||
Status s = sst_file_writer.Open(file_path);
|
||||
if (!s.ok()) {
|
||||
@@ -69,7 +73,11 @@ class ExternalSSTFileTest : public DBTestBase {
|
||||
if (s.ok()) {
|
||||
IngestExternalFileOptions ifo;
|
||||
ifo.allow_global_seqno = allow_global_seqno;
|
||||
s = db_->IngestExternalFile({file_path}, ifo);
|
||||
if (cfh) {
|
||||
s = db_->IngestExternalFile(cfh, {file_path}, ifo);
|
||||
} else {
|
||||
s = db_->IngestExternalFile({file_path}, ifo);
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok() && true_data) {
|
||||
@@ -84,25 +92,29 @@ class ExternalSSTFileTest : public DBTestBase {
|
||||
Status GenerateAndAddExternalFile(
|
||||
const Options options, std::vector<std::pair<int, std::string>> data,
|
||||
int file_id = -1, bool allow_global_seqno = false, bool sort_data = false,
|
||||
std::map<std::string, std::string>* true_data = nullptr) {
|
||||
std::map<std::string, std::string>* true_data = nullptr,
|
||||
ColumnFamilyHandle* cfh = nullptr) {
|
||||
std::vector<std::pair<std::string, std::string>> file_data;
|
||||
for (auto& entry : data) {
|
||||
file_data.emplace_back(Key(entry.first), entry.second);
|
||||
}
|
||||
return GenerateAndAddExternalFile(options, file_data, file_id,
|
||||
allow_global_seqno, sort_data, true_data);
|
||||
allow_global_seqno, sort_data, true_data,
|
||||
cfh);
|
||||
}
|
||||
|
||||
Status GenerateAndAddExternalFile(
|
||||
const Options options, std::vector<int> keys, int file_id = -1,
|
||||
bool allow_global_seqno = false, bool sort_data = false,
|
||||
std::map<std::string, std::string>* true_data = nullptr) {
|
||||
std::map<std::string, std::string>* true_data = nullptr,
|
||||
ColumnFamilyHandle* cfh = nullptr) {
|
||||
std::vector<std::pair<std::string, std::string>> file_data;
|
||||
for (auto& k : keys) {
|
||||
file_data.emplace_back(Key(k), Key(k) + ToString(file_id));
|
||||
}
|
||||
return GenerateAndAddExternalFile(options, file_data, file_id,
|
||||
allow_global_seqno, sort_data, true_data);
|
||||
allow_global_seqno, sort_data, true_data,
|
||||
cfh);
|
||||
}
|
||||
|
||||
Status DeprecatedAddFile(const std::vector<std::string>& files,
|
||||
@@ -129,6 +141,9 @@ TEST_F(ExternalSSTFileTest, Basic) {
|
||||
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options, options.comparator);
|
||||
|
||||
// Current file size should be 0 after sst_file_writer init and before open a file.
|
||||
ASSERT_EQ(sst_file_writer.FileSize(), 0);
|
||||
|
||||
// file1.sst (0 => 99)
|
||||
std::string file1 = sst_files_dir_ + "file1.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file1));
|
||||
@@ -138,6 +153,10 @@ TEST_F(ExternalSSTFileTest, Basic) {
|
||||
ExternalSstFileInfo file1_info;
|
||||
Status s = sst_file_writer.Finish(&file1_info);
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
|
||||
// Current file size should be non-zero after success write.
|
||||
ASSERT_GT(sst_file_writer.FileSize(), 0);
|
||||
|
||||
ASSERT_EQ(file1_info.file_path, file1);
|
||||
ASSERT_EQ(file1_info.num_entries, 100);
|
||||
ASSERT_EQ(file1_info.smallest_key, Key(0));
|
||||
@@ -172,7 +191,10 @@ TEST_F(ExternalSSTFileTest, Basic) {
|
||||
}
|
||||
ExternalSstFileInfo file3_info;
|
||||
s = sst_file_writer.Finish(&file3_info);
|
||||
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
// Current file size should be non-zero after success finish.
|
||||
ASSERT_GT(sst_file_writer.FileSize(), 0);
|
||||
ASSERT_EQ(file3_info.file_path, file3);
|
||||
ASSERT_EQ(file3_info.num_entries, 105);
|
||||
ASSERT_EQ(file3_info.smallest_key, Key(195));
|
||||
@@ -1738,8 +1760,6 @@ TEST_F(ExternalSSTFileTest, CompactionDeadlock) {
|
||||
// `DBImpl::AddFile:Start` will wait until we be here
|
||||
TEST_SYNC_POINT("ExternalSSTFileTest::DeadLock:1");
|
||||
|
||||
ASSERT_EQ(running_threads.load(), 2);
|
||||
|
||||
// Wait for IngestExternalFile() to start and aquire mutex
|
||||
TEST_SYNC_POINT("ExternalSSTFileTest::DeadLock:2");
|
||||
|
||||
@@ -1780,6 +1800,192 @@ TEST_F(ExternalSSTFileTest, DirtyExit) {
|
||||
ASSERT_NOK(sst_file_writer->Finish());
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileTest, FileWithCFInfo) {
|
||||
Options options = CurrentOptions();
|
||||
CreateAndReopenWithCF({"koko", "toto"}, options);
|
||||
|
||||
SstFileWriter sfw_default(EnvOptions(), options, options.comparator,
|
||||
handles_[0]);
|
||||
SstFileWriter sfw_cf1(EnvOptions(), options, options.comparator, handles_[1]);
|
||||
SstFileWriter sfw_cf2(EnvOptions(), options, options.comparator, handles_[2]);
|
||||
SstFileWriter sfw_unknown(EnvOptions(), options, options.comparator);
|
||||
|
||||
// default_cf.sst
|
||||
const std::string cf_default_sst = sst_files_dir_ + "/default_cf.sst";
|
||||
ASSERT_OK(sfw_default.Open(cf_default_sst));
|
||||
ASSERT_OK(sfw_default.Add("K1", "V1"));
|
||||
ASSERT_OK(sfw_default.Add("K2", "V2"));
|
||||
ASSERT_OK(sfw_default.Finish());
|
||||
|
||||
// cf1.sst
|
||||
const std::string cf1_sst = sst_files_dir_ + "/cf1.sst";
|
||||
ASSERT_OK(sfw_cf1.Open(cf1_sst));
|
||||
ASSERT_OK(sfw_cf1.Add("K3", "V1"));
|
||||
ASSERT_OK(sfw_cf1.Add("K4", "V2"));
|
||||
ASSERT_OK(sfw_cf1.Finish());
|
||||
|
||||
// cf_unknown.sst
|
||||
const std::string unknown_sst = sst_files_dir_ + "/cf_unknown.sst";
|
||||
ASSERT_OK(sfw_unknown.Open(unknown_sst));
|
||||
ASSERT_OK(sfw_unknown.Add("K5", "V1"));
|
||||
ASSERT_OK(sfw_unknown.Add("K6", "V2"));
|
||||
ASSERT_OK(sfw_unknown.Finish());
|
||||
|
||||
IngestExternalFileOptions ifo;
|
||||
|
||||
// SST CF dont match
|
||||
ASSERT_NOK(db_->IngestExternalFile(handles_[0], {cf1_sst}, ifo));
|
||||
// SST CF dont match
|
||||
ASSERT_NOK(db_->IngestExternalFile(handles_[2], {cf1_sst}, ifo));
|
||||
// SST CF match
|
||||
ASSERT_OK(db_->IngestExternalFile(handles_[1], {cf1_sst}, ifo));
|
||||
|
||||
// SST CF dont match
|
||||
ASSERT_NOK(db_->IngestExternalFile(handles_[1], {cf_default_sst}, ifo));
|
||||
// SST CF dont match
|
||||
ASSERT_NOK(db_->IngestExternalFile(handles_[2], {cf_default_sst}, ifo));
|
||||
// SST CF match
|
||||
ASSERT_OK(db_->IngestExternalFile(handles_[0], {cf_default_sst}, ifo));
|
||||
|
||||
// SST CF unknown
|
||||
ASSERT_OK(db_->IngestExternalFile(handles_[1], {unknown_sst}, ifo));
|
||||
// SST CF unknown
|
||||
ASSERT_OK(db_->IngestExternalFile(handles_[2], {unknown_sst}, ifo));
|
||||
// SST CF unknown
|
||||
ASSERT_OK(db_->IngestExternalFile(handles_[0], {unknown_sst}, ifo));
|
||||
|
||||
// Cannot ingest a file into a dropped CF
|
||||
ASSERT_OK(db_->DropColumnFamily(handles_[1]));
|
||||
ASSERT_NOK(db_->IngestExternalFile(handles_[1], {unknown_sst}, ifo));
|
||||
|
||||
// CF was not dropped, ok to Ingest
|
||||
ASSERT_OK(db_->IngestExternalFile(handles_[2], {unknown_sst}, ifo));
|
||||
}
|
||||
|
||||
class TestIngestExternalFileListener : public EventListener {
|
||||
public:
|
||||
void OnExternalFileIngested(DB* db,
|
||||
const ExternalFileIngestionInfo& info) override {
|
||||
ingested_files.push_back(info);
|
||||
}
|
||||
|
||||
std::vector<ExternalFileIngestionInfo> ingested_files;
|
||||
};
|
||||
|
||||
TEST_F(ExternalSSTFileTest, IngestionListener) {
|
||||
Options options = CurrentOptions();
|
||||
TestIngestExternalFileListener* listener =
|
||||
new TestIngestExternalFileListener();
|
||||
options.listeners.emplace_back(listener);
|
||||
CreateAndReopenWithCF({"koko", "toto"}, options);
|
||||
|
||||
// Ingest into default cf
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1, 2}, -1, true, true, nullptr,
|
||||
handles_[0]));
|
||||
ASSERT_EQ(listener->ingested_files.size(), 1);
|
||||
ASSERT_EQ(listener->ingested_files.back().cf_name, "default");
|
||||
ASSERT_EQ(listener->ingested_files.back().global_seqno, 0);
|
||||
ASSERT_EQ(listener->ingested_files.back().table_properties.column_family_id,
|
||||
0);
|
||||
ASSERT_EQ(listener->ingested_files.back().table_properties.column_family_name,
|
||||
"default");
|
||||
|
||||
// Ingest into cf1
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1, 2}, -1, true, true, nullptr,
|
||||
handles_[1]));
|
||||
ASSERT_EQ(listener->ingested_files.size(), 2);
|
||||
ASSERT_EQ(listener->ingested_files.back().cf_name, "koko");
|
||||
ASSERT_EQ(listener->ingested_files.back().global_seqno, 0);
|
||||
ASSERT_EQ(listener->ingested_files.back().table_properties.column_family_id,
|
||||
1);
|
||||
ASSERT_EQ(listener->ingested_files.back().table_properties.column_family_name,
|
||||
"koko");
|
||||
|
||||
// Ingest into cf2
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1, 2}, -1, true, true, nullptr,
|
||||
handles_[2]));
|
||||
ASSERT_EQ(listener->ingested_files.size(), 3);
|
||||
ASSERT_EQ(listener->ingested_files.back().cf_name, "toto");
|
||||
ASSERT_EQ(listener->ingested_files.back().global_seqno, 0);
|
||||
ASSERT_EQ(listener->ingested_files.back().table_properties.column_family_id,
|
||||
2);
|
||||
ASSERT_EQ(listener->ingested_files.back().table_properties.column_family_name,
|
||||
"toto");
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileTest, SnapshotInconsistencyBug) {
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
const int kNumKeys = 10000;
|
||||
|
||||
// Insert keys using normal path and take a snapshot
|
||||
for (int i = 0; i < kNumKeys; i++) {
|
||||
ASSERT_OK(Put(Key(i), Key(i) + "_V1"));
|
||||
}
|
||||
const Snapshot* snap = db_->GetSnapshot();
|
||||
|
||||
// Overwrite all keys using IngestExternalFile
|
||||
std::string sst_file_path = sst_files_dir_ + "file1.sst";
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options, options.comparator);
|
||||
ASSERT_OK(sst_file_writer.Open(sst_file_path));
|
||||
for (int i = 0; i < kNumKeys; i++) {
|
||||
ASSERT_OK(sst_file_writer.Add(Key(i), Key(i) + "_V2"));
|
||||
}
|
||||
ASSERT_OK(sst_file_writer.Finish());
|
||||
|
||||
IngestExternalFileOptions ifo;
|
||||
ifo.move_files = true;
|
||||
ASSERT_OK(db_->IngestExternalFile({sst_file_path}, ifo));
|
||||
|
||||
for (int i = 0; i < kNumKeys; i++) {
|
||||
ASSERT_EQ(Get(Key(i), snap), Key(i) + "_V1");
|
||||
ASSERT_EQ(Get(Key(i)), Key(i) + "_V2");
|
||||
}
|
||||
|
||||
db_->ReleaseSnapshot(snap);
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileTest, FadviseTrigger) {
|
||||
Options options = CurrentOptions();
|
||||
const int kNumKeys = 10000;
|
||||
|
||||
size_t total_fadvised_bytes = 0;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"SstFileWriter::InvalidatePageCache", [&](void* arg) {
|
||||
size_t fadvise_size = *(reinterpret_cast<size_t*>(arg));
|
||||
total_fadvised_bytes += fadvise_size;
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::unique_ptr<SstFileWriter> sst_file_writer;
|
||||
|
||||
std::string sst_file_path = sst_files_dir_ + "file_fadvise_disable.sst";
|
||||
sst_file_writer.reset(new SstFileWriter(EnvOptions(), options,
|
||||
options.comparator, nullptr, false));
|
||||
ASSERT_OK(sst_file_writer->Open(sst_file_path));
|
||||
for (int i = 0; i < kNumKeys; i++) {
|
||||
ASSERT_OK(sst_file_writer->Add(Key(i), Key(i)));
|
||||
}
|
||||
ASSERT_OK(sst_file_writer->Finish());
|
||||
// fadvise disabled
|
||||
ASSERT_EQ(total_fadvised_bytes, 0);
|
||||
|
||||
|
||||
sst_file_path = sst_files_dir_ + "file_fadvise_enable.sst";
|
||||
sst_file_writer.reset(new SstFileWriter(EnvOptions(), options,
|
||||
options.comparator, nullptr, true));
|
||||
ASSERT_OK(sst_file_writer->Open(sst_file_path));
|
||||
for (int i = 0; i < kNumKeys; i++) {
|
||||
ASSERT_OK(sst_file_writer->Add(Key(i), Key(i)));
|
||||
}
|
||||
ASSERT_OK(sst_file_writer->Finish());
|
||||
// fadvise enabled
|
||||
ASSERT_EQ(total_fadvised_bytes, sst_file_writer->FileSize());
|
||||
ASSERT_GT(total_fadvised_bytes, 0);
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
+2
-1
@@ -9,8 +9,9 @@
|
||||
|
||||
#include "db/file_indexer.h"
|
||||
#include <algorithm>
|
||||
#include "rocksdb/comparator.h"
|
||||
#include <functional>
|
||||
#include "db/version_edit.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
|
||||
+8
-1
@@ -230,6 +230,12 @@ Status FlushJob::Run(FileMetaData* file_meta) {
|
||||
return s;
|
||||
}
|
||||
|
||||
void FlushJob::Cancel() {
|
||||
db_mutex_->AssertHeld();
|
||||
assert(base_ != nullptr);
|
||||
base_->Unref();
|
||||
}
|
||||
|
||||
Status FlushJob::WriteLevel0Table() {
|
||||
AutoThreadOperationStageUpdater stage_updater(
|
||||
ThreadStatus::STAGE_FLUSH_WRITE_L0);
|
||||
@@ -277,7 +283,8 @@ Status FlushJob::WriteLevel0Table() {
|
||||
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[0],
|
||||
&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",
|
||||
|
||||
+3
-1
@@ -67,9 +67,11 @@ class FlushJob {
|
||||
|
||||
~FlushJob();
|
||||
|
||||
// Require db_mutex held
|
||||
// Require db_mutex held.
|
||||
// Once PickMemTable() is called, either Run() or Cancel() has to be call.
|
||||
void PickMemTable();
|
||||
Status Run(FileMetaData* file_meta = nullptr);
|
||||
void Cancel();
|
||||
TableProperties GetTableProperties() const { return table_properties_; }
|
||||
|
||||
private:
|
||||
|
||||
@@ -330,7 +330,7 @@ int main(int argc, char** argv) {
|
||||
options.compaction_style = rocksdb::CompactionStyle::kCompactionStyleNone;
|
||||
options.level0_slowdown_writes_trigger = 99999;
|
||||
options.level0_stop_writes_trigger = 99999;
|
||||
options.allow_os_buffer = false;
|
||||
options.use_direct_writes = true;
|
||||
options.write_buffer_size = FLAGS_memtable_size;
|
||||
rocksdb::BlockBasedTableOptions table_options;
|
||||
table_options.block_cache = rocksdb::NewLRUCache(FLAGS_block_cache_size);
|
||||
|
||||
+15
-5
@@ -221,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 =
|
||||
@@ -285,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;
|
||||
@@ -368,6 +371,8 @@ const std::unordered_map<std::string, DBPropertyInfo>
|
||||
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,
|
||||
@@ -705,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
|
||||
@@ -854,7 +865,7 @@ void InternalStats::DumpCFMapStats(std::map<std::string, double>* cf_stats) {
|
||||
}
|
||||
}
|
||||
|
||||
int InternalStats::DumpCFMapStats(
|
||||
void InternalStats::DumpCFMapStats(
|
||||
std::map<int, std::map<LevelStatType, double>>* levels_stats,
|
||||
CompactionStats* compaction_stats_sum) {
|
||||
const VersionStorageInfo* vstorage = cfd_->current()->storage_info();
|
||||
@@ -914,11 +925,10 @@ int InternalStats::DumpCFMapStats(
|
||||
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];
|
||||
char buf[2000];
|
||||
// Per-ColumnFamily stats
|
||||
PrintLevelStatsHeader(buf, sizeof(buf), cfd_->GetName());
|
||||
value->append(buf);
|
||||
@@ -926,8 +936,8 @@ void InternalStats::DumpCFStats(std::string* value) {
|
||||
// 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) {
|
||||
DumpCFMapStats(&levels_stats, &compaction_stats_sum);
|
||||
for (int l = 0; l < number_levels_; ++l) {
|
||||
if (levels_stats.find(l) != levels_stats.end()) {
|
||||
PrintLevelStats(buf, sizeof(buf), "L" + ToString(l), levels_stats[l]);
|
||||
value->append(buf);
|
||||
|
||||
+2
-1
@@ -273,7 +273,7 @@ class InternalStats {
|
||||
private:
|
||||
void DumpDBStats(std::string* value);
|
||||
void DumpCFMapStats(std::map<std::string, double>* cf_stats);
|
||||
int DumpCFMapStats(
|
||||
void DumpCFMapStats(
|
||||
std::map<int, std::map<LevelStatType, double>>* level_stats,
|
||||
CompactionStats* compaction_stats_sum);
|
||||
void DumpCFStats(std::string* value);
|
||||
@@ -401,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
|
||||
|
||||
+6
-6
@@ -119,7 +119,7 @@ TEST_F(EventListenerTest, OnSingleDBCompactionTest) {
|
||||
options.max_bytes_for_level_base = options.target_file_size_base * 2;
|
||||
options.max_bytes_for_level_multiplier = 2;
|
||||
options.compression = kNoCompression;
|
||||
#if ROCKSDB_USING_THREAD_STATUS
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
options.enable_thread_tracking = true;
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
options.level0_file_num_compaction_trigger = kNumL0Files;
|
||||
@@ -176,7 +176,7 @@ class TestFlushListener : public EventListener {
|
||||
ASSERT_GT(info.table_properties.num_data_blocks, 0U);
|
||||
ASSERT_GT(info.table_properties.num_entries, 0U);
|
||||
|
||||
#if ROCKSDB_USING_THREAD_STATUS
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
// Verify the id of the current thread that created this table
|
||||
// file matches the id of any active flush or compaction thread.
|
||||
uint64_t thread_id = env_->GetThreadID();
|
||||
@@ -232,7 +232,7 @@ class TestFlushListener : public EventListener {
|
||||
TEST_F(EventListenerTest, OnSingleDBFlushTest) {
|
||||
Options options;
|
||||
options.write_buffer_size = k110KB;
|
||||
#if ROCKSDB_USING_THREAD_STATUS
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
options.enable_thread_tracking = true;
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
TestFlushListener* listener = new TestFlushListener(options.env);
|
||||
@@ -268,7 +268,7 @@ TEST_F(EventListenerTest, OnSingleDBFlushTest) {
|
||||
TEST_F(EventListenerTest, MultiCF) {
|
||||
Options options;
|
||||
options.write_buffer_size = k110KB;
|
||||
#if ROCKSDB_USING_THREAD_STATUS
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
options.enable_thread_tracking = true;
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
TestFlushListener* listener = new TestFlushListener(options.env);
|
||||
@@ -302,7 +302,7 @@ TEST_F(EventListenerTest, MultiCF) {
|
||||
|
||||
TEST_F(EventListenerTest, MultiDBMultiListeners) {
|
||||
Options options;
|
||||
#if ROCKSDB_USING_THREAD_STATUS
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
options.enable_thread_tracking = true;
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
options.table_properties_collector_factories.push_back(
|
||||
@@ -384,7 +384,7 @@ TEST_F(EventListenerTest, MultiDBMultiListeners) {
|
||||
|
||||
TEST_F(EventListenerTest, DisableBGCompaction) {
|
||||
Options options;
|
||||
#if ROCKSDB_USING_THREAD_STATUS
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
options.enable_thread_tracking = true;
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
TestFlushListener* listener = new TestFlushListener(options.env);
|
||||
|
||||
+15
-22
@@ -560,7 +560,7 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
ValueType type;
|
||||
UnPackSequenceAndType(tag, &s->seq, &type);
|
||||
|
||||
if ((type == kTypeValue || type == kTypeDeletion) &&
|
||||
if ((type == kTypeValue || type == kTypeMerge) &&
|
||||
range_del_agg->ShouldDelete(Slice(key_ptr, key_length))) {
|
||||
type = kTypeRangeDeletion;
|
||||
}
|
||||
@@ -717,29 +717,22 @@ void MemTable::Update(SequenceNumber seq,
|
||||
ValueType type;
|
||||
SequenceNumber unused;
|
||||
UnPackSequenceAndType(tag, &unused, &type);
|
||||
switch (type) {
|
||||
case kTypeValue: {
|
||||
Slice prev_value = GetLengthPrefixedSlice(key_ptr + key_length);
|
||||
uint32_t prev_size = static_cast<uint32_t>(prev_value.size());
|
||||
uint32_t new_size = static_cast<uint32_t>(value.size());
|
||||
if (type == kTypeValue) {
|
||||
Slice prev_value = GetLengthPrefixedSlice(key_ptr + key_length);
|
||||
uint32_t prev_size = static_cast<uint32_t>(prev_value.size());
|
||||
uint32_t new_size = static_cast<uint32_t>(value.size());
|
||||
|
||||
// Update value, if new value size <= previous value size
|
||||
if (new_size <= prev_size ) {
|
||||
char* p = EncodeVarint32(const_cast<char*>(key_ptr) + key_length,
|
||||
new_size);
|
||||
WriteLock wl(GetLock(lkey.user_key()));
|
||||
memcpy(p, value.data(), value.size());
|
||||
assert((unsigned)((p + value.size()) - entry) ==
|
||||
(unsigned)(VarintLength(key_length) + key_length +
|
||||
VarintLength(value.size()) + value.size()));
|
||||
return;
|
||||
}
|
||||
// Update value, if new value size <= previous value size
|
||||
if (new_size <= prev_size ) {
|
||||
char* p = EncodeVarint32(const_cast<char*>(key_ptr) + key_length,
|
||||
new_size);
|
||||
WriteLock wl(GetLock(lkey.user_key()));
|
||||
memcpy(p, value.data(), value.size());
|
||||
assert((unsigned)((p + value.size()) - entry) ==
|
||||
(unsigned)(VarintLength(key_length) + key_length +
|
||||
VarintLength(value.size()) + value.size()));
|
||||
return;
|
||||
}
|
||||
default:
|
||||
// If the latest value is kTypeDeletion, kTypeMerge or kTypeLogData
|
||||
// we don't have enough space for update inplace
|
||||
Add(seq, kTypeValue, key, value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,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&);
|
||||
|
||||
+8
-5
@@ -104,6 +104,10 @@ Status MergeHelper::MergeUntil(InternalIterator* iter,
|
||||
Status s;
|
||||
bool hit_the_next_user_key = false;
|
||||
for (; iter->Valid(); iter->Next(), original_key_is_iter = false) {
|
||||
if (IsShuttingDown()) {
|
||||
return Status::ShutdownInProgress();
|
||||
}
|
||||
|
||||
ParsedInternalKey ikey;
|
||||
assert(keys_.size() == merge_context_.GetNumOperands());
|
||||
|
||||
@@ -192,7 +196,10 @@ Status MergeHelper::MergeUntil(InternalIterator* iter,
|
||||
? CompactionFilter::Decision::kKeep
|
||||
: FilterMerge(orig_ikey.user_key, value_slice);
|
||||
if (range_del_agg != nullptr &&
|
||||
range_del_agg->ShouldDelete(iter->key()) &&
|
||||
|
||||
range_del_agg->ShouldDelete(
|
||||
iter->key(),
|
||||
RangeDelAggregator::RangePositioningMode::kForwardTraversal) &&
|
||||
filter != CompactionFilter::Decision::kRemoveAndSkipUntil) {
|
||||
filter = CompactionFilter::Decision::kRemove;
|
||||
}
|
||||
@@ -275,10 +282,6 @@ Status MergeHelper::MergeUntil(InternalIterator* iter,
|
||||
// We haven't seen the beginning of the key nor a Put/Delete.
|
||||
// Attempt to use the user's associative merge function to
|
||||
// merge the stacked merge operands into a single operand.
|
||||
//
|
||||
// TODO(noetzli) The docblock of MergeUntil suggests that a successful
|
||||
// partial merge returns Status::OK(). Should we change the status code
|
||||
// after a successful partial merge?
|
||||
s = Status::MergeInProgress();
|
||||
if (merge_context_.GetNumOperands() >= 2 &&
|
||||
merge_context_.GetNumOperands() >= min_partial_merge_operands_) {
|
||||
|
||||
+12
-2
@@ -34,11 +34,13 @@ class MergeHelper {
|
||||
const CompactionFilter* compaction_filter, Logger* logger,
|
||||
unsigned min_partial_merge_operands,
|
||||
bool assert_valid_internal_key, SequenceNumber latest_snapshot,
|
||||
int level = 0, Statistics* stats = nullptr)
|
||||
int level = 0, Statistics* stats = nullptr,
|
||||
const std::atomic<bool>* shutting_down = nullptr)
|
||||
: env_(env),
|
||||
user_comparator_(user_comparator),
|
||||
user_merge_operator_(user_merge_operator),
|
||||
compaction_filter_(compaction_filter),
|
||||
shutting_down_(shutting_down),
|
||||
logger_(logger),
|
||||
min_partial_merge_operands_(min_partial_merge_operands),
|
||||
assert_valid_internal_key_(assert_valid_internal_key),
|
||||
@@ -81,10 +83,12 @@ class MergeHelper {
|
||||
//
|
||||
// Returns one of the following statuses:
|
||||
// - OK: Entries were successfully merged.
|
||||
// - MergeInProgress: Put/Delete not encountered and unable to merge operands.
|
||||
// - MergeInProgress: Put/Delete not encountered, and didn't reach the start
|
||||
// of key's history. Output consists of merge operands only.
|
||||
// - Corruption: Merge operator reported unsuccessful merge or a corrupted
|
||||
// key has been encountered and not expected (applies only when compiling
|
||||
// with asserts removed).
|
||||
// - ShutdownInProgress: interrupted by shutdown (*shutting_down == true).
|
||||
//
|
||||
// REQUIRED: The first key in the input is not corrupted.
|
||||
Status MergeUntil(InternalIterator* iter,
|
||||
@@ -150,6 +154,7 @@ class MergeHelper {
|
||||
const Comparator* user_comparator_;
|
||||
const MergeOperator* user_merge_operator_;
|
||||
const CompactionFilter* compaction_filter_;
|
||||
const std::atomic<bool>* shutting_down_;
|
||||
Logger* logger_;
|
||||
unsigned min_partial_merge_operands_;
|
||||
bool assert_valid_internal_key_; // enforce no internal key corruption?
|
||||
@@ -171,6 +176,11 @@ class MergeHelper {
|
||||
bool has_compaction_filter_skip_until_ = false;
|
||||
std::string compaction_filter_value_;
|
||||
InternalKey compaction_filter_skip_until_;
|
||||
|
||||
bool IsShuttingDown() {
|
||||
// This is a best-effort facility, so memory_order_relaxed is sufficient.
|
||||
return shutting_down_ && shutting_down_->load(std::memory_order_relaxed);
|
||||
}
|
||||
};
|
||||
|
||||
// MergeOutputIterator can be used to iterate over the result of a merge.
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace rocksdb {
|
||||
// PinnedIteratorsManager will be notified whenever we need to pin an Iterator
|
||||
// and it will be responsible for deleting pinned Iterators when they are
|
||||
// not needed anymore.
|
||||
class PinnedIteratorsManager {
|
||||
class PinnedIteratorsManager : public Cleanable {
|
||||
public:
|
||||
PinnedIteratorsManager() : pinning_enabled(false) {}
|
||||
~PinnedIteratorsManager() {
|
||||
@@ -59,16 +59,16 @@ class PinnedIteratorsManager {
|
||||
|
||||
// Remove duplicate pointers
|
||||
std::sort(pinned_ptrs_.begin(), pinned_ptrs_.end());
|
||||
std::unique(pinned_ptrs_.begin(), pinned_ptrs_.end());
|
||||
auto unique_end = std::unique(pinned_ptrs_.begin(), pinned_ptrs_.end());
|
||||
|
||||
for (size_t i = 0; i < pinned_ptrs_.size(); i++) {
|
||||
assert(i == 0 || pinned_ptrs_[i].first != pinned_ptrs_[i - 1].first);
|
||||
|
||||
void* ptr = pinned_ptrs_[i].first;
|
||||
ReleaseFunction release_func = pinned_ptrs_[i].second;
|
||||
for (auto i = pinned_ptrs_.begin(); i != unique_end; ++i) {
|
||||
void* ptr = i->first;
|
||||
ReleaseFunction release_func = i->second;
|
||||
release_func(ptr);
|
||||
}
|
||||
pinned_ptrs_.clear();
|
||||
// Also do cleanups from the base Cleanable
|
||||
Cleanable::Reset();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
+302
-34
@@ -11,29 +11,40 @@ namespace rocksdb {
|
||||
|
||||
RangeDelAggregator::RangeDelAggregator(
|
||||
const InternalKeyComparator& icmp,
|
||||
const std::vector<SequenceNumber>& snapshots)
|
||||
: upper_bound_(kMaxSequenceNumber), icmp_(icmp) {
|
||||
const std::vector<SequenceNumber>& snapshots,
|
||||
bool collapse_deletions /* = true */)
|
||||
: upper_bound_(kMaxSequenceNumber),
|
||||
icmp_(icmp),
|
||||
collapse_deletions_(collapse_deletions) {
|
||||
InitRep(snapshots);
|
||||
}
|
||||
|
||||
RangeDelAggregator::RangeDelAggregator(const InternalKeyComparator& icmp,
|
||||
SequenceNumber snapshot)
|
||||
: upper_bound_(snapshot), icmp_(icmp) {}
|
||||
SequenceNumber snapshot,
|
||||
bool collapse_deletions /* = false */)
|
||||
: upper_bound_(snapshot),
|
||||
icmp_(icmp),
|
||||
collapse_deletions_(collapse_deletions) {}
|
||||
|
||||
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_)));
|
||||
snapshot,
|
||||
PositionalTombstoneMap(TombstoneMap(
|
||||
stl_wrappers::LessOfComparator(icmp_.user_comparator()))));
|
||||
}
|
||||
// Data newer than any snapshot falls in this catch-all stripe
|
||||
rep_->stripe_map_.emplace(
|
||||
kMaxSequenceNumber, TombstoneMap(stl_wrappers::LessOfComparator(&icmp_)));
|
||||
kMaxSequenceNumber,
|
||||
PositionalTombstoneMap(TombstoneMap(
|
||||
stl_wrappers::LessOfComparator(icmp_.user_comparator()))));
|
||||
rep_->pinned_iters_mgr_.StartPinning();
|
||||
}
|
||||
|
||||
bool RangeDelAggregator::ShouldDelete(const Slice& internal_key) {
|
||||
bool RangeDelAggregator::ShouldDelete(
|
||||
const Slice& internal_key, RangeDelAggregator::RangePositioningMode mode) {
|
||||
if (rep_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
@@ -41,32 +52,103 @@ bool RangeDelAggregator::ShouldDelete(const Slice& internal_key) {
|
||||
if (!ParseInternalKey(internal_key, &parsed)) {
|
||||
assert(false);
|
||||
}
|
||||
return ShouldDelete(parsed);
|
||||
return ShouldDelete(parsed, mode);
|
||||
}
|
||||
|
||||
bool RangeDelAggregator::ShouldDelete(const ParsedInternalKey& parsed) {
|
||||
bool RangeDelAggregator::ShouldDelete(
|
||||
const ParsedInternalKey& parsed,
|
||||
RangeDelAggregator::RangePositioningMode mode) {
|
||||
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;
|
||||
}
|
||||
auto& positional_tombstone_map = GetPositionalTombstoneMap(parsed.sequence);
|
||||
const auto& tombstone_map = positional_tombstone_map.raw_map;
|
||||
if (tombstone_map.empty()) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
auto& tombstone_map_iter = positional_tombstone_map.iter;
|
||||
if (tombstone_map_iter == tombstone_map.end() &&
|
||||
(mode == kForwardTraversal || mode == kBackwardTraversal)) {
|
||||
// invalid (e.g., if AddTombstones() changed the deletions), so need to
|
||||
// reseek
|
||||
mode = kBinarySearch;
|
||||
}
|
||||
switch (mode) {
|
||||
case kFullScan:
|
||||
assert(!collapse_deletions_);
|
||||
// The maintained state (PositionalTombstoneMap::iter) isn't useful when
|
||||
// we linear scan from the beginning each time, but we maintain it anyways
|
||||
// for consistency.
|
||||
tombstone_map_iter = tombstone_map.begin();
|
||||
while (tombstone_map_iter != tombstone_map.end()) {
|
||||
const auto& tombstone = tombstone_map_iter->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;
|
||||
}
|
||||
++tombstone_map_iter;
|
||||
}
|
||||
return false;
|
||||
case kForwardTraversal:
|
||||
assert(collapse_deletions_ && tombstone_map_iter != tombstone_map.end());
|
||||
if (tombstone_map_iter == tombstone_map.begin() &&
|
||||
icmp_.user_comparator()->Compare(parsed.user_key,
|
||||
tombstone_map_iter->first) < 0) {
|
||||
// before start of deletion intervals
|
||||
return false;
|
||||
}
|
||||
while (std::next(tombstone_map_iter) != tombstone_map.end() &&
|
||||
icmp_.user_comparator()->Compare(
|
||||
std::next(tombstone_map_iter)->first, parsed.user_key) <= 0) {
|
||||
++tombstone_map_iter;
|
||||
}
|
||||
break;
|
||||
case kBackwardTraversal:
|
||||
assert(collapse_deletions_ && tombstone_map_iter != tombstone_map.end());
|
||||
while (tombstone_map_iter != tombstone_map.begin() &&
|
||||
icmp_.user_comparator()->Compare(parsed.user_key,
|
||||
tombstone_map_iter->first) < 0) {
|
||||
--tombstone_map_iter;
|
||||
}
|
||||
if (tombstone_map_iter == tombstone_map.begin() &&
|
||||
icmp_.user_comparator()->Compare(parsed.user_key,
|
||||
tombstone_map_iter->first) < 0) {
|
||||
// before start of deletion intervals
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case kBinarySearch:
|
||||
assert(collapse_deletions_);
|
||||
tombstone_map_iter =
|
||||
tombstone_map.upper_bound(parsed.user_key);
|
||||
if (tombstone_map_iter == tombstone_map.begin()) {
|
||||
// before start of deletion intervals
|
||||
return false;
|
||||
}
|
||||
--tombstone_map_iter;
|
||||
break;
|
||||
}
|
||||
assert(mode != kFullScan);
|
||||
assert(tombstone_map_iter != tombstone_map.end() &&
|
||||
icmp_.user_comparator()->Compare(tombstone_map_iter->first,
|
||||
parsed.user_key) <= 0);
|
||||
assert(std::next(tombstone_map_iter) == tombstone_map.end() ||
|
||||
icmp_.user_comparator()->Compare(
|
||||
parsed.user_key, std::next(tombstone_map_iter)->first) < 0);
|
||||
return parsed.sequence < tombstone_map_iter->second.seq_;
|
||||
}
|
||||
|
||||
bool RangeDelAggregator::ShouldAddTombstones(
|
||||
bool bottommost_level /* = false */) {
|
||||
// TODO(andrewkr): can we just open a file and throw it away if it ends up
|
||||
// empty after AddToBuilder()? This function doesn't take into subcompaction
|
||||
// boundaries so isn't completely accurate.
|
||||
if (rep_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
@@ -78,7 +160,7 @@ bool RangeDelAggregator::ShouldAddTombstones(
|
||||
++stripe_map_iter;
|
||||
}
|
||||
while (stripe_map_iter != rep_->stripe_map_.end()) {
|
||||
if (!stripe_map_iter->second.empty()) {
|
||||
if (!stripe_map_iter->second.raw_map.empty()) {
|
||||
return true;
|
||||
}
|
||||
++stripe_map_iter;
|
||||
@@ -97,6 +179,8 @@ Status RangeDelAggregator::AddTombstones(
|
||||
if (first_iter) {
|
||||
if (rep_ == nullptr) {
|
||||
InitRep({upper_bound_});
|
||||
} else {
|
||||
InvalidateTombstoneMapPositions();
|
||||
}
|
||||
first_iter = false;
|
||||
}
|
||||
@@ -105,8 +189,7 @@ Status RangeDelAggregator::AddTombstones(
|
||||
return Status::Corruption("Unable to parse range tombstone InternalKey");
|
||||
}
|
||||
RangeTombstone tombstone(parsed_key, input->value());
|
||||
auto& tombstone_map = GetTombstoneMap(tombstone.seq_);
|
||||
tombstone_map.emplace(input->key(), std::move(tombstone));
|
||||
AddTombstone(std::move(tombstone));
|
||||
input->Next();
|
||||
}
|
||||
if (!first_iter) {
|
||||
@@ -115,8 +198,172 @@ Status RangeDelAggregator::AddTombstones(
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
RangeDelAggregator::TombstoneMap& RangeDelAggregator::GetTombstoneMap(
|
||||
SequenceNumber seq) {
|
||||
void RangeDelAggregator::InvalidateTombstoneMapPositions() {
|
||||
if (rep_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
for (auto stripe_map_iter = rep_->stripe_map_.begin();
|
||||
stripe_map_iter != rep_->stripe_map_.end(); ++stripe_map_iter) {
|
||||
stripe_map_iter->second.iter = stripe_map_iter->second.raw_map.end();
|
||||
}
|
||||
}
|
||||
|
||||
Status RangeDelAggregator::AddTombstone(RangeTombstone tombstone) {
|
||||
auto& positional_tombstone_map = GetPositionalTombstoneMap(tombstone.seq_);
|
||||
auto& tombstone_map = positional_tombstone_map.raw_map;
|
||||
if (collapse_deletions_) {
|
||||
// In collapsed mode, we only fill the seq_ field in the TombstoneMap's
|
||||
// values. The end_key is unneeded because we assume the tombstone extends
|
||||
// until the next tombstone starts. For gaps between real tombstones and
|
||||
// for the last real tombstone, we denote end keys by inserting fake
|
||||
// tombstones with sequence number zero.
|
||||
std::vector<RangeTombstone> new_range_dels{
|
||||
tombstone, RangeTombstone(tombstone.end_key_, Slice(), 0)};
|
||||
auto new_range_dels_iter = new_range_dels.begin();
|
||||
// Position at the first overlapping existing tombstone; if none exists,
|
||||
// insert until we find an existing one overlapping a new point
|
||||
const Slice* tombstone_map_begin = nullptr;
|
||||
if (!tombstone_map.empty()) {
|
||||
tombstone_map_begin = &tombstone_map.begin()->first;
|
||||
}
|
||||
auto last_range_dels_iter = new_range_dels_iter;
|
||||
while (new_range_dels_iter != new_range_dels.end() &&
|
||||
(tombstone_map_begin == nullptr ||
|
||||
icmp_.user_comparator()->Compare(new_range_dels_iter->start_key_,
|
||||
*tombstone_map_begin) < 0)) {
|
||||
tombstone_map.emplace(
|
||||
new_range_dels_iter->start_key_,
|
||||
RangeTombstone(Slice(), Slice(), new_range_dels_iter->seq_));
|
||||
last_range_dels_iter = new_range_dels_iter;
|
||||
++new_range_dels_iter;
|
||||
}
|
||||
if (new_range_dels_iter == new_range_dels.end()) {
|
||||
return Status::OK();
|
||||
}
|
||||
// above loop advances one too far
|
||||
new_range_dels_iter = last_range_dels_iter;
|
||||
auto tombstone_map_iter =
|
||||
tombstone_map.upper_bound(new_range_dels_iter->start_key_);
|
||||
// if nothing overlapped we would've already inserted all the new points
|
||||
// and returned early
|
||||
assert(tombstone_map_iter != tombstone_map.begin());
|
||||
tombstone_map_iter--;
|
||||
|
||||
// untermed_seq is non-kMaxSequenceNumber when we covered an existing point
|
||||
// but haven't seen its corresponding endpoint. It's used for (1) deciding
|
||||
// whether to forcibly insert the new interval's endpoint; and (2) possibly
|
||||
// raising the seqnum for the to-be-inserted element (we insert the max
|
||||
// seqnum between the next new interval and the unterminated interval).
|
||||
SequenceNumber untermed_seq = kMaxSequenceNumber;
|
||||
while (tombstone_map_iter != tombstone_map.end() &&
|
||||
new_range_dels_iter != new_range_dels.end()) {
|
||||
const Slice *tombstone_map_iter_end = nullptr,
|
||||
*new_range_dels_iter_end = nullptr;
|
||||
if (tombstone_map_iter != tombstone_map.end()) {
|
||||
auto next_tombstone_map_iter = std::next(tombstone_map_iter);
|
||||
if (next_tombstone_map_iter != tombstone_map.end()) {
|
||||
tombstone_map_iter_end = &next_tombstone_map_iter->first;
|
||||
}
|
||||
}
|
||||
if (new_range_dels_iter != new_range_dels.end()) {
|
||||
auto next_new_range_dels_iter = std::next(new_range_dels_iter);
|
||||
if (next_new_range_dels_iter != new_range_dels.end()) {
|
||||
new_range_dels_iter_end = &next_new_range_dels_iter->start_key_;
|
||||
}
|
||||
}
|
||||
|
||||
// our positions in existing/new tombstone collections should always
|
||||
// overlap. The non-overlapping cases are handled above and below this
|
||||
// loop.
|
||||
assert(new_range_dels_iter_end == nullptr ||
|
||||
icmp_.user_comparator()->Compare(tombstone_map_iter->first,
|
||||
*new_range_dels_iter_end) < 0);
|
||||
assert(tombstone_map_iter_end == nullptr ||
|
||||
icmp_.user_comparator()->Compare(new_range_dels_iter->start_key_,
|
||||
*tombstone_map_iter_end) < 0);
|
||||
|
||||
int new_to_old_start_cmp = icmp_.user_comparator()->Compare(
|
||||
new_range_dels_iter->start_key_, tombstone_map_iter->first);
|
||||
// nullptr end means extends infinitely rightwards, set new_to_old_end_cmp
|
||||
// accordingly so we can use common code paths later.
|
||||
int new_to_old_end_cmp;
|
||||
if (new_range_dels_iter_end == nullptr &&
|
||||
tombstone_map_iter_end == nullptr) {
|
||||
new_to_old_end_cmp = 0;
|
||||
} else if (new_range_dels_iter_end == nullptr) {
|
||||
new_to_old_end_cmp = 1;
|
||||
} else if (tombstone_map_iter_end == nullptr) {
|
||||
new_to_old_end_cmp = -1;
|
||||
} else {
|
||||
new_to_old_end_cmp = icmp_.user_comparator()->Compare(
|
||||
*new_range_dels_iter_end, *tombstone_map_iter_end);
|
||||
}
|
||||
|
||||
if (new_to_old_start_cmp < 0) {
|
||||
// the existing one's left endpoint comes after, so raise/delete it if
|
||||
// it's covered.
|
||||
if (tombstone_map_iter->second.seq_ < new_range_dels_iter->seq_) {
|
||||
untermed_seq = tombstone_map_iter->second.seq_;
|
||||
if (tombstone_map_iter != tombstone_map.begin() &&
|
||||
std::prev(tombstone_map_iter)->second.seq_ ==
|
||||
new_range_dels_iter->seq_) {
|
||||
tombstone_map_iter = tombstone_map.erase(tombstone_map_iter);
|
||||
--tombstone_map_iter;
|
||||
} else {
|
||||
tombstone_map_iter->second.seq_ = new_range_dels_iter->seq_;
|
||||
}
|
||||
}
|
||||
} else if (new_to_old_start_cmp > 0) {
|
||||
if (untermed_seq != kMaxSequenceNumber ||
|
||||
tombstone_map_iter->second.seq_ < new_range_dels_iter->seq_) {
|
||||
auto seq = tombstone_map_iter->second.seq_;
|
||||
// need to adjust this element if not intended to span beyond the new
|
||||
// element (i.e., was_tombstone_map_iter_raised == true), or if it
|
||||
// can be raised
|
||||
tombstone_map_iter = tombstone_map.emplace(
|
||||
new_range_dels_iter->start_key_,
|
||||
RangeTombstone(
|
||||
Slice(), Slice(),
|
||||
std::max(
|
||||
untermed_seq == kMaxSequenceNumber ? 0 : untermed_seq,
|
||||
new_range_dels_iter->seq_)));
|
||||
untermed_seq = seq;
|
||||
}
|
||||
} else {
|
||||
// their left endpoints coincide, so raise the existing one if needed
|
||||
if (tombstone_map_iter->second.seq_ < new_range_dels_iter->seq_) {
|
||||
untermed_seq = tombstone_map_iter->second.seq_;
|
||||
tombstone_map_iter->second.seq_ = new_range_dels_iter->seq_;
|
||||
}
|
||||
}
|
||||
|
||||
// advance whichever one ends earlier, or both if their right endpoints
|
||||
// coincide
|
||||
if (new_to_old_end_cmp < 0) {
|
||||
++new_range_dels_iter;
|
||||
} else if (new_to_old_end_cmp > 0) {
|
||||
++tombstone_map_iter;
|
||||
untermed_seq = kMaxSequenceNumber;
|
||||
} else {
|
||||
++new_range_dels_iter;
|
||||
++tombstone_map_iter;
|
||||
untermed_seq = kMaxSequenceNumber;
|
||||
}
|
||||
}
|
||||
while (new_range_dels_iter != new_range_dels.end()) {
|
||||
tombstone_map.emplace(
|
||||
new_range_dels_iter->start_key_,
|
||||
RangeTombstone(Slice(), Slice(), new_range_dels_iter->seq_));
|
||||
++new_range_dels_iter;
|
||||
}
|
||||
} else {
|
||||
tombstone_map.emplace(tombstone.start_key_, std::move(tombstone));
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
RangeDelAggregator::PositionalTombstoneMap&
|
||||
RangeDelAggregator::GetPositionalTombstoneMap(SequenceNumber seq) {
|
||||
assert(rep_ != nullptr);
|
||||
// The stripe includes seqnum for the snapshot above and excludes seqnum for
|
||||
// the snapshot below.
|
||||
@@ -148,10 +395,16 @@ void RangeDelAggregator::AddToBuilder(
|
||||
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());
|
||||
// TODO(andrewkr): these are counted for each compaction output file, so
|
||||
// lots of double-counting.
|
||||
if (!stripe_map_iter->second.raw_map.empty()) {
|
||||
range_del_out_stats->num_range_del_drop_obsolete +=
|
||||
static_cast<int64_t>(stripe_map_iter->second.raw_map.size()) -
|
||||
(collapse_deletions_ ? 1 : 0);
|
||||
range_del_out_stats->num_record_drop_obsolete +=
|
||||
static_cast<int64_t>(stripe_map_iter->second.raw_map.size()) -
|
||||
(collapse_deletions_ ? 1 : 0);
|
||||
}
|
||||
// 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;
|
||||
@@ -161,8 +414,23 @@ void RangeDelAggregator::AddToBuilder(
|
||||
// insert them into a std::map on the read path.
|
||||
bool first_added = false;
|
||||
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;
|
||||
for (auto tombstone_map_iter = stripe_map_iter->second.raw_map.begin();
|
||||
tombstone_map_iter != stripe_map_iter->second.raw_map.end();
|
||||
++tombstone_map_iter) {
|
||||
RangeTombstone tombstone;
|
||||
if (collapse_deletions_) {
|
||||
auto next_tombstone_map_iter = std::next(tombstone_map_iter);
|
||||
if (next_tombstone_map_iter == stripe_map_iter->second.raw_map.end() ||
|
||||
tombstone_map_iter->second.seq_ == 0) {
|
||||
// it's a sentinel tombstone
|
||||
continue;
|
||||
}
|
||||
tombstone.start_key_ = tombstone_map_iter->first;
|
||||
tombstone.end_key_ = next_tombstone_map_iter->first;
|
||||
tombstone.seq_ = tombstone_map_iter->second.seq_;
|
||||
} else {
|
||||
tombstone = tombstone_map_iter->second;
|
||||
}
|
||||
if (upper_bound != nullptr &&
|
||||
icmp_.user_comparator()->Compare(*upper_bound,
|
||||
tombstone.start_key_) <= 0) {
|
||||
@@ -241,7 +509,7 @@ bool RangeDelAggregator::IsEmpty() {
|
||||
}
|
||||
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()) {
|
||||
if (!stripe_map_iter->second.raw_map.empty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+63
-15
@@ -32,27 +32,51 @@ class RangeDelAggregator {
|
||||
// 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().
|
||||
// covered by range tombstones in other snapshot stripes. This constructor
|
||||
// is used for writes (flush/compaction). All DB snapshots are provided
|
||||
// such that no keys are removed that are uncovered according to any DB
|
||||
// snapshot.
|
||||
// Note this overload does not lazily initialize Rep.
|
||||
RangeDelAggregator(const InternalKeyComparator& icmp,
|
||||
const std::vector<SequenceNumber>& snapshots);
|
||||
const std::vector<SequenceNumber>& snapshots,
|
||||
bool collapse_deletions = true);
|
||||
|
||||
// @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.
|
||||
// deletion is encountered. This constructor is used in case of reads (get/
|
||||
// iterator), for which only the user snapshot (upper_bound) is provided
|
||||
// such that the seqnum space is divided into two stripes. Only the older
|
||||
// stripe will be used by ShouldDelete().
|
||||
RangeDelAggregator(const InternalKeyComparator& icmp,
|
||||
SequenceNumber upper_bound);
|
||||
SequenceNumber upper_bound,
|
||||
bool collapse_deletions = false);
|
||||
|
||||
// We maintain position in the tombstone map across calls to ShouldDelete. The
|
||||
// caller may wish to specify a mode to optimize positioning the iterator
|
||||
// during the next call to ShouldDelete. The non-kFullScan modes are only
|
||||
// available when deletion collapsing is enabled.
|
||||
//
|
||||
// For example, if we invoke Next() on an iterator, kForwardTraversal should
|
||||
// be specified to advance one-by-one through deletions until one is found
|
||||
// with its interval containing the key. This will typically be faster than
|
||||
// doing a full binary search (kBinarySearch).
|
||||
enum RangePositioningMode {
|
||||
kFullScan, // used iff collapse_deletions_ == false
|
||||
kForwardTraversal,
|
||||
kBackwardTraversal,
|
||||
kBinarySearch,
|
||||
};
|
||||
|
||||
// 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);
|
||||
// @param mode If collapse_deletions_ is true, this dictates how we will find
|
||||
// the deletion whose interval contains this key. Otherwise, its
|
||||
// value must be kFullScan indicating linear scan from beginning..
|
||||
bool ShouldDelete(const ParsedInternalKey& parsed,
|
||||
RangePositioningMode mode = kFullScan);
|
||||
bool ShouldDelete(const Slice& internal_key,
|
||||
RangePositioningMode mode = kFullScan);
|
||||
bool ShouldAddTombstones(bool bottommost_level = false);
|
||||
|
||||
// Adds tombstones to the tombstone aggregation structure maintained by this
|
||||
@@ -60,6 +84,13 @@ class RangeDelAggregator {
|
||||
// @return non-OK status if any of the tombstone keys are corrupted.
|
||||
Status AddTombstones(std::unique_ptr<InternalIterator> input);
|
||||
|
||||
// Resets iterators maintained across calls to ShouldDelete(). This may be
|
||||
// called when the tombstones change, or the owner may call explicitly, e.g.,
|
||||
// if it's an iterator that just seeked to an arbitrary position. The effect
|
||||
// of invalidation is that the following call to ShouldDelete() will binary
|
||||
// search for its tombstone.
|
||||
void InvalidateTombstoneMapPositions();
|
||||
|
||||
// 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;
|
||||
@@ -87,12 +118,26 @@ class RangeDelAggregator {
|
||||
bool IsEmpty();
|
||||
|
||||
private:
|
||||
// Maps tombstone internal start key -> tombstone object
|
||||
typedef std::map<Slice, RangeTombstone, stl_wrappers::LessOfComparator>
|
||||
// Maps tombstone user start key -> tombstone object
|
||||
typedef std::multimap<Slice, RangeTombstone, stl_wrappers::LessOfComparator>
|
||||
TombstoneMap;
|
||||
// Also maintains position in TombstoneMap last seen by ShouldDelete(). The
|
||||
// end iterator indicates invalidation (e.g., if AddTombstones() changes the
|
||||
// underlying map). End iterator cannot be invalidated.
|
||||
struct PositionalTombstoneMap {
|
||||
explicit PositionalTombstoneMap(TombstoneMap _raw_map)
|
||||
: raw_map(std::move(_raw_map)), iter(raw_map.end()) {}
|
||||
PositionalTombstoneMap(const PositionalTombstoneMap&) = delete;
|
||||
PositionalTombstoneMap(PositionalTombstoneMap&& other)
|
||||
: raw_map(std::move(other.raw_map)), iter(raw_map.end()) {}
|
||||
|
||||
TombstoneMap raw_map;
|
||||
TombstoneMap::const_iterator iter;
|
||||
};
|
||||
|
||||
// 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;
|
||||
typedef std::map<SequenceNumber, PositionalTombstoneMap> StripeMap;
|
||||
|
||||
struct Rep {
|
||||
StripeMap stripe_map_;
|
||||
@@ -103,11 +148,14 @@ class RangeDelAggregator {
|
||||
// once the first range deletion is encountered.
|
||||
void InitRep(const std::vector<SequenceNumber>& snapshots);
|
||||
|
||||
TombstoneMap& GetTombstoneMap(SequenceNumber seq);
|
||||
PositionalTombstoneMap& GetPositionalTombstoneMap(SequenceNumber seq);
|
||||
Status AddTombstone(RangeTombstone tombstone);
|
||||
|
||||
SequenceNumber upper_bound_;
|
||||
std::unique_ptr<Rep> rep_;
|
||||
const InternalKeyComparator& icmp_;
|
||||
// collapse range deletions so they're binary searchable
|
||||
const bool collapse_deletions_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -0,0 +1,157 @@
|
||||
// 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 <algorithm>
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "util/testutil.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class RangeDelAggregatorTest : public testing::Test {};
|
||||
|
||||
namespace {
|
||||
|
||||
struct ExpectedPoint {
|
||||
Slice begin;
|
||||
SequenceNumber seq;
|
||||
};
|
||||
|
||||
enum Direction {
|
||||
kForward,
|
||||
kReverse,
|
||||
};
|
||||
|
||||
void VerifyRangeDels(const std::vector<RangeTombstone>& range_dels,
|
||||
const std::vector<ExpectedPoint>& expected_points) {
|
||||
// Test same result regardless of which order the range deletions are added.
|
||||
for (Direction dir : {kForward, kReverse}) {
|
||||
auto icmp = InternalKeyComparator(BytewiseComparator());
|
||||
RangeDelAggregator range_del_agg(icmp, {} /* snapshots */, true);
|
||||
std::vector<std::string> keys, values;
|
||||
for (const auto& range_del : range_dels) {
|
||||
auto key_and_value = range_del.Serialize();
|
||||
keys.push_back(key_and_value.first.Encode().ToString());
|
||||
values.push_back(key_and_value.second.ToString());
|
||||
}
|
||||
if (dir == kReverse) {
|
||||
std::reverse(keys.begin(), keys.end());
|
||||
std::reverse(values.begin(), values.end());
|
||||
}
|
||||
std::unique_ptr<test::VectorIterator> range_del_iter(
|
||||
new test::VectorIterator(keys, values));
|
||||
range_del_agg.AddTombstones(std::move(range_del_iter));
|
||||
|
||||
for (const auto expected_point : expected_points) {
|
||||
ParsedInternalKey parsed_key;
|
||||
parsed_key.user_key = expected_point.begin;
|
||||
parsed_key.sequence = expected_point.seq;
|
||||
parsed_key.type = kTypeValue;
|
||||
ASSERT_FALSE(range_del_agg.ShouldDelete(
|
||||
parsed_key,
|
||||
RangeDelAggregator::RangePositioningMode::kForwardTraversal));
|
||||
if (parsed_key.sequence > 0) {
|
||||
--parsed_key.sequence;
|
||||
ASSERT_TRUE(range_del_agg.ShouldDelete(
|
||||
parsed_key,
|
||||
RangeDelAggregator::RangePositioningMode::kForwardTraversal));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, Empty) { VerifyRangeDels({}, {{"a", 0}}); }
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, SameStartAndEnd) {
|
||||
VerifyRangeDels({{"a", "a", 5}}, {{" ", 0}, {"a", 0}, {"b", 0}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, Single) {
|
||||
VerifyRangeDels({{"a", "b", 10}}, {{" ", 0}, {"a", 10}, {"b", 0}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, OverlapAboveLeft) {
|
||||
VerifyRangeDels({{"a", "c", 10}, {"b", "d", 5}},
|
||||
{{" ", 0}, {"a", 10}, {"c", 5}, {"d", 0}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, OverlapAboveRight) {
|
||||
VerifyRangeDels({{"a", "c", 5}, {"b", "d", 10}},
|
||||
{{" ", 0}, {"a", 5}, {"b", 10}, {"d", 0}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, OverlapAboveMiddle) {
|
||||
VerifyRangeDels({{"a", "d", 5}, {"b", "c", 10}},
|
||||
{{" ", 0}, {"a", 5}, {"b", 10}, {"c", 5}, {"d", 0}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, OverlapFully) {
|
||||
VerifyRangeDels({{"a", "d", 10}, {"b", "c", 5}},
|
||||
{{" ", 0}, {"a", 10}, {"d", 0}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, OverlapPoint) {
|
||||
VerifyRangeDels({{"a", "b", 5}, {"b", "c", 10}},
|
||||
{{" ", 0}, {"a", 5}, {"b", 10}, {"c", 0}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, SameStartKey) {
|
||||
VerifyRangeDels({{"a", "c", 5}, {"a", "b", 10}},
|
||||
{{" ", 0}, {"a", 10}, {"b", 5}, {"c", 0}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, SameEndKey) {
|
||||
VerifyRangeDels({{"a", "d", 5}, {"b", "d", 10}},
|
||||
{{" ", 0}, {"a", 5}, {"b", 10}, {"d", 0}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, GapsBetweenRanges) {
|
||||
VerifyRangeDels(
|
||||
{{"a", "b", 5}, {"c", "d", 10}, {"e", "f", 15}},
|
||||
{{" ", 0}, {"a", 5}, {"b", 0}, {"c", 10}, {"d", 0}, {"e", 15}, {"f", 0}});
|
||||
}
|
||||
|
||||
// Note the Cover* tests also test cases where tombstones are inserted under a
|
||||
// larger one when VerifyRangeDels() runs them in reverse
|
||||
TEST_F(RangeDelAggregatorTest, CoverMultipleFromLeft) {
|
||||
VerifyRangeDels(
|
||||
{{"b", "d", 5}, {"c", "f", 10}, {"e", "g", 15}, {"a", "f", 20}},
|
||||
{{" ", 0}, {"a", 20}, {"f", 15}, {"g", 0}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, CoverMultipleFromRight) {
|
||||
VerifyRangeDels(
|
||||
{{"b", "d", 5}, {"c", "f", 10}, {"e", "g", 15}, {"c", "h", 20}},
|
||||
{{" ", 0}, {"b", 5}, {"c", 20}, {"h", 0}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, CoverMultipleFully) {
|
||||
VerifyRangeDels(
|
||||
{{"b", "d", 5}, {"c", "f", 10}, {"e", "g", 15}, {"a", "h", 20}},
|
||||
{{" ", 0}, {"a", 20}, {"h", 0}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, AlternateMultipleAboveBelow) {
|
||||
VerifyRangeDels(
|
||||
{{"b", "d", 15}, {"c", "f", 10}, {"e", "g", 20}, {"a", "h", 5}},
|
||||
{{" ", 0},
|
||||
{"a", 5},
|
||||
{"b", 15},
|
||||
{"d", 10},
|
||||
{"e", 20},
|
||||
{"g", 5},
|
||||
{"h", 0}});
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <inttypes.h>
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
@@ -520,6 +520,8 @@ class Version {
|
||||
return next_;
|
||||
}
|
||||
|
||||
int TEST_refs() const { return refs_; }
|
||||
|
||||
VersionStorageInfo* storage_info() { return &storage_info_; }
|
||||
|
||||
VersionSet* version_set() { return vset_; }
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
+1
-1
@@ -317,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;
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
title: RocksDB 5.0.1 Released!
|
||||
layout: post
|
||||
author: yiwu
|
||||
category: blog
|
||||
---
|
||||
|
||||
### 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.
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
+2
-2
@@ -10,7 +10,7 @@ endif
|
||||
|
||||
.PHONY: clean librocksdb
|
||||
|
||||
all: simple_example column_families_example compact_files_example c_simple_example optimistic_transaction_example transaction_example compaction_filter_example
|
||||
all: simple_example column_families_example compact_files_example c_simple_example optimistic_transaction_example transaction_example compaction_filter_example options_file_example
|
||||
|
||||
simple_example: librocksdb simple_example.cc
|
||||
$(CXX) $(CXXFLAGS) $@.cc -o$@ ../librocksdb.a -I../include -O2 -std=c++11 $(PLATFORM_LDFLAGS) $(PLATFORM_CXXFLAGS) $(EXEC_LDFLAGS)
|
||||
@@ -40,7 +40,7 @@ options_file_example: librocksdb options_file_example.cc
|
||||
$(CXX) $(CXXFLAGS) $@.cc -o$@ ../librocksdb.a -I../include -O2 -std=c++11 $(PLATFORM_LDFLAGS) $(PLATFORM_CXXFLAGS) $(EXEC_LDFLAGS)
|
||||
|
||||
clean:
|
||||
rm -rf ./simple_example ./column_families_example ./compact_files_example ./compaction_filter_example ./c_simple_example c_simple_example.o ./optimistic_transaction_example ./transaction_example
|
||||
rm -rf ./simple_example ./column_families_example ./compact_files_example ./compaction_filter_example ./c_simple_example c_simple_example.o ./optimistic_transaction_example ./transaction_example ./options_file_example
|
||||
|
||||
librocksdb:
|
||||
cd .. && $(MAKE) static_lib
|
||||
@@ -1,3 +1,8 @@
|
||||
// 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 <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
@@ -73,12 +73,13 @@
|
||||
error_if_exists=false
|
||||
recycle_log_file_num=0
|
||||
skip_log_error_on_recovery=false
|
||||
allow_mmap_reads=false
|
||||
allow_os_buffer=true
|
||||
db_log_dir=
|
||||
new_table_reader_for_compaction_inputs=true
|
||||
allow_mmap_reads=false
|
||||
allow_mmap_writes=false
|
||||
|
||||
use_direct_reads=false
|
||||
use_direct_writes=false
|
||||
|
||||
|
||||
[CFOptions "default"]
|
||||
compaction_style=kCompactionStyleLevel
|
||||
@@ -127,7 +128,7 @@
|
||||
write_buffer_size=134217728
|
||||
disable_auto_compactions=false
|
||||
inplace_update_support=false
|
||||
|
||||
|
||||
[TableOptions/BlockBasedTable "default"]
|
||||
format_version=2
|
||||
whole_key_filtering=true
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// 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 <cstdio>
|
||||
#include <string>
|
||||
|
||||
|
||||
+52
-2
@@ -90,6 +90,7 @@ typedef struct rocksdb_iterator_t rocksdb_iterator_t;
|
||||
typedef struct rocksdb_logger_t rocksdb_logger_t;
|
||||
typedef struct rocksdb_mergeoperator_t rocksdb_mergeoperator_t;
|
||||
typedef struct rocksdb_options_t rocksdb_options_t;
|
||||
typedef struct rocksdb_compactoptions_t rocksdb_compactoptions_t;
|
||||
typedef struct rocksdb_block_based_table_options_t
|
||||
rocksdb_block_based_table_options_t;
|
||||
typedef struct rocksdb_cuckoo_table_options_t
|
||||
@@ -315,6 +316,15 @@ extern ROCKSDB_LIBRARY_API void rocksdb_compact_range_cf(
|
||||
const char* start_key, size_t start_key_len, const char* limit_key,
|
||||
size_t limit_key_len);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_compact_range_opt(
|
||||
rocksdb_t* db, rocksdb_compactoptions_t* opt, const char* start_key,
|
||||
size_t start_key_len, const char* limit_key, size_t limit_key_len);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_compact_range_cf_opt(
|
||||
rocksdb_t* db, rocksdb_column_family_handle_t* column_family,
|
||||
rocksdb_compactoptions_t* opt, const char* start_key, size_t start_key_len,
|
||||
const char* limit_key, size_t limit_key_len);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_delete_file(rocksdb_t* db,
|
||||
const char* name);
|
||||
|
||||
@@ -414,6 +424,22 @@ extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_deletev(
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_deletev_cf(
|
||||
rocksdb_writebatch_t* b, rocksdb_column_family_handle_t* column_family,
|
||||
int num_keys, const char* const* keys_list, const size_t* keys_list_sizes);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_delete_range(
|
||||
rocksdb_writebatch_t* b, const char* start_key, size_t start_key_len,
|
||||
const char* end_key, size_t end_key_len);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_delete_range_cf(
|
||||
rocksdb_writebatch_t* b, rocksdb_column_family_handle_t* column_family,
|
||||
const char* start_key, size_t start_key_len, const char* end_key,
|
||||
size_t end_key_len);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_delete_rangev(
|
||||
rocksdb_writebatch_t* b, int num_keys, const char* const* start_keys_list,
|
||||
const size_t* start_keys_list_sizes, const char* const* end_keys_list,
|
||||
const size_t* end_keys_list_sizes);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_delete_rangev_cf(
|
||||
rocksdb_writebatch_t* b, rocksdb_column_family_handle_t* column_family,
|
||||
int num_keys, const char* const* start_keys_list,
|
||||
const size_t* start_keys_list_sizes, const char* const* end_keys_list,
|
||||
const size_t* end_keys_list_sizes);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_put_log_data(
|
||||
rocksdb_writebatch_t*, const char* blob, size_t len);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_writebatch_iterate(
|
||||
@@ -631,12 +657,14 @@ extern ROCKSDB_LIBRARY_API void rocksdb_options_set_manifest_preallocation_size(
|
||||
extern ROCKSDB_LIBRARY_API void
|
||||
rocksdb_options_set_purge_redundant_kvs_while_flush(rocksdb_options_t*,
|
||||
unsigned char);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_allow_os_buffer(
|
||||
rocksdb_options_t*, unsigned char);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_allow_mmap_reads(
|
||||
rocksdb_options_t*, unsigned char);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_allow_mmap_writes(
|
||||
rocksdb_options_t*, unsigned char);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_use_direct_reads(
|
||||
rocksdb_options_t*, unsigned char);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_use_direct_writes(
|
||||
rocksdb_options_t*, unsigned char);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_is_fd_close_on_exec(
|
||||
rocksdb_options_t*, unsigned char);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_skip_log_error_on_recovery(
|
||||
@@ -849,6 +877,10 @@ extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_tailing(
|
||||
rocksdb_readoptions_t*, unsigned char);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_readahead_size(
|
||||
rocksdb_readoptions_t*, size_t);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_pin_data(
|
||||
rocksdb_readoptions_t*, unsigned char);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_total_order_seek(
|
||||
rocksdb_readoptions_t*, unsigned char);
|
||||
|
||||
/* Write options */
|
||||
|
||||
@@ -861,6 +893,20 @@ extern ROCKSDB_LIBRARY_API void rocksdb_writeoptions_set_sync(
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_writeoptions_disable_WAL(
|
||||
rocksdb_writeoptions_t* opt, int disable);
|
||||
|
||||
/* Compact range options */
|
||||
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_compactoptions_t*
|
||||
rocksdb_compactoptions_create();
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_compactoptions_destroy(
|
||||
rocksdb_compactoptions_t*);
|
||||
extern ROCKSDB_LIBRARY_API void
|
||||
rocksdb_compactoptions_set_exclusive_manual_compaction(
|
||||
rocksdb_compactoptions_t*, unsigned char);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_compactoptions_set_change_level(
|
||||
rocksdb_compactoptions_t*, unsigned char);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_compactoptions_set_target_level(
|
||||
rocksdb_compactoptions_t*, int);
|
||||
|
||||
/* Flush options */
|
||||
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_flushoptions_t*
|
||||
@@ -877,6 +923,10 @@ extern ROCKSDB_LIBRARY_API rocksdb_cache_t* rocksdb_cache_create_lru(
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_cache_destroy(rocksdb_cache_t* cache);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_cache_set_capacity(
|
||||
rocksdb_cache_t* cache, size_t capacity);
|
||||
extern ROCKSDB_LIBRARY_API size_t
|
||||
rocksdb_cache_get_usage(rocksdb_cache_t* cache);
|
||||
extern ROCKSDB_LIBRARY_API size_t
|
||||
rocksdb_cache_get_pinned_usage(rocksdb_cache_t* cache);
|
||||
|
||||
/* Env */
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "rocksdb/status.h"
|
||||
@@ -101,6 +102,12 @@ class Cache {
|
||||
// function.
|
||||
virtual Handle* Lookup(const Slice& key, Statistics* stats = nullptr) = 0;
|
||||
|
||||
// Increments the reference count for the handle if it refers to an entry in
|
||||
// the cache. Returns true if refcount was incremented; otherwise, returns
|
||||
// false.
|
||||
// REQUIRES: handle must have been returned by a method on *this.
|
||||
virtual bool Ref(Handle* handle) = 0;
|
||||
|
||||
// Release a mapping returned by a previous Lookup().
|
||||
// REQUIRES: handle must not have been released yet.
|
||||
// REQUIRES: handle must have been returned by a method on *this.
|
||||
@@ -167,6 +174,8 @@ class Cache {
|
||||
// Prerequisit: no entry is referenced.
|
||||
virtual void EraseUnRefEntries() = 0;
|
||||
|
||||
virtual std::string GetPrintableOptions() const { return ""; }
|
||||
|
||||
private:
|
||||
// No copying allowed
|
||||
Cache(const Cache&);
|
||||
|
||||
@@ -140,6 +140,12 @@ class CompactionFilter {
|
||||
// by kRemoveAndSkipUntil can disappear from a snapshot - beware
|
||||
// if you're using TransactionDB or DB::GetSnapshot().
|
||||
//
|
||||
// Another warning: if value for a key was overwritten or merged into
|
||||
// (multiple Put()s or Merge()s), and compaction filter skips this key
|
||||
// with kRemoveAndSkipUntil, it's possible that it will remove only
|
||||
// the new value, exposing the old value that was supposed to be
|
||||
// overwritten.
|
||||
//
|
||||
// If you use kRemoveAndSkipUntil, consider also reducing
|
||||
// compaction_readahead_size option.
|
||||
//
|
||||
|
||||
@@ -288,6 +288,8 @@ Status GetStringFromColumnFamilyOptions(std::string* opts_str,
|
||||
Status GetStringFromCompressionType(std::string* compression_str,
|
||||
CompressionType compression_type);
|
||||
|
||||
std::vector<CompressionType> GetSupportedCompressions();
|
||||
|
||||
Status GetBlockBasedTableOptionsFromString(
|
||||
const BlockBasedTableOptions& table_options,
|
||||
const std::string& opts_str,
|
||||
|
||||
@@ -500,6 +500,10 @@ class DB {
|
||||
// live data in bytes.
|
||||
static const std::string kEstimateLiveDataSize;
|
||||
|
||||
// "rocksdb.min-log-number-to-keep" - return the minmum log number of the
|
||||
// log files that should be kept.
|
||||
static const std::string kMinLogNumberToKeep;
|
||||
|
||||
// "rocksdb.total-sst-files-size" - returns total size (bytes) of all SST
|
||||
// files.
|
||||
// WARNING: may slow down online queries if there are too many files.
|
||||
@@ -565,6 +569,7 @@ class DB {
|
||||
// "rocksdb.num-live-versions"
|
||||
// "rocksdb.current-super-version-number"
|
||||
// "rocksdb.estimate-live-data-size"
|
||||
// "rocksdb.min-log-number-to-keep"
|
||||
// "rocksdb.total-sst-files-size"
|
||||
// "rocksdb.base-level"
|
||||
// "rocksdb.estimate-pending-compaction-bytes"
|
||||
|
||||
+46
-34
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <cstdarg>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -50,6 +51,7 @@ struct ThreadStatus;
|
||||
using std::unique_ptr;
|
||||
using std::shared_ptr;
|
||||
|
||||
const size_t kDefaultPageSize = 4 * 1024;
|
||||
|
||||
// Options while opening a file to read/write
|
||||
struct EnvOptions {
|
||||
@@ -60,9 +62,6 @@ struct EnvOptions {
|
||||
// construct from Options
|
||||
explicit EnvOptions(const DBOptions& options);
|
||||
|
||||
// If true, then allow caching of data in environment buffers
|
||||
bool use_os_buffer = true;
|
||||
|
||||
// If true, then use mmap to read data
|
||||
bool use_mmap_reads = false;
|
||||
|
||||
@@ -200,6 +199,10 @@ class Env {
|
||||
// Store in *result the names of the children of the specified directory.
|
||||
// The names are relative to "dir".
|
||||
// Original contents of *results are dropped.
|
||||
// Returns OK if "dir" exists and "*result" contains its children.
|
||||
// NotFound if "dir" does not exist, the calling process does not have
|
||||
// permission to access "dir", or if "dir" is invalid.
|
||||
// IOError if an IO Error was encountered
|
||||
virtual Status GetChildren(const std::string& dir,
|
||||
std::vector<std::string>* result) = 0;
|
||||
|
||||
@@ -209,6 +212,10 @@ class Env {
|
||||
// result.
|
||||
// The name attributes are relative to "dir".
|
||||
// Original contents of *results are dropped.
|
||||
// Returns OK if "dir" exists and "*result" contains its children.
|
||||
// NotFound if "dir" does not exist, the calling process does not have
|
||||
// permission to access "dir", or if "dir" is invalid.
|
||||
// IOError if an IO Error was encountered
|
||||
virtual Status GetChildrenFileAttributes(const std::string& dir,
|
||||
std::vector<FileAttributes>* result);
|
||||
|
||||
@@ -364,8 +371,8 @@ class Env {
|
||||
// OptimizeForManifestWrite will create a new EnvOptions object that is a copy
|
||||
// of the EnvOptions in the parameters, but is optimized for writing manifest
|
||||
// files. Default implementation returns the copy of the same object.
|
||||
virtual EnvOptions OptimizeForManifestWrite(const EnvOptions& env_options)
|
||||
const;
|
||||
virtual EnvOptions OptimizeForManifestWrite(
|
||||
const EnvOptions& env_options) const;
|
||||
|
||||
// Returns the status of all threads that belong to the current Env.
|
||||
virtual Status GetThreadList(std::vector<ThreadStatus>* thread_list) {
|
||||
@@ -423,12 +430,27 @@ class SequentialFile {
|
||||
// REQUIRES: External synchronization
|
||||
virtual Status Skip(uint64_t n) = 0;
|
||||
|
||||
// Indicates the upper layers if the current SequentialFile implementation
|
||||
// uses direct IO.
|
||||
virtual bool use_direct_io() const { return false; }
|
||||
|
||||
// Use the returned alignment value to allocate
|
||||
// aligned buffer for Direct I/O
|
||||
virtual size_t GetRequiredBufferAlignment() const { return kDefaultPageSize; }
|
||||
|
||||
// Remove any kind of caching of data from the offset to offset+length
|
||||
// of this file. If the length is 0, then it refers to the end of file.
|
||||
// If the system is not caching the file contents, then this is a noop.
|
||||
virtual Status InvalidateCache(size_t offset, size_t length) {
|
||||
return Status::NotSupported("InvalidateCache not supported.");
|
||||
}
|
||||
|
||||
// Positioned Read for direct I/O
|
||||
// If Direct I/O enabled, offset, n, and scratch should be properly aligned
|
||||
virtual Status PositionedRead(uint64_t offset, size_t n, Slice* result,
|
||||
char* scratch) {
|
||||
return Status::NotSupported();
|
||||
}
|
||||
};
|
||||
|
||||
// A file abstraction for randomly reading the contents of a file.
|
||||
@@ -446,6 +468,7 @@ class RandomAccessFile {
|
||||
// status.
|
||||
//
|
||||
// Safe for concurrent use by multiple threads.
|
||||
// If Direct I/O enabled, offset, n, and scratch should be aligned properly.
|
||||
virtual Status Read(uint64_t offset, size_t n, Slice* result,
|
||||
char* scratch) const = 0;
|
||||
|
||||
@@ -483,6 +506,14 @@ class RandomAccessFile {
|
||||
|
||||
virtual void Hint(AccessPattern pattern) {}
|
||||
|
||||
// Indicates the upper layers if the current RandomAccessFile implementation
|
||||
// uses direct IO.
|
||||
virtual bool use_direct_io() const { return false; }
|
||||
|
||||
// Use the returned alignment value to allocate
|
||||
// aligned buffer for Direct I/O
|
||||
virtual size_t GetRequiredBufferAlignment() const { return kDefaultPageSize; }
|
||||
|
||||
// Remove any kind of caching of data from the offset to offset+length
|
||||
// of this file. If the length is 0, then it refers to the end of file.
|
||||
// If the system is not caching the file contents, then this is a noop.
|
||||
@@ -503,21 +534,6 @@ class WritableFile {
|
||||
}
|
||||
virtual ~WritableFile();
|
||||
|
||||
// Indicates if the class makes use of unbuffered I/O
|
||||
// If false you must pass aligned buffer to Write()
|
||||
virtual bool UseOSBuffer() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
const size_t c_DefaultPageSize = 4 * 1024;
|
||||
|
||||
// Use the returned alignment value to allocate
|
||||
// aligned buffer for Write() when UseOSBuffer()
|
||||
// returns false
|
||||
virtual size_t GetRequiredBufferAlignment() const {
|
||||
return c_DefaultPageSize;
|
||||
}
|
||||
|
||||
// Append data to the end of the file
|
||||
// Note: A WriteabelFile object must support either Append or
|
||||
// PositionedAppend, so the users cannot mix the two.
|
||||
@@ -529,7 +545,7 @@ class WritableFile {
|
||||
// the sector. The implementation thus needs to also rewrite the last
|
||||
// partial sector.
|
||||
// Note: PositionAppend does not guarantee moving the file offset after the
|
||||
// write. A WriteabelFile object must support either Append or
|
||||
// write. A WritableFile object must support either Append or
|
||||
// PositionedAppend, so the users cannot mix the two.
|
||||
//
|
||||
// PositionedAppend() can only happen on the page/sector boundaries. For that
|
||||
@@ -576,8 +592,11 @@ class WritableFile {
|
||||
|
||||
// Indicates the upper layers if the current WritableFile implementation
|
||||
// uses direct IO.
|
||||
virtual bool UseDirectIO() const { return false; }
|
||||
virtual bool use_direct_io() const { return false; }
|
||||
|
||||
// Use the returned alignment value to allocate
|
||||
// aligned buffer for Direct I/O
|
||||
virtual size_t GetRequiredBufferAlignment() const { return kDefaultPageSize; }
|
||||
/*
|
||||
* Change the priority in rate limiter if rate limiting is enabled.
|
||||
* If rate limiting is not enabled, this call has no effect.
|
||||
@@ -686,20 +705,13 @@ class RandomRWFile {
|
||||
RandomRWFile() {}
|
||||
virtual ~RandomRWFile() {}
|
||||
|
||||
// Indicates if the class makes use of unbuffered I/O
|
||||
// Indicates if the class makes use of direct I/O
|
||||
// If false you must pass aligned buffer to Write()
|
||||
virtual bool UseOSBuffer() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
const size_t c_DefaultPageSize = 4 * 1024;
|
||||
virtual bool use_direct_io() const { return false; }
|
||||
|
||||
// Use the returned alignment value to allocate
|
||||
// aligned buffer for Write() when UseOSBuffer()
|
||||
// returns false
|
||||
virtual size_t GetRequiredBufferAlignment() const {
|
||||
return c_DefaultPageSize;
|
||||
}
|
||||
// aligned buffer for Direct I/O
|
||||
virtual size_t GetRequiredBufferAlignment() const { return kDefaultPageSize; }
|
||||
|
||||
// Used by the file_reader_writer to decide if the ReadAhead wrapper
|
||||
// should simply forward the call and do not enact read_ahead buffering or locking.
|
||||
@@ -713,7 +725,7 @@ class RandomRWFile {
|
||||
virtual void EnableReadAhead() {}
|
||||
|
||||
// Write bytes in `data` at offset `offset`, Returns Status::OK() on success.
|
||||
// Pass aligned buffer when UseOSBuffer() returns false.
|
||||
// Pass aligned buffer when use_direct_io() returns true.
|
||||
virtual Status Write(uint64_t offset, const Slice& data) = 0;
|
||||
|
||||
// Read up to `n` bytes starting from offset `offset` and store them in
|
||||
|
||||
@@ -36,6 +36,9 @@ class Cleanable {
|
||||
// not abstract and therefore clients should not override it.
|
||||
typedef void (*CleanupFunction)(void* arg1, void* arg2);
|
||||
void RegisterCleanup(CleanupFunction function, void* arg1, void* arg2);
|
||||
void DelegateCleanupsTo(Cleanable* other);
|
||||
// DoCleanup and also resets the pointers for reuse
|
||||
void Reset();
|
||||
|
||||
protected:
|
||||
struct Cleanup {
|
||||
@@ -45,6 +48,13 @@ class Cleanable {
|
||||
Cleanup* next;
|
||||
};
|
||||
Cleanup cleanup_;
|
||||
// It also becomes the owner of c
|
||||
void RegisterCleanup(Cleanup* c);
|
||||
|
||||
private:
|
||||
// Performs all the cleanups. It does not reset the pointers. Making it
|
||||
// private to prevent misuse
|
||||
inline void DoCleanup();
|
||||
};
|
||||
|
||||
class Iterator : public Cleanable {
|
||||
|
||||
@@ -170,6 +170,20 @@ struct MemTableInfo {
|
||||
|
||||
};
|
||||
|
||||
struct ExternalFileIngestionInfo {
|
||||
// the name of the column family
|
||||
std::string cf_name;
|
||||
// Path of the file outside the DB
|
||||
std::string external_file_path;
|
||||
// Path of the file inside the DB
|
||||
std::string internal_file_path;
|
||||
// The global sequence number assigned to keys in this file
|
||||
SequenceNumber global_seqno;
|
||||
// Table properties of the table being flushed
|
||||
TableProperties table_properties;
|
||||
};
|
||||
|
||||
|
||||
// EventListener class contains a set of call-back functions that will
|
||||
// be called when specific RocksDB event happens such as flush. It can
|
||||
// be used as a building block for developing custom features such as
|
||||
@@ -291,6 +305,15 @@ class EventListener {
|
||||
virtual void OnColumnFamilyHandleDeletionStarted(ColumnFamilyHandle* handle) {
|
||||
}
|
||||
|
||||
// A call-back function for RocksDB which will be called after an external
|
||||
// file is ingested using IngestExternalFile.
|
||||
//
|
||||
// Note that the this function will run on the same thread as
|
||||
// IngestExternalFile(), if this function is blocked, IngestExternalFile()
|
||||
// will be blocked from finishing.
|
||||
virtual void OnExternalFileIngested(
|
||||
DB* /*db*/, const ExternalFileIngestionInfo& /*info*/) {}
|
||||
|
||||
virtual ~EventListener() {}
|
||||
};
|
||||
|
||||
|
||||
+12
-20
@@ -1100,26 +1100,6 @@ struct DBOptions {
|
||||
// large amounts of data (such as xfs's allocsize option).
|
||||
size_t manifest_preallocation_size;
|
||||
|
||||
// Hint the OS that it should not buffer disk I/O. Enabling this
|
||||
// parameter may improve performance but increases pressure on the
|
||||
// system cache.
|
||||
//
|
||||
// The exact behavior of this parameter is platform dependent.
|
||||
//
|
||||
// On POSIX systems, after RocksDB reads data from disk it will
|
||||
// mark the pages as "unneeded". The operating system may - or may not
|
||||
// - evict these pages from memory, reducing pressure on the system
|
||||
// cache. If the disk block is requested again this can result in
|
||||
// additional disk I/O.
|
||||
//
|
||||
// On WINDOWS system, files will be opened in "unbuffered I/O" mode
|
||||
// which means that data read from the disk will not be cached or
|
||||
// bufferized. The hardware buffer of the devices may however still
|
||||
// be used. Memory mapped files are not impacted by this parameter.
|
||||
//
|
||||
// Default: true
|
||||
bool allow_os_buffer;
|
||||
|
||||
// Allow the OS to mmap file for reading sst tables. Default: false
|
||||
bool allow_mmap_reads;
|
||||
|
||||
@@ -1128,10 +1108,22 @@ struct DBOptions {
|
||||
// Default: false
|
||||
bool allow_mmap_writes;
|
||||
|
||||
// Enable direct I/O mode for read/write
|
||||
// they may or may not improve performance depending on the use case
|
||||
//
|
||||
// Files will be opened in "direct I/O" mode
|
||||
// which means that data r/w from the disk will not be cached or
|
||||
// bufferized. The hardware buffer of the devices may however still
|
||||
// be used. Memory mapped files are not impacted by these parameters.
|
||||
|
||||
// Use O_DIRECT for reading file
|
||||
// Default: false
|
||||
bool use_direct_reads;
|
||||
|
||||
// Use O_DIRECT for writing file
|
||||
// Default: false
|
||||
bool use_direct_writes;
|
||||
|
||||
// If false, fallocate() calls are bypassed
|
||||
bool allow_fallocate;
|
||||
|
||||
|
||||
@@ -54,6 +54,8 @@ class PersistentCache {
|
||||
// Persistent cache can be initialized as a tier of caches. The stats are per
|
||||
// tire top-down
|
||||
virtual StatsType Stats() = 0;
|
||||
|
||||
virtual std::string GetPrintableOptions() const = 0;
|
||||
};
|
||||
|
||||
// Factor method to create a new persistent cache
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <string>
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/table_properties.h"
|
||||
#include "rocksdb/types.h"
|
||||
|
||||
namespace rocksdb {
|
||||
@@ -43,8 +44,15 @@ struct ExternalSstFileInfo {
|
||||
// All keys in files generated by SstFileWriter will have sequence number = 0
|
||||
class SstFileWriter {
|
||||
public:
|
||||
// User can pass `column_family` to specify that the the generated file will
|
||||
// be ingested into this column_family, note that passing nullptr means that
|
||||
// the column_family is unknown.
|
||||
// If invalidate_page_cache is set to true, SstFileWriter will give the OS a
|
||||
// hint that this file pages is not needed everytime we write 1MB to the file
|
||||
SstFileWriter(const EnvOptions& env_options, const Options& options,
|
||||
const Comparator* user_comparator);
|
||||
const Comparator* user_comparator,
|
||||
ColumnFamilyHandle* column_family = nullptr,
|
||||
bool invalidate_page_cache = true);
|
||||
|
||||
~SstFileWriter();
|
||||
|
||||
@@ -61,7 +69,12 @@ class SstFileWriter {
|
||||
// which will be populated with information about the created sst file
|
||||
Status Finish(ExternalSstFileInfo* file_info = nullptr);
|
||||
|
||||
// Return the current file size.
|
||||
uint64_t FileSize();
|
||||
|
||||
private:
|
||||
void InvalidatePageCache(bool closing);
|
||||
|
||||
struct Rep;
|
||||
Rep* rep_;
|
||||
};
|
||||
|
||||
@@ -75,6 +75,7 @@ class Status {
|
||||
|
||||
SubCode subcode() const { return subcode_; }
|
||||
|
||||
// Returns a C style string indicating the message of the Status
|
||||
const char* getState() const { return state_; }
|
||||
|
||||
// Return a success status.
|
||||
|
||||
@@ -157,9 +157,9 @@ struct BlockBasedTableOptions {
|
||||
// a SstTable. Instead, buffer in WritableFileWriter will take
|
||||
// care of the flushing when it is full.
|
||||
//
|
||||
// On Windows, this option helps a lot when unbuffered I/O
|
||||
// (allow_os_buffer = false) is used, since it avoids small
|
||||
// unbuffered disk write.
|
||||
// This option helps a lot when direct I/O writes
|
||||
// (use_direct_writes = true) is used, since it avoids small
|
||||
// direct disk write.
|
||||
//
|
||||
// User may also adjust writable_file_max_buffer_size to optimize disk I/O
|
||||
// size.
|
||||
|
||||
@@ -20,12 +20,11 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#ifndef ROCKSDB_USING_THREAD_STATUS
|
||||
#define ROCKSDB_USING_THREAD_STATUS \
|
||||
!defined(ROCKSDB_LITE) && \
|
||||
#if !defined(ROCKSDB_LITE) && \
|
||||
!defined(NROCKSDB_THREAD_STATUS) && \
|
||||
!defined(OS_MACOSX) && \
|
||||
!defined(IOS_CROSS_COMPILE)
|
||||
#define ROCKSDB_USING_THREAD_STATUS
|
||||
#endif
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
|
||||
// vim: ts=8 sw=2 smarttab
|
||||
// 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.
|
||||
#ifndef ROCKSDB_UTILITIES_ENV_LIBRADOS_H
|
||||
#define ROCKSDB_UTILITIES_ENV_LIBRADOS_H
|
||||
|
||||
@@ -15,17 +17,16 @@ namespace rocksdb {
|
||||
class LibradosWritableFile;
|
||||
|
||||
class EnvLibrados : public EnvWrapper {
|
||||
public:
|
||||
public:
|
||||
// Create a brand new sequentially-readable file with the specified name.
|
||||
// On success, stores a pointer to the new file in *result and returns OK.
|
||||
// On failure stores nullptr in *result and returns non-OK. If the file does
|
||||
// not exist, returns a non-OK status.
|
||||
//
|
||||
// The returned file will only be accessed by one thread at a time.
|
||||
Status NewSequentialFile(
|
||||
const std::string& fname,
|
||||
std::unique_ptr<SequentialFile>* result,
|
||||
const EnvOptions& options);
|
||||
Status NewSequentialFile(const std::string& fname,
|
||||
std::unique_ptr<SequentialFile>* result,
|
||||
const EnvOptions& options) override;
|
||||
|
||||
// Create a brand new random access read-only file with the
|
||||
// specified name. On success, stores a pointer to the new file in
|
||||
@@ -34,10 +35,9 @@ public:
|
||||
// status.
|
||||
//
|
||||
// The returned file may be concurrently accessed by multiple threads.
|
||||
Status NewRandomAccessFile(
|
||||
const std::string& fname,
|
||||
std::unique_ptr<RandomAccessFile>* result,
|
||||
const EnvOptions& options);
|
||||
Status NewRandomAccessFile(const std::string& fname,
|
||||
std::unique_ptr<RandomAccessFile>* result,
|
||||
const EnvOptions& options) override;
|
||||
|
||||
// Create an object that writes to a new file with the specified
|
||||
// name. Deletes any existing file with the same name and creates a
|
||||
@@ -46,17 +46,15 @@ public:
|
||||
// returns non-OK.
|
||||
//
|
||||
// The returned file will only be accessed by one thread at a time.
|
||||
Status NewWritableFile(
|
||||
const std::string& fname,
|
||||
std::unique_ptr<WritableFile>* result,
|
||||
const EnvOptions& options);
|
||||
Status NewWritableFile(const std::string& fname,
|
||||
std::unique_ptr<WritableFile>* result,
|
||||
const EnvOptions& options) override;
|
||||
|
||||
// Reuse an existing file by renaming it and opening it as writable.
|
||||
Status ReuseWritableFile(
|
||||
const std::string& fname,
|
||||
const std::string& old_fname,
|
||||
std::unique_ptr<WritableFile>* result,
|
||||
const EnvOptions& options);
|
||||
Status ReuseWritableFile(const std::string& fname,
|
||||
const std::string& old_fname,
|
||||
std::unique_ptr<WritableFile>* result,
|
||||
const EnvOptions& options) override;
|
||||
|
||||
// Create an object that represents a directory. Will fail if directory
|
||||
// doesn't exist. If the directory exists, it will open the directory
|
||||
@@ -65,47 +63,44 @@ public:
|
||||
// On success, stores a pointer to the new Directory in
|
||||
// *result and returns OK. On failure stores nullptr in *result and
|
||||
// returns non-OK.
|
||||
Status NewDirectory(
|
||||
const std::string& name,
|
||||
std::unique_ptr<Directory>* result);
|
||||
Status NewDirectory(const std::string& name,
|
||||
std::unique_ptr<Directory>* result) override;
|
||||
|
||||
// Returns OK if the named file exists.
|
||||
// NotFound if the named file does not exist,
|
||||
// the calling process does not have permission to determine
|
||||
// whether this file exists, or if the path is invalid.
|
||||
// IOError if an IO Error was encountered
|
||||
Status FileExists(const std::string& fname);
|
||||
Status FileExists(const std::string& fname) override;
|
||||
|
||||
// Store in *result the names of the children of the specified directory.
|
||||
// The names are relative to "dir".
|
||||
// Original contents of *results are dropped.
|
||||
Status GetChildren(const std::string& dir,
|
||||
std::vector<std::string>* result);
|
||||
Status GetChildren(const std::string& dir, std::vector<std::string>* result);
|
||||
|
||||
// Delete the named file.
|
||||
Status DeleteFile(const std::string& fname);
|
||||
Status DeleteFile(const std::string& fname) override;
|
||||
|
||||
// Create the specified directory. Returns error if directory exists.
|
||||
Status CreateDir(const std::string& dirname);
|
||||
Status CreateDir(const std::string& dirname) override;
|
||||
|
||||
// Creates directory if missing. Return Ok if it exists, or successful in
|
||||
// Creating.
|
||||
Status CreateDirIfMissing(const std::string& dirname);
|
||||
Status CreateDirIfMissing(const std::string& dirname) override;
|
||||
|
||||
// Delete the specified directory.
|
||||
Status DeleteDir(const std::string& dirname);
|
||||
Status DeleteDir(const std::string& dirname) override;
|
||||
|
||||
// Store the size of fname in *file_size.
|
||||
Status GetFileSize(const std::string& fname, uint64_t* file_size);
|
||||
Status GetFileSize(const std::string& fname, uint64_t* file_size) override;
|
||||
|
||||
// Store the last modification time of fname in *file_mtime.
|
||||
Status GetFileModificationTime(const std::string& fname,
|
||||
uint64_t* file_mtime);
|
||||
uint64_t* file_mtime) override;
|
||||
// Rename file src to target.
|
||||
Status RenameFile(const std::string& src,
|
||||
const std::string& target);
|
||||
Status RenameFile(const std::string& src, const std::string& target) override;
|
||||
// Hard Link file src to target.
|
||||
Status LinkFile(const std::string& src, const std::string& target);
|
||||
Status LinkFile(const std::string& src, const std::string& target) override;
|
||||
|
||||
// Lock the specified file. Used to prevent concurrent access to
|
||||
// the same db by multiple processes. On failure, stores nullptr in
|
||||
@@ -129,8 +124,7 @@ public:
|
||||
Status UnlockFile(FileLock* lock);
|
||||
|
||||
// Get full directory name for this db.
|
||||
Status GetAbsolutePath(const std::string& db_path,
|
||||
std::string* output_path);
|
||||
Status GetAbsolutePath(const std::string& db_path, std::string* output_path);
|
||||
|
||||
// Generate unique id
|
||||
std::string GenerateUniqueId();
|
||||
@@ -142,31 +136,29 @@ public:
|
||||
const std::string& config_path,
|
||||
const std::string& db_pool);
|
||||
|
||||
explicit EnvLibrados(const std::string& client_name, // first 3 parameters are for RADOS client init
|
||||
const std::string& cluster_name,
|
||||
const uint64_t flags,
|
||||
const std::string& db_name,
|
||||
const std::string& config_path,
|
||||
const std::string& db_pool,
|
||||
const std::string& wal_dir,
|
||||
const std::string& wal_pool,
|
||||
const uint64_t write_buffer_size);
|
||||
~EnvLibrados() {
|
||||
_rados.shutdown();
|
||||
}
|
||||
private:
|
||||
explicit EnvLibrados(
|
||||
const std::string& client_name, // first 3 parameters are
|
||||
// for RADOS client init
|
||||
const std::string& cluster_name, const uint64_t flags,
|
||||
const std::string& db_name, const std::string& config_path,
|
||||
const std::string& db_pool, const std::string& wal_dir,
|
||||
const std::string& wal_pool, const uint64_t write_buffer_size);
|
||||
~EnvLibrados() { _rados.shutdown(); }
|
||||
|
||||
private:
|
||||
std::string _client_name;
|
||||
std::string _cluster_name;
|
||||
uint64_t _flags;
|
||||
std::string _db_name; // get from user, readable string; Also used as db_id for db metadata
|
||||
std::string _db_name; // get from user, readable string; Also used as db_id
|
||||
// for db metadata
|
||||
std::string _config_path;
|
||||
librados::Rados _rados; // RADOS client
|
||||
librados::Rados _rados; // RADOS client
|
||||
std::string _db_pool_name;
|
||||
librados::IoCtx _db_pool_ioctx; // IoCtx for connecting db_pool
|
||||
std::string _wal_dir; // WAL dir path
|
||||
librados::IoCtx _db_pool_ioctx; // IoCtx for connecting db_pool
|
||||
std::string _wal_dir; // WAL dir path
|
||||
std::string _wal_pool_name;
|
||||
librados::IoCtx _wal_pool_ioctx; // IoCtx for connecting wal_pool
|
||||
uint64_t _write_buffer_size; // WritableFile buffer max size
|
||||
librados::IoCtx _wal_pool_ioctx; // IoCtx for connecting wal_pool
|
||||
uint64_t _write_buffer_size; // WritableFile buffer max size
|
||||
|
||||
/* private function to communicate with rados */
|
||||
std::string _CreateFid();
|
||||
@@ -175,10 +167,8 @@ private:
|
||||
Status _RenameFid(const std::string& old_fname, const std::string& new_fname);
|
||||
Status _AddFid(const std::string& fname, const std::string& fid);
|
||||
Status _DelFid(const std::string& fname);
|
||||
Status _GetSubFnames(
|
||||
const std::string& dirname,
|
||||
std::vector<std::string> * result
|
||||
);
|
||||
Status _GetSubFnames(const std::string& dirname,
|
||||
std::vector<std::string>* result);
|
||||
librados::IoCtx* _GetIoctx(const std::string& prefix);
|
||||
friend class LibradosWritableFile;
|
||||
};
|
||||
|
||||
@@ -15,10 +15,9 @@
|
||||
// semantics and behavior are correct (in that they match that of an
|
||||
// existing, stable Env, like the default POSIX one).
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
#pragma once
|
||||
|
||||
#ifndef STORAGE_ROCKSDB_INCLUDE_UTILITIES_ENVMIRROR_H_
|
||||
#define STORAGE_ROCKSDB_INCLUDE_UTLIITIES_ENVMIRROR_H_
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
@@ -174,6 +173,4 @@ class EnvMirror : public EnvWrapper {
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
#endif // STORAGE_ROCKSDB_INCLUDE_UTILITIES_ENVMIRROR_H_
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -209,10 +209,11 @@ class Transaction {
|
||||
// or other errors if this key could not be read.
|
||||
virtual Status GetForUpdate(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice& key, std::string* value) = 0;
|
||||
const Slice& key, std::string* value,
|
||||
bool exclusive = true) = 0;
|
||||
|
||||
virtual Status GetForUpdate(const ReadOptions& options, const Slice& key,
|
||||
std::string* value) = 0;
|
||||
std::string* value, bool exclusive = true) = 0;
|
||||
|
||||
virtual std::vector<Status> MultiGetForUpdate(
|
||||
const ReadOptions& options,
|
||||
@@ -401,10 +402,10 @@ class Transaction {
|
||||
|
||||
virtual bool IsDeadlockDetect() const { return false; }
|
||||
|
||||
virtual TransactionID GetWaitingTxn(uint32_t* column_family_id,
|
||||
const std::string** key) const {
|
||||
virtual std::vector<TransactionID> GetWaitingTxns(uint32_t* column_family_id,
|
||||
std::string* key) const {
|
||||
assert(false);
|
||||
return 0;
|
||||
return std::vector<TransactionID>();
|
||||
}
|
||||
|
||||
enum TransactionState {
|
||||
|
||||
@@ -104,7 +104,8 @@ struct TransactionOptions {
|
||||
|
||||
struct KeyLockInfo {
|
||||
std::string key;
|
||||
TransactionID id;
|
||||
std::vector<TransactionID> ids;
|
||||
bool exclusive;
|
||||
};
|
||||
|
||||
class TransactionDB : public StackableDB {
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
#pragma once
|
||||
|
||||
#define ROCKSDB_MAJOR 5
|
||||
#define ROCKSDB_MINOR 0
|
||||
#define ROCKSDB_PATCH 0
|
||||
#define ROCKSDB_MINOR 1
|
||||
#define ROCKSDB_PATCH 5
|
||||
|
||||
// Do not use these. We made the mistake of declaring macros starting with
|
||||
// double underscore. Now we have to live with our choice. We'll deprecate these
|
||||
|
||||
@@ -1417,12 +1417,18 @@ public class DbBenchmark {
|
||||
}
|
||||
},
|
||||
/* TODO(yhchiang): enable the following
|
||||
bufferedio(rocksdb::EnvOptions().use_os_buffer,
|
||||
"Allow buffered io using OS buffers.") {
|
||||
direct_reads(rocksdb::EnvOptions().use_direct_reads,
|
||||
"Allow direct I/O reads.") {
|
||||
@Override public Object parseValue(String value) {
|
||||
return parseBoolean(value);
|
||||
}
|
||||
},
|
||||
},
|
||||
direct_writes(rocksdb::EnvOptions().use_direct_reads,
|
||||
"Allow direct I/O reads.") {
|
||||
@Override public Object parseValue(String value) {
|
||||
return parseBoolean(value);
|
||||
}
|
||||
},
|
||||
*/
|
||||
mmap_read(false,
|
||||
"Allow reads to occur via mmap-ing files.") {
|
||||
|
||||
@@ -49,23 +49,43 @@ void Java_org_rocksdb_EnvOptions_disposeInternal(JNIEnv *env, jobject jobj,
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_EnvOptions
|
||||
* Method: setUseOsBuffer
|
||||
* Method: setUseDirectReads
|
||||
* Signature: (JZ)V
|
||||
*/
|
||||
void Java_org_rocksdb_EnvOptions_setUseOsBuffer(JNIEnv *env, jobject jobj,
|
||||
jlong jhandle,
|
||||
jboolean use_os_buffer) {
|
||||
ENV_OPTIONS_SET_BOOL(jhandle, use_os_buffer);
|
||||
void Java_org_rocksdb_EnvOptions_setUseDirectReads(JNIEnv *env, jobject jobj,
|
||||
jlong jhandle,
|
||||
jboolean use_direct_reads) {
|
||||
ENV_OPTIONS_SET_BOOL(jhandle, use_direct_reads);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_EnvOptions
|
||||
* Method: useOsBuffer
|
||||
* Method: useDirectReads
|
||||
* Signature: (J)Z
|
||||
*/
|
||||
jboolean Java_org_rocksdb_EnvOptions_useOsBuffer(JNIEnv *env, jobject jobj,
|
||||
jlong jhandle) {
|
||||
return ENV_OPTIONS_GET(jhandle, use_os_buffer);
|
||||
jboolean Java_org_rocksdb_EnvOptions_useDirectReads(JNIEnv *env, jobject jobj,
|
||||
jlong jhandle) {
|
||||
return ENV_OPTIONS_GET(jhandle, use_direct_reads);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_EnvOptions
|
||||
* Method: setUseDirectWrites
|
||||
* Signature: (JZ)V
|
||||
*/
|
||||
void Java_org_rocksdb_EnvOptions_setUseDirectWrites(
|
||||
JNIEnv *env, jobject jobj, jlong jhandle, jboolean use_direct_writes) {
|
||||
ENV_OPTIONS_SET_BOOL(jhandle, use_direct_writes);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_EnvOptions
|
||||
* Method: useDirectWrites
|
||||
* Signature: (J)Z
|
||||
*/
|
||||
jboolean Java_org_rocksdb_EnvOptions_useDirectWrites(JNIEnv *env, jobject jobj,
|
||||
jlong jhandle) {
|
||||
return ENV_OPTIONS_GET(jhandle, use_direct_writes);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -110,47 +130,6 @@ jboolean Java_org_rocksdb_EnvOptions_useMmapWrites(JNIEnv *env, jobject jobj,
|
||||
return ENV_OPTIONS_GET(jhandle, use_mmap_writes);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_EnvOptions
|
||||
* Method: setUseDirectReads
|
||||
* Signature: (JZ)V
|
||||
*/
|
||||
void Java_org_rocksdb_EnvOptions_setUseDirectReads(JNIEnv *env, jobject jobj,
|
||||
jlong jhandle,
|
||||
jboolean use_direct_reads) {
|
||||
ENV_OPTIONS_SET_BOOL(jhandle, use_direct_reads);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_EnvOptions
|
||||
* Method: useDirectReads
|
||||
* Signature: (J)Z
|
||||
*/
|
||||
jboolean Java_org_rocksdb_EnvOptions_useDirectReads(JNIEnv *env, jobject jobj,
|
||||
jlong jhandle) {
|
||||
return ENV_OPTIONS_GET(jhandle, use_direct_reads);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_EnvOptions
|
||||
* Method: setUseDirectWrites
|
||||
* Signature: (JZ)V
|
||||
*/
|
||||
void Java_org_rocksdb_EnvOptions_setUseDirectWrites(
|
||||
JNIEnv *env, jobject jobj, jlong jhandle, jboolean use_direct_writes) {
|
||||
ENV_OPTIONS_SET_BOOL(jhandle, use_direct_writes);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_EnvOptions
|
||||
* Method: useDirectWrites
|
||||
* Signature: (J)Z
|
||||
*/
|
||||
jboolean Java_org_rocksdb_EnvOptions_useDirectWrites(JNIEnv *env, jobject jobj,
|
||||
jlong jhandle) {
|
||||
return ENV_OPTIONS_GET(jhandle, use_direct_writes);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_EnvOptions
|
||||
* Method: setAllowFallocate
|
||||
|
||||
+77
-31
@@ -870,27 +870,6 @@ void Java_org_rocksdb_Options_setManifestPreallocationSize(
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
* Method: allowOsBuffer
|
||||
* Signature: (J)Z
|
||||
*/
|
||||
jboolean Java_org_rocksdb_Options_allowOsBuffer(
|
||||
JNIEnv* env, jobject jobj, jlong jhandle) {
|
||||
return reinterpret_cast<rocksdb::Options*>(jhandle)->allow_os_buffer;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
* Method: setAllowOsBuffer
|
||||
* Signature: (JZ)V
|
||||
*/
|
||||
void Java_org_rocksdb_Options_setAllowOsBuffer(
|
||||
JNIEnv* env, jobject jobj, jlong jhandle, jboolean allow_os_buffer) {
|
||||
reinterpret_cast<rocksdb::Options*>(jhandle)->allow_os_buffer =
|
||||
static_cast<bool>(allow_os_buffer);
|
||||
}
|
||||
|
||||
/*
|
||||
* Method: setTableFactory
|
||||
* Signature: (JJ)V
|
||||
@@ -943,6 +922,50 @@ void Java_org_rocksdb_Options_setAllowMmapWrites(
|
||||
static_cast<bool>(allow_mmap_writes);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
* Method: useDirectReads
|
||||
* Signature: (J)Z
|
||||
*/
|
||||
jboolean Java_org_rocksdb_Options_useDirectReads(JNIEnv* env, jobject jobj,
|
||||
jlong jhandle) {
|
||||
return reinterpret_cast<rocksdb::Options*>(jhandle)->use_direct_reads;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
* Method: setUseDirectReads
|
||||
* Signature: (JZ)V
|
||||
*/
|
||||
void Java_org_rocksdb_Options_setUseDirectReads(JNIEnv* env, jobject jobj,
|
||||
jlong jhandle,
|
||||
jboolean use_direct_reads) {
|
||||
reinterpret_cast<rocksdb::Options*>(jhandle)->use_direct_reads =
|
||||
static_cast<bool>(use_direct_reads);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
* Method: useDirectWrites
|
||||
* Signature: (J)Z
|
||||
*/
|
||||
jboolean Java_org_rocksdb_Options_useDirectWrites(JNIEnv* env, jobject jobj,
|
||||
jlong jhandle) {
|
||||
return reinterpret_cast<rocksdb::Options*>(jhandle)->use_direct_writes;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
* Method: setUseDirectReads
|
||||
* Signature: (JZ)V
|
||||
*/
|
||||
void Java_org_rocksdb_Options_setUseDirectWrites(JNIEnv* env, jobject jobj,
|
||||
jlong jhandle,
|
||||
jboolean use_direct_writes) {
|
||||
reinterpret_cast<rocksdb::Options*>(jhandle)->use_direct_writes =
|
||||
static_cast<bool>(use_direct_writes);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
* Method: isFdCloseOnExec
|
||||
@@ -4144,23 +4167,46 @@ jlong Java_org_rocksdb_DBOptions_manifestPreallocationSize(
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_DBOptions
|
||||
* Method: setAllowOsBuffer
|
||||
* Signature: (JZ)V
|
||||
* Method: useDirectReads
|
||||
* Signature: (J)Z
|
||||
*/
|
||||
void Java_org_rocksdb_DBOptions_setAllowOsBuffer(
|
||||
JNIEnv* env, jobject jobj, jlong jhandle, jboolean allow_os_buffer) {
|
||||
reinterpret_cast<rocksdb::DBOptions*>(jhandle)->allow_os_buffer =
|
||||
static_cast<bool>(allow_os_buffer);
|
||||
jboolean Java_org_rocksdb_DBOptions_useDirectReads(JNIEnv* env, jobject jobj,
|
||||
jlong jhandle) {
|
||||
return reinterpret_cast<rocksdb::DBOptions*>(jhandle)->use_direct_reads;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_DBOptions
|
||||
* Method: allowOsBuffer
|
||||
* Method: setUseDirectReads
|
||||
* Signature: (JZ)V
|
||||
*/
|
||||
void Java_org_rocksdb_DBOptions_setUseDirectReads(JNIEnv* env, jobject jobj,
|
||||
jlong jhandle,
|
||||
jboolean use_direct_reads) {
|
||||
reinterpret_cast<rocksdb::DBOptions*>(jhandle)->use_direct_reads =
|
||||
static_cast<bool>(use_direct_reads);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_DBOptions
|
||||
* Method: useDirectWrites
|
||||
* Signature: (J)Z
|
||||
*/
|
||||
jboolean Java_org_rocksdb_DBOptions_allowOsBuffer(
|
||||
JNIEnv* env, jobject jobj, jlong jhandle) {
|
||||
return reinterpret_cast<rocksdb::DBOptions*>(jhandle)->allow_os_buffer;
|
||||
jboolean Java_org_rocksdb_DBOptions_useDirectWrites(JNIEnv* env, jobject jobj,
|
||||
jlong jhandle) {
|
||||
return reinterpret_cast<rocksdb::DBOptions*>(jhandle)->use_direct_writes;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_DBOptions
|
||||
* Method: setUseDirectReads
|
||||
* Signature: (JZ)V
|
||||
*/
|
||||
void Java_org_rocksdb_DBOptions_setUseDirectWrites(JNIEnv* env, jobject jobj,
|
||||
jlong jhandle,
|
||||
jboolean use_direct_writes) {
|
||||
reinterpret_cast<rocksdb::DBOptions*>(jhandle)->use_direct_writes =
|
||||
static_cast<bool>(use_direct_writes);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#define JAVA_ROCKSJNI_PORTAL_H_
|
||||
|
||||
#include <jni.h>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -119,8 +120,8 @@ class StatusJni : public RocksDBNativeClass<rocksdb::Status*, StatusJni> {
|
||||
|
||||
jstring jstate = nullptr;
|
||||
if (status.getState() != nullptr) {
|
||||
std::string s(status.getState());
|
||||
jstate = env->NewStringUTF(s.c_str());
|
||||
const char* const state = status.getState();
|
||||
jstate = env->NewStringUTF(state);
|
||||
}
|
||||
return env->NewObject(getJClass(env), mid, toJavaStatusCode(status.code()),
|
||||
toJavaStatusSubCode(status.subcode()), jstate);
|
||||
@@ -159,7 +160,7 @@ class StatusJni : public RocksDBNativeClass<rocksdb::Status*, StatusJni> {
|
||||
case rocksdb::Status::Code::kTryAgain:
|
||||
return 0xD;
|
||||
default:
|
||||
return 0xFF; // undefined
|
||||
return 0x7F; // undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,9 +177,9 @@ class StatusJni : public RocksDBNativeClass<rocksdb::Status*, StatusJni> {
|
||||
case rocksdb::Status::SubCode::kLockLimit:
|
||||
return 0x3;
|
||||
case rocksdb::Status::SubCode::kMaxSubCode:
|
||||
return 0xFE;
|
||||
return 0x7E;
|
||||
default:
|
||||
return 0xFF; // undefined
|
||||
return 0x7F; // undefined
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Loaded 100 of 219 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user